Skip to content

Commit da239a1

Browse files
refactor(test): run web tests on native parallel via injectable seams (#2307)
* fix(ci): restore green CI on main after parallel test merge Declare transitive deps for Bun 1.3 isolated workspace installs and align google-auth-library/gaxios versions so root type-check resolves express, Google API, and web test types correctly. Co-authored-by: Cursor <cursoragent@cursor.com> * refactor(test): clean up post-Jest migration test infrastructure Remove one-time Jest migration scripts and @types/jest, fix stale playbook docs, split web.preload into setup modules, consolidate test launchers via runner-utils, and add mongo-free :fast paths for backend/sync/scripts using synthetic COMPASS_TEST_MONGO_URI env (no mongod boot). Co-authored-by: Cursor <cursoragent@cursor.com> * fix(test): resolve merge issues and CI lint failures Use main bun.lock for compatible bson/mongodb versions, fix bench comment that prematurely closed a block comment, and apply biome formatting fixes. Co-authored-by: Cursor <cursoragent@cursor.com> * refactor(test): run web tests on native parallel via injectable seams Replace preload mock.module clusters for session, toast, and Google auth with registry ports so web can use bun test --parallel like other packages, removing the per-file test-isolated launcher. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(web): re-mirror indexeddb globals after parallel isolate reset Move globalThis mirroring out of the cached import promise so ensureIndexedDbTestEnv re-applies fake-indexeddb when Bun --isolate clears globals but the module-level indexedDbReady promise is settled. * refactor(web): simplify web test infrastructure seams Replace verbose session port binding with a typed cast, inline the toast port default helper, and merge teardown afterAll hooks. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(test): harden indexeddb mirror and deflake calendar list toggle test Re-mirror fake-indexeddb globals after Bun isolate clears them, route session-expired toast dismiss through the toast port, and wait for optimistic aria-pressed updates under CI contention. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(web): reset session provider singletons between tests Parallel web test workers reuse module state, so sessionInit flags, auth store, and onAnyEvent listeners must be cleared in afterEach alongside other module-level stores. * fix(scripts): restore 60s hook timeout for fast parallel test tiers * fix(web): close SSE stream in resetSessionProviderForTests * fix(test): reset BaseApi adapter and deflake CalendarList under parallel Clear the shared axios adapter after every web test so parallel workers do not inherit stale mocks, and wait for optimistic toggle state with zero-delay userEvent. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(test): stabilize web suite in single-process runner Run web tests sequentially to avoid MSW XHR isolate breakage, revert session provider reset that poisoned MSW, migrate AuthModal off mock.module for Google auth, and deflake calendar-list and reconnect-toast tests. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 403969e commit da239a1

37 files changed

Lines changed: 529 additions & 531 deletions

docs/development/feature-file-map.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ Use this document to find the first files to inspect for common Compass changes.
104104

105105
## Test Anchors
106106

107-
- Per-file test launchers: `packages/scripts/src/testing/test-with-mongo.ts` (Mongo packages), `packages/scripts/src/testing/test-isolated.ts` (web)
107+
- Per-file test launchers: `packages/scripts/src/testing/test-mongo-env.ts` (Mongo packages), `packages/scripts/src/testing/test-parallel.ts` (core + web + fast tiers)
108108
- Bun/Jest compatibility shim used by every package's tests: `packages/scripts/src/testing/apply-bun-jest-compat.cjs`
109109
- Core test setup: `packages/core/src/__tests__`
110110
- Web test setup: `packages/web/src/__tests__`

docs/development/testing-playbook.md

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -47,21 +47,25 @@ E2E workflow (`test-e2e.yml`) is separate and runs on pull requests to `main` vi
4747
Every package runs on Bun's native test runner (Bun 1.3.14+); Jest has been removed.
4848

4949
- `bun run test:core``test-parallel.ts core`: `bun test --parallel` with `core.preload.ts`.
50-
- `bun run test:web``test-isolated.ts`: one Bun process per test file with `web.preload.ts` (jsdom, MSW, Zustand reset). Required because preload `mock.module` mocks are process-global and Bun's `--parallel`/`--isolate` clears them between files.
50+
- `bun run test:web``test-parallel.ts web`: one `bun test` process with `web.preload.ts` (jsdom, MSW, Zustand reset, injectable test seams). Files run sequentially — not `--parallel` — because MSW's XHR patch and jsdom globals do not survive Bun's per-file `--isolate`.
5151
- `bun run test:backend`, `bun run test:scripts`, and `bun run test:sync``test-mongo-env.ts` boots one shared in-memory Mongo replica set, then runs `bun test --parallel` with the package preload. Per-file DB names come from `setupTestDb(import.meta.url)`.
5252
- `bun run test:backend:fast`, `bun run test:sync:fast`, and `bun run test:scripts:fast``test-parallel.ts` with mongo-free preloads; excludes `*.db.test.*` via `--path-ignore-patterns`. No mongod boot — use these for day-to-day backend/sync/scripts work that does not touch persistence.
53-
- Backend Google Calendar and SuperTokens behavior in tests use injectable seams (`TestGcalFixture`, `session.middleware`, `supertokens.registry`, `LoggerFactory`) instead of preload `mock.module` clusters.
53+
- Backend and web SuperTokens, toast, and Google-auth behavior in tests use injectable seams (`TestGcalFixture`, `session.middleware`, `supertokens.registry`, `session.port`, `toast.port`, `useStartGoogleAuthorization.registry`, `LoggerFactory`) instead of preload `mock.module` clusters.
5454
- Test files import lifecycle/assertion APIs from `bun:test` only (`mock`, `spyOn`, `mock.module` where unavoidable).
5555

5656
### Known infrastructure constraints
5757

58-
**Web per-file isolation.** `test-isolated.ts` spawns one `bun test` process per web test file (up to 8 concurrent). This is not optional today: the web preload registers process-wide `mock.module` mocks for SuperTokens, react-toastify, and Google auth. Bun's native `--parallel` enables `--isolate`, which clears `mock.module` state and causes cross-file leaks. Eliminating the launcher requires either a Bun fix or migrating those deps to injectable seams (as backend already does).
58+
**Web test seams.** Session, toast, and Google authorization use injectable ports registered in `@web/__tests__/helpers/web-test-seams.ts`. The preload lifecycle calls `installDefaultWebTestSeams()` in `beforeEach` and `resetWebTestSeams()` in `afterEach`, so web no longer needs preload `mock.module` clusters or a per-file process launcher.
59+
60+
**Web sequential runner.** Web runs in one Bun process with files executed sequentially. Native `--parallel` is intentionally disabled: Bun's `--isolate` clears jsdom/MSW globals between files, breaking XHR mocking (`oldXMLHttpRequest is undefined`). Core/backend still use `--parallel`.
61+
62+
**IndexedDB in tests.** `ensureIndexedDbTestEnv()` re-applies fake-indexeddb globals when needed.
5963

6064
**Store reset registry.** Zustand stores are module singletons. `@web/__tests__/utils/state/reset-stores.ts` registers every store that must reset between tests; `web.preload.ts` calls `resetAllStores()` in a global `afterEach`. When adding a new store, register it in both `reset-stores.ts` and `seed-stores.ts` or state will leak silently across tests.
6165

6266
**Fast vs full mongo tiers.** Files named `*.db.test.ts(x)` connect to the shared in-memory replica set started by `test-mongo-env.ts`. Everything else is "fast" and can run without mongod via the `:fast` scripts. Full-suite commands (`test:backend`, etc.) still boot mongod because some non-db tests import backend modules that expect `MONGO_URI` to be set even when they do not connect.
6367

64-
**Web preload modules.** Setup lives under `packages/web/src/__tests__/setup/` (jsdom env, browser polyfills, asset stubs, indexedDB, test lifecycle). The entry point is `web.preload.ts`, which calls `mockNodeModules()` before dynamically importing the lifecycle module so `mock.module` registration is not hoisted past SuperTokens mocks.
68+
**Web preload modules.** Setup lives under `packages/web/src/__tests__/setup/` (jsdom env, browser polyfills, asset stubs, indexedDB, test lifecycle). The entry point is `web.preload.ts`, which loads setup side effects then dynamically imports the lifecycle module.
6569

6670
### Test file naming and tiers
6771

@@ -132,8 +136,8 @@ Isolation rules:
132136
- Avoid mocking shared UI primitives such as `TooltipWrapper`, `@floating-ui/react`, or session hooks in broad component tests. Even with per-file isolation, broad mocks can hide integration behavior inside that file.
133137
- If a test replaces globals (`fetch`, `document.getElementById`, storage, timers, console methods), restore the original value in teardown.
134138
- Prefer `renderWithStore`, `createStoreWrapper`, or a focused provider harness over mocking `@web/store` or `store.hooks`.
135-
- `bun run test:web` is the acceptance check for web test isolation. It runs every file in a fresh process via `test-isolated.ts`; a focused test can still miss interactions within its own file.
136-
- `mock.module` is process-global, not test-scoped. The per-file runner prevents cross-file leaks, and the web preload restores mocks when each file finishes, but tests within one file still share that file's module registry.
139+
- `bun run test:web` is the acceptance check for the web suite. It runs the full package on native `bun test --parallel`; a focused single-file run can still miss cross-file interactions.
140+
- `mock.module` is process-global within a test file, not test-scoped. Prefer injectable seams (`session.port`, `toast.port`, hook registries) or `spyOn` with teardown. File-level `mock.module` remains acceptable for one-off module substitution when a seam does not exist yet.
137141
- To focus an element on mount in this jsdom setup, use React's `autoFocus` prop or a stable callback ref (`ref={useCallback(n => n?.focus(), [])}`) — both fire in the commit phase. A `useEffect(() => ref.current?.focus())` does **not** make the element `document.activeElement` in tests. `autoFocus` trips biome's `lint/a11y/noAutofocus` (error) and a JSX-attribute `biome-ignore` comment breaks the formatter, so prefer the callback ref.
138142
- `FloatingFocusManager` fights virtual-focus combobox palettes: for a component that keeps real focus in one input while using `useListNavigation({ virtual: true })` + `aria-activedescendant` (a command-palette style pattern), `FloatingFocusManager` asynchronously grabs focus to the panel container and steals it back from the input. Drop it — `useDismiss` still handles Escape/outside-press without it. It belongs on anchored forms with a real reference element instead (e.g. `FloatingEventForm`).
139143

@@ -143,7 +147,7 @@ Primary setup files:
143147

144148
- `packages/web/src/__tests__/web.preload.ts` (orchestrator)
145149
- `packages/web/src/__tests__/web.test.init.ts` (API base URL, Google client id)
146-
- `packages/web/src/__tests__/__mocks__/mock.setup.ts` (SuperTokens, toastify, Google auth module mocks)
150+
- `packages/web/src/__tests__/helpers/web-test-seams.ts` (default session/toast/Google-auth test ports)
147151
- `packages/web/src/__tests__/__mocks__/server/mock.handlers.ts`
148152

149153
Current defaults worth knowing:
@@ -152,7 +156,7 @@ Current defaults worth knowing:
152156
- unhandled HTTP requests fail the test (instead of silently passing)
153157
- IndexedDB is provided by `fake-indexeddb/auto`
154158
- `structuredClone` is polyfilled for test environments that do not provide it
155-
- SuperTokens session existence is reset to `true` in `beforeEach`
159+
- SuperTokens session existence defaults to `true` via `installDefaultWebTestSeams()` in `beforeEach`
156160

157161
Important built-in handlers include:
158162

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
"test:core": "bun packages/scripts/src/testing/test-parallel.ts core --",
3939
"test:sync": "bun packages/scripts/src/testing/test-mongo-env.ts sync --",
4040
"test:sync:fast": "bun packages/scripts/src/testing/test-parallel.ts sync-fast -- --path-ignore-patterns '**/*.db.test.ts'",
41-
"test:web": "bun packages/scripts/src/testing/test-isolated.ts",
41+
"test:web": "bun packages/scripts/src/testing/test-parallel.ts web --",
4242
"test:scripts": "bun packages/scripts/src/testing/test-mongo-env.ts scripts --",
4343
"test:scripts:fast": "bun packages/scripts/src/testing/test-parallel.ts scripts-fast -- --path-ignore-patterns '**/*.db.test.ts'",
4444
"test:scripts:db": "bun packages/scripts/src/testing/test-mongo-env.ts scripts -- './packages/scripts/src/**/*.db.test.ts'",

packages/scripts/src/testing/test-isolated.ts

Lines changed: 0 additions & 69 deletions
This file was deleted.

packages/scripts/src/testing/test-parallel.ts

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,12 @@ import { backendTestSpawnEnv } from "./backend-test-env";
1010
import { formatDuration, resolveTestTargets } from "./runner-utils";
1111
import { resolve } from "node:path";
1212

13-
type ProfileName = "core" | "backend-fast" | "sync-fast" | "scripts-fast";
13+
type ProfileName =
14+
| "core"
15+
| "web"
16+
| "backend-fast"
17+
| "sync-fast"
18+
| "scripts-fast";
1419

1520
/** Synthetic URI for :fast tiers — env contract only, no mongod connection. */
1621
const FAST_MONGO_URI = "mongodb://127.0.0.1:27017/unused-compass-test";
@@ -24,6 +29,11 @@ const PROFILES: Record<
2429
scan: "./packages/core/src",
2530
label: "core",
2631
},
32+
web: {
33+
preload: "packages/web/src/__tests__/web.preload.ts",
34+
scan: "./packages/web/src",
35+
label: "web",
36+
},
2737
"backend-fast": {
2838
preload: "packages/backend/src/__tests__/backend.preload.fast.ts",
2939
scan: "./packages/backend/src",
@@ -50,7 +60,7 @@ const extraArgs =
5060

5161
if (!profile || !PROFILES[profile]) {
5262
console.error(
53-
"Usage: test-parallel.ts <core|backend-fast|sync-fast|scripts-fast> -- [bun test flags/paths...]",
63+
"Usage: test-parallel.ts <core|web|backend-fast|sync-fast|scripts-fast> -- [bun test flags/paths...]",
5464
);
5565
process.exit(2);
5666
}
@@ -61,10 +71,21 @@ const { targets, bunFlags } = resolveTestTargets(scan, extraArgs);
6171

6272
const started = Date.now();
6373

74+
const needsHookTimeout =
75+
profile === "backend-fast" ||
76+
profile === "sync-fast" ||
77+
profile === "scripts-fast";
78+
79+
// Web uses jsdom + MSW XHR patching + module singletons that Bun's parallel
80+
// `--isolate` clears between files (MSW's oldXMLHttpRequest becomes undefined).
81+
// One process, sequential files is still far faster than the old per-file launcher.
82+
const parallelFlag = profile === "web" ? [] : ["--parallel"];
83+
6484
const testTargets = [
6585
"bun",
6686
"test",
67-
"--parallel",
87+
...parallelFlag,
88+
...(needsHookTimeout ? ["--timeout", "60000"] : []),
6889
"--preload",
6990
preloadPath,
7091
...bunFlags,

packages/web/src/__tests__/__mocks__/asset-empty-style.js

Lines changed: 0 additions & 1 deletion
This file was deleted.

packages/web/src/__tests__/__mocks__/asset-file-stub.js

Lines changed: 0 additions & 1 deletion
This file was deleted.

packages/web/src/__tests__/__mocks__/asset-svg-stub.tsx

Lines changed: 0 additions & 8 deletions
This file was deleted.

packages/web/src/__tests__/__mocks__/css.stub.js

Lines changed: 0 additions & 4 deletions
This file was deleted.

packages/web/src/__tests__/__mocks__/file.stub.js

Lines changed: 0 additions & 3 deletions
This file was deleted.

0 commit comments

Comments
 (0)