Skip to content

Commit 0a23278

Browse files
committed
test: shim navigator in platformPaths tests for Node CI
1 parent 192ae6d commit 0a23278

2 files changed

Lines changed: 37 additions & 8 deletions

File tree

memory/mistakes.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,3 +121,13 @@ Rule: When converting effects between `useEffect` and `useLayoutEffect`, preserv
121121
Root cause: `autoScrollRef.current = true` still ran in `useEffect` after the new layout scroll pass, so first render on thread switch could evaluate stale `false`.
122122
Fix applied: Updated `src/features/messages/components/Messages.tsx` hook ordering/dependencies and added `re-pins to bottom on thread switch even when previous thread was scrolled up` in `src/features/messages/components/Messages.test.tsx`.
123123
Prevention rule: For scroll/anchor refs, pair layout-timing ref resets with layout-timing consumers and add regression coverage for cross-thread transitions.
124+
125+
## 2026-02-07 21:14
126+
Context: CI `test-js` failure (`platformPaths.test.ts`)
127+
Type: mistake
128+
Event: New mobile platform tests mutated `navigator` directly without ensuring a `navigator` object exists in Node test environments.
129+
Action: Updated `withNavigatorValues` in `src/utils/platformPaths.test.ts` to create a temporary `globalThis.navigator` shim when missing, restore descriptors after each test, and clean up with `Reflect.deleteProperty`.
130+
Rule: Node-targeted unit tests must not assume browser globals exist; create and tear down explicit shims in helper setup.
131+
Root cause: The tests were authored assuming `navigator` is always available, but Vitest runs with `environment: node` in CI.
132+
Fix applied: Added a global-scope navigator shim path and descriptor-safe restore logic in `src/utils/platformPaths.test.ts`.
133+
Prevention rule: For tests that patch `navigator`, `window`, or `document`, guard setup with `typeof ... === \"undefined\"` and perform full teardown in `finally`.

src/utils/platformPaths.test.ts

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,47 @@
11
import { describe, expect, it } from "vitest";
22
import { isMobilePlatform } from "./platformPaths";
33

4+
const globalScope = globalThis as typeof globalThis & { navigator?: Navigator };
5+
46
function withNavigatorValues(
57
values: Partial<Pick<Navigator, "platform" | "userAgent">>,
68
run: () => void,
79
) {
8-
const originalPlatform = Object.getOwnPropertyDescriptor(navigator, "platform");
9-
const originalUserAgent = Object.getOwnPropertyDescriptor(navigator, "userAgent");
10-
Object.defineProperty(navigator, "platform", {
10+
const hadNavigator = typeof globalScope.navigator !== "undefined";
11+
if (!hadNavigator) {
12+
Object.defineProperty(globalScope, "navigator", {
13+
configurable: true,
14+
writable: true,
15+
value: {},
16+
});
17+
}
18+
19+
const activeNavigator = globalScope.navigator as Navigator;
20+
const originalPlatform = Object.getOwnPropertyDescriptor(activeNavigator, "platform");
21+
const originalUserAgent = Object.getOwnPropertyDescriptor(activeNavigator, "userAgent");
22+
Object.defineProperty(activeNavigator, "platform", {
1123
configurable: true,
12-
value: values.platform ?? navigator.platform,
24+
value: values.platform ?? activeNavigator.platform ?? "",
1325
});
14-
Object.defineProperty(navigator, "userAgent", {
26+
Object.defineProperty(activeNavigator, "userAgent", {
1527
configurable: true,
16-
value: values.userAgent ?? navigator.userAgent,
28+
value: values.userAgent ?? activeNavigator.userAgent ?? "",
1729
});
1830
try {
1931
run();
2032
} finally {
2133
if (originalPlatform) {
22-
Object.defineProperty(navigator, "platform", originalPlatform);
34+
Object.defineProperty(activeNavigator, "platform", originalPlatform);
35+
} else {
36+
delete (activeNavigator as { platform?: string }).platform;
2337
}
2438
if (originalUserAgent) {
25-
Object.defineProperty(navigator, "userAgent", originalUserAgent);
39+
Object.defineProperty(activeNavigator, "userAgent", originalUserAgent);
40+
} else {
41+
delete (activeNavigator as { userAgent?: string }).userAgent;
42+
}
43+
if (!hadNavigator) {
44+
Reflect.deleteProperty(globalScope, "navigator");
2645
}
2746
}
2847
}

0 commit comments

Comments
 (0)