diff --git a/tests/offscreen/handshake.test.ts b/tests/offscreen/handshake.test.ts new file mode 100644 index 0000000..72fe3ba --- /dev/null +++ b/tests/offscreen/handshake.test.ts @@ -0,0 +1,67 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; + +interface PortStub { + name: string; + postMessage: ReturnType; + onMessage: { addListener: ReturnType }; + onDisconnect: { addListener: ReturnType }; + disconnect: ReturnType; +} + +interface ChromeStub { + runtime: { + id: string; + sendMessage: (m: unknown) => void; + onMessage: { addListener: ReturnType }; + connect: () => PortStub; + }; +} + +interface GlobalWithChrome { + chrome?: ChromeStub; + MediaRecorder?: { isTypeSupported: (mime: string) => boolean }; +} + +function buildChromeStub(calls: unknown[]): ChromeStub { + return { + runtime: { + id: 'ext-id-test', + sendMessage: (m: unknown) => { + calls.push(m); + }, + onMessage: { addListener: vi.fn() }, + connect: () => ({ + name: 'video-keepalive', + postMessage: vi.fn(), + onMessage: { addListener: vi.fn() }, + onDisconnect: { addListener: vi.fn() }, + disconnect: vi.fn(), + }), + }, + }; +} + +describe('OFFSCREEN_READY handshake', () => { + beforeEach(() => { + vi.resetModules(); + (globalThis as unknown as GlobalWithChrome).MediaRecorder = { + isTypeSupported: vi.fn().mockReturnValue(true), + }; + }); + + it('sends OFFSCREEN_READY after listener registration', async () => { + const calls: unknown[] = []; + const stub = buildChromeStub(calls); + (globalThis as unknown as GlobalWithChrome).chrome = stub; + await import('../../src/offscreen/recorder'); + expect(stub.runtime.onMessage.addListener).toHaveBeenCalled(); + expect(calls).toEqual( + expect.arrayContaining([expect.objectContaining({ type: 'OFFSCREEN_READY' })]) + ); + const readyCount = calls.filter( + (m): m is { type: string } => + typeof m === 'object' && m !== null && (m as { type?: unknown }).type === 'OFFSCREEN_READY' + ).length; + expect(readyCount).toBe(1); + }); +}); diff --git a/tests/offscreen/port.test.ts b/tests/offscreen/port.test.ts new file mode 100644 index 0000000..0fed9c2 --- /dev/null +++ b/tests/offscreen/port.test.ts @@ -0,0 +1,89 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; + +interface PortStub { + name: string; + postMessage: ReturnType; + onMessage: { addListener: ReturnType }; + onDisconnect: { addListener: (fn: () => void) => void }; + disconnect: ReturnType; +} + +interface ChromeStub { + runtime: { + id: string; + sendMessage: ReturnType; + onMessage: { addListener: ReturnType }; + connect: () => PortStub; + }; +} + +interface GlobalWithChrome { + chrome?: ChromeStub; + MediaRecorder?: { isTypeSupported: (mime: string) => boolean }; +} + +describe('port reconnect', () => { + beforeEach(() => { + vi.resetModules(); + (globalThis as unknown as GlobalWithChrome).MediaRecorder = { + isTypeSupported: vi.fn().mockReturnValue(true), + }; + }); + + it('connects on module load', async () => { + let connectCount = 0; + const disconnectListeners: Array<() => void> = []; + const stub: ChromeStub = { + runtime: { + id: 'ext-id-test', + sendMessage: vi.fn(), + onMessage: { addListener: vi.fn() }, + connect: () => { + connectCount++; + return { + name: 'video-keepalive', + postMessage: vi.fn(), + onMessage: { addListener: vi.fn() }, + onDisconnect: { + addListener: (fn: () => void) => disconnectListeners.push(fn), + }, + disconnect: vi.fn(), + }; + }, + }, + }; + (globalThis as unknown as GlobalWithChrome).chrome = stub; + await import('../../src/offscreen/recorder'); + expect(connectCount).toBe(1); + }); + + it('reconnects when port disconnects', async () => { + let connectCount = 0; + const disconnectListeners: Array<() => void> = []; + const stub: ChromeStub = { + runtime: { + id: 'ext-id-test', + sendMessage: vi.fn(), + onMessage: { addListener: vi.fn() }, + connect: () => { + connectCount++; + return { + name: 'video-keepalive', + postMessage: vi.fn(), + onMessage: { addListener: vi.fn() }, + onDisconnect: { + addListener: (fn: () => void) => disconnectListeners.push(fn), + }, + disconnect: vi.fn(), + }; + }, + }, + }; + (globalThis as unknown as GlobalWithChrome).chrome = stub; + await import('../../src/offscreen/recorder'); + expect(connectCount).toBe(1); + // Fire the disconnect — module should reconnect + disconnectListeners.forEach((fn) => fn()); + expect(connectCount).toBeGreaterThanOrEqual(2); + }); +});