import { act } from "react"; import { createRoot, type Root } from "react-dom/client"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { DevicePanel } from "../src/browser/DevicePanel.js"; declare global { // eslint-disable-next-line no-var var IS_REACT_ACT_ENVIRONMENT: boolean | undefined; } const apiBaseUrl = "https://ekklesia.example.test"; let container: HTMLDivElement; let root: Root; beforeEach(() => { globalThis.IS_REACT_ACT_ENVIRONMENT = true; container = document.createElement("div"); document.body.append(container); root = createRoot(container); }); afterEach(async () => { await act(async () => root.unmount()); container.remove(); }); function json(body: unknown, status = 200): Response { return new Response(JSON.stringify(body), { headers: { "content-type": "application/json" }, status, }); } function server() { let revoked = false; const revokes: string[] = []; const fetch = vi.fn(async (input: RequestInfo | URL) => { const url = String(input); if (url.endsWith("/api/v1/devices")) { return json({ devices: [{ id: "device-1", name: "Foyer TV", permissions: [], revoked }], }); } if (url.endsWith("/api/v1/devices/device-1/revoke")) { revoked = true; revokes.push(url); return new Response(null, { status: 204 }); } return json({ code: "NOT_FOUND" }, 404); }) as unknown as typeof globalThis.fetch; return { fetch, revokes }; } function query(testId: string): HTMLElement | null { return container.querySelector(`[data-testid="${testId}"]`); } async function click(testId: string): Promise { await act(async () => { query(testId)?.click(); for (let index = 0; index < 5; index += 1) { await new Promise((resolve) => setTimeout(resolve, 0)); } }); } /** * Revoking a display strands that screen until someone re-pairs it in person, so the panel explains * the consequence in its own dialog first — never with a browser-native confirm (DESIGN §21). */ describe("display device revocation", () => { it("asks in a product dialog before revoking, and does nothing on cancel", async () => { const platform = server(); const confirm = vi.fn(() => true); Object.assign(window, { confirm }); await act(async () => { root.render(); }); await click("device-revoke-device-1"); const dialog = container.querySelector('[role="dialog"]'); expect(dialog?.textContent).toContain("Revoke Foyer TV?"); expect(dialog?.textContent).toContain("pairing code"); expect(platform.revokes).toEqual([]); await click("device-revoke-cancel"); expect(container.querySelector('[role="dialog"]')).toBeNull(); expect(platform.revokes).toEqual([]); expect(confirm).not.toHaveBeenCalled(); }); it("revokes only after the destructive action is confirmed", async () => { const platform = server(); await act(async () => { root.render(); }); await click("device-revoke-device-1"); await click("device-revoke-confirm"); expect(platform.revokes).toHaveLength(1); expect(container.querySelector('[role="dialog"]')).toBeNull(); expect(query("device-device-1")?.textContent).toContain("(revoked)"); }); });