import type { Dispatch } from "@deadhead/schema"; import { focusManager } from "@tanstack/react-query"; import { act, screen, waitFor } from "@testing-library/react"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { useClock } from "../src/clock/clock"; import { fakeApi, renderApp, samplePlayerState } from "./render"; const NOW = Date.parse("2100-01-01T00:00:00Z"); function inFlight(arrivalAt: number): Dispatch { return { id: "d1", shipId: "loaner", pilotId: "avatar", contract: null, originId: "earth", destinationId: "moon", tutorial: true, departureAt: NOW - 60_000, arrivalAt, distanceMillionKm: 0.39, fuelCost: 0, fuelBurnedFu: 0, expectedPayout: 240, seed: 1, status: "in-flight", resolvedAt: null, }; } const stateCalls = (api: ReturnType) => api.call.mock.calls.filter(([name]) => name === "getPlayerState").length; describe("player state store", () => { beforeEach(() => { // Fake only the clock and the 1 s ticker; leave setTimeout real for React Query. vi.useFakeTimers({ toFake: ["Date", "setInterval", "clearInterval"] }); vi.setSystemTime(NOW); useClock.setState({ offsetMs: 0, synced: false, now: NOW }); }); afterEach(() => vi.useRealTimers()); it("refetches automatically when the clock passes an in-flight arrival", async () => { const api = fakeApi({ getServerTime: () => ({ realTime: Date.now(), gameTime: 0 }), getPlayerState: () => samplePlayerState({ activeDispatches: [inFlight(NOW + 30_000)] }), }); renderApp(api); await screen.findByRole("heading", { level: 1, name: "Bridge" }); expect(stateCalls(api)).toBe(1); act(() => vi.advanceTimersByTime(29_000)); expect(stateCalls(api)).toBe(1); act(() => vi.advanceTimersByTime(2_000)); await waitFor(() => expect(stateCalls(api)).toBe(2)); // The same arrival does not trigger again on later ticks. act(() => vi.advanceTimersByTime(5_000)); expect(stateCalls(api)).toBe(2); }); }); describe("player state store on focus", () => { afterEach(() => focusManager.setFocused(undefined)); it("refetches when the app regains focus", async () => { const api = fakeApi({ getPlayerState: () => samplePlayerState() }); renderApp(api); await screen.findByRole("heading", { level: 1, name: "Bridge" }); const before = stateCalls(api); // Leaving and returning to the app, as the browser reports it to TanStack Query. act(() => { focusManager.setFocused(false); focusManager.setFocused(true); }); await waitFor(() => expect(stateCalls(api)).toBeGreaterThan(before)); }); });