test(dashboard): add Vitest infrastructure and first test suites#243
test(dashboard): add Vitest infrastructure and first test suites#243walidozich wants to merge 4 commits into
Conversation
There was a problem hiding this comment.
Pull request overview
Adds Vitest-based testing infrastructure to apps/dashboard, enabling .tsx component tests (via a jsdom project) while keeping existing logic tests on Node, and introduces initial unit test suites for several previously-untested modules. Also extends CONTRIBUTING.md with guidance on the new dashboard test setup and workflow.
Changes:
- Add
apps/dashboard/vitest.config.tswith@/*runtime aliasing, JSX transform override, and a split Node/jsdom project setup. - Add
apps/dashboard/vitest.setup.tsto register jest-dom matchers and ensure React Testing Library cleanup runs withglobals: false. - Add new unit test suites for dotenv parsing, subdomain normalization, project status derivation, and deployment phase detection; update dashboard devDependencies accordingly.
Reviewed changes
Copilot reviewed 8 out of 9 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| CONTRIBUTING.md | Documents test layout and the dashboard’s split Vitest project approach. |
| bun.lock | Adds the new dashboard devDependencies (including jsdom/testing-library) and resulting lock updates. |
| apps/dashboard/package.json | Adds Vitest DOM/component testing devDependencies. |
| apps/dashboard/vitest.config.ts | Introduces Vitest config (alias + JSX override) and splits tests into Node vs jsdom projects by extension. |
| apps/dashboard/vitest.setup.ts | Adds jest-dom integration and explicit RTL cleanup after each test. |
| apps/dashboard/src/lib/dotenv.test.ts | Adds unit coverage for dotenv parsing behavior. |
| apps/dashboard/src/utils/deploymentPhaseDetector.test.ts | Adds extensive unit coverage for phase detection and log parsing/aggregation behavior. |
| apps/dashboard/src/utils/project-status.test.ts | Adds unit coverage for status precedence + label/meta utilities. |
| apps/dashboard/src/utils/subdomain.test.ts | Adds unit coverage for subdomain normalization behaviors. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| Tests are colocated with the code they cover: `foo.test.ts` sits next to `foo.ts`. Every | ||
| workspace uses [Vitest](https://vitest.dev/). |
| expect(normalizeSubdomain("café")).toBe("caf"); | ||
| }); | ||
|
|
||
| it("strips leading and trailing hyphens, unlike normalizeSubdomandInput", () => { |
| // Exhaustiveness guard: every ProjectStatus value must have non-empty | ||
| // styling, so adding a new status without wiring up its badge/dot fails | ||
| // this test loudly instead of silently rendering blank CSS classes. | ||
| const allStatuses: ProjectStatus[] = [ |
|
Thanks, all three were correct. Addressed in 07c7dc2. 1. 2. Typo fixed. 3. The "exhaustiveness guard" comment overclaimed. You were right that a manual array silently drifts. Rather than soften the wording, I made the guard real by keying it as a const STATUS_COVERAGE: Record<ProjectStatus, true> = { live: true, /* ... */ draft: true };
const allStatuses = Object.keys(STATUS_COVERAGE) as ProjectStatus[];Verified it actually fails now by temporarily adding So adding a status without covering it now breaks |
Summary
Adds Vitest test infrastructure to
apps/dashboardand the first suites that use it, taking the workspace from 25 tests to 116 and making React component tests possible for the first time.Motivation
apps/dashboardis the largest workspace in the repo (474 source files) and had 4 test files. That gap was not neglect: writing a component test was impossible without config changes. Three separate blockers:vitest.config.tsexisted for the workspace.tsconfig.jsonsets"jsx": "preserve", which Vitest's esbuild transform honors, so any.tsxtest file was a syntax error before it ran.@/*alias did not resolve at runtime. Vitest does not readtsconfig.paths. The existingconfirm-server-access.test.tsimports@/lib/api, but only viaimport type, which esbuild erases, so it passed by accident. The first test doing a real value import through@/would have failed to resolve.With the infrastructure in place, this PR also covers four pure-logic modules that had no regression protection, including the project status derivation that decides what every project card displays.
Related issue
Refs #216. Per CONTRIBUTING.md, tests do not need prior issue agreement, so this is not gated on one.
Changes
apps/dashboardvitest.config.ts. Wires the@alias for runtime resolution, forcesjsx: "automatic"to override the app'spreservesetting, and splits the suite into two Vitest projects:.test.tson Node,.test.tsxon jsdom.vitest.setup.ts. Registers@testing-library/jest-dommatchers and an explicitcleanup()inafterEach(needed becauseglobalsis disabled, so RTL's automatic cleanup never registers).src/utils/deploymentPhaseDetector.test.ts(39 tests) covering the deploy phase state machine.src/utils/project-status.test.ts(29 tests) pinning every branch of thegetProjectStatusprecedence chain.src/utils/subdomain.test.ts(12 tests).src/lib/dotenv.test.ts(11 tests).jsdom,@testing-library/react,@testing-library/dom,@testing-library/jest-dom,@testing-library/user-event.CONTRIBUTING.mdWhy the environment is split rather than flat jsdom
Running the whole suite under jsdom breaks the existing
src/i18n/i18n-parity.test.ts. jsdom patches the globalURLsoimport.meta.urlresolves against the document location instead of afile:base, andscripts/check-i18n.mjsthen throwsTypeError: The URL must be of scheme file. Splitting by extension keeps pure-logic tests on Node, which matches their pre-existing behavior exactly and avoids paying jsdom startup for suites that never touch the DOM. Environment setup time for the existing suite went from 7.75s under flat jsdom to 0ms.environmentMatchGlobswas removed in Vitest 4, sotest.projectsis the supported mechanism.Verification
Full suite, typecheck, and a scoped Prettier check:
$ bun run --cwd apps/dashboard test ✓ unit src/lib/dotenv.test.ts (11 tests) ✓ unit src/app/(dashboard)/projects/[id]/components/env-diff.test.ts (12 tests) ✓ unit src/utils/subdomain.test.ts (12 tests) ✓ unit src/components/permissions/confirm-server-access.test.ts (6 tests) ✓ unit src/utils/project-status.test.ts (29 tests) ✓ unit src/utils/deploymentPhaseDetector.test.ts (39 tests) ✓ unit src/i18n/i18n-parity.test.ts (2 tests) ✓ unit src/lib/api/urls.test.ts (5 tests) Test Files 8 passed (8) Tests 116 passed (116)The 25 pre-existing tests pass unchanged, which was the hard requirement for adding the config.
The full workspace suite is green, confirming the new config does not disturb any other workspace:
Proving the tests can fail. Every suite was mutation-checked. As one example, inverting the precedence so
activeDeploymentIdis checked beforeawaitingDecisioninproject-status.ts:Exactly the two guarding tests fail, with no collateral cascade. Source restored afterwards with
git checkout --, confirmed clean viagit diff.Component testing works. Verified with a temporary
.tsxtest asserting all four load-bearing behaviors together (JSX transform, jsdom render, atoBeInTheDocument()matcher proving the setup file ran, a real value import through@/, and cleanup between tests). Removed before commit; the first committed component test will land with the follow-up coveringlib/sseMessageProcessors.ts.Notes for reviewers
Four pre-existing bugs surfaced in
deploymentPhaseDetector.tswhile writing its tests. They are not fixed here, because behavior changes need an agreed issue first. The tests characterize current behavior rather than asserting desired behavior, and I am happy to open an issue:readypattern/done/iis unanchored, so text like "we condone this" is classified asready.---PHASE: xyz---marker returnsprogress: 0unconditionally, discarding real progress elsewhere in the line.parseLogEntryproduces malformed output like"-3:-40"whentimestampprecedesstartTime, becausepadStartdoes not handle a negative sign.aggregatePhaseInforeads onlylogs.slice(-5)with a strict>against an initialmaxProgressof 0, so a completed 100% log outside that window is invisible to the aggregate.Prettier reports two pre-existing findings in
CONTRIBUTING.md(lines 11-12 and 35) that predate this branch. I deliberately left them alone rather than bundling unrelated reformatting; my added lines are Prettier-clean.Checklist
bun run test,bun run --cwd <workspace> lint, andbun formatall pass locally