Test it: unit, component, e2e, and a11y in CI
Continues from the last build: Last rung gave Pulse a themed, reusable design system, consistent buttons, inputs, and cards, dark mode that does not flicker. But nothing verifies any of it still works.
Pulse finally looks like a real product: a themed design system, consistent buttons and cards, dark mode that does not flicker.
What you'll build
Pulse ships with a layered test suite, unit, component, and end-to-end, that runs on every pull request, plus an automated accessibility gate that fails the build on real violations. The team refactors with evidence instead of guesswork.
See how we teach, before you sign up
You don't just get code dumped on you. Every starter file and every solution is explained line-by-line, in plain English. Here's one real file from this project:
import { setupServer } from 'msw/node'
import { http, HttpResponse } from 'msw'
export const handlers = [
http.get('/api/segments', () => {
return HttpResponse.json({
segments: [
{ id: 'seg_1', name: 'Power users', rules: [{ field: 'events', op: 'gt', value: 10 }] },
],
})
}),
http.post('/api/segments', async ({ request }) => {
const body = await request.json() as { name?: string }
if (!body.name || body.name.trim().length === 0) {
return HttpResponse.json({ errors: { name: 'Name is required' } }, { status: 422 })
}
return HttpResponse.json({ segment: { id: 'seg_new', name: body.name, rules: [] } }, { status: 201 })
}),
]
export const server = setupServer(...handlers)
Reading this file
setupServer(...handlers)One server instance, started in a test setup file, so every test file gets the same baseline handlers instead of redefining fetch mocks by hand.http.post('/api/segments'Mirrors the real contract's 422 validation path, so component and e2e tests can exercise the error UI without a real backend.status: 422Matches the documented API contract exactly, this is what keeps the mock honest instead of inventing a friendlier shape.status: 201The success path returns the created segment, letting tests assert the UI reflects what the server would really send back.HttpResponse.jsonMSW's typed response helper, sets the right content-type automatically so tests do not need to fake headers.
The single MSW handler set every unit, component, and integration test imports, so mocked responses stay consistent across the suite.
That's 1 of 9 explained code blocks in this single project.
The build, milestone by milestone
- 1
Wire up the testing trophy foundation
5 guided stepsEvery layer of the trophy (unit, component, e2e) needs a fast, deterministic way to stand in for the given API. Getting the MSW server and Vitest config right once means every later test file is three lines of setup instead of thirty.
- 2
Unit test the pure segment-matching logic
5 guided stepsThis is the base of the trophy on purpose: pure functions need no DOM, no network, no browser, so they run in milliseconds and catch logic bugs (off-by-one thresholds, wrong operators) long before a slow e2e test would ever notice them.
- 3
Component test SegmentForm and EventSearch with accessible queries
5 guided stepsComponent tests are the trophy's biggest layer for a reason: they exercise real rendering, real event handling, and a real (mocked) network round trip, while staying fast enough to run on every save. Querying by role instead of test id means a broken label or missing aria-live shows up as a test failure, not a support ticket.
- 4
Playwright e2e for create-segment and validation
5 guided stepsUnit and component tests prove the pieces work in isolation; e2e proves the pieces work wired together in a real browser, real routing, real focus management included. Intercepting the network keeps that proof fast and deterministic instead of flaky and slow.
- 5
Gate merges on an automated axe a11y scan
5 guided stepsAutomated tools cannot catch everything (they miss things like unclear link text or bad reading order) but they reliably catch the mechanical failures, missing labels, bad contrast, invalid ARIA, that regress silently every time someone reshuffles markup. Making that check a required CI gate is what actually stops a regression from merging.
What's inside when you start
You'll walk away with
This is portfolio-grade. Build it free.
Sign up to unlock every milestone step-by-step, the code skeletons, full reference solutions, and checkable tasks, with your progress saved as you build.
Start building