chirp-pair-capacitor.test.ts

3.7 KB · raw

1import { beforeEach, afterEach, describe, expect, it, vi } from "vitest";2import { startMDNSListener } from "./chirp-pair-capacitor";34interface RegisteredListener {5  event: string;6  handler: (data: unknown) => void;7}89function installMockCapacitor(opts: {10  withSyncPlugin?: boolean;11  withPeerDiscoveredEvent?: boolean;12  appActive?: boolean;13} = {}): { listeners: RegisteredListener[]; removeCount: { v: number } } {14  const listeners: RegisteredListener[] = [];15  const removeCount = { v: 0 };16  const syncPlugin = opts.withSyncPlugin === false ? undefined : {17    startPairingServer: vi.fn(),18    stopPairingServer: vi.fn(),19    sendFrame: vi.fn(),20    addListener: opts.withPeerDiscoveredEvent === false21      ? undefined22      : vi.fn((event: string, handler: (data: unknown) => void) => {23          listeners.push({ event, handler });24          return { remove: () => { removeCount.v++; } };25        }),26  };27  const appPlugin = {28    getState: vi.fn(async () => ({ isActive: opts.appActive ?? true })),29  };30  (globalThis as { Capacitor?: unknown }).Capacitor = {31    isNativePlatform: () => true,32    Plugins: { SyncPlugin: syncPlugin, App: appPlugin },33  };34  return { listeners, removeCount };35}3637describe("startMDNSListener", () => {38  let sentMessages: Array<{ type: string; payload?: unknown }>;3940  beforeEach(() => {41    sentMessages = [];42    (globalThis as { chrome?: unknown }).chrome = {43      runtime: {44        sendMessage: vi.fn(async (msg: { type: string; payload?: unknown }) => {45          sentMessages.push(msg);46        }),47      },48    };49  });5051  afterEach(() => {52    delete (globalThis as { Capacitor?: unknown }).Capacitor;53    delete (globalThis as { chrome?: unknown }).chrome;54  });5556  it("is a no-op when the SyncPlugin is unavailable", () => {57    installMockCapacitor({ withSyncPlugin: false });58    const stop = startMDNSListener();59    expect(typeof stop).toBe("function");60    stop();61  });6263  it("registers a peerDiscovered listener when the plugin exposes addListener", () => {64    const { listeners } = installMockCapacitor();65    startMDNSListener();66    expect(listeners.length).toBe(1);67    expect(listeners[0].event).toBe("peerDiscovered");68  });6970  it("fires TRIGGER_SYNC with reason 'mdns' when peerDiscovered arrives in the foreground", async () => {71    const { listeners } = installMockCapacitor({ appActive: true });72    startMDNSListener();73    listeners[0].handler({ deviceId: "pixel-7", ip: "192.168.1.42", port: 8765 });74    // sendMessage runs inside the handler's `await`; let microtasks flush.75    await new Promise((r) => setTimeout(r, 0));76    expect(sentMessages).toEqual([77      {78        type: "TRIGGER_SYNC",79        payload: { reason: "mdns", deviceId: "pixel-7", discoveredIp: "192.168.1.42", discoveredPort: 8765 },80      },81    ]);82  });8384  it("does NOT fire TRIGGER_SYNC when the app is backgrounded", async () => {85    const { listeners } = installMockCapacitor({ appActive: false });86    startMDNSListener();87    listeners[0].handler({ deviceId: "pixel-7", ip: "192.168.1.42", port: 8765 });88    await new Promise((r) => setTimeout(r, 0));89    expect(sentMessages).toEqual([]);90  });9192  it("ignores malformed events (missing deviceId or ip)", async () => {93    const { listeners } = installMockCapacitor({ appActive: true });94    startMDNSListener();95    listeners[0].handler({ ip: "192.168.1.42" });96    listeners[0].handler({ deviceId: "pixel-7" });97    listeners[0].handler(null);98    await new Promise((r) => setTimeout(r, 0));99    expect(sentMessages).toEqual([]);100  });101102  it("returns a teardown that removes the native listener", () => {103    const { removeCount } = installMockCapacitor();104    const stop = startMDNSListener();105    stop();106    expect(removeCount.v).toBe(1);107  });108});