Add Prettier and ESLint tooling (Refs #77)

This commit is contained in:
2026-10-09 15:56:13 -04:00
parent 8ed059b8d3
commit 16469e2d43
711 changed files with 99737 additions and 28286 deletions
+66 -19
View File
@@ -6,7 +6,10 @@ const response = (line: string) => new Response(JSON.stringify({ ok: true, lines
function deferred<T>() {
let resolve!: (value: T) => void;
let reject!: (error: Error) => void;
const promise = new Promise<T>((done, fail) => { resolve = done; reject = fail; });
const promise = new Promise<T>((done, fail) => {
resolve = done;
reject = fail;
});
return { promise, resolve, reject };
}
let documentStub: EventTarget & { visibilityState: string };
@@ -16,12 +19,18 @@ beforeEach(() => {
documentStub = Object.assign(new EventTarget(), { visibilityState: 'visible' });
vi.stubGlobal('document', documentStub);
});
afterEach(() => { controller?.dispose(); vi.useRealTimers(); vi.unstubAllGlobals(); });
afterEach(() => {
controller?.dispose();
vi.useRealTimers();
vi.unstubAllGlobals();
});
function setup() {
const fetcher = vi.fn<typeof fetch>();
const onReset = vi.fn();
const onData = vi.fn(); const onError = vi.fn(); const onLoading = vi.fn();
const onData = vi.fn();
const onError = vi.fn();
const onLoading = vi.fn();
controller = createLogRefresh({ fetcher, onReset, onData, onError, onLoading });
return { fetcher, onReset, onData, onError, onLoading };
}
@@ -32,11 +41,22 @@ const visibility = (state: string) => {
describe('log refresh controls', () => {
it('retains a manually loaded log through same-server fleet updates and resets only on server navigation', async () => {
const fetcher = vi.fn<typeof fetch>().mockImplementation(async () => response('loaded log'));
const fetcher = vi
.fn<typeof fetch>()
.mockImplementation(async () => response('loaded log'));
let displayed: string[] = [];
const reset = vi.fn(() => { displayed = []; });
controller = createLogRefresh({ fetcher, onReset: reset,
onData: (_key, body) => { displayed = body.lines ?? []; }, onError: vi.fn(), onLoading: vi.fn() });
const reset = vi.fn(() => {
displayed = [];
});
controller = createLogRefresh({
fetcher,
onReset: reset,
onData: (_key, body) => {
displayed = body.lines ?? [];
},
onError: vi.fn(),
onLoading: vi.fn()
});
controller.configure(target, true, 5000);
await controller.refresh();
controller.configure(target, false, 5000);
@@ -68,7 +88,8 @@ describe('log refresh controls', () => {
await controller.refresh();
controller.configure(target, false, 5000);
await vi.advanceTimersByTimeAsync(30_000);
visibility('hidden'); visibility('visible');
visibility('hidden');
visibility('visible');
await vi.advanceTimersByTimeAsync(30_000);
expect(fetcher).toHaveBeenCalledTimes(1);
expect(onData).toHaveBeenCalledTimes(1);
@@ -77,7 +98,8 @@ describe('log refresh controls', () => {
it('aborts a pending refresh immediately and ignores its late data and loading updates after resume', async () => {
const { fetcher, onData, onLoading, onError } = setup();
const old = deferred<Response>(); const fresh = deferred<Response>();
const old = deferred<Response>();
const fresh = deferred<Response>();
fetcher.mockReturnValueOnce(old.promise).mockReturnValueOnce(fresh.promise);
controller.configure(target, true, 5000);
const pending = controller.refresh();
@@ -89,7 +111,9 @@ describe('log refresh controls', () => {
controller.configure(target, true, 5000);
const resumed = controller.refresh();
expect(fetcher).toHaveBeenCalledTimes(2);
onData.mockClear(); onError.mockClear(); onLoading.mockClear();
onData.mockClear();
onError.mockClear();
onLoading.mockClear();
// Simulate a transport that delivers a response despite cancellation.
old.resolve(response('obsolete'));
await pending;
@@ -98,7 +122,10 @@ describe('log refresh controls', () => {
expect(onLoading).not.toHaveBeenCalled();
fresh.resolve(response('current'));
await resumed;
expect(onData).toHaveBeenCalledWith('console', expect.objectContaining({ lines: ['current'] }));
expect(onData).toHaveBeenCalledWith(
'console',
expect.objectContaining({ lines: ['current'] })
);
expect(onLoading).toHaveBeenLastCalledWith(false);
});
@@ -115,20 +142,28 @@ describe('log refresh controls', () => {
next.reject(new Error('cancelled response'));
await pending;
expect(onData).toHaveBeenCalledTimes(1);
expect(onData).toHaveBeenLastCalledWith('console', expect.objectContaining({ lines: ['saved'] }));
expect(onData).toHaveBeenLastCalledWith(
'console',
expect.objectContaining({ lines: ['saved'] })
);
expect(onError).not.toHaveBeenCalled();
});
it('allows a manual refresh while paused without restarting automatic polling', async () => {
const { fetcher, onData } = setup();
fetcher.mockResolvedValueOnce(response('initial')).mockResolvedValueOnce(response('manual'));
fetcher
.mockResolvedValueOnce(response('initial'))
.mockResolvedValueOnce(response('manual'));
controller.configure(target, true, 5000);
await controller.refresh();
controller.configure(target, false, 5000);
await controller.refresh();
await vi.advanceTimersByTimeAsync(30_000);
expect(fetcher).toHaveBeenCalledTimes(2);
expect(onData).toHaveBeenLastCalledWith('console', expect.objectContaining({ lines: ['manual'] }));
expect(onData).toHaveBeenLastCalledWith(
'console',
expect.objectContaining({ lines: ['manual'] })
);
});
it('loads a selected log while paused and never accepts a reply from the prior server or tab', async () => {
@@ -146,7 +181,10 @@ describe('log refresh controls', () => {
expect(fetcher).toHaveBeenCalledTimes(2);
expect(fetcher.mock.calls[1][0]).toContain('/another-server/logs/games_zm?lines=1000');
expect(onData).toHaveBeenCalledTimes(1);
expect(onData).toHaveBeenLastCalledWith('games_zm', expect.objectContaining({ lines: ['selected log'] }));
expect(onData).toHaveBeenLastCalledWith(
'games_zm',
expect.objectContaining({ lines: ['selected log'] })
);
});
it('resumes one polling loop and coalesces manual and scheduled reads', async () => {
@@ -173,7 +211,9 @@ describe('log refresh controls', () => {
await Promise.resolve();
controller.dispose();
expect(fetcher.mock.calls[0][1]!.signal!.aborted).toBe(true);
onData.mockClear(); onError.mockClear(); onLoading.mockClear();
onData.mockClear();
onError.mockClear();
onLoading.mockClear();
body.resolve({ ok: true, lines: ['late body'] });
await pending;
visibility('visible');
@@ -187,7 +227,8 @@ describe('log refresh controls', () => {
it('keeps the last tail on a non-JSON gateway failure and can recover with a retry', async () => {
const { fetcher, onData, onError } = setup();
fetcher.mockResolvedValueOnce(response('saved'))
fetcher
.mockResolvedValueOnce(response('saved'))
.mockResolvedValueOnce(new Response('<html>Bad Gateway</html>', { status: 502 }))
.mockResolvedValueOnce(response('recovered'));
controller.configure(target, false, 5000);
@@ -195,10 +236,16 @@ it('keeps the last tail on a non-JSON gateway failure and can recover with a ret
await controller.refresh();
expect(onError).toHaveBeenLastCalledWith('HTTP 502: Expected a JSON log response.');
expect(onData).toHaveBeenCalledTimes(1);
expect(onData).toHaveBeenLastCalledWith('console', expect.objectContaining({ lines: ['saved'] }));
expect(onData).toHaveBeenLastCalledWith(
'console',
expect.objectContaining({ lines: ['saved'] })
);
await controller.refresh();
expect(onError).toHaveBeenLastCalledWith('');
expect(onData).toHaveBeenLastCalledWith('console', expect.objectContaining({ lines: ['recovered'] }));
expect(onData).toHaveBeenLastCalledWith(
'console',
expect.objectContaining({ lines: ['recovered'] })
);
});
it('rejects malformed successful log payloads before they reach the renderer', async () => {