@samitouri / QOS-React-2 / commits / 3c397fe760

[test] Stop leaking mocked performance clocks into later test files (#37379)

Test files running in the Node.js Jest environment share the worker process's `performance` object, because [`jest-environment-node` installs it by reference](https://github.com/jestjs/jest/blob/v29.7.0/packages/jest-environment-node/src/index.ts#L85-L103) rather than by copy. When a test mocked the clock with `Object.defineProperty(performance, 'now', ...)`, the mutation hit the shared object and was never undone, since Jest only restores `jest.spyOn` mocks when a file's runtime is torn down ([`jest-runtime`'s `teardown()` calls `restoreAllMocks()`](https://github.com/jestjs/jest/blob/v29.7.0/packages/jest-runtime/src/index.ts#L1358-L1359)). Every subsequent test file in the same worker then observed the fake clock, including jsdom-based files, whose [`performance.now()` subtracts a window-creation timestamp from the shared object's `now()`](https://github.com/jsdom/jsdom/blob/v22.1.0/lib/jsdom/living/hr-time/Performance-impl.js#L13-L14). This change switches the six affected test files to `jest.spyOn(performance, 'now')` and `jest.spyOn(performance, 'timeOrigin', 'get')`, which Jest restores automatically at teardown. `ReactFlightDOMEdge-test.js` runs in jsdom and therefore did not leak, but it used the same pattern and is converted for consistency. This change is mostly for test hygiene. Was discovered while investigating a flaky `{"time":NaN}` serialisation bug (e.g. https://github.com/react/react/actions/runs/32878999825/job/97903912119) Co-authored-by: Claude Code (kimi-k3[1m]) <noreply@anthropic.com>

Sebastian "Sebbie" Silbermann committed Aug 26, 2026 at 12:49 UTC 3c397fe76015d6988cdcb6f74aff978a3cc6a37a
6 files changed +14 -50
packages/react-client/src/__tests__/ReactFlight-test.js
+2 -9
@@ -92,17 +92,10 @@ describe('ReactFlight', () => {
92 beforeEach(() => {
93 // Mock performance.now for timing tests
94 let time = 10;
95 - const now = jest.fn().mockImplementation(() => {
95 + jest.spyOn(performance, 'timeOrigin', 'get').mockReturnValue(time);
96 + jest.spyOn(performance, 'now').mockImplementation(() => {
97 return time++;
98 });
98 - Object.defineProperty(performance, 'timeOrigin', {
99 - value: time,
100 - configurable: true,
101 - });
102 - Object.defineProperty(performance, 'now', {
103 - value: now,
104 - configurable: true,
105 - });
99
100 jest.resetModules();
101 jest.mock('react', () => require('react/react.react-server'));
packages/react-client/src/__tests__/ReactFlightDebugChannel-test.js
+2 -9
@@ -29,17 +29,10 @@ describe('ReactFlight', () => {
29 beforeEach(() => {
30 // Mock performance.now for timing tests
31 let time = 10;
32 - const now = jest.fn().mockImplementation(() => {
32 + jest.spyOn(performance, 'timeOrigin', 'get').mockReturnValue(time);
33 + jest.spyOn(performance, 'now').mockImplementation(() => {
34 return time++;
35 });
35 - Object.defineProperty(performance, 'timeOrigin', {
36 - value: time,
37 - configurable: true,
38 - });
39 - Object.defineProperty(performance, 'now', {
40 - value: now,
41 - configurable: true,
42 - });
36
37 jest.resetModules();
38 jest.mock('react', () => require('react/react.react-server'));
packages/react-reconciler/src/__tests__/ReactOwnerStacks-test.js
+2 -9
@@ -27,17 +27,10 @@ describe('ReactOwnerStacks', () => {
27 time += timeMS;
28 };
29
30 - const now = jest.fn().mockImplementation(() => {
30 + jest.spyOn(performance, 'timeOrigin', 'get').mockReturnValue(time);
31 + jest.spyOn(performance, 'now').mockImplementation(() => {
32 return time++;
33 });
33 - Object.defineProperty(performance, 'timeOrigin', {
34 - value: time,
35 - configurable: true,
36 - });
37 - Object.defineProperty(performance, 'now', {
38 - value: now,
39 - configurable: true,
40 - });
34
35 jest.resetModules();
36 React = require('react');
packages/react-reconciler/src/__tests__/ReactPerformanceTrack-test.js
+4 -5
@@ -19,8 +19,9 @@ describe('ReactPerformanceTracks', () => {
19
20 beforeEach(() => {
21 performanceMeasureCalls.length = 0;
22 - Object.defineProperty(performance, 'measure', {
23 - value: jest.fn((measureName, reusableOptions) => {
22 + jest
23 + .spyOn(performance, 'measure')
24 + .mockImplementation((measureName, reusableOptions) => {
25 performanceMeasureCalls.push([
26 measureName,
27 {
@@ -28,9 +29,7 @@ describe('ReactPerformanceTracks', () => {
29 ...reusableOptions,
30 },
31 ]);
31 - }),
32 - configurable: true,
33 - });
32 + });
33 console.timeStamp = () => {};
34 jest.spyOn(console, 'timeStamp').mockImplementation(() => {});
35
packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMEdge-test.js
+2 -9
@@ -47,17 +47,10 @@ describe('ReactFlightDOMEdge', () => {
47 beforeEach(() => {
48 // Mock performance.now for timing tests
49 let time = 10;
50 - const now = jest.fn().mockImplementation(() => {
50 + jest.spyOn(performance, 'timeOrigin', 'get').mockReturnValue(time);
51 + jest.spyOn(performance, 'now').mockImplementation(() => {
52 return time++;
53 });
53 - Object.defineProperty(performance, 'timeOrigin', {
54 - value: time,
55 - configurable: true,
56 - });
57 - Object.defineProperty(performance, 'now', {
58 - value: now,
59 - configurable: true,
60 - });
54
55 jest.resetModules();
56
packages/react-server/src/__tests__/ReactFlightServer-test.js
+2 -9
@@ -46,17 +46,10 @@ describe('ReactFlight', () => {
46 time += timeMS;
47 jest.advanceTimersByTime(timeMS);
48 };
49 - const now = jest.fn().mockImplementation(() => {
49 + jest.spyOn(performance, 'timeOrigin', 'get').mockReturnValue(time);
50 + jest.spyOn(performance, 'now').mockImplementation(() => {
51 return time++;
52 });
52 - Object.defineProperty(performance, 'timeOrigin', {
53 - value: time,
54 - configurable: true,
55 - });
56 - Object.defineProperty(performance, 'now', {
57 - value: now,
58 - configurable: true,
59 - });
53
54 jest.resetModules();
55 jest.mock('react', () => require('react/react.react-server'));