1import { describe, expect, it, vi } from "vite-plus/test";
2
3import { act, fakeTimers, render } from "../../test/render";
4import { LIVE_REFRESH_MIN_INTERVAL_MS, useLiveRefresh } from "./useLiveRefresh";
5
6function Probe(props: {
7 readonly refresh: () => void;
8 readonly viewKey: string;
9 readonly enabled?: boolean;
10 readonly intervalMs?: number | null;
11 readonly refreshOnShow?: boolean;
12}) {
13 useLiveRefresh(props.refresh, {
14 key: props.viewKey,
15 enabled: props.enabled ?? true,
16 ...(props.intervalMs !== undefined ? { intervalMs: props.intervalMs } : {}),
17 ...(props.refreshOnShow !== undefined ? { refreshOnShow: props.refreshOnShow } : {}),
18 });
19 return null;
20}
21
22describe("useLiveRefresh", () => {
23 it("with refreshOnShow, reads on every show unless it read within the floor", async () => {
24 const refresh = vi.fn();
25 // The first enable reads the clock.
26 using _timers = fakeTimers({ now: 1_000_000 });
27 const probe = (enabled: boolean) => (
28 <Probe refresh={refresh} viewKey="show" enabled={enabled} refreshOnShow intervalMs={null} />
29 );
30 // A bare hook probe; nothing here navigates.
31 await using screen = await render(probe(true), { router: false });
32 expect(refresh).toHaveBeenCalledTimes(1);
33
34 await screen.rerender(probe(false));
35 await act(() => vi.advanceTimersByTime(LIVE_REFRESH_MIN_INTERVAL_MS - 1));
36 await screen.rerender(probe(true));
37 expect(refresh).toHaveBeenCalledTimes(1);
38
39 await screen.rerender(probe(false));
40 await act(() => vi.advanceTimersByTime(1));
41 await screen.rerender(probe(true));
42 expect(refresh).toHaveBeenCalledTimes(2);
43 });
44
45 it("with intervalMs null, never refreshes on a timer", async () => {
46 const periodic = vi.fn();
47 const untimed = vi.fn();
48 // The interaction clock starts when the first view mounts.
49 using _timers = fakeTimers({ now: 1_000_000 });
50 await using _screen = await render(
51 <>
52 <Probe refresh={periodic} viewKey="periodic" intervalMs={60_000} />
53 <Probe refresh={untimed} viewKey="untimed" intervalMs={null} />
54 </>,
55 // A bare hook probe; nothing here navigates.
56 { router: false },
57 );
58
59 // Past the default 5 minute interval, but inside the 6 minute idle cutoff.
60 await act(() => vi.advanceTimersByTime(5.5 * 60_000));
61
62 expect(periodic).toHaveBeenCalledTimes(5);
63 expect(untimed).not.toHaveBeenCalled();
64 });
65});