Skip to content

Commit

Permalink
Update test and stack frame code to support newer V8 stack formats (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
Brian Vaughn authored Oct 11, 2021
1 parent 55d7500 commit c16b005
Show file tree
Hide file tree
Showing 4 changed files with 63 additions and 3 deletions.
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
// 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

0 comments on commit c16b005

Please sign in to comment.