Milestone v1 (v2.0.0): Mokosh — Session Capture #1

Merged
strategy155 merged 297 commits from gsd/phase-04-harden-clean-up-optional into main 2026-05-31 15:34:17 +00:00
2 changed files with 156 additions and 0 deletions
Showing only changes of commit 408aa3354c - Show all commits

View File

@@ -0,0 +1,67 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
interface PortStub {
name: string;
postMessage: ReturnType<typeof vi.fn>;
onMessage: { addListener: ReturnType<typeof vi.fn> };
onDisconnect: { addListener: ReturnType<typeof vi.fn> };
disconnect: ReturnType<typeof vi.fn>;
}
interface ChromeStub {
runtime: {
id: string;
sendMessage: (m: unknown) => void;
onMessage: { addListener: ReturnType<typeof vi.fn> };
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);
});
});

View File

@@ -0,0 +1,89 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
interface PortStub {
name: string;
postMessage: ReturnType<typeof vi.fn>;
onMessage: { addListener: ReturnType<typeof vi.fn> };
onDisconnect: { addListener: (fn: () => void) => void };
disconnect: ReturnType<typeof vi.fn>;
}
interface ChromeStub {
runtime: {
id: string;
sendMessage: ReturnType<typeof vi.fn>;
onMessage: { addListener: ReturnType<typeof vi.fn> };
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);
});
});