// The RenderModel→DOM boundary, statically: a projected field that nothing // paints is the bug four widgets shared. See context/code/clients.md. import { test } from "node:test"; import assert from "node:assert/strict"; import { readFileSync } from "node:fs"; import { fileURLToPath } from "node:url"; const read = (rel) => readFileSync(fileURLToPath(new URL(rel, import.meta.url)), "utf8"); /// Comments name model fields routinely, so a field mentioned only in one /// would read as painted. Strip them before asking what the code touches. const code = (rel) => read(rel) .replace(/\/\*[\s\S]*?\*\//g, " ") .replace(/(^|[^:"'`])\/\/.*$/gm, "$1"); /// Fields the page deliberately does not read, each with the reason. A field /// belongs here only with an argument; the list is the debt, written down. // improvise-z754 put the title bar's facts in one shared cache and taught // `src/draw.rs::draw_title` to fold it; this page never learned to. That is // improvise-5jna, open, and this list is what stops it being forgotten. const UNPAINTED = { doc_name: "improvise-5jna: the title bar is drawn by the TUI and not here", doc_dirty: "improvise-5jna", doc_stored_as: "improvise-5jna", }; /// The `pub` fields of `RenderModel`, read off the Rust that defines it. function renderModelFields() { const src = read("../../crates/improvise-wasm-client/src/render.rs"); const start = src.indexOf("pub struct RenderModel {"); assert.ok(start > 0, "RenderModel moved — this test reads the struct itself"); const body = src.slice(start, src.indexOf("\n}", start)); const fields = [...body.matchAll(/\n {4}pub ([a-z_0-9]+):/g)].map((m) => m[1]); assert.ok(fields.length > 10, `found only ${fields.length} fields`); return fields; } test("every field the render model projects is painted, or named as debt", () => { // Both halves of the page: `app.js` paints, `shell.js` plans the grid. const page = code("../app.js") + code("../shell.js"); const unpainted = renderModelFields().filter((field) => !page.includes(`model.${field}`)); assert.deepEqual( unpainted.sort(), Object.keys(UNPAINTED).sort(), "a projected field reaches no element — paint it, or add it to UNPAINTED with the reason", ); }); test("every element the page looks up exists in the page", () => { const app = read("../app.js"); const html = read("../index.html"); const ids = [...app.matchAll(/getElementById\("([^"]+)"\)/g)].map((m) => m[1]); assert.ok(ids.length >= 8, `found only ${ids.length} element lookups`); for (const id of ids) { assert.ok(html.includes(`id="${id}"`), `app.js looks up #${id}, which index.html has not got`); } });