Skip to content

Run the front-end unit tests from the repo root, with one storage for every Node - #596

Merged
jehanazad merged 4 commits into
mainfrom
web/root-unit-tests
Oct 1, 2026
Merged

jehanazad merged 4 commits into
mainfrom
web/root-unit-tests

Conversation

@Babissimo

@Babissimo Babissimo commented Sep 23, 2026 •

Copy link
Copy Markdown
Contributor

ClickUp: 2.4 Run the front-end unit tests from the repo root, with one localStorage stub. Also closes the Node 25+ half of 123zgec36re.

Stacked on #613, at the top of the stack main ← #595 ← #602 ← #620 ← #607 ← #617 ← #613 ← #596, which lands in one deploy.

Why

  • There was no root Vitest config or root test script. A bare vitest from the root collected e2e's Playwright specs and ran the dashboard tests without dashboard/vite.config.js (no jsdom, globals, jest-dom or @edsc/timeline alias): about eleven false failures. The only documented way to run everything was a chained one-liner in ONBOARDING.
  • Node 25 and later define localStorage and sessionStorage globals that hide jsdom's (localStorage is undefined without --localstorage-file). Eight test files carried their own copy of an in-memory Storage to get round it, and LiveAircraftMap.test.tsx, which used the ambient one, failed on any local Node 25+ while CI's Node 20 passed.

What

Two commits:

  1. One storage for every dashboard test. dashboard/src/test/setupStorage.ts, a second setupFiles entry, installs jsdom's own localStorage and sessionStorage on window before collection and again before every test, emptied each time. So every Node sees the same implementation, and a test that swaps in a throwing storage cannot leak it into the next. The eight copies go; tests seed and read window.localStorage directly. The throwing-storage stubs stay, as they test a different thing.
  2. Root commands. A root vitest.config.js runs every workspace whose test script runs Vitest (dashboard, packages/shared) as a project under its own config. The root package.json gains test, typecheck, lint, build and check, the last running the four in web-build's order. This is what 2.2's just check is to call. ONBOARDING's one-liner is replaced.

CI is unchanged: web-build still runs each workspace's own scripts from its matrix.

Verification

  • Root npm test on Node 26.9 (the laptop), with no NODE_OPTIONS: 119 files, 1388 tests, all passed.
  • Node 20 is CI's, and web-build runs these same suites on it, so this PR's run is the Node 20 result. A local run in node:20-alpine at a load average above 250 failed only on timeouts (13 at the 5 s limit, one findByRole), all in tests that pass on Node 26 and none touching storage, plus scalarTheme.test.ts, which reads backend/ and so could not import from the copy that run used.
  • Control: with the setup file emptied, LiveAircraftMap.test.tsx and usePersistedState.test.tsx fail 11 tests on Node 26 with Cannot read properties of undefined (reading 'setItem').
  • The changed files also pass under --pool=vmThreads.
  • vitest list from the root collects 116 dashboard and 3 shared files, and no e2e specs.
  • npm run typecheck and npm run lint pass (one pre-existing set-state-in-effect warning).

🤖 Generated with Claude Code

@claude

This comment has been minimized.

Babissimo added a commit that referenced this pull request Sep 25, 2026
Ten test files carried their own matchMedia stub, each a near copy of the last: five identical, two keeping listeners so a test could flip the OS preference, three inline in a render helper, and two in LiveAircraftMap installed through vi.stubGlobal. A fix to one (a listener that is never removed, a missing media field) reached none of the others.

dashboard/src/test/matchMedia.ts now holds the one stub, with the listener-keeping behaviour the theme tests need, and every file imports it. It installs through vi.stubGlobal so LiveAircraftMap's unstubAllGlobals teardown still removes it, and its implementation is given to vi.fn directly so a vi.resetAllMocks cannot empty it.

It stays a helper the tests call rather than a default in the shared setup file. ThemeContext treats a missing matchMedia as a real case (jsdom, any non-browser render), and a test that does not stub one exercises that path; a setup-file default would take it away from every test at once.

Raised by the review on #596. ClickUp 2.4 (123zgec4mxr).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Babissimo

Copy link
Copy Markdown
Contributor Author

Both review findings are addressed in 2251bb2.

  1. Silent fallback in setupStorage.ts. The setup file now throws when the environment is jsdom (recognised by jsdom's default user agent, … jsdom/<version>) but globalThis.jsdom is missing. A file that opts into another environment still loads, since it has no jsdom user agent. Checked both ways: a copy reading a missing global failed theme.test.tsx with the new error, and a @vitest-environment node probe file passed.

  2. Duplicated matchMedia stub. All ten copies now import one helper, dashboard/src/test/matchMedia.ts (its own commit). It is a helper the tests call, not a default in the shared setup file: ThemeContext treats a missing matchMedia as a real case (jsdom, any non-browser render), and a test that doesn't stub it exercises that path. A setup-file default would remove that path from every test at once. The helper installs through vi.stubGlobal, so LiveAircraftMap's unstubAllGlobals teardown still removes it.

Babissimo added a commit that referenced this pull request Sep 25, 2026
Ten test files carried their own matchMedia stub, each a near copy of the last: five identical, two keeping listeners so a test could flip the OS preference, three inline in a render helper, and two in LiveAircraftMap installed through vi.stubGlobal. A fix to one (a listener that is never removed, a missing media field) reached none of the others.

dashboard/src/test/matchMedia.ts now holds the one stub, with the listener-keeping behaviour the theme tests need, and every file imports it. It installs through vi.stubGlobal so LiveAircraftMap's unstubAllGlobals teardown still removes it, and its implementation is given to vi.fn directly so a vi.resetAllMocks cannot empty it.

It stays a helper the tests call rather than a default in the shared setup file. ThemeContext treats a missing matchMedia as a real case (jsdom, any non-browser render), and a test that does not stub one exercises that path; a setup-file default would take it away from every test at once.

Raised by the review on #596. ClickUp 2.4 (123zgec4mxr).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@claude

This comment has been minimized.

Babissimo and others added 3 commits September 28, 2026 12:25
Node 25 and later define localStorage and sessionStorage globals of their own (localStorage is undefined without --localstorage-file), and in vitest's jsdom environment they hide jsdom's. Eight test files worked around it with their own in-memory Storage, each a copy of the last, and LiveAircraftMap.test.tsx used the ambient one and failed its beforeEach on any local Node 25+ while CI's Node 20 passed.

One setup file now installs jsdom's own storage on window before collection and again before every test, emptying it each time, so every test sees the same implementation on every Node, and a test that swaps in a throwing storage cannot leak it into the next. The per-file copies go, and the tests seed and read window.localStorage directly. jsdom's storage rather than a hand-written Map keeps the Storage semantics real.

Closes the Node 25+ half of ClickUp 123zgec36re; part of 2.4 (123zgec4mxr).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…he repo root

There was no root Vitest config and no root test script. A bare vitest from the root collected e2e's Playwright specs and ran the dashboard tests without dashboard/vite.config.js (no jsdom, no globals, no jest-dom, no @edsc/timeline alias), which reads as about eleven failing files. The only documented way to run everything was a chained one-liner in ONBOARDING.

A root vitest.config.js now runs every workspace with a test script as a project under its own config, derived from package.json so a new workspace joins without an edit here. The root package.json gains test, typecheck, lint, build and check; check runs the four in CI's order, so a local pass covers what web-build runs. It is the npm half of the just check recipe 2.2 adds. CI keeps running each workspace's scripts from its matrix, so nothing there changes.

ClickUp 2.4 (123zgec4mxr).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Ten test files carried their own matchMedia stub, each a near copy of the last: five identical, two keeping listeners so a test could flip the OS preference, three inline in a render helper, and two in LiveAircraftMap installed through vi.stubGlobal. A fix to one (a listener that is never removed, a missing media field) reached none of the others.

dashboard/src/test/matchMedia.ts now holds the one stub, with the listener-keeping behaviour the theme tests need, and every file imports it. It installs through vi.stubGlobal so LiveAircraftMap's unstubAllGlobals teardown still removes it, and its implementation is given to vi.fn directly so a vi.resetAllMocks cannot empty it.

It stays a helper the tests call rather than a default in the shared setup file. ThemeContext treats a missing matchMedia as a real case (jsdom, any non-browser render), and a test that does not stub one exercises that path; a setup-file default would take it away from every test at once.

Raised by the review on #596. ClickUp 2.4 (123zgec4mxr).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@claude

This comment has been minimized.

Base automatically changed from tools/verify-deploy to main October 1, 2026 01:38
@claude

claude Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

Claude finished @jehanazad's task in 2m 10s —— View job


Tasks

  • Gather context (PR diff, changed files, prior review comments)
  • Understand the request (run /code-review:code-review on this PR)
  • Run code review
  • Post findings

Review

Isolated this PR's own diff (aa67e90^..128adab, the three commits unique to this branch — the rest of the branch is the stack it's built on) and independently re-checked it against the 18 files/+150/-285 the PR describes.

  • vitest.config.js: manifest(dir).scripts?.test ?? "" filtered against /\bvitest\b/ correctly keeps dashboard and packages/shared (both "test": "vitest run") and drops e2e (no test script, only test:e2e*). Confirmed vitest: ^4.1.4 is the version installed at the root — projects is the correct Vitest 3+ config key for this (the renamed replacement for the old workspace option), so there's no version mismatch with the config shape used.
  • setupStorage.ts: the jsdom-UA guard only throws when globalThis.jsdom is missing and the environment's UA matches jsdom's own (/\bjsdom\//), so a file that opts into @vitest-environment node is unaffected, as the PR's follow-up comment claims. The beforeEach re-install()s before clearing, which is required because the "throws on every access" stubs in theme.test.tsx, sidebarCollapse.test.tsx, etc. all replace window.localStorage with configurable: true, letting setupStorage's Object.defineProperty reclaim it on the next test — verified this is consistent across every such stub left in the diff, no non-configurable replacement that would break the recovery.
  • CI stays decoupled from the new root config: web-build's matrix (.github/workflows/ci.yml lines ~350–365) runs npm test -w <workspace>, which executes that workspace's own test script from inside its own directory — it never touches the new root vitest.config.js, so the "CI is unchanged" claim holds.
  • Mechanical test refactors (appearanceSwitch, errorBoundary, frontDoor, mapShell, mapThemeBinding, physicsRoute, signOutLanding, signedOutChrome, sidebarCollapse, theme, usePersistedState, LiveAircraftMap): read every file's diff in full. All are 1:1 replacements of local storage/matchMedia stubs with window.localStorage / the shared stubMatchMedia helper — no change in seeded values, assertions, or cleanup order. Grepped the whole dashboard/src tree for leftover stubStorage definitions or inline matchMedia mocks: none remain.
  • Root package.json: new scripts block adds no new dependencies, so no package-lock.json update is needed; check runs lint && typecheck && test && build, matching web-build's per-workspace order as the PR states.

This matches the outcome of the two earlier review passes already in this thread (the 2251bb29 fixes for the silent-fallback and duplicated-matchMedia findings, and the subsequent independent pass) — no new issues found on this pass either. As in that prior pass, I did not execute the suite here (no dependency install in this environment); this is a static review, and the PR's own verification section (Node 26 and Node 20 runs, the empty-setup-file control, --pool=vmThreads) covers the empirical side.

No issues found.
· branch web/root-unit-tests

@jehanazad
jehanazad merged commit 271641f into main Oct 1, 2026
18 checks passed
@jehanazad
jehanazad deleted the web/root-unit-tests branch October 1, 2026 01:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants