Make the renderer's contract reachable from the DOM tests, so the fixtures stop being a copy (#94)#116
Conversation
…tests can render for real The `dom` vitest project has the DOM lib and not the Worker's ambient types, so it could not import src/render/comments.ts: that module type-imported RenderableComment from src/db, and src/db is typed against D1Database. The DOM fixtures therefore hand-wrote the comment markup the Worker sends, and nothing tied the copy to the original — a rename applied across the renderer and its own worker test would have left mount.ts selecting a class that no longer arrives, with every suite green. RenderableComment moves to src/render/comments.ts and src/db imports it. That is the direction the dependency should already have run: the renderer is the general half of the pair — D1 is one source of rows and the v1.1 build-time path takes them from a site generator — and a shape owned by the data layer drags Cloudflare's ambient types into every program that renders. src/db deliberately does not re-export it; one type with two import paths is how a second declaration appears later. The two worker render tests that took it from src/db now take it from src/render, which is where a render test should have been asking anyway. test/dom/embed/mount.test.ts now builds its bodies by calling renderComments, mirroring both wire shapes: threadHtml() is the page read (src/read/route.ts) and postedHtml() is the submit echo, flattened to a root the way src/submit/pipeline.ts flattens it. The hand-written copy had already drifted from both — the invitation carried a full stop the renderer does not emit, and a posted comment arrived as a bare <li> where the Worker sends a whole rendered list. The selectors in the assertions stay string literals on purpose. mount.ts reads those names as literals too, so a selector derived from the same source as the markup would follow a rename and prove nothing about the file under test. The literals are the pin; the fixture is what moves under them. No change to rendered output. The renderer diff is a comment block, a moved type declaration and one deleted import; bundling both revisions of src/render and rendering the same rows — empty page, nested replies, an out-of-range timestamp, an orphaned reply, a markup-shaped author name, a custom strings table and the truncation notice — gives byte-identical results, sha256 b602202bcbb18515616f9b784bda2eb498fb0e5ee414d58c2183d408a5b65af9 on both sides. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Coordinator verificationThe hole is closed, and I reproduced the proofI renamed a class consistently across the renderer and its own worker test — the exact scenario #94 says goes green today: That is the issue in two lines. The worker suite cannot see a coordinated rename because the renderer and its tests move together; the DOM tests now can, because their fixtures are built by calling The chosen seam is better than the one I briefedI suggested making the class names importable. That would have made the names shared while the fixture markup stayed hand-written and free to drift — a narrower fix that leaves the same class of hole one level down. Moving Rejecting the Both judgement calls hold upNot re-exporting from DOM selectors stay string literals. This is the subtle one and it is right: Byte-identical output, demonstrated rather than assertedBundling both revisions of One thing for merge orderingThis touches Worth merging before the dashboard branch grows, rather than after. #114 is a good catch
|
Closes #94.
The seam, and why this one
The issue proposed extracting
COMMENT_CLASS_NAMESinto a dependency-free module. I went one layer deeper instead, because the class-name list was a symptom rather than the coupling.src/render/comments.tstype-importedRenderableCommentfromsrc/db, andsrc/dbis typed againstD1Database. Thedomvitest project deliberately has the DOM lib and not the Worker's ambient types, so importing the renderer fromtest/dom/**pulled the whole data layer into a program that cannot type it:So
RenderableCommentmoves tosrc/render/comments.ts, andsrc/dbimports it. That is the direction the dependency should already have run: the renderer is the general half of the pair — D1 is one source of rows and the v1.1 build-time path takes them from a site generator — and a shape owned by the data layer drags Cloudflare's ambient types into every program that renders.Two consequences worth flagging in review:
src/dbdoes not re-export it. One type with two import paths is how a second declaration shows up later. The two worker render tests that took it fromsrc/dbnow take it fromsrc/render, which is where a render test should have been asking anyway.Rejected: widening
test/dom/tsconfig.jsonto admit the ambient types. It papers over the coupling and gives the browser half of the repo Cloudflare'sfetchandResponse, which is the exact thing the split exists to prevent.What the fixtures do now
test/dom/embed/mount.test.tsbuilds its bodies by callingrenderComments, mirroring both wire shapes —threadHtml()is the page read (src/read/route.ts),postedHtml()is the submit echo flattened to a root the waysrc/submit/pipeline.tsflattens it.The hand-written copy had already drifted from both, which is the concrete cost of the gap:
Be the first to comment.— the renderer emits no full stop;<li>, where the Worker sends a whole rendered<ol>.The selectors in the assertions stay string literals on purpose.
mount.tsreads those names as literals too, so a selector derived from the same source as the markup would follow a rename and prove nothing about the file under test. The literals are the pin; the fixture is what moves under them.Kill-shot
Both runs apply the rename consistently across the renderer and its own worker test — the scenario #94 describes, which went fully green before this change.
1. Rename a class:
charcha-comment-author→charcha-comment-name(insrc/render/comments.ts,COMMENT_CLASS_NAMES, andtest/worker/render/comments.test.ts)2. Rename the id template:
id="charcha-comment-${…}"→id="charcha-c-${…}"— this is whatmount.tsparses back out withcomment.id.replace('charcha-comment-', '').Both restored from backup; suite green afterwards (
47 passed (47),823 passed (823)).The rendered HTML is unchanged
Not inferred from green tests. The renderer diff is a comment block, a moved type declaration and one deleted import — no emitted string is touched — and that was checked mechanically: both revisions of
src/renderwere bundled with esbuild and handed the same rows (empty page, nested replies, an out-of-range timestamp, an orphaned reply, a markup-shaped author name, a custom strings table, and the truncation notice).test/worker/render/comments.test.tsasserts the full markup string literally, and it is untouched apart from its import line.The embed bundle is unaffected — nothing under
src/embed/importssrc/render, andcheck:sizereports the same6791 B gzipped of 10240 B allowed.Reviewing the
vocabulary.test.tshunkGitHub will show it as
Bin 8436 -> 8398 bytes. That is not this change — the file contains a raw NUL byte in an XSS payload on line 73, so git has always classified it as binary. Filed as #114. The delta here is exactly 38 bytes, which is the two import lines collapsing into one:Verification
Skills used:
api-and-interface-design(which module owns the shared type),test-driven-development(the failing DOM typecheck came first),code-review-and-quality,improvescoped to the branch — which is what turned up #114 — andsuperpowers:verification-before-completion.For the owner
src/db/index.ts(the interface declaration becomes an import). That file belongs to the parallel dashboard session, so expect a possible conflict there and nowhere else in this PR.src/embed/mount.tsis untouched — the A 201/202 whose body carries no comment tells the reader it worked and shows them nothing #93 session owns it. The remaining half of The renderer's class-name contract cannot reach the DOM tests, so their fixtures are an uncoupled copy #94's "worth checking at the same time" note is whethermount.tsshould select those names through the shared module. My read is that it should not: literals on both sides are what make the rename break. Happy to be overruled.🤖 Generated with Claude Code