site.test.mjs (6811B)
1 import test from 'node:test'; 2 import assert from 'node:assert/strict'; 3 import {readFile,access} from 'node:fs/promises'; 4 const here=new URL('../',import.meta.url); 5 const read=file=>readFile(new URL(file,here),'utf8'); 6 test('single flowing editor starts before ecosystem, without tabs or graph JSON UI',async()=>{ 7 const html=await read('index.html'); 8 assert.doesNotMatch(html,/role="tab|data-open-pane|id="graph-result"|id="export-definition"|class="run-tools"/); 9 assert.ok(html.indexOf('class="hero wrap"')<html.indexOf('id="editor"')); 10 assert.ok(html.indexOf('id="editor"')<html.indexOf('id="ecosystem"')); 11 const ids=[...html.matchAll(/\bid="([^"]+)"/g)].map(m=>m[1]);assert.equal(ids.length,new Set(ids).size); 12 const choices=html.match(/<select id="output-type">([\s\S]*?)<\/select>/)[1]; 13 for(const value of ['tricu','triangle','dag','string','number','record'])assert.ok(choices.includes(`value="${value}"`)); 14 assert.doesNotMatch(choices,/value="tree"|value="list"/); 15 assert.match(choices,/<option value="triangle">△ source<\/option>/); 16 assert.doesNotMatch(choices,/Lambada/i); 17 assert.match(html,/id="output-target"/);assert.match(html,/Stored definition/); 18 const app=await read('src/app.ts');assert.match(app,/void initializePackages\(\)/);assert.doesNotMatch(app,/pane-change|activePane/); 19 await assert.rejects(access(new URL('dist/tabs.js',here)));await assert.rejects(access(new URL('dist/export-app.js',here))); 20 }); 21 test('editor preserves authoring, IO, package and immutable download controls',async()=>{ 22 const html=await read('index.html'); 23 for(const id of ['source-editor','expression-editor','compile','stop','restore-source','output-type','mode','step-mode','input-form','line-input','next','arguments','raw-arguments','use-guided-inputs','download-bundle','package-version','program-hash','share'])assert.ok(html.includes(`id="${id}"`),id); 24 assert.match(html,/Edits compile after a pause/);assert.match(html,/Stored definitions use the original bundle records/); 25 assert.doesNotMatch(html,/id="edit-source"|compile-consent|slow-compilation|uncapped|execution-limits/); 26 const app=await read('src/app.ts');assert.match(app,/sourceEditor.classList.toggle\('modified', sourceModified\)/); 27 assert.match(app,/sourceEditor.value !== compiledSource/); 28 assert.match(app,/data.hash !== selection\?\.hash/); 29 assert.match(app,/r\.name === name && r\.version === versionPicker\.value/); 30 }); 31 test('inspector supports mobile modality, escape, focus return and focus containment',async()=>{ 32 const html=await read('index.html'),controller=await read('src/inspector.ts'); 33 assert.match(html,/aria-controls="settings-panel" aria-expanded="false"/); 34 for(const token of ['aria-modal','aria-expanded','element.inert','Escape','Tab','toggle.focus()','getClientRects'])assert.ok(controller.includes(token),token); 35 const css=await read('style.css');assert.match(css,/\.workspace\{width:100%;max-width:none;margin:0;border-left:0;border-right:0;border-radius:0/); 36 assert.match(css,/#source-editor\{font-size:16px;line-height:27px;padding:16px;width:100%/); 37 assert.match(css,/prefers-reduced-motion/); 38 }); 39 test('ecosystem remains with copyright and footer feedback',async()=>{ 40 const html=await read('index.html');assert.equal((html.match(/<article>/g)||[]).length,6); 41 assert.match(html,/Copyright 2026 <a href="https:\/\/eversole.co">James Eversole<\/a>/); 42 assert.doesNotMatch(html,/>Demo documentation<\/a>/); 43 assert.doesNotMatch(html,/Arboricx and tricu are research experiments|was discovered by|Johannes Bader/); 44 for(const match of html.matchAll(/href="https:\/\/git\.eversole\.co\/James\/tricu\/file\/([^"#]+)\.html(?:#[^"]*)?"/g))await access(new URL('../../../'+match[1],import.meta.url)); 45 }); 46 test('conformance workers retain unbounded execution and shared transport limits',async()=>{ 47 const {BROWSER_BUDGET,PACKAGE_LOAD_MS,EXPORT_ACTIVE_MS}=await import('../dist/execution.js'); 48 assert.deepEqual(BROWSER_BUDGET,{fuel:Infinity,nodes:Infinity,stack:Infinity});assert.equal(PACKAGE_LOAD_MS,60000);assert.equal(EXPORT_ACTIVE_MS,120000); 49 for(const file of ['src/conformance/worker.ts','src/conformance/compiler-worker.ts']){ 50 const source=await read(file);assert.match(source,/const budget = BROWSER_BUDGET/);assert.doesNotMatch(source,/executionSettings|executionBudget|\['limits'\]|\['uncapped'\]/); 51 } 52 for(const file of ['src/protocol.ts','src/compiler-protocol.ts','src/app.ts'])assert.doesNotMatch(await read(file),/ExecutionLimits|readSettings|limits-change|activeMilliseconds/); 53 await assert.rejects(access(new URL('dist/limits-ui.js',here))); 54 }); 55 56 test('evaluation actions follow their expression and precede output, outside the sticky toolbar',async()=>{ 57 const html=await read('index.html'); 58 const toolbar=html.slice(html.indexOf('class="editor-toolbar"'),html.indexOf('id="editor-body"')); 59 assert.doesNotMatch(toolbar,/id="run"/);assert.match(toolbar,/id="stop" disabled hidden/); 60 assert.ok(html.indexOf('id="expression-editor"')<html.indexOf('id="run"')); 61 assert.ok(html.indexOf('id="run"')<html.indexOf('class="output-panel"')); 62 assert.ok(html.indexOf('class="output-panel"')<html.indexOf('id="settings-panel"')); 63 assert.match(html,/id="stop-run"/); 64 }); 65 test('hero invitation, direct footer feedback and self-hosted preview metadata',async()=>{ 66 const html=await read('index.html'); 67 assert.match(html,/<title>Arboricx: tricu and portable tree programs<\/title>/); 68 assert.match(html,/Write familiar-looking code and explore how it becomes a tree you can run, inspect, and share\./); 69 assert.doesNotMatch(html,/Start Here walks through/); 70 assert.match(html,/<footer[^>]*>.*href="mailto:james@eversole.co">Email feedback<\/a>.*<\/footer>/); 71 assert.match(html,/<link rel="canonical" href="https:\/\/arbori.cx\/">/); 72 assert.match(html,/<meta property="og:image" content="https:\/\/arbori.cx\/images\/preview.png">/); 73 for(const path of ['images/favicon.png','images/icon.svg','images/apple-touch-icon.png','images/preview.png'])await access(new URL(path,here)); 74 assert.doesNotMatch(html,/twitter:|facebook\.com|linkedin\.com|contact-form|Privacy policy/); 75 }); 76 77 test('import picker and static agent entrypoint are discoverable and accessible',async()=>{ 78 const html=await read('index.html'); 79 assert.match(html,/<button id="add-import"[^>]*>Add import…<\/button>/); 80 assert.match(html,/<dialog id="import-dialog" aria-labelledby="import-dialog-title">/); 81 for(const id of ['import-search','import-alias','import-version','import-export-search'])assert.ok(html.includes(`for="${id}"`)); 82 assert.match(html,/<link rel="help" type="text\/plain" href=".\/llms.txt"/); 83 assert.match(html,/<footer[^>]*>.*For AI assistants.*<\/footer>/); 84 const source=await read('src/import-picker.ts');assert.match(source,/textContent/);assert.doesNotMatch(source,/innerHTML/); 85 });