import { renderAdminIndex } from './panel_html.mjs'; // Deterministic Overview widget regression; simulated DOM/status, no running API. import assert from 'node:assert/strict'; import fs from 'node:fs'; import path from 'node:path'; import vm from 'node:vm'; import { fileURLToPath } from 'node:url'; const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..'); const html = renderAdminIndex(root); const registry = JSON.parse(fs.readFileSync(path.join(root, 'src/admin/web/dashboard-widgets.json'), 'utf8')); const lang = JSON.parse(fs.readFileSync(path.join(root, 'langs/en_US.json'), 'utf8')).translations; const code = file => fs.readFileSync(path.join(root, 'src/admin/web', file), 'utf8'); const widgets = new Map(), ids = new Map(); class Element { constructor() { this.hidden = false; this.attributes = new Map(); this.style = {}; this.textContent = '-'; } setAttribute(key, value) { this.attributes.set(key, String(value)); } removeAttribute(key) { this.attributes.delete(key); } getAttribute(key) { return this.attributes.get(key) ?? null; } } for (const [, id] of html.matchAll(/\bid="([^"]+)"/g)) { assert.ok(!ids.has(id), `duplicate DOM id: ${id}`); ids.set(id, new Element()); } for (const [, id] of html.matchAll(/data-dashboard-widget="([^"]+)"/g)) widgets.set(id, new Element()); assert.equal(registry.length, widgets.size, 'registry and DOM widget count must match'); for (const entry of registry) assert.ok(widgets.has(entry.id), `registry references missing DOM card ${entry.id}`); let now = 1000; const document = { hidden: false, addEventListener() {}, querySelector(query) { return widgets.get(query.match(/^\[data-dashboard-widget="([^"]+)"\]$/)?.[1]) ?? null; }, }; const window = { addEventListener() {}, rustProxyRefresh: { current: () => ({ overview_seconds: 3 }) } }; const context = vm.createContext({ window, document, performance: { now: () => now }, currentPanel: 'overview', $: id => ids.get(id) ?? null, fmt: (v, digits = 0) => Number(v).toFixed(digits), fmtBytes: v => `${Number(v)} B`, t: key => lang[key] || key, console, }); for (const script of ['app/rate-sampling.js', 'app/chart-scales.js', 'app/overview-extra-metrics.js', 'app/overview-wide-metrics.js']) { vm.runInContext(code(script), context, { filename: script }); } const extra = window.rustProxyExtraOverview, wide = window.rustProxyWideOverview; const status = { revision: 9, system: { open_fds: 60, max_fds: 300 }, cache: { hits: 100, misses: 50, resident_bytes: 150, hot_limit_bytes: 300, cold_queue_bytes: 25, cold_queue_limit_bytes: 100, cold_bytes: 400, cold_limit_bytes: 1000 }, metrics: { requests: 300, http1_requests: 200, http2_requests: 80, http3_requests: 20, compression_gzip: 10, compression_br: 10, compression_zstd: 10, compression_deflate: 1 }, transform_metrics: { applied: 10, cache_hits: 10, skipped: 10, errors: 1 }, }; extra.render(status); wide.render(status); assert.equal(ids.get('overview-extra-extra_cache_memory-meter-fill').style.width, '50%'); assert.equal(ids.get('overview-extra-extra_cold_queue_memory-meter-fill').style.width, '25%'); assert.equal(ids.get('overview-extra-extra_cold_storage-meter-fill').style.width, '40%'); widgets.get('extra_cache_memory').hidden = false; widgets.get('extra_cache_hit_rate').hidden = false; widgets.get('extra_h2_share').hidden = false; widgets.get('extra_h1_share').hidden = false; widgets.get('extra_h3_share').hidden = false; widgets.get('extra_process_fds').hidden = false; extra.render(status); assert.equal(ids.get('overview-extra-extra_process_fds-meter-fill').style.width, '20%'); assert.equal(ids.get('overview-extra-extra_cache_hit_rate').textContent, '-'); // first counter sample has no rate assert.equal(ids.get('overview-wide-wide_transform_activity-summary').textContent, '-'); now = 4000; status.cache.hits += 30; status.cache.misses += 10; status.metrics.requests += 40; status.metrics.http1_requests += 10; status.metrics.http2_requests += 20; status.metrics.http3_requests += 10; status.metrics.compression_gzip += 3; status.metrics.compression_br += 6; status.metrics.compression_zstd += 9; status.metrics.compression_deflate += 12; status.transform_metrics.applied += 15; status.transform_metrics.cache_hits += 3; status.transform_metrics.skipped += 6; status.transform_metrics.errors += 1; extra.render(status); wide.render(status); assert.equal(ids.get('overview-extra-extra_cache_hit_rate').textContent, '75.0%'); assert.equal(ids.get('extra-cache-ratio-hit-arc').getAttribute('stroke-dasharray'), '75 25'); assert.equal(ids.get('extra-cache-ratio-miss-arc').getAttribute('stroke-dashoffset'), '-75'); assert.equal(ids.get('extra-cache-ratio-hit-count').textContent, '30'); assert.equal(ids.get('extra-cache-ratio-miss-count').textContent, '10'); assert.equal(ids.get('overview-extra-extra_h2_share-meter-fill').style.width, '50%'); assert.equal(ids.get('overview-wide-wide_transform_activity-summary').textContent, '8.3/s'); assert.equal(ids.get('overview-wide-wide_compression_activity-summary').textContent, '10.0/s'); for (const name of ['applied', 'cache_hits', 'skipped', 'errors']) assert.ok(ids.get(`overview-wide-wide_transform_activity-${name}-line`).getAttribute('d')?.length); for (const name of ['gzip', 'br', 'zstd', 'deflate']) assert.ok(ids.get(`overview-wide-wide_compression_activity-${name}-line`).getAttribute('d')?.length); // No hidden-tab spike or fake zero when a long interval is skipped. now = 100000; status.cache.hits += 300; status.cache.misses += 200; status.metrics.compression_gzip += 600; extra.render(status); wide.render(status); assert.equal(ids.get('overview-extra-extra_cache_hit_rate').textContent, '-'); assert.equal(ids.get('overview-wide-wide_compression_activity-summary').textContent, '-'); now = 103000; status.cache.hits += 5; status.cache.misses += 5; status.metrics.compression_gzip += 3; extra.render(status); wide.render(status); assert.equal(ids.get('overview-extra-extra_cache_hit_rate').textContent, '50.0%'); assert.equal(ids.get('overview-wide-wide_compression_activity-summary').textContent, '1.0/s'); // Bounded metric with no configured budget remains unknown, not a made-up percentage. status.cache.cold_limit_bytes = 0; extra.render(status); assert.equal(ids.get('overview-extra-extra_cold_storage').textContent, '400 B'); assert.equal(ids.get('overview-extra-extra_cold_storage-meter').getAttribute('aria-valuenow'), null); assert.equal(ids.get('overview-extra-extra_cold_storage-meter-fill').style.width, '0%'); extra.donut('cache-ratio', 75, 25); assert.equal(ids.get('cache-ratio-hit-arc').getAttribute('stroke-dasharray'), '75 25'); assert.equal(ids.get('cache-ratio-chart').getAttribute('aria-label'), 'HIT 75, MISS 25'); extra.donut('cache-ratio', 0, 0); assert.equal(ids.get('cache-ratio-hit-arc').getAttribute('stroke-dasharray'), '0 100'); assert.ok(code('app/runtime.js').includes('rustProxyExtraOverview?.donut?.("cache-ratio"'), 'main HIT/MISS tile must share tested donut rendering'); console.log('PASS: bounded meters, COLD tiers, HIT/MISS rings, transform and compression rates, pause/no-limit');