@samitouri / QOS-React-1 / commits / 269bd40a95

[test] Remove the custom `toThrow` override for legacy V8 error messages (#37384)

The custom `toThrow` override in `scripts/jest/matchers/toThrow.js` wrapped the built-in matcher to rewrite the pre-Node-17 V8 error message format ("Cannot read property 'x' of undefined") into the modern one ("Cannot read properties of undefined (reading 'x')"), so the test suite could run on Node 12 to 16. On the Node versions this repo runs on (20 per `.nvmrc`, 24 in CI), V8 only ever produces the modern format, so the override is a passthrough. Mostly removing this because the custom matcher deep-imports `expect/build/toThrowMatchers`, which no longer resolves on Jest 30 because each Jest package is now bundled into a single file, so this removal unblocks the Jest 30 upgrade stacked on top. Co-authored-by: Claude Code (kimi-k3[1m]) <noreply@anthropic.com>

Sebastian "Sebbie" Silbermann committed Aug 26, 2026 at 19:24 UTC 269bd40a953a5339ad47c643f9b9dbce88ab878d
5 files changed +9 -61
packages/react-dom/src/__tests__/ReactServerRendering-test.js
+8 -8
@@ -147,7 +147,7 @@ describe('ReactDOMServer', () => {
147 it('should throw with silly args', () => {
148 expect(
149 ReactDOMServer.renderToString.bind(ReactDOMServer, {x: 123}),
150 - ).toThrowError(
150 + ).toThrow(
151 'Objects are not valid as a React child (found: object with keys {x})',
152 );
153 });
@@ -155,7 +155,7 @@ describe('ReactDOMServer', () => {
155 it('should throw prop mapping error for an <iframe /> with invalid props', () => {
156 expect(() => {
157 ReactDOMServer.renderToString(<iframe style="border:none;" />);
158 - }).toThrowError(
158 + }).toThrow(
159 'The `style` prop expects a mapping from style properties to values, not ' +
160 "a string. For example, style={{marginRight: spacing + 'em'}} when using JSX.",
161 );
@@ -290,7 +290,7 @@ describe('ReactDOMServer', () => {
290 it('should throw with silly args', () => {
291 expect(
292 ReactDOMServer.renderToStaticMarkup.bind(ReactDOMServer, {x: 123}),
293 - ).toThrowError(
293 + ).toThrow(
294 'Objects are not valid as a React child (found: object with keys {x})',
295 );
296 });
@@ -715,7 +715,7 @@ describe('ReactDOMServer', () => {
715 <span key={2} />
716 </Wrapper>,
717 );
718 - }).toThrowError(/Cannot assign to read only property.*/);
718 + }).toThrow(/Cannot assign to read only property.*/);
719 } else {
720 expect(
721 ReactDOMServer.renderToStaticMarkup(
@@ -966,7 +966,7 @@ describe('ReactDOMServer', () => {
966
967 expect(() => {
968 ReactDOMServer.renderToString(<Foo />);
969 - }).toThrow("Cannot read property 'world' of undefined");
969 + }).toThrow("Cannot read properties of undefined (reading 'world')");
970 });
971
972 it('should warn when class contextType is undefined', () => {
@@ -981,7 +981,7 @@ describe('ReactDOMServer', () => {
981
982 expect(() => {
983 ReactDOMServer.renderToString(<Foo />);
984 - }).toThrow("Cannot read property 'world' of undefined");
984 + }).toThrow("Cannot read properties of undefined (reading 'world')");
985 assertConsoleErrorDev([
986 'Foo defines an invalid contextType. ' +
987 'contextType should point to the Context object returned by React.createContext(). ' +
@@ -1007,7 +1007,7 @@ describe('ReactDOMServer', () => {
1007
1008 expect(() => {
1009 ReactDOMServer.renderToString(<Foo />);
1010 - }).toThrow("Cannot read property 'hello' of undefined");
1010 + }).toThrow("Cannot read properties of undefined (reading 'hello')");
1011 assertConsoleErrorDev([
1012 'Foo defines an invalid contextType. ' +
1013 'contextType should point to the Context object returned by React.createContext(). ' +
@@ -1026,7 +1026,7 @@ describe('ReactDOMServer', () => {
1026
1027 expect(() => {
1028 ReactDOMServer.renderToString(<Foo />);
1029 - }).toThrow("Cannot read property 'world' of undefined");
1029 + }).toThrow("Cannot read properties of undefined (reading 'world')");
1030 assertConsoleErrorDev([
1031 'Foo defines an invalid contextType. ' +
1032 'contextType should point to the Context object returned by React.createContext(). ' +
packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.js
+1 -1
@@ -234,7 +234,7 @@ describe('ReactHooksWithNoopRenderer', () => {
234
235 it('throws when called outside the render phase', async () => {
236 expect(() => useState(0)).toThrow(
237 - "Cannot read property 'useState' of null",
237 + "Cannot read properties of null (reading 'useState')",
238 );
239 assertConsoleErrorDev([
240 'Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for' +
scripts/jest/matchers/toThrow.js deleted
-50
@@ -1,50 +0,0 @@
1 -'use strict';
2 -
3 -// V8 uses a different message format when reading properties of null or undefined.
4 -// Older versions use e.g. "Cannot read property 'world' of undefined"
5 -// Newer versions use e.g. "Cannot read properties of undefined (reading 'world')"
6 -// This file overrides the built-in toThrow() matches to handle both cases,
7 -// enabling the React project to support Node 12-16 without forking tests.
8 -
9 -const toThrowMatchers = require('expect/build/toThrowMatchers').default;
10 -const builtInToThrow = toThrowMatchers.toThrow;
11 -
12 -// Detect the newer stack format:
13 -let newErrorFormat = false;
14 -try {
15 - null.test();
16 -} catch (error) {
17 - if (error.message.includes('Cannot read properties of null')) {
18 - newErrorFormat = true;
19 - }
20 -}
21 -
22 -// Detect the message pattern we need to rename:
23 -const regex = /Cannot read property '([^']+)' of (.+)/;
24 -
25 -// Massage strings (written in the older format) to match the newer format
26 -// if tests are currently running on Node 16+
27 -function normalizeErrorMessage(message) {
28 - if (newErrorFormat) {
29 - const match = message.match(regex);
30 - if (match) {
31 - return `Cannot read properties of ${match[2]} (reading '${match[1]}')`;
32 - }
33 - }
34 -
35 - return message;
36 -}
37 -
38 -function toThrow(value, expectedValue) {
39 - if (typeof expectedValue === 'string') {
40 - expectedValue = normalizeErrorMessage(expectedValue);
41 - } else if (expectedValue instanceof Error) {
42 - expectedValue.message = normalizeErrorMessage(expectedValue.message);
43 - }
44 -
45 - return builtInToThrow.call(this, value, expectedValue);
46 -}
47 -
48 -module.exports = {
49 - toThrow,
50 -};
scripts/jest/setupTests.js
-1
@@ -47,7 +47,6 @@ if (process.env.REACT_CLASS_EQUIVALENCE_TEST) {
47
48 expect.extend({
49 ...require('./matchers/reactTestMatchers'),
50 - ...require('./matchers/toThrow'),
50 });
51
52 // We have a Babel transform that inserts guards against infinite loops.
scripts/jest/spec-equivalence-reporter/setupTests.js
-1
@@ -60,5 +60,4 @@ afterEach(assertConsoleLogsCleared);
60
61 expect.extend({
62 ...require('../matchers/reactTestMatchers'),
63 - ...require('../matchers/toThrow'),
63 });