import { describe, test, expect, vi, afterEach } from 'vitest'; import { installConsoleErrorBuffer, getRecentConsoleErrors, __resetConsoleErrorBuffer, } from './console-buffer'; afterEach(() => { __resetConsoleErrorBuffer(); vi.restoreAllMocks(); }); describe('console error buffer', () => { test('captures console.error calls', () => { vi.spyOn(console, 'error').mockImplementation(() => {}); installConsoleErrorBuffer(); console.error('boom', 42); const entries = getRecentConsoleErrors(); expect(entries).toHaveLength(1); expect(entries[0].message).toBe('boom 42'); expect(typeof entries[0].ts).toBe('number'); }); test('always chains to the original console.error', () => { const original = vi.spyOn(console, 'error').mockImplementation(() => {}); installConsoleErrorBuffer(); console.error('passed through'); expect(original).toHaveBeenCalledWith('passed through'); }); test('retains only the last 20 entries, oldest first', () => { vi.spyOn(console, 'error').mockImplementation(() => {}); installConsoleErrorBuffer(); for (let i = 0; i < 25; i++) console.error(`e${i}`); const entries = getRecentConsoleErrors(); expect(entries).toHaveLength(20); expect(entries[0].message).toBe('e5'); expect(entries[19].message).toBe('e24'); }); test('truncates a long message to 500 characters', () => { vi.spyOn(console, 'error').mockImplementation(() => {}); installConsoleErrorBuffer(); console.error('x'.repeat(900)); expect(getRecentConsoleErrors()[0].message).toHaveLength(500); }); test('installing twice does not double-capture', () => { vi.spyOn(console, 'error').mockImplementation(() => {}); installConsoleErrorBuffer(); installConsoleErrorBuffer(); console.error('once'); expect(getRecentConsoleErrors()).toHaveLength(1); }); test('captures window error events', () => { installConsoleErrorBuffer(); window.dispatchEvent(new ErrorEvent('error', { message: 'window blew up' })); expect(getRecentConsoleErrors().some((e) => e.message.includes('window blew up'))).toBe(true); }); test('getRecentConsoleErrors returns a copy, not the live array', () => { vi.spyOn(console, 'error').mockImplementation(() => {}); installConsoleErrorBuffer(); console.error('a'); const first = getRecentConsoleErrors(); first.push({ ts: 0, message: 'injected' }); expect(getRecentConsoleErrors()).toHaveLength(1); }); test('reinstalling after external code wraps console.error does not double-record, and reset restores the true original', () => { const trueOriginal = vi.spyOn(console, 'error').mockImplementation(() => {}); installConsoleErrorBuffer(); // External code wraps our patch without knowing anything about our // marker convention -- this is exactly what a third-party script or // another monitor might do. const ourPatch = console.error; const external = vi.fn((...args: unknown[]) => { ourPatch(...args); }); console.error = external; installConsoleErrorBuffer(); console.error('dup-check'); expect(getRecentConsoleErrors().filter((e) => e.message === 'dup-check')).toHaveLength(1); __resetConsoleErrorBuffer(); expect(console.error).toBe(trueOriginal); }); test('surviving module re-execution: reinstalling after the module reloads does not re-wrap an already-patched console.error', async () => { vi.spyOn(console, 'error').mockImplementation(() => {}); const mod1 = await import('./console-buffer'); mod1.installConsoleErrorBuffer(); const patchedAfterFirstInstall = console.error; // Simulate HMR: the module graph re-evaluates, producing a fresh module // instance with its own reset top-level state, while the *global* // console.error is still whatever the previous instance patched it to. vi.resetModules(); const mod2 = await import('./console-buffer'); mod2.installConsoleErrorBuffer(); // The marker on the live console.error -- not module-local state -- is // what installConsoleErrorBuffer() consults, so the reloaded instance // must recognize the existing patch and leave it alone rather than // wrapping it a second time. expect(console.error).toBe(patchedAfterFirstInstall); console.error('once-across-reload'); expect( mod1.getRecentConsoleErrors().filter((e) => e.message === 'once-across-reload') ).toHaveLength(1); mod1.__resetConsoleErrorBuffer(); }); });