/
githubmr
/
facebook-react
Обзор
Документация
Войти
/
githubmr
/
facebook-react
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
main
packages/react-reconciler/src/__tests__/ReactIncrementalErrorLogging-test.js
288 строк
8 KB
Ricky
[flags] remove enableOwnerStacks (#32426)
04 мар 2025, 20:34
Не верифицирован
04 мар 2025, 20:34
e0fe347
Код
Авторство
О чём код?
/** * 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. * * @emails react-core * @jest-environment node */ 'use strict'; let React; let ReactNoop; let Scheduler; let waitForAll; let uncaughtExceptionMock; async function fakeAct(cb) { // We don't use act/waitForThrow here because we want to observe how errors are reported for real. await cb(); Scheduler.unstable_flushAll(); } describe('ReactIncrementalErrorLogging', () => { beforeEach(() => { jest.resetModules(); React = require('react'); ReactNoop = require('react-noop-renderer'); Scheduler = require('scheduler'); const InternalTestUtils = require('internal-test-utils'); waitForAll = InternalTestUtils.waitForAll; }); // Note: in this test file we won't be using assertConsoleDev() matchers // because they filter out precisely the messages we want to test for. let oldConsoleWarn; let oldConsoleError; beforeEach(() => { oldConsoleWarn = console.warn; oldConsoleError = console.error; console.warn = jest.fn(); console.error = jest.fn(); uncaughtExceptionMock = jest.fn(); process.on('uncaughtException', uncaughtExceptionMock); }); afterEach(() => { console.warn = oldConsoleWarn; console.error = oldConsoleError; process.off('uncaughtException', uncaughtExceptionMock); oldConsoleWarn = null; oldConsoleError = null; uncaughtExceptionMock = null; }); it('should log errors that occur during the begin phase', async () => { class ErrorThrowingComponent extends React.Component { constructor(props) { super(props); throw new Error('constructor error'); } render() { return <div />; } } await fakeAct(() => { ReactNoop.render( <div> <span> <ErrorThrowingComponent /> </span> </div>, ); }); expect(uncaughtExceptionMock).toHaveBeenCalledTimes(1); expect(uncaughtExceptionMock).toHaveBeenCalledWith( expect.objectContaining({ message: 'constructor error', }), ); if (__DEV__) { expect(console.warn).toHaveBeenCalledTimes(1); expect(console.warn).toHaveBeenCalledWith( expect.stringContaining('%s'), expect.stringContaining( 'An error occurred in the <ErrorThrowingComponent> component.', ), expect.stringContaining( 'Consider adding an error boundary to your tree ' + 'to customize error handling behavior.', ), // The component stack is not added without the polyfill/devtools. // expect.stringMatching( // new RegExp( // '\\s+(in|at) ErrorThrowingComponent' // ), // ), ); } }); it('should log errors that occur during the commit phase', async () => { class ErrorThrowingComponent extends React.Component { componentDidMount() { throw new Error('componentDidMount error'); } render() { return <div />; } } await fakeAct(() => { ReactNoop.render( <div> <span> <ErrorThrowingComponent /> </span> </div>, ); }); expect(uncaughtExceptionMock).toHaveBeenCalledTimes(1); expect(uncaughtExceptionMock).toHaveBeenCalledWith( expect.objectContaining({ message: 'componentDidMount error', }), ); if (__DEV__) { expect(console.warn).toHaveBeenCalledTimes(1); expect(console.warn).toHaveBeenCalledWith( expect.stringContaining('%s'), expect.stringContaining( 'An error occurred in the <ErrorThrowingComponent> component.', ), expect.stringContaining( 'Consider adding an error boundary to your tree ' + 'to customize error handling behavior.', ), // The component stack is not added without the polyfill/devtools. // expect.stringMatching( // new RegExp( // '\\s+(in|at) ErrorThrowingComponent' // ), // ), ); } }); it('should ignore errors thrown in log method to prevent cycle', async () => { const logCapturedErrorCalls = []; console.error.mockImplementation(error => { // Test what happens when logging itself is buggy. logCapturedErrorCalls.push(error); throw new Error('logCapturedError error'); }); class ErrorBoundary extends React.Component { state = {error: null}; componentDidCatch(error) { this.setState({error}); } render() { return this.state.error ? null : this.props.children; } } class ErrorThrowingComponent extends React.Component { render() { throw new Error('render error'); } } await fakeAct(() => { ReactNoop.render( <div> <ErrorBoundary> <span> <ErrorThrowingComponent /> </span> </ErrorBoundary> </div>, ); }); expect(logCapturedErrorCalls.length).toBe(1); if (__DEV__) { expect(console.error).toHaveBeenCalledWith( expect.stringContaining('%o'), expect.objectContaining({ message: 'render error', }), expect.stringContaining( 'The above error occurred in the <ErrorThrowingComponent> component.', ), expect.stringContaining( 'React will try to recreate this component tree from scratch ' + 'using the error boundary you provided, ErrorBoundary.', ), // The component stack is not added without the polyfill/devtools. // expect.stringMatching( // new RegExp( // '\\s+(in|at) ErrorThrowingComponent' // ), // ), ); } else { expect(logCapturedErrorCalls[0]).toEqual( expect.objectContaining({ message: 'render error', }), ); } // The error thrown in logCapturedError should be rethrown with a clean stack expect(() => { jest.runAllTimers(); }).toThrow('logCapturedError error'); }); it('resets instance variables before unmounting failed node', async () => { class ErrorBoundary extends React.Component { state = {error: null}; componentDidCatch(error) { this.setState({error}); } render() { return this.state.error ? null : this.props.children; } } class Foo extends React.Component { state = {step: 0}; componentDidMount() { this.setState({step: 1}); } componentWillUnmount() { Scheduler.log('componentWillUnmount: ' + this.state.step); } render() { Scheduler.log('render: ' + this.state.step); if (this.state.step > 0) { throw new Error('oops'); } return null; } } ReactNoop.render( <ErrorBoundary> <Foo /> </ErrorBoundary>, ); await waitForAll( [ 'render: 0', 'render: 1', // Retry one more time before handling error 'render: 1', 'componentWillUnmount: 0', ].filter(Boolean), ); expect(console.error).toHaveBeenCalledTimes(1); if (__DEV__) { expect(console.error).toHaveBeenCalledWith( expect.stringContaining('%o'), expect.objectContaining({ message: 'oops', }), expect.stringContaining( 'The above error occurred in the <Foo> component.', ), expect.stringContaining( 'React will try to recreate this component tree from scratch ' + 'using the error boundary you provided, ErrorBoundary.', ), // The component stack is not added without the polyfill/devtools. // expect.stringMatching( // new RegExp('\\s+(in|at) Foo') // ), ); } else { expect(console.error).toHaveBeenCalledWith( expect.objectContaining({ message: 'oops', }), ); } }); });