const test = require('node:test'); const assert = require('node:assert/strict'); const fs = require('node:fs'); const vm = require('node:vm'); const path = require('node:path'); const source = fs.readFileSync(path.join(__dirname, '../frontend/web/static/js/integration.js'), 'utf8'); const start = source.indexOf(' async function navigateWithinConsole('); const end = source.indexOf(' function requestConsoleNavigation(', start); const deferred = () => { let resolve; const promise = new Promise(done => { resolve = done; }); return { promise, resolve }; }; const flush = () => new Promise(resolve => setImmediate(resolve)); function harness() { const requests = [], visits = [], errors = [], redirects = []; const content = {querySelector: () => null, replaceWith() {}, setAttribute() {}, removeAttribute() {}}; const context = { URL, loadedUIVersion: '', currentPath: '/', persistentTerminalNavigation: {navigating: false, pendingNavigation: null, sequence: 0}, sameOriginConsoleURL: target => new URL(target, 'http://device/'), fetch: url => { const result = deferred(); requests.push({url, result}); return result.promise; }, DOMParser: class {parseFromString() {return {body: {dataset: {}}, querySelector: () => content};}}, document: {body: {dataset: {}}, querySelector: () => content, importNode: node => node, dispatchEvent() {}}, window: {location: {assign: url => redirects.push(url)}, history: {pushState() {}, replaceState() {}}}, CustomEvent: class {}, copyNavigationChrome() {}, isPortDebugPath: () => false, initializeCurrentPage: async () => {}, report: error => errors.push(error), }; context.setCurrentPath = url => {context.currentPath = url; visits.push(url);}; vm.createContext(context); vm.runInContext(source.slice(start, end), context); const reply = (index, status = 200) => requests[index].result.resolve({status, ok: status === 200, text: async () => ''}); return {context, requests, visits, errors, redirects, reply, navigate: context.navigateWithinConsole}; } test('slow page initialization preserves only the last menu click without overlapping page writes', async () => { const h = harness(), initial = deferred(); h.context.initializeCurrentPage = () => initial.promise; const first = h.navigate('/network', 'push'); h.reply(0); await flush(); await h.navigate('/ports/1/parameters', 'push'); await h.navigate('/logs/system', 'replace'); assert.equal(h.requests.length, 1); initial.resolve(); await first; assert.deepEqual(h.requests.map(r => r.url), ['/network', '/logs/system']); h.reply(1); await flush(); assert.deepEqual(h.visits, ['/network', '/logs/system']); assert.equal(h.context.persistentTerminalNavigation.navigating, false); }); test('a failed request still releases the pending destination', async () => { const h = harness(); const first = h.navigate('/network', 'push'); await h.navigate('/port-status', 'push'); h.reply(0, 503); await assert.rejects(first, /HTTP 503/); assert.equal(h.requests[1].url, '/port-status'); h.reply(1); await flush(); assert.deepEqual(h.visits, ['/port-status']); }); test('expired login clears queued navigation before redirecting', async () => { const h = harness(); const first = h.navigate('/network', 'push'); await h.navigate('/port-status', 'push'); h.reply(0, 401); await first; assert.equal(h.requests.length, 1); assert.equal(h.redirects.length, 1); assert.equal(h.context.persistentTerminalNavigation.pendingNavigation, null); });