// Verify the real opt-in Stream Dashboard component against a lightweight DOM. // No server, browser layout engine or Rust executable is exercised here. import assert from 'node:assert/strict'; import fs from 'node:fs'; import vm from 'node:vm'; const src = fs.readFileSync('src/admin/web/app/stream-dashboard.js', 'utf8'); const preferencesSource = fs.readFileSync('src/admin/web/app/preferences.js', 'utf8'); const ids = ['stream_overview', 'stream_traffic', 'stream_sessions', 'stream_backend_health']; assert(!/\b(setInterval|setTimeout|fetch|XMLHttpRequest)\s*\(/.test(src), 'widgets must not create timers/fetches'); const cards = new Map(ids.map(id => [id, { hidden: true }])); const elements = new Map(); for (const name of ['overview', 'overview-detail', 'sessions', 'sessions-detail', 'health', 'health-detail', 'traffic', 'traffic-detail', 'rx', 'tx', 'rx-rate', 'tx-rate']) { elements.set(`stream-dashboard-${name}`, { textContent: '-', attrs: new Map(), setAttribute(k, v) { this.attrs.set(k, v); } }); } const preferences = { dashboard_hidden_widgets: [...ids] }; const window = { rustProxyPreferences: { current: () => preferences } }; let clock = 1000; const scriptContext = { window, document: { querySelector(selector) { const match = selector.match(/^\[data-dashboard-widget="([^\"]+)"\]$/); return match ? cards.get(match[1]) || null : null; } }, $: name => elements.get(name), ADMIN_APPEARANCE_DEFAULTS: { dashboard_hidden_widgets: ids }, envelope: { config: { streams: [ { id: 'tcp', enabled: true, protocol: 'tcp' }, { id: 'udp', enabled: true, protocol: 'udp' } ] } }, latestStatus: null, currentPanel: 'overview', performance: { now: () => clock }, t: key => key, fmt: n => `${n}`, bytes: n => `${n} B`, Math, }; vm.runInNewContext(fs.readFileSync('src/admin/web/app/rate-sampling.js', 'utf8') + '\n' + src, scriptContext); const widget = window.rustProxyStreamDashboard; const status = { streams: { active: [ { id: 'tcp', enabled: true, protocol: 'tcp' }, { id: 'udp', enabled: true, protocol: 'udp' } ], metrics: { tcp: { active: 3, bytes_in: 500, bytes_out: 1000, backends: [ { id: 'a', enabled: true, drain: false, status: 'healthy' }, { id: 'b', enabled: true, drain: false, status: 'unhealthy' } ] }, udp: { active: 2, bytes_in: 100, bytes_out: 200, backends: [ { id: 'a', enabled: true, drain: false, status: 'unverified' } ] } } } }; scriptContext.latestStatus = status; widget.render(status); assert(ids.every(id => cards.get(id).hidden), 'new widgets are hidden by default'); assert.equal(elements.get('stream-dashboard-traffic').textContent, '-', 'hidden chart must not sample'); preferences.dashboard_hidden_widgets = []; widget.render(status); assert(ids.every(id => !cards.get(id).hidden)); assert.equal(elements.get('stream-dashboard-sessions').textContent, '5'); assert.equal(elements.get('stream-dashboard-overview').textContent, '2 / 2'); assert.equal(elements.get('stream-dashboard-health').textContent, '1 / 1'); clock += 1000; status.streams.metrics.tcp.bytes_in += 1024; status.streams.metrics.tcp.bytes_out += 512; widget.render(status); assert.equal(elements.get('stream-dashboard-rx-rate').textContent, '1024 B/s'); assert.equal(elements.get('stream-dashboard-tx-rate').textContent, '512 B/s'); assert(elements.get('stream-dashboard-rx').attrs.get('points')?.includes(',')); preferences.dashboard_hidden_widgets = ['stream_traffic']; widget.syncVisibility(); assert(cards.get('stream_traffic').hidden); const previous = elements.get('stream-dashboard-rx').attrs.get('points'); clock += 1000; widget.render(status); assert.equal(elements.get('stream-dashboard-rx').attrs.get('points'), previous, 'hidden chart must not update'); // Explicitly enabled cards remain visible even without active/configured streams. // The user should see a neutral state, not a widget that ignores Appearance. preferences.dashboard_hidden_widgets = []; scriptContext.envelope.config.streams = []; scriptContext.latestStatus = { streams: { active: [], metrics: {} } }; widget.render(scriptContext.latestStatus); assert(ids.every(id => !cards.get(id).hidden), 'appearance must control Stream card visibility even with no listeners'); for (const name of ['overview', 'sessions', 'health', 'traffic']) { assert.equal(elements.get(`stream-dashboard-${name}`).textContent, '-'); if (name !== 'traffic') assert.equal(elements.get(`stream-dashboard-${name}-detail`).textContent, 'streamWidgetUnavailable'); } assert.equal(elements.get('stream-dashboard-rx-rate').textContent, '-'); assert.equal(elements.get('stream-dashboard-tx-rate').textContent, '-'); assert.equal(elements.get('stream-dashboard-rx').attrs.get('points'), '0,55 300,55', 'empty status must clear the graph'); // A desired but not yet running Stream must still make the overview useful. scriptContext.envelope.config.streams = [{ id: 'pending', enabled: true, protocol: 'tcp' }]; preferences.dashboard_hidden_widgets = []; widget.render({ streams: { active: [], metrics: {} } }); assert.equal(elements.get('stream-dashboard-overview').textContent, '0 / 1'); assert(!cards.get('stream_overview').hidden, 'restart-pending listener does not suppress the card'); preferences.dashboard_hidden_widgets = [...ids]; widget.render(scriptContext.latestStatus); assert(ids.every(id => cards.get(id).hidden), 'only the Appearance switch hides opt-in cards'); widget.reset(); // Reuse original Appearance normalization logic to protect 0.31.x saved layouts. const registryPrelude = `const ADMIN_DASHBOARD_WIDGET_REGISTRY = Object.freeze(${fs.readFileSync("src/admin/web/dashboard-widgets.json", "utf8")});`; const definitions = preferencesSource.slice(preferencesSource.indexOf('const ADMIN_DASHBOARD_WIDGETS'), preferencesSource.indexOf('const CHART_STYLE_KEYS')); const normalize = preferencesSource.slice(preferencesSource.indexOf('function normalizeDashboardWidgets'), preferencesSource.indexOf('function browserLanguage')); const sandbox = {}; vm.runInNewContext(`${registryPrelude}\n${definitions}\n${normalize}\nthis.normalizeDashboardWidgets = normalizeDashboardWidgets; this.normalizeHiddenDashboardWidgets = normalizeHiddenDashboardWidgets`, sandbox); const oldOrder = ['requests', 'errors']; const migratedOrder = [...sandbox.normalizeDashboardWidgets(oldOrder)]; const migratedHidden = [...sandbox.normalizeHiddenDashboardWidgets([], oldOrder)]; assert.deepEqual(migratedOrder.slice(0, 2), oldOrder); assert(ids.every(id => migratedOrder.includes(id) && migratedHidden.includes(id)), 'legacy order must add cards as hidden'); console.log('PASS: opt-in Stream dashboard uses shared data with no polling, handles user visibility/empty-status states, and migrates old Appearance layouts.');