Step 7 - Testing, quality and deployment
4 min read
Testing, quality and deployment
Close the loop: prove the thing works, then ship it as static files.
Test layers
Unit — engine and AI. Already specified in steps 2 and 3. These are the deepest tests and must stay fast: the whole suite runs in under 30 seconds, including the exhaustive never-lose search.
Unit — state and storage. Transitions, alternating starters, score counting, and the corrupt / unavailable / throwing storage cases from step 5.
Integration — DOM. With jsdom, mount the whole app into a detached document and drive it
through simulated events:
- Clicking three cells in a winning pattern ends the round, marks the winning cells and increments the right counter.
- A full board with no line reports a draw and increments
draws. - Clicking an occupied cell changes nothing — no state change, no counter movement.
- Clicking any cell after the game ends changes nothing.
- Keyboard-only play: focus the board, navigate with arrows, place with
Enter, and complete a whole game without a single synthetic click. - Switching to
human-vs-computerwithhumanMark: 'O'and a fixed opening ofXresults in the computer having moved before the player interacts. - Setting difficulty to
hardand playing a scripted human sequence never ends in a human win.
Use fake timers for the cosmetic AI delay so integration tests do not sleep.
Accessibility. Run axe-core against the rendered document in three states — fresh board,
mid-game, and finished game — and assert zero violations at the wcag2a and wcag2aa levels.
axe-core is permitted as a dev dependency for this purpose.
Manual checklist
Run before release, and record the result in the repository README:
- Play a full game with the mouse, the keyboard and a touchscreen.
- Play with a screen reader (VoiceOver or NVDA): the board position, each cell's content and the outcome are all announced, and nothing is announced twice.
- Verify in current Chrome, Firefox and Safari, desktop and mobile.
- Toggle the OS between light and dark, and reduced motion on and off.
- Zoom the browser to 200% and confirm nothing is clipped or overlapping.
- Reload mid-game: settings and scores return, a fresh board appears.
- Disable
localStoragein browser settings and confirm the game still plays. - Go offline after the first load and confirm the game still plays.
- Lose ten times in a row to Hard — or rather, fail to win once.
Performance budget
| Metric | Budget |
|---|---|
| Total transferred (gzipped) | < 30 kB |
| JavaScript (uncompressed) | < 60 kB |
| CSS (uncompressed) | < 12 kB |
| Time to interactive on a mid-range phone | < 1 s |
| Hard AI decision, worst case | < 100 ms |
| Cumulative layout shift | 0 |
| Lighthouse Performance / Accessibility / Best Practices | 100 / 100 / 100 |
Layout shift of exactly zero is achievable here and is worth insisting on: the board is a fixed grid and the status line must reserve its height so a longer message does not push the board down.
Build and deployment
npm run build produces a fully static dist/ — HTML, one JS bundle, one CSS file, one SVG. It has
no server requirement, so it can be hosted anywhere: Firebase Hosting, GitHub Pages, Netlify,
Cloudflare Pages, or a plain directory behind nginx. Document one path in the README and mention that
the others need no code change.
Set Vite's base to './' so the build also works from a subdirectory and from file://.
Recommended headers where the host allows them: the same CSP as the meta tag,
X-Content-Type-Options: nosniff, Referrer-Policy: no-referrer. Hashed asset filenames get a long
Cache-Control; index.html must not be cached aggressively.
Optionally add a minimal service worker that pre-caches the four build artefacts for offline play. If included, it must not be a network proxy — cache-first for its own assets and nothing else — and it must be registerable but not required for the game to work.
Acceptance criteria
-
npm run testpasses with zero failures and zero skipped tests, in under 30 seconds. - Overall statement coverage is at least 85%, and at least 95% for
src/engine/. - The jsdom integration suite covers win, draw, occupied-cell, post-game click, keyboard-only play and computer-moves-first, and all pass.
-
axe-corereports zerowcag2a/wcag2aaviolations in fresh, mid-game and finished states. -
npm run buildsucceeds,npm run previewserves a working game, and the samedist/also works when opened directly from the filesystem. - Every performance budget figure above is met and recorded.
- Lighthouse scores 100 for Performance, Accessibility and Best Practices on the production build.
- The manual checklist has been completed and its result recorded in the repository README.
-
dist/requests nothing from a third-party origin, verified in the network panel. - The game plays correctly with the network disconnected after first load.
-
npm run verifypasses on a clean checkout afternpm ci.
Discussion
0 commentsNo comments yet. Start the discussion.