site.test.mjs (8648B)
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 assert.doesNotMatch(html,/id="expression-editor"|id="output-target"|class="output-panel"|class="expression-dock"/); 13 const inspector=await read('src/definition-inspector.ts'); 14 for(const token of ["'tricu'", "'triangle'", "'dag'", "'string'", "'number'", "'values'"])assert.ok(inspector.includes(token)); 15 const app=await read('src/app.ts');assert.match(app,/void initializePackages\(\)/);assert.doesNotMatch(app,/pane-change|activePane/); 16 await assert.rejects(access(new URL('dist/tabs.js',here)));await assert.rejects(access(new URL('dist/export-app.js',here))); 17 }); 18 test('editor preserves authoring, IO, package and immutable download controls',async()=>{ 19 const html=await read('index.html'); 20 for(const id of ['source-editor','compile','stop','restore-source','download-bundle','package-version','program-hash','share','package-definition-search'])assert.ok(html.includes(`id="${id}"`),id); 21 assert.match(html,/Edits compile after a pause/);assert.match(html,/Stored definitions can be inspected without source/); 22 assert.doesNotMatch(html,/id="edit-source"|compile-consent|slow-compilation|uncapped|execution-limits/); 23 const app=await read('src/app.ts');assert.match(app,/sourceEditor.dom.classList.toggle\('modified', modified\)/); 24 assert.match(app,/sourceEditor.getSource\(\) !== compiledSource/); 25 assert.match(app,/data.hash !== hash/); 26 assert.match(app,/r\.name === name && r\.version === versionPicker\.value/); 27 }); 28 test('inspector supports mobile modality, escape, focus return and focus containment',async()=>{ 29 const html=await read('index.html'),controller=await read('src/inspector.ts'); 30 assert.match(html,/aria-controls="settings-panel" aria-expanded="false"/); 31 for(const token of ['aria-modal','aria-expanded','element.inert','Escape','Tab','toggle.focus()','getClientRects'])assert.ok(controller.includes(token),token); 32 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/); 33 assert.match(css,/#source-editor\{font-size:16px;line-height:27px;padding:16px;width:100%/); 34 assert.match(css,/prefers-reduced-motion/); 35 const rules=css.replace(/\/\*[\s\S]*?\*\//g,'');assert.equal((rules.match(/\{/g)||[]).length,(rules.match(/\}/g)||[]).length,'Balanced style blocks after removing comments'); 36 assert.equal((css.match(/\/\*/g)||[]).length,(css.match(/\*\//g)||[]).length,'Complete style comments'); 37 }); 38 test('ecosystem remains with copyright and footer feedback',async()=>{ 39 const html=await read('index.html');assert.equal((html.match(/<article>/g)||[]).length,6); 40 assert.match(html,/Copyright 2026 <a href="https:\/\/eversole.co">James Eversole<\/a>/); 41 assert.doesNotMatch(html,/>Demo documentation<\/a>/); 42 assert.doesNotMatch(html,/Arboricx and tricu are research experiments|was discovered by|Johannes Bader/); 43 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)); 44 }); 45 test('conformance workers retain unbounded execution and shared transport limits',async()=>{ 46 const {BROWSER_BUDGET,PACKAGE_LOAD_MS,EXPORT_ACTIVE_MS}=await import('../dist/execution.js'); 47 assert.deepEqual(BROWSER_BUDGET,{fuel:Infinity,nodes:Infinity,stack:Infinity});assert.equal(PACKAGE_LOAD_MS,60000);assert.equal(EXPORT_ACTIVE_MS,120000); 48 for(const file of ['src/conformance/worker.ts','src/conformance/compiler-worker.ts']){ 49 const source=await read(file);assert.match(source,/const budget = BROWSER_BUDGET/);assert.doesNotMatch(source,/executionSettings|executionBudget|\['limits'\]|\['uncapped'\]/); 50 } 51 for(const file of ['src/protocol.ts','src/compiler-protocol.ts','src/app.ts'])assert.doesNotMatch(await read(file),/ExecutionLimits|readSettings|limits-change|activeMilliseconds/); 52 await assert.rejects(access(new URL('dist/limits-ui.js',here))); 53 }); 54 55 test('inline IO has explicit authority, revision checks and full-result actions',async()=>{ 56 const html=await read('index.html'),app=await read('src/app.ts'),inspector=await read('src/definition-inspector.ts'),worker=await read('src/wasm-worker.ts'); 57 assert.doesNotMatch(html,/id="run"|id="stop-run"|id="mode"|id="arguments"|id="output-type"/); 58 for(const token of ['run-io','stop-io','terminal-io','step-mode','next','Returned value','Copy','Download'])assert.ok(inspector.includes(token),token); 59 assert.doesNotMatch(inspector,/input-form|line-input|tricuIO v1/); 60 assert.match(inspector,/io\.run\(selected/);assert.match(inspector,/ioDetected/);assert.match(inspector,/valueText\(value\)/);assert.match(inspector,/saved\.text/); 61 assert.match(app,/editRevision !== compiledRevision/);assert.match(worker,/session\.probeIO\(name\)/);assert.match(worker,/session\.runDefinitionIO/); 62 }); 63 test('hero invitation, direct footer feedback and self-hosted preview metadata',async()=>{ 64 const html=await read('index.html'); 65 assert.match(html,/<title>Arboricx: tricu and portable tree programs<\/title>/); 66 assert.match(html,/Write familiar-looking code and explore how it becomes a tree you can run, inspect, and share\./); 67 assert.doesNotMatch(html,/Start Here walks through/); 68 assert.match(html,/<footer[^>]*>.*href="mailto:james@eversole.co">Email feedback<\/a>.*<\/footer>/); 69 assert.match(html,/<link rel="canonical" href="https:\/\/arbori.cx\/">/); 70 assert.match(html,/<meta property="og:image" content="https:\/\/arbori.cx\/images\/preview.png">/); 71 for(const path of ['images/favicon.png','images/icon.svg','images/apple-touch-icon.png','images/preview.png'])await access(new URL(path,here)); 72 assert.doesNotMatch(html,/twitter:|facebook\.com|linkedin\.com|contact-form|Privacy policy/); 73 }); 74 75 test('import picker and static agent entrypoint are discoverable and accessible',async()=>{ 76 const html=await read('index.html'); 77 assert.match(html,/<button id="add-import"[^>]*>Explore libraries…<\/button>/); 78 assert.match(html,/<dialog id="import-dialog" aria-labelledby="import-dialog-title">/); 79 for(const id of ['import-search','import-alias','import-version','import-export-search'])assert.ok(html.includes(`for="${id}"`)); 80 assert.match(html,/<link rel="help" type="text\/plain" href=".\/llms.txt"/); 81 assert.match(html,/<footer[^>]*>.*For AI assistants.*<\/footer>/); 82 const source=await read('src/import-picker.ts');assert.match(source,/textContent/);assert.doesNotMatch(source,/innerHTML/); 83 }); 84 85 test('full Playground recovery stays in the toolbar and warns before losing local work',async()=>{ 86 const html=await read('index.html'),app=await read('src/app.ts'); 87 const button=html.match(/<button id="reload-playground"[^>]*>Reload Playground<\/button>/)?.[0]; 88 assert.ok(button);assert.doesNotMatch(button,/disabled|hidden/); 89 assert.ok(html.indexOf('id="reload-playground"')<html.indexOf('id="editor-body"')); 90 assert.match(app,/draft \|\| localModules\.entries\(\)\.length > 0/); 91 assert.match(app,/window\.confirm\(/);assert.match(app,/location\.reload\(\)/); 92 assert.doesNotMatch(app,/reload-playground'\)\.disabled/); 93 }); 94 test('Ecosystem describes the live content store serving this Playground',async()=>{ 95 const html=await read('index.html'); 96 assert.match(html,/This Playground gets its demo, library and compiler bundles from a live Arboricx content store server written in tricu/); 97 assert.match(html,/verifies their hashes before using them/); 98 assert.doesNotMatch(html,/same language used for the examples can serve its own packages/); 99 }); 100 101 // Durable user-facing contract; runtime and gesture semantics are browser-tested. 102 test('inspection instructions use declaration names rather than an arrow gutter',async()=>{ 103 for(const file of ['index.html','README.md','reference-input/getting-started.md'])assert.doesNotMatch(await read(file),/▶/,file); 104 const css=await read('style.css');assert.doesNotMatch(css,/definition-gutter|padding-left:48px/); 105 assert.match(await read('src/definition-inspector.ts'),/Edit name/); 106 });