test(web): a Node suite between the boundaries, Playwright only on them

E2E and integration tests are rare and pin boundary contracts; unit tests
assume those contracts and test between them (user, 2026-09-20). The browser
half has four boundaries — RenderModel→DOM, JS→wasm, page→peer, worker→OPFS —
and everything between them needed no browser at all.

`web/shell.js` and `web/serial.js` are the seams: pure modules that RETURN A
DESCRIPTION rather than take a DOM double. `gridPlan(model)` answers every
question `gridTable` used to answer while creating elements — the data-row
index across group headers, what a group header spans, a null group label
against an empty one, an empty edit buffer against no editor — and `app.js` is
now a loop that builds elements from it. Painting stays untested by Node; what
to paint stopped being untestable.

26 tests in `node --test web/tests` (0.12s, no Rust toolchain, no wasm bundle,
no npm install), plus three in `web/tests/bundle/` that read the generated
.d.ts and so run in the browser job. Playwright goes from 29 tests/36.9s to
16/15.5s on one machine: `widgets.spec.js` and `pointer.spec.js` are gone,
replaced by `contract.spec.js`, which asserts that EVERY field the render model
projects reaches the screen. The four widgets those specs were written for were
one failure at one boundary, and one assertion catches the fifth.

It caught it: `doc_name`/`doc_dirty`/`doc_stored_as` reach no element
(improvise-5jna, already open). They are named in `UNPAINTED` with the ticket,
so the list is the debt written down rather than an exemption.

Thirteen faults injected into the Node suite and eight into the served page,
each watched to fail the test it names; the last of those was strengthened
because the first version passed. `web/assemble.sh` grew an import-completeness
guard whose own first version was blind to the multi-line import it exists for.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Edward Langley
2026-09-20 13:33:49 -07:00
parent 1af51c99c6
commit 36ade35240
19 changed files with 1143 additions and 499 deletions
+26 -1
View File
@@ -97,6 +97,25 @@ jobs:
- name: improvise-gp
run: cargo check -p improvise-gp --target wasm32-unknown-unknown
# ---------------------------------------------------------------------------
# The browser JavaScript's unit tests. Its own job, deliberately: a failure
# here is a decision `web/shell.js` or `web/serial.js` got wrong, and
# reporting that as "Browser harness" would put it behind an 80 MB browser
# download and two wasm-pack builds. It needs neither — `node --test` is in
# the runtime, and this job installs nothing at all.
js:
name: Browser JS unit tests (node --test)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v4
with:
node-version: 22
# The glob is the SHELL's, not node's: `web/tests/bundle/` needs a built
# wasm bundle and runs in the browser job instead.
- name: What the page decides between its boundaries
run: node --test web/tests/*.test.js
kb:
name: Knowledge base consistency
runs-on: ubuntu-latest
@@ -169,6 +188,12 @@ jobs:
# `harness/serve` — the same tree `pages-build` publishes, plus /opfs/.
- name: Build the harness
run: ./harness/build.sh
# The JS↔wasm boundary: every call web/app.js makes is declared by the
# bundle, with that arity. It needs the bundle and nothing else, so it
# runs here rather than in the `js` job, and before Playwright — a
# renamed export is cheaper to read here than as a blank page.
- name: The page's calls match the bundle it calls
run: node --test web/tests/bundle/*.test.js
- name: Run the harness
working-directory: harness
run: npx playwright test
@@ -243,7 +268,7 @@ jobs:
pages-deploy:
name: Deploy to GitHub Pages
needs: [test, wasm, browser, pages-build]
needs: [test, wasm, js, browser, pages-build]
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
permissions: