import-picker-probe.mjs (8373B)
1 const assert=(ok,message)=>{if(!ok)throw Error(message);}; 2 const el=id=>document.getElementById(id),delay=ms=>new Promise(r=>setTimeout(r,ms)); 3 const input=(id,value)=>{el(id).value=value;el(id).dispatchEvent(new Event('input'));}; 4 const change=(id,value)=>{el(id).value=value;el(id).dispatchEvent(new Event('change'));}; 5 async function wait(fn,label){for(let i=0;i<4500;i++){if(fn())return;await delay(20);}throw Error('Timeout '+label+' / '+el('status').textContent+' / '+el('import-status').textContent);} 6 const status=value=>wait(()=>el('status').textContent===value,value); 7 export async function run(control,backend='haskell-wasm'){ 8 const base=new URL('../',document.querySelector('script[type=module][src]').src); 9 const {createRuntimeSession}=(await import(new URL('dist/probe-api.js',base))).runtimeSession; 10 const catalogue=await(await fetch(new URL('reference/libraries.json',base))).json(); 11 const lib=name=>catalogue.releases.find(r=>r.name==='tricu-lib-'+name&&r.version==='0.1.0'); 12 const math=lib('native-math'); 13 // Static references and independently specified examples can be consumed without 14 // a page session or a JavaScript module catalogue. 15 const llms=await(await fetch(new URL('llms.txt',location.href))).text(); 16 assert(llms.includes('reference/getting-started.md'),'Agent entrypoint points into this release'); 17 for(const [,path]of llms.matchAll(/\]\((\.\/[^)]+)\)/g))assert((await fetch(new URL(path,location.href))).ok,'Readable static reference '+path); 18 const examples=await(await fetch(new URL('reference/examples.json',base))).json(); 19 const host={before:async()=>{},after:()=>{},write:()=>{},read:async()=>'',running:()=>{}}; 20 const session=await createRuntimeSession(backend,true); 21 try{ 22 for(const example of examples){ 23 let terminal='';await session.compile(example.source,'empty'); 24 const result=await session.runExpression(example.expression,example.output,example.mode,{...host,write:text=>{terminal+=text;}}); 25 assert(result.value===example.expected,'Documented result '+example.id+': '+JSON.stringify(result)); 26 assert(terminal===example.terminal,'Documented terminal '+example.id); 27 } 28 await session.compile('!export answer\nanswer = 42','empty'); 29 const stage=await session.inspectModule(`-- 🐕\n!import ${math.manifest} as N\n!export answer\nanswer = 1`); 30 assert(stage.imports[0][0]===math.manifest&&stage.imports[0][1]==='N','Native header response'); 31 assert((await session.runExpression('answer','number','pure',host)).value==='42','Inspection preserves compiled scope'); 32 let rejected=false;try{await session.inspectModule(`!import ${math.manifest} as\n!export answer\nanswer=1`);}catch{rejected=true;} 33 assert(rejected,'Malformed header rejected'); 34 assert((await session.runExpression('answer','number','pure',host)).value==='42','Failed inspection preserves compiled scope'); 35 }finally{session.dispose();} 36 await status('Ready');change('runtime-backend',backend);await status('Ready'); 37 document.querySelector('[data-compiler-example=module]').click();await status('Compiled'); 38 const original='-- preserve this comment 🐕\n!export answer\nanswer = 42'; 39 input('source-editor',original);await status('Compiled'); 40 const open=async()=>{el('add-import').click();await wait(()=>el('import-results').querySelector('button'),'library list');assert(el('import-dialog').open,'Modal open');}; 41 const select=async()=>{input('import-search','remainder');await wait(()=>el('import-results').querySelector(`[data-manifest="${math.manifest}"]`),'search export');el('import-results').querySelector(`[data-manifest="${math.manifest}"]`).click();}; 42 await open();await select(); 43 assert(el('import-title').textContent==='native-math','Search reaches native math'); 44 assert(el('import-preview').textContent===`!import ${math.manifest} as N`,'Exact full manifest preview'); 45 assert(el('import-version').value==='0.1.0','Newest version initially selected'); 46 assert(el('import-version').options.length===2,'History available in details'); 47 assert(el('import-usage').textContent.includes('multiply')&&el('import-example').textContent.includes('Expected result: 42'),'Calling conventions and tested example available inline'); 48 input('import-alias','Numbers');change('import-version','0.0.9');await wait(()=>!el('insert-import').disabled,'historical release check'); 49 assert(el('import-version').value==='0.0.9'&&el('import-preview').textContent.endsWith('as Numbers'),'History preserves chosen namespace'); 50 change('import-version','0.1.0');input('import-alias','N'); 51 await wait(()=>!el('insert-import').disabled,'candidate compiled'); 52 el('insert-import').click(); 53 assert(!el('import-dialog').open,'Insertion closes dialog'); 54 assert(el('source-editor').value===`!import ${math.manifest} as N\n`+original,'Insertion preserves all original source'); 55 assert(el('source-editor').selectionStart===0&&el('source-editor').selectionEnd===(`!import ${math.manifest} as N\n`).length,'Inserted line selected'); 56 await status('Compiled'); 57 input('expression-editor','N.add answer 8');change('output-type','number');el('run').click();await status('Finished');assert(el('result').textContent==='50','Inserted namespace usable'); 58 // Undo is one native textarea edit; compilation does not replace the editor. 59 el('source-editor').focus();assert(document.execCommand('undo'),'Undo available'); 60 assert(el('source-editor').value===original,'One undo restores exact original source');await status('Compiled'); 61 await open();await select();await wait(()=>!el('insert-import').disabled,'reinsert checked');el('insert-import').click();await status('Compiled'); 62 const inserted=el('source-editor').value; 63 await open();await select();await wait(()=>!el('insert-import').disabled,'existing import'); 64 assert(el('import-status').textContent.includes('Already imported as N'),'Existing namespace shown');el('insert-import').click(); 65 assert(el('source-editor').value===inserted,'Jump does not edit'); 66 // Alias conflict is based on the native header; a failed check leaves the editor intact. 67 await open();input('import-search','conversions');el('import-results').querySelector('button').click();input('import-alias','N'); 68 await wait(()=>el('import-status').textContent.includes('already used'),'namespace conflict');assert(el('insert-import').disabled,'Conflict blocks insert');el('import-cancel').click(); 69 assert(el('source-editor').value===inserted,'Cancellation preserves source'); 70 input('source-editor','!export answer\nanswer = (');await status('Compilation failed'); 71 await open();await select();await wait(()=>el('import-status').textContent.includes('copy the import'),'malformed source fallback');assert(el('insert-import').disabled,'Malformed source blocks insertion');el('import-cancel').click(); 72 input('source-editor',original);await status('Compiled'); 73 await open();await select();el('import-cancel').click();await delay(400);assert(!el('import-dialog').open,'Check cancelled');assert(el('source-editor').value===original,'Cancellation is an edit-free operation'); 74 // Local filenames are text and their validated interfaces are searchable. 75 const bytes=await(await fetch('/_arboricx/bundle/hash/'+math.hash)).arrayBuffer(); 76 const files=new DataTransfer();files.items.add(new File([bytes],'<svg onload=bad>.arboricx'));el('local-module-file').files=files.files;el('local-module-file').dispatchEvent(new Event('change')); 77 await wait(()=>el('local-module-list').children.length===1&&!el('local-module-file').disabled,'local validation');await status('Compiled'); 78 await open();input('import-search','<svg');assert(!el('import-results').querySelector('svg'),'Filename is text');el('import-results').querySelector('button').click(); 79 assert(el('import-version').hidden,'Local file has no invented version');await wait(()=>!el('insert-import').disabled,'local import check');el('insert-import').click();await status('Compiled'); 80 input('expression-editor','Local.add 5 6');el('run').click();await status('Finished');assert(el('result').textContent==='11','Local library from unified picker'); 81 return {userAgent:navigator.userAgent,checks:['static agent references; complete published interfaces; general documented examples; native read-only header inspection; export search; exact imports; undo; existing import jump; namespace conflicts; malformed source copy fallback; cancellation; local-file discovery'],backend}; 82 }