import-fold-inputs.mjs (5706B)
1 // Trusted input on the packaged Playground's canonical editor document. 2 import assert from 'node:assert/strict'; 3 import {writeFile} from 'node:fs/promises'; 4 const pause=ms=>new Promise(r=>setTimeout(r,ms)); 5 export async function importFoldInputs({send,evaluate,report,prefix}){ 6 const e='globalThis.__sourceEditor',initial=report.source; 7 const wait=async condition=>{for(let i=0;!await evaluate(condition);i++){assert(i<1600,condition);await pause(25);}}; 8 const key=async(key,code,vk,modifiers=0)=>{await send('Input.dispatchKeyEvent',{type:'keyDown',key,code,windowsVirtualKeyCode:vk,modifiers,...(key==='Enter'?{text:'\r'}:{})});await send('Input.dispatchKeyEvent',{type:'keyUp',key,code,windowsVirtualKeyCode:vk,modifiers});await pause(30);}; 9 const point=async expression=>evaluate(`(()=>{const element=${expression};element.scrollIntoView({block:'center',behavior:'instant'});const r=element.getBoundingClientRect();return {x:r.left+r.width*.25,y:r.top+r.height/2};})()`); 10 const click=async expression=>{const p=await point(expression);await send('Input.dispatchMouseEvent',{type:'mousePressed',...p,button:'left',clickCount:1});await send('Input.dispatchMouseEvent',{type:'mouseReleased',...p,button:'left',clickCount:1});await pause(50);}; 11 const widget=`${e}.dom.querySelector('.import-hash')`,alias=`[...${e}.dom.querySelectorAll('.syntax-name')].find(span=>span.textContent==='N')`; 12 const counts=`({folded:${e}.dom.querySelectorAll('.import-hash').length,full:${e}.dom.querySelectorAll('.import-hash-full').length})`; 13 await send('Browser.grantPermissions',{origin:await evaluate('location.origin'),permissions:['clipboardReadWrite','clipboardSanitizedWrite']}); 14 for(const width of [390,1440]){ 15 await send('Emulation.setDeviceMetricsOverride',{width,height:1000,deviceScaleFactor:1,mobile:width<741}); 16 await evaluate(`${e}.replaceSource(${JSON.stringify(initial)},true)`);await wait("document.getElementById('status').textContent==='Compiled'"); 17 assert.deepEqual(await evaluate(counts),{folded:3,full:0}); 18 await click(widget);assert.deepEqual(await evaluate(counts),{folded:2,full:1},'Folded click moves cursor to import'); 19 await click(alias);assert.deepEqual(await evaluate(counts),{folded:2,full:1}); 20 assert.equal(await evaluate(`${e}.getSelection().from`),initial.indexOf(' as N')+4,'Caret maps past hidden 64-character hash'); 21 await send('Input.insertText',{text:'M'});await key('z','KeyZ',90,2);assert.equal(await evaluate(`${e}.getSource()`),initial,'Typing undo'); 22 await evaluate(`${e}.select(${initial.indexOf('!import')},${initial.indexOf('!import '+report.math+' as P')})`); 23 assert.deepEqual(await evaluate(counts),{folded:2,full:1}); 24 await key('c','KeyC',67,2);const selected=await evaluate('navigator.clipboard.readText()');assert(selected.includes(report.math)&&!selected.includes('...'),'Partial selection copies complete hashes'); 25 await key('a','KeyA',65,2);await key('c','KeyC',67,2);assert.equal(await evaluate('navigator.clipboard.readText()'),initial,'Native full-source clipboard'); 26 await evaluate(`${e}.select(0)`);const inserted=`!import ${report.math} as Q\n`; 27 await send('Input.insertText',{text:inserted});await pause(30);assert.equal((await evaluate(counts)).folded,4,'Pasted import gains fold'); 28 await key('z','KeyZ',90,2);assert.equal(await evaluate(`${e}.getSource()`),initial,'Undo restores source after dynamic decoration'); 29 await key('Z','KeyZ',90,10);assert.equal(await evaluate(`${e}.getSource()`),inserted+initial,'Redo restores source after dynamic decoration');await key('z','KeyZ',90,2); 30 await evaluate(`${e}.select(${initial.length})`);const p=await point(alias); 31 await send('Input.dispatchTouchEvent',{type:'touchStart',touchPoints:[p]});await send('Input.dispatchTouchEvent',{type:'touchEnd',touchPoints:[]});await pause(80); 32 assert.deepEqual(await evaluate(counts),{folded:2,full:1},'Touch reveals only the touched import line'); 33 await evaluate(`${e}.select(${initial.indexOf(report.math)+64})`);await key('Backspace','Backspace',8);assert.equal((await evaluate(`${e}.getSource()`)).length,initial.length-1,'Hash editing deletes one character');await key('z','KeyZ',90,2);assert.equal(await evaluate(`${e}.getSource()`),initial); 34 await wait("document.getElementById('status').textContent==='Compiled'");await evaluate(`${e}.setAvailability(true,false)`); 35 await send('Input.insertText',{text:'MUST NOT EDIT'});assert.equal(await evaluate(`${e}.getSource()`),initial,'Busy/read-only blocks native edits');await evaluate(`${e}.setAvailability(false,false)`); 36 if(prefix){const {cssContentSize:r}=await send('Page.getLayoutMetrics');const shot=await send('Page.captureScreenshot',{format:'png',captureBeyondViewport:true,clip:{x:0,y:0,width:r.width,height:r.height,scale:1}});await writeFile(prefix+'-folds-'+width+'.png',Buffer.from(shot.data,'base64'));} 37 } 38 // Enter inserts a plain newline; read-only state also applies to command keys. 39 await evaluate(`${e}.replaceSource(' answer = 42',true);${e}.select(${(' answer = 42').length})`);await key('Enter','Enter',13); 40 assert.equal(await evaluate(`${e}.getSource()`),' answer = 42\n','Enter inserts exactly one newline'); 41 await evaluate(`${e}.setAvailability(true,false)`);await key('Enter','Enter',13);assert.equal(await evaluate(`${e}.getSource()`),' answer = 42\n','Read-only also rejects Enter'); 42 await evaluate(`${e}.setAvailability(false,false);${e}.replaceSource(${JSON.stringify(initial)},true)`);await wait("document.getElementById('status').textContent==='Compiled'"); 43 report.checks.push('trusted per-line mouse/touch; canonical alias caret; full and selected-source clipboard; pasted-import native undo/redo; read-only at 390/1440px'); 44 }