ide-probe.mjs (7948B)
1 import {syntaxProbe} from './syntax-probe.mjs'; 2 // Real single-page workspace. Independent expectations, no substitute evaluator. 3 const el=id=>document.getElementById(id); 4 const assert=(ok,message)=>{if(!ok)throw Error(message);}; 5 const delay=ms=>new Promise(r=>setTimeout(r,ms)); 6 async function wait(fn){const end=Date.now()+90000;while(Date.now()<end){if(fn())return;await delay(20);}throw Error('Timed out: '+el('status').textContent+' / '+el('result').textContent);} 7 const status=s=>wait(()=>el('status').textContent===s); 8 const change=(id,value)=>{el(id).value=value;el(id).dispatchEvent(new Event('change'));}; 9 const input=(id,value)=>{el(id).value=value;el(id).dispatchEvent(new Event('input'));}; 10 export async function run(control,backend='haskell-wasm'){ 11 await status('Ready'); 12 await syntaxProbe(); 13 assert([...el('runtime-backend').options].map(o=>o.value).join(',')==='haskell-wasm,zig-wasm','Only Haskell and Zig are production runtimes'); 14 for(const path of ['worker.js','compiler-worker.js','conformance/worker.js','conformance/compiler-worker.js'])assert((await fetch(new URL('./'+path,document.querySelector('script[type=module][src]').src))).status===404,'JS conformance Workers are not shipped: '+path); 15 assert(el('program-picker').value==='demo:names-values','Names and values loads on arrival'); 16 assert(!document.querySelector('[role=tab],#graph-result,#export-definition'),'No tabs or graph-JSON surface'); 17 assert(!el('result').textContent&&!el('terminal').textContent,'Arrival does not execute'); 18 assert(document.querySelector('footer a[href="https://eversole.co"]').textContent==='James Eversole','Copyright author link'); 19 assert(el('editor').compareDocumentPosition(el('ecosystem'))&Node.DOCUMENT_POSITION_FOLLOWING,'Ecosystem follows editor'); 20 el('settings-toggle').click();assert(!el('settings-panel').hidden&&el('settings-toggle').getAttribute('aria-expanded')==='true','Inspector opens'); 21 el('settings-close').click();assert(el('settings-panel').hidden&&document.activeElement===el('settings-toggle'),'Close returns focus'); 22 change('runtime-backend',backend);await status('Ready'); 23 const requests=async()=>await(await fetch(control+'/requests')).json(); 24 const before=(await requests()).filter(x=>x.endsWith('/compiler.json')||x.endsWith('/data/compiler.dag')).length; 25 assert(before===0,'Arrival does not load compiler assets'); 26 change('program-picker','demo:language-reference');await status('Ready'); 27 change('output-target','definition');change('export','leaf'); 28 assert(el('run').textContent==='Show definition'&&el('mode').disabled,'Stored target is visibly distinct from execution'); 29 for(const [format,want] of [['tricu','t'],['triangle','△'],['dag','tree △\ntree\n'],['ternary','0']]){ 30 change('output-type',format);el('run').click();await wait(()=>!el('copy-output').disabled); 31 assert(el('result').textContent===want,'Stored '+format); 32 assert(/Stored definition leaf.*SHA-256/.test(el('output-provenance').textContent),'Stored provenance'); 33 } 34 assert((await requests()).filter(x=>x.endsWith('/compiler.json')||x.endsWith('/data/compiler.dag')).length===before,'Stored output never initializes the compiler'); 35 change('output-target','result'); 36 const evaluate=async(source,format)=>{input('expression-editor',source);change('output-type',format);el('run').click();await status('Finished');}; 37 for(const [format,want] of [['tricu','(t t t)'],['triangle','(△ △ △)'],['ternary','200']]){ 38 await evaluate('identity (t t t)',format);assert(el('result').textContent===want,'Applied result '+format); 39 assert(/Evaluated result/.test(el('output-provenance').textContent),'Result not mislabeled as a definition'); 40 } 41 await evaluate('identity (t t t)','dag');assert(/tree :n\d+\ntree\n$/.test(el('result').textContent),'Result DAG interface'); 42 change('mode','io');await evaluate('t "tricuIO" (t 1 (t 0 (t t t)))','tricu');assert(el('result').textContent==='(t t t)','IO result, not IO envelope');change('mode','pure'); 43 let copied;const clipboard=Object.getOwnPropertyDescriptor(navigator,'clipboard'); 44 Object.defineProperty(navigator,'clipboard',{configurable:true,value:{writeText:async value=>{copied=value;}}}); 45 const createURL=URL.createObjectURL,click=HTMLAnchorElement.prototype.click;let blob,download; 46 URL.createObjectURL=value=>{blob=value;return createURL(value);};HTMLAnchorElement.prototype.click=function(){download=this.download;}; 47 try{ 48 await evaluate(JSON.stringify('a'.repeat(600)),'tricu');assert(el('result').textContent.length===16384,'Large result has a bounded UI preview'); 49 assert(/Preview: first 16,384/.test(el('output-provenance').textContent),'Partial preview is explicit'); 50 el('copy-output').click();await wait(()=>copied!==undefined);assert(copied.length>16384&&copied.startsWith(el('result').textContent),'Copy contains complete source'); 51 el('download-output').click();assert(download==='result.tri'&&await blob.text()===copied,'Download contains the identical complete source'); 52 const previousOutput=el('result').textContent; 53 const expectedHash=el('bundle-hash').value;el('download-bundle').click();await wait(()=>/Downloaded original/.test(el('bundle-download-status').textContent)); 54 const actual=[...new Uint8Array(await crypto.subtle.digest('SHA-256',await blob.arrayBuffer()))].map(x=>x.toString(16).padStart(2,'0')).join(''); 55 assert(el('result').textContent===previousOutput,'Bundle download does not replace evaluated output'); 56 assert(actual===expectedHash&&download===expectedHash+'.arboricx','Bundle download remains original authenticated bytes'); 57 }finally{URL.createObjectURL=createURL;HTMLAnchorElement.prototype.click=click;if(clipboard)Object.defineProperty(navigator,'clipboard',clipboard);else delete navigator.clipboard;} 58 // An exponentially expanded value stays small as a physically shared DAG. 59 const bindings=['x0 = t t t'];for(let i=1;i<=28;i++)bindings.push(`x${i} = t x${i-1} x${i-1}`); 60 const shared='let\n '+bindings.join('\n ')+'\n in x28'; 61 await evaluate(shared,'dag');assert(el('result').textContent.length<20000,'Shared result is not flattened'); 62 input('expression-editor',shared);change('output-type','tricu');el('run').click();await status('Stopped'); 63 assert(/Expanded tree exceeds 32 MiB/.test(el('result').textContent),'Oversized expansion rejected before building output'); 64 assert(el('copy-output').disabled&&el('download-output').disabled,'No partial result masquerades as a file'); 65 await evaluate('identity 52','number');assert(el('result').textContent==='52','Recovery after result-rendering error'); 66 // Fresh lazy compiler initialization honors the site pin, with no fallback. 67 change('runtime-backend',backend);await status('Ready'); 68 await fetch(control+'/fault',{method:'POST',body:JSON.stringify({compilerPin:true})}); 69 input('expression-editor','identity 53');el('run').click();await status('Stopped'); 70 assert(/pin mismatch|HTTP|fetch/i.test(el('result').textContent),'Expression compiler pin failure is visible'); 71 await fetch(control+'/fault',{method:'POST',body:JSON.stringify({compilerPin:false})}); 72 await evaluate('identity 53','number');assert(el('result').textContent==='53','Retry authenticates the compiler'); 73 input('bundle-hash','not-a-hash');el('program-hash').requestSubmit();await status('Stopped'); 74 assert(el('run').disabled&&el('copy-output').disabled,'Invalid replacement never falls back to an earlier package/output'); 75 change('program-picker','demo:language-reference');await status('Ready'); 76 const live=await(await import('/live-ux-probe.mjs')).run(control,backend,true); 77 change('program-picker','demo:language-reference');await status('Ready'); 78 await(await import('/evaluation-layout-probe.mjs')).evaluationLayout(); 79 return {userAgent:navigator.userAgent,checks:[...live.checks,'single-page arrival/inspector; stored vs applied outputs; t/△/DAG/ternary; IO result; complete copy/download; expansion refusal; immutable bundle; pin failure/retry']}; 80 }