How this is builtGroundworkHow this is built
22 / 26 written chapters

A3 · Part 3 · How it holds up

Tests and hooks

1630 unit tests, 27 browser tests, and one test that checks what these pages say about the site.

9 min read7 sections
07On this page
  1. The shape of it
  2. Unit tests: 24 files, 405 tests
  3. The test that keeps these pages honest
  4. Browser tests: one smoke spec, 27 tests
  5. Before code leaves the machine
  6. What runs, and when
  7. What the Dependabot runs showed

The shape of it

There are three layers of checking, each slower than the one before:

  • Git hooks on the author's machine, over staged files only.
  • Vitest in Node, over data and pure logic. Most of the weight is here.
  • Playwright in a real browser, against a production build: one small smoke spec.

There is no CI service. A GitHub workflow used to run the compiler, the linters, the unit tests, the build, about 570 browser tests and Lighthouse on every push and pull request; it was removed, and nothing runs these checks for the author now. The browser suite was cut to the smoke spec described below. The test counts in this chapter are counted from the working tree of the branch it was last edited on.

Unit tests: 24 files, 405 tests

vitest.config.ts collects tests/**/*.test.ts and runs them in a Node environment with no DOM. No React component is mounted in a unit test; the one exception renders the topic shell to static markup, to check what it draws for a JavaScript-like and a DSA-like set of props. That is still a decision, not a gap. The risk in this codebase sits in hand-written content and in a few engines with real logic, and components are tested where they actually run, in the browser.

AreaFilesTestsWhat they hold in place
Mock interviewmock-engine, mock-session, mock-readiness, mock-guide, mock74How the interviewer adapts, how a session moves through its stages, the readiness score, and the coaching
Content and SEOcontent, seo, claims, search-index, privacy, next-config, client-bundle, topic-categories, home-rounds129Chapter integrity, the sitemap and robots rules, the site's claims about itself, the search budget, the privacy page's list of services, the pinned redirects, the client bundle, the category of every topic, and the interview rounds the home page previews
Playgroundpolyglot, debug-trace, wasm-assets, contrast37Grading across languages, the step-through tracer, pinned runtime versions, and the contrast of the editor, text and role colours in every theme
Whiteboardwhiteboard-model17Geometry, history and sanitizeEls
Themestheme-roles, theme-contract115Role tokens instead of fixed colours in every stylesheet, and a complete block for every theme
Toolingcomments, error-tracking10The comment checker itself, and Sentry's options and CSP origin

The largest hand-written file is mock-engine.test.ts with 37 tests; theme-roles.test.ts runs 101, most of them one check repeated for each CSS module. The content file, content.test.ts, has 18. Each of those 18 is a mistake that was really made while writing:

  • a chapter marked ready with an empty body
  • <pre> and <code> tags that do not balance, which puts half a chapter inside a code block
  • a diagram with no aria-label
  • an exercise pointing at a chapter that does not exist

The whole suite runs in under 10 seconds.

The test that keeps these pages honest

tests/claims.test.ts exists because this topic is full of numbers. It works out the real figures from lib/content: written chapters, outlines, exercises, interview rounds and topics. It builds the exact sentence each figure appears in, reads the source file as plain text, and expects to find that sentence.

It checks 10 claims across six files, one of them the README, plus the topic-count row of the overview table. One of them is in the scaling chapter. It expects the text Writing the N outlined chapters, with N computed from the data. Write one more outline and the test fails until the sentence is updated.

The test does not check whether a claim is wise. It only stops a claim from going stale in silence, which is how the old versions of these chapters went wrong: they were true when written and then drifted.

Browser tests: one smoke spec, 27 tests

playwright.config.ts starts npm run start on port 3100. That is the production build, not the dev server, because dev mode double-invokes effects and serves unminified code, so npm run build comes first. npm run test:e2e then runs the spec in about ten seconds on three workers, with no retries unless CI is set, in which case it retries a failed test once and fails the run if anyone left a test.only behind.

