import { describe, it, expect, vi, beforeEach } from 'vitest'; interface ChromeStub { runtime: { sendMessage: ReturnType }; } interface GlobalWithChrome { chrome?: ChromeStub; MediaRecorder?: { isTypeSupported: (mime: string) => boolean }; } describe('codec strict mode', () => { beforeEach(() => { vi.resetModules(); (globalThis as unknown as GlobalWithChrome).chrome = { runtime: { sendMessage: vi.fn() }, }; }); // WR-02 fix: assertCodecSupported is a pure predicate that throws. // The previous spec expected a RECORDING_ERROR broadcast from inside // the assertion; that side-effect was removed (single-responsibility + // it double-emitted with startRecording's catch block). The notify is // now solely the caller's (startRecording's) responsibility — exercised // via integration through `classifyCaptureError` rather than from this // unit test, since `startRecording` requires a full getDisplayMedia // stub that jsdom does not provide. it('throws on unsupported vp9 (pure assertion, no side-effect)', async () => { (globalThis as unknown as GlobalWithChrome).MediaRecorder = { isTypeSupported: vi.fn().mockReturnValue(false), }; const mod = await import('../../src/offscreen/recorder'); expect(() => mod.assertCodecSupported()).toThrow(/vp9 unsupported/); const stub = (globalThis as unknown as GlobalWithChrome).chrome!; // The assertion itself MUST NOT emit RECORDING_ERROR. expect(stub.runtime.sendMessage).not.toHaveBeenCalledWith( expect.objectContaining({ type: 'RECORDING_ERROR' }) ); }); it('does not throw when vp9 IS supported', async () => { (globalThis as unknown as GlobalWithChrome).MediaRecorder = { isTypeSupported: vi.fn().mockReturnValue(true), }; const mod = await import('../../src/offscreen/recorder'); expect(() => mod.assertCodecSupported()).not.toThrow(); const stub = (globalThis as unknown as GlobalWithChrome).chrome!; expect(stub.runtime.sendMessage).not.toHaveBeenCalledWith( expect.objectContaining({ type: 'RECORDING_ERROR' }) ); }); }); // WR-01 fix: classifyCaptureError maps DOMException / Error shapes to a // stable string union. The popup / SW route can switch on the code // instead of substring-matching DOMException.message — message text // changes between Chrome versions and locales, name and prototype do not. describe('classifyCaptureError (WR-01 stable error codes)', () => { beforeEach(() => { vi.resetModules(); (globalThis as unknown as GlobalWithChrome).chrome = { runtime: { sendMessage: vi.fn() }, }; (globalThis as unknown as GlobalWithChrome).MediaRecorder = { isTypeSupported: vi.fn().mockReturnValue(true), }; }); function makeDomError(name: string, message: string): Error { const e = new Error(message); (e as { name: string }).name = name; return e; } it('codec error → "codec-unsupported"', async () => { const mod = await import('../../src/offscreen/recorder'); expect(mod.classifyCaptureError(new Error('vp9 unsupported. UA='))).toBe( 'codec-unsupported', ); }); it('NotAllowedError (no system hint) → "user-cancelled"', async () => { const mod = await import('../../src/offscreen/recorder'); expect( mod.classifyCaptureError(makeDomError('NotAllowedError', 'Permission denied by user')), ).toBe('user-cancelled'); }); it('NotAllowedError with "system" in message → "permission-denied"', async () => { const mod = await import('../../src/offscreen/recorder'); expect( mod.classifyCaptureError(makeDomError('NotAllowedError', 'Permission denied by system')), ).toBe('permission-denied'); }); it('SecurityError → "permission-denied"', async () => { const mod = await import('../../src/offscreen/recorder'); expect(mod.classifyCaptureError(makeDomError('SecurityError', 'blocked'))).toBe( 'permission-denied', ); }); it('NotFoundError → "no-source-selected"', async () => { const mod = await import('../../src/offscreen/recorder'); expect(mod.classifyCaptureError(makeDomError('NotFoundError', 'no source'))).toBe( 'no-source-selected', ); }); it('AbortError → "capture-failed"', async () => { const mod = await import('../../src/offscreen/recorder'); expect(mod.classifyCaptureError(makeDomError('AbortError', 'aborted'))).toBe( 'capture-failed', ); }); it('arbitrary error → "unknown"', async () => { const mod = await import('../../src/offscreen/recorder'); expect(mod.classifyCaptureError(new Error('totally novel failure'))).toBe('unknown'); expect(mod.classifyCaptureError('a bare string')).toBe('unknown'); expect(mod.classifyCaptureError(null)).toBe('unknown'); }); });