Files
NODEDC_MISSION_CORE/apps/control-station/test/boardLayout.test.mjs
T

58 lines
3.4 KiB
JavaScript

import test from 'node:test';
import assert from 'node:assert/strict';
import {readFileSync} from 'node:fs';
import ts from 'typescript';
const code=ts.transpileModule(readFileSync(new URL('../../../packages/sensor-ui/src/boardLayout.ts',import.meta.url),'utf8'),{compilerOptions:{module:ts.ModuleKind.ESNext,target:ts.ScriptTarget.ES2022}}).outputText;
const {createBoardLayoutStore,defaultBoardLayout}=await import('data:text/javascript;base64,'+Buffer.from(code).toString('base64'));
const tick=()=>new Promise(resolve=>setImmediate(resolve));
function server(){
let value=structuredClone(defaultBoardLayout);const calls=[];
return {calls,read:async()=>structuredClone(value),patch:async(section,open)=>{
calls.push({section,open});await tick();
value={...value,revision:value.revision+1,open_sections:defaultBoardLayout.open_sections.filter(id=>id===section?open:value.open_sections.includes(id))};
return structuredClone(value);
}};
}
test('rapid toggles persist after navigation and preserve all-closed state',async()=>{
const api=server();const store=createBoardLayoutStore(api);await store.load();
const unsubscribe=store.subscribe(()=>{});
store.change(['settings','devices']);store.change(['devices']);store.change([]);
unsubscribe();
while(store.getSnapshot().saving)await tick();
const reopened=createBoardLayoutStore(api);await reopened.load();
assert.deepEqual(reopened.getSnapshot().value.open_sections,[]);
assert.equal(api.calls.length,3);
});
test('a later toggle wins while an older patch is still in flight',async()=>{
const api=server();const store=createBoardLayoutStore(api);await store.load();
store.change(['settings','devices']);store.change(['computer','settings','devices']);
while(store.getSnapshot().saving)await tick();
assert.deepEqual((await api.read()).open_sections,defaultBoardLayout.open_sections);
});
test('different vehicles and simultaneous section changes do not clobber one another',async()=>{
const api=server(),other=server();const a=createBoardLayoutStore(api),b=createBoardLayoutStore(api),c=createBoardLayoutStore(other);
await Promise.all([a.load(),b.load(),c.load()]);
a.change(['settings','devices']);b.change(['computer','devices']);
while(a.getSnapshot().saving||b.getSnapshot().saving)await tick();
assert.deepEqual((await api.read()).open_sections,['devices']);
assert.deepEqual(c.getSnapshot().value.open_sections,defaultBoardLayout.open_sections);
});
test('failed save reports failure, rolls back, and explicit reload recovers',async()=>{
const api=server();let fail=true;
const store=createBoardLayoutStore({...api,patch:(...args)=>fail?Promise.reject(new Error('offline')):api.patch(...args)});
await store.load();store.change([]);
while(store.getSnapshot().saving)await tick();
assert.ok(store.getSnapshot().error);
assert.deepEqual(store.getSnapshot().value.open_sections,defaultBoardLayout.open_sections);
fail=false;await store.load();store.change(['devices']);
while(store.getSnapshot().saving)await tick();
assert.equal(store.getSnapshot().error,null);
assert.deepEqual((await api.read()).open_sections,['devices']);
});
test('invalid layout cannot overwrite stored preferences',async()=>{
let writes=0;
const store=createBoardLayoutStore({read:async()=>({...defaultBoardLayout,open_sections:['motor-start']}),patch:async()=>{writes++;return defaultBoardLayout;}});
await store.load();store.change([]);
assert.equal(store.getSnapshot().ready,false);assert.ok(store.getSnapshot().error);assert.equal(writes,0);
});