import { afterEach, describe, expect, it, vi } from "vitest"; import { connectAuthoritativeDualScreen } from "./authoritativeDualScreenRuntime"; import { useUiLayoutStore } from "../game/uiLayoutStore"; import { useDpsMeterStore } from "../game/dpsMeterStore"; import { emptyDpsMeter } from "../game/dpsMeter"; import type { DualScreenMessage } from "./dualScreenSync"; describe("dedicated companion DPS synchronization", () => { afterEach(() => { useUiLayoutStore.getState().finish(false); useDpsMeterStore.setState(emptyDpsMeter(), true); vi.unstubAllGlobals(); vi.useRealTimers(); }); it("publishes meter ticks and layout drafts, then unsubscribes on disconnect", () => { vi.useFakeTimers(); vi.setSystemTime(1_000); vi.stubGlobal("window", { setTimeout, clearTimeout, location: { search: "?display=top" } }); const postMessage = vi.fn<(message: DualScreenMessage) => void>(); const channel = { postMessage, onmessage: null } as unknown as BroadcastChannel; const disconnect = connectAuthoritativeDualScreen(channel, { data: { type: "companion-ready" } } as MessageEvent); try { useUiLayoutStore.getState().begin(); useUiLayoutStore.getState().updateDraft((layout) => ({ ...layout, dpsMeterDisplay: "companion", elements: { ...layout.elements, dps: { visible: false, position: { x: .4, y: .6 }, width: 300 }, } })); vi.advanceTimersByTime(100); let latest = postMessage.mock.lastCall?.[0]; expect(latest?.type).toBe("app-state"); if (latest?.type !== "app-state") throw new Error("Missing companion snapshot"); expect(latest.snapshot.game.dpsMeter).toMatchObject({ display: "companion", layout: { visible: false, position: { x: .4, y: .6 }, width: 300, } }); useDpsMeterStore.setState({ startedAt: 1_000, endedAt: null, updatedAt: 3_000, elapsedMs: 2_000, damage: { test: { id: "test", name: "Test", color: "#fff", total: 200 } } }, true); vi.advanceTimersByTime(100); latest = postMessage.mock.lastCall?.[0]; if (latest?.type !== "app-state") throw new Error("Missing DPS snapshot"); expect(latest.snapshot.game.dpsMeter.values).toMatchObject({ totalDamage: 200, totalDps: 100, durationSeconds: 2 }); } finally { disconnect(); } postMessage.mockClear(); useUiLayoutStore.getState().finish(false); useDpsMeterStore.setState(emptyDpsMeter(), true); vi.advanceTimersByTime(100); expect(postMessage).not.toHaveBeenCalled(); }); });