Files

38 lines
2.8 KiB
JavaScript
Raw Permalink Normal View History

const test=require('node:test'),assert=require('node:assert/strict'),fs=require('node:fs'),vm=require('node:vm');
function fixture(){
const space={style:{}},surface={style:{}},label={},frames=new Map();let next=0,observer;
const viewport={clientWidth:1550,clientHeight:601,rect:{width:1550,height:600.5234375},scrollLeft:0,scrollTop:0,
addEventListener(){},getBoundingClientRect(){return this.rect;},
querySelector(s){return s==='.family-map-space'?space:surface;},replaceChildren(){},
scrollTo(x,y){this.scrollLeft=x;this.scrollTop=y;}};
const scope=vm.createContext({$:()=>label,ResizeObserver:class{constructor(fn){observer=fn;}observe(){}},
requestAnimationFrame(fn){frames.set(++next,fn);return next;},cancelAnimationFrame(id){frames.delete(id);}});
vm.runInContext(fs.readFileSync('web/family-map.js','utf8')+';this.MapClass=FamilyMap',scope);
const map=new scope.MapClass(viewport,()=>{});map.graph={width:400,height:264,nodes:[]};
return {map,viewport,space,surface,frames,resize:()=>observer(),flush(){const jobs=[...frames.values()];frames.clear();jobs.forEach(fn=>fn());}};
}
test('fractional fullscreen size never creates a too-tall scroll surface',()=>{
const f=fixture();for(let i=0;i<20;i++){f.map.fit();assert.equal(f.space.style.height,'600px');assert.ok(parseFloat(f.space.style.height)<=f.viewport.rect.height);}
assert.equal(f.surface.style.transform,'translate(575px,168px) scale(1)');
});
test('resize notifications coalesce and unchanged dimensions do not refit',()=>{
const f=fixture();let calls=0;const fit=f.map.fit.bind(f.map);f.map.fit=()=>{calls++;fit();};
f.resize();f.resize();assert.equal(f.frames.size,1);f.flush();assert.equal(calls,1);
f.resize();f.flush();assert.equal(calls,1);
f.viewport.rect.height=700.7;f.viewport.clientHeight=701;f.resize();f.flush();assert.equal(calls,2);
});
test('manual zoom and scroll survive a resize notification',()=>{
const f=fixture();f.map.autoFit=false;f.map.scale=1.5;f.viewport.scrollLeft=15;f.viewport.scrollTop=30;
f.resize();f.flush();assert.equal(f.map.scale,1.5);assert.equal(f.map.autoFit,false);
assert.equal(f.viewport.scrollLeft,15);assert.equal(f.viewport.scrollTop,30);
});
test('hidden canvas does not resize and pending work is cancelled on reset',()=>{
const f=fixture();f.viewport.clientWidth=0;f.resize();f.flush();assert.equal(f.space.style.width,undefined);
f.resize();f.map.reset();assert.equal(f.frames.size,0);assert.equal(f.map.graph,null);
});
test('scaled map clips unscaled overflow and fullscreen fallback clears the sidebar',()=>{
const css=fs.readFileSync('web/family-map.css','utf8');
assert.match(css,/\.family-map-space\{min-width:0;min-height:0;overflow:clip\}/);
assert.match(css,/scrollbar-gutter:stable/);assert.match(css,/\.family-workspace\.family-expanded\{z-index:1100\}/);
});