import { act, fireEvent, render, screen } from "@testing-library/react";
import { useState } from "react";
import { MemoryRouter, useNavigate } from "react-router-dom";
import { afterEach, describe, expect, it, vi } from "vitest";
import { AppSidebarProvider, useAppSidebar } from "@/contexts/AppSidebarContext";
const Harness = () => {
const navigate = useNavigate();
const {
attachmentSection,
setAttachmentSection,
inboxFilter,
setInboxFilter,
mobileOpen,
setMobileOpen,
closeMobileThen,
completeMobileClose,
} = useAppSidebar();
const [actionCount, setActionCount] = useState(0);
return (
);
};
describe("AppSidebarProvider", () => {
afterEach(() => {
vi.unstubAllGlobals();
});
it("keeps page controls local and resets them after a route change", () => {
render(
,
);
fireEvent.click(screen.getByRole("button", { name: "Select media" }));
fireEvent.click(screen.getByRole("button", { name: "Select unread" }));
expect(screen.getByTestId("attachment-section")).toHaveTextContent("media");
expect(screen.getByTestId("inbox-filter")).toHaveTextContent("unread");
fireEvent.click(screen.getByRole("button", { name: "Change route" }));
expect(screen.getByTestId("attachment-section")).toHaveTextContent("all");
expect(screen.getByTestId("inbox-filter")).toHaveTextContent("all");
});
it("defers sidebar actions until the mobile close transition finishes", () => {
let frameCallback: FrameRequestCallback | undefined;
vi.stubGlobal(
"requestAnimationFrame",
vi.fn((callback: FrameRequestCallback) => {
frameCallback = callback;
return 1;
}),
);
vi.stubGlobal("cancelAnimationFrame", vi.fn());
render(
,
);
fireEvent.click(screen.getByRole("button", { name: "Open mobile sidebar" }));
fireEvent.click(screen.getByRole("button", { name: "Run after close" }));
expect(screen.getByTestId("mobile-open")).toHaveTextContent("false");
expect(screen.getByTestId("action-count")).toHaveTextContent("0");
fireEvent.click(screen.getByRole("button", { name: "Finish mobile close" }));
expect(screen.getByTestId("action-count")).toHaveTextContent("0");
act(() => frameCallback?.(0));
expect(screen.getByTestId("action-count")).toHaveTextContent("1");
fireEvent.click(screen.getByRole("button", { name: "Run after close" }));
expect(screen.getByTestId("action-count")).toHaveTextContent("2");
});
it("cancels a pending mobile action when the route changes", () => {
const frameCallbacks = new Map();
let nextFrame = 0;
vi.stubGlobal(
"requestAnimationFrame",
vi.fn((callback: FrameRequestCallback) => {
nextFrame += 1;
frameCallbacks.set(nextFrame, callback);
return nextFrame;
}),
);
vi.stubGlobal(
"cancelAnimationFrame",
vi.fn((frame: number) => frameCallbacks.delete(frame)),
);
render(
,
);
fireEvent.click(screen.getByRole("button", { name: "Open mobile sidebar" }));
fireEvent.click(screen.getByRole("button", { name: "Run after close" }));
fireEvent.click(screen.getByRole("button", { name: "Change route" }));
fireEvent.click(screen.getByRole("button", { name: "Finish mobile close" }));
act(() => frameCallbacks.forEach((callback) => callback(0)));
expect(screen.getByTestId("action-count")).toHaveTextContent("0");
});
});