// Regression for shared server/browser Overview Cards metadata and future opt-in widgets. import fs from 'node:fs'; import vm from 'node:vm'; import assert from 'node:assert/strict'; const registry = JSON.parse(fs.readFileSync('src/admin/web/dashboard-widgets.json','utf8')); const prefs = fs.readFileSync('src/admin/web/app/preferences.js','utf8'); const constants = prefs.slice(prefs.indexOf('const ADMIN_DASHBOARD_WIDGETS'), prefs.indexOf('const CHART_STYLE_KEYS')); const normalize = prefs.slice(prefs.indexOf('function normalizeDashboardWidgets'),prefs.indexOf('function browserLanguage')); const label = prefs.slice(prefs.indexOf('function dashboardWidgetLabel'),prefs.indexOf('function renderDashboardWidgetSettings')); assert(constants.startsWith('const ADMIN_DASHBOARD_WIDGETS')); assert(normalize.startsWith('function normalizeDashboardWidgets')); assert(label.startsWith('function dashboardWidgetLabel')); function client(definitions) { const context = {t: key => `loc:${key}`}; vm.createContext(context); vm.runInContext(`const ADMIN_DASHBOARD_WIDGET_REGISTRY = Object.freeze(${JSON.stringify(definitions)});\n${constants}\n${normalize}\n${label}\n`,context); return { order: value => [...vm.runInContext(`normalizeDashboardWidgets(${JSON.stringify(value)})`,context)], hidden: (value,saved) => [...vm.runInContext(`normalizeHiddenDashboardWidgets(${JSON.stringify(value)},${JSON.stringify(saved)})`,context)], label: id => vm.runInContext(`dashboardWidgetLabel(${JSON.stringify(id)})`,context), }; } const original = client(registry); const previous = registry.filter(widget => widget.category === 'overview').map(widget => widget.id); assert.deepEqual(original.order(previous),registry.map(widget => widget.id)); assert.deepEqual(original.hidden([],previous),registry.filter(widget=>widget.default_hidden).map(widget=>widget.id)); assert.equal(original.label('stream_overview'),'loc:streamWidgetOverview'); assert.equal(original.label('backend_health'),'loc:backendWidgetHealth'); assert.equal(original.label('backend_table'),'loc:vhHealthTitle'); assert.equal(registry.length, 62); const types = new Set(['value', 'value_chart', 'chart', 'panel', 'table']); assert(registry.every(widget => types.has(widget.display_type)), 'Every Overview block needs explicit type metadata'); assert.equal(registry.find(w => w.id === 'requests').display_type, 'value'); assert.equal(registry.find(w => w.id === 'backend_requests').display_type, 'value_chart'); assert.equal(registry.find(w => w.id === 'traffic_chart').display_type, 'chart'); assert.equal(registry.find(w => w.id === 'cache_diagnostics').display_type, 'panel'); assert.equal(registry.find(w => w.id === 'backend_table').display_type, 'table'); // Upgrading from the previous 30-widget 0.32.3 archive keeps its exact // visible/order preferences: only twelve new metrics are opt-in, while // seven existing large panels remain visible in the factory layout. const previouslySaved = registry.slice(0, 30).map(widget => widget.id); const migratedOrder = original.order(previouslySaved); const migratedHidden = original.hidden(registry.slice(0, 30).filter(w => w.default_hidden).map(w => w.id), previouslySaved); assert.deepEqual(migratedOrder.slice(0, 30), previouslySaved); assert.deepEqual(migratedOrder.slice(30), registry.slice(30).map(widget => widget.id)); for (const widget of registry.filter(widget => widget.category === 'performance')) assert(migratedHidden.includes(widget.id), `${widget.id} must be opt-in after upgrade`); for (const widget of registry.filter(widget => widget.category === 'panels' && !widget.default_hidden)) assert(!migratedHidden.includes(widget.id), `${widget.id} must retain default visibility`); for (const widget of registry.filter(widget => widget.id.startsWith('wide_'))) assert(migratedHidden.includes(widget.id), `${widget.id} is a new opt-in chart`); assert.equal(registry.filter(widget => widget.category === 'panels').length, 14); for (const widget of registry.filter(widget => widget.category === 'performance')) assert(widget.default_hidden); for (const id of ['backend_health','backend_activity','backend_errors','backend_latency','backend_traffic','backend_protocols','backend_requests','backend_capacity','backend_http5xx','backend_timeouts','backend_network','backend_retries','backend_queue','backend_rotations']) assert(original.hidden([],previous).includes(id), `${id} must remain opt-in for older preferences`); assert.deepEqual(original.order(['errors','unknown-0.33.0','requests','errors']).slice(0,2),['errors','requests']); assert(original.hidden(['unknown-0.33.0','http3'],registry.map(widget=>widget.id)).includes('http3')); assert(!original.hidden(['unknown-0.33.0','http3'],registry.map(widget=>widget.id)).includes('unknown-0.33.0')); // Adding a new registry card, with an existing UI element and translation key, // automatically offers it in the ordered preference list and hides it by default. const extra={...registry[0], id:'future_upstream', label_key:'futureUpstreamProtocol', description_key:null, element_selector:'[data-dashboard-widget="future_upstream"]', default_hidden:true}; const expanded=client([...registry,extra]); assert.equal(expanded.order(previous).at(-1),'future_upstream'); assert(expanded.hidden([],previous).includes('future_upstream')); assert.equal(expanded.label('future_upstream'),'loc:futureUpstreamProtocol'); assert(!expanded.hidden([],registry.map(widget=>widget.id).concat('future_upstream')).includes('future_upstream'), 'explicitly opted-in widget must not become hidden after a later release'); console.log(`PASS: shared widget registry UI preserves order, visibility and ${registry.length} existing cards`);