Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update test and stack frame code to support newer V8 stack formats #22477

Merged
merged 2 commits into from
Oct 11, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion packages/shared/ReactComponentStackFrame.js
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,15 @@ export function describeNativeComponentFrame(
// The next one that isn't the same should be our match though.
if (c < 0 || sampleLines[s] !== controlLines[c]) {
// V8 adds a "new" prefix for native classes. Let's remove it to make it prettier.
const frame = '\n' + sampleLines[s].replace(' at new ', ' at ');
let frame = '\n' + sampleLines[s].replace(' at new ', ' at ');

// If our component frame is labeled "<anonymous>"
// but we have a user-provided "displayName"
// splice it in to make the stack more readable.
if (fn.displayName && frame.includes('<anonymous>')) {
frame = frame.replace('<anonymous>', fn.displayName);
}

if (__DEV__) {
if (typeof fn === 'function') {
componentFrameCache.set(fn, frame);
Expand Down
50 changes: 50 additions & 0 deletions scripts/jest/matchers/toThrow.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
'use strict';

// V8 uses a different message format when reading properties of null or undefined.
// Older versions use e.g. "Cannot read property 'world' of undefined"
// Newer versions use e.g. "Cannot read properties of undefined (reading 'world')"
// This file overrides the built-in toThrow() matches to handle both cases,
// enabling the React project to support Node 12-16 witout forking tests.

const toThrowMatchers = require('expect/build/toThrowMatchers').default;
const builtInToThrow = toThrowMatchers.toThrow;

// Detect the newer stack format:
let newErrorFormat = false;
try {
null.test();
} catch (error) {
if (error.message.includes('Cannot read properties of null')) {
newErrorFormat = true;
}
}

// Detect the message pattern we need to rename:
const regex = /Cannot read property '([^']+)' of (.+)/;

// Massage strings (written in the older format) to match the newer format
bvaughn marked this conversation as resolved.
Show resolved Hide resolved
// if tests are currently running on Node 16+
function normalizeErrorMessage(message) {
if (newErrorFormat) {
const match = message.match(regex);
if (match) {
return `Cannot read properties of ${match[2]} (reading '${match[1]}')`;
}
}

return message;
}

function toThrow(value, expectedValue) {
if (typeof expectedValue === 'string') {
expectedValue = normalizeErrorMessage(expectedValue);
} else if (expectedValue instanceof Error) {
expectedValue.message = normalizeErrorMessage(expectedValue.message);
}

return builtInToThrow.call(this, value, expectedValue);
}

module.exports = {
toThrow,
};
3 changes: 2 additions & 1 deletion scripts/jest/setupTests.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,9 @@ if (process.env.REACT_CLASS_EQUIVALENCE_TEST) {
}

expect.extend({
...require('./matchers/toWarnDev'),
...require('./matchers/reactTestMatchers'),
...require('./matchers/toThrow'),
...require('./matchers/toWarnDev'),
});

// We have a Babel transform that inserts guards against infinite loops.
Expand Down
3 changes: 2 additions & 1 deletion scripts/jest/spec-equivalence-reporter/setupTests.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,9 @@ global.spyOnProd = function(...args) {
};

expect.extend({
...require('../matchers/toWarnDev'),
...require('../matchers/reactTestMatchers'),
...require('../matchers/toThrow'),
...require('../matchers/toWarnDev'),
});

beforeEach(() => (numExpectations = 0));
Expand Down