definition-name-inputs.mjs (8937B)
1 // Trusted Chromium input, complementary to the cross-browser DOM/geometry probe. 2 import assert from 'node:assert/strict'; 3 import {writeFile} from 'node:fs/promises'; 4 const pause=ms=>new Promise(resolve=>setTimeout(resolve,ms)); 5 export async function definitionNameInputs({send,evaluate,report,prefix}){ 6 const target=`document.querySelector('[data-definition="answer"]')`; 7 const closed=`document.querySelector('.definition-card').hidden`; 8 const wait=async(expression,label)=>{for(let i=0;i<800;i++){if(await evaluate(expression))return;await pause(25);}throw Error('Definition input timeout: '+label+' / '+JSON.stringify(await evaluate(`({active:document.activeElement.outerHTML.slice(0,300),hidden:document.querySelector('.definition-card').hidden,result:document.querySelector('.definition-card pre').textContent,status:document.getElementById('status').textContent})`)));}; 9 const point=async selector=>evaluate(`(()=>{const e=${selector};e.scrollIntoView({block:'center',behavior:'instant'});const r=e.getBoundingClientRect();return {x:r.x+r.width/2,y:r.y+r.height/2};})()`); 10 const mouse=async selector=>{ 11 const {x,y}=await point(selector); 12 await send('Input.dispatchMouseEvent',{type:'mousePressed',x,y,button:'left',clickCount:1}); 13 await send('Input.dispatchMouseEvent',{type:'mouseReleased',x,y,button:'left',clickCount:1}); 14 }; 15 const key=async(key,code,windowsVirtualKeyCode,modifiers=0)=>{ 16 await send('Input.dispatchKeyEvent',{type:'keyDown',key,code,windowsVirtualKeyCode,modifiers,...(key==='Enter'?{text:'\r',unmodifiedText:'\r'}:key===' '?{text:' ',unmodifiedText:' '}: {})}); 17 await send('Input.dispatchKeyEvent',{type:'keyUp',key,code,windowsVirtualKeyCode,modifiers}); 18 }; 19 const close=async()=>{ 20 if(!await evaluate(closed))await mouse(`document.querySelector('[aria-label="Close definition inspection"]')`); 21 await wait(closed,'close'); 22 }; 23 const inspect=async()=>wait(`!${closed}&&!document.querySelector('.definition-card select').disabled&&document.querySelector('.definition-card pre').textContent==='42'`,'actual value'); 24 for(const width of [390,1440]){ 25 await send('Emulation.setDeviceMetricsOverride',{width,height:1000,deviceScaleFactor:1,mobile:width<741}); 26 await evaluate("new Promise(resolve=>requestAnimationFrame(()=>requestAnimationFrame(resolve)))"); 27 await mouse(target);await inspect(); 28 assert(await evaluate("document.activeElement!==globalThis.__sourceEditor.view.contentDOM"),'Inspection keeps focus outside the editing surface'); 29 const expected=await evaluate("globalThis.__sourceEditor.getSource()"); 30 await mouse(`document.querySelector('.edit-definition-name')`); 31 assert(await evaluate("document.activeElement===globalThis.__sourceEditor.view.contentDOM&&globalThis.__sourceEditor.getSource().slice(globalThis.__sourceEditor.getSelection().from,globalThis.__sourceEditor.getSelection().to)==='answer'"),'Trusted Edit name focuses and selects the native identifier'); 32 await send('Input.insertText',{text:'renamed'}); 33 assert.equal(await evaluate("globalThis.__sourceEditor.getSource()"),expected.replace('answer =','renamed ='),'Native typing replaces only the declaration name'); 34 await key('z','KeyZ',90,2); 35 assert.equal(await evaluate("globalThis.__sourceEditor.getSource()"),expected,'Rename has native undo'); 36 await wait("document.getElementById('status').textContent==='Compiled'",'undo compile'); 37 await evaluate(`globalThis.__sourceEditor.focus()`);await key('Tab','Tab',9); 38 assert(await evaluate(`document.activeElement===${target}`),'Tab reaches the declaration name'); 39 await key('Enter','Enter',13);await inspect(); 40 assert(await evaluate("document.activeElement===document.querySelector('.definition-card pre')"),'Keyboard inspection focuses the result'); 41 await key('Escape','Escape',27); 42 assert(await evaluate(`${closed}&&document.activeElement===${target}`),'Escape returns keyboard focus to the definition name / '+JSON.stringify(await evaluate(`({active:document.activeElement.outerHTML.slice(0,250),hidden:${closed},target:${target}.outerHTML,scroll:document.getElementById('source-editor').scrollTop,status:document.getElementById('status').textContent})`))); 43 await key(' ','Space',32);await inspect();await close(); 44 // Clicking the RHS places the editing caret. 45 const rhs=await evaluate("(()=>{const s=[...document.querySelectorAll('.cm-content .syntax-definition')].find(s=>s.textContent==='answer').getBoundingClientRect();return {x:s.right+parseFloat(getComputedStyle(document.getElementById('source-editor')).fontSize)*1.5,y:s.y+s.height/2};})()"); 46 await send('Input.dispatchMouseEvent',{type:'mousePressed',...rhs,button:'left',clickCount:1}); 47 await send('Input.dispatchMouseEvent',{type:'mouseReleased',...rhs,button:'left',clickCount:1}); 48 assert(await evaluate(`${closed}&&document.activeElement===globalThis.__sourceEditor.view.contentDOM`),'RHS click edits normally'); 49 const p=await point(target); 50 await send('Input.dispatchTouchEvent',{type:'touchStart',touchPoints:[{...p,radiusX:2,radiusY:2}]}); 51 await send('Input.dispatchTouchEvent',{type:'touchEnd',touchPoints:[]});await inspect(); 52 assert(await evaluate("document.activeElement!==globalThis.__sourceEditor.view.contentDOM"),'Touch inspection leaves the editing keyboard closed'); 53 await close(); 54 55 const hold=await point(target); 56 await send('Input.dispatchTouchEvent',{type:'touchStart',touchPoints:[{...hold,radiusX:2,radiusY:2}]}); 57 await pause(750);await send('Input.dispatchTouchEvent',{type:'touchEnd',touchPoints:[]});await pause(100); 58 assert(await evaluate(closed),'Real touch hold does not inspect'); 59 assert(await evaluate("document.activeElement===globalThis.__sourceEditor.view.contentDOM&&globalThis.__sourceEditor.getSource().slice(globalThis.__sourceEditor.getSelection().from,globalThis.__sourceEditor.getSelection().to)==='answer'"),'Touch hold selects the identifier in the native editor / '+JSON.stringify(await evaluate(`({active:document.activeElement.outerHTML.slice(0,200),start:globalThis.__sourceEditor.getSelection().from,end:globalThis.__sourceEditor.getSelection().to,target:${target}.outerHTML})`))); 60 await evaluate('document.getSelection().removeAllRanges()'); 61 await evaluate(`globalThis.__sourceEditor.select(0);${target}.focus({preventScroll:true})`); 62 const pan=await point(target); 63 await send('Input.dispatchTouchEvent',{type:'touchStart',touchPoints:[pan]}); 64 await send('Input.dispatchTouchEvent',{type:'touchMove',touchPoints:[{x:pan.x,y:pan.y+40}]}); 65 await pause(550);await send('Input.dispatchTouchEvent',{type:'touchEnd',touchPoints:[]}); 66 assert(await evaluate(`${closed}&&globalThis.__sourceEditor.getSelection().from===0&&globalThis.__sourceEditor.getSelection().to===0`),'Touch panning cancels the hold without selecting or inspecting'); 67 // Drag from ordinary text across a clickable name using editor selection. 68 const drag=await evaluate("(()=>{const w=document.getElementById('source-editor'),a=w.querySelector('.syntax-directive').getBoundingClientRect(),b=w.querySelector('[data-definition=identity]').getBoundingClientRect();return {from:{x:a.left+2,y:a.top+a.height/2},to:{x:b.left+b.width/2,y:b.top+b.height/2}};})()"); 69 await send('Input.dispatchMouseEvent',{type:'mousePressed',...drag.from,button:'left',clickCount:1}); 70 await send('Input.dispatchMouseEvent',{type:'mouseMoved',...drag.to,button:'left',buttons:1}); 71 await send('Input.dispatchMouseEvent',{type:'mouseReleased',...drag.to,button:'left',clickCount:1}); 72 assert(await evaluate(`${closed}&&globalThis.__sourceEditor.getSource().slice(globalThis.__sourceEditor.getSelection().from,globalThis.__sourceEditor.getSelection().to).includes('answer = 42')`),'Native drag selection crosses name decorations without inspecting'); 73 // Select an identifier range in the canonical document. 74 await evaluate("(()=>{const e=globalThis.__sourceEditor,start=e.getSource().indexOf('identity x');e.select(start,start+14);})()"); 75 assert(await evaluate("globalThis.__sourceEditor.getSelection().to>globalThis.__sourceEditor.getSelection().from"),'Native source selections survive name decoration'); 76 if(prefix){ 77 await evaluate("document.activeElement.blur();window.scrollTo({top:0,behavior:'instant'})"); 78 const capture=async suffix=>{ 79 const {cssContentSize:r}=await send('Page.getLayoutMetrics'); 80 const shot=await send('Page.captureScreenshot',{format:'png',captureBeyondViewport:true,clip:{x:0,y:0,width:r.width,height:r.height,scale:1}}); 81 await writeFile(prefix+'-'+suffix+'-'+width+'.png',Buffer.from(shot.data,'base64')); 82 }; 83 await capture('names');await mouse(target);await inspect();await capture('card');await close(); 84 } 85 } 86 report.checks.push('trusted mouse/touch name activation without editing keyboard; native Edit name typing/undo; Enter/Space/Escape focus; RHS caret placement; touch-hold name selection; panning cancellation; native cross-name drag selection at 390/1440px'); 87 }