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 (
{attachmentSection} {inboxFilter} {String(mobileOpen)} {actionCount}
); }; 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"); }); });