@samitouri / QOS-React-2 / commits / c16b005f2d

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

Brian Vaughn committed Oct 11, 2021 at 18:40 UTC c16b005f2d71acde26658f035ff491a5e027da39
4 files changed +63 -3
packages/shared/ReactComponentStackFrame.js
+9 -1
@@ -168,7 +168,15 @@ export function describeNativeComponentFrame(
168 // The next one that isn't the same should be our match though.
169 if (c < 0 || sampleLines[s] !== controlLines[c]) {
170 // 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 +
180 if (__DEV__) {
181 if (typeof fn === 'function') {
182 componentFrameCache.set(fn, frame);
scripts/jest/matchers/toThrow.js new
+50
@@ -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
@@ -45,8 +45,9 @@ if (process.env.REACT_CLASS_EQUIVALENCE_TEST) {
45 }
46
47 expect.extend({
48 - ...require('./matchers/toWarnDev'),
48 ...require('./matchers/reactTestMatchers'),
49 + ...require('./matchers/toThrow'),
50 + ...require('./matchers/toWarnDev'),
51 });
52
53 // We have a Babel transform that inserts guards against infinite loops.
scripts/jest/spec-equivalence-reporter/setupTests.js
+2 -1
@@ -46,8 +46,9 @@ global.spyOnProd = function(...args) {
46 };
47
48 expect.extend({
49 - ...require('../matchers/toWarnDev'),
49 ...require('../matchers/reactTestMatchers'),
50 + ...require('../matchers/toThrow'),
51 + ...require('../matchers/toWarnDev'),
52 });
53
54 beforeEach(() => (numExpectations = 0));