diff options
| author | Gabriel Schneider <[email protected]> | 2026-08-24 10:56:05 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-08-25 09:42:07 -0300 |
| commit | c3d0b84b7961ae26d2d654e7120821cc2d83d20d (patch) | |
| tree | 0b9a060a8ff0ee7d83c8df5ca797a99e0d66c53e /test/web_harness.mjs | |
| parent | 70bde600793ea70bd68832018a154671c6bf1512 (diff) | |
| download | pardes-c3d0b84b7961ae26d2d654e7120821cc2d83d20d.tar.gz pardes-c3d0b84b7961ae26d2d654e7120821cc2d83d20d.zip | |
host: the core owns the event loop; every platform becomes a vtable of optional methods
Diffstat (limited to 'test/web_harness.mjs')
| -rw-r--r-- | test/web_harness.mjs | 56 |
1 files changed, 38 insertions, 18 deletions
diff --git a/test/web_harness.mjs b/test/web_harness.mjs index 087dd91b..7bfc2d60 100644 --- a/test/web_harness.mjs +++ b/test/web_harness.mjs @@ -17,25 +17,47 @@ async function main() { const webDir = path.resolve(process.argv[2]); const wasmBytes = await fs.readFile(path.join(webDir, "pardes.wasm")); const module = await WebAssembly.compile(wasmBytes); - assert.deepEqual(WebAssembly.Module.imports(module), [], "the freestanding core must have no JS or libc imports"); - const { exports: wasm } = await WebAssembly.instantiate(module, {}); + const { DomRenderer, animationTicks, browserHost } = await import(pathToFileURL(path.join(webDir, "app.mjs"))); + assert.deepEqual( + [...new Set(WebAssembly.Module.imports(module).map((entry) => entry.module))], + ["pardes"], + "the freestanding core must import nothing but the page's own host", + ); + + // The page's real import object, with every call recorded: the browser host + // is only what a page can actually do, and a replay must ask it for nothing. + const host = browserHost(); + const asked = []; + for (const [name, fn] of Object.entries(host.imports.pardes)) { + host.imports.pardes[name] = (...args) => { + asked.push(name); + return fn(...args); + }; + } + const { exports: wasm } = await WebAssembly.instantiate(module, host.imports); + host.wasm = wasm; for (const name of [ "memory", "pardes_init", "pardes_frame", "pardes_frame_ptr", "pardes_key", - "pardes_mouse", "pardes_pointer_leave", "pardes_gui_tagline_font_percent", "pardes_output", - "pardes_lsp_response", "pardes_pipe_response", "pardes_file_changed", - "pardes_effect_next", "pardes_deinit", + "pardes_mouse", "pardes_pointer_leave", "pardes_gui_tagline_font_percent", + "pardes_paste", "pardes_tick", "pardes_animation_tick", "pardes_animating", + "pardes_should_quit", "pardes_deinit", ]) assert.ok(wasm[name], `missing wasm export ${name}`); assert.equal(wasm.pardes_init(80, 24), 0, `wasm init error ${wasm.pardes_last_error()}`); + assert.equal(wasm.pardes_frame(), 0, "a frame exists only once the core has presented one"); + wasm.pardes_tick(); assert.equal(wasm.pardes_frame(), 80 * 24); assert.ok(wasm.pardes_frame_ptr() > 0); - const { DomRenderer, animationTicks } = await import(pathToFileURL(path.join(webDir, "app.mjs"))); const document = new FakeDocument(); const root = document.createElement("main"); root.ownerDocument = document; const renderer = new DomRenderer(root, wasm); + const draw = () => { + wasm.pardes_tick(); + return renderer.render(); + }; const first = renderer.render(); assert.equal(first.cols, 80); @@ -69,7 +91,7 @@ async function main() { assert.equal(animationTicks(stalled, 60_000), 4, "suspended tab catch-up was not bounded"); const firstCell = renderer.cells[0]; - const second = renderer.render(); + const second = draw(); assert.equal(renderer.cells[0], firstCell, "stable frames should reuse DOM nodes"); assert.equal(second.cells.length, 80 * 24); @@ -77,7 +99,7 @@ async function main() { // over-scroll (the core clamps at row zero) so adding more source files does // not silently move the hard-coded build.zig click onto some later path. for (let tick = 0; tick < 128; tick += 1) wasm.pardes_mouse(3, 0, 5, 3, 0); - const listingTop = renderer.render(); + const listingTop = draw(); assert.match( listingTop.cells.map((cell) => cell.text).join(""), /git ls-files --cached --others --exclude-standard.*build\.zig/, @@ -85,23 +107,21 @@ async function main() { ); wasm.pardes_mouse(2, 0, 5, 3, 0); wasm.pardes_mouse(2, 1, 5, 3, 0); - const looked = renderer.render(); - assert.match(looked.cells.map((cell) => cell.text).join(""), /build\.zig Save New Del/, "right-click LOOK did not open the embedded source"); + const looked = draw(); + assert.match( + looked.cells.map((cell) => cell.text).join(""), + /build\.zig\s+Save New Newtty Del/, + "right-click LOOK did not open the embedded source", + ); const sourceRows = Array.from({ length: looked.rows }, (_, row) => looked.cells.slice(row * looked.cols, (row + 1) * looked.cols)); const codeRows = sourceRows.filter((row) => row.map((cell) => cell.text).join("").match(/^\s*\d+\s/)); const codeColors = new Set(codeRows.flatMap((row) => row.slice(5).map((cell) => `${cell.fg}:${cell.attrs & 1}`))); assert.ok(codeColors.size >= 3, "Tree-sitter did not apply distinct syntax styles to build.zig"); - let sawWatch = false; - for (let index = 0; index < 32; index += 1) { - const kind = wasm.pardes_effect_next(); - if (!kind) break; - if (kind === 9) sawWatch = true; - } - assert.ok(sawWatch, "JS effect drain did not receive the opened file watch request"); + assert.deepEqual(asked, [], "a plain replay asked the page for host IO"); wasm.pardes_resize(41, 13); - const resized = renderer.render(); + const resized = draw(); assert.equal(resized.cols, 41); assert.equal(resized.rows, 13); assert.equal(root.children.length, 13); |
