Files
Alta-Proxy-Tool/test/renderer-hierarchy.test.js
T

77 lines
3.9 KiB
JavaScript

'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.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);
});