import { beforeEach, afterEach, describe, expect, it, vi } from "vitest"; import { startMDNSListener } from "./chirp-pair-capacitor"; interface RegisteredListener { event: string; handler: (data: unknown) => void; } function installMockCapacitor(opts: { withSyncPlugin?: boolean; withPeerDiscoveredEvent?: boolean; appActive?: boolean; } = {}): { listeners: RegisteredListener[]; removeCount: { v: number } } { const listeners: RegisteredListener[] = []; const removeCount = { v: 0 }; const syncPlugin = opts.withSyncPlugin === false ? undefined : { startPairingServer: vi.fn(), stopPairingServer: vi.fn(), sendFrame: vi.fn(), addListener: opts.withPeerDiscoveredEvent === false ? undefined : vi.fn((event: string, handler: (data: unknown) => void) => { listeners.push({ event, handler }); return { remove: () => { removeCount.v++; } }; }), }; const appPlugin = { getState: vi.fn(async () => ({ isActive: opts.appActive ?? true })), }; (globalThis as { Capacitor?: unknown }).Capacitor = { isNativePlatform: () => true, Plugins: { SyncPlugin: syncPlugin, App: appPlugin }, }; return { listeners, removeCount }; } describe("startMDNSListener", () => { let sentMessages: Array<{ type: string; payload?: unknown }>; beforeEach(() => { sentMessages = []; (globalThis as { chrome?: unknown }).chrome = { runtime: { sendMessage: vi.fn(async (msg: { type: string; payload?: unknown }) => { sentMessages.push(msg); }), }, }; }); afterEach(() => { delete (globalThis as { Capacitor?: unknown }).Capacitor; delete (globalThis as { chrome?: unknown }).chrome; }); it("is a no-op when the SyncPlugin is unavailable", () => { installMockCapacitor({ withSyncPlugin: false }); const stop = startMDNSListener(); expect(typeof stop).toBe("function"); stop(); }); it("registers a peerDiscovered listener when the plugin exposes addListener", () => { const { listeners } = installMockCapacitor(); startMDNSListener(); expect(listeners.length).toBe(1); expect(listeners[0].event).toBe("peerDiscovered"); }); it("fires TRIGGER_SYNC with reason 'mdns' when peerDiscovered arrives in the foreground", async () => { const { listeners } = installMockCapacitor({ appActive: true }); startMDNSListener(); listeners[0].handler({ deviceId: "pixel-7", ip: "192.168.1.42", port: 8765 }); // sendMessage runs inside the handler's `await`; let microtasks flush. await new Promise((r) => setTimeout(r, 0)); expect(sentMessages).toEqual([ { type: "TRIGGER_SYNC", payload: { reason: "mdns" } }, ]); }); it("does NOT fire TRIGGER_SYNC when the app is backgrounded", async () => { const { listeners } = installMockCapacitor({ appActive: false }); startMDNSListener(); listeners[0].handler({ deviceId: "pixel-7", ip: "192.168.1.42", port: 8765 }); await new Promise((r) => setTimeout(r, 0)); expect(sentMessages).toEqual([]); }); it("ignores malformed events (missing deviceId or ip)", async () => { const { listeners } = installMockCapacitor({ appActive: true }); startMDNSListener(); listeners[0].handler({ ip: "192.168.1.42" }); listeners[0].handler({ deviceId: "pixel-7" }); listeners[0].handler(null); await new Promise((r) => setTimeout(r, 0)); expect(sentMessages).toEqual([]); }); it("returns a teardown that removes the native listener", () => { const { removeCount } = installMockCapacitor(); const stop = startMDNSListener(); stop(); expect(removeCount.v).toBe(1); }); });