Best JavaScript code snippet using testing-library-react-hooks
ReactLegacyErrorBoundaries-test.internal.js
Source:ReactLegacyErrorBoundaries-test.internal.js  
1/**2 * Copyright (c) Facebook, Inc. and its affiliates.3 *4 * This source code is licensed under the MIT license found in the5 * LICENSE file in the root directory of this source tree.6 *7 * @emails react-core8 */9'use strict';10let PropTypes;11let React;12let ReactDOM;13let ReactFeatureFlags;14// TODO: Refactor this test once componentDidCatch setState is deprecated.15describe('ReactLegacyErrorBoundaries', () => {16  let log;17  let BrokenConstructor;18  let BrokenComponentWillMount;19  let BrokenComponentDidMount;20  let BrokenComponentWillReceiveProps;21  let BrokenComponentWillUpdate;22  let BrokenComponentDidUpdate;23  let BrokenComponentWillUnmount;24  let BrokenRenderErrorBoundary;25  let BrokenComponentWillMountErrorBoundary;26  let BrokenComponentDidMountErrorBoundary;27  let BrokenRender;28  let ErrorBoundary;29  let BothErrorBoundaries;30  let ErrorMessage;31  let NoopErrorBoundary;32  let RetryErrorBoundary;33  let Normal;34  beforeEach(() => {35    jest.resetModules();36    PropTypes = require('prop-types');37    ReactFeatureFlags = require('shared/ReactFeatureFlags');38    ReactFeatureFlags.replayFailedUnitOfWorkWithInvokeGuardedCallback = false;39    ReactDOM = require('react-dom');40    React = require('react');41    log = [];42    BrokenConstructor = class extends React.Component {43      constructor(props) {44        super(props);45        log.push('BrokenConstructor constructor [!]');46        throw new Error('Hello');47      }48      render() {49        log.push('BrokenConstructor render');50        return <div>{this.props.children}</div>;51      }52      UNSAFE_componentWillMount() {53        log.push('BrokenConstructor componentWillMount');54      }55      componentDidMount() {56        log.push('BrokenConstructor componentDidMount');57      }58      UNSAFE_componentWillReceiveProps() {59        log.push('BrokenConstructor componentWillReceiveProps');60      }61      UNSAFE_componentWillUpdate() {62        log.push('BrokenConstructor componentWillUpdate');63      }64      componentDidUpdate() {65        log.push('BrokenConstructor componentDidUpdate');66      }67      componentWillUnmount() {68        log.push('BrokenConstructor componentWillUnmount');69      }70    };71    BrokenComponentWillMount = class extends React.Component {72      constructor(props) {73        super(props);74        log.push('BrokenComponentWillMount constructor');75      }76      render() {77        log.push('BrokenComponentWillMount render');78        return <div>{this.props.children}</div>;79      }80      UNSAFE_componentWillMount() {81        log.push('BrokenComponentWillMount componentWillMount [!]');82        throw new Error('Hello');83      }84      componentDidMount() {85        log.push('BrokenComponentWillMount componentDidMount');86      }87      UNSAFE_componentWillReceiveProps() {88        log.push('BrokenComponentWillMount componentWillReceiveProps');89      }90      UNSAFE_componentWillUpdate() {91        log.push('BrokenComponentWillMount componentWillUpdate');92      }93      componentDidUpdate() {94        log.push('BrokenComponentWillMount componentDidUpdate');95      }96      componentWillUnmount() {97        log.push('BrokenComponentWillMount componentWillUnmount');98      }99    };100    BrokenComponentDidMount = class extends React.Component {101      constructor(props) {102        super(props);103        log.push('BrokenComponentDidMount constructor');104      }105      render() {106        log.push('BrokenComponentDidMount render');107        return <div>{this.props.children}</div>;108      }109      UNSAFE_componentWillMount() {110        log.push('BrokenComponentDidMount componentWillMount');111      }112      componentDidMount() {113        log.push('BrokenComponentDidMount componentDidMount [!]');114        throw new Error('Hello');115      }116      UNSAFE_componentWillReceiveProps() {117        log.push('BrokenComponentDidMount componentWillReceiveProps');118      }119      UNSAFE_componentWillUpdate() {120        log.push('BrokenComponentDidMount componentWillUpdate');121      }122      componentDidUpdate() {123        log.push('BrokenComponentDidMount componentDidUpdate');124      }125      componentWillUnmount() {126        log.push('BrokenComponentDidMount componentWillUnmount');127      }128    };129    BrokenComponentWillReceiveProps = class extends React.Component {130      constructor(props) {131        super(props);132        log.push('BrokenComponentWillReceiveProps constructor');133      }134      render() {135        log.push('BrokenComponentWillReceiveProps render');136        return <div>{this.props.children}</div>;137      }138      UNSAFE_componentWillMount() {139        log.push('BrokenComponentWillReceiveProps componentWillMount');140      }141      componentDidMount() {142        log.push('BrokenComponentWillReceiveProps componentDidMount');143      }144      UNSAFE_componentWillReceiveProps() {145        log.push(146          'BrokenComponentWillReceiveProps componentWillReceiveProps [!]',147        );148        throw new Error('Hello');149      }150      UNSAFE_componentWillUpdate() {151        log.push('BrokenComponentWillReceiveProps componentWillUpdate');152      }153      componentDidUpdate() {154        log.push('BrokenComponentWillReceiveProps componentDidUpdate');155      }156      componentWillUnmount() {157        log.push('BrokenComponentWillReceiveProps componentWillUnmount');158      }159    };160    BrokenComponentWillUpdate = class extends React.Component {161      constructor(props) {162        super(props);163        log.push('BrokenComponentWillUpdate constructor');164      }165      render() {166        log.push('BrokenComponentWillUpdate render');167        return <div>{this.props.children}</div>;168      }169      UNSAFE_componentWillMount() {170        log.push('BrokenComponentWillUpdate componentWillMount');171      }172      componentDidMount() {173        log.push('BrokenComponentWillUpdate componentDidMount');174      }175      UNSAFE_componentWillReceiveProps() {176        log.push('BrokenComponentWillUpdate componentWillReceiveProps');177      }178      UNSAFE_componentWillUpdate() {179        log.push('BrokenComponentWillUpdate componentWillUpdate [!]');180        throw new Error('Hello');181      }182      componentDidUpdate() {183        log.push('BrokenComponentWillUpdate componentDidUpdate');184      }185      componentWillUnmount() {186        log.push('BrokenComponentWillUpdate componentWillUnmount');187      }188    };189    BrokenComponentDidUpdate = class extends React.Component {190      static defaultProps = {191        errorText: 'Hello',192      };193      constructor(props) {194        super(props);195        log.push('BrokenComponentDidUpdate constructor');196      }197      render() {198        log.push('BrokenComponentDidUpdate render');199        return <div>{this.props.children}</div>;200      }201      UNSAFE_componentWillMount() {202        log.push('BrokenComponentDidUpdate componentWillMount');203      }204      componentDidMount() {205        log.push('BrokenComponentDidUpdate componentDidMount');206      }207      UNSAFE_componentWillReceiveProps() {208        log.push('BrokenComponentDidUpdate componentWillReceiveProps');209      }210      UNSAFE_componentWillUpdate() {211        log.push('BrokenComponentDidUpdate componentWillUpdate');212      }213      componentDidUpdate() {214        log.push('BrokenComponentDidUpdate componentDidUpdate [!]');215        throw new Error(this.props.errorText);216      }217      componentWillUnmount() {218        log.push('BrokenComponentDidUpdate componentWillUnmount');219      }220    };221    BrokenComponentWillUnmount = class extends React.Component {222      static defaultProps = {223        errorText: 'Hello',224      };225      constructor(props) {226        super(props);227        log.push('BrokenComponentWillUnmount constructor');228      }229      render() {230        log.push('BrokenComponentWillUnmount render');231        return <div>{this.props.children}</div>;232      }233      UNSAFE_componentWillMount() {234        log.push('BrokenComponentWillUnmount componentWillMount');235      }236      componentDidMount() {237        log.push('BrokenComponentWillUnmount componentDidMount');238      }239      UNSAFE_componentWillReceiveProps() {240        log.push('BrokenComponentWillUnmount componentWillReceiveProps');241      }242      UNSAFE_componentWillUpdate() {243        log.push('BrokenComponentWillUnmount componentWillUpdate');244      }245      componentDidUpdate() {246        log.push('BrokenComponentWillUnmount componentDidUpdate');247      }248      componentWillUnmount() {249        log.push('BrokenComponentWillUnmount componentWillUnmount [!]');250        throw new Error(this.props.errorText);251      }252    };253    BrokenComponentWillMountErrorBoundary = class extends React.Component {254      constructor(props) {255        super(props);256        this.state = {error: null};257        log.push('BrokenComponentWillMountErrorBoundary constructor');258      }259      render() {260        if (this.state.error) {261          log.push('BrokenComponentWillMountErrorBoundary render error');262          return <div>Caught an error: {this.state.error.message}.</div>;263        }264        log.push('BrokenComponentWillMountErrorBoundary render success');265        return <div>{this.props.children}</div>;266      }267      UNSAFE_componentWillMount() {268        log.push(269          'BrokenComponentWillMountErrorBoundary componentWillMount [!]',270        );271        throw new Error('Hello');272      }273      componentDidMount() {274        log.push('BrokenComponentWillMountErrorBoundary componentDidMount');275      }276      componentWillUnmount() {277        log.push('BrokenComponentWillMountErrorBoundary componentWillUnmount');278      }279      componentDidCatch(error) {280        log.push('BrokenComponentWillMountErrorBoundary componentDidCatch');281        this.setState({error});282      }283    };284    BrokenComponentDidMountErrorBoundary = class extends React.Component {285      constructor(props) {286        super(props);287        this.state = {error: null};288        log.push('BrokenComponentDidMountErrorBoundary constructor');289      }290      render() {291        if (this.state.error) {292          log.push('BrokenComponentDidMountErrorBoundary render error');293          return <div>Caught an error: {this.state.error.message}.</div>;294        }295        log.push('BrokenComponentDidMountErrorBoundary render success');296        return <div>{this.props.children}</div>;297      }298      UNSAFE_componentWillMount() {299        log.push('BrokenComponentDidMountErrorBoundary componentWillMount');300      }301      componentDidMount() {302        log.push('BrokenComponentDidMountErrorBoundary componentDidMount [!]');303        throw new Error('Hello');304      }305      componentWillUnmount() {306        log.push('BrokenComponentDidMountErrorBoundary componentWillUnmount');307      }308      componentDidCatch(error) {309        log.push('BrokenComponentDidMountErrorBoundary componentDidCatch');310        this.setState({error});311      }312    };313    BrokenRenderErrorBoundary = class extends React.Component {314      constructor(props) {315        super(props);316        this.state = {error: null};317        log.push('BrokenRenderErrorBoundary constructor');318      }319      render() {320        if (this.state.error) {321          log.push('BrokenRenderErrorBoundary render error [!]');322          throw new Error('Hello');323        }324        log.push('BrokenRenderErrorBoundary render success');325        return <div>{this.props.children}</div>;326      }327      UNSAFE_componentWillMount() {328        log.push('BrokenRenderErrorBoundary componentWillMount');329      }330      componentDidMount() {331        log.push('BrokenRenderErrorBoundary componentDidMount');332      }333      componentWillUnmount() {334        log.push('BrokenRenderErrorBoundary componentWillUnmount');335      }336      componentDidCatch(error) {337        log.push('BrokenRenderErrorBoundary componentDidCatch');338        this.setState({error});339      }340    };341    BrokenRender = class extends React.Component {342      constructor(props) {343        super(props);344        log.push('BrokenRender constructor');345      }346      render() {347        log.push('BrokenRender render [!]');348        throw new Error('Hello');349      }350      UNSAFE_componentWillMount() {351        log.push('BrokenRender componentWillMount');352      }353      componentDidMount() {354        log.push('BrokenRender componentDidMount');355      }356      UNSAFE_componentWillReceiveProps() {357        log.push('BrokenRender componentWillReceiveProps');358      }359      UNSAFE_componentWillUpdate() {360        log.push('BrokenRender componentWillUpdate');361      }362      componentDidUpdate() {363        log.push('BrokenRender componentDidUpdate');364      }365      componentWillUnmount() {366        log.push('BrokenRender componentWillUnmount');367      }368    };369    NoopErrorBoundary = class extends React.Component {370      constructor(props) {371        super(props);372        log.push('NoopErrorBoundary constructor');373      }374      render() {375        log.push('NoopErrorBoundary render');376        return <BrokenRender />;377      }378      UNSAFE_componentWillMount() {379        log.push('NoopErrorBoundary componentWillMount');380      }381      componentDidMount() {382        log.push('NoopErrorBoundary componentDidMount');383      }384      componentWillUnmount() {385        log.push('NoopErrorBoundary componentWillUnmount');386      }387      componentDidCatch() {388        log.push('NoopErrorBoundary componentDidCatch');389      }390    };391    Normal = class extends React.Component {392      static defaultProps = {393        logName: 'Normal',394      };395      constructor(props) {396        super(props);397        log.push(`${this.props.logName} constructor`);398      }399      render() {400        log.push(`${this.props.logName} render`);401        return <div>{this.props.children}</div>;402      }403      UNSAFE_componentWillMount() {404        log.push(`${this.props.logName} componentWillMount`);405      }406      componentDidMount() {407        log.push(`${this.props.logName} componentDidMount`);408      }409      UNSAFE_componentWillReceiveProps() {410        log.push(`${this.props.logName} componentWillReceiveProps`);411      }412      UNSAFE_componentWillUpdate() {413        log.push(`${this.props.logName} componentWillUpdate`);414      }415      componentDidUpdate() {416        log.push(`${this.props.logName} componentDidUpdate`);417      }418      componentWillUnmount() {419        log.push(`${this.props.logName} componentWillUnmount`);420      }421    };422    ErrorBoundary = class extends React.Component {423      constructor(props) {424        super(props);425        this.state = {error: null};426        log.push(`${this.props.logName} constructor`);427      }428      render() {429        if (this.state.error && !this.props.forceRetry) {430          log.push(`${this.props.logName} render error`);431          return this.props.renderError(this.state.error, this.props);432        }433        log.push(`${this.props.logName} render success`);434        return <div>{this.props.children}</div>;435      }436      componentDidCatch(error) {437        log.push(`${this.props.logName} componentDidCatch`);438        this.setState({error});439      }440      UNSAFE_componentWillMount() {441        log.push(`${this.props.logName} componentWillMount`);442      }443      componentDidMount() {444        log.push(`${this.props.logName} componentDidMount`);445      }446      UNSAFE_componentWillReceiveProps() {447        log.push(`${this.props.logName} componentWillReceiveProps`);448      }449      UNSAFE_componentWillUpdate() {450        log.push(`${this.props.logName} componentWillUpdate`);451      }452      componentDidUpdate() {453        log.push(`${this.props.logName} componentDidUpdate`);454      }455      componentWillUnmount() {456        log.push(`${this.props.logName} componentWillUnmount`);457      }458    };459    ErrorBoundary.defaultProps = {460      logName: 'ErrorBoundary',461      renderError(error, props) {462        return (463          <div ref={props.errorMessageRef}>464            Caught an error: {error.message}.465          </div>466        );467      },468    };469    BothErrorBoundaries = class extends React.Component {470      constructor(props) {471        super(props);472        this.state = {error: null};473        log.push('BothErrorBoundaries constructor');474      }475      static getDerivedStateFromError(error) {476        log.push('BothErrorBoundaries static getDerivedStateFromError');477        return {error};478      }479      render() {480        if (this.state.error) {481          log.push('BothErrorBoundaries render error');482          return <div>Caught an error: {this.state.error.message}.</div>;483        }484        log.push('BothErrorBoundaries render success');485        return <div>{this.props.children}</div>;486      }487      componentDidCatch(error) {488        log.push('BothErrorBoundaries componentDidCatch');489        this.setState({error});490      }491      UNSAFE_componentWillMount() {492        log.push('BothErrorBoundaries componentWillMount');493      }494      componentDidMount() {495        log.push('BothErrorBoundaries componentDidMount');496      }497      UNSAFE_componentWillReceiveProps() {498        log.push('BothErrorBoundaries componentWillReceiveProps');499      }500      UNSAFE_componentWillUpdate() {501        log.push('BothErrorBoundaries componentWillUpdate');502      }503      componentDidUpdate() {504        log.push('BothErrorBoundaries componentDidUpdate');505      }506      componentWillUnmount() {507        log.push('BothErrorBoundaries componentWillUnmount');508      }509    };510    RetryErrorBoundary = class extends React.Component {511      constructor(props) {512        super(props);513        log.push('RetryErrorBoundary constructor');514      }515      render() {516        log.push('RetryErrorBoundary render');517        return <BrokenRender />;518      }519      UNSAFE_componentWillMount() {520        log.push('RetryErrorBoundary componentWillMount');521      }522      componentDidMount() {523        log.push('RetryErrorBoundary componentDidMount');524      }525      componentWillUnmount() {526        log.push('RetryErrorBoundary componentWillUnmount');527      }528      componentDidCatch(error) {529        log.push('RetryErrorBoundary componentDidCatch [!]');530        // In Fiber, calling setState() (and failing) is treated as a rethrow.531        this.setState({});532      }533    };534    ErrorMessage = class extends React.Component {535      constructor(props) {536        super(props);537        log.push('ErrorMessage constructor');538      }539      UNSAFE_componentWillMount() {540        log.push('ErrorMessage componentWillMount');541      }542      componentDidMount() {543        log.push('ErrorMessage componentDidMount');544      }545      componentWillUnmount() {546        log.push('ErrorMessage componentWillUnmount');547      }548      render() {549        log.push('ErrorMessage render');550        return <div>Caught an error: {this.props.message}.</div>;551      }552    };553  });554  it('does not swallow exceptions on mounting without boundaries', () => {555    let container = document.createElement('div');556    expect(() => {557      ReactDOM.render(<BrokenRender />, container);558    }).toThrow('Hello');559    container = document.createElement('div');560    expect(() => {561      ReactDOM.render(<BrokenComponentWillMount />, container);562    }).toThrow('Hello');563    container = document.createElement('div');564    expect(() => {565      ReactDOM.render(<BrokenComponentDidMount />, container);566    }).toThrow('Hello');567  });568  it('does not swallow exceptions on updating without boundaries', () => {569    let container = document.createElement('div');570    ReactDOM.render(<BrokenComponentWillUpdate />, container);571    expect(() => {572      ReactDOM.render(<BrokenComponentWillUpdate />, container);573    }).toThrow('Hello');574    container = document.createElement('div');575    ReactDOM.render(<BrokenComponentWillReceiveProps />, container);576    expect(() => {577      ReactDOM.render(<BrokenComponentWillReceiveProps />, container);578    }).toThrow('Hello');579    container = document.createElement('div');580    ReactDOM.render(<BrokenComponentDidUpdate />, container);581    expect(() => {582      ReactDOM.render(<BrokenComponentDidUpdate />, container);583    }).toThrow('Hello');584  });585  it('does not swallow exceptions on unmounting without boundaries', () => {586    const container = document.createElement('div');587    ReactDOM.render(<BrokenComponentWillUnmount />, container);588    expect(() => {589      ReactDOM.unmountComponentAtNode(container);590    }).toThrow('Hello');591  });592  it('prevents errors from leaking into other roots', () => {593    const container1 = document.createElement('div');594    const container2 = document.createElement('div');595    const container3 = document.createElement('div');596    ReactDOM.render(<span>Before 1</span>, container1);597    expect(() => {598      ReactDOM.render(<BrokenRender />, container2);599    }).toThrow('Hello');600    ReactDOM.render(601      <ErrorBoundary>602        <BrokenRender />603      </ErrorBoundary>,604      container3,605    );606    expect(container1.firstChild.textContent).toBe('Before 1');607    expect(container2.firstChild).toBe(null);608    expect(container3.firstChild.textContent).toBe('Caught an error: Hello.');609    ReactDOM.render(<span>After 1</span>, container1);610    ReactDOM.render(<span>After 2</span>, container2);611    ReactDOM.render(612      <ErrorBoundary forceRetry={true}>After 3</ErrorBoundary>,613      container3,614    );615    expect(container1.firstChild.textContent).toBe('After 1');616    expect(container2.firstChild.textContent).toBe('After 2');617    expect(container3.firstChild.textContent).toBe('After 3');618    ReactDOM.unmountComponentAtNode(container1);619    ReactDOM.unmountComponentAtNode(container2);620    ReactDOM.unmountComponentAtNode(container3);621    expect(container1.firstChild).toBe(null);622    expect(container2.firstChild).toBe(null);623    expect(container3.firstChild).toBe(null);624  });625  it('logs a single error using both error boundaries', () => {626    const container = document.createElement('div');627    expect(() =>628      ReactDOM.render(629        <BothErrorBoundaries>630          <BrokenRender />631        </BothErrorBoundaries>,632        container,633      ),634    ).toErrorDev('The above error occurred in the <BrokenRender> component', {635      logAllErrors: true,636    });637    expect(container.firstChild.textContent).toBe('Caught an error: Hello.');638    expect(log).toEqual([639      'BothErrorBoundaries constructor',640      'BothErrorBoundaries componentWillMount',641      'BothErrorBoundaries render success',642      'BrokenRender constructor',643      'BrokenRender componentWillMount',644      'BrokenRender render [!]',645      // Both getDerivedStateFromError and componentDidCatch should be called646      'BothErrorBoundaries static getDerivedStateFromError',647      'BothErrorBoundaries componentWillMount',648      'BothErrorBoundaries render error',649      // Fiber mounts with null children before capturing error650      'BothErrorBoundaries componentDidMount',651      // Catch and render an error message652      'BothErrorBoundaries componentDidCatch',653      'BothErrorBoundaries componentWillUpdate',654      'BothErrorBoundaries render error',655      'BothErrorBoundaries componentDidUpdate',656    ]);657    log.length = 0;658    ReactDOM.unmountComponentAtNode(container);659    expect(log).toEqual(['BothErrorBoundaries componentWillUnmount']);660  });661  it('renders an error state if child throws in render', () => {662    const container = document.createElement('div');663    ReactDOM.render(664      <ErrorBoundary>665        <BrokenRender />666      </ErrorBoundary>,667      container,668    );669    expect(container.firstChild.textContent).toBe('Caught an error: Hello.');670    expect(log).toEqual([671      'ErrorBoundary constructor',672      'ErrorBoundary componentWillMount',673      'ErrorBoundary render success',674      'BrokenRender constructor',675      'BrokenRender componentWillMount',676      'BrokenRender render [!]',677      // Fiber mounts with null children before capturing error678      'ErrorBoundary componentDidMount',679      // Catch and render an error message680      'ErrorBoundary componentDidCatch',681      'ErrorBoundary componentWillUpdate',682      'ErrorBoundary render error',683      'ErrorBoundary componentDidUpdate',684    ]);685    log.length = 0;686    ReactDOM.unmountComponentAtNode(container);687    expect(log).toEqual(['ErrorBoundary componentWillUnmount']);688  });689  it('renders an error state if child throws in constructor', () => {690    const container = document.createElement('div');691    ReactDOM.render(692      <ErrorBoundary>693        <BrokenConstructor />694      </ErrorBoundary>,695      container,696    );697    expect(container.firstChild.textContent).toBe('Caught an error: Hello.');698    expect(log).toEqual([699      'ErrorBoundary constructor',700      'ErrorBoundary componentWillMount',701      'ErrorBoundary render success',702      'BrokenConstructor constructor [!]',703      // Fiber mounts with null children before capturing error704      'ErrorBoundary componentDidMount',705      // Catch and render an error message706      'ErrorBoundary componentDidCatch',707      'ErrorBoundary componentWillUpdate',708      'ErrorBoundary render error',709      'ErrorBoundary componentDidUpdate',710    ]);711    log.length = 0;712    ReactDOM.unmountComponentAtNode(container);713    expect(log).toEqual(['ErrorBoundary componentWillUnmount']);714  });715  it('renders an error state if child throws in componentWillMount', () => {716    const container = document.createElement('div');717    ReactDOM.render(718      <ErrorBoundary>719        <BrokenComponentWillMount />720      </ErrorBoundary>,721      container,722    );723    expect(container.firstChild.textContent).toBe('Caught an error: Hello.');724    expect(log).toEqual([725      'ErrorBoundary constructor',726      'ErrorBoundary componentWillMount',727      'ErrorBoundary render success',728      'BrokenComponentWillMount constructor',729      'BrokenComponentWillMount componentWillMount [!]',730      'ErrorBoundary componentDidMount',731      'ErrorBoundary componentDidCatch',732      'ErrorBoundary componentWillUpdate',733      'ErrorBoundary render error',734      'ErrorBoundary componentDidUpdate',735    ]);736    log.length = 0;737    ReactDOM.unmountComponentAtNode(container);738    expect(log).toEqual(['ErrorBoundary componentWillUnmount']);739  });740  it('renders an error state if context provider throws in componentWillMount', () => {741    class BrokenComponentWillMountWithContext extends React.Component {742      static childContextTypes = {foo: PropTypes.number};743      getChildContext() {744        return {foo: 42};745      }746      render() {747        return <div>{this.props.children}</div>;748      }749      UNSAFE_componentWillMount() {750        throw new Error('Hello');751      }752    }753    const container = document.createElement('div');754    ReactDOM.render(755      <ErrorBoundary>756        <BrokenComponentWillMountWithContext />757      </ErrorBoundary>,758      container,759    );760    expect(container.firstChild.textContent).toBe('Caught an error: Hello.');761  });762  if (!require('shared/ReactFeatureFlags').disableModulePatternComponents) {763    it('renders an error state if module-style context provider throws in componentWillMount', () => {764      function BrokenComponentWillMountWithContext() {765        return {766          getChildContext() {767            return {foo: 42};768          },769          render() {770            return <div>{this.props.children}</div>;771          },772          UNSAFE_componentWillMount() {773            throw new Error('Hello');774          },775        };776      }777      BrokenComponentWillMountWithContext.childContextTypes = {778        foo: PropTypes.number,779      };780      const container = document.createElement('div');781      expect(() =>782        ReactDOM.render(783          <ErrorBoundary>784            <BrokenComponentWillMountWithContext />785          </ErrorBoundary>,786          container,787        ),788      ).toErrorDev(789        'Warning: The <BrokenComponentWillMountWithContext /> component appears to be a function component that ' +790          'returns a class instance. ' +791          'Change BrokenComponentWillMountWithContext to a class that extends React.Component instead. ' +792          "If you can't use a class try assigning the prototype on the function as a workaround. " +793          '`BrokenComponentWillMountWithContext.prototype = React.Component.prototype`. ' +794          "Don't use an arrow function since it cannot be called with `new` by React.",795      );796      expect(container.firstChild.textContent).toBe('Caught an error: Hello.');797    });798  }799  it('mounts the error message if mounting fails', () => {800    function renderError(error) {801      return <ErrorMessage message={error.message} />;802    }803    const container = document.createElement('div');804    ReactDOM.render(805      <ErrorBoundary renderError={renderError}>806        <BrokenRender />807      </ErrorBoundary>,808      container,809    );810    expect(log).toEqual([811      'ErrorBoundary constructor',812      'ErrorBoundary componentWillMount',813      'ErrorBoundary render success',814      'BrokenRender constructor',815      'BrokenRender componentWillMount',816      'BrokenRender render [!]',817      'ErrorBoundary componentDidMount',818      'ErrorBoundary componentDidCatch',819      'ErrorBoundary componentWillUpdate',820      'ErrorBoundary render error',821      'ErrorMessage constructor',822      'ErrorMessage componentWillMount',823      'ErrorMessage render',824      'ErrorMessage componentDidMount',825      'ErrorBoundary componentDidUpdate',826    ]);827    log.length = 0;828    ReactDOM.unmountComponentAtNode(container);829    expect(log).toEqual([830      'ErrorBoundary componentWillUnmount',831      'ErrorMessage componentWillUnmount',832    ]);833  });834  it('propagates errors on retry on mounting', () => {835    const container = document.createElement('div');836    ReactDOM.render(837      <ErrorBoundary>838        <RetryErrorBoundary>839          <BrokenRender />840        </RetryErrorBoundary>841      </ErrorBoundary>,842      container,843    );844    expect(container.firstChild.textContent).toBe('Caught an error: Hello.');845    expect(log).toEqual([846      'ErrorBoundary constructor',847      'ErrorBoundary componentWillMount',848      'ErrorBoundary render success',849      'RetryErrorBoundary constructor',850      'RetryErrorBoundary componentWillMount',851      'RetryErrorBoundary render',852      'BrokenRender constructor',853      'BrokenRender componentWillMount',854      'BrokenRender render [!]',855      // In Fiber, failed error boundaries render null before attempting to recover856      'RetryErrorBoundary componentDidMount',857      'RetryErrorBoundary componentDidCatch [!]',858      'ErrorBoundary componentDidMount',859      // Retry860      'RetryErrorBoundary render',861      'BrokenRender constructor',862      'BrokenRender componentWillMount',863      'BrokenRender render [!]',864      // This time, the error propagates to the higher boundary865      'RetryErrorBoundary componentWillUnmount',866      'ErrorBoundary componentDidCatch',867      // Render the error868      'ErrorBoundary componentWillUpdate',869      'ErrorBoundary render error',870      'ErrorBoundary componentDidUpdate',871    ]);872    log.length = 0;873    ReactDOM.unmountComponentAtNode(container);874    expect(log).toEqual(['ErrorBoundary componentWillUnmount']);875  });876  it('propagates errors inside boundary during componentWillMount', () => {877    const container = document.createElement('div');878    ReactDOM.render(879      <ErrorBoundary>880        <BrokenComponentWillMountErrorBoundary />881      </ErrorBoundary>,882      container,883    );884    expect(container.firstChild.textContent).toBe('Caught an error: Hello.');885    expect(log).toEqual([886      'ErrorBoundary constructor',887      'ErrorBoundary componentWillMount',888      'ErrorBoundary render success',889      'BrokenComponentWillMountErrorBoundary constructor',890      'BrokenComponentWillMountErrorBoundary componentWillMount [!]',891      // The error propagates to the higher boundary892      'ErrorBoundary componentDidMount',893      'ErrorBoundary componentDidCatch',894      'ErrorBoundary componentWillUpdate',895      'ErrorBoundary render error',896      'ErrorBoundary componentDidUpdate',897    ]);898    log.length = 0;899    ReactDOM.unmountComponentAtNode(container);900    expect(log).toEqual(['ErrorBoundary componentWillUnmount']);901  });902  it('propagates errors inside boundary while rendering error state', () => {903    const container = document.createElement('div');904    ReactDOM.render(905      <ErrorBoundary>906        <BrokenRenderErrorBoundary>907          <BrokenRender />908        </BrokenRenderErrorBoundary>909      </ErrorBoundary>,910      container,911    );912    expect(container.firstChild.textContent).toBe('Caught an error: Hello.');913    expect(log).toEqual([914      'ErrorBoundary constructor',915      'ErrorBoundary componentWillMount',916      'ErrorBoundary render success',917      'BrokenRenderErrorBoundary constructor',918      'BrokenRenderErrorBoundary componentWillMount',919      'BrokenRenderErrorBoundary render success',920      'BrokenRender constructor',921      'BrokenRender componentWillMount',922      'BrokenRender render [!]',923      // The first error boundary catches the error924      // It adjusts state but throws displaying the message925      // Finish mounting with null children926      'BrokenRenderErrorBoundary componentDidMount',927      // Attempt to handle the error928      'BrokenRenderErrorBoundary componentDidCatch',929      'ErrorBoundary componentDidMount',930      'BrokenRenderErrorBoundary render error [!]',931      // Boundary fails with new error, propagate to next boundary932      'BrokenRenderErrorBoundary componentWillUnmount',933      // Attempt to handle the error again934      'ErrorBoundary componentDidCatch',935      'ErrorBoundary componentWillUpdate',936      'ErrorBoundary render error',937      'ErrorBoundary componentDidUpdate',938    ]);939    log.length = 0;940    ReactDOM.unmountComponentAtNode(container);941    expect(log).toEqual(['ErrorBoundary componentWillUnmount']);942  });943  it('does not call componentWillUnmount when aborting initial mount', () => {944    const container = document.createElement('div');945    ReactDOM.render(946      <ErrorBoundary>947        <Normal />948        <BrokenRender />949        <Normal />950      </ErrorBoundary>,951      container,952    );953    expect(container.firstChild.textContent).toBe('Caught an error: Hello.');954    expect(log).toEqual([955      'ErrorBoundary constructor',956      'ErrorBoundary componentWillMount',957      'ErrorBoundary render success',958      // Render first child959      'Normal constructor',960      'Normal componentWillMount',961      'Normal render',962      // Render second child (it throws)963      'BrokenRender constructor',964      'BrokenRender componentWillMount',965      'BrokenRender render [!]',966      // Render third child, even though an earlier sibling threw.967      'Normal constructor',968      'Normal componentWillMount',969      'Normal render',970      // Finish mounting with null children971      'ErrorBoundary componentDidMount',972      // Handle the error973      'ErrorBoundary componentDidCatch',974      // Render the error message975      'ErrorBoundary componentWillUpdate',976      'ErrorBoundary render error',977      'ErrorBoundary componentDidUpdate',978    ]);979    log.length = 0;980    ReactDOM.unmountComponentAtNode(container);981    expect(log).toEqual(['ErrorBoundary componentWillUnmount']);982  });983  it('resets callback refs if mounting aborts', () => {984    function childRef(x) {985      log.push('Child ref is set to ' + x);986    }987    function errorMessageRef(x) {988      log.push('Error message ref is set to ' + x);989    }990    const container = document.createElement('div');991    ReactDOM.render(992      <ErrorBoundary errorMessageRef={errorMessageRef}>993        <div ref={childRef} />994        <BrokenRender />995      </ErrorBoundary>,996      container,997    );998    expect(container.textContent).toBe('Caught an error: Hello.');999    expect(log).toEqual([1000      'ErrorBoundary constructor',1001      'ErrorBoundary componentWillMount',1002      'ErrorBoundary render success',1003      'BrokenRender constructor',1004      'BrokenRender componentWillMount',1005      'BrokenRender render [!]',1006      // Handle error:1007      // Finish mounting with null children1008      'ErrorBoundary componentDidMount',1009      // Handle the error1010      'ErrorBoundary componentDidCatch',1011      // Render the error message1012      'ErrorBoundary componentWillUpdate',1013      'ErrorBoundary render error',1014      'Error message ref is set to [object HTMLDivElement]',1015      'ErrorBoundary componentDidUpdate',1016    ]);1017    log.length = 0;1018    ReactDOM.unmountComponentAtNode(container);1019    expect(log).toEqual([1020      'ErrorBoundary componentWillUnmount',1021      'Error message ref is set to null',1022    ]);1023  });1024  it('resets object refs if mounting aborts', () => {1025    const childRef = React.createRef();1026    const errorMessageRef = React.createRef();1027    const container = document.createElement('div');1028    ReactDOM.render(1029      <ErrorBoundary errorMessageRef={errorMessageRef}>1030        <div ref={childRef} />1031        <BrokenRender />1032      </ErrorBoundary>,1033      container,1034    );1035    expect(container.textContent).toBe('Caught an error: Hello.');1036    expect(log).toEqual([1037      'ErrorBoundary constructor',1038      'ErrorBoundary componentWillMount',1039      'ErrorBoundary render success',1040      'BrokenRender constructor',1041      'BrokenRender componentWillMount',1042      'BrokenRender render [!]',1043      // Handle error:1044      // Finish mounting with null children1045      'ErrorBoundary componentDidMount',1046      // Handle the error1047      'ErrorBoundary componentDidCatch',1048      // Render the error message1049      'ErrorBoundary componentWillUpdate',1050      'ErrorBoundary render error',1051      'ErrorBoundary componentDidUpdate',1052    ]);1053    expect(errorMessageRef.current.toString()).toEqual(1054      '[object HTMLDivElement]',1055    );1056    log.length = 0;1057    ReactDOM.unmountComponentAtNode(container);1058    expect(log).toEqual(['ErrorBoundary componentWillUnmount']);1059    expect(errorMessageRef.current).toEqual(null);1060  });1061  it('successfully mounts if no error occurs', () => {1062    const container = document.createElement('div');1063    ReactDOM.render(1064      <ErrorBoundary>1065        <div>Mounted successfully.</div>1066      </ErrorBoundary>,1067      container,1068    );1069    expect(container.firstChild.textContent).toBe('Mounted successfully.');1070    expect(log).toEqual([1071      'ErrorBoundary constructor',1072      'ErrorBoundary componentWillMount',1073      'ErrorBoundary render success',1074      'ErrorBoundary componentDidMount',1075    ]);1076    log.length = 0;1077    ReactDOM.unmountComponentAtNode(container);1078    expect(log).toEqual(['ErrorBoundary componentWillUnmount']);1079  });1080  it('catches if child throws in constructor during update', () => {1081    const container = document.createElement('div');1082    ReactDOM.render(1083      <ErrorBoundary>1084        <Normal />1085      </ErrorBoundary>,1086      container,1087    );1088    log.length = 0;1089    ReactDOM.render(1090      <ErrorBoundary>1091        <Normal />1092        <Normal logName="Normal2" />1093        <BrokenConstructor />1094      </ErrorBoundary>,1095      container,1096    );1097    expect(container.textContent).toBe('Caught an error: Hello.');1098    expect(log).toEqual([1099      'ErrorBoundary componentWillReceiveProps',1100      'ErrorBoundary componentWillUpdate',1101      'ErrorBoundary render success',1102      'Normal componentWillReceiveProps',1103      'Normal componentWillUpdate',1104      'Normal render',1105      // Normal2 will attempt to mount:1106      'Normal2 constructor',1107      'Normal2 componentWillMount',1108      'Normal2 render',1109      // BrokenConstructor will abort rendering:1110      'BrokenConstructor constructor [!]',1111      // Finish updating with null children1112      'Normal componentWillUnmount',1113      'ErrorBoundary componentDidUpdate',1114      // Handle the error1115      'ErrorBoundary componentDidCatch',1116      // Render the error message1117      'ErrorBoundary componentWillUpdate',1118      'ErrorBoundary render error',1119      'ErrorBoundary componentDidUpdate',1120    ]);1121    log.length = 0;1122    ReactDOM.unmountComponentAtNode(container);1123    expect(log).toEqual(['ErrorBoundary componentWillUnmount']);1124  });1125  it('catches if child throws in componentWillMount during update', () => {1126    const container = document.createElement('div');1127    ReactDOM.render(1128      <ErrorBoundary>1129        <Normal />1130      </ErrorBoundary>,1131      container,1132    );1133    log.length = 0;1134    ReactDOM.render(1135      <ErrorBoundary>1136        <Normal />1137        <Normal logName="Normal2" />1138        <BrokenComponentWillMount />1139      </ErrorBoundary>,1140      container,1141    );1142    expect(container.textContent).toBe('Caught an error: Hello.');1143    expect(log).toEqual([1144      'ErrorBoundary componentWillReceiveProps',1145      'ErrorBoundary componentWillUpdate',1146      'ErrorBoundary render success',1147      'Normal componentWillReceiveProps',1148      'Normal componentWillUpdate',1149      'Normal render',1150      // Normal2 will attempt to mount:1151      'Normal2 constructor',1152      'Normal2 componentWillMount',1153      'Normal2 render',1154      // BrokenComponentWillMount will abort rendering:1155      'BrokenComponentWillMount constructor',1156      'BrokenComponentWillMount componentWillMount [!]',1157      // Finish updating with null children1158      'Normal componentWillUnmount',1159      'ErrorBoundary componentDidUpdate',1160      // Handle the error1161      'ErrorBoundary componentDidCatch',1162      // Render the error message1163      'ErrorBoundary componentWillUpdate',1164      'ErrorBoundary render error',1165      'ErrorBoundary componentDidUpdate',1166    ]);1167    log.length = 0;1168    ReactDOM.unmountComponentAtNode(container);1169    expect(log).toEqual(['ErrorBoundary componentWillUnmount']);1170  });1171  it('catches if child throws in componentWillReceiveProps during update', () => {1172    const container = document.createElement('div');1173    ReactDOM.render(1174      <ErrorBoundary>1175        <Normal />1176        <BrokenComponentWillReceiveProps />1177      </ErrorBoundary>,1178      container,1179    );1180    log.length = 0;1181    ReactDOM.render(1182      <ErrorBoundary>1183        <Normal />1184        <BrokenComponentWillReceiveProps />1185      </ErrorBoundary>,1186      container,1187    );1188    expect(container.textContent).toBe('Caught an error: Hello.');1189    expect(log).toEqual([1190      'ErrorBoundary componentWillReceiveProps',1191      'ErrorBoundary componentWillUpdate',1192      'ErrorBoundary render success',1193      'Normal componentWillReceiveProps',1194      'Normal componentWillUpdate',1195      'Normal render',1196      // BrokenComponentWillReceiveProps will abort rendering:1197      'BrokenComponentWillReceiveProps componentWillReceiveProps [!]',1198      // Finish updating with null children1199      'Normal componentWillUnmount',1200      'BrokenComponentWillReceiveProps componentWillUnmount',1201      'ErrorBoundary componentDidUpdate',1202      // Handle the error1203      'ErrorBoundary componentDidCatch',1204      'ErrorBoundary componentWillUpdate',1205      'ErrorBoundary render error',1206      'ErrorBoundary componentDidUpdate',1207    ]);1208    log.length = 0;1209    ReactDOM.unmountComponentAtNode(container);1210    expect(log).toEqual(['ErrorBoundary componentWillUnmount']);1211  });1212  it('catches if child throws in componentWillUpdate during update', () => {1213    const container = document.createElement('div');1214    ReactDOM.render(1215      <ErrorBoundary>1216        <Normal />1217        <BrokenComponentWillUpdate />1218      </ErrorBoundary>,1219      container,1220    );1221    log.length = 0;1222    ReactDOM.render(1223      <ErrorBoundary>1224        <Normal />1225        <BrokenComponentWillUpdate />1226      </ErrorBoundary>,1227      container,1228    );1229    expect(container.textContent).toBe('Caught an error: Hello.');1230    expect(log).toEqual([1231      'ErrorBoundary componentWillReceiveProps',1232      'ErrorBoundary componentWillUpdate',1233      'ErrorBoundary render success',1234      'Normal componentWillReceiveProps',1235      'Normal componentWillUpdate',1236      'Normal render',1237      // BrokenComponentWillUpdate will abort rendering:1238      'BrokenComponentWillUpdate componentWillReceiveProps',1239      'BrokenComponentWillUpdate componentWillUpdate [!]',1240      // Finish updating with null children1241      'Normal componentWillUnmount',1242      'BrokenComponentWillUpdate componentWillUnmount',1243      'ErrorBoundary componentDidUpdate',1244      // Handle the error1245      'ErrorBoundary componentDidCatch',1246      'ErrorBoundary componentWillUpdate',1247      'ErrorBoundary render error',1248      'ErrorBoundary componentDidUpdate',1249    ]);1250    log.length = 0;1251    ReactDOM.unmountComponentAtNode(container);1252    expect(log).toEqual(['ErrorBoundary componentWillUnmount']);1253  });1254  it('catches if child throws in render during update', () => {1255    const container = document.createElement('div');1256    ReactDOM.render(1257      <ErrorBoundary>1258        <Normal />1259      </ErrorBoundary>,1260      container,1261    );1262    log.length = 0;1263    ReactDOM.render(1264      <ErrorBoundary>1265        <Normal />1266        <Normal logName="Normal2" />1267        <BrokenRender />1268      </ErrorBoundary>,1269      container,1270    );1271    expect(container.textContent).toBe('Caught an error: Hello.');1272    expect(log).toEqual([1273      'ErrorBoundary componentWillReceiveProps',1274      'ErrorBoundary componentWillUpdate',1275      'ErrorBoundary render success',1276      'Normal componentWillReceiveProps',1277      'Normal componentWillUpdate',1278      'Normal render',1279      // Normal2 will attempt to mount:1280      'Normal2 constructor',1281      'Normal2 componentWillMount',1282      'Normal2 render',1283      // BrokenRender will abort rendering:1284      'BrokenRender constructor',1285      'BrokenRender componentWillMount',1286      'BrokenRender render [!]',1287      // Finish updating with null children1288      'Normal componentWillUnmount',1289      'ErrorBoundary componentDidUpdate',1290      // Handle the error1291      'ErrorBoundary componentDidCatch',1292      'ErrorBoundary componentWillUpdate',1293      'ErrorBoundary render error',1294      'ErrorBoundary componentDidUpdate',1295    ]);1296    log.length = 0;1297    ReactDOM.unmountComponentAtNode(container);1298    expect(log).toEqual(['ErrorBoundary componentWillUnmount']);1299  });1300  it('keeps refs up-to-date during updates', () => {1301    function child1Ref(x) {1302      log.push('Child1 ref is set to ' + x);1303    }1304    function child2Ref(x) {1305      log.push('Child2 ref is set to ' + x);1306    }1307    function errorMessageRef(x) {1308      log.push('Error message ref is set to ' + x);1309    }1310    const container = document.createElement('div');1311    ReactDOM.render(1312      <ErrorBoundary errorMessageRef={errorMessageRef}>1313        <div ref={child1Ref} />1314      </ErrorBoundary>,1315      container,1316    );1317    expect(log).toEqual([1318      'ErrorBoundary constructor',1319      'ErrorBoundary componentWillMount',1320      'ErrorBoundary render success',1321      'Child1 ref is set to [object HTMLDivElement]',1322      'ErrorBoundary componentDidMount',1323    ]);1324    log.length = 0;1325    ReactDOM.render(1326      <ErrorBoundary errorMessageRef={errorMessageRef}>1327        <div ref={child1Ref} />1328        <div ref={child2Ref} />1329        <BrokenRender />1330      </ErrorBoundary>,1331      container,1332    );1333    expect(container.textContent).toBe('Caught an error: Hello.');1334    expect(log).toEqual([1335      'ErrorBoundary componentWillReceiveProps',1336      'ErrorBoundary componentWillUpdate',1337      'ErrorBoundary render success',1338      // BrokenRender will abort rendering:1339      'BrokenRender constructor',1340      'BrokenRender componentWillMount',1341      'BrokenRender render [!]',1342      // Finish updating with null children1343      'Child1 ref is set to null',1344      'ErrorBoundary componentDidUpdate',1345      // Handle the error1346      'ErrorBoundary componentDidCatch',1347      'ErrorBoundary componentWillUpdate',1348      'ErrorBoundary render error',1349      'Error message ref is set to [object HTMLDivElement]',1350      // Child2 ref is never set because its mounting aborted1351      'ErrorBoundary componentDidUpdate',1352    ]);1353    log.length = 0;1354    ReactDOM.unmountComponentAtNode(container);1355    expect(log).toEqual([1356      'ErrorBoundary componentWillUnmount',1357      'Error message ref is set to null',1358    ]);1359  });1360  it('recovers from componentWillUnmount errors on update', () => {1361    const container = document.createElement('div');1362    ReactDOM.render(1363      <ErrorBoundary>1364        <BrokenComponentWillUnmount />1365        <BrokenComponentWillUnmount />1366        <Normal />1367      </ErrorBoundary>,1368      container,1369    );1370    log.length = 0;1371    ReactDOM.render(1372      <ErrorBoundary>1373        <BrokenComponentWillUnmount />1374      </ErrorBoundary>,1375      container,1376    );1377    expect(container.textContent).toBe('Caught an error: Hello.');1378    expect(log).toEqual([1379      'ErrorBoundary componentWillReceiveProps',1380      'ErrorBoundary componentWillUpdate',1381      'ErrorBoundary render success',1382      // Update existing child:1383      'BrokenComponentWillUnmount componentWillReceiveProps',1384      'BrokenComponentWillUnmount componentWillUpdate',1385      'BrokenComponentWillUnmount render',1386      // Unmounting throws:1387      'BrokenComponentWillUnmount componentWillUnmount [!]',1388      // Fiber proceeds with lifecycles despite errors1389      'Normal componentWillUnmount',1390      // The components have updated in this phase1391      'BrokenComponentWillUnmount componentDidUpdate',1392      'ErrorBoundary componentDidUpdate',1393      // Now that commit phase is done, Fiber unmounts the boundary's children1394      'BrokenComponentWillUnmount componentWillUnmount [!]',1395      'ErrorBoundary componentDidCatch',1396      // The initial render was aborted, so1397      // Fiber retries from the root.1398      'ErrorBoundary componentWillUpdate',1399      'ErrorBoundary componentDidUpdate',1400      // The second willUnmount error should be captured and logged, too.1401      'ErrorBoundary componentDidCatch',1402      'ErrorBoundary componentWillUpdate',1403      // Render an error now (stack will do it later)1404      'ErrorBoundary render error',1405      // Attempt to unmount previous child:1406      // Done1407      'ErrorBoundary componentDidUpdate',1408    ]);1409    log.length = 0;1410    ReactDOM.unmountComponentAtNode(container);1411    expect(log).toEqual(['ErrorBoundary componentWillUnmount']);1412  });1413  it('recovers from nested componentWillUnmount errors on update', () => {1414    const container = document.createElement('div');1415    ReactDOM.render(1416      <ErrorBoundary>1417        <Normal>1418          <BrokenComponentWillUnmount />1419        </Normal>1420        <BrokenComponentWillUnmount />1421      </ErrorBoundary>,1422      container,1423    );1424    log.length = 0;1425    ReactDOM.render(1426      <ErrorBoundary>1427        <Normal>1428          <BrokenComponentWillUnmount />1429        </Normal>1430      </ErrorBoundary>,1431      container,1432    );1433    expect(container.textContent).toBe('Caught an error: Hello.');1434    expect(log).toEqual([1435      'ErrorBoundary componentWillReceiveProps',1436      'ErrorBoundary componentWillUpdate',1437      'ErrorBoundary render success',1438      // Update existing children:1439      'Normal componentWillReceiveProps',1440      'Normal componentWillUpdate',1441      'Normal render',1442      'BrokenComponentWillUnmount componentWillReceiveProps',1443      'BrokenComponentWillUnmount componentWillUpdate',1444      'BrokenComponentWillUnmount render',1445      // Unmounting throws:1446      'BrokenComponentWillUnmount componentWillUnmount [!]',1447      // Fiber proceeds with lifecycles despite errors1448      'BrokenComponentWillUnmount componentDidUpdate',1449      'Normal componentDidUpdate',1450      'ErrorBoundary componentDidUpdate',1451      'Normal componentWillUnmount',1452      'BrokenComponentWillUnmount componentWillUnmount [!]',1453      // Now that commit phase is done, Fiber handles errors1454      'ErrorBoundary componentDidCatch',1455      // The initial render was aborted, so1456      // Fiber retries from the root.1457      'ErrorBoundary componentWillUpdate',1458      'ErrorBoundary componentDidUpdate',1459      // The second willUnmount error should be captured and logged, too.1460      'ErrorBoundary componentDidCatch',1461      'ErrorBoundary componentWillUpdate',1462      // Render an error now (stack will do it later)1463      'ErrorBoundary render error',1464      // Done1465      'ErrorBoundary componentDidUpdate',1466    ]);1467    log.length = 0;1468    ReactDOM.unmountComponentAtNode(container);1469    expect(log).toEqual(['ErrorBoundary componentWillUnmount']);1470  });1471  it('picks the right boundary when handling unmounting errors', () => {1472    function renderInnerError(error) {1473      return <div>Caught an inner error: {error.message}.</div>;1474    }1475    function renderOuterError(error) {1476      return <div>Caught an outer error: {error.message}.</div>;1477    }1478    const container = document.createElement('div');1479    ReactDOM.render(1480      <ErrorBoundary1481        logName="OuterErrorBoundary"1482        renderError={renderOuterError}>1483        <ErrorBoundary1484          logName="InnerErrorBoundary"1485          renderError={renderInnerError}>1486          <BrokenComponentWillUnmount />1487        </ErrorBoundary>1488      </ErrorBoundary>,1489      container,1490    );1491    log.length = 0;1492    ReactDOM.render(1493      <ErrorBoundary1494        logName="OuterErrorBoundary"1495        renderError={renderOuterError}>1496        <ErrorBoundary1497          logName="InnerErrorBoundary"1498          renderError={renderInnerError}1499        />1500      </ErrorBoundary>,1501      container,1502    );1503    expect(container.textContent).toBe('Caught an inner error: Hello.');1504    expect(log).toEqual([1505      // Update outer boundary1506      'OuterErrorBoundary componentWillReceiveProps',1507      'OuterErrorBoundary componentWillUpdate',1508      'OuterErrorBoundary render success',1509      // Update inner boundary1510      'InnerErrorBoundary componentWillReceiveProps',1511      'InnerErrorBoundary componentWillUpdate',1512      'InnerErrorBoundary render success',1513      // Try unmounting child1514      'BrokenComponentWillUnmount componentWillUnmount [!]',1515      // Fiber proceeds with lifecycles despite errors1516      // Inner and outer boundaries have updated in this phase1517      'InnerErrorBoundary componentDidUpdate',1518      'OuterErrorBoundary componentDidUpdate',1519      // Now that commit phase is done, Fiber handles errors1520      // Only inner boundary receives the error:1521      'InnerErrorBoundary componentDidCatch',1522      'InnerErrorBoundary componentWillUpdate',1523      // Render an error now1524      'InnerErrorBoundary render error',1525      // In Fiber, this was a local update to the1526      // inner boundary so only its hook fires1527      'InnerErrorBoundary componentDidUpdate',1528    ]);1529    log.length = 0;1530    ReactDOM.unmountComponentAtNode(container);1531    expect(log).toEqual([1532      'OuterErrorBoundary componentWillUnmount',1533      'InnerErrorBoundary componentWillUnmount',1534    ]);1535  });1536  it('can recover from error state', () => {1537    const container = document.createElement('div');1538    ReactDOM.render(1539      <ErrorBoundary>1540        <BrokenRender />1541      </ErrorBoundary>,1542      container,1543    );1544    ReactDOM.render(1545      <ErrorBoundary>1546        <Normal />1547      </ErrorBoundary>,1548      container,1549    );1550    // Error boundary doesn't retry by itself:1551    expect(container.textContent).toBe('Caught an error: Hello.');1552    // Force the success path:1553    log.length = 0;1554    ReactDOM.render(1555      <ErrorBoundary forceRetry={true}>1556        <Normal />1557      </ErrorBoundary>,1558      container,1559    );1560    expect(container.textContent).not.toContain('Caught an error');1561    expect(log).toEqual([1562      'ErrorBoundary componentWillReceiveProps',1563      'ErrorBoundary componentWillUpdate',1564      'ErrorBoundary render success',1565      // Mount children:1566      'Normal constructor',1567      'Normal componentWillMount',1568      'Normal render',1569      // Finalize updates:1570      'Normal componentDidMount',1571      'ErrorBoundary componentDidUpdate',1572    ]);1573    log.length = 0;1574    ReactDOM.unmountComponentAtNode(container);1575    expect(log).toEqual([1576      'ErrorBoundary componentWillUnmount',1577      'Normal componentWillUnmount',1578    ]);1579  });1580  it('can update multiple times in error state', () => {1581    const container = document.createElement('div');1582    ReactDOM.render(1583      <ErrorBoundary>1584        <BrokenRender />1585      </ErrorBoundary>,1586      container,1587    );1588    expect(container.textContent).toBe('Caught an error: Hello.');1589    ReactDOM.render(1590      <ErrorBoundary>1591        <BrokenRender />1592      </ErrorBoundary>,1593      container,1594    );1595    expect(container.textContent).toBe('Caught an error: Hello.');1596    ReactDOM.render(<div>Other screen</div>, container);1597    expect(container.textContent).toBe('Other screen');1598    ReactDOM.unmountComponentAtNode(container);1599  });1600  it("doesn't get into inconsistent state during removals", () => {1601    const container = document.createElement('div');1602    ReactDOM.render(1603      <ErrorBoundary>1604        <Normal />1605        <BrokenComponentWillUnmount />1606        <Normal />1607      </ErrorBoundary>,1608      container,1609    );1610    ReactDOM.render(<ErrorBoundary />, container);1611    expect(container.textContent).toBe('Caught an error: Hello.');1612    log.length = 0;1613    ReactDOM.unmountComponentAtNode(container);1614    expect(log).toEqual(['ErrorBoundary componentWillUnmount']);1615  });1616  it("doesn't get into inconsistent state during additions", () => {1617    const container = document.createElement('div');1618    ReactDOM.render(<ErrorBoundary />, container);1619    ReactDOM.render(1620      <ErrorBoundary>1621        <Normal />1622        <BrokenRender />1623        <Normal />1624      </ErrorBoundary>,1625      container,1626    );1627    expect(container.textContent).toBe('Caught an error: Hello.');1628    log.length = 0;1629    ReactDOM.unmountComponentAtNode(container);1630    expect(log).toEqual(['ErrorBoundary componentWillUnmount']);1631  });1632  it("doesn't get into inconsistent state during reorders", () => {1633    function getAMixOfNormalAndBrokenRenderElements() {1634      const elements = [];1635      for (let i = 0; i < 100; i++) {1636        elements.push(<Normal key={i} />);1637      }1638      elements.push(<MaybeBrokenRender key={100} />);1639      let currentIndex = elements.length;1640      while (0 !== currentIndex) {1641        const randomIndex = Math.floor(Math.random() * currentIndex);1642        currentIndex -= 1;1643        const temporaryValue = elements[currentIndex];1644        elements[currentIndex] = elements[randomIndex];1645        elements[randomIndex] = temporaryValue;1646      }1647      return elements;1648    }1649    class MaybeBrokenRender extends React.Component {1650      render() {1651        if (fail) {1652          throw new Error('Hello');1653        }1654        return <div>{this.props.children}</div>;1655      }1656    }1657    let fail = false;1658    const container = document.createElement('div');1659    ReactDOM.render(1660      <ErrorBoundary>{getAMixOfNormalAndBrokenRenderElements()}</ErrorBoundary>,1661      container,1662    );1663    expect(container.textContent).not.toContain('Caught an error');1664    fail = true;1665    ReactDOM.render(1666      <ErrorBoundary>{getAMixOfNormalAndBrokenRenderElements()}</ErrorBoundary>,1667      container,1668    );1669    expect(container.textContent).toBe('Caught an error: Hello.');1670    log.length = 0;1671    ReactDOM.unmountComponentAtNode(container);1672    expect(log).toEqual(['ErrorBoundary componentWillUnmount']);1673  });1674  it('catches errors originating downstream', () => {1675    let fail = false;1676    class Stateful extends React.Component {1677      state = {shouldThrow: false};1678      render() {1679        if (fail) {1680          log.push('Stateful render [!]');1681          throw new Error('Hello');1682        }1683        return <div>{this.props.children}</div>;1684      }1685    }1686    let statefulInst;1687    const container = document.createElement('div');1688    ReactDOM.render(1689      <ErrorBoundary>1690        <Stateful ref={inst => (statefulInst = inst)} />1691      </ErrorBoundary>,1692      container,1693    );1694    log.length = 0;1695    expect(() => {1696      fail = true;1697      statefulInst.forceUpdate();1698    }).not.toThrow();1699    expect(log).toEqual([1700      'Stateful render [!]',1701      'ErrorBoundary componentDidCatch',1702      'ErrorBoundary componentWillUpdate',1703      'ErrorBoundary render error',1704      'ErrorBoundary componentDidUpdate',1705    ]);1706    log.length = 0;1707    ReactDOM.unmountComponentAtNode(container);1708    expect(log).toEqual(['ErrorBoundary componentWillUnmount']);1709  });1710  it('catches errors in componentDidMount', () => {1711    const container = document.createElement('div');1712    ReactDOM.render(1713      <ErrorBoundary>1714        <BrokenComponentWillUnmount>1715          <Normal />1716        </BrokenComponentWillUnmount>1717        <BrokenComponentDidMount />1718        <Normal logName="LastChild" />1719      </ErrorBoundary>,1720      container,1721    );1722    expect(log).toEqual([1723      'ErrorBoundary constructor',1724      'ErrorBoundary componentWillMount',1725      'ErrorBoundary render success',1726      'BrokenComponentWillUnmount constructor',1727      'BrokenComponentWillUnmount componentWillMount',1728      'BrokenComponentWillUnmount render',1729      'Normal constructor',1730      'Normal componentWillMount',1731      'Normal render',1732      'BrokenComponentDidMount constructor',1733      'BrokenComponentDidMount componentWillMount',1734      'BrokenComponentDidMount render',1735      'LastChild constructor',1736      'LastChild componentWillMount',1737      'LastChild render',1738      // Start flushing didMount queue1739      'Normal componentDidMount',1740      'BrokenComponentWillUnmount componentDidMount',1741      'BrokenComponentDidMount componentDidMount [!]',1742      // Continue despite the error1743      'LastChild componentDidMount',1744      'ErrorBoundary componentDidMount',1745      // Now we are ready to handle the error1746      // Safely unmount every child1747      'BrokenComponentWillUnmount componentWillUnmount [!]',1748      // Continue unmounting safely despite any errors1749      'Normal componentWillUnmount',1750      'BrokenComponentDidMount componentWillUnmount',1751      'LastChild componentWillUnmount',1752      // Handle the error1753      'ErrorBoundary componentDidCatch',1754      'ErrorBoundary componentWillUpdate',1755      // The willUnmount error should be captured and logged, too.1756      'ErrorBoundary componentDidUpdate',1757      'ErrorBoundary componentDidCatch',1758      'ErrorBoundary componentWillUpdate',1759      'ErrorBoundary render error',1760      // The update has finished1761      'ErrorBoundary componentDidUpdate',1762    ]);1763    log.length = 0;1764    ReactDOM.unmountComponentAtNode(container);1765    expect(log).toEqual(['ErrorBoundary componentWillUnmount']);1766  });1767  it('catches errors in componentDidUpdate', () => {1768    const container = document.createElement('div');1769    ReactDOM.render(1770      <ErrorBoundary>1771        <BrokenComponentDidUpdate />1772      </ErrorBoundary>,1773      container,1774    );1775    log.length = 0;1776    ReactDOM.render(1777      <ErrorBoundary>1778        <BrokenComponentDidUpdate />1779      </ErrorBoundary>,1780      container,1781    );1782    expect(log).toEqual([1783      'ErrorBoundary componentWillReceiveProps',1784      'ErrorBoundary componentWillUpdate',1785      'ErrorBoundary render success',1786      'BrokenComponentDidUpdate componentWillReceiveProps',1787      'BrokenComponentDidUpdate componentWillUpdate',1788      'BrokenComponentDidUpdate render',1789      // All lifecycles run1790      'BrokenComponentDidUpdate componentDidUpdate [!]',1791      'ErrorBoundary componentDidUpdate',1792      'BrokenComponentDidUpdate componentWillUnmount',1793      // Then, error is handled1794      'ErrorBoundary componentDidCatch',1795      'ErrorBoundary componentWillUpdate',1796      'ErrorBoundary render error',1797      'ErrorBoundary componentDidUpdate',1798    ]);1799    log.length = 0;1800    ReactDOM.unmountComponentAtNode(container);1801    expect(log).toEqual(['ErrorBoundary componentWillUnmount']);1802  });1803  it('propagates errors inside boundary during componentDidMount', () => {1804    const container = document.createElement('div');1805    ReactDOM.render(1806      <ErrorBoundary>1807        <BrokenComponentDidMountErrorBoundary1808          renderError={error => (1809            <div>We should never catch our own error: {error.message}.</div>1810          )}1811        />1812      </ErrorBoundary>,1813      container,1814    );1815    expect(container.firstChild.textContent).toBe('Caught an error: Hello.');1816    expect(log).toEqual([1817      'ErrorBoundary constructor',1818      'ErrorBoundary componentWillMount',1819      'ErrorBoundary render success',1820      'BrokenComponentDidMountErrorBoundary constructor',1821      'BrokenComponentDidMountErrorBoundary componentWillMount',1822      'BrokenComponentDidMountErrorBoundary render success',1823      'BrokenComponentDidMountErrorBoundary componentDidMount [!]',1824      // Fiber proceeds with the hooks1825      'ErrorBoundary componentDidMount',1826      'BrokenComponentDidMountErrorBoundary componentWillUnmount',1827      // The error propagates to the higher boundary1828      'ErrorBoundary componentDidCatch',1829      // Fiber retries from the root1830      'ErrorBoundary componentWillUpdate',1831      'ErrorBoundary render error',1832      'ErrorBoundary componentDidUpdate',1833    ]);1834    log.length = 0;1835    ReactDOM.unmountComponentAtNode(container);1836    expect(log).toEqual(['ErrorBoundary componentWillUnmount']);1837  });1838  it('calls componentDidCatch for each error that is captured', () => {1839    function renderUnmountError(error) {1840      return <div>Caught an unmounting error: {error.message}.</div>;1841    }1842    function renderUpdateError(error) {1843      return <div>Caught an updating error: {error.message}.</div>;1844    }1845    const container = document.createElement('div');1846    ReactDOM.render(1847      <ErrorBoundary logName="OuterErrorBoundary">1848        <ErrorBoundary1849          logName="InnerUnmountBoundary"1850          renderError={renderUnmountError}>1851          <BrokenComponentWillUnmount errorText="E1" />1852          <BrokenComponentWillUnmount errorText="E2" />1853        </ErrorBoundary>1854        <ErrorBoundary1855          logName="InnerUpdateBoundary"1856          renderError={renderUpdateError}>1857          <BrokenComponentDidUpdate errorText="E3" />1858          <BrokenComponentDidUpdate errorText="E4" />1859        </ErrorBoundary>1860      </ErrorBoundary>,1861      container,1862    );1863    log.length = 0;1864    ReactDOM.render(1865      <ErrorBoundary logName="OuterErrorBoundary">1866        <ErrorBoundary1867          logName="InnerUnmountBoundary"1868          renderError={renderUnmountError}1869        />1870        <ErrorBoundary1871          logName="InnerUpdateBoundary"1872          renderError={renderUpdateError}>1873          <BrokenComponentDidUpdate errorText="E3" />1874          <BrokenComponentDidUpdate errorText="E4" />1875        </ErrorBoundary>1876      </ErrorBoundary>,1877      container,1878    );1879    expect(container.firstChild.textContent).toBe(1880      'Caught an unmounting error: E2.' + 'Caught an updating error: E4.',1881    );1882    expect(log).toEqual([1883      // Begin update phase1884      'OuterErrorBoundary componentWillReceiveProps',1885      'OuterErrorBoundary componentWillUpdate',1886      'OuterErrorBoundary render success',1887      'InnerUnmountBoundary componentWillReceiveProps',1888      'InnerUnmountBoundary componentWillUpdate',1889      'InnerUnmountBoundary render success',1890      'InnerUpdateBoundary componentWillReceiveProps',1891      'InnerUpdateBoundary componentWillUpdate',1892      'InnerUpdateBoundary render success',1893      // First come the updates1894      'BrokenComponentDidUpdate componentWillReceiveProps',1895      'BrokenComponentDidUpdate componentWillUpdate',1896      'BrokenComponentDidUpdate render',1897      'BrokenComponentDidUpdate componentWillReceiveProps',1898      'BrokenComponentDidUpdate componentWillUpdate',1899      'BrokenComponentDidUpdate render',1900      // We're in commit phase now, deleting1901      'BrokenComponentWillUnmount componentWillUnmount [!]',1902      'BrokenComponentWillUnmount componentWillUnmount [!]',1903      // Continue despite errors, handle them after commit is done1904      'InnerUnmountBoundary componentDidUpdate',1905      // We're still in commit phase, now calling update lifecycles1906      'BrokenComponentDidUpdate componentDidUpdate [!]',1907      // Again, continue despite errors, we'll handle them later1908      'BrokenComponentDidUpdate componentDidUpdate [!]',1909      'InnerUpdateBoundary componentDidUpdate',1910      'OuterErrorBoundary componentDidUpdate',1911      // After the commit phase, attempt to recover from any errors that1912      // were captured1913      'BrokenComponentDidUpdate componentWillUnmount',1914      'BrokenComponentDidUpdate componentWillUnmount',1915      'InnerUnmountBoundary componentDidCatch',1916      'InnerUnmountBoundary componentDidCatch',1917      'InnerUpdateBoundary componentDidCatch',1918      'InnerUpdateBoundary componentDidCatch',1919      'InnerUnmountBoundary componentWillUpdate',1920      'InnerUnmountBoundary render error',1921      'InnerUpdateBoundary componentWillUpdate',1922      'InnerUpdateBoundary render error',1923      'InnerUnmountBoundary componentDidUpdate',1924      'InnerUpdateBoundary componentDidUpdate',1925    ]);1926    log.length = 0;1927    ReactDOM.unmountComponentAtNode(container);1928    expect(log).toEqual([1929      'OuterErrorBoundary componentWillUnmount',1930      'InnerUnmountBoundary componentWillUnmount',1931      'InnerUpdateBoundary componentWillUnmount',1932    ]);1933  });1934  it('discards a bad root if the root component fails', () => {1935    const X = null;1936    const Y = undefined;1937    let err1;1938    let err2;1939    try {1940      const container = document.createElement('div');1941      expect(() => ReactDOM.render(<X />, container)).toErrorDev(1942        'React.createElement: type is invalid -- expected a string ' +1943          '(for built-in components) or a class/function ' +1944          '(for composite components) but got: null.',1945      );1946    } catch (err) {1947      err1 = err;1948    }1949    try {1950      const container = document.createElement('div');1951      expect(() => ReactDOM.render(<Y />, container)).toErrorDev(1952        'React.createElement: type is invalid -- expected a string ' +1953          '(for built-in components) or a class/function ' +1954          '(for composite components) but got: undefined.',1955      );1956    } catch (err) {1957      err2 = err;1958    }1959    expect(err1.message).toMatch(/got: null/);1960    expect(err2.message).toMatch(/got: undefined/);1961  });1962  it('renders empty output if error boundary does not handle the error', () => {1963    const container = document.createElement('div');1964    expect(() => {1965      ReactDOM.render(1966        <div>1967          Sibling1968          <NoopErrorBoundary>1969            <BrokenRender />1970          </NoopErrorBoundary>1971        </div>,1972        container,1973      );1974    }).toErrorDev(1975      'ErrorBoundary: Error boundaries should implement getDerivedStateFromError()',1976    );1977    expect(container.firstChild.textContent).toBe('Sibling');1978    expect(log).toEqual([1979      'NoopErrorBoundary constructor',1980      'NoopErrorBoundary componentWillMount',1981      'NoopErrorBoundary render',1982      'BrokenRender constructor',1983      'BrokenRender componentWillMount',1984      'BrokenRender render [!]',1985      // In Fiber, noop error boundaries render null1986      'NoopErrorBoundary componentDidMount',1987      'NoopErrorBoundary componentDidCatch',1988      // Nothing happens.1989    ]);1990    log.length = 0;1991    ReactDOM.unmountComponentAtNode(container);1992    expect(log).toEqual(['NoopErrorBoundary componentWillUnmount']);1993  });1994  it('passes first error when two errors happen in commit', () => {1995    const errors = [];1996    let caughtError;1997    class Parent extends React.Component {1998      render() {1999        return <Child />;2000      }2001      componentDidMount() {2002        errors.push('parent sad');2003        throw new Error('parent sad');2004      }2005    }2006    class Child extends React.Component {2007      render() {2008        return <div />;2009      }2010      componentDidMount() {2011        errors.push('child sad');2012        throw new Error('child sad');2013      }2014    }2015    const container = document.createElement('div');2016    try {2017      // Here, we test the behavior where there is no error boundary and we2018      // delegate to the host root.2019      ReactDOM.render(<Parent />, container);2020    } catch (e) {2021      if (e.message !== 'parent sad' && e.message !== 'child sad') {2022        throw e;2023      }2024      caughtError = e;2025    }2026    expect(errors).toEqual(['child sad', 'parent sad']);2027    // Error should be the first thrown2028    expect(caughtError.message).toBe('child sad');2029  });2030  it('propagates uncaught error inside unbatched initial mount', () => {2031    function Foo() {2032      throw new Error('foo error');2033    }2034    const container = document.createElement('div');2035    expect(() => {2036      ReactDOM.unstable_batchedUpdates(() => {2037        ReactDOM.render(<Foo />, container);2038      });2039    }).toThrow('foo error');2040  });2041  it('handles errors that occur in before-mutation commit hook', () => {2042    const errors = [];2043    let caughtError;2044    class Parent extends React.Component {2045      getSnapshotBeforeUpdate() {2046        errors.push('parent sad');2047        throw new Error('parent sad');2048      }2049      componentDidUpdate() {}2050      render() {2051        return <Child {...this.props} />;2052      }2053    }2054    class Child extends React.Component {2055      getSnapshotBeforeUpdate() {2056        errors.push('child sad');2057        throw new Error('child sad');2058      }2059      componentDidUpdate() {}2060      render() {2061        return <div />;2062      }2063    }2064    const container = document.createElement('div');2065    ReactDOM.render(<Parent value={1} />, container);2066    try {2067      ReactDOM.render(<Parent value={2} />, container);2068    } catch (e) {2069      if (e.message !== 'parent sad' && e.message !== 'child sad') {2070        throw e;2071      }2072      caughtError = e;2073    }2074    expect(errors).toEqual(['child sad', 'parent sad']);2075    // Error should be the first thrown2076    expect(caughtError.message).toBe('child sad');2077  });...ReactErrorBoundaries-test.js
Source:ReactErrorBoundaries-test.js  
1/**2 * Copyright (c) 2013-present, Facebook, Inc.3 *4 * This source code is licensed under the MIT license found in the5 * LICENSE file in the root directory of this source tree.6 *7 * @emails react-core8 */9'use strict';10let PropTypes;11let React;12let ReactDOM;13describe('ReactErrorBoundaries', () => {14  let log;15  let BrokenConstructor;16  let BrokenComponentWillMount;17  let BrokenComponentDidMount;18  let BrokenComponentWillReceiveProps;19  let BrokenComponentWillUpdate;20  let BrokenComponentDidUpdate;21  let BrokenComponentWillUnmount;22  let BrokenRenderErrorBoundary;23  let BrokenComponentWillMountErrorBoundary;24  let BrokenComponentDidMountErrorBoundary;25  let BrokenRender;26  let ErrorBoundary;27  let ErrorMessage;28  let NoopErrorBoundary;29  let RetryErrorBoundary;30  let Normal;31  beforeEach(() => {32    PropTypes = require('prop-types');33    ReactDOM = require('react-dom');34    React = require('react');35    log = [];36    BrokenConstructor = class extends React.Component {37      constructor(props) {38        super(props);39        log.push('BrokenConstructor constructor [!]');40        throw new Error('Hello');41      }42      render() {43        log.push('BrokenConstructor render');44        return <div>{this.props.children}</div>;45      }46      UNSAFE_componentWillMount() {47        log.push('BrokenConstructor componentWillMount');48      }49      componentDidMount() {50        log.push('BrokenConstructor componentDidMount');51      }52      UNSAFE_componentWillReceiveProps() {53        log.push('BrokenConstructor componentWillReceiveProps');54      }55      UNSAFE_componentWillUpdate() {56        log.push('BrokenConstructor componentWillUpdate');57      }58      componentDidUpdate() {59        log.push('BrokenConstructor componentDidUpdate');60      }61      componentWillUnmount() {62        log.push('BrokenConstructor componentWillUnmount');63      }64    };65    BrokenComponentWillMount = class extends React.Component {66      constructor(props) {67        super(props);68        log.push('BrokenComponentWillMount constructor');69      }70      render() {71        log.push('BrokenComponentWillMount render');72        return <div>{this.props.children}</div>;73      }74      UNSAFE_componentWillMount() {75        log.push('BrokenComponentWillMount componentWillMount [!]');76        throw new Error('Hello');77      }78      componentDidMount() {79        log.push('BrokenComponentWillMount componentDidMount');80      }81      UNSAFE_componentWillReceiveProps() {82        log.push('BrokenComponentWillMount componentWillReceiveProps');83      }84      UNSAFE_componentWillUpdate() {85        log.push('BrokenComponentWillMount componentWillUpdate');86      }87      componentDidUpdate() {88        log.push('BrokenComponentWillMount componentDidUpdate');89      }90      componentWillUnmount() {91        log.push('BrokenComponentWillMount componentWillUnmount');92      }93    };94    BrokenComponentDidMount = class extends React.Component {95      constructor(props) {96        super(props);97        log.push('BrokenComponentDidMount constructor');98      }99      render() {100        log.push('BrokenComponentDidMount render');101        return <div>{this.props.children}</div>;102      }103      UNSAFE_componentWillMount() {104        log.push('BrokenComponentDidMount componentWillMount');105      }106      componentDidMount() {107        log.push('BrokenComponentDidMount componentDidMount [!]');108        throw new Error('Hello');109      }110      UNSAFE_componentWillReceiveProps() {111        log.push('BrokenComponentDidMount componentWillReceiveProps');112      }113      UNSAFE_componentWillUpdate() {114        log.push('BrokenComponentDidMount componentWillUpdate');115      }116      componentDidUpdate() {117        log.push('BrokenComponentDidMount componentDidUpdate');118      }119      componentWillUnmount() {120        log.push('BrokenComponentDidMount componentWillUnmount');121      }122    };123    BrokenComponentWillReceiveProps = class extends React.Component {124      constructor(props) {125        super(props);126        log.push('BrokenComponentWillReceiveProps constructor');127      }128      render() {129        log.push('BrokenComponentWillReceiveProps render');130        return <div>{this.props.children}</div>;131      }132      UNSAFE_componentWillMount() {133        log.push('BrokenComponentWillReceiveProps componentWillMount');134      }135      componentDidMount() {136        log.push('BrokenComponentWillReceiveProps componentDidMount');137      }138      UNSAFE_componentWillReceiveProps() {139        log.push(140          'BrokenComponentWillReceiveProps componentWillReceiveProps [!]',141        );142        throw new Error('Hello');143      }144      UNSAFE_componentWillUpdate() {145        log.push('BrokenComponentWillReceiveProps componentWillUpdate');146      }147      componentDidUpdate() {148        log.push('BrokenComponentWillReceiveProps componentDidUpdate');149      }150      componentWillUnmount() {151        log.push('BrokenComponentWillReceiveProps componentWillUnmount');152      }153    };154    BrokenComponentWillUpdate = class extends React.Component {155      constructor(props) {156        super(props);157        log.push('BrokenComponentWillUpdate constructor');158      }159      render() {160        log.push('BrokenComponentWillUpdate render');161        return <div>{this.props.children}</div>;162      }163      UNSAFE_componentWillMount() {164        log.push('BrokenComponentWillUpdate componentWillMount');165      }166      componentDidMount() {167        log.push('BrokenComponentWillUpdate componentDidMount');168      }169      UNSAFE_componentWillReceiveProps() {170        log.push('BrokenComponentWillUpdate componentWillReceiveProps');171      }172      UNSAFE_componentWillUpdate() {173        log.push('BrokenComponentWillUpdate componentWillUpdate [!]');174        throw new Error('Hello');175      }176      componentDidUpdate() {177        log.push('BrokenComponentWillUpdate componentDidUpdate');178      }179      componentWillUnmount() {180        log.push('BrokenComponentWillUpdate componentWillUnmount');181      }182    };183    BrokenComponentDidUpdate = class extends React.Component {184      static defaultProps = {185        errorText: 'Hello',186      };187      constructor(props) {188        super(props);189        log.push('BrokenComponentDidUpdate constructor');190      }191      render() {192        log.push('BrokenComponentDidUpdate render');193        return <div>{this.props.children}</div>;194      }195      UNSAFE_componentWillMount() {196        log.push('BrokenComponentDidUpdate componentWillMount');197      }198      componentDidMount() {199        log.push('BrokenComponentDidUpdate componentDidMount');200      }201      UNSAFE_componentWillReceiveProps() {202        log.push('BrokenComponentDidUpdate componentWillReceiveProps');203      }204      UNSAFE_componentWillUpdate() {205        log.push('BrokenComponentDidUpdate componentWillUpdate');206      }207      componentDidUpdate() {208        log.push('BrokenComponentDidUpdate componentDidUpdate [!]');209        throw new Error(this.props.errorText);210      }211      componentWillUnmount() {212        log.push('BrokenComponentDidUpdate componentWillUnmount');213      }214    };215    BrokenComponentWillUnmount = class extends React.Component {216      static defaultProps = {217        errorText: 'Hello',218      };219      constructor(props) {220        super(props);221        log.push('BrokenComponentWillUnmount constructor');222      }223      render() {224        log.push('BrokenComponentWillUnmount render');225        return <div>{this.props.children}</div>;226      }227      UNSAFE_componentWillMount() {228        log.push('BrokenComponentWillUnmount componentWillMount');229      }230      componentDidMount() {231        log.push('BrokenComponentWillUnmount componentDidMount');232      }233      UNSAFE_componentWillReceiveProps() {234        log.push('BrokenComponentWillUnmount componentWillReceiveProps');235      }236      UNSAFE_componentWillUpdate() {237        log.push('BrokenComponentWillUnmount componentWillUpdate');238      }239      componentDidUpdate() {240        log.push('BrokenComponentWillUnmount componentDidUpdate');241      }242      componentWillUnmount() {243        log.push('BrokenComponentWillUnmount componentWillUnmount [!]');244        throw new Error(this.props.errorText);245      }246    };247    BrokenComponentWillMountErrorBoundary = class extends React.Component {248      constructor(props) {249        super(props);250        this.state = {error: null};251        log.push('BrokenComponentWillMountErrorBoundary constructor');252      }253      render() {254        if (this.state.error) {255          log.push('BrokenComponentWillMountErrorBoundary render error');256          return <div>Caught an error: {this.state.error.message}.</div>;257        }258        log.push('BrokenComponentWillMountErrorBoundary render success');259        return <div>{this.props.children}</div>;260      }261      UNSAFE_componentWillMount() {262        log.push(263          'BrokenComponentWillMountErrorBoundary componentWillMount [!]',264        );265        throw new Error('Hello');266      }267      componentDidMount() {268        log.push('BrokenComponentWillMountErrorBoundary componentDidMount');269      }270      componentWillUnmount() {271        log.push('BrokenComponentWillMountErrorBoundary componentWillUnmount');272      }273      componentDidCatch(error) {274        log.push('BrokenComponentWillMountErrorBoundary componentDidCatch');275        this.setState({error});276      }277    };278    BrokenComponentDidMountErrorBoundary = class extends React.Component {279      constructor(props) {280        super(props);281        this.state = {error: null};282        log.push('BrokenComponentDidMountErrorBoundary constructor');283      }284      render() {285        if (this.state.error) {286          log.push('BrokenComponentDidMountErrorBoundary render error');287          return <div>Caught an error: {this.state.error.message}.</div>;288        }289        log.push('BrokenComponentDidMountErrorBoundary render success');290        return <div>{this.props.children}</div>;291      }292      UNSAFE_componentWillMount() {293        log.push('BrokenComponentDidMountErrorBoundary componentWillMount');294      }295      componentDidMount() {296        log.push('BrokenComponentDidMountErrorBoundary componentDidMount [!]');297        throw new Error('Hello');298      }299      componentWillUnmount() {300        log.push('BrokenComponentDidMountErrorBoundary componentWillUnmount');301      }302      componentDidCatch(error) {303        log.push('BrokenComponentDidMountErrorBoundary componentDidCatch');304        this.setState({error});305      }306    };307    BrokenRenderErrorBoundary = class extends React.Component {308      constructor(props) {309        super(props);310        this.state = {error: null};311        log.push('BrokenRenderErrorBoundary constructor');312      }313      render() {314        if (this.state.error) {315          log.push('BrokenRenderErrorBoundary render error [!]');316          throw new Error('Hello');317        }318        log.push('BrokenRenderErrorBoundary render success');319        return <div>{this.props.children}</div>;320      }321      UNSAFE_componentWillMount() {322        log.push('BrokenRenderErrorBoundary componentWillMount');323      }324      componentDidMount() {325        log.push('BrokenRenderErrorBoundary componentDidMount');326      }327      componentWillUnmount() {328        log.push('BrokenRenderErrorBoundary componentWillUnmount');329      }330      componentDidCatch(error) {331        log.push('BrokenRenderErrorBoundary componentDidCatch');332        this.setState({error});333      }334    };335    BrokenRender = class extends React.Component {336      constructor(props) {337        super(props);338        log.push('BrokenRender constructor');339      }340      render() {341        log.push('BrokenRender render [!]');342        throw new Error('Hello');343      }344      UNSAFE_componentWillMount() {345        log.push('BrokenRender componentWillMount');346      }347      componentDidMount() {348        log.push('BrokenRender componentDidMount');349      }350      UNSAFE_componentWillReceiveProps() {351        log.push('BrokenRender componentWillReceiveProps');352      }353      UNSAFE_componentWillUpdate() {354        log.push('BrokenRender componentWillUpdate');355      }356      componentDidUpdate() {357        log.push('BrokenRender componentDidUpdate');358      }359      componentWillUnmount() {360        log.push('BrokenRender componentWillUnmount');361      }362    };363    NoopErrorBoundary = class extends React.Component {364      constructor(props) {365        super(props);366        log.push('NoopErrorBoundary constructor');367      }368      render() {369        log.push('NoopErrorBoundary render');370        return <BrokenRender />;371      }372      UNSAFE_componentWillMount() {373        log.push('NoopErrorBoundary componentWillMount');374      }375      componentDidMount() {376        log.push('NoopErrorBoundary componentDidMount');377      }378      componentWillUnmount() {379        log.push('NoopErrorBoundary componentWillUnmount');380      }381      componentDidCatch() {382        log.push('NoopErrorBoundary componentDidCatch');383      }384    };385    Normal = class extends React.Component {386      static defaultProps = {387        logName: 'Normal',388      };389      constructor(props) {390        super(props);391        log.push(`${this.props.logName} constructor`);392      }393      render() {394        log.push(`${this.props.logName} render`);395        return <div>{this.props.children}</div>;396      }397      UNSAFE_componentWillMount() {398        log.push(`${this.props.logName} componentWillMount`);399      }400      componentDidMount() {401        log.push(`${this.props.logName} componentDidMount`);402      }403      UNSAFE_componentWillReceiveProps() {404        log.push(`${this.props.logName} componentWillReceiveProps`);405      }406      UNSAFE_componentWillUpdate() {407        log.push(`${this.props.logName} componentWillUpdate`);408      }409      componentDidUpdate() {410        log.push(`${this.props.logName} componentDidUpdate`);411      }412      componentWillUnmount() {413        log.push(`${this.props.logName} componentWillUnmount`);414      }415    };416    ErrorBoundary = class extends React.Component {417      constructor(props) {418        super(props);419        this.state = {error: null};420        log.push(`${this.props.logName} constructor`);421      }422      render() {423        if (this.state.error && !this.props.forceRetry) {424          log.push(`${this.props.logName} render error`);425          return this.props.renderError(this.state.error, this.props);426        }427        log.push(`${this.props.logName} render success`);428        return <div>{this.props.children}</div>;429      }430      componentDidCatch(error) {431        log.push(`${this.props.logName} componentDidCatch`);432        this.setState({error});433      }434      UNSAFE_componentWillMount() {435        log.push(`${this.props.logName} componentWillMount`);436      }437      componentDidMount() {438        log.push(`${this.props.logName} componentDidMount`);439      }440      UNSAFE_componentWillReceiveProps() {441        log.push(`${this.props.logName} componentWillReceiveProps`);442      }443      UNSAFE_componentWillUpdate() {444        log.push(`${this.props.logName} componentWillUpdate`);445      }446      componentDidUpdate() {447        log.push(`${this.props.logName} componentDidUpdate`);448      }449      componentWillUnmount() {450        log.push(`${this.props.logName} componentWillUnmount`);451      }452    };453    ErrorBoundary.defaultProps = {454      logName: 'ErrorBoundary',455      renderError(error, props) {456        return (457          <div ref={props.errorMessageRef}>458            Caught an error: {error.message}.459          </div>460        );461      },462    };463    RetryErrorBoundary = class extends React.Component {464      constructor(props) {465        super(props);466        log.push('RetryErrorBoundary constructor');467      }468      render() {469        log.push('RetryErrorBoundary render');470        return <BrokenRender />;471      }472      UNSAFE_componentWillMount() {473        log.push('RetryErrorBoundary componentWillMount');474      }475      componentDidMount() {476        log.push('RetryErrorBoundary componentDidMount');477      }478      componentWillUnmount() {479        log.push('RetryErrorBoundary componentWillUnmount');480      }481      componentDidCatch(error) {482        log.push('RetryErrorBoundary componentDidCatch [!]');483        // In Fiber, calling setState() (and failing) is treated as a rethrow.484        this.setState({});485      }486    };487    ErrorMessage = class extends React.Component {488      constructor(props) {489        super(props);490        log.push('ErrorMessage constructor');491      }492      UNSAFE_componentWillMount() {493        log.push('ErrorMessage componentWillMount');494      }495      componentDidMount() {496        log.push('ErrorMessage componentDidMount');497      }498      componentWillUnmount() {499        log.push('ErrorMessage componentWillUnmount');500      }501      render() {502        log.push('ErrorMessage render');503        return <div>Caught an error: {this.props.message}.</div>;504      }505    };506  });507  it('does not swallow exceptions on mounting without boundaries', () => {508    let container = document.createElement('div');509    expect(() => {510      ReactDOM.render(<BrokenRender />, container);511    }).toThrow('Hello');512    container = document.createElement('div');513    expect(() => {514      ReactDOM.render(<BrokenComponentWillMount />, container);515    }).toThrow('Hello');516    container = document.createElement('div');517    expect(() => {518      ReactDOM.render(<BrokenComponentDidMount />, container);519    }).toThrow('Hello');520  });521  it('does not swallow exceptions on updating without boundaries', () => {522    let container = document.createElement('div');523    ReactDOM.render(<BrokenComponentWillUpdate />, container);524    expect(() => {525      ReactDOM.render(<BrokenComponentWillUpdate />, container);526    }).toThrow('Hello');527    container = document.createElement('div');528    ReactDOM.render(<BrokenComponentWillReceiveProps />, container);529    expect(() => {530      ReactDOM.render(<BrokenComponentWillReceiveProps />, container);531    }).toThrow('Hello');532    container = document.createElement('div');533    ReactDOM.render(<BrokenComponentDidUpdate />, container);534    expect(() => {535      ReactDOM.render(<BrokenComponentDidUpdate />, container);536    }).toThrow('Hello');537  });538  it('does not swallow exceptions on unmounting without boundaries', () => {539    const container = document.createElement('div');540    ReactDOM.render(<BrokenComponentWillUnmount />, container);541    expect(() => {542      ReactDOM.unmountComponentAtNode(container);543    }).toThrow('Hello');544  });545  it('prevents errors from leaking into other roots', () => {546    const container1 = document.createElement('div');547    const container2 = document.createElement('div');548    const container3 = document.createElement('div');549    ReactDOM.render(<span>Before 1</span>, container1);550    expect(() => {551      ReactDOM.render(<BrokenRender />, container2);552    }).toThrow('Hello');553    ReactDOM.render(554      <ErrorBoundary>555        <BrokenRender />556      </ErrorBoundary>,557      container3,558    );559    expect(container1.firstChild.textContent).toBe('Before 1');560    expect(container2.firstChild).toBe(null);561    expect(container3.firstChild.textContent).toBe('Caught an error: Hello.');562    ReactDOM.render(<span>After 1</span>, container1);563    ReactDOM.render(<span>After 2</span>, container2);564    ReactDOM.render(565      <ErrorBoundary forceRetry={true}>After 3</ErrorBoundary>,566      container3,567    );568    expect(container1.firstChild.textContent).toBe('After 1');569    expect(container2.firstChild.textContent).toBe('After 2');570    expect(container3.firstChild.textContent).toBe('After 3');571    ReactDOM.unmountComponentAtNode(container1);572    ReactDOM.unmountComponentAtNode(container2);573    ReactDOM.unmountComponentAtNode(container3);574    expect(container1.firstChild).toBe(null);575    expect(container2.firstChild).toBe(null);576    expect(container3.firstChild).toBe(null);577  });578  it('renders an error state if child throws in render', () => {579    const container = document.createElement('div');580    ReactDOM.render(581      <ErrorBoundary>582        <BrokenRender />583      </ErrorBoundary>,584      container,585    );586    expect(container.firstChild.textContent).toBe('Caught an error: Hello.');587    expect(log).toEqual([588      'ErrorBoundary constructor',589      'ErrorBoundary componentWillMount',590      'ErrorBoundary render success',591      'BrokenRender constructor',592      'BrokenRender componentWillMount',593      'BrokenRender render [!]',594      // Fiber mounts with null children before capturing error595      'ErrorBoundary componentDidMount',596      // Catch and render an error message597      'ErrorBoundary componentDidCatch',598      'ErrorBoundary componentWillUpdate',599      'ErrorBoundary render error',600      'ErrorBoundary componentDidUpdate',601    ]);602    log.length = 0;603    ReactDOM.unmountComponentAtNode(container);604    expect(log).toEqual(['ErrorBoundary componentWillUnmount']);605  });606  it('renders an error state if child throws in constructor', () => {607    const container = document.createElement('div');608    ReactDOM.render(609      <ErrorBoundary>610        <BrokenConstructor />611      </ErrorBoundary>,612      container,613    );614    expect(container.firstChild.textContent).toBe('Caught an error: Hello.');615    expect(log).toEqual([616      'ErrorBoundary constructor',617      'ErrorBoundary componentWillMount',618      'ErrorBoundary render success',619      'BrokenConstructor constructor [!]',620      // Fiber mounts with null children before capturing error621      'ErrorBoundary componentDidMount',622      // Catch and render an error message623      'ErrorBoundary componentDidCatch',624      'ErrorBoundary componentWillUpdate',625      'ErrorBoundary render error',626      'ErrorBoundary componentDidUpdate',627    ]);628    log.length = 0;629    ReactDOM.unmountComponentAtNode(container);630    expect(log).toEqual(['ErrorBoundary componentWillUnmount']);631  });632  it('renders an error state if child throws in componentWillMount', () => {633    const container = document.createElement('div');634    ReactDOM.render(635      <ErrorBoundary>636        <BrokenComponentWillMount />637      </ErrorBoundary>,638      container,639    );640    expect(container.firstChild.textContent).toBe('Caught an error: Hello.');641    expect(log).toEqual([642      'ErrorBoundary constructor',643      'ErrorBoundary componentWillMount',644      'ErrorBoundary render success',645      'BrokenComponentWillMount constructor',646      'BrokenComponentWillMount componentWillMount [!]',647      'ErrorBoundary componentDidMount',648      'ErrorBoundary componentDidCatch',649      'ErrorBoundary componentWillUpdate',650      'ErrorBoundary render error',651      'ErrorBoundary componentDidUpdate',652    ]);653    log.length = 0;654    ReactDOM.unmountComponentAtNode(container);655    expect(log).toEqual(['ErrorBoundary componentWillUnmount']);656  });657  it('renders an error state if context provider throws in componentWillMount', () => {658    class BrokenComponentWillMountWithContext extends React.Component {659      static childContextTypes = {foo: PropTypes.number};660      getChildContext() {661        return {foo: 42};662      }663      render() {664        return <div>{this.props.children}</div>;665      }666      UNSAFE_componentWillMount() {667        throw new Error('Hello');668      }669    }670    const container = document.createElement('div');671    ReactDOM.render(672      <ErrorBoundary>673        <BrokenComponentWillMountWithContext />674      </ErrorBoundary>,675      container,676    );677    expect(container.firstChild.textContent).toBe('Caught an error: Hello.');678  });679  it('renders an error state if module-style context provider throws in componentWillMount', () => {680    function BrokenComponentWillMountWithContext() {681      return {682        getChildContext() {683          return {foo: 42};684        },685        render() {686          return <div>{this.props.children}</div>;687        },688        UNSAFE_componentWillMount() {689          throw new Error('Hello');690        },691      };692    }693    BrokenComponentWillMountWithContext.childContextTypes = {694      foo: PropTypes.number,695    };696    const container = document.createElement('div');697    ReactDOM.render(698      <ErrorBoundary>699        <BrokenComponentWillMountWithContext />700      </ErrorBoundary>,701      container,702    );703    expect(container.firstChild.textContent).toBe('Caught an error: Hello.');704  });705  it('mounts the error message if mounting fails', () => {706    function renderError(error) {707      return <ErrorMessage message={error.message} />;708    }709    const container = document.createElement('div');710    ReactDOM.render(711      <ErrorBoundary renderError={renderError}>712        <BrokenRender />713      </ErrorBoundary>,714      container,715    );716    expect(log).toEqual([717      'ErrorBoundary constructor',718      'ErrorBoundary componentWillMount',719      'ErrorBoundary render success',720      'BrokenRender constructor',721      'BrokenRender componentWillMount',722      'BrokenRender render [!]',723      'ErrorBoundary componentDidMount',724      'ErrorBoundary componentDidCatch',725      'ErrorBoundary componentWillUpdate',726      'ErrorBoundary render error',727      'ErrorMessage constructor',728      'ErrorMessage componentWillMount',729      'ErrorMessage render',730      'ErrorMessage componentDidMount',731      'ErrorBoundary componentDidUpdate',732    ]);733    log.length = 0;734    ReactDOM.unmountComponentAtNode(container);735    expect(log).toEqual([736      'ErrorBoundary componentWillUnmount',737      'ErrorMessage componentWillUnmount',738    ]);739  });740  it('propagates errors on retry on mounting', () => {741    const container = document.createElement('div');742    ReactDOM.render(743      <ErrorBoundary>744        <RetryErrorBoundary>745          <BrokenRender />746        </RetryErrorBoundary>747      </ErrorBoundary>,748      container,749    );750    expect(container.firstChild.textContent).toBe('Caught an error: Hello.');751    expect(log).toEqual([752      'ErrorBoundary constructor',753      'ErrorBoundary componentWillMount',754      'ErrorBoundary render success',755      'RetryErrorBoundary constructor',756      'RetryErrorBoundary componentWillMount',757      'RetryErrorBoundary render',758      'BrokenRender constructor',759      'BrokenRender componentWillMount',760      'BrokenRender render [!]',761      // In Fiber, failed error boundaries render null before attempting to recover762      'RetryErrorBoundary componentDidMount',763      'RetryErrorBoundary componentDidCatch [!]',764      'ErrorBoundary componentDidMount',765      // Retry766      'RetryErrorBoundary render',767      'BrokenRender constructor',768      'BrokenRender componentWillMount',769      'BrokenRender render [!]',770      // This time, the error propagates to the higher boundary771      'RetryErrorBoundary componentWillUnmount',772      'ErrorBoundary componentDidCatch',773      // Render the error774      'ErrorBoundary componentWillUpdate',775      'ErrorBoundary render error',776      'ErrorBoundary componentDidUpdate',777    ]);778    log.length = 0;779    ReactDOM.unmountComponentAtNode(container);780    expect(log).toEqual(['ErrorBoundary componentWillUnmount']);781  });782  it('propagates errors inside boundary during componentWillMount', () => {783    const container = document.createElement('div');784    ReactDOM.render(785      <ErrorBoundary>786        <BrokenComponentWillMountErrorBoundary />787      </ErrorBoundary>,788      container,789    );790    expect(container.firstChild.textContent).toBe('Caught an error: Hello.');791    expect(log).toEqual([792      'ErrorBoundary constructor',793      'ErrorBoundary componentWillMount',794      'ErrorBoundary render success',795      'BrokenComponentWillMountErrorBoundary constructor',796      'BrokenComponentWillMountErrorBoundary componentWillMount [!]',797      // The error propagates to the higher boundary798      'ErrorBoundary componentDidMount',799      'ErrorBoundary componentDidCatch',800      'ErrorBoundary componentWillUpdate',801      'ErrorBoundary render error',802      'ErrorBoundary componentDidUpdate',803    ]);804    log.length = 0;805    ReactDOM.unmountComponentAtNode(container);806    expect(log).toEqual(['ErrorBoundary componentWillUnmount']);807  });808  it('propagates errors inside boundary while rendering error state', () => {809    const container = document.createElement('div');810    ReactDOM.render(811      <ErrorBoundary>812        <BrokenRenderErrorBoundary>813          <BrokenRender />814        </BrokenRenderErrorBoundary>815      </ErrorBoundary>,816      container,817    );818    expect(container.firstChild.textContent).toBe('Caught an error: Hello.');819    expect(log).toEqual([820      'ErrorBoundary constructor',821      'ErrorBoundary componentWillMount',822      'ErrorBoundary render success',823      'BrokenRenderErrorBoundary constructor',824      'BrokenRenderErrorBoundary componentWillMount',825      'BrokenRenderErrorBoundary render success',826      'BrokenRender constructor',827      'BrokenRender componentWillMount',828      'BrokenRender render [!]',829      // The first error boundary catches the error830      // It adjusts state but throws displaying the message831      // Finish mounting with null children832      'BrokenRenderErrorBoundary componentDidMount',833      // Attempt to handle the error834      'BrokenRenderErrorBoundary componentDidCatch',835      'ErrorBoundary componentDidMount',836      'BrokenRenderErrorBoundary render error [!]',837      // Boundary fails with new error, propagate to next boundary838      'BrokenRenderErrorBoundary componentWillUnmount',839      // Attempt to handle the error again840      'ErrorBoundary componentDidCatch',841      'ErrorBoundary componentWillUpdate',842      'ErrorBoundary render error',843      'ErrorBoundary componentDidUpdate',844    ]);845    log.length = 0;846    ReactDOM.unmountComponentAtNode(container);847    expect(log).toEqual(['ErrorBoundary componentWillUnmount']);848  });849  it('does not call componentWillUnmount when aborting initial mount', () => {850    const container = document.createElement('div');851    ReactDOM.render(852      <ErrorBoundary>853        <Normal />854        <BrokenRender />855        <Normal />856      </ErrorBoundary>,857      container,858    );859    expect(container.firstChild.textContent).toBe('Caught an error: Hello.');860    expect(log).toEqual([861      'ErrorBoundary constructor',862      'ErrorBoundary componentWillMount',863      'ErrorBoundary render success',864      // Render first child865      'Normal constructor',866      'Normal componentWillMount',867      'Normal render',868      // Render second child (it throws)869      'BrokenRender constructor',870      'BrokenRender componentWillMount',871      'BrokenRender render [!]',872      // Finish mounting with null children873      'ErrorBoundary componentDidMount',874      // Handle the error875      'ErrorBoundary componentDidCatch',876      // Render the error message877      'ErrorBoundary componentWillUpdate',878      'ErrorBoundary render error',879      'ErrorBoundary componentDidUpdate',880    ]);881    log.length = 0;882    ReactDOM.unmountComponentAtNode(container);883    expect(log).toEqual(['ErrorBoundary componentWillUnmount']);884  });885  it('resets callback refs if mounting aborts', () => {886    function childRef(x) {887      log.push('Child ref is set to ' + x);888    }889    function errorMessageRef(x) {890      log.push('Error message ref is set to ' + x);891    }892    const container = document.createElement('div');893    ReactDOM.render(894      <ErrorBoundary errorMessageRef={errorMessageRef}>895        <div ref={childRef} />896        <BrokenRender />897      </ErrorBoundary>,898      container,899    );900    expect(container.textContent).toBe('Caught an error: Hello.');901    expect(log).toEqual([902      'ErrorBoundary constructor',903      'ErrorBoundary componentWillMount',904      'ErrorBoundary render success',905      'BrokenRender constructor',906      'BrokenRender componentWillMount',907      'BrokenRender render [!]',908      // Handle error:909      // Finish mounting with null children910      'ErrorBoundary componentDidMount',911      // Handle the error912      'ErrorBoundary componentDidCatch',913      // Render the error message914      'ErrorBoundary componentWillUpdate',915      'ErrorBoundary render error',916      'Error message ref is set to [object HTMLDivElement]',917      'ErrorBoundary componentDidUpdate',918    ]);919    log.length = 0;920    ReactDOM.unmountComponentAtNode(container);921    expect(log).toEqual([922      'ErrorBoundary componentWillUnmount',923      'Error message ref is set to null',924    ]);925  });926  it('resets object refs if mounting aborts', () => {927    let childRef = React.createRef();928    let errorMessageRef = React.createRef();929    const container = document.createElement('div');930    ReactDOM.render(931      <ErrorBoundary errorMessageRef={errorMessageRef}>932        <div ref={childRef} />933        <BrokenRender />934      </ErrorBoundary>,935      container,936    );937    expect(container.textContent).toBe('Caught an error: Hello.');938    expect(log).toEqual([939      'ErrorBoundary constructor',940      'ErrorBoundary componentWillMount',941      'ErrorBoundary render success',942      'BrokenRender constructor',943      'BrokenRender componentWillMount',944      'BrokenRender render [!]',945      // Handle error:946      // Finish mounting with null children947      'ErrorBoundary componentDidMount',948      // Handle the error949      'ErrorBoundary componentDidCatch',950      // Render the error message951      'ErrorBoundary componentWillUpdate',952      'ErrorBoundary render error',953      'ErrorBoundary componentDidUpdate',954    ]);955    expect(errorMessageRef.value.toString()).toEqual('[object HTMLDivElement]');956    log.length = 0;957    ReactDOM.unmountComponentAtNode(container);958    expect(log).toEqual(['ErrorBoundary componentWillUnmount']);959    expect(errorMessageRef.value).toEqual(null);960  });961  it('successfully mounts if no error occurs', () => {962    const container = document.createElement('div');963    ReactDOM.render(964      <ErrorBoundary>965        <div>Mounted successfully.</div>966      </ErrorBoundary>,967      container,968    );969    expect(container.firstChild.textContent).toBe('Mounted successfully.');970    expect(log).toEqual([971      'ErrorBoundary constructor',972      'ErrorBoundary componentWillMount',973      'ErrorBoundary render success',974      'ErrorBoundary componentDidMount',975    ]);976    log.length = 0;977    ReactDOM.unmountComponentAtNode(container);978    expect(log).toEqual(['ErrorBoundary componentWillUnmount']);979  });980  it('catches if child throws in constructor during update', () => {981    const container = document.createElement('div');982    ReactDOM.render(983      <ErrorBoundary>984        <Normal />985      </ErrorBoundary>,986      container,987    );988    log.length = 0;989    ReactDOM.render(990      <ErrorBoundary>991        <Normal />992        <Normal logName="Normal2" />993        <BrokenConstructor />994      </ErrorBoundary>,995      container,996    );997    expect(container.textContent).toBe('Caught an error: Hello.');998    expect(log).toEqual([999      'ErrorBoundary componentWillReceiveProps',1000      'ErrorBoundary componentWillUpdate',1001      'ErrorBoundary render success',1002      'Normal componentWillReceiveProps',1003      'Normal componentWillUpdate',1004      'Normal render',1005      // Normal2 will attempt to mount:1006      'Normal2 constructor',1007      'Normal2 componentWillMount',1008      'Normal2 render',1009      // BrokenConstructor will abort rendering:1010      'BrokenConstructor constructor [!]',1011      // Finish updating with null children1012      'Normal componentWillUnmount',1013      'ErrorBoundary componentDidUpdate',1014      // Handle the error1015      'ErrorBoundary componentDidCatch',1016      // Render the error message1017      'ErrorBoundary componentWillUpdate',1018      'ErrorBoundary render error',1019      'ErrorBoundary componentDidUpdate',1020    ]);1021    log.length = 0;1022    ReactDOM.unmountComponentAtNode(container);1023    expect(log).toEqual(['ErrorBoundary componentWillUnmount']);1024  });1025  it('catches if child throws in componentWillMount during update', () => {1026    const container = document.createElement('div');1027    ReactDOM.render(1028      <ErrorBoundary>1029        <Normal />1030      </ErrorBoundary>,1031      container,1032    );1033    log.length = 0;1034    ReactDOM.render(1035      <ErrorBoundary>1036        <Normal />1037        <Normal logName="Normal2" />1038        <BrokenComponentWillMount />1039      </ErrorBoundary>,1040      container,1041    );1042    expect(container.textContent).toBe('Caught an error: Hello.');1043    expect(log).toEqual([1044      'ErrorBoundary componentWillReceiveProps',1045      'ErrorBoundary componentWillUpdate',1046      'ErrorBoundary render success',1047      'Normal componentWillReceiveProps',1048      'Normal componentWillUpdate',1049      'Normal render',1050      // Normal2 will attempt to mount:1051      'Normal2 constructor',1052      'Normal2 componentWillMount',1053      'Normal2 render',1054      // BrokenComponentWillMount will abort rendering:1055      'BrokenComponentWillMount constructor',1056      'BrokenComponentWillMount componentWillMount [!]',1057      // Finish updating with null children1058      'Normal componentWillUnmount',1059      'ErrorBoundary componentDidUpdate',1060      // Handle the error1061      'ErrorBoundary componentDidCatch',1062      // Render the error message1063      'ErrorBoundary componentWillUpdate',1064      'ErrorBoundary render error',1065      'ErrorBoundary componentDidUpdate',1066    ]);1067    log.length = 0;1068    ReactDOM.unmountComponentAtNode(container);1069    expect(log).toEqual(['ErrorBoundary componentWillUnmount']);1070  });1071  it('catches if child throws in componentWillReceiveProps during update', () => {1072    const container = document.createElement('div');1073    ReactDOM.render(1074      <ErrorBoundary>1075        <Normal />1076        <BrokenComponentWillReceiveProps />1077      </ErrorBoundary>,1078      container,1079    );1080    log.length = 0;1081    ReactDOM.render(1082      <ErrorBoundary>1083        <Normal />1084        <BrokenComponentWillReceiveProps />1085      </ErrorBoundary>,1086      container,1087    );1088    expect(container.textContent).toBe('Caught an error: Hello.');1089    expect(log).toEqual([1090      'ErrorBoundary componentWillReceiveProps',1091      'ErrorBoundary componentWillUpdate',1092      'ErrorBoundary render success',1093      'Normal componentWillReceiveProps',1094      'Normal componentWillUpdate',1095      'Normal render',1096      // BrokenComponentWillReceiveProps will abort rendering:1097      'BrokenComponentWillReceiveProps componentWillReceiveProps [!]',1098      // Finish updating with null children1099      'Normal componentWillUnmount',1100      'BrokenComponentWillReceiveProps componentWillUnmount',1101      'ErrorBoundary componentDidUpdate',1102      // Handle the error1103      'ErrorBoundary componentDidCatch',1104      'ErrorBoundary componentWillUpdate',1105      'ErrorBoundary render error',1106      'ErrorBoundary componentDidUpdate',1107    ]);1108    log.length = 0;1109    ReactDOM.unmountComponentAtNode(container);1110    expect(log).toEqual(['ErrorBoundary componentWillUnmount']);1111  });1112  it('catches if child throws in componentWillUpdate during update', () => {1113    const container = document.createElement('div');1114    ReactDOM.render(1115      <ErrorBoundary>1116        <Normal />1117        <BrokenComponentWillUpdate />1118      </ErrorBoundary>,1119      container,1120    );1121    log.length = 0;1122    ReactDOM.render(1123      <ErrorBoundary>1124        <Normal />1125        <BrokenComponentWillUpdate />1126      </ErrorBoundary>,1127      container,1128    );1129    expect(container.textContent).toBe('Caught an error: Hello.');1130    expect(log).toEqual([1131      'ErrorBoundary componentWillReceiveProps',1132      'ErrorBoundary componentWillUpdate',1133      'ErrorBoundary render success',1134      'Normal componentWillReceiveProps',1135      'Normal componentWillUpdate',1136      'Normal render',1137      // BrokenComponentWillUpdate will abort rendering:1138      'BrokenComponentWillUpdate componentWillReceiveProps',1139      'BrokenComponentWillUpdate componentWillUpdate [!]',1140      // Finish updating with null children1141      'Normal componentWillUnmount',1142      'BrokenComponentWillUpdate componentWillUnmount',1143      'ErrorBoundary componentDidUpdate',1144      // Handle the error1145      'ErrorBoundary componentDidCatch',1146      'ErrorBoundary componentWillUpdate',1147      'ErrorBoundary render error',1148      'ErrorBoundary componentDidUpdate',1149    ]);1150    log.length = 0;1151    ReactDOM.unmountComponentAtNode(container);1152    expect(log).toEqual(['ErrorBoundary componentWillUnmount']);1153  });1154  it('catches if child throws in render during update', () => {1155    const container = document.createElement('div');1156    ReactDOM.render(1157      <ErrorBoundary>1158        <Normal />1159      </ErrorBoundary>,1160      container,1161    );1162    log.length = 0;1163    ReactDOM.render(1164      <ErrorBoundary>1165        <Normal />1166        <Normal logName="Normal2" />1167        <BrokenRender />1168      </ErrorBoundary>,1169      container,1170    );1171    expect(container.textContent).toBe('Caught an error: Hello.');1172    expect(log).toEqual([1173      'ErrorBoundary componentWillReceiveProps',1174      'ErrorBoundary componentWillUpdate',1175      'ErrorBoundary render success',1176      'Normal componentWillReceiveProps',1177      'Normal componentWillUpdate',1178      'Normal render',1179      // Normal2 will attempt to mount:1180      'Normal2 constructor',1181      'Normal2 componentWillMount',1182      'Normal2 render',1183      // BrokenRender will abort rendering:1184      'BrokenRender constructor',1185      'BrokenRender componentWillMount',1186      'BrokenRender render [!]',1187      // Finish updating with null children1188      'Normal componentWillUnmount',1189      'ErrorBoundary componentDidUpdate',1190      // Handle the error1191      'ErrorBoundary componentDidCatch',1192      'ErrorBoundary componentWillUpdate',1193      'ErrorBoundary render error',1194      'ErrorBoundary componentDidUpdate',1195    ]);1196    log.length = 0;1197    ReactDOM.unmountComponentAtNode(container);1198    expect(log).toEqual(['ErrorBoundary componentWillUnmount']);1199  });1200  it('keeps refs up-to-date during updates', () => {1201    function child1Ref(x) {1202      log.push('Child1 ref is set to ' + x);1203    }1204    function child2Ref(x) {1205      log.push('Child2 ref is set to ' + x);1206    }1207    function errorMessageRef(x) {1208      log.push('Error message ref is set to ' + x);1209    }1210    const container = document.createElement('div');1211    ReactDOM.render(1212      <ErrorBoundary errorMessageRef={errorMessageRef}>1213        <div ref={child1Ref} />1214      </ErrorBoundary>,1215      container,1216    );1217    expect(log).toEqual([1218      'ErrorBoundary constructor',1219      'ErrorBoundary componentWillMount',1220      'ErrorBoundary render success',1221      'Child1 ref is set to [object HTMLDivElement]',1222      'ErrorBoundary componentDidMount',1223    ]);1224    log.length = 0;1225    ReactDOM.render(1226      <ErrorBoundary errorMessageRef={errorMessageRef}>1227        <div ref={child1Ref} />1228        <div ref={child2Ref} />1229        <BrokenRender />1230      </ErrorBoundary>,1231      container,1232    );1233    expect(container.textContent).toBe('Caught an error: Hello.');1234    expect(log).toEqual([1235      'ErrorBoundary componentWillReceiveProps',1236      'ErrorBoundary componentWillUpdate',1237      'ErrorBoundary render success',1238      // BrokenRender will abort rendering:1239      'BrokenRender constructor',1240      'BrokenRender componentWillMount',1241      'BrokenRender render [!]',1242      // Finish updating with null children1243      'Child1 ref is set to null',1244      'ErrorBoundary componentDidUpdate',1245      // Handle the error1246      'ErrorBoundary componentDidCatch',1247      'ErrorBoundary componentWillUpdate',1248      'ErrorBoundary render error',1249      'Error message ref is set to [object HTMLDivElement]',1250      // Child2 ref is never set because its mounting aborted1251      'ErrorBoundary componentDidUpdate',1252    ]);1253    log.length = 0;1254    ReactDOM.unmountComponentAtNode(container);1255    expect(log).toEqual([1256      'ErrorBoundary componentWillUnmount',1257      'Error message ref is set to null',1258    ]);1259  });1260  it('recovers from componentWillUnmount errors on update', () => {1261    const container = document.createElement('div');1262    ReactDOM.render(1263      <ErrorBoundary>1264        <BrokenComponentWillUnmount />1265        <BrokenComponentWillUnmount />1266        <Normal />1267      </ErrorBoundary>,1268      container,1269    );1270    log.length = 0;1271    ReactDOM.render(1272      <ErrorBoundary>1273        <BrokenComponentWillUnmount />1274      </ErrorBoundary>,1275      container,1276    );1277    expect(container.textContent).toBe('Caught an error: Hello.');1278    expect(log).toEqual([1279      'ErrorBoundary componentWillReceiveProps',1280      'ErrorBoundary componentWillUpdate',1281      'ErrorBoundary render success',1282      // Update existing child:1283      'BrokenComponentWillUnmount componentWillReceiveProps',1284      'BrokenComponentWillUnmount componentWillUpdate',1285      'BrokenComponentWillUnmount render',1286      // Unmounting throws:1287      'BrokenComponentWillUnmount componentWillUnmount [!]',1288      // Fiber proceeds with lifecycles despite errors1289      'Normal componentWillUnmount',1290      // The components have updated in this phase1291      'BrokenComponentWillUnmount componentDidUpdate',1292      'ErrorBoundary componentDidUpdate',1293      // Now that commit phase is done, Fiber unmounts the boundary's children1294      'BrokenComponentWillUnmount componentWillUnmount [!]',1295      'ErrorBoundary componentDidCatch',1296      // The initial render was aborted, so1297      // Fiber retries from the root.1298      'ErrorBoundary componentWillUpdate',1299      // Render an error now (stack will do it later)1300      'ErrorBoundary render error',1301      // Attempt to unmount previous child:1302      // Done1303      'ErrorBoundary componentDidUpdate',1304    ]);1305    log.length = 0;1306    ReactDOM.unmountComponentAtNode(container);1307    expect(log).toEqual(['ErrorBoundary componentWillUnmount']);1308  });1309  it('recovers from nested componentWillUnmount errors on update', () => {1310    const container = document.createElement('div');1311    ReactDOM.render(1312      <ErrorBoundary>1313        <Normal>1314          <BrokenComponentWillUnmount />1315        </Normal>1316        <BrokenComponentWillUnmount />1317      </ErrorBoundary>,1318      container,1319    );1320    log.length = 0;1321    ReactDOM.render(1322      <ErrorBoundary>1323        <Normal>1324          <BrokenComponentWillUnmount />1325        </Normal>1326      </ErrorBoundary>,1327      container,1328    );1329    expect(container.textContent).toBe('Caught an error: Hello.');1330    expect(log).toEqual([1331      'ErrorBoundary componentWillReceiveProps',1332      'ErrorBoundary componentWillUpdate',1333      'ErrorBoundary render success',1334      // Update existing children:1335      'Normal componentWillReceiveProps',1336      'Normal componentWillUpdate',1337      'Normal render',1338      'BrokenComponentWillUnmount componentWillReceiveProps',1339      'BrokenComponentWillUnmount componentWillUpdate',1340      'BrokenComponentWillUnmount render',1341      // Unmounting throws:1342      'BrokenComponentWillUnmount componentWillUnmount [!]',1343      // Fiber proceeds with lifecycles despite errors1344      'BrokenComponentWillUnmount componentDidUpdate',1345      'Normal componentDidUpdate',1346      'ErrorBoundary componentDidUpdate',1347      'Normal componentWillUnmount',1348      'BrokenComponentWillUnmount componentWillUnmount [!]',1349      // Now that commit phase is done, Fiber handles errors1350      'ErrorBoundary componentDidCatch',1351      // The initial render was aborted, so1352      // Fiber retries from the root.1353      'ErrorBoundary componentWillUpdate',1354      // Render an error now (stack will do it later)1355      'ErrorBoundary render error',1356      // Done1357      'ErrorBoundary componentDidUpdate',1358    ]);1359    log.length = 0;1360    ReactDOM.unmountComponentAtNode(container);1361    expect(log).toEqual(['ErrorBoundary componentWillUnmount']);1362  });1363  it('picks the right boundary when handling unmounting errors', () => {1364    function renderInnerError(error) {1365      return <div>Caught an inner error: {error.message}.</div>;1366    }1367    function renderOuterError(error) {1368      return <div>Caught an outer error: {error.message}.</div>;1369    }1370    const container = document.createElement('div');1371    ReactDOM.render(1372      <ErrorBoundary1373        logName="OuterErrorBoundary"1374        renderError={renderOuterError}>1375        <ErrorBoundary1376          logName="InnerErrorBoundary"1377          renderError={renderInnerError}>1378          <BrokenComponentWillUnmount />1379        </ErrorBoundary>1380      </ErrorBoundary>,1381      container,1382    );1383    log.length = 0;1384    ReactDOM.render(1385      <ErrorBoundary1386        logName="OuterErrorBoundary"1387        renderError={renderOuterError}>1388        <ErrorBoundary1389          logName="InnerErrorBoundary"1390          renderError={renderInnerError}1391        />1392      </ErrorBoundary>,1393      container,1394    );1395    expect(container.textContent).toBe('Caught an inner error: Hello.');1396    expect(log).toEqual([1397      // Update outer boundary1398      'OuterErrorBoundary componentWillReceiveProps',1399      'OuterErrorBoundary componentWillUpdate',1400      'OuterErrorBoundary render success',1401      // Update inner boundary1402      'InnerErrorBoundary componentWillReceiveProps',1403      'InnerErrorBoundary componentWillUpdate',1404      'InnerErrorBoundary render success',1405      // Try unmounting child1406      'BrokenComponentWillUnmount componentWillUnmount [!]',1407      // Fiber proceeds with lifecycles despite errors1408      // Inner and outer boundaries have updated in this phase1409      'InnerErrorBoundary componentDidUpdate',1410      'OuterErrorBoundary componentDidUpdate',1411      // Now that commit phase is done, Fiber handles errors1412      // Only inner boundary receives the error:1413      'InnerErrorBoundary componentDidCatch',1414      'InnerErrorBoundary componentWillUpdate',1415      // Render an error now1416      'InnerErrorBoundary render error',1417      // In Fiber, this was a local update to the1418      // inner boundary so only its hook fires1419      'InnerErrorBoundary componentDidUpdate',1420    ]);1421    log.length = 0;1422    ReactDOM.unmountComponentAtNode(container);1423    expect(log).toEqual([1424      'OuterErrorBoundary componentWillUnmount',1425      'InnerErrorBoundary componentWillUnmount',1426    ]);1427  });1428  it('can recover from error state', () => {1429    const container = document.createElement('div');1430    ReactDOM.render(1431      <ErrorBoundary>1432        <BrokenRender />1433      </ErrorBoundary>,1434      container,1435    );1436    ReactDOM.render(1437      <ErrorBoundary>1438        <Normal />1439      </ErrorBoundary>,1440      container,1441    );1442    // Error boundary doesn't retry by itself:1443    expect(container.textContent).toBe('Caught an error: Hello.');1444    // Force the success path:1445    log.length = 0;1446    ReactDOM.render(1447      <ErrorBoundary forceRetry={true}>1448        <Normal />1449      </ErrorBoundary>,1450      container,1451    );1452    expect(container.textContent).not.toContain('Caught an error');1453    expect(log).toEqual([1454      'ErrorBoundary componentWillReceiveProps',1455      'ErrorBoundary componentWillUpdate',1456      'ErrorBoundary render success',1457      // Mount children:1458      'Normal constructor',1459      'Normal componentWillMount',1460      'Normal render',1461      // Finalize updates:1462      'Normal componentDidMount',1463      'ErrorBoundary componentDidUpdate',1464    ]);1465    log.length = 0;1466    ReactDOM.unmountComponentAtNode(container);1467    expect(log).toEqual([1468      'ErrorBoundary componentWillUnmount',1469      'Normal componentWillUnmount',1470    ]);1471  });1472  it('can update multiple times in error state', () => {1473    const container = document.createElement('div');1474    ReactDOM.render(1475      <ErrorBoundary>1476        <BrokenRender />1477      </ErrorBoundary>,1478      container,1479    );1480    expect(container.textContent).toBe('Caught an error: Hello.');1481    ReactDOM.render(1482      <ErrorBoundary>1483        <BrokenRender />1484      </ErrorBoundary>,1485      container,1486    );1487    expect(container.textContent).toBe('Caught an error: Hello.');1488    ReactDOM.render(<div>Other screen</div>, container);1489    expect(container.textContent).toBe('Other screen');1490    ReactDOM.unmountComponentAtNode(container);1491  });1492  it("doesn't get into inconsistent state during removals", () => {1493    const container = document.createElement('div');1494    ReactDOM.render(1495      <ErrorBoundary>1496        <Normal />1497        <BrokenComponentWillUnmount />1498        <Normal />1499      </ErrorBoundary>,1500      container,1501    );1502    ReactDOM.render(<ErrorBoundary />, container);1503    expect(container.textContent).toBe('Caught an error: Hello.');1504    log.length = 0;1505    ReactDOM.unmountComponentAtNode(container);1506    expect(log).toEqual(['ErrorBoundary componentWillUnmount']);1507  });1508  it("doesn't get into inconsistent state during additions", () => {1509    const container = document.createElement('div');1510    ReactDOM.render(<ErrorBoundary />, container);1511    ReactDOM.render(1512      <ErrorBoundary>1513        <Normal />1514        <BrokenRender />1515        <Normal />1516      </ErrorBoundary>,1517      container,1518    );1519    expect(container.textContent).toBe('Caught an error: Hello.');1520    log.length = 0;1521    ReactDOM.unmountComponentAtNode(container);1522    expect(log).toEqual(['ErrorBoundary componentWillUnmount']);1523  });1524  it("doesn't get into inconsistent state during reorders", () => {1525    function getAMixOfNormalAndBrokenRenderElements() {1526      const elements = [];1527      for (let i = 0; i < 100; i++) {1528        elements.push(<Normal key={i} />);1529      }1530      elements.push(<MaybeBrokenRender key={100} />);1531      let currentIndex = elements.length;1532      while (0 !== currentIndex) {1533        const randomIndex = Math.floor(Math.random() * currentIndex);1534        currentIndex -= 1;1535        const temporaryValue = elements[currentIndex];1536        elements[currentIndex] = elements[randomIndex];1537        elements[randomIndex] = temporaryValue;1538      }1539      return elements;1540    }1541    class MaybeBrokenRender extends React.Component {1542      render() {1543        if (fail) {1544          throw new Error('Hello');1545        }1546        return <div>{this.props.children}</div>;1547      }1548    }1549    let fail = false;1550    const container = document.createElement('div');1551    ReactDOM.render(1552      <ErrorBoundary>{getAMixOfNormalAndBrokenRenderElements()}</ErrorBoundary>,1553      container,1554    );1555    expect(container.textContent).not.toContain('Caught an error');1556    fail = true;1557    ReactDOM.render(1558      <ErrorBoundary>{getAMixOfNormalAndBrokenRenderElements()}</ErrorBoundary>,1559      container,1560    );1561    expect(container.textContent).toBe('Caught an error: Hello.');1562    log.length = 0;1563    ReactDOM.unmountComponentAtNode(container);1564    expect(log).toEqual(['ErrorBoundary componentWillUnmount']);1565  });1566  it('catches errors originating downstream', () => {1567    let fail = false;1568    class Stateful extends React.Component {1569      state = {shouldThrow: false};1570      render() {1571        if (fail) {1572          log.push('Stateful render [!]');1573          throw new Error('Hello');1574        }1575        return <div>{this.props.children}</div>;1576      }1577    }1578    let statefulInst;1579    const container = document.createElement('div');1580    ReactDOM.render(1581      <ErrorBoundary>1582        <Stateful ref={inst => (statefulInst = inst)} />1583      </ErrorBoundary>,1584      container,1585    );1586    log.length = 0;1587    expect(() => {1588      fail = true;1589      statefulInst.forceUpdate();1590    }).not.toThrow();1591    expect(log).toEqual([1592      'Stateful render [!]',1593      'ErrorBoundary componentDidCatch',1594      'ErrorBoundary componentWillUpdate',1595      'ErrorBoundary render error',1596      'ErrorBoundary componentDidUpdate',1597    ]);1598    log.length = 0;1599    ReactDOM.unmountComponentAtNode(container);1600    expect(log).toEqual(['ErrorBoundary componentWillUnmount']);1601  });1602  it('catches errors in componentDidMount', () => {1603    const container = document.createElement('div');1604    ReactDOM.render(1605      <ErrorBoundary>1606        <BrokenComponentWillUnmount>1607          <Normal />1608        </BrokenComponentWillUnmount>1609        <BrokenComponentDidMount />1610        <Normal logName="LastChild" />1611      </ErrorBoundary>,1612      container,1613    );1614    expect(log).toEqual([1615      'ErrorBoundary constructor',1616      'ErrorBoundary componentWillMount',1617      'ErrorBoundary render success',1618      'BrokenComponentWillUnmount constructor',1619      'BrokenComponentWillUnmount componentWillMount',1620      'BrokenComponentWillUnmount render',1621      'Normal constructor',1622      'Normal componentWillMount',1623      'Normal render',1624      'BrokenComponentDidMount constructor',1625      'BrokenComponentDidMount componentWillMount',1626      'BrokenComponentDidMount render',1627      'LastChild constructor',1628      'LastChild componentWillMount',1629      'LastChild render',1630      // Start flushing didMount queue1631      'Normal componentDidMount',1632      'BrokenComponentWillUnmount componentDidMount',1633      'BrokenComponentDidMount componentDidMount [!]',1634      // Continue despite the error1635      'LastChild componentDidMount',1636      'ErrorBoundary componentDidMount',1637      // Now we are ready to handle the error1638      // Safely unmount every child1639      'BrokenComponentWillUnmount componentWillUnmount [!]',1640      // Continue unmounting safely despite any errors1641      'Normal componentWillUnmount',1642      'BrokenComponentDidMount componentWillUnmount',1643      'LastChild componentWillUnmount',1644      // Handle the error1645      'ErrorBoundary componentDidCatch',1646      'ErrorBoundary componentWillUpdate',1647      'ErrorBoundary render error',1648      // The update has finished1649      'ErrorBoundary componentDidUpdate',1650    ]);1651    log.length = 0;1652    ReactDOM.unmountComponentAtNode(container);1653    expect(log).toEqual(['ErrorBoundary componentWillUnmount']);1654  });1655  it('catches errors in componentDidUpdate', () => {1656    const container = document.createElement('div');1657    ReactDOM.render(1658      <ErrorBoundary>1659        <BrokenComponentDidUpdate />1660      </ErrorBoundary>,1661      container,1662    );1663    log.length = 0;1664    ReactDOM.render(1665      <ErrorBoundary>1666        <BrokenComponentDidUpdate />1667      </ErrorBoundary>,1668      container,1669    );1670    expect(log).toEqual([1671      'ErrorBoundary componentWillReceiveProps',1672      'ErrorBoundary componentWillUpdate',1673      'ErrorBoundary render success',1674      'BrokenComponentDidUpdate componentWillReceiveProps',1675      'BrokenComponentDidUpdate componentWillUpdate',1676      'BrokenComponentDidUpdate render',1677      // All lifecycles run1678      'BrokenComponentDidUpdate componentDidUpdate [!]',1679      'ErrorBoundary componentDidUpdate',1680      'BrokenComponentDidUpdate componentWillUnmount',1681      // Then, error is handled1682      'ErrorBoundary componentDidCatch',1683      'ErrorBoundary componentWillUpdate',1684      'ErrorBoundary render error',1685      'ErrorBoundary componentDidUpdate',1686    ]);1687    log.length = 0;1688    ReactDOM.unmountComponentAtNode(container);1689    expect(log).toEqual(['ErrorBoundary componentWillUnmount']);1690  });1691  it('propagates errors inside boundary during componentDidMount', () => {1692    const container = document.createElement('div');1693    ReactDOM.render(1694      <ErrorBoundary>1695        <BrokenComponentDidMountErrorBoundary1696          renderError={error => (1697            <div>We should never catch our own error: {error.message}.</div>1698          )}1699        />1700      </ErrorBoundary>,1701      container,1702    );1703    expect(container.firstChild.textContent).toBe('Caught an error: Hello.');1704    expect(log).toEqual([1705      'ErrorBoundary constructor',1706      'ErrorBoundary componentWillMount',1707      'ErrorBoundary render success',1708      'BrokenComponentDidMountErrorBoundary constructor',1709      'BrokenComponentDidMountErrorBoundary componentWillMount',1710      'BrokenComponentDidMountErrorBoundary render success',1711      'BrokenComponentDidMountErrorBoundary componentDidMount [!]',1712      // Fiber proceeds with the hooks1713      'ErrorBoundary componentDidMount',1714      'BrokenComponentDidMountErrorBoundary componentWillUnmount',1715      // The error propagates to the higher boundary1716      'ErrorBoundary componentDidCatch',1717      // Fiber retries from the root1718      'ErrorBoundary componentWillUpdate',1719      'ErrorBoundary render error',1720      'ErrorBoundary componentDidUpdate',1721    ]);1722    log.length = 0;1723    ReactDOM.unmountComponentAtNode(container);1724    expect(log).toEqual(['ErrorBoundary componentWillUnmount']);1725  });1726  it('lets different boundaries catch their own first errors', () => {1727    function renderUnmountError(error) {1728      return <div>Caught an unmounting error: {error.message}.</div>;1729    }1730    function renderUpdateError(error) {1731      return <div>Caught an updating error: {error.message}.</div>;1732    }1733    const container = document.createElement('div');1734    ReactDOM.render(1735      <ErrorBoundary logName="OuterErrorBoundary">1736        <ErrorBoundary1737          logName="InnerUnmountBoundary"1738          renderError={renderUnmountError}>1739          <BrokenComponentWillUnmount errorText="E1" />1740          <BrokenComponentWillUnmount errorText="E2" />1741        </ErrorBoundary>1742        <ErrorBoundary1743          logName="InnerUpdateBoundary"1744          renderError={renderUpdateError}>1745          <BrokenComponentDidUpdate errorText="E3" />1746          <BrokenComponentDidUpdate errorText="E4" />1747        </ErrorBoundary>1748      </ErrorBoundary>,1749      container,1750    );1751    log.length = 0;1752    ReactDOM.render(1753      <ErrorBoundary logName="OuterErrorBoundary">1754        <ErrorBoundary1755          logName="InnerUnmountBoundary"1756          renderError={renderUnmountError}1757        />1758        <ErrorBoundary1759          logName="InnerUpdateBoundary"1760          renderError={renderUpdateError}>1761          <BrokenComponentDidUpdate errorText="E3" />1762          <BrokenComponentDidUpdate errorText="E4" />1763        </ErrorBoundary>1764      </ErrorBoundary>,1765      container,1766    );1767    expect(container.firstChild.textContent).toBe(1768      'Caught an unmounting error: E1.' + 'Caught an updating error: E3.',1769    );1770    expect(log).toEqual([1771      // Begin update phase1772      'OuterErrorBoundary componentWillReceiveProps',1773      'OuterErrorBoundary componentWillUpdate',1774      'OuterErrorBoundary render success',1775      'InnerUnmountBoundary componentWillReceiveProps',1776      'InnerUnmountBoundary componentWillUpdate',1777      'InnerUnmountBoundary render success',1778      'InnerUpdateBoundary componentWillReceiveProps',1779      'InnerUpdateBoundary componentWillUpdate',1780      'InnerUpdateBoundary render success',1781      // First come the updates1782      'BrokenComponentDidUpdate componentWillReceiveProps',1783      'BrokenComponentDidUpdate componentWillUpdate',1784      'BrokenComponentDidUpdate render',1785      'BrokenComponentDidUpdate componentWillReceiveProps',1786      'BrokenComponentDidUpdate componentWillUpdate',1787      'BrokenComponentDidUpdate render',1788      // We're in commit phase now, deleting1789      'BrokenComponentWillUnmount componentWillUnmount [!]',1790      'BrokenComponentWillUnmount componentWillUnmount [!]',1791      // Continue despite errors, handle them after commit is done1792      'InnerUnmountBoundary componentDidUpdate',1793      // We're still in commit phase, now calling update lifecycles1794      'BrokenComponentDidUpdate componentDidUpdate [!]',1795      // Again, continue despite errors, we'll handle them later1796      'BrokenComponentDidUpdate componentDidUpdate [!]',1797      'InnerUpdateBoundary componentDidUpdate',1798      'OuterErrorBoundary componentDidUpdate',1799      // After the commit phase, attempt to recover from any errors that1800      // were captured1801      'BrokenComponentDidUpdate componentWillUnmount',1802      'BrokenComponentDidUpdate componentWillUnmount',1803      'InnerUnmountBoundary componentDidCatch',1804      'InnerUpdateBoundary componentDidCatch',1805      'InnerUnmountBoundary componentWillUpdate',1806      'InnerUnmountBoundary render error',1807      'InnerUpdateBoundary componentWillUpdate',1808      'InnerUpdateBoundary render error',1809      'InnerUnmountBoundary componentDidUpdate',1810      'InnerUpdateBoundary componentDidUpdate',1811    ]);1812    log.length = 0;1813    ReactDOM.unmountComponentAtNode(container);1814    expect(log).toEqual([1815      'OuterErrorBoundary componentWillUnmount',1816      'InnerUnmountBoundary componentWillUnmount',1817      'InnerUpdateBoundary componentWillUnmount',1818    ]);1819  });1820  it('discards a bad root if the root component fails', () => {1821    const X = null;1822    const Y = undefined;1823    let err1;1824    let err2;1825    try {1826      let container = document.createElement('div');1827      expect(() => ReactDOM.render(<X />, container)).toWarnDev(1828        'React.createElement: type is invalid -- expected a string ' +1829          '(for built-in components) or a class/function ' +1830          '(for composite components) but got: null.',1831      );1832    } catch (err) {1833      err1 = err;1834    }1835    try {1836      let container = document.createElement('div');1837      expect(() => ReactDOM.render(<Y />, container)).toWarnDev(1838        'React.createElement: type is invalid -- expected a string ' +1839          '(for built-in components) or a class/function ' +1840          '(for composite components) but got: undefined.',1841      );1842    } catch (err) {1843      err2 = err;1844    }1845    expect(err1.message).toMatch(/got: null/);1846    expect(err2.message).toMatch(/got: undefined/);1847  });1848  it('renders empty output if error boundary does not handle the error', () => {1849    const container = document.createElement('div');1850    ReactDOM.render(1851      <div>1852        Sibling1853        <NoopErrorBoundary>1854          <BrokenRender />1855        </NoopErrorBoundary>1856      </div>,1857      container,1858    );1859    expect(container.firstChild.textContent).toBe('Sibling');1860    expect(log).toEqual([1861      'NoopErrorBoundary constructor',1862      'NoopErrorBoundary componentWillMount',1863      'NoopErrorBoundary render',1864      'BrokenRender constructor',1865      'BrokenRender componentWillMount',1866      'BrokenRender render [!]',1867      // In Fiber, noop error boundaries render null1868      'NoopErrorBoundary componentDidMount',1869      'NoopErrorBoundary componentDidCatch',1870      // Nothing happens.1871    ]);1872    log.length = 0;1873    ReactDOM.unmountComponentAtNode(container);1874    expect(log).toEqual(['NoopErrorBoundary componentWillUnmount']);1875  });1876  it('passes first error when two errors happen in commit', () => {1877    const errors = [];1878    let caughtError;1879    class Parent extends React.Component {1880      render() {1881        return <Child />;1882      }1883      componentDidMount() {1884        errors.push('parent sad');1885        throw new Error('parent sad');1886      }1887    }1888    class Child extends React.Component {1889      render() {1890        return <div />;1891      }1892      componentDidMount() {1893        errors.push('child sad');1894        throw new Error('child sad');1895      }1896    }1897    const container = document.createElement('div');1898    try {1899      // Here, we test the behavior where there is no error boundary and we1900      // delegate to the host root.1901      ReactDOM.render(<Parent />, container);1902    } catch (e) {1903      if (e.message !== 'parent sad' && e.message !== 'child sad') {1904        throw e;1905      }1906      caughtError = e;1907    }1908    expect(errors).toEqual(['child sad', 'parent sad']);1909    // Error should be the first thrown1910    expect(caughtError.message).toBe('child sad');1911  });1912  it('propagates uncaught error inside unbatched initial mount', () => {1913    function Foo() {1914      throw new Error('foo error');1915    }1916    const container = document.createElement('div');1917    expect(() => {1918      ReactDOM.unstable_batchedUpdates(() => {1919        ReactDOM.render(<Foo />, container);1920      });1921    }).toThrow('foo error');1922  });...StrictEffectsModeDefaults-test.internal.js
Source:StrictEffectsModeDefaults-test.internal.js  
1/**2 * Copyright (c) Facebook, Inc. and its affiliates.3 *4 * This source code is licensed under the MIT license found in the5 * LICENSE file in the root directory of this source tree.6 *7 * @emails react-core8 */9'use strict';10let React;11let ReactNoop;12let Scheduler;13describe('StrictEffectsMode defaults', () => {14  beforeEach(() => {15    jest.resetModules();16    React = require('react');17    ReactNoop = require('react-noop-renderer');18    Scheduler = require('scheduler');19    const ReactFeatureFlags = require('shared/ReactFeatureFlags');20    ReactFeatureFlags.enableStrictEffects = __DEV__;21    ReactFeatureFlags.createRootStrictEffectsByDefault = __DEV__;22  });23  it('should not double invoke effects in legacy mode', () => {24    function App({text}) {25      React.useEffect(() => {26        Scheduler.unstable_yieldValue('useEffect mount');27        return () => Scheduler.unstable_yieldValue('useEffect unmount');28      });29      React.useLayoutEffect(() => {30        Scheduler.unstable_yieldValue('useLayoutEffect mount');31        return () => Scheduler.unstable_yieldValue('useLayoutEffect unmount');32      });33      return text;34    }35    ReactNoop.act(() => {36      ReactNoop.renderLegacySyncRoot(<App text={'mount'} />);37    });38    expect(Scheduler).toHaveYielded([39      'useLayoutEffect mount',40      'useEffect mount',41    ]);42  });43  it('should not double invoke class lifecycles in legacy mode', () => {44    class App extends React.PureComponent {45      componentDidMount() {46        Scheduler.unstable_yieldValue('componentDidMount');47      }48      componentDidUpdate() {49        Scheduler.unstable_yieldValue('componentDidUpdate');50      }51      componentWillUnmount() {52        Scheduler.unstable_yieldValue('componentWillUnmount');53      }54      render() {55        return this.props.text;56      }57    }58    ReactNoop.act(() => {59      ReactNoop.renderLegacySyncRoot(<App text={'mount'} />);60    });61    expect(Scheduler).toHaveYielded(['componentDidMount']);62  });63  if (__DEV__) {64    it('should flush double-invoked effects within the same frame as layout effects if there are no passive effects', () => {65      function ComponentWithEffects({label}) {66        React.useLayoutEffect(() => {67          Scheduler.unstable_yieldValue(`useLayoutEffect mount "${label}"`);68          return () =>69            Scheduler.unstable_yieldValue(`useLayoutEffect unmount "${label}"`);70        });71        return label;72      }73      ReactNoop.act(() => {74        ReactNoop.render(75          <>76            <ComponentWithEffects label={'one'} />77          </>,78        );79        expect(Scheduler).toFlushUntilNextPaint([80          'useLayoutEffect mount "one"',81          'useLayoutEffect unmount "one"',82          'useLayoutEffect mount "one"',83        ]);84      });85      ReactNoop.act(() => {86        ReactNoop.render(87          <>88            <ComponentWithEffects label={'one'} />89            <ComponentWithEffects label={'two'} />90          </>,91        );92        expect(Scheduler).toFlushUntilNextPaint([93          // Cleanup and re-run "one" (and "two") since there is no dependencies array.94          'useLayoutEffect unmount "one"',95          'useLayoutEffect mount "one"',96          'useLayoutEffect mount "two"',97          // Since "two" is new, it should be double-invoked.98          'useLayoutEffect unmount "two"',99          'useLayoutEffect mount "two"',100        ]);101      });102    });103    // This test also verifies that double-invoked effects flush synchronously104    // within the same frame as passive effects.105    it('should double invoke effects only for newly mounted components', () => {106      function ComponentWithEffects({label}) {107        React.useEffect(() => {108          Scheduler.unstable_yieldValue(`useEffect mount "${label}"`);109          return () =>110            Scheduler.unstable_yieldValue(`useEffect unmount "${label}"`);111        });112        React.useLayoutEffect(() => {113          Scheduler.unstable_yieldValue(`useLayoutEffect mount "${label}"`);114          return () =>115            Scheduler.unstable_yieldValue(`useLayoutEffect unmount "${label}"`);116        });117        return label;118      }119      ReactNoop.act(() => {120        ReactNoop.render(121          <>122            <ComponentWithEffects label={'one'} />123          </>,124        );125        expect(Scheduler).toFlushAndYieldThrough([126          'useLayoutEffect mount "one"',127        ]);128        expect(Scheduler).toFlushAndYield([129          'useEffect mount "one"',130          'useLayoutEffect unmount "one"',131          'useEffect unmount "one"',132          'useLayoutEffect mount "one"',133          'useEffect mount "one"',134        ]);135      });136      ReactNoop.act(() => {137        ReactNoop.render(138          <>139            <ComponentWithEffects label={'one'} />140            <ComponentWithEffects label={'two'} />141          </>,142        );143        expect(Scheduler).toFlushAndYieldThrough([144          // Cleanup and re-run "one" (and "two") since there is no dependencies array.145          'useLayoutEffect unmount "one"',146          'useLayoutEffect mount "one"',147          'useLayoutEffect mount "two"',148        ]);149        expect(Scheduler).toFlushAndYield([150          'useEffect unmount "one"',151          'useEffect mount "one"',152          'useEffect mount "two"',153          // Since "two" is new, it should be double-invoked.154          'useLayoutEffect unmount "two"',155          'useEffect unmount "two"',156          'useLayoutEffect mount "two"',157          'useEffect mount "two"',158        ]);159      });160    });161    it('double invoking for effects for modern roots', () => {162      function App({text}) {163        React.useEffect(() => {164          Scheduler.unstable_yieldValue('useEffect mount');165          return () => Scheduler.unstable_yieldValue('useEffect unmount');166        });167        React.useLayoutEffect(() => {168          Scheduler.unstable_yieldValue('useLayoutEffect mount');169          return () => Scheduler.unstable_yieldValue('useLayoutEffect unmount');170        });171        return text;172      }173      ReactNoop.act(() => {174        ReactNoop.render(<App text={'mount'} />);175      });176      expect(Scheduler).toHaveYielded([177        'useLayoutEffect mount',178        'useEffect mount',179        'useLayoutEffect unmount',180        'useEffect unmount',181        'useLayoutEffect mount',182        'useEffect mount',183      ]);184      ReactNoop.act(() => {185        ReactNoop.render(<App text={'update'} />);186      });187      expect(Scheduler).toHaveYielded([188        'useLayoutEffect unmount',189        'useLayoutEffect mount',190        'useEffect unmount',191        'useEffect mount',192      ]);193      ReactNoop.act(() => {194        ReactNoop.render(null);195      });196      expect(Scheduler).toHaveYielded([197        'useLayoutEffect unmount',198        'useEffect unmount',199      ]);200    });201    it('multiple effects are double invoked in the right order (all mounted, all unmounted, all remounted)', () => {202      function App({text}) {203        React.useEffect(() => {204          Scheduler.unstable_yieldValue('useEffect One mount');205          return () => Scheduler.unstable_yieldValue('useEffect One unmount');206        });207        React.useEffect(() => {208          Scheduler.unstable_yieldValue('useEffect Two mount');209          return () => Scheduler.unstable_yieldValue('useEffect Two unmount');210        });211        return text;212      }213      ReactNoop.act(() => {214        ReactNoop.render(<App text={'mount'} />);215      });216      expect(Scheduler).toHaveYielded([217        'useEffect One mount',218        'useEffect Two mount',219        'useEffect One unmount',220        'useEffect Two unmount',221        'useEffect One mount',222        'useEffect Two mount',223      ]);224      ReactNoop.act(() => {225        ReactNoop.render(<App text={'update'} />);226      });227      expect(Scheduler).toHaveYielded([228        'useEffect One unmount',229        'useEffect Two unmount',230        'useEffect One mount',231        'useEffect Two mount',232      ]);233      ReactNoop.act(() => {234        ReactNoop.render(null);235      });236      expect(Scheduler).toHaveYielded([237        'useEffect One unmount',238        'useEffect Two unmount',239      ]);240    });241    it('multiple layout effects are double invoked in the right order (all mounted, all unmounted, all remounted)', () => {242      function App({text}) {243        React.useLayoutEffect(() => {244          Scheduler.unstable_yieldValue('useLayoutEffect One mount');245          return () =>246            Scheduler.unstable_yieldValue('useLayoutEffect One unmount');247        });248        React.useLayoutEffect(() => {249          Scheduler.unstable_yieldValue('useLayoutEffect Two mount');250          return () =>251            Scheduler.unstable_yieldValue('useLayoutEffect Two unmount');252        });253        return text;254      }255      ReactNoop.act(() => {256        ReactNoop.render(<App text={'mount'} />);257      });258      expect(Scheduler).toHaveYielded([259        'useLayoutEffect One mount',260        'useLayoutEffect Two mount',261        'useLayoutEffect One unmount',262        'useLayoutEffect Two unmount',263        'useLayoutEffect One mount',264        'useLayoutEffect Two mount',265      ]);266      ReactNoop.act(() => {267        ReactNoop.render(<App text={'update'} />);268      });269      expect(Scheduler).toHaveYielded([270        'useLayoutEffect One unmount',271        'useLayoutEffect Two unmount',272        'useLayoutEffect One mount',273        'useLayoutEffect Two mount',274      ]);275      ReactNoop.act(() => {276        ReactNoop.render(null);277      });278      expect(Scheduler).toHaveYielded([279        'useLayoutEffect One unmount',280        'useLayoutEffect Two unmount',281      ]);282    });283    it('useEffect and useLayoutEffect is called twice when there is no unmount', () => {284      function App({text}) {285        React.useEffect(() => {286          Scheduler.unstable_yieldValue('useEffect mount');287        });288        React.useLayoutEffect(() => {289          Scheduler.unstable_yieldValue('useLayoutEffect mount');290        });291        return text;292      }293      ReactNoop.act(() => {294        ReactNoop.render(<App text={'mount'} />);295      });296      expect(Scheduler).toHaveYielded([297        'useLayoutEffect mount',298        'useEffect mount',299        'useLayoutEffect mount',300        'useEffect mount',301      ]);302      ReactNoop.act(() => {303        ReactNoop.render(<App text={'update'} />);304      });305      expect(Scheduler).toHaveYielded([306        'useLayoutEffect mount',307        'useEffect mount',308      ]);309      ReactNoop.act(() => {310        ReactNoop.render(null);311      });312      expect(Scheduler).toHaveYielded([]);313    });314    it('passes the right context to class component lifecycles', () => {315      class App extends React.PureComponent {316        test() {}317        componentDidMount() {318          this.test();319          Scheduler.unstable_yieldValue('componentDidMount');320        }321        componentDidUpdate() {322          this.test();323          Scheduler.unstable_yieldValue('componentDidUpdate');324        }325        componentWillUnmount() {326          this.test();327          Scheduler.unstable_yieldValue('componentWillUnmount');328        }329        render() {330          return null;331        }332      }333      ReactNoop.act(() => {334        ReactNoop.render(<App />);335      });336      expect(Scheduler).toHaveYielded([337        'componentDidMount',338        'componentWillUnmount',339        'componentDidMount',340      ]);341    });342    it('double invoking works for class components', () => {343      class App extends React.PureComponent {344        componentDidMount() {345          Scheduler.unstable_yieldValue('componentDidMount');346        }347        componentDidUpdate() {348          Scheduler.unstable_yieldValue('componentDidUpdate');349        }350        componentWillUnmount() {351          Scheduler.unstable_yieldValue('componentWillUnmount');352        }353        render() {354          return this.props.text;355        }356      }357      ReactNoop.act(() => {358        ReactNoop.render(<App text={'mount'} />);359      });360      expect(Scheduler).toHaveYielded([361        'componentDidMount',362        'componentWillUnmount',363        'componentDidMount',364      ]);365      ReactNoop.act(() => {366        ReactNoop.render(<App text={'update'} />);367      });368      expect(Scheduler).toHaveYielded(['componentDidUpdate']);369      ReactNoop.act(() => {370        ReactNoop.render(null);371      });372      expect(Scheduler).toHaveYielded(['componentWillUnmount']);373    });374    it('double flushing passive effects only results in one double invoke', () => {375      function App({text}) {376        const [state, setState] = React.useState(0);377        React.useEffect(() => {378          if (state !== 1) {379            setState(1);380          }381          Scheduler.unstable_yieldValue('useEffect mount');382          return () => Scheduler.unstable_yieldValue('useEffect unmount');383        });384        React.useLayoutEffect(() => {385          Scheduler.unstable_yieldValue('useLayoutEffect mount');386          return () => Scheduler.unstable_yieldValue('useLayoutEffect unmount');387        });388        Scheduler.unstable_yieldValue(text);389        return text;390      }391      ReactNoop.act(() => {392        ReactNoop.render(<App text={'mount'} />);393      });394      expect(Scheduler).toHaveYielded([395        'mount',396        'useLayoutEffect mount',397        'useEffect mount',398        'useLayoutEffect unmount',399        'useEffect unmount',400        'useLayoutEffect mount',401        'useEffect mount',402        'mount',403        'useLayoutEffect unmount',404        'useLayoutEffect mount',405        'useEffect unmount',406        'useEffect mount',407      ]);408    });409    it('newly mounted components after initial mount get double invoked', () => {410      let _setShowChild;411      function Child() {412        React.useEffect(() => {413          Scheduler.unstable_yieldValue('Child useEffect mount');414          return () => Scheduler.unstable_yieldValue('Child useEffect unmount');415        });416        React.useLayoutEffect(() => {417          Scheduler.unstable_yieldValue('Child useLayoutEffect mount');418          return () =>419            Scheduler.unstable_yieldValue('Child useLayoutEffect unmount');420        });421        return null;422      }423      function App() {424        const [showChild, setShowChild] = React.useState(false);425        _setShowChild = setShowChild;426        React.useEffect(() => {427          Scheduler.unstable_yieldValue('App useEffect mount');428          return () => Scheduler.unstable_yieldValue('App useEffect unmount');429        });430        React.useLayoutEffect(() => {431          Scheduler.unstable_yieldValue('App useLayoutEffect mount');432          return () =>433            Scheduler.unstable_yieldValue('App useLayoutEffect unmount');434        });435        return showChild && <Child />;436      }437      ReactNoop.act(() => {438        ReactNoop.render(<App />);439      });440      expect(Scheduler).toHaveYielded([441        'App useLayoutEffect mount',442        'App useEffect mount',443        'App useLayoutEffect unmount',444        'App useEffect unmount',445        'App useLayoutEffect mount',446        'App useEffect mount',447      ]);448      ReactNoop.act(() => {449        _setShowChild(true);450      });451      expect(Scheduler).toHaveYielded([452        'App useLayoutEffect unmount',453        'Child useLayoutEffect mount',454        'App useLayoutEffect mount',455        'App useEffect unmount',456        'Child useEffect mount',457        'App useEffect mount',458        'Child useLayoutEffect unmount',459        'Child useEffect unmount',460        'Child useLayoutEffect mount',461        'Child useEffect mount',462      ]);463    });464    it('classes and functions are double invoked together correctly', () => {465      class ClassChild extends React.PureComponent {466        componentDidMount() {467          Scheduler.unstable_yieldValue('componentDidMount');468        }469        componentWillUnmount() {470          Scheduler.unstable_yieldValue('componentWillUnmount');471        }472        render() {473          return this.props.text;474        }475      }476      function FunctionChild({text}) {477        React.useEffect(() => {478          Scheduler.unstable_yieldValue('useEffect mount');479          return () => Scheduler.unstable_yieldValue('useEffect unmount');480        });481        React.useLayoutEffect(() => {482          Scheduler.unstable_yieldValue('useLayoutEffect mount');483          return () => Scheduler.unstable_yieldValue('useLayoutEffect unmount');484        });485        return text;486      }487      function App({text}) {488        return (489          <>490            <ClassChild text={text} />491            <FunctionChild text={text} />492          </>493        );494      }495      ReactNoop.act(() => {496        ReactNoop.render(<App text={'mount'} />);497      });498      expect(Scheduler).toHaveYielded([499        'componentDidMount',500        'useLayoutEffect mount',501        'useEffect mount',502        'componentWillUnmount',503        'useLayoutEffect unmount',504        'useEffect unmount',505        'componentDidMount',506        'useLayoutEffect mount',507        'useEffect mount',508      ]);509      ReactNoop.act(() => {510        ReactNoop.render(<App text={'mount'} />);511      });512      expect(Scheduler).toHaveYielded([513        'useLayoutEffect unmount',514        'useLayoutEffect mount',515        'useEffect unmount',516        'useEffect mount',517      ]);518      ReactNoop.act(() => {519        ReactNoop.render(null);520      });521      expect(Scheduler).toHaveYielded([522        'componentWillUnmount',523        'useLayoutEffect unmount',524        'useEffect unmount',525      ]);526    });527  }...StrictEffectsMode-test.js
Source:StrictEffectsMode-test.js  
...90      'useEffect unmount',91      'useEffect mount',92    ]);93    act(() => {94      renderer.unmount();95    });96    expect(Scheduler).toHaveYielded([97      'useLayoutEffect unmount',98      'useEffect unmount',99    ]);100  });101  it('multiple effects are double invoked in the right order (all mounted, all unmounted, all remounted)', () => {102    function App({text}) {103      React.useEffect(() => {104        Scheduler.unstable_yieldValue('useEffect One mount');105        return () => Scheduler.unstable_yieldValue('useEffect One unmount');106      });107      React.useEffect(() => {108        Scheduler.unstable_yieldValue('useEffect Two mount');109        return () => Scheduler.unstable_yieldValue('useEffect Two unmount');110      });111      return text;112    }113    let renderer;114    act(() => {115      renderer = ReactTestRenderer.create(<App text={'mount'} />, {116        unstable_isConcurrent: true,117      });118    });119    if (supportsDoubleInvokeEffects()) {120      expect(Scheduler).toHaveYielded([121        'useEffect One mount',122        'useEffect Two mount',123        'useEffect One unmount',124        'useEffect Two unmount',125        'useEffect One mount',126        'useEffect Two mount',127      ]);128    } else {129      expect(Scheduler).toHaveYielded([130        'useEffect One mount',131        'useEffect Two mount',132      ]);133    }134    act(() => {135      renderer.update(<App text={'update'} />);136    });137    expect(Scheduler).toHaveYielded([138      'useEffect One unmount',139      'useEffect Two unmount',140      'useEffect One mount',141      'useEffect Two mount',142    ]);143    act(() => {144      renderer.unmount(null);145    });146    expect(Scheduler).toHaveYielded([147      'useEffect One unmount',148      'useEffect Two unmount',149    ]);150  });151  it('multiple layout effects are double invoked in the right order (all mounted, all unmounted, all remounted)', () => {152    function App({text}) {153      React.useLayoutEffect(() => {154        Scheduler.unstable_yieldValue('useLayoutEffect One mount');155        return () =>156          Scheduler.unstable_yieldValue('useLayoutEffect One unmount');157      });158      React.useLayoutEffect(() => {159        Scheduler.unstable_yieldValue('useLayoutEffect Two mount');160        return () =>161          Scheduler.unstable_yieldValue('useLayoutEffect Two unmount');162      });163      return text;164    }165    let renderer;166    act(() => {167      renderer = ReactTestRenderer.create(<App text={'mount'} />, {168        unstable_isConcurrent: true,169      });170    });171    if (supportsDoubleInvokeEffects()) {172      expect(Scheduler).toHaveYielded([173        'useLayoutEffect One mount',174        'useLayoutEffect Two mount',175        'useLayoutEffect One unmount',176        'useLayoutEffect Two unmount',177        'useLayoutEffect One mount',178        'useLayoutEffect Two mount',179      ]);180    } else {181      expect(Scheduler).toHaveYielded([182        'useLayoutEffect One mount',183        'useLayoutEffect Two mount',184      ]);185    }186    act(() => {187      renderer.update(<App text={'update'} />);188    });189    expect(Scheduler).toHaveYielded([190      'useLayoutEffect One unmount',191      'useLayoutEffect Two unmount',192      'useLayoutEffect One mount',193      'useLayoutEffect Two mount',194    ]);195    act(() => {196      renderer.unmount();197    });198    expect(Scheduler).toHaveYielded([199      'useLayoutEffect One unmount',200      'useLayoutEffect Two unmount',201    ]);202  });203  it('useEffect and useLayoutEffect is called twice when there is no unmount', () => {204    function App({text}) {205      React.useEffect(() => {206        Scheduler.unstable_yieldValue('useEffect mount');207      });208      React.useLayoutEffect(() => {209        Scheduler.unstable_yieldValue('useLayoutEffect mount');210      });211      return text;212    }213    let renderer;214    act(() => {215      renderer = ReactTestRenderer.create(<App text={'mount'} />, {216        unstable_isConcurrent: true,217      });218    });219    if (supportsDoubleInvokeEffects()) {220      expect(Scheduler).toHaveYielded([221        'useLayoutEffect mount',222        'useEffect mount',223        'useLayoutEffect mount',224        'useEffect mount',225      ]);226    } else {227      expect(Scheduler).toHaveYielded([228        'useLayoutEffect mount',229        'useEffect mount',230      ]);231    }232    act(() => {233      renderer.update(<App text={'update'} />);234    });235    expect(Scheduler).toHaveYielded([236      'useLayoutEffect mount',237      'useEffect mount',238    ]);239    act(() => {240      renderer.unmount();241    });242    expect(Scheduler).toHaveYielded([]);243  });244  it('passes the right context to class component lifecycles', () => {245    class App extends React.PureComponent {246      test() {}247      componentDidMount() {248        this.test();249        Scheduler.unstable_yieldValue('componentDidMount');250      }251      componentDidUpdate() {252        this.test();253        Scheduler.unstable_yieldValue('componentDidUpdate');254      }255      componentWillUnmount() {256        this.test();257        Scheduler.unstable_yieldValue('componentWillUnmount');258      }259      render() {260        return null;261      }262    }263    act(() => {264      ReactTestRenderer.create(<App />, {unstable_isConcurrent: true});265    });266    if (supportsDoubleInvokeEffects()) {267      expect(Scheduler).toHaveYielded([268        'componentDidMount',269        'componentWillUnmount',270        'componentDidMount',271      ]);272    } else {273      expect(Scheduler).toHaveYielded(['componentDidMount']);274    }275  });276  it('double invoking works for class components', () => {277    class App extends React.PureComponent {278      componentDidMount() {279        Scheduler.unstable_yieldValue('componentDidMount');280      }281      componentDidUpdate() {282        Scheduler.unstable_yieldValue('componentDidUpdate');283      }284      componentWillUnmount() {285        Scheduler.unstable_yieldValue('componentWillUnmount');286      }287      render() {288        return this.props.text;289      }290    }291    let renderer;292    act(() => {293      renderer = ReactTestRenderer.create(<App text={'mount'} />, {294        unstable_isConcurrent: true,295      });296    });297    if (supportsDoubleInvokeEffects()) {298      expect(Scheduler).toHaveYielded([299        'componentDidMount',300        'componentWillUnmount',301        'componentDidMount',302      ]);303    } else {304      expect(Scheduler).toHaveYielded(['componentDidMount']);305    }306    act(() => {307      renderer.update(<App text={'update'} />);308    });309    expect(Scheduler).toHaveYielded(['componentDidUpdate']);310    act(() => {311      renderer.unmount();312    });313    expect(Scheduler).toHaveYielded(['componentWillUnmount']);314  });315  it('should not double invoke class lifecycles in legacy mode', () => {316    class App extends React.PureComponent {317      componentDidMount() {318        Scheduler.unstable_yieldValue('componentDidMount');319      }320      componentDidUpdate() {321        Scheduler.unstable_yieldValue('componentDidUpdate');322      }323      componentWillUnmount() {324        Scheduler.unstable_yieldValue('componentWillUnmount');325      }326      render() {327        return this.props.text;328      }329    }330    act(() => {331      ReactTestRenderer.create(<App text={'mount'} />);332    });333    expect(Scheduler).toHaveYielded(['componentDidMount']);334  });335  it('double flushing passive effects only results in one double invoke', () => {336    function App({text}) {337      const [state, setState] = React.useState(0);338      React.useEffect(() => {339        if (state !== 1) {340          setState(1);341        }342        Scheduler.unstable_yieldValue('useEffect mount');343        return () => Scheduler.unstable_yieldValue('useEffect unmount');344      });345      React.useLayoutEffect(() => {346        Scheduler.unstable_yieldValue('useLayoutEffect mount');347        return () => Scheduler.unstable_yieldValue('useLayoutEffect unmount');348      });349      Scheduler.unstable_yieldValue(text);350      return text;351    }352    act(() => {353      ReactTestRenderer.create(<App text={'mount'} />, {354        unstable_isConcurrent: true,355      });356    });357    if (supportsDoubleInvokeEffects()) {358      expect(Scheduler).toHaveYielded([359        'mount',360        'useLayoutEffect mount',361        'useEffect mount',362        'useLayoutEffect unmount',363        'useEffect unmount',364        'useLayoutEffect mount',365        'useEffect mount',366        'mount',367        'useLayoutEffect unmount',368        'useLayoutEffect mount',369        'useEffect unmount',370        'useEffect mount',371      ]);372    } else {373      expect(Scheduler).toHaveYielded([374        'mount',375        'useLayoutEffect mount',376        'useEffect mount',377        'mount',378        'useLayoutEffect unmount',379        'useLayoutEffect mount',380        'useEffect unmount',381        'useEffect mount',382      ]);383    }384  });385  it('newly mounted components after initial mount get double invoked', () => {386    let _setShowChild;387    function Child() {388      React.useEffect(() => {389        Scheduler.unstable_yieldValue('Child useEffect mount');390        return () => Scheduler.unstable_yieldValue('Child useEffect unmount');391      });392      React.useLayoutEffect(() => {393        Scheduler.unstable_yieldValue('Child useLayoutEffect mount');394        return () =>395          Scheduler.unstable_yieldValue('Child useLayoutEffect unmount');396      });397      return null;398    }399    function App() {400      const [showChild, setShowChild] = React.useState(false);401      _setShowChild = setShowChild;402      React.useEffect(() => {403        Scheduler.unstable_yieldValue('App useEffect mount');404        return () => Scheduler.unstable_yieldValue('App useEffect unmount');405      });406      React.useLayoutEffect(() => {407        Scheduler.unstable_yieldValue('App useLayoutEffect mount');408        return () =>409          Scheduler.unstable_yieldValue('App useLayoutEffect unmount');410      });411      return showChild && <Child />;412    }413    act(() => {414      ReactTestRenderer.create(<App />, {unstable_isConcurrent: true});415    });416    if (supportsDoubleInvokeEffects()) {417      expect(Scheduler).toHaveYielded([418        'App useLayoutEffect mount',419        'App useEffect mount',420        'App useLayoutEffect unmount',421        'App useEffect unmount',422        'App useLayoutEffect mount',423        'App useEffect mount',424      ]);425    } else {426      expect(Scheduler).toHaveYielded([427        'App useLayoutEffect mount',428        'App useEffect mount',429      ]);430    }431    act(() => {432      _setShowChild(true);433    });434    if (supportsDoubleInvokeEffects()) {435      expect(Scheduler).toHaveYielded([436        'App useLayoutEffect unmount',437        'Child useLayoutEffect mount',438        'App useLayoutEffect mount',439        'App useEffect unmount',440        'Child useEffect mount',441        'App useEffect mount',442        'Child useLayoutEffect unmount',443        'Child useEffect unmount',444        'Child useLayoutEffect mount',445        'Child useEffect mount',446      ]);447    } else {448      expect(Scheduler).toHaveYielded([449        'App useLayoutEffect unmount',450        'Child useLayoutEffect mount',451        'App useLayoutEffect mount',452        'App useEffect unmount',453        'Child useEffect mount',454        'App useEffect mount',455      ]);456    }457  });458  it('classes and functions are double invoked together correctly', () => {459    class ClassChild extends React.PureComponent {460      componentDidMount() {461        Scheduler.unstable_yieldValue('componentDidMount');462      }463      componentWillUnmount() {464        Scheduler.unstable_yieldValue('componentWillUnmount');465      }466      render() {467        return this.props.text;468      }469    }470    function FunctionChild({text}) {471      React.useEffect(() => {472        Scheduler.unstable_yieldValue('useEffect mount');473        return () => Scheduler.unstable_yieldValue('useEffect unmount');474      });475      React.useLayoutEffect(() => {476        Scheduler.unstable_yieldValue('useLayoutEffect mount');477        return () => Scheduler.unstable_yieldValue('useLayoutEffect unmount');478      });479      return text;480    }481    function App({text}) {482      return (483        <>484          <ClassChild text={text} />485          <FunctionChild text={text} />486        </>487      );488    }489    let renderer;490    act(() => {491      renderer = ReactTestRenderer.create(<App text={'mount'} />, {492        unstable_isConcurrent: true,493      });494    });495    if (supportsDoubleInvokeEffects()) {496      expect(Scheduler).toHaveYielded([497        'componentDidMount',498        'useLayoutEffect mount',499        'useEffect mount',500        'componentWillUnmount',501        'useLayoutEffect unmount',502        'useEffect unmount',503        'componentDidMount',504        'useLayoutEffect mount',505        'useEffect mount',506      ]);507    } else {508      expect(Scheduler).toHaveYielded([509        'componentDidMount',510        'useLayoutEffect mount',511        'useEffect mount',512      ]);513    }514    act(() => {515      renderer.update(<App text={'mount'} />);516    });517    expect(Scheduler).toHaveYielded([518      'useLayoutEffect unmount',519      'useLayoutEffect mount',520      'useEffect unmount',521      'useEffect mount',522    ]);523    act(() => {524      renderer.unmount();525    });526    expect(Scheduler).toHaveYielded([527      'componentWillUnmount',528      'useLayoutEffect unmount',529      'useEffect unmount',530    ]);531  });...ReactDoubleInvokeEvents-test.js
Source:ReactDoubleInvokeEvents-test.js  
...89      'useEffect unmount',90      'useEffect mount',91    ]);92    act(() => {93      renderer.unmount();94    });95    expect(Scheduler).toHaveYielded([96      'useLayoutEffect unmount',97      'useEffect unmount',98    ]);99  });100  it('multiple effects are double invoked in the right order (all mounted, all unmounted, all remounted)', () => {101    function App({text}) {102      React.useEffect(() => {103        Scheduler.unstable_yieldValue('useEffect One mount');104        return () => Scheduler.unstable_yieldValue('useEffect One unmount');105      });106      React.useEffect(() => {107        Scheduler.unstable_yieldValue('useEffect Two mount');108        return () => Scheduler.unstable_yieldValue('useEffect Two unmount');109      });110      return text;111    }112    let renderer;113    act(() => {114      renderer = ReactTestRenderer.create(<App text={'mount'} />, {115        unstable_isConcurrent: true,116      });117    });118    if (supportsDoubleInvokeEffects()) {119      expect(Scheduler).toHaveYielded([120        'useEffect One mount',121        'useEffect Two mount',122        'useEffect One unmount',123        'useEffect Two unmount',124        'useEffect One mount',125        'useEffect Two mount',126      ]);127    } else {128      expect(Scheduler).toHaveYielded([129        'useEffect One mount',130        'useEffect Two mount',131      ]);132    }133    act(() => {134      renderer.update(<App text={'update'} />);135    });136    expect(Scheduler).toHaveYielded([137      'useEffect One unmount',138      'useEffect Two unmount',139      'useEffect One mount',140      'useEffect Two mount',141    ]);142    act(() => {143      renderer.unmount(null);144    });145    expect(Scheduler).toHaveYielded([146      'useEffect One unmount',147      'useEffect Two unmount',148    ]);149  });150  it('multiple layout effects are double invoked in the right order (all mounted, all unmounted, all remounted)', () => {151    function App({text}) {152      React.useLayoutEffect(() => {153        Scheduler.unstable_yieldValue('useLayoutEffect One mount');154        return () =>155          Scheduler.unstable_yieldValue('useLayoutEffect One unmount');156      });157      React.useLayoutEffect(() => {158        Scheduler.unstable_yieldValue('useLayoutEffect Two mount');159        return () =>160          Scheduler.unstable_yieldValue('useLayoutEffect Two unmount');161      });162      return text;163    }164    let renderer;165    act(() => {166      renderer = ReactTestRenderer.create(<App text={'mount'} />, {167        unstable_isConcurrent: true,168      });169    });170    if (supportsDoubleInvokeEffects()) {171      expect(Scheduler).toHaveYielded([172        'useLayoutEffect One mount',173        'useLayoutEffect Two mount',174        'useLayoutEffect One unmount',175        'useLayoutEffect Two unmount',176        'useLayoutEffect One mount',177        'useLayoutEffect Two mount',178      ]);179    } else {180      expect(Scheduler).toHaveYielded([181        'useLayoutEffect One mount',182        'useLayoutEffect Two mount',183      ]);184    }185    act(() => {186      renderer.update(<App text={'update'} />);187    });188    expect(Scheduler).toHaveYielded([189      'useLayoutEffect One unmount',190      'useLayoutEffect Two unmount',191      'useLayoutEffect One mount',192      'useLayoutEffect Two mount',193    ]);194    act(() => {195      renderer.unmount();196    });197    expect(Scheduler).toHaveYielded([198      'useLayoutEffect One unmount',199      'useLayoutEffect Two unmount',200    ]);201  });202  it('useEffect and useLayoutEffect is called twice when there is no unmount', () => {203    function App({text}) {204      React.useEffect(() => {205        Scheduler.unstable_yieldValue('useEffect mount');206      });207      React.useLayoutEffect(() => {208        Scheduler.unstable_yieldValue('useLayoutEffect mount');209      });210      return text;211    }212    let renderer;213    act(() => {214      renderer = ReactTestRenderer.create(<App text={'mount'} />, {215        unstable_isConcurrent: true,216      });217    });218    if (supportsDoubleInvokeEffects()) {219      expect(Scheduler).toHaveYielded([220        'useLayoutEffect mount',221        'useEffect mount',222        'useLayoutEffect mount',223        'useEffect mount',224      ]);225    } else {226      expect(Scheduler).toHaveYielded([227        'useLayoutEffect mount',228        'useEffect mount',229      ]);230    }231    act(() => {232      renderer.update(<App text={'update'} />);233    });234    expect(Scheduler).toHaveYielded([235      'useLayoutEffect mount',236      'useEffect mount',237    ]);238    act(() => {239      renderer.unmount();240    });241    expect(Scheduler).toHaveYielded([]);242  });243  it('passes the right context to class component lifecycles', () => {244    class App extends React.PureComponent {245      test() {}246      componentDidMount() {247        this.test();248        Scheduler.unstable_yieldValue('componentDidMount');249      }250      componentDidUpdate() {251        this.test();252        Scheduler.unstable_yieldValue('componentDidUpdate');253      }254      componentWillUnmount() {255        this.test();256        Scheduler.unstable_yieldValue('componentWillUnmount');257      }258      render() {259        return null;260      }261    }262    act(() => {263      ReactTestRenderer.create(<App />, {unstable_isConcurrent: true});264    });265    if (supportsDoubleInvokeEffects()) {266      expect(Scheduler).toHaveYielded([267        'componentDidMount',268        'componentWillUnmount',269        'componentDidMount',270      ]);271    } else {272      expect(Scheduler).toHaveYielded(['componentDidMount']);273    }274  });275  it('double invoking works for class components', () => {276    class App extends React.PureComponent {277      componentDidMount() {278        Scheduler.unstable_yieldValue('componentDidMount');279      }280      componentDidUpdate() {281        Scheduler.unstable_yieldValue('componentDidUpdate');282      }283      componentWillUnmount() {284        Scheduler.unstable_yieldValue('componentWillUnmount');285      }286      render() {287        return this.props.text;288      }289    }290    let renderer;291    act(() => {292      renderer = ReactTestRenderer.create(<App text={'mount'} />, {293        unstable_isConcurrent: true,294      });295    });296    if (supportsDoubleInvokeEffects()) {297      expect(Scheduler).toHaveYielded([298        'componentDidMount',299        'componentWillUnmount',300        'componentDidMount',301      ]);302    } else {303      expect(Scheduler).toHaveYielded(['componentDidMount']);304    }305    act(() => {306      renderer.update(<App text={'update'} />);307    });308    expect(Scheduler).toHaveYielded(['componentDidUpdate']);309    act(() => {310      renderer.unmount();311    });312    expect(Scheduler).toHaveYielded(['componentWillUnmount']);313  });314  it('should not double invoke class lifecycles in legacy mode', () => {315    class App extends React.PureComponent {316      componentDidMount() {317        Scheduler.unstable_yieldValue('componentDidMount');318      }319      componentDidUpdate() {320        Scheduler.unstable_yieldValue('componentDidUpdate');321      }322      componentWillUnmount() {323        Scheduler.unstable_yieldValue('componentWillUnmount');324      }325      render() {326        return this.props.text;327      }328    }329    act(() => {330      ReactTestRenderer.create(<App text={'mount'} />);331    });332    expect(Scheduler).toHaveYielded(['componentDidMount']);333  });334  it('double flushing passive effects only results in one double invoke', () => {335    function App({text}) {336      const [state, setState] = React.useState(0);337      React.useEffect(() => {338        if (state !== 1) {339          setState(1);340        }341        Scheduler.unstable_yieldValue('useEffect mount');342        return () => Scheduler.unstable_yieldValue('useEffect unmount');343      });344      React.useLayoutEffect(() => {345        Scheduler.unstable_yieldValue('useLayoutEffect mount');346        return () => Scheduler.unstable_yieldValue('useLayoutEffect unmount');347      });348      Scheduler.unstable_yieldValue(text);349      return text;350    }351    act(() => {352      ReactTestRenderer.create(<App text={'mount'} />, {353        unstable_isConcurrent: true,354      });355    });356    if (supportsDoubleInvokeEffects()) {357      expect(Scheduler).toHaveYielded([358        'mount',359        'useLayoutEffect mount',360        'useEffect mount',361        'useLayoutEffect unmount',362        'useEffect unmount',363        'useLayoutEffect mount',364        'useEffect mount',365        'mount',366        'useLayoutEffect unmount',367        'useLayoutEffect mount',368        'useEffect unmount',369        'useEffect mount',370      ]);371    } else {372      expect(Scheduler).toHaveYielded([373        'mount',374        'useLayoutEffect mount',375        'useEffect mount',376        'mount',377        'useLayoutEffect unmount',378        'useLayoutEffect mount',379        'useEffect unmount',380        'useEffect mount',381      ]);382    }383  });384  it('newly mounted components after initial mount get double invoked', () => {385    let _setShowChild;386    function Child() {387      React.useEffect(() => {388        Scheduler.unstable_yieldValue('Child useEffect mount');389        return () => Scheduler.unstable_yieldValue('Child useEffect unmount');390      });391      React.useLayoutEffect(() => {392        Scheduler.unstable_yieldValue('Child useLayoutEffect mount');393        return () =>394          Scheduler.unstable_yieldValue('Child useLayoutEffect unmount');395      });396      return null;397    }398    function App() {399      const [showChild, setShowChild] = React.useState(false);400      _setShowChild = setShowChild;401      React.useEffect(() => {402        Scheduler.unstable_yieldValue('App useEffect mount');403        return () => Scheduler.unstable_yieldValue('App useEffect unmount');404      });405      React.useLayoutEffect(() => {406        Scheduler.unstable_yieldValue('App useLayoutEffect mount');407        return () =>408          Scheduler.unstable_yieldValue('App useLayoutEffect unmount');409      });410      return showChild && <Child />;411    }412    act(() => {413      ReactTestRenderer.create(<App />, {unstable_isConcurrent: true});414    });415    if (supportsDoubleInvokeEffects()) {416      expect(Scheduler).toHaveYielded([417        'App useLayoutEffect mount',418        'App useEffect mount',419        'App useLayoutEffect unmount',420        'App useEffect unmount',421        'App useLayoutEffect mount',422        'App useEffect mount',423      ]);424    } else {425      expect(Scheduler).toHaveYielded([426        'App useLayoutEffect mount',427        'App useEffect mount',428      ]);429    }430    act(() => {431      _setShowChild(true);432    });433    if (supportsDoubleInvokeEffects()) {434      expect(Scheduler).toHaveYielded([435        'App useLayoutEffect unmount',436        'Child useLayoutEffect mount',437        'App useLayoutEffect mount',438        'App useEffect unmount',439        'Child useEffect mount',440        'App useEffect mount',441        'Child useLayoutEffect unmount',442        'Child useEffect unmount',443        'Child useLayoutEffect mount',444        'Child useEffect mount',445      ]);446    } else {447      expect(Scheduler).toHaveYielded([448        'App useLayoutEffect unmount',449        'Child useLayoutEffect mount',450        'App useLayoutEffect mount',451        'App useEffect unmount',452        'Child useEffect mount',453        'App useEffect mount',454      ]);455    }456  });457  it('classes and functions are double invoked together correctly', () => {458    class ClassChild extends React.PureComponent {459      componentDidMount() {460        Scheduler.unstable_yieldValue('componentDidMount');461      }462      componentWillUnmount() {463        Scheduler.unstable_yieldValue('componentWillUnmount');464      }465      render() {466        return this.props.text;467      }468    }469    function FunctionChild({text}) {470      React.useEffect(() => {471        Scheduler.unstable_yieldValue('useEffect mount');472        return () => Scheduler.unstable_yieldValue('useEffect unmount');473      });474      React.useLayoutEffect(() => {475        Scheduler.unstable_yieldValue('useLayoutEffect mount');476        return () => Scheduler.unstable_yieldValue('useLayoutEffect unmount');477      });478      return text;479    }480    function App({text}) {481      return (482        <>483          <ClassChild text={text} />484          <FunctionChild text={text} />485        </>486      );487    }488    let renderer;489    act(() => {490      renderer = ReactTestRenderer.create(<App text={'mount'} />, {491        unstable_isConcurrent: true,492      });493    });494    if (supportsDoubleInvokeEffects()) {495      expect(Scheduler).toHaveYielded([496        'componentDidMount',497        'useLayoutEffect mount',498        'useEffect mount',499        'componentWillUnmount',500        'useLayoutEffect unmount',501        'useEffect unmount',502        'componentDidMount',503        'useLayoutEffect mount',504        'useEffect mount',505      ]);506    } else {507      expect(Scheduler).toHaveYielded([508        'componentDidMount',509        'useLayoutEffect mount',510        'useEffect mount',511      ]);512    }513    act(() => {514      renderer.update(<App text={'mount'} />);515    });516    expect(Scheduler).toHaveYielded([517      'useLayoutEffect unmount',518      'useLayoutEffect mount',519      'useEffect unmount',520      'useEffect mount',521    ]);522    act(() => {523      renderer.unmount();524    });525    expect(Scheduler).toHaveYielded([526      'componentWillUnmount',527      'useLayoutEffect unmount',528      'useEffect unmount',529    ]);530  });...ReactDoubleInvokeEvents-test.internal.js
Source:ReactDoubleInvokeEvents-test.internal.js  
...84      'useEffect unmount',85      'useEffect mount',86    ]);87    act(() => {88      renderer.unmount();89    });90    expect(Scheduler).toHaveYielded([91      'useLayoutEffect unmount',92      'useEffect unmount',93    ]);94  });95  it('multiple effects are double invoked in the right order (all mounted, all unmounted, all remounted)', () => {96    function App({text}) {97      React.useEffect(() => {98        Scheduler.unstable_yieldValue('useEffect One mount');99        return () => Scheduler.unstable_yieldValue('useEffect One unmount');100      });101      React.useEffect(() => {102        Scheduler.unstable_yieldValue('useEffect Two mount');103        return () => Scheduler.unstable_yieldValue('useEffect Two unmount');104      });105      return text;106    }107    let renderer;108    act(() => {109      renderer = ReactTestRenderer.create(<App text={'mount'} />, {110        unstable_isConcurrent: true,111      });112    });113    if (__DEV__ && __VARIANT__) {114      expect(Scheduler).toHaveYielded([115        'useEffect One mount',116        'useEffect Two mount',117        'useEffect One unmount',118        'useEffect Two unmount',119        'useEffect One mount',120        'useEffect Two mount',121      ]);122    } else {123      expect(Scheduler).toHaveYielded([124        'useEffect One mount',125        'useEffect Two mount',126      ]);127    }128    act(() => {129      renderer.update(<App text={'update'} />);130    });131    expect(Scheduler).toHaveYielded([132      'useEffect One unmount',133      'useEffect Two unmount',134      'useEffect One mount',135      'useEffect Two mount',136    ]);137    act(() => {138      renderer.unmount(null);139    });140    expect(Scheduler).toHaveYielded([141      'useEffect One unmount',142      'useEffect Two unmount',143    ]);144  });145  it('multiple layout effects are double invoked in the right order (all mounted, all unmounted, all remounted)', () => {146    function App({text}) {147      React.useLayoutEffect(() => {148        Scheduler.unstable_yieldValue('useLayoutEffect One mount');149        return () =>150          Scheduler.unstable_yieldValue('useLayoutEffect One unmount');151      });152      React.useLayoutEffect(() => {153        Scheduler.unstable_yieldValue('useLayoutEffect Two mount');154        return () =>155          Scheduler.unstable_yieldValue('useLayoutEffect Two unmount');156      });157      return text;158    }159    let renderer;160    act(() => {161      renderer = ReactTestRenderer.create(<App text={'mount'} />, {162        unstable_isConcurrent: true,163      });164    });165    if (__DEV__ && __VARIANT__) {166      expect(Scheduler).toHaveYielded([167        'useLayoutEffect One mount',168        'useLayoutEffect Two mount',169        'useLayoutEffect One unmount',170        'useLayoutEffect Two unmount',171        'useLayoutEffect One mount',172        'useLayoutEffect Two mount',173      ]);174    } else {175      expect(Scheduler).toHaveYielded([176        'useLayoutEffect One mount',177        'useLayoutEffect Two mount',178      ]);179    }180    act(() => {181      renderer.update(<App text={'update'} />);182    });183    expect(Scheduler).toHaveYielded([184      'useLayoutEffect One unmount',185      'useLayoutEffect Two unmount',186      'useLayoutEffect One mount',187      'useLayoutEffect Two mount',188    ]);189    act(() => {190      renderer.unmount();191    });192    expect(Scheduler).toHaveYielded([193      'useLayoutEffect One unmount',194      'useLayoutEffect Two unmount',195    ]);196  });197  it('useEffect and useLayoutEffect is called twice when there is no unmount', () => {198    function App({text}) {199      React.useEffect(() => {200        Scheduler.unstable_yieldValue('useEffect mount');201      });202      React.useLayoutEffect(() => {203        Scheduler.unstable_yieldValue('useLayoutEffect mount');204      });205      return text;206    }207    let renderer;208    act(() => {209      renderer = ReactTestRenderer.create(<App text={'mount'} />, {210        unstable_isConcurrent: true,211      });212    });213    if (__DEV__ && __VARIANT__) {214      expect(Scheduler).toHaveYielded([215        'useLayoutEffect mount',216        'useEffect mount',217        'useLayoutEffect mount',218        'useEffect mount',219      ]);220    } else {221      expect(Scheduler).toHaveYielded([222        'useLayoutEffect mount',223        'useEffect mount',224      ]);225    }226    act(() => {227      renderer.update(<App text={'update'} />);228    });229    expect(Scheduler).toHaveYielded([230      'useLayoutEffect mount',231      'useEffect mount',232    ]);233    act(() => {234      renderer.unmount();235    });236    expect(Scheduler).toHaveYielded([]);237  });238  it('passes the right context to class component lifecycles', () => {239    class App extends React.PureComponent {240      test() {}241      componentDidMount() {242        this.test();243        Scheduler.unstable_yieldValue('componentDidMount');244      }245      componentDidUpdate() {246        this.test();247        Scheduler.unstable_yieldValue('componentDidUpdate');248      }249      componentWillUnmount() {250        this.test();251        Scheduler.unstable_yieldValue('componentWillUnmount');252      }253      render() {254        return null;255      }256    }257    act(() => {258      ReactTestRenderer.create(<App />, {unstable_isConcurrent: true});259    });260    if (__DEV__ && __VARIANT__) {261      expect(Scheduler).toHaveYielded([262        'componentDidMount',263        'componentWillUnmount',264        'componentDidMount',265      ]);266    } else {267      expect(Scheduler).toHaveYielded(['componentDidMount']);268    }269  });270  it('double invoking works for class components', () => {271    class App extends React.PureComponent {272      componentDidMount() {273        Scheduler.unstable_yieldValue('componentDidMount');274      }275      componentDidUpdate() {276        Scheduler.unstable_yieldValue('componentDidUpdate');277      }278      componentWillUnmount() {279        Scheduler.unstable_yieldValue('componentWillUnmount');280      }281      render() {282        return this.props.text;283      }284    }285    let renderer;286    act(() => {287      renderer = ReactTestRenderer.create(<App text={'mount'} />, {288        unstable_isConcurrent: true,289      });290    });291    if (__DEV__ && __VARIANT__) {292      expect(Scheduler).toHaveYielded([293        'componentDidMount',294        'componentWillUnmount',295        'componentDidMount',296      ]);297    } else {298      expect(Scheduler).toHaveYielded(['componentDidMount']);299    }300    act(() => {301      renderer.update(<App text={'update'} />);302    });303    expect(Scheduler).toHaveYielded(['componentDidUpdate']);304    act(() => {305      renderer.unmount();306    });307    expect(Scheduler).toHaveYielded(['componentWillUnmount']);308  });309  it('should not double invoke class lifecycles in legacy mode', () => {310    class App extends React.PureComponent {311      componentDidMount() {312        Scheduler.unstable_yieldValue('componentDidMount');313      }314      componentDidUpdate() {315        Scheduler.unstable_yieldValue('componentDidUpdate');316      }317      componentWillUnmount() {318        Scheduler.unstable_yieldValue('componentWillUnmount');319      }320      render() {321        return this.props.text;322      }323    }324    act(() => {325      ReactTestRenderer.create(<App text={'mount'} />);326    });327    expect(Scheduler).toHaveYielded(['componentDidMount']);328  });329  it('double flushing passive effects only results in one double invoke', () => {330    function App({text}) {331      const [state, setState] = React.useState(0);332      React.useEffect(() => {333        if (state !== 1) {334          setState(1);335        }336        Scheduler.unstable_yieldValue('useEffect mount');337        return () => Scheduler.unstable_yieldValue('useEffect unmount');338      });339      React.useLayoutEffect(() => {340        Scheduler.unstable_yieldValue('useLayoutEffect mount');341        return () => Scheduler.unstable_yieldValue('useLayoutEffect unmount');342      });343      Scheduler.unstable_yieldValue(text);344      return text;345    }346    act(() => {347      ReactTestRenderer.create(<App text={'mount'} />, {348        unstable_isConcurrent: true,349      });350    });351    if (__DEV__ && __VARIANT__) {352      expect(Scheduler).toHaveYielded([353        'mount',354        'useLayoutEffect mount',355        'useEffect mount',356        'useLayoutEffect unmount',357        'useEffect unmount',358        'useLayoutEffect mount',359        'useEffect mount',360        'mount',361        'useLayoutEffect unmount',362        'useLayoutEffect mount',363        'useEffect unmount',364        'useEffect mount',365      ]);366    } else {367      expect(Scheduler).toHaveYielded([368        'mount',369        'useLayoutEffect mount',370        'useEffect mount',371        'mount',372        'useLayoutEffect unmount',373        'useLayoutEffect mount',374        'useEffect unmount',375        'useEffect mount',376      ]);377    }378  });379  it('newly mounted components after initial mount get double invoked', () => {380    let _setShowChild;381    function Child() {382      React.useEffect(() => {383        Scheduler.unstable_yieldValue('Child useEffect mount');384        return () => Scheduler.unstable_yieldValue('Child useEffect unmount');385      });386      React.useLayoutEffect(() => {387        Scheduler.unstable_yieldValue('Child useLayoutEffect mount');388        return () =>389          Scheduler.unstable_yieldValue('Child useLayoutEffect unmount');390      });391      return null;392    }393    function App() {394      const [showChild, setShowChild] = React.useState(false);395      _setShowChild = setShowChild;396      React.useEffect(() => {397        Scheduler.unstable_yieldValue('App useEffect mount');398        return () => Scheduler.unstable_yieldValue('App useEffect unmount');399      });400      React.useLayoutEffect(() => {401        Scheduler.unstable_yieldValue('App useLayoutEffect mount');402        return () =>403          Scheduler.unstable_yieldValue('App useLayoutEffect unmount');404      });405      return showChild && <Child />;406    }407    act(() => {408      ReactTestRenderer.create(<App />, {unstable_isConcurrent: true});409    });410    if (__DEV__ && __VARIANT__) {411      expect(Scheduler).toHaveYielded([412        'App useLayoutEffect mount',413        'App useEffect mount',414        'App useLayoutEffect unmount',415        'App useEffect unmount',416        'App useLayoutEffect mount',417        'App useEffect mount',418      ]);419    } else {420      expect(Scheduler).toHaveYielded([421        'App useLayoutEffect mount',422        'App useEffect mount',423      ]);424    }425    act(() => {426      _setShowChild(true);427    });428    if (__DEV__ && __VARIANT__) {429      expect(Scheduler).toHaveYielded([430        'App useLayoutEffect unmount',431        'Child useLayoutEffect mount',432        'App useLayoutEffect mount',433        'App useEffect unmount',434        'Child useEffect mount',435        'App useEffect mount',436        'Child useLayoutEffect unmount',437        'Child useEffect unmount',438        'Child useLayoutEffect mount',439        'Child useEffect mount',440      ]);441    } else {442      expect(Scheduler).toHaveYielded([443        'App useLayoutEffect unmount',444        'Child useLayoutEffect mount',445        'App useLayoutEffect mount',446        'App useEffect unmount',447        'Child useEffect mount',448        'App useEffect mount',449      ]);450    }451  });452  it('classes and functions are double invoked together correctly', () => {453    class ClassChild extends React.PureComponent {454      componentDidMount() {455        Scheduler.unstable_yieldValue('componentDidMount');456      }457      componentWillUnmount() {458        Scheduler.unstable_yieldValue('componentWillUnmount');459      }460      render() {461        return this.props.text;462      }463    }464    function FunctionChild({text}) {465      React.useEffect(() => {466        Scheduler.unstable_yieldValue('useEffect mount');467        return () => Scheduler.unstable_yieldValue('useEffect unmount');468      });469      React.useLayoutEffect(() => {470        Scheduler.unstable_yieldValue('useLayoutEffect mount');471        return () => Scheduler.unstable_yieldValue('useLayoutEffect unmount');472      });473      return text;474    }475    function App({text}) {476      return (477        <>478          <ClassChild text={text} />479          <FunctionChild text={text} />480        </>481      );482    }483    let renderer;484    act(() => {485      renderer = ReactTestRenderer.create(<App text={'mount'} />, {486        unstable_isConcurrent: true,487      });488    });489    if (__DEV__ && __VARIANT__) {490      expect(Scheduler).toHaveYielded([491        'componentDidMount',492        'useLayoutEffect mount',493        'useEffect mount',494        'componentWillUnmount',495        'useLayoutEffect unmount',496        'useEffect unmount',497        'componentDidMount',498        'useLayoutEffect mount',499        'useEffect mount',500      ]);501    } else {502      expect(Scheduler).toHaveYielded([503        'componentDidMount',504        'useLayoutEffect mount',505        'useEffect mount',506      ]);507    }508    act(() => {509      renderer.update(<App text={'mount'} />);510    });511    expect(Scheduler).toHaveYielded([512      'useLayoutEffect unmount',513      'useLayoutEffect mount',514      'useEffect unmount',515      'useEffect mount',516    ]);517    act(() => {518      renderer.unmount();519    });520    expect(Scheduler).toHaveYielded([521      'componentWillUnmount',522      'useLayoutEffect unmount',523      'useEffect unmount',524    ]);525  });...ReactMultiChild-test.js
Source:ReactMultiChild-test.js  
1/**2 * Copyright 2013-2015, Facebook, Inc.3 * All rights reserved.4 *5 * This source code is licensed under the BSD-style license found in the6 * LICENSE file in the root directory of this source tree. An additional grant7 * of patent rights can be found in the PATENTS file in the same directory.8 *9 * @emails react-core10 */11'use strict';12var mocks = require('mocks');13describe('ReactMultiChild', function() {14  var React;15  beforeEach(function() {16    require('mock-modules').dumpCache();17    React = require('React');18  });19  describe('reconciliation', function() {20    it('should update children when possible', function() {21      var container = document.createElement('div');22      var mockMount = mocks.getMockFunction();23      var mockUpdate = mocks.getMockFunction();24      var mockUnmount = mocks.getMockFunction();25      var MockComponent = React.createClass({26        componentDidMount: mockMount,27        componentDidUpdate: mockUpdate,28        componentWillUnmount: mockUnmount,29        render: function() {30          return <span />;31        }32      });33      expect(mockMount.mock.calls.length).toBe(0);34      expect(mockUpdate.mock.calls.length).toBe(0);35      expect(mockUnmount.mock.calls.length).toBe(0);36      React.render(<div><MockComponent /></div>, container);37      expect(mockMount.mock.calls.length).toBe(1);38      expect(mockUpdate.mock.calls.length).toBe(0);39      expect(mockUnmount.mock.calls.length).toBe(0);40      React.render(<div><MockComponent /></div>, container);41      expect(mockMount.mock.calls.length).toBe(1);42      expect(mockUpdate.mock.calls.length).toBe(1);43      expect(mockUnmount.mock.calls.length).toBe(0);44    });45    it('should replace children with different constructors', function() {46      var container = document.createElement('div');47      var mockMount = mocks.getMockFunction();48      var mockUnmount = mocks.getMockFunction();49      var MockComponent = React.createClass({50        componentDidMount: mockMount,51        componentWillUnmount: mockUnmount,52        render: function() {53          return <span />;54        }55      });56      expect(mockMount.mock.calls.length).toBe(0);57      expect(mockUnmount.mock.calls.length).toBe(0);58      React.render(<div><MockComponent /></div>, container);59      expect(mockMount.mock.calls.length).toBe(1);60      expect(mockUnmount.mock.calls.length).toBe(0);61      React.render(<div><span /></div>, container);62      expect(mockMount.mock.calls.length).toBe(1);63      expect(mockUnmount.mock.calls.length).toBe(1);64    });65    it('should replace children with different owners', function() {66      var container = document.createElement('div');67      var mockMount = mocks.getMockFunction();68      var mockUnmount = mocks.getMockFunction();69      var MockComponent = React.createClass({70        componentDidMount: mockMount,71        componentWillUnmount: mockUnmount,72        render: function() {73          return <span />;74        }75      });76      var WrapperComponent = React.createClass({77        render: function() {78          return this.props.children || <MockComponent />;79        }80      });81      expect(mockMount.mock.calls.length).toBe(0);82      expect(mockUnmount.mock.calls.length).toBe(0);83      React.render(<WrapperComponent />, container);84      expect(mockMount.mock.calls.length).toBe(1);85      expect(mockUnmount.mock.calls.length).toBe(0);86      React.render(87        <WrapperComponent><MockComponent /></WrapperComponent>,88        container89      );90      expect(mockMount.mock.calls.length).toBe(2);91      expect(mockUnmount.mock.calls.length).toBe(1);92    });93    it('should replace children with different keys', function() {94      var container = document.createElement('div');95      var mockMount = mocks.getMockFunction();96      var mockUnmount = mocks.getMockFunction();97      var MockComponent = React.createClass({98        componentDidMount: mockMount,99        componentWillUnmount: mockUnmount,100        render: function() {101          return <span />;102        }103      });104      expect(mockMount.mock.calls.length).toBe(0);105      expect(mockUnmount.mock.calls.length).toBe(0);106      React.render(<div><MockComponent key="A" /></div>, container);107      expect(mockMount.mock.calls.length).toBe(1);108      expect(mockUnmount.mock.calls.length).toBe(0);109      React.render(<div><MockComponent key="B" /></div>, container);110      expect(mockMount.mock.calls.length).toBe(2);111      expect(mockUnmount.mock.calls.length).toBe(1);112    });113  });114  describe('innerHTML', function() {115    var setInnerHTML;116    // Only run this suite if `Element.prototype.innerHTML` can be spied on.117    var innerHTMLDescriptor = Object.getOwnPropertyDescriptor(118      Element.prototype,119      'innerHTML'120    );121    if (!innerHTMLDescriptor) {122      return;123    }124    beforeEach(function() {125      Object.defineProperty(Element.prototype, 'innerHTML', {126        set: setInnerHTML = jasmine.createSpy().andCallFake(127          innerHTMLDescriptor.set128        )129      });130    });131    it('should only set `innerHTML` once on update', function() {132      var container = document.createElement('div');133      React.render(134        <div>135          <p><span /></p>136          <p><span /></p>137          <p><span /></p>138        </div>,139        container140      );141      // Warm the cache used by `getMarkupWrap`.142      React.render(143        <div>144          <p><span /><span /></p>145          <p><span /><span /></p>146          <p><span /><span /></p>147        </div>,148        container149      );150      expect(setInnerHTML).toHaveBeenCalled();151      var callCountOnMount = setInnerHTML.calls.length;152      React.render(153        <div>154          <p><span /><span /><span /></p>155          <p><span /><span /><span /></p>156          <p><span /><span /><span /></p>157        </div>,158        container159      );160      expect(setInnerHTML.calls.length).toBe(callCountOnMount + 1);161    });162  });...componentWillUnmount.test.js
Source:componentWillUnmount.test.js  
1import { createElement, render, Component } from 'preact';2import { setupScratch, teardown } from '../../_util/helpers';3/** @jsx createElement */4describe('Lifecycle methods', () => {5	/** @type {HTMLDivElement} */6	let scratch;7	beforeEach(() => {8		scratch = setupScratch();9	});10	afterEach(() => {11		teardown(scratch);12	});13	describe('top-level componentWillUnmount', () => {14		it('should invoke componentWillUnmount for top-level components', () => {15			class Foo extends Component {16				componentDidMount() {}17				componentWillUnmount() {}18				render() {19					return 'foo';20				}21			}22			class Bar extends Component {23				componentDidMount() {}24				componentWillUnmount() {}25				render() {26					return 'bar';27				}28			}29			sinon.spy(Foo.prototype, 'componentDidMount');30			sinon.spy(Foo.prototype, 'componentWillUnmount');31			sinon.spy(Foo.prototype, 'render');32			sinon.spy(Bar.prototype, 'componentDidMount');33			sinon.spy(Bar.prototype, 'componentWillUnmount');34			sinon.spy(Bar.prototype, 'render');35			render(<Foo />, scratch);36			expect(Foo.prototype.componentDidMount, 'initial render').to.have.been37				.calledOnce;38			render(<Bar />, scratch);39			expect(Foo.prototype.componentWillUnmount, 'when replaced').to.have.been40				.calledOnce;41			expect(Bar.prototype.componentDidMount, 'when replaced').to.have.been42				.calledOnce;43			render(<div />, scratch);44			expect(Bar.prototype.componentWillUnmount, 'when removed').to.have.been45				.calledOnce;46		});47		it('should only remove dom after componentWillUnmount was called', () => {48			class Foo extends Component {49				componentWillUnmount() {50					expect(document.getElementById('foo')).to.not.equal(null);51				}52				render() {53					return <div id="foo" />;54				}55			}56			render(<Foo />, scratch);57			render(null, scratch);58		});59	});...Using AI Code Generation
1import React from 'react';2import { render, unmountComponentAtNode } from 'react-dom';3import { act } from 'react-dom/test-utils';4import { useFetch } from './useFetch';5let container = null;6beforeEach(() => {7  container = document.createElement('div');8  document.body.appendChild(container);9});10afterEach(() => {11  unmountComponentAtNode(container);12  container.remove();13  container = null;14});15it('should fetch and display data', async () => {16  const fakeData = { name: 'test' };17  jest.spyOn(global, 'fetch').mockImplementation(() =>18    Promise.resolve({19      json: () => Promise.resolve(fakeData),20    })21  );22  await act(async () => {23  });24  expect(container.querySelector('summary').textContent).toBe(fakeData.name);25  expect(container.querySelector('strong').textContent).toBe(fakeData.name);26  global.fetch.mockRestore();27});28import { useState, useEffect } from 'react';29export function useFetch(url) {30  const [data, setData] = useState(null);31  useEffect(() => {32    async function fetchData() {33      const response = await fetch(url);34      const data = await response.json();35      setData(data);36    }37    fetchData();38  }, [url]);39  return data;40}41import { renderHook, act } from '@testing-library/react-hooks';42import { useFetch } from './useFetch';43it('should fetch and display data', async () => {44  const fakeData = { name: 'test' };45  jest.spyOn(global, 'fetch').mockImplementation(() =>46    Promise.resolve({47      json: () => Promise.resolve(fakeData),48    })49  );50  await waitForNextUpdate();51  expect(result.current).toBe(fakeData);52  global.fetch.mockRestore();53});54import { useState, useEffect } from 'react';55export function useFetch(url) {56  const [data, setData] = useState(null);57  useEffect(() => {58    async function fetchData() {59      const response = await fetch(url);60      const data = await response.json();61      setData(data);62    }63    fetchData();64  }, [url]);Using AI Code Generation
1import { unmount } from '@testing-library/react-hooks'2import { renderHook } from '@testing-library/react-hooks'3import { act } from '@testing-library/react-hooks'4import { useCount } from './useCount'5describe('useCount', () => {6  it('should increment the count', () => {7    const { result } = renderHook(() => useCount())8    const { count } = result.current9    expect(count).toBe(0)10    act(() => result.current.increment())11    const { count: newCount } = result.current12    expect(newCount).toBe(1)13  })14  it('should decrement the count', () => {15    const { result } = renderHook(() => useCount())16    const { count } = result.current17    expect(count).toBe(0)18    act(() => result.current.decrement())19    const { count: newCount } = result.current20    expect(newCount).toBe(-1)21  })22  it('should reset the count', () => {23    const { result } = renderHook(() => useCount())24    const { count } = result.current25    expect(count).toBe(0)26    act(() => result.current.decrement())Using AI Code Generation
1import { renderHook, act } from '@testing-library/react-hooks'2import { useCounter } from './useCounter'3describe('useCounter', () => {4  it('should increment the counter', () => {5    const { result } = renderHook(() => useCounter())6    act(() => {7      result.current.increment()8    })9    expect(result.current.count).toBe(1)10  })11  it('should decrement the counter', () => {12    const { result } = renderHook(() => useCounter())13    act(() => {14      result.current.decrement()15    })16    expect(result.current.count).toBe(-1)17  })18})19import { useState } from 'react'20export const useCounter = () => {21  const [count, setCount] = useState(0)22  const increment = () => {23    setCount(count + 1)24  }25  const decrement = () => {26    setCount(count - 1)27  }28  return { count, increment, decrement }29}30import { useState } from 'react'31export const useCounter = () => {32  const [count, setCount] = useState<number>(0)33  const increment = () => {34    setCount(count + 1)35  }36  const decrement = () => {37    setCount(count - 1)38  }39  return { count, increment, decrement }40}41import { useState } from 'react'42export const useCounter = () => {43  const [count, setCount] = useState<number>(0)44  const increment = () => {45    setCount(count + 1)46  }47  const decrement = () => {48    setCount(count - 1)49  }50  return { count, increment, decrement }51}52export declare const useCounter: () => {53  increment: () => void54  decrement: () => void55}56import { useState } from 'react'57export const useCounter = () => {58  const [count, setCount] = useState(0)59  const increment = () => {60    setCount(count + 1)61  }62  const decrement = () => {63    setCount(count - 1)64  }65  return { count, increment, decrement }66}67import {Using AI Code Generation
1import { unmount } from '@testing-library/react-hooks'2afterEach(() => {3  unmount()4})5afterEach(() => {6  unmount()7})8import { unmount } from '@testing-library/react-hooks'9afterEach(() => {10  unmount()11})12afterEach(() => {13  unmount()14})15import { unmount } from '@testing-library/react-hooks'16afterEach(() => {17  unmount()18})19afterEach(() => {20  unmount()21})22import { unmount } from '@testing-library/react-hooks'23afterEach(() => {24  unmount()25})26afterEach(() => {27  unmount()28})29import { unmount } from '@testing-library/react-hooks'30afterEach(() => {31  unmount()32})33afterEach(() => {34  unmount()35})36import { unmount } from '@testing-library/react-hooks'37afterEach(() => {38  unmount()39})40afterEach(() => {41  unmount()42})43import { unmount } from '@testing-library/react-hooks'44afterEach(() => {Using AI Code Generation
1import { unmount } from '@testing-library/react-hooks'2import { renderHook } from '@testing-library/react-hooks'3import { renderHook } from '@testing-library/react-hooks'4import { renderHook } from '@testing-library/react-hooks'5import { renderHook } from '@testing-library/react-hooks'6import { renderHook } from '@testing-library/react-hooks'7import { renderHook } from '@testing-library/react-hooks'8import { renderHook } from '@testing-library/react-hooks'9import { renderHook } from '@testing-library/react-hooks'10import { renderHook } from '@testing-library/react-hooks'11import { renderHook } from '@testing-library/react-hooks'12import { renderHook } from '@testing-library/react-hooks'13import { renderHook } from '@testing-library/react-hooks'14import { renderHook } from '@testing-library/react-hooks'15import { renderHook } from '@testing-library/react-hooks'16import { renderHook } from '@testing-library/react-hooks'17import { renderHook } from '@testing-library/react-hooks'18import { renderHook } from '@testing-library/react-hooks'19import { renderHook } from '@testing-library/react-hooks'20import { renderHook } from '@testing-library/react-hooks'21import { renderHook } from '@testing-library/react-hooks'Using AI Code Generation
1import { unmount } from '@testing-library/react-hooks';2import { renderHook, act } from '@testing-library/react-hooks';3import { useCounter } from './useCounter';4describe('useCounter', () => {5  it('should increment counter', () => {6    const { result } = renderHook(() => useCounter());7    act(() => {8      result.current.increment();9    });10    expect(result.current.count).toBe(1);11  });12  it('should decrement counter', () => {13    const { result } = renderHook(() => useCounter());14    act(() => {15      result.current.decrement();16    });17    expect(result.current.count).toBe(-1);18  });19  it('should reset counter', () => {20    const { result } = renderHook(() => useCounter());21    act(() => {22      result.current.increment();23      result.current.increment();24      result.current.reset();25    });26    expect(result.current.count).toBe(0);27  });28  afterEach(() => {29    unmount();30  });31});Using AI Code Generation
1import { unmount } from '@testing-library/react-hooks';2afterEach(() => {3  unmount();4});5it('should return the initial value', () => {6  const { result } = renderHook(() => useCounter());7  expect(result.current.count).toBe(0);8});9it('should increment the count', () => {10  const { result } = renderHook(() => useCounter());11  act(() => {12    result.current.increment();13  });14  expect(result.current.count).toBe(1);15});16it('should decrement the count', () => {17  const { result } = renderHook(() => useCounter());18  act(() => {19    result.current.decrement();20  });21  expect(result.current.count).toBe(-1);22});23it('should reset the count', () => {24  const { result } = renderHook(() => useCounter());25  act(() => {26    result.current.increment();27    result.current.reset();28  });29  expect(result.current.count).toBe(0);30});31it('should set the count', () => {32  const { result } = renderHook(() => useCounter());33  act(() => {34    result.current.setCount(5);35  });36  expect(result.current.count).toBe(5);37});38it('should set the count to 0 if the value passed is undefined', () => {39  const { result } = renderHook(() => useCounter());40  act(() => {41    result.current.setCount(undefined);42  });43  expect(result.current.count).toBe(0);44});45it('should set the count to 0 if the value passed is null', () => {46  const { result } = renderHook(() => useCounter());47  act(() => {48    result.current.setCount(null);49  });50  expect(result.current.count).toBe(0);51});52it('should set the count to 0 if the value passed is NaN', () => {53  const { result } = renderHook(() => useCounter());54  act(() => {55    result.current.setCount(NaN);56  });57  expect(result.current.count).toBe(0);58});59it('should set theLearn to execute automation testing from scratch with LambdaTest Learning Hub. Right from setting up the prerequisites to run your first automation test, to following best practices and diving deeper into advanced test scenarios. LambdaTest Learning Hubs compile a list of step-by-step guides to help you be proficient with different test automation frameworks i.e. Selenium, Cypress, TestNG etc.
You could also refer to video tutorials over LambdaTest YouTube channel to get step by step demonstration from industry experts.
Get 100 minutes of automation test minutes FREE!!
