import {expect, jest, test, beforeEach, afterEach} from '@jest/globals'; import { LocalStorageHandler } from '../../src/handler'; import { LogRecord } from '../../src/log-record'; import { DEBUG, WARNING, ERROR } from '../../src/log-level'; // mock localStorage for Node/Jest environment const store: {[key: string]: string} = {}; const localStorageMock = { getItem: (key: string): string | null => store[key] ?? null, setItem: (key: string, value: string) => { store[key] = value }, removeItem: (key: string) => { delete store[key] }, }; function clearStore() { for (const key of Object.keys(store)) { delete store[key] } } function makeRecord(level: number, msg: string): LogRecord { return new LogRecord('test', { level, msg }); } beforeEach(() => { clearStore(); (globalThis as any).localStorage = localStorageMock; }); afterEach(() => { clearStore(); delete (globalThis as any).localStorage; }); describe('LocalStorageHandler', () => { describe('constructor', () => { test('uses default key and limits', () => { const h = new LocalStorageHandler(); expect(h.getEntries()).toEqual([]); }); test('throws when localStorage is unavailable', () => { delete (globalThis as any).localStorage; expect(() => new LocalStorageHandler()).toThrow( 'requires a browser environment' ); }); test('accepts custom options', () => { const h = new LocalStorageHandler({ key: 'custom-log', maxEntries: 50, maxBytes: 4096, }); h.emit(makeRecord(DEBUG, 'test')); expect(store['custom-log']).toBeDefined(); expect(store['esm-logging']).toBeUndefined(); }); }); describe('emit', () => { test('stores formatted log entry', () => { const h = new LocalStorageHandler(); h.emit(makeRecord(WARNING, 'hello')); const entries = h.getEntries(); expect(entries.length).toBe(1); expect(entries[0]).toContain('hello'); }); test('appends multiple entries', () => { const h = new LocalStorageHandler(); h.emit(makeRecord(DEBUG, 'first')); h.emit(makeRecord(DEBUG, 'second')); h.emit(makeRecord(DEBUG, 'third')); expect(h.getEntries().length).toBe(3); }); test('persists across handler instances with same key', () => { const h1 = new LocalStorageHandler({ key: 'shared' }); h1.emit(makeRecord(DEBUG, 'from h1')); const h2 = new LocalStorageHandler({ key: 'shared' }); const entries = h2.getEntries(); expect(entries.length).toBe(1); expect(entries[0]).toContain('from h1'); }); }); describe('rotation by entry count', () => { test('discards oldest entries when maxEntries exceeded', () => { const h = new LocalStorageHandler({ maxEntries: 3 }); h.emit(makeRecord(DEBUG, 'msg-1')); h.emit(makeRecord(DEBUG, 'msg-2')); h.emit(makeRecord(DEBUG, 'msg-3')); h.emit(makeRecord(DEBUG, 'msg-4')); const entries = h.getEntries(); expect(entries.length).toBe(3); expect(entries[0]).toContain('msg-2'); expect(entries[2]).toContain('msg-4'); }); test('maxEntries of 1 keeps only the latest', () => { const h = new LocalStorageHandler({ maxEntries: 1 }); h.emit(makeRecord(DEBUG, 'old')); h.emit(makeRecord(DEBUG, 'new')); const entries = h.getEntries(); expect(entries.length).toBe(1); expect(entries[0]).toContain('new'); }); }); describe('rotation by byte size', () => { test('discards oldest entries when maxBytes exceeded', () => { const h = new LocalStorageHandler({ maxEntries: 0, maxBytes: 100 }); // emit entries until we exceed the limit for (let i = 0; i < 20; i++) { h.emit(makeRecord(DEBUG, `message-${i}`)); } const entries = h.getEntries(); const serialized = JSON.stringify(entries); expect(serialized.length).toBeLessThanOrEqual(100); expect(entries.length).toBeGreaterThan(0); }); }); describe('getEntries', () => { test('returns empty array when no entries', () => { const h = new LocalStorageHandler(); expect(h.getEntries()).toEqual([]); }); test('returns empty array on corrupt data', () => { store['esm-logging'] = 'not valid json{{{'; const h = new LocalStorageHandler(); expect(h.getEntries()).toEqual([]); }); }); describe('clearEntries', () => { test('removes all stored entries', () => { const h = new LocalStorageHandler(); h.emit(makeRecord(DEBUG, 'to be cleared')); expect(h.getEntries().length).toBe(1); h.clearEntries(); expect(h.getEntries()).toEqual([]); }); }); describe('close', () => { test('sets closed to true', () => { const h = new LocalStorageHandler(); expect(h.closed).toBe(false); h.close(); expect(h.closed).toBe(true); }); }); });