Skip to content

Commit c16b005

Browse files
author
Brian Vaughn
authored
Update test and stack frame code to support newer V8 stack formats (#22477)
1 parent 55d7500 commit c16b005

File tree

4 files changed

+63
-3
lines changed

4 files changed

+63
-3
lines changed

packages/shared/ReactComponentStackFrame.js

+9-1
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,15 @@ export function describeNativeComponentFrame(
168168
// The next one that isn't the same should be our match though.
169169
if (c < 0 || sampleLines[s] !== controlLines[c]) {
170170
// V8 adds a "new" prefix for native classes. Let's remove it to make it prettier.
171-
const frame = '\n' + sampleLines[s].replace(' at new ', ' at ');
171+
let frame = '\n' + sampleLines[s].replace(' at new ', ' at ');
172+
173+
// If our component frame is labeled "<anonymous>"
174+
// but we have a user-provided "displayName"
175+
// splice it in to make the stack more readable.
176+
if (fn.displayName && frame.includes('<anonymous>')) {
177+
frame = frame.replace('<anonymous>', fn.displayName);
178+
}
179+
172180
if (__DEV__) {
173181
if (typeof fn === 'function') {
174182
componentFrameCache.set(fn, frame);

scripts/jest/matchers/toThrow.js

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
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 witout 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

+2-1
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,9 @@ if (process.env.REACT_CLASS_EQUIVALENCE_TEST) {
4545
}
4646

4747
expect.extend({
48-
...require('./matchers/toWarnDev'),
4948
...require('./matchers/reactTestMatchers'),
49+
...require('./matchers/toThrow'),
50+
...require('./matchers/toWarnDev'),
5051
});
5152

5253
// We have a Babel transform that inserts guards against infinite loops.

scripts/jest/spec-equivalence-reporter/setupTests.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,9 @@ global.spyOnProd = function(...args) {
4646
};
4747

4848
expect.extend({
49-
...require('../matchers/toWarnDev'),
5049
...require('../matchers/reactTestMatchers'),
50+
...require('../matchers/toThrow'),
51+
...require('../matchers/toWarnDev'),
5152
});
5253

5354
beforeEach(() => (numExpectations = 0));

0 commit comments

Comments
 (0)