'use strict'; const test = require('node:test'); const assert = require('node:assert/strict'); const fs = require('node:fs'); const path = require('node:path'); const { describeVirtualRows } = require('../sidebar-view'); const ROOT = path.resolve(__dirname, '..'); const read = (name) => fs.readFileSync(path.join(ROOT, name), 'utf8'); function syntheticSnapshot(count) { const rows = Array.from({ length: count }, (_, index) => ({ key: `camera:00000000-0000-4000-8000-${String(index).padStart(12, '0')}`, id: `00000000-0000-4000-8000-${String(index).padStart(12, '0')}`, kind: 'camera', name: `Camera ${index}`, ariaLevel: 3, ariaSelected: index === 4, ariaPosInSet: index + 1, ariaSetSize: count, })); const startIndex = Math.max(0, Math.floor(count / 2) - 20); return { state: { activeKey: rows[startIndex].key, selectedKey: rows[4].key }, rows: Object.assign(rows, { hiddenSelected: true }), window: { rows: rows.slice(startIndex, startIndex + 60), startIndex, offsetTop: startIndex * 30, totalHeight: count * 30 }, }; } test('renderer loads hierarchy foundations in order and exposes an accessible virtual tree', () => { const html = read('index.html'); const treeIndex = html.indexOf('device-tree.js'); const controllerIndex = html.indexOf('sidebar-controller.js'); const viewIndex = html.indexOf('sidebar-view.js'); const rendererIndex = html.indexOf('renderer.js'); assert.ok(treeIndex >= 0 && treeIndex < controllerIndex && controllerIndex < viewIndex && viewIndex < rendererIndex); assert.match(html, /id="deviceList"[^>]*role="tree"[^>]*tabindex="0"[^>]*aria-activedescendant/); assert.match(html, /id="deviceResults"[^>]*role="status"[^>]*aria-live="polite"/); }); test('renderer uses one hierarchy request and no parallel flat discovery', () => { const renderer = read('renderer.js'); assert.match(renderer, /electronAPI\.getDeviceHierarchy\(\)/); assert.doesNotMatch(renderer, /electronAPI\.getDevices\(|electronAPI\.getDeviceSites\(|Promise\.all\(\s*\[\s*window\.electronAPI\.get/); assert.doesNotMatch(renderer, /\ballDevices\b|\ballSites\b|\bcollapsedSites\b|groupDevicesBySite/); assert.doesNotMatch(renderer, /Selected camera is hidden|hidden by the current view/i); assert.match(renderer, /loadGeneration/); assert.match(renderer, /generation\s*!==\s*loadGeneration/); }); test('renderer uses delegated tree events, throttled scroll, and debounced search', () => { const renderer = read('renderer.js'); assert.equal((renderer.match(/deviceList\.addEventListener\('click'/g) || []).length, 1); assert.equal((renderer.match(/deviceList\.addEventListener\('keydown'/g) || []).length, 1); assert.equal((renderer.match(/deviceList\.addEventListener\('scroll'/g) || []).length, 1); assert.match(renderer, /requestAnimationFrame/); assert.match(renderer, /setTimeout\([^,]+,\s*125\)/s); }); test('virtual row descriptors stay bounded for 1,500 and 5,000 visible rows', () => { for (const count of [1_500, 5_000]) { const descriptors = describeVirtualRows(syntheticSnapshot(count), { activeDeviceIds: new Set() }); assert.ok(descriptors.length <= 80, `${count} rows mounted ${descriptors.length} descriptors`); assert.equal(descriptors[0].role, 'treeitem'); assert.equal(descriptors[0].aria.level, 3); assert.match(descriptors[0].domId, /^apt-tree-row-/); } }); test('view descriptors preserve selection and refresh proxy/status classes without rebuilding rows', () => { const snapshot = syntheticSnapshot(1_500); const selectedId = snapshot.rows[4].id; const before = describeVirtualRows(snapshot, { activeDeviceIds: new Set() }); const camera = before.find((row) => row.kind === 'camera'); const after = describeVirtualRows(snapshot, { activeDeviceIds: new Set([camera.deviceId]), isOnline: () => true }); assert.ok(after.find((row) => row.key === camera.key).classes.includes('proxy-active')); assert.ok(after.find((row) => row.key === camera.key).classes.includes('online')); assert.equal(snapshot.rows[4].id, selectedId); assert.equal(snapshot.rows.hiddenSelected, true); });