158 lines
5.4 KiB
TypeScript
158 lines
5.4 KiB
TypeScript
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);
|
|
});
|
|
});
|
|
});
|