fix(web): close P3 chat re-review findings

This commit is contained in:
shaggy (mosaic-dev box)
2026-08-10 01:58:27 -05:00
parent caebf9ef70
commit 48bb19310d
10 changed files with 774 additions and 65 deletions
+33 -8
View File
@@ -10,25 +10,45 @@ vi.mock('socket.io-client', () => ({
import { destroySocket, getSocket } from './socket';
function createMockSocket(): {
interface MockChatSocket {
on: ReturnType<typeof vi.fn>;
offAny: ReturnType<typeof vi.fn>;
disconnect: ReturnType<typeof vi.fn>;
} {
const mockSocket = {
on: vi.fn(() => mockSocket),
/** Test-only helper: fires every handler registered for `event` via
* `.on`, mirroring how a real socket.io-client instance invokes its own
* listeners (e.g. calling the registered `disconnect` handler(s) on a
* real transient disconnect). */
trigger(event: string): void;
}
function createMockSocket(): MockChatSocket {
const handlers = new Map<string, Set<() => void>>();
const mockSocket: MockChatSocket = {
on: vi.fn((event: string, handler: () => void) => {
if (!handlers.has(event)) handlers.set(event, new Set());
handlers.get(event)?.add(handler);
return mockSocket;
}),
offAny: vi.fn(() => mockSocket),
disconnect: vi.fn(() => mockSocket),
trigger(event: string): void {
for (const handler of handlers.get(event) ?? []) handler();
},
};
return mockSocket;
}
let currentMock!: MockChatSocket;
describe('chat socket', () => {
beforeEach(() => {
ioMock.mockReset();
// A fresh object per io() call so identity assertions (same singleton vs.
// a genuinely new instance) are meaningful.
ioMock.mockImplementation(() => createMockSocket());
ioMock.mockImplementation(() => {
currentMock = createMockSocket();
return currentMock;
});
});
afterEach(() => {
@@ -52,9 +72,14 @@ describe('chat socket', () => {
const first = getSocket();
// socket.ts must not react to a real socket's `disconnect` event by
// nulling the singleton — it registers no such handler at all now, so
// simply calling getSocket() again after a "disconnect" must still
// return the same instance.
// nulling the singleton — it registers no such handler at all now.
// Actually fire every handler registered via `.on('disconnect', ...)`
// (mirroring a real socket.io-client reconnect) instead of merely
// calling getSocket() again: this is what makes the test fail if
// production reintroduces `socket.on('disconnect', () => { socket =
// null; })`, since that handler would run here and null the singleton
// before the next getSocket() call.
currentMock.trigger('disconnect');
const second = getSocket();
expect(second).toBe(first);