From b1ce1927022d374198441afafeb27199fc8a052a Mon Sep 17 00:00:00 2001 From: eunhhu Date: Thu, 24 Sep 2026 17:14:54 +0100 Subject: Establish native micro runtime checkpoint --- prototypes/workbench/qa-release.js | 59 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 prototypes/workbench/qa-release.js (limited to 'prototypes/workbench/qa-release.js') diff --git a/prototypes/workbench/qa-release.js b/prototypes/workbench/qa-release.js new file mode 100644 index 0000000..1674c5b --- /dev/null +++ b/prototypes/workbench/qa-release.js @@ -0,0 +1,59 @@ +// Playwright CLI run-code. Covers release routing and stale release coordinates. +async page => { + const checks = []; + const state = () => page.evaluate(() => window.pureWorkbench.snapshot()); + const check = (ok, label) => checks.push({label, passed:Boolean(ok)}); + const near = (a,b) => Math.abs(a-b)<0.1; + const client = (x,y) => page.evaluate(({x,y}) => { + const p = new DOMPoint(x,y).matrixTransform(document.querySelector('#scene').getScreenCTM()); + return {x:p.x,y:p.y}; + },{x,y}); + const move = async(x,y) => { const p=await client(x,y); await page.mouse.move(p.x,p.y); }; + const start = async() => { + await page.locator('#reset').click(); await page.locator('#scene').scrollIntoViewIfNeeded(); + await move(90,170); await page.mouse.down(); await move(370,230); + }; + await page.setViewportSize({width:1440,height:1000}); + await page.waitForFunction(() => window.pureWorkbench?.snapshot().ready); + await page.locator('#example').selectOption('direct'); + await page.locator('#load-example').click(); await page.locator('#apply').click(); + await page.evaluate(() => document.querySelector('#scene').addEventListener('pointerdown',e=>{ window.__releaseID=e.pointerId; })); + await start(); await page.mouse.up(); let s=await state(); + check(near(s.states.x,360)&&near(s.states.y,220)&&!s.held.length&&s.lastEvent.phase==='up','ordinary in-scene release retains the displayed position'); + await start(); await move(750,400); await page.mouse.up(); s=await state(); + check(near(s.states.x,564)&&near(s.states.y,276)&&!s.held.length&&s.lastEvent.phase==='up','ordinary release outside the scene retains the clamped position'); + for (const scenario of ['document-pointerup','child-nonbubbling-up','mouse-only-up','stale-scene-up']) { + await start(); + const position=await client(90,170); // Deliberately stale press coordinates. + await page.evaluate(({scenario,position})=>{ + const scene=document.querySelector('#scene'), tile=document.querySelector('[data-point-input="drag"]'); + const fields={pointerId:window.__releaseID,pointerType:'mouse',button:0,buttons:0,clientX:position.x,clientY:position.y,bubbles:scenario!=='child-nonbubbling-up'}; + if(scenario==='mouse-only-up') window.dispatchEvent(new MouseEvent('mouseup',fields)); + else (scenario==='document-pointerup'?document:scenario==='child-nonbubbling-up'?tile:scene).dispatchEvent(new PointerEvent('pointerup',fields)); + if(scene.hasPointerCapture(window.__releaseID)) scene.releasePointerCapture(window.__releaseID); + },{scenario,position}); + await page.mouse.up(); s=await state(); + check(near(s.states.x,360)&&near(s.states.y,220)&&!s.held.length&&s.lastEvent.phase==='up',`${scenario}: release commits once without a coordinate jump or later cancel`); + check(s.successfulEvents===3,`${scenario}: duplicate release/capture loss produces no extra authored event`); + } + await start(); + await page.evaluate(()=>document.querySelector('[data-point-input="drag"]').dispatchEvent(new PointerEvent('lostpointercapture',{pointerId:window.__releaseID,bubbles:true}))); + s=await state(); check(s.held.includes('drag')&&near(s.states.x,360),'descendant capture-loss notification cannot cancel the root owner'); + await page.mouse.up(); + await start(); + await page.evaluate(()=>document.querySelector('#scene').releasePointerCapture(window.__releaseID)); + await move(380,240); s=await state(); + check(!s.held.length&&s.states.x===80&&s.states.y===160&&s.lastEvent.phase==='cancel','actual root capture loss while held still cancels'); + await page.mouse.up(); + await start(); + await page.evaluate(()=>document.dispatchEvent(new PointerEvent('pointercancel',{pointerId:window.__releaseID,bubbles:true}))); + await page.mouse.up(); s=await state(); + check(!s.held.length&&s.states.x===80&&s.states.y===160&&s.lastEvent.phase==='cancel','cancellation outside the scene still rolls back and ignores the late up'); + await start(); + await page.evaluate(()=>window.dispatchEvent(new Event('blur'))); + await page.mouse.up(); s=await state(); + check(!s.held.length&&s.states.x===80&&s.states.y===160&&s.lastEvent.phase==='cancel','focus loss still cancels rather than committing'); + const result={checks,passed:checks.filter(c=>c.passed).length,failed:checks.filter(c=>!c.passed)}; + await page.evaluate(result=>{window.__pureReleaseEvidence=result;},result); + return result; +} -- cgit v1.2.3