45 lines
3.4 KiB
JavaScript
45 lines
3.4 KiB
JavaScript
const test=require('node:test'),assert=require('node:assert/strict'),fs=require('node:fs');
|
|
const charts=()=>import('./frontend/src/composables/charts.mjs');
|
|
test('dashboard counts physical devices once, not lenses; handles missing network metadata',async()=>{
|
|
const {summarizeDevices}=await charts();
|
|
assert.equal(summarizeDevices().total,0);
|
|
const result=summarizeDevices({assets:[{id:'camera',networkAccess:'tailscale'}],cameras:[{assetId:'camera'},{assetId:'camera'}],recorders:[{networkAccess:'lan'}],devices:[{deviceType:'灯'}, {deviceType:'灯',networkAccess:'lan'}]});
|
|
assert.equal(result.total,4); assert.equal(result.types.find(x=>x.label==='灯').count,2);
|
|
assert.equal(result.networks.find(x=>x.label==='待登记').count,1);
|
|
});
|
|
test('chart aggregates remain numerical and empty charts are valid',async()=>{
|
|
const {bars,distribution}=await charts();
|
|
assert.deepEqual(bars().series[0].data,[]);
|
|
assert.equal(distribution([{label:'a',count:-2}]).series[0].data[0].value,0);
|
|
assert.equal(bars([{label:'a',count:'3'}]).series[0].data[0].value,3);
|
|
});
|
|
test('network chart cannot draw phantom parent or cyclic connections',async()=>{
|
|
const {networkChart}=await charts();
|
|
const option=networkChart([{device:{id:'r',name:'路由',source:'device'},depth:0},{device:{id:'c',name:'摄像头',uplinkId:'r',networkHosts:['192.168.8.160']},depth:1},{device:{id:'x',name:'独立',uplinkId:'missing'},depth:0}]);
|
|
assert.deepEqual(option.series[0].links,[{source:'r',target:'c'}]);
|
|
assert.equal(option.series[0].data.length,3);assert.equal(option.series[0].data[1].targetId,'c');
|
|
assert.match(option.series[0].data[1].caption,/192\.168\.8\.160/);
|
|
assert.equal(option.series[0].layout,'none');
|
|
});
|
|
test('platform graph only includes supplied authorized households and no device leakage',async()=>{
|
|
const {platformChart}=await charts();
|
|
const series=platformChart([{id:'one',name:'家庭A',deviceCount:2}],'本平台').series[0];
|
|
assert.deepEqual(series.data.map(x=>x.id),['platform','home:one']);
|
|
assert.equal(series.data[1].targetId,'one');assert.equal(series.links.length,1);
|
|
});
|
|
test('ECharts runtime is local, lazy, deterministic and releases resources',()=>{
|
|
const runtime=fs.readFileSync('frontend/src/composables/charts-runtime.js','utf8'),widget=fs.readFileSync('frontend/src/components/LocalChart.vue','utf8');
|
|
assert.match(runtime,/echarts\/core/);assert.doesNotMatch(runtime,/https?:\/\//);
|
|
assert.match(widget,/import\('\.\.\/composables\/charts-runtime\.js'\)/);
|
|
assert.match(widget,/animation: false/);assert.match(widget,/fingerprint !== last/);
|
|
assert.match(widget,/renderMode: 'richText'/);assert.match(widget,/onBeforeUnmount/);
|
|
assert.match(widget,/resizeObserver\?\.disconnect/);assert.match(widget,/chart\?\.dispose/);
|
|
});
|
|
test('deployed entry and controller dependencies have no remote CDN references',()=>{
|
|
const html=fs.readFileSync('web/ui/index.html','utf8');
|
|
for(const match of html.matchAll(/(?:src|href)="([^"]+)"/g))assert.ok(match[1].startsWith('/'),match[1]);
|
|
const scripts=JSON.parse(fs.readFileSync('frontend/src/compat/scripts.json','utf8'));
|
|
for(const script of scripts){assert.ok(script.startsWith('/')&&!script.startsWith('//'));assert.ok(fs.existsSync('web'+script));}
|
|
for(const file of ['frontend/src/main.js','frontend/src/dashboard.css','frontend/src/theme.css','frontend/src/bootstrap-polish.css'])assert.doesNotMatch(fs.readFileSync(file,'utf8'),/(?:@import\s+|(?:src|href)\s*=\s*|url\(\s*['"]?)https?:\/\//);
|
|
});
|