1import { act, useLayoutEffect } from "react";
2import { create, type ReactTestRenderer } from "react-test-renderer";
3import { afterEach, beforeEach, describe, expect, it, vi } from "vite-plus/test";
4
5import { usePanelNavigationSuppression } from "./panelAnimations";
6
7let renderer: ReactTestRenderer | null = null;
8let pendingFrames: FrameRequestCallback[] = [];
9let observed: boolean[] = [];
10
11function SuppressionProbe({ navigationKey }: { navigationKey: string }) {
12 const suppressed = usePanelNavigationSuppression(navigationKey);
13 useLayoutEffect(() => {
14 observed.push(suppressed);
15 }, [suppressed]);
16 return null;
17}
18
19beforeEach(() => {
20 pendingFrames = [];
21 observed = [];
22 vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT", true);
23 vi.stubGlobal("window", {
24 requestAnimationFrame: vi.fn((callback: FrameRequestCallback) => {
25 pendingFrames.push(callback);
26 return pendingFrames.length;
27 }),
28 cancelAnimationFrame: vi.fn(),
29 });
30});
31
32afterEach(async () => {
33 await act(() => renderer?.unmount());
34 vi.unstubAllGlobals();
35});
36
37async function paintPendingFrame() {
38 const callback = pendingFrames.shift();
39 await act(() => callback?.(0));
40}
41
42describe("usePanelNavigationSuppression", () => {
43 it("suppresses initial and navigated panel state until each route has painted", async () => {
44 await act(() => {
45 renderer = create(<SuppressionProbe navigationKey="/thread/one" />);
46 });
47 expect(observed.at(-1)).toBe(true);
48
49 await paintPendingFrame();
50 expect(observed.at(-1)).toBe(true);
51 await paintPendingFrame();
52 expect(observed.at(-1)).toBe(false);
53
54 await act(() => {
55 renderer?.update(<SuppressionProbe navigationKey="/thread/two" />);
56 });
57 expect(observed.at(-1)).toBe(true);
58
59 await paintPendingFrame();
60 expect(observed.at(-1)).toBe(true);
61 await paintPendingFrame();
62 expect(observed.at(-1)).toBe(false);
63 });
64});