Track view-scoped overlay placements and retain portalled editors

This commit is contained in:
2026-11-09 18:28:13 +08:00
parent e6b9999202
commit 2f385433be
9 changed files with 73 additions and 6 deletions
+5
View File
@@ -0,0 +1,5 @@
import {test} from 'node:test';import assert from 'node:assert/strict';import {createOverlayIndex} from '../../src/lib/canvas-bindings/overlays.ts';
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};
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);});
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();});
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);});
+1
View File
@@ -344,6 +344,7 @@ try {
await until(`!!document.querySelector('[aria-label="分区名称"]')`,'frame title editor did not open');
await click('收起子画布');await until(`!document.querySelector('.react-flow__node[data-id=b]')&&document.querySelectorAll('.react-flow__edge').length>0`,'collapsed partition did not render its upper-canvas connection');assert.equal(documents.get(root).elements.get('edge').target.elementId,'b');assert.ok(documents.get(root).elements.has('b'));await click('展开子画布');await until(`!!document.querySelector('.react-flow__node[data-id=b] textarea')`,'expanding partition did not restore the member');
console.log('PASS collapsed partitions retain model endpoints and render upper-canvas connections through headless frame anchors');
assert.ok(await evaluate(`document.querySelector('[aria-label="分区名称"]').closest('[data-canvas-overlay-target]')?.dataset.canvasOverlayView`),'portalled frame title did not register its explicit view/target placement');
await evaluate(`(()=>{window.pinnedFrameTitle=document.querySelector('[aria-label="分区名称"]');window.pinnedFrameTitle.focus();window.pinnedFrameTitle.select();})()`);await call('Input.insertText',{text:'Pinned partition draft'});
const panFar=`document.querySelector('.react-flow__pane').dispatchEvent(new WheelEvent('wheel',{deltaX:20000,deltaY:20000,bubbles:true,cancelable:true}))`,panHome=`(()=>{const matrix=new DOMMatrix(getComputedStyle(document.querySelector('.react-flow__viewport')).transform);document.querySelector('.react-flow__pane').dispatchEvent(new WheelEvent('wheel',{deltaX:matrix.e/.5,deltaY:matrix.f/.5,bubbles:true,cancelable:true}));})()`;
await evaluate(panFar);await pause(250);assert.equal(await evaluate(`window.pinnedFrameTitle.isConnected&&document.activeElement===window.pinnedFrameTitle&&window.pinnedFrameTitle.value==='Pinned partition draft'`),true,'viewport culling discarded an unsaved frame title');