test(desktop): one DOM for the whole run and no module mocks

Merged
SA
1mo ago
+4221-4321

Part of removing cross-file test leaks so suites do not depend on bun test --isolate. Draft: the tests pass in any order, but five production components were restructured and none was checked in the running app yet (list at the end).

Why tests depended on file order

  1. mock.module is process-wide in bun. Fakes of renderer/lib/cloud-trpc, host-service-client, @superset/workspace-client, ../env, @superset/local-db, react and others reached every file that loaded later.
  2. happy-dom was registered and unregistered per file (58 register, 52 unregister). After the first unregister, that window's queueMicrotask and setTimeout never fire again. react-dom captures both at import, so in every later DOM file a React update outside act() never rendered. This was the cause of the useDiffCodeViewItems failures.
  3. Leftover state: LanguageAwareI18nProvider.test.tsx left the ja locale active (8月22日 in date tests); six files called a bare mock.restore(), which also tears down the preload's electron mock; two tests replaced globalThis.localStorage.

What changed

  • One DOM per process. test-setup.ts registers happy-dom once, in place of the hand-written document/window stubs, and never unregisters. It then puts back the native fetch, Request, Response, Headers, URL, WebSocket, abort classes, streams, timers and queueMicrotask, so non-DOM tests keep the runtime they had. Per-file register/unregister is removed from about 55 files. Two nested-process fixtures got an isRegistered guard.
  • No mock.module and no bare mock.restore() remain in apps/desktop/src outside the fixtures/ files that run in a nested process. Renderer tests use the real tRPC providers with a fake link, or a local Bun.serve.
  • Main process seams: DaemonTerminalManager, restartDaemon, tryListExistingDaemonSessions, the host-service coordinator and lock, ai-name, WorkerTaskRunner, killPortTarget, terminal-ws-transport and browserRuntimeRegistry take an optional dependency that defaults to the previous import.
  • Renderer logic moved out of hooks into functions: openNewWorkspace, openNewSession, openNewWorkspaceForLocalProject, importFolderFirst, invalidateGitQueries, resolveStatusRefetchInterval, useIsV1FlipLockedFor, useIsV2CloudEnabledFor.
  • PostHogLocaleTagger moved from inside LanguageAwareI18nProvider to its first child in routes/-layout.tsx, the only place that renders the provider.
  • useDiffCodeViewItems reads the client from workspaceTrpc.useUtils(); WorkspaceClientProvider passes the same client to both.

Components split into a container and a props-driven part

OriginalNew partPure move?
PagesViewPagesListJSX and classes identical. Hook order changed: session, favorites, workspace names and create-with-agent now run in the parent first. New optional searchDebounceMs.
PageWatcherMenuPageWatcherPopoverJSX, classes and mutations identical. Five data hooks moved to the parent in the same order; host client and agent picker arrive as props.
AgentSessionPickerAgentSessionSelectJSX and classes identical. The popover is controlled through open/onOpenChange props carrying the same state.
ExperimentalSettingsExperimentalSettingsListYes. Three session booleans arrive as props.
V2ProjectSettingsV2ProjectSettingsBodyJSX and classes identical. Three sections that need more providers are passed in as nodes at their original positions.

Tests

  • 7,979 tests in 531 files (7,969 in 526 before).
  • Deleted: V2ProjectSettings.test.tsx; its three tests asserted that mocked children appeared. Replaced by V2ProjectSettingsBody.test.tsx, which renders the real children and guards the #5843 regression (icon picker present and enabled).
  • Renamed with their tests kept: PagesView.test → PagesList.test (19), PageWatcherMenu.test → PageWatcherPopover.test (26), useFolderFirstImport.test → importFolderFirst.test, useGitStatus.test → invalidateGitQueries.test plus a new useGitStatus.test against a local WebSocket host. ExperimentalSettings: four tests became three.
  • Not covered: nothing fails if V2ProjectSettings stops rendering the body or drops one of its three passed-in sections. useOpenNewWorkspaceForLocalProject is tested as a function, not through the hook, because a mounted authClient.useSession() makes a real request.

Verification

bun 1.4.2, bun test --randomize --seed N: seeds 1-14 pass without --isolate (about 38 s) and seeds 1-13 pass with it (about 73 s). One --isolate run of seed 1 had four git and shell tests time out while the machine was overloaded, and passed on rerun. Repo typecheck, biome and check:i18n pass.

Before this leaves draft

Check in the running app: the Pages list (search, favorites, create with agent), the page watcher menu, the agent session picker, Experimental settings, V2 project settings, folder-first repository import, and that the PostHog locale tag is still set.


Summary by cubic

Makes the desktop test suite pass in any order without bun test --isolate by removing the cross-file leaks behind the order dependence: process-wide mock.module calls, per-file happy-dom registration, and leftover global state. Still a draft: five production components were split and need checking in the running app (Pages list, page watcher menu, agent session picker, Experimental/V2 project settings, folder-first import, PostHog locale tag). Verified with bun test --randomize seeds 1–14 without --isolate and 1–13 with it.

Why tests depended on order

  • mock.module is process-wide in bun, so mocks from one file reached every later file's imports.
  • Per-file happy-dom register/unregister broke queueMicrotask and setTimeout, so React updates outside act() never rendered in later DOM files.
  • Leftover state: the ja locale stayed active, six files called a bare mock.restore() that tore down the preload's electron mock, and two tests replaced globalThis.localStorage.

What changed

  • test-setup.ts registers happy-dom once per process, never unregisters it, and restores native fetch, WebSocket, streams, timers, and queueMicrotask; per-file register/unregister removed from ~55 files.
  • No mock.module or bare mock.restore() remain outside fixtures/; renderer tests use real tRPC providers with a fake link or a local Bun.serve.
  • Main-process code and renderer logic take optional dependency parameters that default to their previous imports.
  • Five components split into a container and a props-driven part (PagesList, PageWatcherPopover, AgentSessionSelect, ExperimentalSettingsList, V2ProjectSettingsBody); V2ProjectSettings.test.tsx replaced by V2ProjectSettingsBody.test.tsx.