58 lines
3.4 KiB
JavaScript
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);
|
|
});
|