Files
react/packages/react-devtools-shared/src/__tests__/componentStacks-test.js
Sebastian Markbåge 8fb0233a84 Include server component names in the componentStack in DEV (#28415)
I'm a bit ambivalent about this one because it's not the main strategy
that I plan on pursuing. I plan on replacing most DEV-only specific
stacks like `console.error` stacks with a new take on owner stacks and
native stacks. The future owner stacks may or may not be exposed to
error boundaries in DEV but if they are they'd be a new errorInfo
property since they're owner based and not available in prod.

The use case in `console.error` mostly goes away in the future so this
PR is mainly for error boundaries. It doesn't hurt to have it in there
while I'm working on the better stacks though.

The `componentStack` property exposed to error boundaries is more like
production behavior similar to `new Error().stack` (which even in DEV
won't ever expose owner stacks because `console.createTask` doesn't
affect these). I'm not sure it's worth adding server components in DEV
(this PR) because then you have forked behavior between dev and prod.

However, since even in the future there won't be any other place to get
the *parent* stack, maybe this can be useful information even if it's
only dev. We could expose a third property on errorInfo that's DEV only
and parent stack but including server components. That doesn't seem
worth it over just having the stack differ in dev and prod.

I don't plan on adding line/column number to these particular stacks.

A follow up could be to add this to Fizz prerender too but only in DEV.
2024-02-23 12:04:55 -05:00

127 lines
3.5 KiB
JavaScript

/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import {getVersionedRenderImplementation, normalizeCodeLocInfo} from './utils';
describe('component stack', () => {
let React;
let act;
let mockError;
let mockWarn;
beforeEach(() => {
// Intercept native console methods before DevTools bootstraps.
// Normalize component stack locations.
mockError = jest.fn();
mockWarn = jest.fn();
console.error = (...args) => {
mockError(...args.map(normalizeCodeLocInfo));
};
console.warn = (...args) => {
mockWarn(...args.map(normalizeCodeLocInfo));
};
const utils = require('./utils');
act = utils.act;
React = require('react');
});
const {render} = getVersionedRenderImplementation();
// @reactVersion >=16.9
it('should log the current component stack along with an error or warning', () => {
const Grandparent = () => <Parent />;
const Parent = () => <Child />;
const Child = () => {
console.error('Test error.');
console.warn('Test warning.');
return null;
};
act(() => render(<Grandparent />));
expect(mockError).toHaveBeenCalledWith(
'Test error.',
'\n in Child (at **)' +
'\n in Parent (at **)' +
'\n in Grandparent (at **)',
);
expect(mockWarn).toHaveBeenCalledWith(
'Test warning.',
'\n in Child (at **)' +
'\n in Parent (at **)' +
'\n in Grandparent (at **)',
);
});
// This test should have caught #19911
// but didn't because both DevTools and ReactDOM are running in the same memory space,
// so the case we're testing against (DevTools prod build and React DEV build) doesn't exist.
// It would be nice to figure out a way to test this combination at some point...
xit('should disable the current dispatcher before shallow rendering so no effects get scheduled', () => {
let useEffectCount = 0;
const Example = props => {
React.useEffect(() => {
useEffectCount++;
expect(props).toBeDefined();
}, [props]);
console.warn('Warning to trigger appended component stacks.');
return null;
};
act(() => render(<Example test="abc" />));
expect(useEffectCount).toBe(1);
expect(mockWarn).toHaveBeenCalledWith(
'Warning to trigger appended component stacks.',
'\n in Example (at **)',
);
});
// @reactVersion >=18.3
it('should log the current component stack with debug info from promises', () => {
const Child = () => {
console.error('Test error.');
console.warn('Test warning.');
return null;
};
const ChildPromise = Promise.resolve(<Child />);
ChildPromise.status = 'fulfilled';
ChildPromise.value = <Child />;
ChildPromise._debugInfo = [
{
name: 'ServerComponent',
env: 'Server',
},
];
const Parent = () => ChildPromise;
const Grandparent = () => <Parent />;
act(() => render(<Grandparent />));
expect(mockError).toHaveBeenCalledWith(
'Test error.',
'\n in Child (at **)' +
'\n in ServerComponent (at **)' +
'\n in Parent (at **)' +
'\n in Grandparent (at **)',
);
expect(mockWarn).toHaveBeenCalledWith(
'Test warning.',
'\n in Child (at **)' +
'\n in ServerComponent (at **)' +
'\n in Parent (at **)' +
'\n in Grandparent (at **)',
);
});
});