SpecTestsCovers
smoke.spec.ts278 pages load with their heading and no console error; 19 flows (the home page with its heading and the Menu pill, the Menu opening and closing on Escape, a topic cover and its level picker link, a chapter with its rail and no sideways scroll at 390 pixels wide, the path trail with every exercise list open and no stop covering another, the playground running a JavaScript snippet, an exercise page running code and grading a submission, unknown URLs returning a real 404, a DSA chapter switching its code to Python and back, the binary search player stepping forward and back, marking a DSA chapter read opening its check that a keyboard user answers and finishes, the DSA home page telling a passed chapter's check state in words, and the end-of-chapter popup opening once, Yes opening the check and a dismissal never coming back, a first-time reader arriving from the old /dsa address, finding their level and landing on a plan that Continue follows past the chapters they tested out with the shared storage keys untouched, and a placed reader opening and marking a Revise earlier chapter while a reader with marks sees Re-check my level, the DSA home leading a first-time reader to Check my level with a three-stop preview and a journey of three stages that fold open and shut by keyboard and open from a link, and a placed reader seeing their level, progress ring, up-next card and one Continue button with the plan inside its closed details, and a placement run that survives a reload and steps back while only ?retake=1 starts a retake)

The eight pages are the problems list, the interview book, the mock interview, review, progress, privacy, this section and the Git guide. A test that clicks first waits for the element to be hydrated, by looking for the property React puts on a DOM node once it owns it, so it cannot click before the handler exists and nothing in the spec sleeps. It ignores requests to /_vercel/. Those scripts exist only on Vercel's edge, so they return 404 locally every time.

What the cut removed

The old suite also ran axe over every page and state in all nine themes at two widths, opened every written chapter at 390 pixels wide, probed the composition of every hero stage in every theme and font, and covered the tour, the interview book, the mock room, review, progress, the workbench, the whiteboard and keyboard focus in detail, including checks that nothing flashes the wrong state before hydration. None of that is checked in a browser any more. What still guards the colours is tests/contrast.test.ts and the theme tests, which read the tokens of all nine themes in Node. Layout, overflow, focus and hydration behaviour on those pages are now checked by looking.

Before code leaves the machine

.husky/pre-commit runs lint-staged over the staged files only:

  • ESLint with fixes, then Prettier, for .ts, .tsx and .mjs
  • Prettier for CSS, Markdown and JSON
  • cspell for every staged file, whatever its type

.husky/pre-push runs npm run comments. This project keeps its source free of comments, and scripts/comments.mjs enforces that:

  • It parses each tracked .ts, .tsx, .js, .mjs, .cjs and .css file with the TypeScript compiler's own scanner. So a // inside a string, a template, a regular expression or JSX text is correctly left alone.
  • It skips public/, docs/, node_modules/ and .next/.
  • It keeps the directives that tools read: eslint-disable, @ts-expect-error, webpackIgnore, prettier-ignore, cspell: and a few more.
  • With --fix, it removes each comment together with the line it sat on, then runs Prettier over the files it changed.

Five unit tests cover the checker.

The rule has one door

The check runs in the pre-push hook and in npm run check. A push with --no-verify, or a commit made in the GitHub web editor, skips the hook, and nothing else runs it.

What runs, and when

Nothing here runs by itself except the two git hooks. The rest are commands the author runs.

CommandWhat it runs
npm run checkTypecheck, lint, the comment check, formatting, spelling and the unit tests
npm run buildThe production build, which type-checks again and renders every page; Vercel runs the same command
npm run test:e2eThe smoke spec, against the build
npm run build then npm run test:perfThe Lighthouse budgets in .lighthouserc.json, three runs on three URLs, started through npx

Pushing a branch is also a check. Vercel builds a preview of every pushed branch and reports it as a commit status called Vercel, with a URL to open. Vercel runs the build but not the linters or the tests.

Lighthouse runs through npx rather than from the project's own dependencies. The CLI pulls in an old Lighthouse and Puppeteer, and that was thirteen advisories the lockfile did not need for a tool that only runs when someone asks.

What the Dependabot runs showed

The failed runs on Dependabot's pull requests, from when CI still ran, show each layer doing its job. Those pull requests now get no automatic check at all:

  • The Pyodide bump failed in wasm-assets.test.ts: "expected 314.0.6 to be 314.0.7". The CDN URL uses a version typed into lib/wasmAssets.ts, and the test refuses to let package.json and that constant disagree.
  • The dev-tooling group failed at npm ci in 23 seconds. A newer Vite wanted a newer esbuild than the exact 0.25.12 the project pins.
  • The Next group failed on Lighthouse, on a single slow run. That run is why there are now three.