import {test} from 'node:test'; import assert from 'node:assert/strict'; import {navigateBrandDocument, visitBrowserDocument} from './brand-navigation.mjs'; const response = status => ({ok: () => status === 200, status: () => status}); test('successful documents are fetched once', async () => { let calls = 0; await navigateBrandDocument(async () => { calls++; return response(200); }, '/canvas'); assert.equal(calls, 1); }); test('one proxy failure can recover within the original deadline', async () => { const timeouts = []; let elapsed = 0; await navigateBrandDocument(async timeout => { timeouts.push(timeout); elapsed = 15000; return response(timeouts.length === 1 ? 500 : 200); }, '/canvas', () => elapsed); assert.deepEqual(timeouts, [60000, 45000]); }); test('connection reset and interrupted bootstrap scripts can recover once', async () => { for(const failure of ['net::ERR_CONNECTION_RESET','Document bootstrap script request failed: net::ERR_ABORTED']){ let calls = 0; await navigateBrandDocument(async () => { if (++calls === 1) throw new Error(failure); return response(200); }, '/canvas'); assert.equal(calls, 2); } }); test('persistent server failure is not hidden', async () => { let calls = 0; await assert.rejects(navigateBrandDocument(async () => { calls++; return response(502); }, '/canvas'), /HTTP 502/); assert.equal(calls, 2); }); test('missing routes, authentication failures and ordinary timeouts are not retried', async () => { for (const status of [401, 403, 404]) { let calls = 0; await assert.rejects(navigateBrandDocument(async () => { calls++; return response(status); }, '/canvas'), /HTTP/); assert.equal(calls, 1); } let calls = 0; await assert.rejects(navigateBrandDocument(async () => { calls++; throw new Error('Timeout 60000ms exceeded'); }, '/canvas'), /Timeout/); assert.equal(calls, 1); }); test('recovery cannot extend the total navigation budget', async () => { let calls = 0, elapsed = 0; await assert.rejects(navigateBrandDocument(async () => { calls++; elapsed = 60000; return response(503); }, '/canvas', () => elapsed), /budget exhausted/); assert.equal(calls, 1); }); function browserFixture(failBootstrap = false, failHydration = false, blockedOnce = false, readyVisible = true) { const listeners = new Map(), calls = []; const page = { on(event, fn) { if (!listeners.has(event)) listeners.set(event, new Set()); listeners.get(event).add(fn); }, off(event, fn) { listeners.get(event)?.delete(fn); }, async goto(url, options) { calls.push(options.waitUntil); const request = {resourceType: () => 'script', failure: () => ({errorText: 'net::ERR_ABORTED'})}; for (const fn of listeners.get('request') || []) fn(request); if (failBootstrap && calls.length === 1) for (const fn of listeners.get('requestfailed') || []) fn(request); return response(200); }, locator() { return {first: () => ({waitFor: async options => { calls.push('hydrated'); if (!readyVisible && options.state !== 'attached') throw Error('Ready marker is hidden by responsive CSS'); if (failHydration || (blockedOnce && calls.length === 2)) throw Error('Hydration timeout'); }})}; }, }; return {page, calls, listeners}; } test('document visit completes deferred scripts before checking hydration and removes listeners', async () => { const {page, calls, listeners} = browserFixture(); await visitBrowserDocument(page, '/account'); assert.deepEqual(calls, ['domcontentloaded', 'hydrated']); assert.ok([...listeners.values()].every(set => set.size === 0)); }); test('interrupted bootstrap retries only document loading before hydration', async () => { const {page, calls} = browserFixture(true, false, true); await visitBrowserDocument(page, '/account'); assert.deepEqual(calls, ['domcontentloaded', 'hydrated', 'domcontentloaded', 'hydrated']); }); test('hydration failure is surfaced without retrying the application', async () => { const {page, calls} = browserFixture(false, true); await assert.rejects(visitBrowserDocument(page, '/account'), /Hydration timeout/); assert.deepEqual(calls, ['domcontentloaded', 'hydrated']); }); test('cancelled script does not reject an already hydrated document', async () => { const {page, calls} = browserFixture(true); await visitBrowserDocument(page, '/account'); assert.deepEqual(calls, ['domcontentloaded', 'hydrated']); }); test('hydrated pages are ready even when responsive CSS hides the marker', async () => { const {page, calls} = browserFixture(false, false, false, false); await visitBrowserDocument(page, '/graphic'); assert.deepEqual(calls, ['domcontentloaded', 'hydrated']); });