// @vitest-environment jsdom import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { DEFAULT_RELAY_URL } from "../../shared/rendezvous"; import { flushView, inputText, renderView } from "../views/view-test-utils"; const sendMessage = vi.hoisted(() => vi.fn()); vi.mock("../../shared/bridge", () => ({ sendMessage })); import { RELAY_DISCLOSURE, SyncModeSelector } from "./SyncModeSelector"; let settings = { mode: "public" as "public" | "private" | "off", relayUrl: DEFAULT_RELAY_URL }; let disposeView: (() => void) | null = null; beforeEach(() => { settings = { mode: "public", relayUrl: DEFAULT_RELAY_URL }; sendMessage.mockReset(); sendMessage.mockImplementation(async (msg: { type: string; payload?: Record }) => { if (msg.type === "GET_SYNC_SETTINGS") return { ok: true, data: settings }; if (msg.type === "SET_SYNC_SETTINGS") { const mode = msg.payload?.mode as typeof settings.mode | undefined; if (mode) { settings = { mode, relayUrl: mode === "public" ? DEFAULT_RELAY_URL : settings.relayUrl }; } else { const url = String(msg.payload?.relayUrl ?? ""); if (!url.startsWith("wss://")) return { ok: false, error: "invalid_relay_url" }; settings = { mode: url === DEFAULT_RELAY_URL ? "public" : "private", relayUrl: url }; } return { ok: true, data: settings }; } return { ok: false }; }); }); afterEach(() => { disposeView?.(); disposeView = null; }); async function mount(compact = false) { const view = renderView(() => ); disposeView = view.dispose; await flushView(); return view.container; } function optionNamed(container: HTMLElement, title: string): HTMLButtonElement { const match = [...container.querySelectorAll("button.peach-sync-mode")].find( (b) => b.querySelector(".peach-sync-mode-title")?.textContent === title, ); if (!match) throw new Error(`no option titled ${title}`); return match; } describe("sync mode selector", () => { it("offers exactly the three named choices as a radio group", async () => { const container = await mount(); const titles = [...container.querySelectorAll(".peach-sync-mode-title")].map((n) => n.textContent); expect(titles).toEqual(["Public relay", "Private relay", "No sync"]); expect(container.querySelector("[role='radiogroup']")).not.toBeNull(); expect(optionNamed(container, "Public relay").getAttribute("aria-checked")).toBe("true"); }); it("shows the disclosure once, whichever state is active", async () => { const container = await mount(); expect(container.querySelectorAll(".peach-sync-mode-disclosure")).toHaveLength(1); expect(container.querySelector(".peach-sync-mode-disclosure")?.textContent).toBe(RELAY_DISCLOSURE); optionNamed(container, "No sync").click(); await flushView(); expect(container.querySelector(".peach-sync-mode-disclosure")?.textContent).toBe(RELAY_DISCLOSURE); }); it("shows no URL field under public or off", async () => { const container = await mount(); expect(container.querySelector("#sync-relay-url")).toBeNull(); optionNamed(container, "No sync").click(); await flushView(); expect(container.querySelector("#sync-relay-url")).toBeNull(); }); it("flags private with no URL instead of silently using the public relay", async () => { const container = await mount(); optionNamed(container, "Private relay").click(); await flushView(); const field = container.querySelector("#sync-relay-url"); expect(field).not.toBeNull(); // Only the required secure scheme is pre-filled, never Peach's public host. expect(field!.value).toBe("wss://"); const warn = container.querySelector(".peach-sync-mode-warn"); expect(warn?.textContent).toContain("Sync is paused until you enter your relay URL"); }); it("clears the warning once a real relay is saved", async () => { const container = await mount(); optionNamed(container, "Private relay").click(); await flushView(); inputText(container.querySelector("#sync-relay-url")!, "wss://relay.example.com"); [...container.querySelectorAll("button")].find((b) => b.textContent === "save")!.click(); await flushView(); expect(container.querySelector(".peach-sync-mode-warn")).toBeNull(); expect(settings).toEqual({ mode: "private", relayUrl: "wss://relay.example.com" }); }); }); describe("compact form, used mid-pairing", () => { it("is a one-line summary until asked to change", async () => { const container = await mount(true); expect(container.querySelector(".peach-sync-mode-summary")?.textContent).toContain( "Using Public relay", ); expect(container.querySelector(".peach-sync-mode")).toBeNull(); container.querySelector(".peach-sync-mode-change")!.click(); await flushView(); expect(container.querySelectorAll(".peach-sync-mode")).toHaveLength(3); }); it("reports a committed change so the host can restart pairing", async () => { const onChange = vi.fn(); const view = renderView(() => ); disposeView = view.dispose; await flushView(); view.container.querySelector(".peach-sync-mode-change")!.click(); await flushView(); optionNamed(view.container, "No sync").click(); await flushView(); expect(onChange).toHaveBeenCalledWith({ mode: "off", relayUrl: DEFAULT_RELAY_URL }); }); });