/** * 浮层索引的作用域、可见性与租约测试。纯屏幕矩形代替DOM测量,检查焦点/组合态保留及旧实例清理不会误删替换实例。 */ import {test} from 'node:test';import assert from 'node:assert/strict';import {createOverlayIndex} from '../../src/lib/canvas-bindings/overlays.ts'; /** 默认title浮层指向a/incarnation,x可移到视口外;viewport固定200×200供可见性比较。 */ const value=(incarnation=1,x=0)=>({placement:'title',target:{id:'a',incarnation},bounds:{x,y:0,width:100,height:30},focused:false,composing:false}),viewport={x:0,y:0,width:200,height:200}; // 两个view可用同portal ID而独立;屏幕外普通项隐藏,focused/composing保留,但身份失效仍必须排除。 test('screen placements are scoped by view, reject stale incarnations and preserve focused or composing offscreen editors',()=>{const a=createOverlayIndex('view-a'),b=createOverlayIndex('view-b');a.register('portal',value());b.register('portal',value(2));assert.equal(a.visible(viewport,target=>target.incarnation===1).length,1);assert.equal(b.visible(viewport,target=>target.incarnation===1).length,0);const lease=a.register('portal',value(1,1000));assert.equal(a.visible(viewport,()=>true).length,0);lease.update({...value(1,1000),composing:true});assert.equal(a.visible(viewport,()=>true).length,1);lease.update({...value(1,1000),focused:true});assert.equal(a.visible(viewport,()=>true).length,1);assert.equal(a.visible(viewport,()=>false).length,0);}); // 同注册ID替换后旧lease.release/update无权改新项,相同测量也不换快照或通知,只有fresh释放才移除。 // baseline 在两次注册之后记录,排除注册本身的通知;strictEqual 检查相等更新复用原快照对象。 test('late old-instance updates or cleanup cannot remove a replacement overlay; unchanged measurements do not notify',()=>{const index=createOverlayIndex('v');let changes=0;const stop=index.subscribe(()=>changes++),old=index.register('same',value()),fresh=index.register('same',value(2));const snapshot=index.getSnapshot(),baseline=changes;old.release();old.update(value());fresh.update(value(2));assert.strictEqual(index.getSnapshot(),snapshot);assert.equal(changes,baseline);fresh.release();assert.equal(index.getSnapshot().length,0);stop();}); // NaN矩形替换被拒绝,原快照与可见项保留;显式clear才清空。 test('invalid overlay replacement leaves the prior placement and index intact',()=>{const index=createOverlayIndex('v');index.register('x',value());const before=index.getSnapshot();assert.throws(()=>index.register('x',{...value(),bounds:{x:NaN,y:0,width:1,height:1}}));assert.strictEqual(index.getSnapshot(),before);assert.equal(index.visible(viewport,()=>true).length,1);index.clear();assert.equal(index.visible(viewport,()=>true).length,0);});