Best JavaScript code snippet using storybook-root
ReactErrorBoundaries-test.internal.js
Source:ReactErrorBoundaries-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;14describe('ReactErrorBoundaries', () => {15 let log;16 let BrokenConstructor;17 let BrokenComponentWillMount;18 let BrokenComponentDidMount;19 let BrokenComponentWillReceiveProps;20 let BrokenComponentWillUpdate;21 let BrokenComponentDidUpdate;22 let BrokenComponentWillUnmount;23 let BrokenRenderErrorBoundary;24 let BrokenComponentWillMountErrorBoundary;25 let BrokenComponentDidMountErrorBoundary;26 let BrokenRender;27 let BrokenUseEffect;28 let BrokenUseLayoutEffect;29 let ErrorBoundary;30 let ErrorMessage;31 let NoopErrorBoundary;32 let RetryErrorBoundary;33 let Normal;34 beforeEach(() => {35 jest.useFakeTimers();36 jest.resetModules();37 PropTypes = require('prop-types');38 ReactFeatureFlags = require('shared/ReactFeatureFlags');39 ReactFeatureFlags.replayFailedUnitOfWorkWithInvokeGuardedCallback = false;40 ReactDOM = require('react-dom');41 React = require('react');42 log = [];43 BrokenConstructor = class extends React.Component {44 constructor(props) {45 super(props);46 log.push('BrokenConstructor constructor [!]');47 throw new Error('Hello');48 }49 render() {50 log.push('BrokenConstructor render');51 return <div>{this.props.children}</div>;52 }53 UNSAFE_componentWillMount() {54 log.push('BrokenConstructor componentWillMount');55 }56 componentDidMount() {57 log.push('BrokenConstructor componentDidMount');58 }59 UNSAFE_componentWillReceiveProps() {60 log.push('BrokenConstructor componentWillReceiveProps');61 }62 UNSAFE_componentWillUpdate() {63 log.push('BrokenConstructor componentWillUpdate');64 }65 componentDidUpdate() {66 log.push('BrokenConstructor componentDidUpdate');67 }68 componentWillUnmount() {69 log.push('BrokenConstructor componentWillUnmount');70 }71 };72 BrokenComponentWillMount = class extends React.Component {73 constructor(props) {74 super(props);75 log.push('BrokenComponentWillMount constructor');76 }77 render() {78 log.push('BrokenComponentWillMount render');79 return <div>{this.props.children}</div>;80 }81 UNSAFE_componentWillMount() {82 log.push('BrokenComponentWillMount componentWillMount [!]');83 throw new Error('Hello');84 }85 componentDidMount() {86 log.push('BrokenComponentWillMount componentDidMount');87 }88 UNSAFE_componentWillReceiveProps() {89 log.push('BrokenComponentWillMount componentWillReceiveProps');90 }91 UNSAFE_componentWillUpdate() {92 log.push('BrokenComponentWillMount componentWillUpdate');93 }94 componentDidUpdate() {95 log.push('BrokenComponentWillMount componentDidUpdate');96 }97 componentWillUnmount() {98 log.push('BrokenComponentWillMount componentWillUnmount');99 }100 };101 BrokenComponentDidMount = class extends React.Component {102 constructor(props) {103 super(props);104 log.push('BrokenComponentDidMount constructor');105 }106 render() {107 log.push('BrokenComponentDidMount render');108 return <div>{this.props.children}</div>;109 }110 UNSAFE_componentWillMount() {111 log.push('BrokenComponentDidMount componentWillMount');112 }113 componentDidMount() {114 log.push('BrokenComponentDidMount componentDidMount [!]');115 throw new Error('Hello');116 }117 UNSAFE_componentWillReceiveProps() {118 log.push('BrokenComponentDidMount componentWillReceiveProps');119 }120 UNSAFE_componentWillUpdate() {121 log.push('BrokenComponentDidMount componentWillUpdate');122 }123 componentDidUpdate() {124 log.push('BrokenComponentDidMount componentDidUpdate');125 }126 componentWillUnmount() {127 log.push('BrokenComponentDidMount componentWillUnmount');128 }129 };130 BrokenComponentWillReceiveProps = class extends React.Component {131 constructor(props) {132 super(props);133 log.push('BrokenComponentWillReceiveProps constructor');134 }135 render() {136 log.push('BrokenComponentWillReceiveProps render');137 return <div>{this.props.children}</div>;138 }139 UNSAFE_componentWillMount() {140 log.push('BrokenComponentWillReceiveProps componentWillMount');141 }142 componentDidMount() {143 log.push('BrokenComponentWillReceiveProps componentDidMount');144 }145 UNSAFE_componentWillReceiveProps() {146 log.push(147 'BrokenComponentWillReceiveProps componentWillReceiveProps [!]',148 );149 throw new Error('Hello');150 }151 UNSAFE_componentWillUpdate() {152 log.push('BrokenComponentWillReceiveProps componentWillUpdate');153 }154 componentDidUpdate() {155 log.push('BrokenComponentWillReceiveProps componentDidUpdate');156 }157 componentWillUnmount() {158 log.push('BrokenComponentWillReceiveProps componentWillUnmount');159 }160 };161 BrokenComponentWillUpdate = class extends React.Component {162 constructor(props) {163 super(props);164 log.push('BrokenComponentWillUpdate constructor');165 }166 render() {167 log.push('BrokenComponentWillUpdate render');168 return <div>{this.props.children}</div>;169 }170 UNSAFE_componentWillMount() {171 log.push('BrokenComponentWillUpdate componentWillMount');172 }173 componentDidMount() {174 log.push('BrokenComponentWillUpdate componentDidMount');175 }176 UNSAFE_componentWillReceiveProps() {177 log.push('BrokenComponentWillUpdate componentWillReceiveProps');178 }179 UNSAFE_componentWillUpdate() {180 log.push('BrokenComponentWillUpdate componentWillUpdate [!]');181 throw new Error('Hello');182 }183 componentDidUpdate() {184 log.push('BrokenComponentWillUpdate componentDidUpdate');185 }186 componentWillUnmount() {187 log.push('BrokenComponentWillUpdate componentWillUnmount');188 }189 };190 BrokenComponentDidUpdate = class extends React.Component {191 static defaultProps = {192 errorText: 'Hello',193 };194 constructor(props) {195 super(props);196 log.push('BrokenComponentDidUpdate constructor');197 }198 render() {199 log.push('BrokenComponentDidUpdate render');200 return <div>{this.props.children}</div>;201 }202 UNSAFE_componentWillMount() {203 log.push('BrokenComponentDidUpdate componentWillMount');204 }205 componentDidMount() {206 log.push('BrokenComponentDidUpdate componentDidMount');207 }208 UNSAFE_componentWillReceiveProps() {209 log.push('BrokenComponentDidUpdate componentWillReceiveProps');210 }211 UNSAFE_componentWillUpdate() {212 log.push('BrokenComponentDidUpdate componentWillUpdate');213 }214 componentDidUpdate() {215 log.push('BrokenComponentDidUpdate componentDidUpdate [!]');216 throw new Error(this.props.errorText);217 }218 componentWillUnmount() {219 log.push('BrokenComponentDidUpdate componentWillUnmount');220 }221 };222 BrokenComponentWillUnmount = class extends React.Component {223 static defaultProps = {224 errorText: 'Hello',225 };226 constructor(props) {227 super(props);228 log.push('BrokenComponentWillUnmount constructor');229 }230 render() {231 log.push('BrokenComponentWillUnmount render');232 return <div>{this.props.children}</div>;233 }234 UNSAFE_componentWillMount() {235 log.push('BrokenComponentWillUnmount componentWillMount');236 }237 componentDidMount() {238 log.push('BrokenComponentWillUnmount componentDidMount');239 }240 UNSAFE_componentWillReceiveProps() {241 log.push('BrokenComponentWillUnmount componentWillReceiveProps');242 }243 UNSAFE_componentWillUpdate() {244 log.push('BrokenComponentWillUnmount componentWillUpdate');245 }246 componentDidUpdate() {247 log.push('BrokenComponentWillUnmount componentDidUpdate');248 }249 componentWillUnmount() {250 log.push('BrokenComponentWillUnmount componentWillUnmount [!]');251 throw new Error(this.props.errorText);252 }253 };254 BrokenComponentWillMountErrorBoundary = class extends React.Component {255 constructor(props) {256 super(props);257 this.state = {error: null};258 log.push('BrokenComponentWillMountErrorBoundary constructor');259 }260 render() {261 if (this.state.error) {262 log.push('BrokenComponentWillMountErrorBoundary render error');263 return <div>Caught an error: {this.state.error.message}.</div>;264 }265 log.push('BrokenComponentWillMountErrorBoundary render success');266 return <div>{this.props.children}</div>;267 }268 UNSAFE_componentWillMount() {269 log.push(270 'BrokenComponentWillMountErrorBoundary componentWillMount [!]',271 );272 throw new Error('Hello');273 }274 componentDidMount() {275 log.push('BrokenComponentWillMountErrorBoundary componentDidMount');276 }277 componentWillUnmount() {278 log.push('BrokenComponentWillMountErrorBoundary componentWillUnmount');279 }280 static getDerivedStateFromError(error) {281 log.push(282 'BrokenComponentWillMountErrorBoundary static getDerivedStateFromError',283 );284 return {error};285 }286 };287 BrokenComponentDidMountErrorBoundary = class extends React.Component {288 constructor(props) {289 super(props);290 this.state = {error: null};291 log.push('BrokenComponentDidMountErrorBoundary constructor');292 }293 render() {294 if (this.state.error) {295 log.push('BrokenComponentDidMountErrorBoundary render error');296 return <div>Caught an error: {this.state.error.message}.</div>;297 }298 log.push('BrokenComponentDidMountErrorBoundary render success');299 return <div>{this.props.children}</div>;300 }301 UNSAFE_componentWillMount() {302 log.push('BrokenComponentDidMountErrorBoundary componentWillMount');303 }304 componentDidMount() {305 log.push('BrokenComponentDidMountErrorBoundary componentDidMount [!]');306 throw new Error('Hello');307 }308 componentWillUnmount() {309 log.push('BrokenComponentDidMountErrorBoundary componentWillUnmount');310 }311 static getDerivedStateFromError(error) {312 log.push(313 'BrokenComponentDidMountErrorBoundary static getDerivedStateFromError',314 );315 return {error};316 }317 };318 BrokenRenderErrorBoundary = class extends React.Component {319 constructor(props) {320 super(props);321 this.state = {error: null};322 log.push('BrokenRenderErrorBoundary constructor');323 }324 render() {325 if (this.state.error) {326 log.push('BrokenRenderErrorBoundary render error [!]');327 throw new Error('Hello');328 }329 log.push('BrokenRenderErrorBoundary render success');330 return <div>{this.props.children}</div>;331 }332 UNSAFE_componentWillMount() {333 log.push('BrokenRenderErrorBoundary componentWillMount');334 }335 componentDidMount() {336 log.push('BrokenRenderErrorBoundary componentDidMount');337 }338 componentWillUnmount() {339 log.push('BrokenRenderErrorBoundary componentWillUnmount');340 }341 static getDerivedStateFromError(error) {342 log.push('BrokenRenderErrorBoundary static getDerivedStateFromError');343 return {error};344 }345 };346 BrokenRender = class extends React.Component {347 constructor(props) {348 super(props);349 log.push('BrokenRender constructor');350 }351 render() {352 log.push('BrokenRender render [!]');353 throw new Error('Hello');354 }355 UNSAFE_componentWillMount() {356 log.push('BrokenRender componentWillMount');357 }358 componentDidMount() {359 log.push('BrokenRender componentDidMount');360 }361 UNSAFE_componentWillReceiveProps() {362 log.push('BrokenRender componentWillReceiveProps');363 }364 UNSAFE_componentWillUpdate() {365 log.push('BrokenRender componentWillUpdate');366 }367 componentDidUpdate() {368 log.push('BrokenRender componentDidUpdate');369 }370 componentWillUnmount() {371 log.push('BrokenRender componentWillUnmount');372 }373 };374 BrokenUseEffect = props => {375 log.push('BrokenUseEffect render');376 React.useEffect(() => {377 log.push('BrokenUseEffect useEffect [!]');378 throw new Error('Hello');379 });380 return props.children;381 };382 BrokenUseLayoutEffect = props => {383 log.push('BrokenUseLayoutEffect render');384 React.useLayoutEffect(() => {385 log.push('BrokenUseLayoutEffect useLayoutEffect [!]');386 throw new Error('Hello');387 });388 return props.children;389 };390 NoopErrorBoundary = class extends React.Component {391 constructor(props) {392 super(props);393 log.push('NoopErrorBoundary constructor');394 }395 render() {396 log.push('NoopErrorBoundary render');397 return <BrokenRender />;398 }399 UNSAFE_componentWillMount() {400 log.push('NoopErrorBoundary componentWillMount');401 }402 componentDidMount() {403 log.push('NoopErrorBoundary componentDidMount');404 }405 componentWillUnmount() {406 log.push('NoopErrorBoundary componentWillUnmount');407 }408 static getDerivedStateFromError() {409 log.push('NoopErrorBoundary static getDerivedStateFromError');410 }411 };412 Normal = class extends React.Component {413 static defaultProps = {414 logName: 'Normal',415 };416 constructor(props) {417 super(props);418 log.push(`${this.props.logName} constructor`);419 }420 render() {421 log.push(`${this.props.logName} render`);422 return <div>{this.props.children}</div>;423 }424 UNSAFE_componentWillMount() {425 log.push(`${this.props.logName} componentWillMount`);426 }427 componentDidMount() {428 log.push(`${this.props.logName} componentDidMount`);429 }430 UNSAFE_componentWillReceiveProps() {431 log.push(`${this.props.logName} componentWillReceiveProps`);432 }433 UNSAFE_componentWillUpdate() {434 log.push(`${this.props.logName} componentWillUpdate`);435 }436 componentDidUpdate() {437 log.push(`${this.props.logName} componentDidUpdate`);438 }439 componentWillUnmount() {440 log.push(`${this.props.logName} componentWillUnmount`);441 }442 };443 ErrorBoundary = class extends React.Component {444 constructor(props) {445 super(props);446 this.state = {error: null};447 log.push(`${this.props.logName} constructor`);448 }449 render() {450 if (this.state.error && !this.props.forceRetry) {451 log.push(`${this.props.logName} render error`);452 return this.props.renderError(this.state.error, this.props);453 }454 log.push(`${this.props.logName} render success`);455 return <div>{this.props.children}</div>;456 }457 static getDerivedStateFromError(error) {458 log.push('ErrorBoundary static getDerivedStateFromError');459 return {error};460 }461 UNSAFE_componentWillMount() {462 log.push(`${this.props.logName} componentWillMount`);463 }464 componentDidMount() {465 log.push(`${this.props.logName} componentDidMount`);466 }467 UNSAFE_componentWillReceiveProps() {468 log.push(`${this.props.logName} componentWillReceiveProps`);469 }470 UNSAFE_componentWillUpdate() {471 log.push(`${this.props.logName} componentWillUpdate`);472 }473 componentDidUpdate() {474 log.push(`${this.props.logName} componentDidUpdate`);475 }476 componentWillUnmount() {477 log.push(`${this.props.logName} componentWillUnmount`);478 }479 };480 ErrorBoundary.defaultProps = {481 logName: 'ErrorBoundary',482 renderError(error, props) {483 return (484 <div ref={props.errorMessageRef}>485 Caught an error: {error.message}.486 </div>487 );488 },489 };490 RetryErrorBoundary = class extends React.Component {491 constructor(props) {492 super(props);493 log.push('RetryErrorBoundary constructor');494 }495 render() {496 log.push('RetryErrorBoundary render');497 return <BrokenRender />;498 }499 UNSAFE_componentWillMount() {500 log.push('RetryErrorBoundary componentWillMount');501 }502 componentDidMount() {503 log.push('RetryErrorBoundary componentDidMount');504 }505 componentWillUnmount() {506 log.push('RetryErrorBoundary componentWillUnmount');507 }508 static getDerivedStateFromError(error) {509 log.push('RetryErrorBoundary static getDerivedStateFromError [!]');510 // In Fiber, calling setState() (and failing) is treated as a rethrow.511 return {};512 }513 };514 ErrorMessage = class extends React.Component {515 constructor(props) {516 super(props);517 log.push('ErrorMessage constructor');518 }519 UNSAFE_componentWillMount() {520 log.push('ErrorMessage componentWillMount');521 }522 componentDidMount() {523 log.push('ErrorMessage componentDidMount');524 }525 componentWillUnmount() {526 log.push('ErrorMessage componentWillUnmount');527 }528 render() {529 log.push('ErrorMessage render');530 return <div>Caught an error: {this.props.message}.</div>;531 }532 };533 });534 it('does not swallow exceptions on mounting without boundaries', () => {535 let container = document.createElement('div');536 expect(() => {537 ReactDOM.render(<BrokenRender />, container);538 }).toThrow('Hello');539 container = document.createElement('div');540 expect(() => {541 ReactDOM.render(<BrokenComponentWillMount />, container);542 }).toThrow('Hello');543 container = document.createElement('div');544 expect(() => {545 ReactDOM.render(<BrokenComponentDidMount />, container);546 }).toThrow('Hello');547 });548 it('does not swallow exceptions on updating without boundaries', () => {549 let container = document.createElement('div');550 ReactDOM.render(<BrokenComponentWillUpdate />, container);551 expect(() => {552 ReactDOM.render(<BrokenComponentWillUpdate />, container);553 }).toThrow('Hello');554 container = document.createElement('div');555 ReactDOM.render(<BrokenComponentWillReceiveProps />, container);556 expect(() => {557 ReactDOM.render(<BrokenComponentWillReceiveProps />, container);558 }).toThrow('Hello');559 container = document.createElement('div');560 ReactDOM.render(<BrokenComponentDidUpdate />, container);561 expect(() => {562 ReactDOM.render(<BrokenComponentDidUpdate />, container);563 }).toThrow('Hello');564 });565 it('does not swallow exceptions on unmounting without boundaries', () => {566 const container = document.createElement('div');567 ReactDOM.render(<BrokenComponentWillUnmount />, container);568 expect(() => {569 ReactDOM.unmountComponentAtNode(container);570 }).toThrow('Hello');571 });572 it('prevents errors from leaking into other roots', () => {573 const container1 = document.createElement('div');574 const container2 = document.createElement('div');575 const container3 = document.createElement('div');576 ReactDOM.render(<span>Before 1</span>, container1);577 expect(() => {578 ReactDOM.render(<BrokenRender />, container2);579 }).toThrow('Hello');580 ReactDOM.render(581 <ErrorBoundary>582 <BrokenRender />583 </ErrorBoundary>,584 container3,585 );586 expect(container1.firstChild.textContent).toBe('Before 1');587 expect(container2.firstChild).toBe(null);588 expect(container3.firstChild.textContent).toBe('Caught an error: Hello.');589 ReactDOM.render(<span>After 1</span>, container1);590 ReactDOM.render(<span>After 2</span>, container2);591 ReactDOM.render(592 <ErrorBoundary forceRetry={true}>After 3</ErrorBoundary>,593 container3,594 );595 expect(container1.firstChild.textContent).toBe('After 1');596 expect(container2.firstChild.textContent).toBe('After 2');597 expect(container3.firstChild.textContent).toBe('After 3');598 ReactDOM.unmountComponentAtNode(container1);599 ReactDOM.unmountComponentAtNode(container2);600 ReactDOM.unmountComponentAtNode(container3);601 expect(container1.firstChild).toBe(null);602 expect(container2.firstChild).toBe(null);603 expect(container3.firstChild).toBe(null);604 });605 it('renders an error state if child throws in render', () => {606 const container = document.createElement('div');607 ReactDOM.render(608 <ErrorBoundary>609 <BrokenRender />610 </ErrorBoundary>,611 container,612 );613 expect(container.firstChild.textContent).toBe('Caught an error: Hello.');614 expect(log).toEqual([615 'ErrorBoundary constructor',616 'ErrorBoundary componentWillMount',617 'ErrorBoundary render success',618 'BrokenRender constructor',619 'BrokenRender componentWillMount',620 'BrokenRender render [!]',621 // Catch and render an error message622 'ErrorBoundary static getDerivedStateFromError',623 'ErrorBoundary componentWillMount',624 'ErrorBoundary render error',625 'ErrorBoundary componentDidMount',626 ]);627 log.length = 0;628 ReactDOM.unmountComponentAtNode(container);629 expect(log).toEqual(['ErrorBoundary componentWillUnmount']);630 });631 it('renders an error state if child throws in constructor', () => {632 const container = document.createElement('div');633 ReactDOM.render(634 <ErrorBoundary>635 <BrokenConstructor />636 </ErrorBoundary>,637 container,638 );639 expect(container.firstChild.textContent).toBe('Caught an error: Hello.');640 expect(log).toEqual([641 'ErrorBoundary constructor',642 'ErrorBoundary componentWillMount',643 'ErrorBoundary render success',644 'BrokenConstructor constructor [!]',645 // Catch and render an error message646 'ErrorBoundary static getDerivedStateFromError',647 'ErrorBoundary componentWillMount',648 'ErrorBoundary render error',649 'ErrorBoundary componentDidMount',650 ]);651 log.length = 0;652 ReactDOM.unmountComponentAtNode(container);653 expect(log).toEqual(['ErrorBoundary componentWillUnmount']);654 });655 it('renders an error state if child throws in componentWillMount', () => {656 const container = document.createElement('div');657 ReactDOM.render(658 <ErrorBoundary>659 <BrokenComponentWillMount />660 </ErrorBoundary>,661 container,662 );663 expect(container.firstChild.textContent).toBe('Caught an error: Hello.');664 expect(log).toEqual([665 'ErrorBoundary constructor',666 'ErrorBoundary componentWillMount',667 'ErrorBoundary render success',668 'BrokenComponentWillMount constructor',669 'BrokenComponentWillMount componentWillMount [!]',670 // Catch and render an error message671 'ErrorBoundary static getDerivedStateFromError',672 'ErrorBoundary componentWillMount',673 'ErrorBoundary render error',674 'ErrorBoundary componentDidMount',675 ]);676 log.length = 0;677 ReactDOM.unmountComponentAtNode(container);678 expect(log).toEqual(['ErrorBoundary componentWillUnmount']);679 });680 it('renders an error state if context provider throws in componentWillMount', () => {681 class BrokenComponentWillMountWithContext extends React.Component {682 static childContextTypes = {foo: PropTypes.number};683 getChildContext() {684 return {foo: 42};685 }686 render() {687 return <div>{this.props.children}</div>;688 }689 UNSAFE_componentWillMount() {690 throw new Error('Hello');691 }692 }693 const container = document.createElement('div');694 ReactDOM.render(695 <ErrorBoundary>696 <BrokenComponentWillMountWithContext />697 </ErrorBoundary>,698 container,699 );700 expect(container.firstChild.textContent).toBe('Caught an error: Hello.');701 });702 it('renders an error state if module-style context provider throws in componentWillMount', () => {703 function BrokenComponentWillMountWithContext() {704 return {705 getChildContext() {706 return {foo: 42};707 },708 render() {709 return <div>{this.props.children}</div>;710 },711 UNSAFE_componentWillMount() {712 throw new Error('Hello');713 },714 };715 }716 BrokenComponentWillMountWithContext.childContextTypes = {717 foo: PropTypes.number,718 };719 const container = document.createElement('div');720 ReactDOM.render(721 <ErrorBoundary>722 <BrokenComponentWillMountWithContext />723 </ErrorBoundary>,724 container,725 );726 expect(container.firstChild.textContent).toBe('Caught an error: Hello.');727 });728 it('mounts the error message if mounting fails', () => {729 function renderError(error) {730 return <ErrorMessage message={error.message} />;731 }732 const container = document.createElement('div');733 ReactDOM.render(734 <ErrorBoundary renderError={renderError}>735 <BrokenRender />736 </ErrorBoundary>,737 container,738 );739 expect(log).toEqual([740 'ErrorBoundary constructor',741 'ErrorBoundary componentWillMount',742 'ErrorBoundary render success',743 'BrokenRender constructor',744 'BrokenRender componentWillMount',745 'BrokenRender render [!]',746 'ErrorBoundary static getDerivedStateFromError',747 'ErrorBoundary componentWillMount',748 'ErrorBoundary render error',749 'ErrorMessage constructor',750 'ErrorMessage componentWillMount',751 'ErrorMessage render',752 'ErrorMessage componentDidMount',753 'ErrorBoundary componentDidMount',754 ]);755 log.length = 0;756 ReactDOM.unmountComponentAtNode(container);757 expect(log).toEqual([758 'ErrorBoundary componentWillUnmount',759 'ErrorMessage componentWillUnmount',760 ]);761 });762 it('propagates errors on retry on mounting', () => {763 const container = document.createElement('div');764 ReactDOM.render(765 <ErrorBoundary>766 <RetryErrorBoundary>767 <BrokenRender />768 </RetryErrorBoundary>769 </ErrorBoundary>,770 container,771 );772 expect(container.firstChild.textContent).toBe('Caught an error: Hello.');773 expect(log).toEqual([774 'ErrorBoundary constructor',775 'ErrorBoundary componentWillMount',776 'ErrorBoundary render success',777 'RetryErrorBoundary constructor',778 'RetryErrorBoundary componentWillMount',779 'RetryErrorBoundary render',780 'BrokenRender constructor',781 'BrokenRender componentWillMount',782 'BrokenRender render [!]',783 // Retry784 'RetryErrorBoundary static getDerivedStateFromError [!]',785 'RetryErrorBoundary componentWillMount',786 'RetryErrorBoundary render',787 'BrokenRender constructor',788 'BrokenRender componentWillMount',789 'BrokenRender render [!]',790 // This time, the error propagates to the higher boundary791 'ErrorBoundary static getDerivedStateFromError',792 'ErrorBoundary componentWillMount',793 'ErrorBoundary render error',794 'ErrorBoundary componentDidMount',795 ]);796 log.length = 0;797 ReactDOM.unmountComponentAtNode(container);798 expect(log).toEqual(['ErrorBoundary componentWillUnmount']);799 });800 it('propagates errors inside boundary during componentWillMount', () => {801 const container = document.createElement('div');802 ReactDOM.render(803 <ErrorBoundary>804 <BrokenComponentWillMountErrorBoundary />805 </ErrorBoundary>,806 container,807 );808 expect(container.firstChild.textContent).toBe('Caught an error: Hello.');809 expect(log).toEqual([810 'ErrorBoundary constructor',811 'ErrorBoundary componentWillMount',812 'ErrorBoundary render success',813 'BrokenComponentWillMountErrorBoundary constructor',814 'BrokenComponentWillMountErrorBoundary componentWillMount [!]',815 // The error propagates to the higher boundary816 'ErrorBoundary static getDerivedStateFromError',817 'ErrorBoundary componentWillMount',818 'ErrorBoundary render error',819 'ErrorBoundary componentDidMount',820 ]);821 log.length = 0;822 ReactDOM.unmountComponentAtNode(container);823 expect(log).toEqual(['ErrorBoundary componentWillUnmount']);824 });825 it('propagates errors inside boundary while rendering error state', () => {826 const container = document.createElement('div');827 ReactDOM.render(828 <ErrorBoundary>829 <BrokenRenderErrorBoundary>830 <BrokenRender />831 </BrokenRenderErrorBoundary>832 </ErrorBoundary>,833 container,834 );835 expect(container.firstChild.textContent).toBe('Caught an error: Hello.');836 expect(log).toEqual([837 'ErrorBoundary constructor',838 'ErrorBoundary componentWillMount',839 'ErrorBoundary render success',840 'BrokenRenderErrorBoundary constructor',841 'BrokenRenderErrorBoundary componentWillMount',842 'BrokenRenderErrorBoundary render success',843 'BrokenRender constructor',844 'BrokenRender componentWillMount',845 'BrokenRender render [!]',846 // Attempt to handle the error847 'BrokenRenderErrorBoundary static getDerivedStateFromError',848 'BrokenRenderErrorBoundary componentWillMount',849 'BrokenRenderErrorBoundary render error [!]',850 // Attempt to handle the error again851 'ErrorBoundary static getDerivedStateFromError',852 'ErrorBoundary componentWillMount',853 'ErrorBoundary render error',854 'ErrorBoundary componentDidMount',855 ]);856 log.length = 0;857 ReactDOM.unmountComponentAtNode(container);858 expect(log).toEqual(['ErrorBoundary componentWillUnmount']);859 });860 it('does not call componentWillUnmount when aborting initial mount', () => {861 const container = document.createElement('div');862 ReactDOM.render(863 <ErrorBoundary>864 <Normal />865 <BrokenRender />866 <Normal />867 </ErrorBoundary>,868 container,869 );870 expect(container.firstChild.textContent).toBe('Caught an error: Hello.');871 expect(log).toEqual([872 'ErrorBoundary constructor',873 'ErrorBoundary componentWillMount',874 'ErrorBoundary render success',875 // Render first child876 'Normal constructor',877 'Normal componentWillMount',878 'Normal render',879 // Render second child (it throws)880 'BrokenRender constructor',881 'BrokenRender componentWillMount',882 'BrokenRender render [!]',883 // Render third child, even though an earlier sibling threw.884 'Normal constructor',885 'Normal componentWillMount',886 'Normal render',887 // Handle the error888 'ErrorBoundary static getDerivedStateFromError',889 'ErrorBoundary componentWillMount',890 'ErrorBoundary render error',891 'ErrorBoundary componentDidMount',892 ]);893 log.length = 0;894 ReactDOM.unmountComponentAtNode(container);895 expect(log).toEqual(['ErrorBoundary componentWillUnmount']);896 });897 it('resets callback refs if mounting aborts', () => {898 function childRef(x) {899 log.push('Child ref is set to ' + x);900 }901 function errorMessageRef(x) {902 log.push('Error message ref is set to ' + x);903 }904 const container = document.createElement('div');905 ReactDOM.render(906 <ErrorBoundary errorMessageRef={errorMessageRef}>907 <div ref={childRef} />908 <BrokenRender />909 </ErrorBoundary>,910 container,911 );912 expect(container.textContent).toBe('Caught an error: Hello.');913 expect(log).toEqual([914 'ErrorBoundary constructor',915 'ErrorBoundary componentWillMount',916 'ErrorBoundary render success',917 'BrokenRender constructor',918 'BrokenRender componentWillMount',919 'BrokenRender render [!]',920 // Handle the error921 'ErrorBoundary static getDerivedStateFromError',922 'ErrorBoundary componentWillMount',923 'ErrorBoundary render error',924 'Error message ref is set to [object HTMLDivElement]',925 'ErrorBoundary componentDidMount',926 ]);927 log.length = 0;928 ReactDOM.unmountComponentAtNode(container);929 expect(log).toEqual([930 'ErrorBoundary componentWillUnmount',931 'Error message ref is set to null',932 ]);933 });934 it('resets object refs if mounting aborts', () => {935 let childRef = React.createRef();936 let errorMessageRef = React.createRef();937 const container = document.createElement('div');938 ReactDOM.render(939 <ErrorBoundary errorMessageRef={errorMessageRef}>940 <div ref={childRef} />941 <BrokenRender />942 </ErrorBoundary>,943 container,944 );945 expect(container.textContent).toBe('Caught an error: Hello.');946 expect(log).toEqual([947 'ErrorBoundary constructor',948 'ErrorBoundary componentWillMount',949 'ErrorBoundary render success',950 'BrokenRender constructor',951 'BrokenRender componentWillMount',952 'BrokenRender render [!]',953 // Handle the error954 'ErrorBoundary static getDerivedStateFromError',955 'ErrorBoundary componentWillMount',956 'ErrorBoundary render error',957 'ErrorBoundary componentDidMount',958 ]);959 expect(errorMessageRef.current.toString()).toEqual(960 '[object HTMLDivElement]',961 );962 log.length = 0;963 ReactDOM.unmountComponentAtNode(container);964 expect(log).toEqual(['ErrorBoundary componentWillUnmount']);965 expect(errorMessageRef.current).toEqual(null);966 });967 it('successfully mounts if no error occurs', () => {968 const container = document.createElement('div');969 ReactDOM.render(970 <ErrorBoundary>971 <div>Mounted successfully.</div>972 </ErrorBoundary>,973 container,974 );975 expect(container.firstChild.textContent).toBe('Mounted successfully.');976 expect(log).toEqual([977 'ErrorBoundary constructor',978 'ErrorBoundary componentWillMount',979 'ErrorBoundary render success',980 'ErrorBoundary componentDidMount',981 ]);982 log.length = 0;983 ReactDOM.unmountComponentAtNode(container);984 expect(log).toEqual(['ErrorBoundary componentWillUnmount']);985 });986 it('catches if child throws in constructor during update', () => {987 const container = document.createElement('div');988 ReactDOM.render(989 <ErrorBoundary>990 <Normal />991 </ErrorBoundary>,992 container,993 );994 log.length = 0;995 ReactDOM.render(996 <ErrorBoundary>997 <Normal />998 <Normal logName="Normal2" />999 <BrokenConstructor />1000 </ErrorBoundary>,1001 container,1002 );1003 expect(container.textContent).toBe('Caught an error: Hello.');1004 expect(log).toEqual([1005 'ErrorBoundary componentWillReceiveProps',1006 'ErrorBoundary componentWillUpdate',1007 'ErrorBoundary render success',1008 'Normal componentWillReceiveProps',1009 'Normal componentWillUpdate',1010 'Normal render',1011 // Normal2 will attempt to mount:1012 'Normal2 constructor',1013 'Normal2 componentWillMount',1014 'Normal2 render',1015 // BrokenConstructor will abort rendering:1016 'BrokenConstructor constructor [!]',1017 // Handle the error1018 'ErrorBoundary static getDerivedStateFromError',1019 // Render the error message1020 'ErrorBoundary componentWillUpdate',1021 'ErrorBoundary render error',1022 'Normal componentWillUnmount',1023 'ErrorBoundary componentDidUpdate',1024 ]);1025 log.length = 0;1026 ReactDOM.unmountComponentAtNode(container);1027 expect(log).toEqual(['ErrorBoundary componentWillUnmount']);1028 });1029 it('catches if child throws in componentWillMount during update', () => {1030 const container = document.createElement('div');1031 ReactDOM.render(1032 <ErrorBoundary>1033 <Normal />1034 </ErrorBoundary>,1035 container,1036 );1037 log.length = 0;1038 ReactDOM.render(1039 <ErrorBoundary>1040 <Normal />1041 <Normal logName="Normal2" />1042 <BrokenComponentWillMount />1043 </ErrorBoundary>,1044 container,1045 );1046 expect(container.textContent).toBe('Caught an error: Hello.');1047 expect(log).toEqual([1048 'ErrorBoundary componentWillReceiveProps',1049 'ErrorBoundary componentWillUpdate',1050 'ErrorBoundary render success',1051 'Normal componentWillReceiveProps',1052 'Normal componentWillUpdate',1053 'Normal render',1054 // Normal2 will attempt to mount:1055 'Normal2 constructor',1056 'Normal2 componentWillMount',1057 'Normal2 render',1058 // BrokenComponentWillMount will abort rendering:1059 'BrokenComponentWillMount constructor',1060 'BrokenComponentWillMount componentWillMount [!]',1061 // Handle the error1062 'ErrorBoundary static getDerivedStateFromError',1063 // Render the error message1064 'ErrorBoundary componentWillUpdate',1065 'ErrorBoundary render error',1066 'Normal componentWillUnmount',1067 'ErrorBoundary componentDidUpdate',1068 ]);1069 log.length = 0;1070 ReactDOM.unmountComponentAtNode(container);1071 expect(log).toEqual(['ErrorBoundary componentWillUnmount']);1072 });1073 it('catches if child throws in componentWillReceiveProps during update', () => {1074 const container = document.createElement('div');1075 ReactDOM.render(1076 <ErrorBoundary>1077 <Normal />1078 <BrokenComponentWillReceiveProps />1079 </ErrorBoundary>,1080 container,1081 );1082 log.length = 0;1083 ReactDOM.render(1084 <ErrorBoundary>1085 <Normal />1086 <BrokenComponentWillReceiveProps />1087 </ErrorBoundary>,1088 container,1089 );1090 expect(container.textContent).toBe('Caught an error: Hello.');1091 expect(log).toEqual([1092 'ErrorBoundary componentWillReceiveProps',1093 'ErrorBoundary componentWillUpdate',1094 'ErrorBoundary render success',1095 'Normal componentWillReceiveProps',1096 'Normal componentWillUpdate',1097 'Normal render',1098 // BrokenComponentWillReceiveProps will abort rendering:1099 'BrokenComponentWillReceiveProps componentWillReceiveProps [!]',1100 // Handle the error1101 'ErrorBoundary static getDerivedStateFromError',1102 // Render the error message1103 'ErrorBoundary componentWillUpdate',1104 'ErrorBoundary render error',1105 'Normal componentWillUnmount',1106 'BrokenComponentWillReceiveProps componentWillUnmount',1107 'ErrorBoundary componentDidUpdate',1108 ]);1109 log.length = 0;1110 ReactDOM.unmountComponentAtNode(container);1111 expect(log).toEqual(['ErrorBoundary componentWillUnmount']);1112 });1113 it('catches if child throws in componentWillUpdate during update', () => {1114 const container = document.createElement('div');1115 ReactDOM.render(1116 <ErrorBoundary>1117 <Normal />1118 <BrokenComponentWillUpdate />1119 </ErrorBoundary>,1120 container,1121 );1122 log.length = 0;1123 ReactDOM.render(1124 <ErrorBoundary>1125 <Normal />1126 <BrokenComponentWillUpdate />1127 </ErrorBoundary>,1128 container,1129 );1130 expect(container.textContent).toBe('Caught an error: Hello.');1131 expect(log).toEqual([1132 'ErrorBoundary componentWillReceiveProps',1133 'ErrorBoundary componentWillUpdate',1134 'ErrorBoundary render success',1135 'Normal componentWillReceiveProps',1136 'Normal componentWillUpdate',1137 'Normal render',1138 // BrokenComponentWillUpdate will abort rendering:1139 'BrokenComponentWillUpdate componentWillReceiveProps',1140 'BrokenComponentWillUpdate componentWillUpdate [!]',1141 // Handle the error1142 'ErrorBoundary static getDerivedStateFromError',1143 'ErrorBoundary componentWillUpdate',1144 'ErrorBoundary render error',1145 'Normal componentWillUnmount',1146 'BrokenComponentWillUpdate componentWillUnmount',1147 'ErrorBoundary componentDidUpdate',1148 ]);1149 log.length = 0;1150 ReactDOM.unmountComponentAtNode(container);1151 expect(log).toEqual(['ErrorBoundary componentWillUnmount']);1152 });1153 it('catches if child throws in render during update', () => {1154 const container = document.createElement('div');1155 ReactDOM.render(1156 <ErrorBoundary>1157 <Normal />1158 </ErrorBoundary>,1159 container,1160 );1161 log.length = 0;1162 ReactDOM.render(1163 <ErrorBoundary>1164 <Normal />1165 <Normal logName="Normal2" />1166 <BrokenRender />1167 </ErrorBoundary>,1168 container,1169 );1170 expect(container.textContent).toBe('Caught an error: Hello.');1171 expect(log).toEqual([1172 'ErrorBoundary componentWillReceiveProps',1173 'ErrorBoundary componentWillUpdate',1174 'ErrorBoundary render success',1175 'Normal componentWillReceiveProps',1176 'Normal componentWillUpdate',1177 'Normal render',1178 // Normal2 will attempt to mount:1179 'Normal2 constructor',1180 'Normal2 componentWillMount',1181 'Normal2 render',1182 // BrokenRender will abort rendering:1183 'BrokenRender constructor',1184 'BrokenRender componentWillMount',1185 'BrokenRender render [!]',1186 // Handle the error1187 'ErrorBoundary static getDerivedStateFromError',1188 'ErrorBoundary componentWillUpdate',1189 'ErrorBoundary render error',1190 'Normal componentWillUnmount',1191 'ErrorBoundary componentDidUpdate',1192 ]);1193 log.length = 0;1194 ReactDOM.unmountComponentAtNode(container);1195 expect(log).toEqual(['ErrorBoundary componentWillUnmount']);1196 });1197 it('keeps refs up-to-date during updates', () => {1198 function child1Ref(x) {1199 log.push('Child1 ref is set to ' + x);1200 }1201 function child2Ref(x) {1202 log.push('Child2 ref is set to ' + x);1203 }1204 function errorMessageRef(x) {1205 log.push('Error message ref is set to ' + x);1206 }1207 const container = document.createElement('div');1208 ReactDOM.render(1209 <ErrorBoundary errorMessageRef={errorMessageRef}>1210 <div ref={child1Ref} />1211 </ErrorBoundary>,1212 container,1213 );1214 expect(log).toEqual([1215 'ErrorBoundary constructor',1216 'ErrorBoundary componentWillMount',1217 'ErrorBoundary render success',1218 'Child1 ref is set to [object HTMLDivElement]',1219 'ErrorBoundary componentDidMount',1220 ]);1221 log.length = 0;1222 ReactDOM.render(1223 <ErrorBoundary errorMessageRef={errorMessageRef}>1224 <div ref={child1Ref} />1225 <div ref={child2Ref} />1226 <BrokenRender />1227 </ErrorBoundary>,1228 container,1229 );1230 expect(container.textContent).toBe('Caught an error: Hello.');1231 expect(log).toEqual([1232 'ErrorBoundary componentWillReceiveProps',1233 'ErrorBoundary componentWillUpdate',1234 'ErrorBoundary render success',1235 // BrokenRender will abort rendering:1236 'BrokenRender constructor',1237 'BrokenRender componentWillMount',1238 'BrokenRender render [!]',1239 // Handle the error1240 'ErrorBoundary static getDerivedStateFromError',1241 'ErrorBoundary componentWillUpdate',1242 'ErrorBoundary render error',1243 // Update Child1 ref since Child1 has been unmounted1244 // Child2 ref is never set because its mounting aborted1245 'Child1 ref is set to null',1246 'Error message ref is set to [object HTMLDivElement]',1247 'ErrorBoundary componentDidUpdate',1248 ]);1249 log.length = 0;1250 ReactDOM.unmountComponentAtNode(container);1251 expect(log).toEqual([1252 'ErrorBoundary componentWillUnmount',1253 'Error message ref is set to null',1254 ]);1255 });1256 it('recovers from componentWillUnmount errors on update', () => {1257 const container = document.createElement('div');1258 ReactDOM.render(1259 <ErrorBoundary>1260 <BrokenComponentWillUnmount />1261 <BrokenComponentWillUnmount />1262 <Normal />1263 </ErrorBoundary>,1264 container,1265 );1266 log.length = 0;1267 ReactDOM.render(1268 <ErrorBoundary>1269 <BrokenComponentWillUnmount />1270 </ErrorBoundary>,1271 container,1272 );1273 expect(container.textContent).toBe('Caught an error: Hello.');1274 expect(log).toEqual([1275 'ErrorBoundary componentWillReceiveProps',1276 'ErrorBoundary componentWillUpdate',1277 'ErrorBoundary render success',1278 // Update existing child:1279 'BrokenComponentWillUnmount componentWillReceiveProps',1280 'BrokenComponentWillUnmount componentWillUpdate',1281 'BrokenComponentWillUnmount render',1282 // Unmounting throws:1283 'BrokenComponentWillUnmount componentWillUnmount [!]',1284 // Fiber proceeds with lifecycles despite errors1285 'Normal componentWillUnmount',1286 // The components have updated in this phase1287 'BrokenComponentWillUnmount componentDidUpdate',1288 'ErrorBoundary componentDidUpdate',1289 // The initial render was aborted, so1290 // Fiber retries from the root.1291 'ErrorBoundary static getDerivedStateFromError',1292 'ErrorBoundary componentWillUpdate',1293 'ErrorBoundary render error',1294 'BrokenComponentWillUnmount componentWillUnmount [!]',1295 'ErrorBoundary componentDidUpdate',1296 // The second willUnmount error should be captured and logged, too.1297 'ErrorBoundary static getDerivedStateFromError',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 // Now that commit phase is done, Fiber handles errors1348 'ErrorBoundary static getDerivedStateFromError',1349 'ErrorBoundary componentWillUpdate',1350 'ErrorBoundary render error',1351 'Normal componentWillUnmount',1352 'BrokenComponentWillUnmount componentWillUnmount [!]',1353 'ErrorBoundary componentDidUpdate',1354 // The second willUnmount error should be captured and logged, too.1355 'ErrorBoundary static getDerivedStateFromError',1356 'ErrorBoundary componentWillUpdate',1357 // Render an error now (stack will do it later)1358 'ErrorBoundary render error',1359 // Done1360 'ErrorBoundary componentDidUpdate',1361 ]);1362 log.length = 0;1363 ReactDOM.unmountComponentAtNode(container);1364 expect(log).toEqual(['ErrorBoundary componentWillUnmount']);1365 });1366 it('picks the right boundary when handling unmounting errors', () => {1367 function renderInnerError(error) {1368 return <div>Caught an inner error: {error.message}.</div>;1369 }1370 function renderOuterError(error) {1371 return <div>Caught an outer error: {error.message}.</div>;1372 }1373 const container = document.createElement('div');1374 ReactDOM.render(1375 <ErrorBoundary1376 logName="OuterErrorBoundary"1377 renderError={renderOuterError}>1378 <ErrorBoundary1379 logName="InnerErrorBoundary"1380 renderError={renderInnerError}>1381 <BrokenComponentWillUnmount />1382 </ErrorBoundary>1383 </ErrorBoundary>,1384 container,1385 );1386 log.length = 0;1387 ReactDOM.render(1388 <ErrorBoundary1389 logName="OuterErrorBoundary"1390 renderError={renderOuterError}>1391 <ErrorBoundary1392 logName="InnerErrorBoundary"1393 renderError={renderInnerError}1394 />1395 </ErrorBoundary>,1396 container,1397 );1398 expect(container.textContent).toBe('Caught an inner error: Hello.');1399 expect(log).toEqual([1400 // Update outer boundary1401 'OuterErrorBoundary componentWillReceiveProps',1402 'OuterErrorBoundary componentWillUpdate',1403 'OuterErrorBoundary render success',1404 // Update inner boundary1405 'InnerErrorBoundary componentWillReceiveProps',1406 'InnerErrorBoundary componentWillUpdate',1407 'InnerErrorBoundary render success',1408 // Try unmounting child1409 'BrokenComponentWillUnmount componentWillUnmount [!]',1410 // Now that commit phase is done, Fiber handles errors1411 // Only inner boundary receives the error:1412 'InnerErrorBoundary componentDidUpdate',1413 'OuterErrorBoundary componentDidUpdate',1414 'ErrorBoundary static getDerivedStateFromError',1415 'InnerErrorBoundary componentWillUpdate',1416 // Render an error now1417 'InnerErrorBoundary render error',1418 // In Fiber, this was a local update to the1419 // inner boundary so only its hook fires1420 'InnerErrorBoundary componentDidUpdate',1421 ]);1422 log.length = 0;1423 ReactDOM.unmountComponentAtNode(container);1424 expect(log).toEqual([1425 'OuterErrorBoundary componentWillUnmount',1426 'InnerErrorBoundary componentWillUnmount',1427 ]);1428 });1429 it('can recover from error state', () => {1430 const container = document.createElement('div');1431 ReactDOM.render(1432 <ErrorBoundary>1433 <BrokenRender />1434 </ErrorBoundary>,1435 container,1436 );1437 ReactDOM.render(1438 <ErrorBoundary>1439 <Normal />1440 </ErrorBoundary>,1441 container,1442 );1443 // Error boundary doesn't retry by itself:1444 expect(container.textContent).toBe('Caught an error: Hello.');1445 // Force the success path:1446 log.length = 0;1447 ReactDOM.render(1448 <ErrorBoundary forceRetry={true}>1449 <Normal />1450 </ErrorBoundary>,1451 container,1452 );1453 expect(container.textContent).not.toContain('Caught an error');1454 expect(log).toEqual([1455 'ErrorBoundary componentWillReceiveProps',1456 'ErrorBoundary componentWillUpdate',1457 'ErrorBoundary render success',1458 // Mount children:1459 'Normal constructor',1460 'Normal componentWillMount',1461 'Normal render',1462 // Finalize updates:1463 'Normal componentDidMount',1464 'ErrorBoundary componentDidUpdate',1465 ]);1466 log.length = 0;1467 ReactDOM.unmountComponentAtNode(container);1468 expect(log).toEqual([1469 'ErrorBoundary componentWillUnmount',1470 'Normal componentWillUnmount',1471 ]);1472 });1473 it('can update multiple times in error state', () => {1474 const container = document.createElement('div');1475 ReactDOM.render(1476 <ErrorBoundary>1477 <BrokenRender />1478 </ErrorBoundary>,1479 container,1480 );1481 expect(container.textContent).toBe('Caught an error: Hello.');1482 ReactDOM.render(1483 <ErrorBoundary>1484 <BrokenRender />1485 </ErrorBoundary>,1486 container,1487 );1488 expect(container.textContent).toBe('Caught an error: Hello.');1489 ReactDOM.render(<div>Other screen</div>, container);1490 expect(container.textContent).toBe('Other screen');1491 ReactDOM.unmountComponentAtNode(container);1492 });1493 it("doesn't get into inconsistent state during removals", () => {1494 const container = document.createElement('div');1495 ReactDOM.render(1496 <ErrorBoundary>1497 <Normal />1498 <BrokenComponentWillUnmount />1499 <Normal />1500 </ErrorBoundary>,1501 container,1502 );1503 ReactDOM.render(<ErrorBoundary />, container);1504 expect(container.textContent).toBe('Caught an error: Hello.');1505 log.length = 0;1506 ReactDOM.unmountComponentAtNode(container);1507 expect(log).toEqual(['ErrorBoundary componentWillUnmount']);1508 });1509 it("doesn't get into inconsistent state during additions", () => {1510 const container = document.createElement('div');1511 ReactDOM.render(<ErrorBoundary />, container);1512 ReactDOM.render(1513 <ErrorBoundary>1514 <Normal />1515 <BrokenRender />1516 <Normal />1517 </ErrorBoundary>,1518 container,1519 );1520 expect(container.textContent).toBe('Caught an error: Hello.');1521 log.length = 0;1522 ReactDOM.unmountComponentAtNode(container);1523 expect(log).toEqual(['ErrorBoundary componentWillUnmount']);1524 });1525 it("doesn't get into inconsistent state during reorders", () => {1526 function getAMixOfNormalAndBrokenRenderElements() {1527 const elements = [];1528 for (let i = 0; i < 100; i++) {1529 elements.push(<Normal key={i} />);1530 }1531 elements.push(<MaybeBrokenRender key={100} />);1532 let currentIndex = elements.length;1533 while (0 !== currentIndex) {1534 const randomIndex = Math.floor(Math.random() * currentIndex);1535 currentIndex -= 1;1536 const temporaryValue = elements[currentIndex];1537 elements[currentIndex] = elements[randomIndex];1538 elements[randomIndex] = temporaryValue;1539 }1540 return elements;1541 }1542 class MaybeBrokenRender extends React.Component {1543 render() {1544 if (fail) {1545 throw new Error('Hello');1546 }1547 return <div>{this.props.children}</div>;1548 }1549 }1550 let fail = false;1551 const container = document.createElement('div');1552 ReactDOM.render(1553 <ErrorBoundary>{getAMixOfNormalAndBrokenRenderElements()}</ErrorBoundary>,1554 container,1555 );1556 expect(container.textContent).not.toContain('Caught an error');1557 fail = true;1558 ReactDOM.render(1559 <ErrorBoundary>{getAMixOfNormalAndBrokenRenderElements()}</ErrorBoundary>,1560 container,1561 );1562 expect(container.textContent).toBe('Caught an error: Hello.');1563 log.length = 0;1564 ReactDOM.unmountComponentAtNode(container);1565 expect(log).toEqual(['ErrorBoundary componentWillUnmount']);1566 });1567 it('catches errors originating downstream', () => {1568 let fail = false;1569 class Stateful extends React.Component {1570 state = {shouldThrow: false};1571 render() {1572 if (fail) {1573 log.push('Stateful render [!]');1574 throw new Error('Hello');1575 }1576 return <div>{this.props.children}</div>;1577 }1578 }1579 let statefulInst;1580 const container = document.createElement('div');1581 ReactDOM.render(1582 <ErrorBoundary>1583 <Stateful ref={inst => (statefulInst = inst)} />1584 </ErrorBoundary>,1585 container,1586 );1587 log.length = 0;1588 expect(() => {1589 fail = true;1590 statefulInst.forceUpdate();1591 }).not.toThrow();1592 expect(log).toEqual([1593 'Stateful render [!]',1594 'ErrorBoundary static getDerivedStateFromError',1595 'ErrorBoundary componentWillUpdate',1596 'ErrorBoundary render error',1597 'ErrorBoundary componentDidUpdate',1598 ]);1599 log.length = 0;1600 ReactDOM.unmountComponentAtNode(container);1601 expect(log).toEqual(['ErrorBoundary componentWillUnmount']);1602 });1603 it('catches errors in componentDidMount', () => {1604 const container = document.createElement('div');1605 ReactDOM.render(1606 <ErrorBoundary>1607 <BrokenComponentWillUnmount>1608 <Normal />1609 </BrokenComponentWillUnmount>1610 <BrokenComponentDidMount />1611 <Normal logName="LastChild" />1612 </ErrorBoundary>,1613 container,1614 );1615 expect(log).toEqual([1616 'ErrorBoundary constructor',1617 'ErrorBoundary componentWillMount',1618 'ErrorBoundary render success',1619 'BrokenComponentWillUnmount constructor',1620 'BrokenComponentWillUnmount componentWillMount',1621 'BrokenComponentWillUnmount render',1622 'Normal constructor',1623 'Normal componentWillMount',1624 'Normal render',1625 'BrokenComponentDidMount constructor',1626 'BrokenComponentDidMount componentWillMount',1627 'BrokenComponentDidMount render',1628 'LastChild constructor',1629 'LastChild componentWillMount',1630 'LastChild render',1631 // Start flushing didMount queue1632 'Normal componentDidMount',1633 'BrokenComponentWillUnmount componentDidMount',1634 'BrokenComponentDidMount componentDidMount [!]',1635 // Continue despite the error1636 'LastChild componentDidMount',1637 // Now we are ready to handle the error1638 'ErrorBoundary componentDidMount',1639 'ErrorBoundary static getDerivedStateFromError',1640 'ErrorBoundary componentWillUpdate',1641 'ErrorBoundary render error',1642 // Safely unmount every child1643 'BrokenComponentWillUnmount componentWillUnmount [!]',1644 // Continue unmounting safely despite any errors1645 'Normal componentWillUnmount',1646 'BrokenComponentDidMount componentWillUnmount',1647 'LastChild componentWillUnmount',1648 // The willUnmount error should be captured and logged, too.1649 'ErrorBoundary componentDidUpdate',1650 'ErrorBoundary static getDerivedStateFromError',1651 'ErrorBoundary componentWillUpdate',1652 'ErrorBoundary render error',1653 // The update has finished1654 'ErrorBoundary componentDidUpdate',1655 ]);1656 log.length = 0;1657 ReactDOM.unmountComponentAtNode(container);1658 expect(log).toEqual(['ErrorBoundary componentWillUnmount']);1659 });1660 it('catches errors in componentDidUpdate', () => {1661 const container = document.createElement('div');1662 ReactDOM.render(1663 <ErrorBoundary>1664 <BrokenComponentDidUpdate />1665 </ErrorBoundary>,1666 container,1667 );1668 log.length = 0;1669 ReactDOM.render(1670 <ErrorBoundary>1671 <BrokenComponentDidUpdate />1672 </ErrorBoundary>,1673 container,1674 );1675 expect(log).toEqual([1676 'ErrorBoundary componentWillReceiveProps',1677 'ErrorBoundary componentWillUpdate',1678 'ErrorBoundary render success',1679 'BrokenComponentDidUpdate componentWillReceiveProps',1680 'BrokenComponentDidUpdate componentWillUpdate',1681 'BrokenComponentDidUpdate render',1682 // All lifecycles run1683 'BrokenComponentDidUpdate componentDidUpdate [!]',1684 'ErrorBoundary componentDidUpdate',1685 // Then, error is handled1686 'ErrorBoundary static getDerivedStateFromError',1687 'ErrorBoundary componentWillUpdate',1688 'ErrorBoundary render error',1689 'BrokenComponentDidUpdate componentWillUnmount',1690 'ErrorBoundary componentDidUpdate',1691 ]);1692 log.length = 0;1693 ReactDOM.unmountComponentAtNode(container);1694 expect(log).toEqual(['ErrorBoundary componentWillUnmount']);1695 });1696 it('catches errors in useEffect', () => {1697 const container = document.createElement('div');1698 ReactDOM.render(1699 <ErrorBoundary>1700 <BrokenUseEffect>Initial value</BrokenUseEffect>1701 </ErrorBoundary>,1702 container,1703 );1704 expect(log).toEqual([1705 'ErrorBoundary constructor',1706 'ErrorBoundary componentWillMount',1707 'ErrorBoundary render success',1708 'BrokenUseEffect render',1709 'ErrorBoundary componentDidMount',1710 ]);1711 expect(container.firstChild.textContent).toBe('Initial value');1712 log.length = 0;1713 jest.runAllTimers();1714 // Flush passive effects and handle the error1715 expect(log).toEqual([1716 'BrokenUseEffect useEffect [!]',1717 // Handle the error1718 'ErrorBoundary static getDerivedStateFromError',1719 'ErrorBoundary componentWillUpdate',1720 'ErrorBoundary render error',1721 'ErrorBoundary componentDidUpdate',1722 ]);1723 expect(container.firstChild.textContent).toBe('Caught an error: Hello.');1724 });1725 it('catches errors in useLayoutEffect', () => {1726 const container = document.createElement('div');1727 ReactDOM.render(1728 <ErrorBoundary>1729 <BrokenUseLayoutEffect>Initial value</BrokenUseLayoutEffect>1730 </ErrorBoundary>,1731 container,1732 );1733 expect(log).toEqual([1734 'ErrorBoundary constructor',1735 'ErrorBoundary componentWillMount',1736 'ErrorBoundary render success',1737 'BrokenUseLayoutEffect render',1738 'BrokenUseLayoutEffect useLayoutEffect [!]',1739 // Fiber proceeds with the hooks1740 'ErrorBoundary componentDidMount',1741 // The error propagates to the higher boundary1742 'ErrorBoundary static getDerivedStateFromError',1743 // Fiber retries from the root1744 'ErrorBoundary componentWillUpdate',1745 'ErrorBoundary render error',1746 'ErrorBoundary componentDidUpdate',1747 ]);1748 expect(container.firstChild.textContent).toBe('Caught an error: Hello.');1749 });1750 it('propagates errors inside boundary during componentDidMount', () => {1751 const container = document.createElement('div');1752 ReactDOM.render(1753 <ErrorBoundary>1754 <BrokenComponentDidMountErrorBoundary1755 renderError={error => (1756 <div>We should never catch our own error: {error.message}.</div>1757 )}1758 />1759 </ErrorBoundary>,1760 container,1761 );1762 expect(container.firstChild.textContent).toBe('Caught an error: Hello.');1763 expect(log).toEqual([1764 'ErrorBoundary constructor',1765 'ErrorBoundary componentWillMount',1766 'ErrorBoundary render success',1767 'BrokenComponentDidMountErrorBoundary constructor',1768 'BrokenComponentDidMountErrorBoundary componentWillMount',1769 'BrokenComponentDidMountErrorBoundary render success',1770 'BrokenComponentDidMountErrorBoundary componentDidMount [!]',1771 // Fiber proceeds with the hooks1772 'ErrorBoundary componentDidMount',1773 // The error propagates to the higher boundary1774 'ErrorBoundary static getDerivedStateFromError',1775 // Fiber retries from the root1776 'ErrorBoundary componentWillUpdate',1777 'ErrorBoundary render error',1778 'BrokenComponentDidMountErrorBoundary componentWillUnmount',1779 'ErrorBoundary componentDidUpdate',1780 ]);1781 log.length = 0;1782 ReactDOM.unmountComponentAtNode(container);1783 expect(log).toEqual(['ErrorBoundary componentWillUnmount']);1784 });1785 it('calls static getDerivedStateFromError for each error that is captured', () => {1786 function renderUnmountError(error) {1787 return <div>Caught an unmounting error: {error.message}.</div>;1788 }1789 function renderUpdateError(error) {1790 return <div>Caught an updating error: {error.message}.</div>;1791 }1792 const container = document.createElement('div');1793 ReactDOM.render(1794 <ErrorBoundary logName="OuterErrorBoundary">1795 <ErrorBoundary1796 logName="InnerUnmountBoundary"1797 renderError={renderUnmountError}>1798 <BrokenComponentWillUnmount errorText="E1" />1799 <BrokenComponentWillUnmount errorText="E2" />1800 </ErrorBoundary>1801 <ErrorBoundary1802 logName="InnerUpdateBoundary"1803 renderError={renderUpdateError}>1804 <BrokenComponentDidUpdate errorText="E3" />1805 <BrokenComponentDidUpdate errorText="E4" />1806 </ErrorBoundary>1807 </ErrorBoundary>,1808 container,1809 );1810 log.length = 0;1811 ReactDOM.render(1812 <ErrorBoundary logName="OuterErrorBoundary">1813 <ErrorBoundary1814 logName="InnerUnmountBoundary"1815 renderError={renderUnmountError}1816 />1817 <ErrorBoundary1818 logName="InnerUpdateBoundary"1819 renderError={renderUpdateError}>1820 <BrokenComponentDidUpdate errorText="E3" />1821 <BrokenComponentDidUpdate errorText="E4" />1822 </ErrorBoundary>1823 </ErrorBoundary>,1824 container,1825 );1826 expect(container.firstChild.textContent).toBe(1827 'Caught an unmounting error: E2.' + 'Caught an updating error: E4.',1828 );1829 expect(log).toEqual([1830 // Begin update phase1831 'OuterErrorBoundary componentWillReceiveProps',1832 'OuterErrorBoundary componentWillUpdate',1833 'OuterErrorBoundary render success',1834 'InnerUnmountBoundary componentWillReceiveProps',1835 'InnerUnmountBoundary componentWillUpdate',1836 'InnerUnmountBoundary render success',1837 'InnerUpdateBoundary componentWillReceiveProps',1838 'InnerUpdateBoundary componentWillUpdate',1839 'InnerUpdateBoundary render success',1840 // First come the updates1841 'BrokenComponentDidUpdate componentWillReceiveProps',1842 'BrokenComponentDidUpdate componentWillUpdate',1843 'BrokenComponentDidUpdate render',1844 'BrokenComponentDidUpdate componentWillReceiveProps',1845 'BrokenComponentDidUpdate componentWillUpdate',1846 'BrokenComponentDidUpdate render',1847 // We're in commit phase now, deleting1848 'BrokenComponentWillUnmount componentWillUnmount [!]',1849 'BrokenComponentWillUnmount componentWillUnmount [!]',1850 // Continue despite errors, handle them after commit is done1851 'InnerUnmountBoundary componentDidUpdate',1852 // We're still in commit phase, now calling update lifecycles1853 'BrokenComponentDidUpdate componentDidUpdate [!]',1854 // Again, continue despite errors, we'll handle them later1855 'BrokenComponentDidUpdate componentDidUpdate [!]',1856 'InnerUpdateBoundary componentDidUpdate',1857 'OuterErrorBoundary componentDidUpdate',1858 // After the commit phase, attempt to recover from any errors that1859 // were captured1860 'ErrorBoundary static getDerivedStateFromError',1861 'ErrorBoundary static getDerivedStateFromError',1862 'InnerUnmountBoundary componentWillUpdate',1863 'InnerUnmountBoundary render error',1864 'ErrorBoundary static getDerivedStateFromError',1865 'ErrorBoundary static getDerivedStateFromError',1866 'InnerUpdateBoundary componentWillUpdate',1867 'InnerUpdateBoundary render error',1868 'BrokenComponentDidUpdate componentWillUnmount',1869 'BrokenComponentDidUpdate componentWillUnmount',1870 'InnerUnmountBoundary componentDidUpdate',1871 'InnerUpdateBoundary componentDidUpdate',1872 ]);1873 log.length = 0;1874 ReactDOM.unmountComponentAtNode(container);1875 expect(log).toEqual([1876 'OuterErrorBoundary componentWillUnmount',1877 'InnerUnmountBoundary componentWillUnmount',1878 'InnerUpdateBoundary componentWillUnmount',1879 ]);1880 });1881 it('discards a bad root if the root component fails', () => {1882 const X = null;1883 const Y = undefined;1884 let err1;1885 let err2;1886 try {1887 let container = document.createElement('div');1888 expect(() => ReactDOM.render(<X />, container)).toWarnDev(1889 'React.createElement: type is invalid -- expected a string ' +1890 '(for built-in components) or a class/function ' +1891 '(for composite components) but got: null.',1892 );1893 } catch (err) {1894 err1 = err;1895 }1896 try {1897 let container = document.createElement('div');1898 expect(() => ReactDOM.render(<Y />, container)).toWarnDev(1899 'React.createElement: type is invalid -- expected a string ' +1900 '(for built-in components) or a class/function ' +1901 '(for composite components) but got: undefined.',1902 );1903 } catch (err) {1904 err2 = err;1905 }1906 expect(err1.message).toMatch(/got: null/);1907 expect(err2.message).toMatch(/got: undefined/);1908 });1909 it('renders empty output if error boundary does not handle the error', () => {1910 const container = document.createElement('div');1911 expect(() =>1912 ReactDOM.render(1913 <div>1914 Sibling1915 <NoopErrorBoundary>1916 <BrokenRender />1917 </NoopErrorBoundary>1918 </div>,1919 container,1920 ),1921 ).toThrow('Hello');1922 expect(container.innerHTML).toBe('');1923 expect(log).toEqual([1924 'NoopErrorBoundary constructor',1925 'NoopErrorBoundary componentWillMount',1926 'NoopErrorBoundary render',1927 'BrokenRender constructor',1928 'BrokenRender componentWillMount',1929 'BrokenRender render [!]',1930 // Noop error boundaries retry render (and fail again)1931 'NoopErrorBoundary static getDerivedStateFromError',1932 'NoopErrorBoundary render',1933 'BrokenRender constructor',1934 'BrokenRender componentWillMount',1935 'BrokenRender render [!]',1936 ]);1937 });1938 it('passes first error when two errors happen in commit', () => {1939 const errors = [];1940 let caughtError;1941 class Parent extends React.Component {1942 render() {1943 return <Child />;1944 }1945 componentDidMount() {1946 errors.push('parent sad');1947 throw new Error('parent sad');1948 }1949 }1950 class Child extends React.Component {1951 render() {1952 return <div />;1953 }1954 componentDidMount() {1955 errors.push('child sad');1956 throw new Error('child sad');1957 }1958 }1959 const container = document.createElement('div');1960 try {1961 // Here, we test the behavior where there is no error boundary and we1962 // delegate to the host root.1963 ReactDOM.render(<Parent />, container);1964 } catch (e) {1965 if (e.message !== 'parent sad' && e.message !== 'child sad') {1966 throw e;1967 }1968 caughtError = e;1969 }1970 expect(errors).toEqual(['child sad', 'parent sad']);1971 // Error should be the first thrown1972 expect(caughtError.message).toBe('child sad');1973 });1974 it('propagates uncaught error inside unbatched initial mount', () => {1975 function Foo() {1976 throw new Error('foo error');1977 }1978 const container = document.createElement('div');1979 expect(() => {1980 ReactDOM.unstable_batchedUpdates(() => {1981 ReactDOM.render(<Foo />, container);1982 });1983 }).toThrow('foo error');1984 });1985 it('handles errors that occur in before-mutation commit hook', () => {1986 const errors = [];1987 let caughtError;1988 class Parent extends React.Component {1989 getSnapshotBeforeUpdate() {1990 errors.push('parent sad');1991 throw new Error('parent sad');1992 }1993 componentDidUpdate() {}1994 render() {1995 return <Child {...this.props} />;1996 }1997 }1998 class Child extends React.Component {1999 getSnapshotBeforeUpdate() {2000 errors.push('child sad');2001 throw new Error('child sad');2002 }2003 componentDidUpdate() {}2004 render() {2005 return <div />;2006 }2007 }2008 const container = document.createElement('div');2009 ReactDOM.render(<Parent value={1} />, container);2010 try {2011 ReactDOM.render(<Parent value={2} />, container);2012 } catch (e) {2013 if (e.message !== 'parent sad' && e.message !== 'child sad') {2014 throw e;2015 }2016 caughtError = e;2017 }2018 expect(errors).toEqual(['child sad', 'parent sad']);2019 // Error should be the first thrown2020 expect(caughtError.message).toBe('child sad');2021 });2022 it('should warn if an error boundary with only componentDidCatch does not update state', () => {2023 class InvalidErrorBoundary extends React.Component {2024 componentDidCatch(error, info) {2025 // This component does not define getDerivedStateFromError().2026 // It also doesn't call setState().2027 // So it would swallow errors (which is probably unintentional).2028 }2029 render() {2030 return this.props.children;2031 }2032 }2033 const Throws = () => {2034 throw new Error('expected');2035 };2036 const container = document.createElement('div');2037 expect(() => {2038 ReactDOM.render(2039 <InvalidErrorBoundary>2040 <Throws />2041 </InvalidErrorBoundary>,2042 container,2043 );2044 }).toWarnDev(2045 'InvalidErrorBoundary: Error boundaries should implement getDerivedStateFromError(). ' +2046 'In that method, return a state update to display an error message or fallback UI.',2047 {withoutStack: true},2048 );2049 expect(container.textContent).toBe('');2050 });2051 it('should call both componentDidCatch and getDerivedStateFromError if both exist on a component', () => {2052 let componentDidCatchError, getDerivedStateFromErrorError;2053 class ErrorBoundaryWithBothMethods extends React.Component {2054 state = {error: null};2055 static getDerivedStateFromError(error) {2056 getDerivedStateFromErrorError = error;2057 return {error};2058 }2059 componentDidCatch(error, info) {2060 componentDidCatchError = error;2061 }2062 render() {2063 return this.state.error ? 'ErrorBoundary' : this.props.children;2064 }2065 }2066 const thrownError = new Error('expected');2067 const Throws = () => {2068 throw thrownError;2069 };2070 const container = document.createElement('div');2071 ReactDOM.render(2072 <ErrorBoundaryWithBothMethods>2073 <Throws />2074 </ErrorBoundaryWithBothMethods>,2075 container,2076 );2077 expect(container.textContent).toBe('ErrorBoundary');2078 expect(componentDidCatchError).toBe(thrownError);2079 expect(getDerivedStateFromErrorError).toBe(thrownError);2080 });2081 it('should catch errors from invariants in completion phase', () => {2082 const container = document.createElement('div');2083 ReactDOM.render(2084 <ErrorBoundary>2085 <input>2086 <div />2087 </input>2088 </ErrorBoundary>,2089 container,2090 );2091 expect(container.textContent).toContain(2092 'Caught an error: input is a void element tag',2093 );2094 });...
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 ErrorMessage;30 let NoopErrorBoundary;31 let RetryErrorBoundary;32 let Normal;33 beforeEach(() => {34 jest.resetModules();35 PropTypes = require('prop-types');36 ReactFeatureFlags = require('shared/ReactFeatureFlags');37 ReactFeatureFlags.replayFailedUnitOfWorkWithInvokeGuardedCallback = false;38 ReactDOM = require('react-dom');39 React = require('react');40 log = [];41 BrokenConstructor = class extends React.Component {42 constructor(props) {43 super(props);44 log.push('BrokenConstructor constructor [!]');45 throw new Error('Hello');46 }47 render() {48 log.push('BrokenConstructor render');49 return <div>{this.props.children}</div>;50 }51 UNSAFE_componentWillMount() {52 log.push('BrokenConstructor componentWillMount');53 }54 componentDidMount() {55 log.push('BrokenConstructor componentDidMount');56 }57 UNSAFE_componentWillReceiveProps() {58 log.push('BrokenConstructor componentWillReceiveProps');59 }60 UNSAFE_componentWillUpdate() {61 log.push('BrokenConstructor componentWillUpdate');62 }63 componentDidUpdate() {64 log.push('BrokenConstructor componentDidUpdate');65 }66 componentWillUnmount() {67 log.push('BrokenConstructor componentWillUnmount');68 }69 };70 BrokenComponentWillMount = class extends React.Component {71 constructor(props) {72 super(props);73 log.push('BrokenComponentWillMount constructor');74 }75 render() {76 log.push('BrokenComponentWillMount render');77 return <div>{this.props.children}</div>;78 }79 UNSAFE_componentWillMount() {80 log.push('BrokenComponentWillMount componentWillMount [!]');81 throw new Error('Hello');82 }83 componentDidMount() {84 log.push('BrokenComponentWillMount componentDidMount');85 }86 UNSAFE_componentWillReceiveProps() {87 log.push('BrokenComponentWillMount componentWillReceiveProps');88 }89 UNSAFE_componentWillUpdate() {90 log.push('BrokenComponentWillMount componentWillUpdate');91 }92 componentDidUpdate() {93 log.push('BrokenComponentWillMount componentDidUpdate');94 }95 componentWillUnmount() {96 log.push('BrokenComponentWillMount componentWillUnmount');97 }98 };99 BrokenComponentDidMount = class extends React.Component {100 constructor(props) {101 super(props);102 log.push('BrokenComponentDidMount constructor');103 }104 render() {105 log.push('BrokenComponentDidMount render');106 return <div>{this.props.children}</div>;107 }108 UNSAFE_componentWillMount() {109 log.push('BrokenComponentDidMount componentWillMount');110 }111 componentDidMount() {112 log.push('BrokenComponentDidMount componentDidMount [!]');113 throw new Error('Hello');114 }115 UNSAFE_componentWillReceiveProps() {116 log.push('BrokenComponentDidMount componentWillReceiveProps');117 }118 UNSAFE_componentWillUpdate() {119 log.push('BrokenComponentDidMount componentWillUpdate');120 }121 componentDidUpdate() {122 log.push('BrokenComponentDidMount componentDidUpdate');123 }124 componentWillUnmount() {125 log.push('BrokenComponentDidMount componentWillUnmount');126 }127 };128 BrokenComponentWillReceiveProps = class extends React.Component {129 constructor(props) {130 super(props);131 log.push('BrokenComponentWillReceiveProps constructor');132 }133 render() {134 log.push('BrokenComponentWillReceiveProps render');135 return <div>{this.props.children}</div>;136 }137 UNSAFE_componentWillMount() {138 log.push('BrokenComponentWillReceiveProps componentWillMount');139 }140 componentDidMount() {141 log.push('BrokenComponentWillReceiveProps componentDidMount');142 }143 UNSAFE_componentWillReceiveProps() {144 log.push(145 'BrokenComponentWillReceiveProps componentWillReceiveProps [!]',146 );147 throw new Error('Hello');148 }149 UNSAFE_componentWillUpdate() {150 log.push('BrokenComponentWillReceiveProps componentWillUpdate');151 }152 componentDidUpdate() {153 log.push('BrokenComponentWillReceiveProps componentDidUpdate');154 }155 componentWillUnmount() {156 log.push('BrokenComponentWillReceiveProps componentWillUnmount');157 }158 };159 BrokenComponentWillUpdate = class extends React.Component {160 constructor(props) {161 super(props);162 log.push('BrokenComponentWillUpdate constructor');163 }164 render() {165 log.push('BrokenComponentWillUpdate render');166 return <div>{this.props.children}</div>;167 }168 UNSAFE_componentWillMount() {169 log.push('BrokenComponentWillUpdate componentWillMount');170 }171 componentDidMount() {172 log.push('BrokenComponentWillUpdate componentDidMount');173 }174 UNSAFE_componentWillReceiveProps() {175 log.push('BrokenComponentWillUpdate componentWillReceiveProps');176 }177 UNSAFE_componentWillUpdate() {178 log.push('BrokenComponentWillUpdate componentWillUpdate [!]');179 throw new Error('Hello');180 }181 componentDidUpdate() {182 log.push('BrokenComponentWillUpdate componentDidUpdate');183 }184 componentWillUnmount() {185 log.push('BrokenComponentWillUpdate componentWillUnmount');186 }187 };188 BrokenComponentDidUpdate = class extends React.Component {189 static defaultProps = {190 errorText: 'Hello',191 };192 constructor(props) {193 super(props);194 log.push('BrokenComponentDidUpdate constructor');195 }196 render() {197 log.push('BrokenComponentDidUpdate render');198 return <div>{this.props.children}</div>;199 }200 UNSAFE_componentWillMount() {201 log.push('BrokenComponentDidUpdate componentWillMount');202 }203 componentDidMount() {204 log.push('BrokenComponentDidUpdate componentDidMount');205 }206 UNSAFE_componentWillReceiveProps() {207 log.push('BrokenComponentDidUpdate componentWillReceiveProps');208 }209 UNSAFE_componentWillUpdate() {210 log.push('BrokenComponentDidUpdate componentWillUpdate');211 }212 componentDidUpdate() {213 log.push('BrokenComponentDidUpdate componentDidUpdate [!]');214 throw new Error(this.props.errorText);215 }216 componentWillUnmount() {217 log.push('BrokenComponentDidUpdate componentWillUnmount');218 }219 };220 BrokenComponentWillUnmount = class extends React.Component {221 static defaultProps = {222 errorText: 'Hello',223 };224 constructor(props) {225 super(props);226 log.push('BrokenComponentWillUnmount constructor');227 }228 render() {229 log.push('BrokenComponentWillUnmount render');230 return <div>{this.props.children}</div>;231 }232 UNSAFE_componentWillMount() {233 log.push('BrokenComponentWillUnmount componentWillMount');234 }235 componentDidMount() {236 log.push('BrokenComponentWillUnmount componentDidMount');237 }238 UNSAFE_componentWillReceiveProps() {239 log.push('BrokenComponentWillUnmount componentWillReceiveProps');240 }241 UNSAFE_componentWillUpdate() {242 log.push('BrokenComponentWillUnmount componentWillUpdate');243 }244 componentDidUpdate() {245 log.push('BrokenComponentWillUnmount componentDidUpdate');246 }247 componentWillUnmount() {248 log.push('BrokenComponentWillUnmount componentWillUnmount [!]');249 throw new Error(this.props.errorText);250 }251 };252 BrokenComponentWillMountErrorBoundary = class extends React.Component {253 constructor(props) {254 super(props);255 this.state = {error: null};256 log.push('BrokenComponentWillMountErrorBoundary constructor');257 }258 render() {259 if (this.state.error) {260 log.push('BrokenComponentWillMountErrorBoundary render error');261 return <div>Caught an error: {this.state.error.message}.</div>;262 }263 log.push('BrokenComponentWillMountErrorBoundary render success');264 return <div>{this.props.children}</div>;265 }266 UNSAFE_componentWillMount() {267 log.push(268 'BrokenComponentWillMountErrorBoundary componentWillMount [!]',269 );270 throw new Error('Hello');271 }272 componentDidMount() {273 log.push('BrokenComponentWillMountErrorBoundary componentDidMount');274 }275 componentWillUnmount() {276 log.push('BrokenComponentWillMountErrorBoundary componentWillUnmount');277 }278 componentDidCatch(error) {279 log.push('BrokenComponentWillMountErrorBoundary componentDidCatch');280 this.setState({error});281 }282 };283 BrokenComponentDidMountErrorBoundary = class extends React.Component {284 constructor(props) {285 super(props);286 this.state = {error: null};287 log.push('BrokenComponentDidMountErrorBoundary constructor');288 }289 render() {290 if (this.state.error) {291 log.push('BrokenComponentDidMountErrorBoundary render error');292 return <div>Caught an error: {this.state.error.message}.</div>;293 }294 log.push('BrokenComponentDidMountErrorBoundary render success');295 return <div>{this.props.children}</div>;296 }297 UNSAFE_componentWillMount() {298 log.push('BrokenComponentDidMountErrorBoundary componentWillMount');299 }300 componentDidMount() {301 log.push('BrokenComponentDidMountErrorBoundary componentDidMount [!]');302 throw new Error('Hello');303 }304 componentWillUnmount() {305 log.push('BrokenComponentDidMountErrorBoundary componentWillUnmount');306 }307 componentDidCatch(error) {308 log.push('BrokenComponentDidMountErrorBoundary componentDidCatch');309 this.setState({error});310 }311 };312 BrokenRenderErrorBoundary = class extends React.Component {313 constructor(props) {314 super(props);315 this.state = {error: null};316 log.push('BrokenRenderErrorBoundary constructor');317 }318 render() {319 if (this.state.error) {320 log.push('BrokenRenderErrorBoundary render error [!]');321 throw new Error('Hello');322 }323 log.push('BrokenRenderErrorBoundary render success');324 return <div>{this.props.children}</div>;325 }326 UNSAFE_componentWillMount() {327 log.push('BrokenRenderErrorBoundary componentWillMount');328 }329 componentDidMount() {330 log.push('BrokenRenderErrorBoundary componentDidMount');331 }332 componentWillUnmount() {333 log.push('BrokenRenderErrorBoundary componentWillUnmount');334 }335 componentDidCatch(error) {336 log.push('BrokenRenderErrorBoundary componentDidCatch');337 this.setState({error});338 }339 };340 BrokenRender = class extends React.Component {341 constructor(props) {342 super(props);343 log.push('BrokenRender constructor');344 }345 render() {346 log.push('BrokenRender render [!]');347 throw new Error('Hello');348 }349 UNSAFE_componentWillMount() {350 log.push('BrokenRender componentWillMount');351 }352 componentDidMount() {353 log.push('BrokenRender componentDidMount');354 }355 UNSAFE_componentWillReceiveProps() {356 log.push('BrokenRender componentWillReceiveProps');357 }358 UNSAFE_componentWillUpdate() {359 log.push('BrokenRender componentWillUpdate');360 }361 componentDidUpdate() {362 log.push('BrokenRender componentDidUpdate');363 }364 componentWillUnmount() {365 log.push('BrokenRender componentWillUnmount');366 }367 };368 NoopErrorBoundary = class extends React.Component {369 constructor(props) {370 super(props);371 log.push('NoopErrorBoundary constructor');372 }373 render() {374 log.push('NoopErrorBoundary render');375 return <BrokenRender />;376 }377 UNSAFE_componentWillMount() {378 log.push('NoopErrorBoundary componentWillMount');379 }380 componentDidMount() {381 log.push('NoopErrorBoundary componentDidMount');382 }383 componentWillUnmount() {384 log.push('NoopErrorBoundary componentWillUnmount');385 }386 componentDidCatch() {387 log.push('NoopErrorBoundary componentDidCatch');388 }389 };390 Normal = class extends React.Component {391 static defaultProps = {392 logName: 'Normal',393 };394 constructor(props) {395 super(props);396 log.push(`${this.props.logName} constructor`);397 }398 render() {399 log.push(`${this.props.logName} render`);400 return <div>{this.props.children}</div>;401 }402 UNSAFE_componentWillMount() {403 log.push(`${this.props.logName} componentWillMount`);404 }405 componentDidMount() {406 log.push(`${this.props.logName} componentDidMount`);407 }408 UNSAFE_componentWillReceiveProps() {409 log.push(`${this.props.logName} componentWillReceiveProps`);410 }411 UNSAFE_componentWillUpdate() {412 log.push(`${this.props.logName} componentWillUpdate`);413 }414 componentDidUpdate() {415 log.push(`${this.props.logName} componentDidUpdate`);416 }417 componentWillUnmount() {418 log.push(`${this.props.logName} componentWillUnmount`);419 }420 };421 ErrorBoundary = class extends React.Component {422 constructor(props) {423 super(props);424 this.state = {error: null};425 log.push(`${this.props.logName} constructor`);426 }427 render() {428 if (this.state.error && !this.props.forceRetry) {429 log.push(`${this.props.logName} render error`);430 return this.props.renderError(this.state.error, this.props);431 }432 log.push(`${this.props.logName} render success`);433 return <div>{this.props.children}</div>;434 }435 componentDidCatch(error) {436 log.push(`${this.props.logName} componentDidCatch`);437 this.setState({error});438 }439 UNSAFE_componentWillMount() {440 log.push(`${this.props.logName} componentWillMount`);441 }442 componentDidMount() {443 log.push(`${this.props.logName} componentDidMount`);444 }445 UNSAFE_componentWillReceiveProps() {446 log.push(`${this.props.logName} componentWillReceiveProps`);447 }448 UNSAFE_componentWillUpdate() {449 log.push(`${this.props.logName} componentWillUpdate`);450 }451 componentDidUpdate() {452 log.push(`${this.props.logName} componentDidUpdate`);453 }454 componentWillUnmount() {455 log.push(`${this.props.logName} componentWillUnmount`);456 }457 };458 ErrorBoundary.defaultProps = {459 logName: 'ErrorBoundary',460 renderError(error, props) {461 return (462 <div ref={props.errorMessageRef}>463 Caught an error: {error.message}.464 </div>465 );466 },467 };468 RetryErrorBoundary = class extends React.Component {469 constructor(props) {470 super(props);471 log.push('RetryErrorBoundary constructor');472 }473 render() {474 log.push('RetryErrorBoundary render');475 return <BrokenRender />;476 }477 UNSAFE_componentWillMount() {478 log.push('RetryErrorBoundary componentWillMount');479 }480 componentDidMount() {481 log.push('RetryErrorBoundary componentDidMount');482 }483 componentWillUnmount() {484 log.push('RetryErrorBoundary componentWillUnmount');485 }486 componentDidCatch(error) {487 log.push('RetryErrorBoundary componentDidCatch [!]');488 // In Fiber, calling setState() (and failing) is treated as a rethrow.489 this.setState({});490 }491 };492 ErrorMessage = class extends React.Component {493 constructor(props) {494 super(props);495 log.push('ErrorMessage constructor');496 }497 UNSAFE_componentWillMount() {498 log.push('ErrorMessage componentWillMount');499 }500 componentDidMount() {501 log.push('ErrorMessage componentDidMount');502 }503 componentWillUnmount() {504 log.push('ErrorMessage componentWillUnmount');505 }506 render() {507 log.push('ErrorMessage render');508 return <div>Caught an error: {this.props.message}.</div>;509 }510 };511 });512 it('does not swallow exceptions on mounting without boundaries', () => {513 let container = document.createElement('div');514 expect(() => {515 ReactDOM.render(<BrokenRender />, container);516 }).toThrow('Hello');517 container = document.createElement('div');518 expect(() => {519 ReactDOM.render(<BrokenComponentWillMount />, container);520 }).toThrow('Hello');521 container = document.createElement('div');522 expect(() => {523 ReactDOM.render(<BrokenComponentDidMount />, container);524 }).toThrow('Hello');525 });526 it('does not swallow exceptions on updating without boundaries', () => {527 let container = document.createElement('div');528 ReactDOM.render(<BrokenComponentWillUpdate />, container);529 expect(() => {530 ReactDOM.render(<BrokenComponentWillUpdate />, container);531 }).toThrow('Hello');532 container = document.createElement('div');533 ReactDOM.render(<BrokenComponentWillReceiveProps />, container);534 expect(() => {535 ReactDOM.render(<BrokenComponentWillReceiveProps />, container);536 }).toThrow('Hello');537 container = document.createElement('div');538 ReactDOM.render(<BrokenComponentDidUpdate />, container);539 expect(() => {540 ReactDOM.render(<BrokenComponentDidUpdate />, container);541 }).toThrow('Hello');542 });543 it('does not swallow exceptions on unmounting without boundaries', () => {544 const container = document.createElement('div');545 ReactDOM.render(<BrokenComponentWillUnmount />, container);546 expect(() => {547 ReactDOM.unmountComponentAtNode(container);548 }).toThrow('Hello');549 });550 it('prevents errors from leaking into other roots', () => {551 const container1 = document.createElement('div');552 const container2 = document.createElement('div');553 const container3 = document.createElement('div');554 ReactDOM.render(<span>Before 1</span>, container1);555 expect(() => {556 ReactDOM.render(<BrokenRender />, container2);557 }).toThrow('Hello');558 ReactDOM.render(559 <ErrorBoundary>560 <BrokenRender />561 </ErrorBoundary>,562 container3,563 );564 expect(container1.firstChild.textContent).toBe('Before 1');565 expect(container2.firstChild).toBe(null);566 expect(container3.firstChild.textContent).toBe('Caught an error: Hello.');567 ReactDOM.render(<span>After 1</span>, container1);568 ReactDOM.render(<span>After 2</span>, container2);569 ReactDOM.render(570 <ErrorBoundary forceRetry={true}>After 3</ErrorBoundary>,571 container3,572 );573 expect(container1.firstChild.textContent).toBe('After 1');574 expect(container2.firstChild.textContent).toBe('After 2');575 expect(container3.firstChild.textContent).toBe('After 3');576 ReactDOM.unmountComponentAtNode(container1);577 ReactDOM.unmountComponentAtNode(container2);578 ReactDOM.unmountComponentAtNode(container3);579 expect(container1.firstChild).toBe(null);580 expect(container2.firstChild).toBe(null);581 expect(container3.firstChild).toBe(null);582 });583 it('renders an error state if child throws in render', () => {584 const container = document.createElement('div');585 ReactDOM.render(586 <ErrorBoundary>587 <BrokenRender />588 </ErrorBoundary>,589 container,590 );591 expect(container.firstChild.textContent).toBe('Caught an error: Hello.');592 expect(log).toEqual([593 'ErrorBoundary constructor',594 'ErrorBoundary componentWillMount',595 'ErrorBoundary render success',596 'BrokenRender constructor',597 'BrokenRender componentWillMount',598 'BrokenRender render [!]',599 // Fiber mounts with null children before capturing error600 'ErrorBoundary componentDidMount',601 // Catch and render an error message602 'ErrorBoundary componentDidCatch',603 'ErrorBoundary componentWillUpdate',604 'ErrorBoundary render error',605 'ErrorBoundary componentDidUpdate',606 ]);607 log.length = 0;608 ReactDOM.unmountComponentAtNode(container);609 expect(log).toEqual(['ErrorBoundary componentWillUnmount']);610 });611 it('renders an error state if child throws in constructor', () => {612 const container = document.createElement('div');613 ReactDOM.render(614 <ErrorBoundary>615 <BrokenConstructor />616 </ErrorBoundary>,617 container,618 );619 expect(container.firstChild.textContent).toBe('Caught an error: Hello.');620 expect(log).toEqual([621 'ErrorBoundary constructor',622 'ErrorBoundary componentWillMount',623 'ErrorBoundary render success',624 'BrokenConstructor constructor [!]',625 // Fiber mounts with null children before capturing error626 'ErrorBoundary componentDidMount',627 // Catch and render an error message628 'ErrorBoundary componentDidCatch',629 'ErrorBoundary componentWillUpdate',630 'ErrorBoundary render error',631 'ErrorBoundary componentDidUpdate',632 ]);633 log.length = 0;634 ReactDOM.unmountComponentAtNode(container);635 expect(log).toEqual(['ErrorBoundary componentWillUnmount']);636 });637 it('renders an error state if child throws in componentWillMount', () => {638 const container = document.createElement('div');639 ReactDOM.render(640 <ErrorBoundary>641 <BrokenComponentWillMount />642 </ErrorBoundary>,643 container,644 );645 expect(container.firstChild.textContent).toBe('Caught an error: Hello.');646 expect(log).toEqual([647 'ErrorBoundary constructor',648 'ErrorBoundary componentWillMount',649 'ErrorBoundary render success',650 'BrokenComponentWillMount constructor',651 'BrokenComponentWillMount componentWillMount [!]',652 'ErrorBoundary componentDidMount',653 'ErrorBoundary componentDidCatch',654 'ErrorBoundary componentWillUpdate',655 'ErrorBoundary render error',656 'ErrorBoundary componentDidUpdate',657 ]);658 log.length = 0;659 ReactDOM.unmountComponentAtNode(container);660 expect(log).toEqual(['ErrorBoundary componentWillUnmount']);661 });662 it('renders an error state if context provider throws in componentWillMount', () => {663 class BrokenComponentWillMountWithContext extends React.Component {664 static childContextTypes = {foo: PropTypes.number};665 getChildContext() {666 return {foo: 42};667 }668 render() {669 return <div>{this.props.children}</div>;670 }671 UNSAFE_componentWillMount() {672 throw new Error('Hello');673 }674 }675 const container = document.createElement('div');676 ReactDOM.render(677 <ErrorBoundary>678 <BrokenComponentWillMountWithContext />679 </ErrorBoundary>,680 container,681 );682 expect(container.firstChild.textContent).toBe('Caught an error: Hello.');683 });684 it('renders an error state if module-style context provider throws in componentWillMount', () => {685 function BrokenComponentWillMountWithContext() {686 return {687 getChildContext() {688 return {foo: 42};689 },690 render() {691 return <div>{this.props.children}</div>;692 },693 UNSAFE_componentWillMount() {694 throw new Error('Hello');695 },696 };697 }698 BrokenComponentWillMountWithContext.childContextTypes = {699 foo: PropTypes.number,700 };701 const container = document.createElement('div');702 ReactDOM.render(703 <ErrorBoundary>704 <BrokenComponentWillMountWithContext />705 </ErrorBoundary>,706 container,707 );708 expect(container.firstChild.textContent).toBe('Caught an error: Hello.');709 });710 it('mounts the error message if mounting fails', () => {711 function renderError(error) {712 return <ErrorMessage message={error.message} />;713 }714 const container = document.createElement('div');715 ReactDOM.render(716 <ErrorBoundary renderError={renderError}>717 <BrokenRender />718 </ErrorBoundary>,719 container,720 );721 expect(log).toEqual([722 'ErrorBoundary constructor',723 'ErrorBoundary componentWillMount',724 'ErrorBoundary render success',725 'BrokenRender constructor',726 'BrokenRender componentWillMount',727 'BrokenRender render [!]',728 'ErrorBoundary componentDidMount',729 'ErrorBoundary componentDidCatch',730 'ErrorBoundary componentWillUpdate',731 'ErrorBoundary render error',732 'ErrorMessage constructor',733 'ErrorMessage componentWillMount',734 'ErrorMessage render',735 'ErrorMessage componentDidMount',736 'ErrorBoundary componentDidUpdate',737 ]);738 log.length = 0;739 ReactDOM.unmountComponentAtNode(container);740 expect(log).toEqual([741 'ErrorBoundary componentWillUnmount',742 'ErrorMessage componentWillUnmount',743 ]);744 });745 it('propagates errors on retry on mounting', () => {746 const container = document.createElement('div');747 ReactDOM.render(748 <ErrorBoundary>749 <RetryErrorBoundary>750 <BrokenRender />751 </RetryErrorBoundary>752 </ErrorBoundary>,753 container,754 );755 expect(container.firstChild.textContent).toBe('Caught an error: Hello.');756 expect(log).toEqual([757 'ErrorBoundary constructor',758 'ErrorBoundary componentWillMount',759 'ErrorBoundary render success',760 'RetryErrorBoundary constructor',761 'RetryErrorBoundary componentWillMount',762 'RetryErrorBoundary render',763 'BrokenRender constructor',764 'BrokenRender componentWillMount',765 'BrokenRender render [!]',766 // In Fiber, failed error boundaries render null before attempting to recover767 'RetryErrorBoundary componentDidMount',768 'RetryErrorBoundary componentDidCatch [!]',769 'ErrorBoundary componentDidMount',770 // Retry771 'RetryErrorBoundary render',772 'BrokenRender constructor',773 'BrokenRender componentWillMount',774 'BrokenRender render [!]',775 // This time, the error propagates to the higher boundary776 'RetryErrorBoundary componentWillUnmount',777 'ErrorBoundary componentDidCatch',778 // Render the error779 'ErrorBoundary componentWillUpdate',780 'ErrorBoundary render error',781 'ErrorBoundary componentDidUpdate',782 ]);783 log.length = 0;784 ReactDOM.unmountComponentAtNode(container);785 expect(log).toEqual(['ErrorBoundary componentWillUnmount']);786 });787 it('propagates errors inside boundary during componentWillMount', () => {788 const container = document.createElement('div');789 ReactDOM.render(790 <ErrorBoundary>791 <BrokenComponentWillMountErrorBoundary />792 </ErrorBoundary>,793 container,794 );795 expect(container.firstChild.textContent).toBe('Caught an error: Hello.');796 expect(log).toEqual([797 'ErrorBoundary constructor',798 'ErrorBoundary componentWillMount',799 'ErrorBoundary render success',800 'BrokenComponentWillMountErrorBoundary constructor',801 'BrokenComponentWillMountErrorBoundary componentWillMount [!]',802 // The error propagates to the higher boundary803 'ErrorBoundary componentDidMount',804 'ErrorBoundary componentDidCatch',805 'ErrorBoundary componentWillUpdate',806 'ErrorBoundary render error',807 'ErrorBoundary componentDidUpdate',808 ]);809 log.length = 0;810 ReactDOM.unmountComponentAtNode(container);811 expect(log).toEqual(['ErrorBoundary componentWillUnmount']);812 });813 it('propagates errors inside boundary while rendering error state', () => {814 const container = document.createElement('div');815 ReactDOM.render(816 <ErrorBoundary>817 <BrokenRenderErrorBoundary>818 <BrokenRender />819 </BrokenRenderErrorBoundary>820 </ErrorBoundary>,821 container,822 );823 expect(container.firstChild.textContent).toBe('Caught an error: Hello.');824 expect(log).toEqual([825 'ErrorBoundary constructor',826 'ErrorBoundary componentWillMount',827 'ErrorBoundary render success',828 'BrokenRenderErrorBoundary constructor',829 'BrokenRenderErrorBoundary componentWillMount',830 'BrokenRenderErrorBoundary render success',831 'BrokenRender constructor',832 'BrokenRender componentWillMount',833 'BrokenRender render [!]',834 // The first error boundary catches the error835 // It adjusts state but throws displaying the message836 // Finish mounting with null children837 'BrokenRenderErrorBoundary componentDidMount',838 // Attempt to handle the error839 'BrokenRenderErrorBoundary componentDidCatch',840 'ErrorBoundary componentDidMount',841 'BrokenRenderErrorBoundary render error [!]',842 // Boundary fails with new error, propagate to next boundary843 'BrokenRenderErrorBoundary componentWillUnmount',844 // Attempt to handle the error again845 'ErrorBoundary componentDidCatch',846 'ErrorBoundary componentWillUpdate',847 'ErrorBoundary render error',848 'ErrorBoundary componentDidUpdate',849 ]);850 log.length = 0;851 ReactDOM.unmountComponentAtNode(container);852 expect(log).toEqual(['ErrorBoundary componentWillUnmount']);853 });854 it('does not call componentWillUnmount when aborting initial mount', () => {855 const container = document.createElement('div');856 ReactDOM.render(857 <ErrorBoundary>858 <Normal />859 <BrokenRender />860 <Normal />861 </ErrorBoundary>,862 container,863 );864 expect(container.firstChild.textContent).toBe('Caught an error: Hello.');865 expect(log).toEqual([866 'ErrorBoundary constructor',867 'ErrorBoundary componentWillMount',868 'ErrorBoundary render success',869 // Render first child870 'Normal constructor',871 'Normal componentWillMount',872 'Normal render',873 // Render second child (it throws)874 'BrokenRender constructor',875 'BrokenRender componentWillMount',876 'BrokenRender render [!]',877 // Render third child, even though an earlier sibling threw.878 'Normal constructor',879 'Normal componentWillMount',880 'Normal render',881 // Finish mounting with null children882 'ErrorBoundary componentDidMount',883 // Handle the error884 'ErrorBoundary componentDidCatch',885 // Render the error message886 'ErrorBoundary componentWillUpdate',887 'ErrorBoundary render error',888 'ErrorBoundary componentDidUpdate',889 ]);890 log.length = 0;891 ReactDOM.unmountComponentAtNode(container);892 expect(log).toEqual(['ErrorBoundary componentWillUnmount']);893 });894 it('resets callback refs if mounting aborts', () => {895 function childRef(x) {896 log.push('Child ref is set to ' + x);897 }898 function errorMessageRef(x) {899 log.push('Error message ref is set to ' + x);900 }901 const container = document.createElement('div');902 ReactDOM.render(903 <ErrorBoundary errorMessageRef={errorMessageRef}>904 <div ref={childRef} />905 <BrokenRender />906 </ErrorBoundary>,907 container,908 );909 expect(container.textContent).toBe('Caught an error: Hello.');910 expect(log).toEqual([911 'ErrorBoundary constructor',912 'ErrorBoundary componentWillMount',913 'ErrorBoundary render success',914 'BrokenRender constructor',915 'BrokenRender componentWillMount',916 'BrokenRender render [!]',917 // Handle error:918 // Finish mounting with null children919 'ErrorBoundary componentDidMount',920 // Handle the error921 'ErrorBoundary componentDidCatch',922 // Render the error message923 'ErrorBoundary componentWillUpdate',924 'ErrorBoundary render error',925 'Error message ref is set to [object HTMLDivElement]',926 'ErrorBoundary componentDidUpdate',927 ]);928 log.length = 0;929 ReactDOM.unmountComponentAtNode(container);930 expect(log).toEqual([931 'ErrorBoundary componentWillUnmount',932 'Error message ref is set to null',933 ]);934 });935 it('resets object refs if mounting aborts', () => {936 let childRef = React.createRef();937 let errorMessageRef = React.createRef();938 const container = document.createElement('div');939 ReactDOM.render(940 <ErrorBoundary errorMessageRef={errorMessageRef}>941 <div ref={childRef} />942 <BrokenRender />943 </ErrorBoundary>,944 container,945 );946 expect(container.textContent).toBe('Caught an error: Hello.');947 expect(log).toEqual([948 'ErrorBoundary constructor',949 'ErrorBoundary componentWillMount',950 'ErrorBoundary render success',951 'BrokenRender constructor',952 'BrokenRender componentWillMount',953 'BrokenRender render [!]',954 // Handle error:955 // Finish mounting with null children956 'ErrorBoundary componentDidMount',957 // Handle the error958 'ErrorBoundary componentDidCatch',959 // Render the error message960 'ErrorBoundary componentWillUpdate',961 'ErrorBoundary render error',962 'ErrorBoundary componentDidUpdate',963 ]);964 expect(errorMessageRef.current.toString()).toEqual(965 '[object HTMLDivElement]',966 );967 log.length = 0;968 ReactDOM.unmountComponentAtNode(container);969 expect(log).toEqual(['ErrorBoundary componentWillUnmount']);970 expect(errorMessageRef.current).toEqual(null);971 });972 it('successfully mounts if no error occurs', () => {973 const container = document.createElement('div');974 ReactDOM.render(975 <ErrorBoundary>976 <div>Mounted successfully.</div>977 </ErrorBoundary>,978 container,979 );980 expect(container.firstChild.textContent).toBe('Mounted successfully.');981 expect(log).toEqual([982 'ErrorBoundary constructor',983 'ErrorBoundary componentWillMount',984 'ErrorBoundary render success',985 'ErrorBoundary componentDidMount',986 ]);987 log.length = 0;988 ReactDOM.unmountComponentAtNode(container);989 expect(log).toEqual(['ErrorBoundary componentWillUnmount']);990 });991 it('catches if child throws in constructor during update', () => {992 const container = document.createElement('div');993 ReactDOM.render(994 <ErrorBoundary>995 <Normal />996 </ErrorBoundary>,997 container,998 );999 log.length = 0;1000 ReactDOM.render(1001 <ErrorBoundary>1002 <Normal />1003 <Normal logName="Normal2" />1004 <BrokenConstructor />1005 </ErrorBoundary>,1006 container,1007 );1008 expect(container.textContent).toBe('Caught an error: Hello.');1009 expect(log).toEqual([1010 'ErrorBoundary componentWillReceiveProps',1011 'ErrorBoundary componentWillUpdate',1012 'ErrorBoundary render success',1013 'Normal componentWillReceiveProps',1014 'Normal componentWillUpdate',1015 'Normal render',1016 // Normal2 will attempt to mount:1017 'Normal2 constructor',1018 'Normal2 componentWillMount',1019 'Normal2 render',1020 // BrokenConstructor will abort rendering:1021 'BrokenConstructor constructor [!]',1022 // Finish updating with null children1023 'Normal componentWillUnmount',1024 'ErrorBoundary componentDidUpdate',1025 // Handle the error1026 'ErrorBoundary componentDidCatch',1027 // Render the error message1028 'ErrorBoundary componentWillUpdate',1029 'ErrorBoundary render error',1030 'ErrorBoundary componentDidUpdate',1031 ]);1032 log.length = 0;1033 ReactDOM.unmountComponentAtNode(container);1034 expect(log).toEqual(['ErrorBoundary componentWillUnmount']);1035 });1036 it('catches if child throws in componentWillMount during update', () => {1037 const container = document.createElement('div');1038 ReactDOM.render(1039 <ErrorBoundary>1040 <Normal />1041 </ErrorBoundary>,1042 container,1043 );1044 log.length = 0;1045 ReactDOM.render(1046 <ErrorBoundary>1047 <Normal />1048 <Normal logName="Normal2" />1049 <BrokenComponentWillMount />1050 </ErrorBoundary>,1051 container,1052 );1053 expect(container.textContent).toBe('Caught an error: Hello.');1054 expect(log).toEqual([1055 'ErrorBoundary componentWillReceiveProps',1056 'ErrorBoundary componentWillUpdate',1057 'ErrorBoundary render success',1058 'Normal componentWillReceiveProps',1059 'Normal componentWillUpdate',1060 'Normal render',1061 // Normal2 will attempt to mount:1062 'Normal2 constructor',1063 'Normal2 componentWillMount',1064 'Normal2 render',1065 // BrokenComponentWillMount will abort rendering:1066 'BrokenComponentWillMount constructor',1067 'BrokenComponentWillMount componentWillMount [!]',1068 // Finish updating with null children1069 'Normal componentWillUnmount',1070 'ErrorBoundary componentDidUpdate',1071 // Handle the error1072 'ErrorBoundary componentDidCatch',1073 // Render the error message1074 'ErrorBoundary componentWillUpdate',1075 'ErrorBoundary render error',1076 'ErrorBoundary componentDidUpdate',1077 ]);1078 log.length = 0;1079 ReactDOM.unmountComponentAtNode(container);1080 expect(log).toEqual(['ErrorBoundary componentWillUnmount']);1081 });1082 it('catches if child throws in componentWillReceiveProps during update', () => {1083 const container = document.createElement('div');1084 ReactDOM.render(1085 <ErrorBoundary>1086 <Normal />1087 <BrokenComponentWillReceiveProps />1088 </ErrorBoundary>,1089 container,1090 );1091 log.length = 0;1092 ReactDOM.render(1093 <ErrorBoundary>1094 <Normal />1095 <BrokenComponentWillReceiveProps />1096 </ErrorBoundary>,1097 container,1098 );1099 expect(container.textContent).toBe('Caught an error: Hello.');1100 expect(log).toEqual([1101 'ErrorBoundary componentWillReceiveProps',1102 'ErrorBoundary componentWillUpdate',1103 'ErrorBoundary render success',1104 'Normal componentWillReceiveProps',1105 'Normal componentWillUpdate',1106 'Normal render',1107 // BrokenComponentWillReceiveProps will abort rendering:1108 'BrokenComponentWillReceiveProps componentWillReceiveProps [!]',1109 // Finish updating with null children1110 'Normal componentWillUnmount',1111 'BrokenComponentWillReceiveProps componentWillUnmount',1112 'ErrorBoundary componentDidUpdate',1113 // Handle the error1114 'ErrorBoundary componentDidCatch',1115 'ErrorBoundary componentWillUpdate',1116 'ErrorBoundary render error',1117 'ErrorBoundary componentDidUpdate',1118 ]);1119 log.length = 0;1120 ReactDOM.unmountComponentAtNode(container);1121 expect(log).toEqual(['ErrorBoundary componentWillUnmount']);1122 });1123 it('catches if child throws in componentWillUpdate during update', () => {1124 const container = document.createElement('div');1125 ReactDOM.render(1126 <ErrorBoundary>1127 <Normal />1128 <BrokenComponentWillUpdate />1129 </ErrorBoundary>,1130 container,1131 );1132 log.length = 0;1133 ReactDOM.render(1134 <ErrorBoundary>1135 <Normal />1136 <BrokenComponentWillUpdate />1137 </ErrorBoundary>,1138 container,1139 );1140 expect(container.textContent).toBe('Caught an error: Hello.');1141 expect(log).toEqual([1142 'ErrorBoundary componentWillReceiveProps',1143 'ErrorBoundary componentWillUpdate',1144 'ErrorBoundary render success',1145 'Normal componentWillReceiveProps',1146 'Normal componentWillUpdate',1147 'Normal render',1148 // BrokenComponentWillUpdate will abort rendering:1149 'BrokenComponentWillUpdate componentWillReceiveProps',1150 'BrokenComponentWillUpdate componentWillUpdate [!]',1151 // Finish updating with null children1152 'Normal componentWillUnmount',1153 'BrokenComponentWillUpdate componentWillUnmount',1154 'ErrorBoundary componentDidUpdate',1155 // Handle the error1156 'ErrorBoundary componentDidCatch',1157 'ErrorBoundary componentWillUpdate',1158 'ErrorBoundary render error',1159 'ErrorBoundary componentDidUpdate',1160 ]);1161 log.length = 0;1162 ReactDOM.unmountComponentAtNode(container);1163 expect(log).toEqual(['ErrorBoundary componentWillUnmount']);1164 });1165 it('catches if child throws in render during update', () => {1166 const container = document.createElement('div');1167 ReactDOM.render(1168 <ErrorBoundary>1169 <Normal />1170 </ErrorBoundary>,1171 container,1172 );1173 log.length = 0;1174 ReactDOM.render(1175 <ErrorBoundary>1176 <Normal />1177 <Normal logName="Normal2" />1178 <BrokenRender />1179 </ErrorBoundary>,1180 container,1181 );1182 expect(container.textContent).toBe('Caught an error: Hello.');1183 expect(log).toEqual([1184 'ErrorBoundary componentWillReceiveProps',1185 'ErrorBoundary componentWillUpdate',1186 'ErrorBoundary render success',1187 'Normal componentWillReceiveProps',1188 'Normal componentWillUpdate',1189 'Normal render',1190 // Normal2 will attempt to mount:1191 'Normal2 constructor',1192 'Normal2 componentWillMount',1193 'Normal2 render',1194 // BrokenRender will abort rendering:1195 'BrokenRender constructor',1196 'BrokenRender componentWillMount',1197 'BrokenRender render [!]',1198 // Finish updating with null children1199 'Normal componentWillUnmount',1200 'ErrorBoundary componentDidUpdate',1201 // Handle the error1202 'ErrorBoundary componentDidCatch',1203 'ErrorBoundary componentWillUpdate',1204 'ErrorBoundary render error',1205 'ErrorBoundary componentDidUpdate',1206 ]);1207 log.length = 0;1208 ReactDOM.unmountComponentAtNode(container);1209 expect(log).toEqual(['ErrorBoundary componentWillUnmount']);1210 });1211 it('keeps refs up-to-date during updates', () => {1212 function child1Ref(x) {1213 log.push('Child1 ref is set to ' + x);1214 }1215 function child2Ref(x) {1216 log.push('Child2 ref is set to ' + x);1217 }1218 function errorMessageRef(x) {1219 log.push('Error message ref is set to ' + x);1220 }1221 const container = document.createElement('div');1222 ReactDOM.render(1223 <ErrorBoundary errorMessageRef={errorMessageRef}>1224 <div ref={child1Ref} />1225 </ErrorBoundary>,1226 container,1227 );1228 expect(log).toEqual([1229 'ErrorBoundary constructor',1230 'ErrorBoundary componentWillMount',1231 'ErrorBoundary render success',1232 'Child1 ref is set to [object HTMLDivElement]',1233 'ErrorBoundary componentDidMount',1234 ]);1235 log.length = 0;1236 ReactDOM.render(1237 <ErrorBoundary errorMessageRef={errorMessageRef}>1238 <div ref={child1Ref} />1239 <div ref={child2Ref} />1240 <BrokenRender />1241 </ErrorBoundary>,1242 container,1243 );1244 expect(container.textContent).toBe('Caught an error: Hello.');1245 expect(log).toEqual([1246 'ErrorBoundary componentWillReceiveProps',1247 'ErrorBoundary componentWillUpdate',1248 'ErrorBoundary render success',1249 // BrokenRender will abort rendering:1250 'BrokenRender constructor',1251 'BrokenRender componentWillMount',1252 'BrokenRender render [!]',1253 // Finish updating with null children1254 'Child1 ref is set to null',1255 'ErrorBoundary componentDidUpdate',1256 // Handle the error1257 'ErrorBoundary componentDidCatch',1258 'ErrorBoundary componentWillUpdate',1259 'ErrorBoundary render error',1260 'Error message ref is set to [object HTMLDivElement]',1261 // Child2 ref is never set because its mounting aborted1262 'ErrorBoundary componentDidUpdate',1263 ]);1264 log.length = 0;1265 ReactDOM.unmountComponentAtNode(container);1266 expect(log).toEqual([1267 'ErrorBoundary componentWillUnmount',1268 'Error message ref is set to null',1269 ]);1270 });1271 it('recovers from componentWillUnmount errors on update', () => {1272 const container = document.createElement('div');1273 ReactDOM.render(1274 <ErrorBoundary>1275 <BrokenComponentWillUnmount />1276 <BrokenComponentWillUnmount />1277 <Normal />1278 </ErrorBoundary>,1279 container,1280 );1281 log.length = 0;1282 ReactDOM.render(1283 <ErrorBoundary>1284 <BrokenComponentWillUnmount />1285 </ErrorBoundary>,1286 container,1287 );1288 expect(container.textContent).toBe('Caught an error: Hello.');1289 expect(log).toEqual([1290 'ErrorBoundary componentWillReceiveProps',1291 'ErrorBoundary componentWillUpdate',1292 'ErrorBoundary render success',1293 // Update existing child:1294 'BrokenComponentWillUnmount componentWillReceiveProps',1295 'BrokenComponentWillUnmount componentWillUpdate',1296 'BrokenComponentWillUnmount render',1297 // Unmounting throws:1298 'BrokenComponentWillUnmount componentWillUnmount [!]',1299 // Fiber proceeds with lifecycles despite errors1300 'Normal componentWillUnmount',1301 // The components have updated in this phase1302 'BrokenComponentWillUnmount componentDidUpdate',1303 'ErrorBoundary componentDidUpdate',1304 // Now that commit phase is done, Fiber unmounts the boundary's children1305 'BrokenComponentWillUnmount componentWillUnmount [!]',1306 'ErrorBoundary componentDidCatch',1307 // The initial render was aborted, so1308 // Fiber retries from the root.1309 'ErrorBoundary componentWillUpdate',1310 'ErrorBoundary componentDidUpdate',1311 // The second willUnmount error should be captured and logged, too.1312 'ErrorBoundary componentDidCatch',1313 'ErrorBoundary componentWillUpdate',1314 // Render an error now (stack will do it later)1315 'ErrorBoundary render error',1316 // Attempt to unmount previous child:1317 // Done1318 'ErrorBoundary componentDidUpdate',1319 ]);1320 log.length = 0;1321 ReactDOM.unmountComponentAtNode(container);1322 expect(log).toEqual(['ErrorBoundary componentWillUnmount']);1323 });1324 it('recovers from nested componentWillUnmount errors on update', () => {1325 const container = document.createElement('div');1326 ReactDOM.render(1327 <ErrorBoundary>1328 <Normal>1329 <BrokenComponentWillUnmount />1330 </Normal>1331 <BrokenComponentWillUnmount />1332 </ErrorBoundary>,1333 container,1334 );1335 log.length = 0;1336 ReactDOM.render(1337 <ErrorBoundary>1338 <Normal>1339 <BrokenComponentWillUnmount />1340 </Normal>1341 </ErrorBoundary>,1342 container,1343 );1344 expect(container.textContent).toBe('Caught an error: Hello.');1345 expect(log).toEqual([1346 'ErrorBoundary componentWillReceiveProps',1347 'ErrorBoundary componentWillUpdate',1348 'ErrorBoundary render success',1349 // Update existing children:1350 'Normal componentWillReceiveProps',1351 'Normal componentWillUpdate',1352 'Normal render',1353 'BrokenComponentWillUnmount componentWillReceiveProps',1354 'BrokenComponentWillUnmount componentWillUpdate',1355 'BrokenComponentWillUnmount render',1356 // Unmounting throws:1357 'BrokenComponentWillUnmount componentWillUnmount [!]',1358 // Fiber proceeds with lifecycles despite errors1359 'BrokenComponentWillUnmount componentDidUpdate',1360 'Normal componentDidUpdate',1361 'ErrorBoundary componentDidUpdate',1362 'Normal componentWillUnmount',1363 'BrokenComponentWillUnmount componentWillUnmount [!]',1364 // Now that commit phase is done, Fiber handles errors1365 'ErrorBoundary componentDidCatch',1366 // The initial render was aborted, so1367 // Fiber retries from the root.1368 'ErrorBoundary componentWillUpdate',1369 'ErrorBoundary componentDidUpdate',1370 // The second willUnmount error should be captured and logged, too.1371 'ErrorBoundary componentDidCatch',1372 'ErrorBoundary componentWillUpdate',1373 // Render an error now (stack will do it later)1374 'ErrorBoundary render error',1375 // Done1376 'ErrorBoundary componentDidUpdate',1377 ]);1378 log.length = 0;1379 ReactDOM.unmountComponentAtNode(container);1380 expect(log).toEqual(['ErrorBoundary componentWillUnmount']);1381 });1382 it('picks the right boundary when handling unmounting errors', () => {1383 function renderInnerError(error) {1384 return <div>Caught an inner error: {error.message}.</div>;1385 }1386 function renderOuterError(error) {1387 return <div>Caught an outer error: {error.message}.</div>;1388 }1389 const container = document.createElement('div');1390 ReactDOM.render(1391 <ErrorBoundary1392 logName="OuterErrorBoundary"1393 renderError={renderOuterError}>1394 <ErrorBoundary1395 logName="InnerErrorBoundary"1396 renderError={renderInnerError}>1397 <BrokenComponentWillUnmount />1398 </ErrorBoundary>1399 </ErrorBoundary>,1400 container,1401 );1402 log.length = 0;1403 ReactDOM.render(1404 <ErrorBoundary1405 logName="OuterErrorBoundary"1406 renderError={renderOuterError}>1407 <ErrorBoundary1408 logName="InnerErrorBoundary"1409 renderError={renderInnerError}1410 />1411 </ErrorBoundary>,1412 container,1413 );1414 expect(container.textContent).toBe('Caught an inner error: Hello.');1415 expect(log).toEqual([1416 // Update outer boundary1417 'OuterErrorBoundary componentWillReceiveProps',1418 'OuterErrorBoundary componentWillUpdate',1419 'OuterErrorBoundary render success',1420 // Update inner boundary1421 'InnerErrorBoundary componentWillReceiveProps',1422 'InnerErrorBoundary componentWillUpdate',1423 'InnerErrorBoundary render success',1424 // Try unmounting child1425 'BrokenComponentWillUnmount componentWillUnmount [!]',1426 // Fiber proceeds with lifecycles despite errors1427 // Inner and outer boundaries have updated in this phase1428 'InnerErrorBoundary componentDidUpdate',1429 'OuterErrorBoundary componentDidUpdate',1430 // Now that commit phase is done, Fiber handles errors1431 // Only inner boundary receives the error:1432 'InnerErrorBoundary componentDidCatch',1433 'InnerErrorBoundary componentWillUpdate',1434 // Render an error now1435 'InnerErrorBoundary render error',1436 // In Fiber, this was a local update to the1437 // inner boundary so only its hook fires1438 'InnerErrorBoundary componentDidUpdate',1439 ]);1440 log.length = 0;1441 ReactDOM.unmountComponentAtNode(container);1442 expect(log).toEqual([1443 'OuterErrorBoundary componentWillUnmount',1444 'InnerErrorBoundary componentWillUnmount',1445 ]);1446 });1447 it('can recover from error state', () => {1448 const container = document.createElement('div');1449 ReactDOM.render(1450 <ErrorBoundary>1451 <BrokenRender />1452 </ErrorBoundary>,1453 container,1454 );1455 ReactDOM.render(1456 <ErrorBoundary>1457 <Normal />1458 </ErrorBoundary>,1459 container,1460 );1461 // Error boundary doesn't retry by itself:1462 expect(container.textContent).toBe('Caught an error: Hello.');1463 // Force the success path:1464 log.length = 0;1465 ReactDOM.render(1466 <ErrorBoundary forceRetry={true}>1467 <Normal />1468 </ErrorBoundary>,1469 container,1470 );1471 expect(container.textContent).not.toContain('Caught an error');1472 expect(log).toEqual([1473 'ErrorBoundary componentWillReceiveProps',1474 'ErrorBoundary componentWillUpdate',1475 'ErrorBoundary render success',1476 // Mount children:1477 'Normal constructor',1478 'Normal componentWillMount',1479 'Normal render',1480 // Finalize updates:1481 'Normal componentDidMount',1482 'ErrorBoundary componentDidUpdate',1483 ]);1484 log.length = 0;1485 ReactDOM.unmountComponentAtNode(container);1486 expect(log).toEqual([1487 'ErrorBoundary componentWillUnmount',1488 'Normal componentWillUnmount',1489 ]);1490 });1491 it('can update multiple times in error state', () => {1492 const container = document.createElement('div');1493 ReactDOM.render(1494 <ErrorBoundary>1495 <BrokenRender />1496 </ErrorBoundary>,1497 container,1498 );1499 expect(container.textContent).toBe('Caught an error: Hello.');1500 ReactDOM.render(1501 <ErrorBoundary>1502 <BrokenRender />1503 </ErrorBoundary>,1504 container,1505 );1506 expect(container.textContent).toBe('Caught an error: Hello.');1507 ReactDOM.render(<div>Other screen</div>, container);1508 expect(container.textContent).toBe('Other screen');1509 ReactDOM.unmountComponentAtNode(container);1510 });1511 it("doesn't get into inconsistent state during removals", () => {1512 const container = document.createElement('div');1513 ReactDOM.render(1514 <ErrorBoundary>1515 <Normal />1516 <BrokenComponentWillUnmount />1517 <Normal />1518 </ErrorBoundary>,1519 container,1520 );1521 ReactDOM.render(<ErrorBoundary />, container);1522 expect(container.textContent).toBe('Caught an error: Hello.');1523 log.length = 0;1524 ReactDOM.unmountComponentAtNode(container);1525 expect(log).toEqual(['ErrorBoundary componentWillUnmount']);1526 });1527 it("doesn't get into inconsistent state during additions", () => {1528 const container = document.createElement('div');1529 ReactDOM.render(<ErrorBoundary />, container);1530 ReactDOM.render(1531 <ErrorBoundary>1532 <Normal />1533 <BrokenRender />1534 <Normal />1535 </ErrorBoundary>,1536 container,1537 );1538 expect(container.textContent).toBe('Caught an error: Hello.');1539 log.length = 0;1540 ReactDOM.unmountComponentAtNode(container);1541 expect(log).toEqual(['ErrorBoundary componentWillUnmount']);1542 });1543 it("doesn't get into inconsistent state during reorders", () => {1544 function getAMixOfNormalAndBrokenRenderElements() {1545 const elements = [];1546 for (let i = 0; i < 100; i++) {1547 elements.push(<Normal key={i} />);1548 }1549 elements.push(<MaybeBrokenRender key={100} />);1550 let currentIndex = elements.length;1551 while (0 !== currentIndex) {1552 const randomIndex = Math.floor(Math.random() * currentIndex);1553 currentIndex -= 1;1554 const temporaryValue = elements[currentIndex];1555 elements[currentIndex] = elements[randomIndex];1556 elements[randomIndex] = temporaryValue;1557 }1558 return elements;1559 }1560 class MaybeBrokenRender extends React.Component {1561 render() {1562 if (fail) {1563 throw new Error('Hello');1564 }1565 return <div>{this.props.children}</div>;1566 }1567 }1568 let fail = false;1569 const container = document.createElement('div');1570 ReactDOM.render(1571 <ErrorBoundary>{getAMixOfNormalAndBrokenRenderElements()}</ErrorBoundary>,1572 container,1573 );1574 expect(container.textContent).not.toContain('Caught an error');1575 fail = true;1576 ReactDOM.render(1577 <ErrorBoundary>{getAMixOfNormalAndBrokenRenderElements()}</ErrorBoundary>,1578 container,1579 );1580 expect(container.textContent).toBe('Caught an error: Hello.');1581 log.length = 0;1582 ReactDOM.unmountComponentAtNode(container);1583 expect(log).toEqual(['ErrorBoundary componentWillUnmount']);1584 });1585 it('catches errors originating downstream', () => {1586 let fail = false;1587 class Stateful extends React.Component {1588 state = {shouldThrow: false};1589 render() {1590 if (fail) {1591 log.push('Stateful render [!]');1592 throw new Error('Hello');1593 }1594 return <div>{this.props.children}</div>;1595 }1596 }1597 let statefulInst;1598 const container = document.createElement('div');1599 ReactDOM.render(1600 <ErrorBoundary>1601 <Stateful ref={inst => (statefulInst = inst)} />1602 </ErrorBoundary>,1603 container,1604 );1605 log.length = 0;1606 expect(() => {1607 fail = true;1608 statefulInst.forceUpdate();1609 }).not.toThrow();1610 expect(log).toEqual([1611 'Stateful render [!]',1612 'ErrorBoundary componentDidCatch',1613 'ErrorBoundary componentWillUpdate',1614 'ErrorBoundary render error',1615 'ErrorBoundary componentDidUpdate',1616 ]);1617 log.length = 0;1618 ReactDOM.unmountComponentAtNode(container);1619 expect(log).toEqual(['ErrorBoundary componentWillUnmount']);1620 });1621 it('catches errors in componentDidMount', () => {1622 const container = document.createElement('div');1623 ReactDOM.render(1624 <ErrorBoundary>1625 <BrokenComponentWillUnmount>1626 <Normal />1627 </BrokenComponentWillUnmount>1628 <BrokenComponentDidMount />1629 <Normal logName="LastChild" />1630 </ErrorBoundary>,1631 container,1632 );1633 expect(log).toEqual([1634 'ErrorBoundary constructor',1635 'ErrorBoundary componentWillMount',1636 'ErrorBoundary render success',1637 'BrokenComponentWillUnmount constructor',1638 'BrokenComponentWillUnmount componentWillMount',1639 'BrokenComponentWillUnmount render',1640 'Normal constructor',1641 'Normal componentWillMount',1642 'Normal render',1643 'BrokenComponentDidMount constructor',1644 'BrokenComponentDidMount componentWillMount',1645 'BrokenComponentDidMount render',1646 'LastChild constructor',1647 'LastChild componentWillMount',1648 'LastChild render',1649 // Start flushing didMount queue1650 'Normal componentDidMount',1651 'BrokenComponentWillUnmount componentDidMount',1652 'BrokenComponentDidMount componentDidMount [!]',1653 // Continue despite the error1654 'LastChild componentDidMount',1655 'ErrorBoundary componentDidMount',1656 // Now we are ready to handle the error1657 // Safely unmount every child1658 'BrokenComponentWillUnmount componentWillUnmount [!]',1659 // Continue unmounting safely despite any errors1660 'Normal componentWillUnmount',1661 'BrokenComponentDidMount componentWillUnmount',1662 'LastChild componentWillUnmount',1663 // Handle the error1664 'ErrorBoundary componentDidCatch',1665 'ErrorBoundary componentWillUpdate',1666 // The willUnmount error should be captured and logged, too.1667 'ErrorBoundary componentDidUpdate',1668 'ErrorBoundary componentDidCatch',1669 'ErrorBoundary componentWillUpdate',1670 'ErrorBoundary render error',1671 // The update has finished1672 'ErrorBoundary componentDidUpdate',1673 ]);1674 log.length = 0;1675 ReactDOM.unmountComponentAtNode(container);1676 expect(log).toEqual(['ErrorBoundary componentWillUnmount']);1677 });1678 it('catches errors in componentDidUpdate', () => {1679 const container = document.createElement('div');1680 ReactDOM.render(1681 <ErrorBoundary>1682 <BrokenComponentDidUpdate />1683 </ErrorBoundary>,1684 container,1685 );1686 log.length = 0;1687 ReactDOM.render(1688 <ErrorBoundary>1689 <BrokenComponentDidUpdate />1690 </ErrorBoundary>,1691 container,1692 );1693 expect(log).toEqual([1694 'ErrorBoundary componentWillReceiveProps',1695 'ErrorBoundary componentWillUpdate',1696 'ErrorBoundary render success',1697 'BrokenComponentDidUpdate componentWillReceiveProps',1698 'BrokenComponentDidUpdate componentWillUpdate',1699 'BrokenComponentDidUpdate render',1700 // All lifecycles run1701 'BrokenComponentDidUpdate componentDidUpdate [!]',1702 'ErrorBoundary componentDidUpdate',1703 'BrokenComponentDidUpdate componentWillUnmount',1704 // Then, error is handled1705 'ErrorBoundary componentDidCatch',1706 'ErrorBoundary componentWillUpdate',1707 'ErrorBoundary render error',1708 'ErrorBoundary componentDidUpdate',1709 ]);1710 log.length = 0;1711 ReactDOM.unmountComponentAtNode(container);1712 expect(log).toEqual(['ErrorBoundary componentWillUnmount']);1713 });1714 it('propagates errors inside boundary during componentDidMount', () => {1715 const container = document.createElement('div');1716 ReactDOM.render(1717 <ErrorBoundary>1718 <BrokenComponentDidMountErrorBoundary1719 renderError={error => (1720 <div>We should never catch our own error: {error.message}.</div>1721 )}1722 />1723 </ErrorBoundary>,1724 container,1725 );1726 expect(container.firstChild.textContent).toBe('Caught an error: Hello.');1727 expect(log).toEqual([1728 'ErrorBoundary constructor',1729 'ErrorBoundary componentWillMount',1730 'ErrorBoundary render success',1731 'BrokenComponentDidMountErrorBoundary constructor',1732 'BrokenComponentDidMountErrorBoundary componentWillMount',1733 'BrokenComponentDidMountErrorBoundary render success',1734 'BrokenComponentDidMountErrorBoundary componentDidMount [!]',1735 // Fiber proceeds with the hooks1736 'ErrorBoundary componentDidMount',1737 'BrokenComponentDidMountErrorBoundary componentWillUnmount',1738 // The error propagates to the higher boundary1739 'ErrorBoundary componentDidCatch',1740 // Fiber retries from the root1741 'ErrorBoundary componentWillUpdate',1742 'ErrorBoundary render error',1743 'ErrorBoundary componentDidUpdate',1744 ]);1745 log.length = 0;1746 ReactDOM.unmountComponentAtNode(container);1747 expect(log).toEqual(['ErrorBoundary componentWillUnmount']);1748 });1749 it('calls componentDidCatch for each error that is captured', () => {1750 function renderUnmountError(error) {1751 return <div>Caught an unmounting error: {error.message}.</div>;1752 }1753 function renderUpdateError(error) {1754 return <div>Caught an updating error: {error.message}.</div>;1755 }1756 const container = document.createElement('div');1757 ReactDOM.render(1758 <ErrorBoundary logName="OuterErrorBoundary">1759 <ErrorBoundary1760 logName="InnerUnmountBoundary"1761 renderError={renderUnmountError}>1762 <BrokenComponentWillUnmount errorText="E1" />1763 <BrokenComponentWillUnmount errorText="E2" />1764 </ErrorBoundary>1765 <ErrorBoundary1766 logName="InnerUpdateBoundary"1767 renderError={renderUpdateError}>1768 <BrokenComponentDidUpdate errorText="E3" />1769 <BrokenComponentDidUpdate errorText="E4" />1770 </ErrorBoundary>1771 </ErrorBoundary>,1772 container,1773 );1774 log.length = 0;1775 ReactDOM.render(1776 <ErrorBoundary logName="OuterErrorBoundary">1777 <ErrorBoundary1778 logName="InnerUnmountBoundary"1779 renderError={renderUnmountError}1780 />1781 <ErrorBoundary1782 logName="InnerUpdateBoundary"1783 renderError={renderUpdateError}>1784 <BrokenComponentDidUpdate errorText="E3" />1785 <BrokenComponentDidUpdate errorText="E4" />1786 </ErrorBoundary>1787 </ErrorBoundary>,1788 container,1789 );1790 expect(container.firstChild.textContent).toBe(1791 'Caught an unmounting error: E2.' + 'Caught an updating error: E4.',1792 );1793 expect(log).toEqual([1794 // Begin update phase1795 'OuterErrorBoundary componentWillReceiveProps',1796 'OuterErrorBoundary componentWillUpdate',1797 'OuterErrorBoundary render success',1798 'InnerUnmountBoundary componentWillReceiveProps',1799 'InnerUnmountBoundary componentWillUpdate',1800 'InnerUnmountBoundary render success',1801 'InnerUpdateBoundary componentWillReceiveProps',1802 'InnerUpdateBoundary componentWillUpdate',1803 'InnerUpdateBoundary render success',1804 // First come the updates1805 'BrokenComponentDidUpdate componentWillReceiveProps',1806 'BrokenComponentDidUpdate componentWillUpdate',1807 'BrokenComponentDidUpdate render',1808 'BrokenComponentDidUpdate componentWillReceiveProps',1809 'BrokenComponentDidUpdate componentWillUpdate',1810 'BrokenComponentDidUpdate render',1811 // We're in commit phase now, deleting1812 'BrokenComponentWillUnmount componentWillUnmount [!]',1813 'BrokenComponentWillUnmount componentWillUnmount [!]',1814 // Continue despite errors, handle them after commit is done1815 'InnerUnmountBoundary componentDidUpdate',1816 // We're still in commit phase, now calling update lifecycles1817 'BrokenComponentDidUpdate componentDidUpdate [!]',1818 // Again, continue despite errors, we'll handle them later1819 'BrokenComponentDidUpdate componentDidUpdate [!]',1820 'InnerUpdateBoundary componentDidUpdate',1821 'OuterErrorBoundary componentDidUpdate',1822 // After the commit phase, attempt to recover from any errors that1823 // were captured1824 'BrokenComponentDidUpdate componentWillUnmount',1825 'BrokenComponentDidUpdate componentWillUnmount',1826 'InnerUnmountBoundary componentDidCatch',1827 'InnerUnmountBoundary componentDidCatch',1828 'InnerUpdateBoundary componentDidCatch',1829 'InnerUpdateBoundary componentDidCatch',1830 'InnerUnmountBoundary componentWillUpdate',1831 'InnerUnmountBoundary render error',1832 'InnerUpdateBoundary componentWillUpdate',1833 'InnerUpdateBoundary render error',1834 'InnerUnmountBoundary componentDidUpdate',1835 'InnerUpdateBoundary componentDidUpdate',1836 ]);1837 log.length = 0;1838 ReactDOM.unmountComponentAtNode(container);1839 expect(log).toEqual([1840 'OuterErrorBoundary componentWillUnmount',1841 'InnerUnmountBoundary componentWillUnmount',1842 'InnerUpdateBoundary componentWillUnmount',1843 ]);1844 });1845 it('discards a bad root if the root component fails', () => {1846 const X = null;1847 const Y = undefined;1848 let err1;1849 let err2;1850 try {1851 let container = document.createElement('div');1852 expect(() => ReactDOM.render(<X />, container)).toWarnDev(1853 'React.createElement: type is invalid -- expected a string ' +1854 '(for built-in components) or a class/function ' +1855 '(for composite components) but got: null.',1856 );1857 } catch (err) {1858 err1 = err;1859 }1860 try {1861 let container = document.createElement('div');1862 expect(() => ReactDOM.render(<Y />, container)).toWarnDev(1863 'React.createElement: type is invalid -- expected a string ' +1864 '(for built-in components) or a class/function ' +1865 '(for composite components) but got: undefined.',1866 );1867 } catch (err) {1868 err2 = err;1869 }1870 expect(err1.message).toMatch(/got: null/);1871 expect(err2.message).toMatch(/got: undefined/);1872 });1873 it('renders empty output if error boundary does not handle the error', () => {1874 const container = document.createElement('div');1875 expect(() => {1876 ReactDOM.render(1877 <div>1878 Sibling1879 <NoopErrorBoundary>1880 <BrokenRender />1881 </NoopErrorBoundary>1882 </div>,1883 container,1884 );1885 }).toWarnDev(1886 'ErrorBoundary: Error boundaries should implement getDerivedStateFromError()',1887 {withoutStack: true},1888 );1889 expect(container.firstChild.textContent).toBe('Sibling');1890 expect(log).toEqual([1891 'NoopErrorBoundary constructor',1892 'NoopErrorBoundary componentWillMount',1893 'NoopErrorBoundary render',1894 'BrokenRender constructor',1895 'BrokenRender componentWillMount',1896 'BrokenRender render [!]',1897 // In Fiber, noop error boundaries render null1898 'NoopErrorBoundary componentDidMount',1899 'NoopErrorBoundary componentDidCatch',1900 // Nothing happens.1901 ]);1902 log.length = 0;1903 ReactDOM.unmountComponentAtNode(container);1904 expect(log).toEqual(['NoopErrorBoundary componentWillUnmount']);1905 });1906 it('passes first error when two errors happen in commit', () => {1907 const errors = [];1908 let caughtError;1909 class Parent extends React.Component {1910 render() {1911 return <Child />;1912 }1913 componentDidMount() {1914 errors.push('parent sad');1915 throw new Error('parent sad');1916 }1917 }1918 class Child extends React.Component {1919 render() {1920 return <div />;1921 }1922 componentDidMount() {1923 errors.push('child sad');1924 throw new Error('child sad');1925 }1926 }1927 const container = document.createElement('div');1928 try {1929 // Here, we test the behavior where there is no error boundary and we1930 // delegate to the host root.1931 ReactDOM.render(<Parent />, container);1932 } catch (e) {1933 if (e.message !== 'parent sad' && e.message !== 'child sad') {1934 throw e;1935 }1936 caughtError = e;1937 }1938 expect(errors).toEqual(['child sad', 'parent sad']);1939 // Error should be the first thrown1940 expect(caughtError.message).toBe('child sad');1941 });1942 it('propagates uncaught error inside unbatched initial mount', () => {1943 function Foo() {1944 throw new Error('foo error');1945 }1946 const container = document.createElement('div');1947 expect(() => {1948 ReactDOM.unstable_batchedUpdates(() => {1949 ReactDOM.render(<Foo />, container);1950 });1951 }).toThrow('foo error');1952 });1953 it('handles errors that occur in before-mutation commit hook', () => {1954 const errors = [];1955 let caughtError;1956 class Parent extends React.Component {1957 getSnapshotBeforeUpdate() {1958 errors.push('parent sad');1959 throw new Error('parent sad');1960 }1961 componentDidUpdate() {}1962 render() {1963 return <Child {...this.props} />;1964 }1965 }1966 class Child extends React.Component {1967 getSnapshotBeforeUpdate() {1968 errors.push('child sad');1969 throw new Error('child sad');1970 }1971 componentDidUpdate() {}1972 render() {1973 return <div />;1974 }1975 }1976 const container = document.createElement('div');1977 ReactDOM.render(<Parent value={1} />, container);1978 try {1979 ReactDOM.render(<Parent value={2} />, container);1980 } catch (e) {1981 if (e.message !== 'parent sad' && e.message !== 'child sad') {1982 throw e;1983 }1984 caughtError = e;1985 }1986 expect(errors).toEqual(['child sad', 'parent sad']);1987 // Error should be the first thrown1988 expect(caughtError.message).toBe('child sad');1989 });...
app.js
Source:app.js
1// @codekit-prepend "utility/date.js"2// @codekit-prepend "utility/element.js"3// @codekit-prepend "utility/misc.js"4// @codekit-prepend "utility/string.js"5// @codekit-append "library/popup.js"6// @codekit-append "inc/api.js"7// @codekit-append "inc/hashtag.js"8// @codekit-append "inc/resource.js"9// @codekit-append "inc/template.js"10/**11 * @author Robert Rusu12 */13let PaymentPage = {14 Library : {},15 Component : {},16 _assets : {17 requested : [],18 loaded : []19 },20 _components : {21 pending : {},22 instanced : {}23 },24 _template_prefix : 'payment-page-',25 settings : {},26 lang : {},27 Init : function ( containerTarget ) {28 containerTarget.find( '[data-payment-page-component]' ).not( '[data-payment-page-component-loaded]' ).each( function() { PaymentPage.initComponent( jQuery(this) ); });29 containerTarget.find( '#payment-page-notification-container .notice-dismiss' ).on( "click", function() {30 PaymentPage.API.post( 'payment-page/v1/administration/dismiss-notification', false );31 });32 },33 initComponent : function( container ) {34 let component = container.attr( 'data-payment-page-component' );35 this.setLoadingContent( container, '', ( component === 'listing-search' ? 'mini' : '' ) );36 if( typeof PaymentPage.Component[ component ] !== "undefined" ) {37 PaymentPage._instanceComponent( component, container );38 } else {39 PaymentPage._loadComponent( component, container );40 }41 },42 getComponentInstance : function( component, component_index ) {43 component_index = parseInt( component_index );44 return PaymentPage._components.instanced[ component ][ component_index ];45 },46 setLoadingContent : function( container, loading_text = '', type = '' ) {47 if( container.find( ' > .payment-page-application-loader-wrapper' ).length === 1 ) {48 container.find( ' > .payment-page-application-loader-status-text' ).remove();49 if( loading_text !== '' )50 container.prepend( '<div class="payment-page-application-loader-status-text">' + loading_text + '</div>' );51 return;52 }53 container.html( payment_page_element_loader( type ) );54 if( loading_text !== '' )55 container.prepend( '<div class="payment-page-application-loader-status-text">' + loading_text + '</div>' );56 },57 setNoResultsContent : function( container ) {58 container.html( '<div data-payment-page-notification="info">' + this.lang.no_results_response + '</div>' );59 },60 setErrorContent : function( container, response ) {61 if( typeof response.error_html !== "undefined" ) {62 container.html( response.error_html );63 PaymentPage.Init( container );64 return;65 }66 let error = ( typeof response.error_notification !== "undefined" ? response.error_notification : response.error );67 container.html( '<div data-payment-page-notification="danger">' + error + '</div>' );68 },69 setCancelledContent : function( container ) {70 if( typeof window.closed !== "undefined" && window.closed ) {71 container.html( '' );72 return;73 }74 container.html( '<div data-payment-page-notification="info">' + this.lang.cancelled_request + '</div>' );75 },76 setFailedAssetFetchContent : function( container ) {77 if( typeof window.closed !== "undefined" && window.closed ) {78 container.html( '' );79 return;80 }81 container.html( '<div data-payment-page-notification="danger">' + this.lang.asset_failed_fetch + '</div>' );82 },83 setHTML : function( container, html ) {84 PaymentPage.Destroy( container );85 container.html( html );86 PaymentPage.Init( container );87 },88 _loadComponent : function( component, componentContainer = false ) {89 if( typeof this._components.pending[ component ] === "undefined" ) {90 if( componentContainer !== false )91 this._components.pending[ component ] = [ componentContainer ];92 this.Resource.loadCSS(93 this.getComponentAssetPath( component, 'style.css' ),94 {95 'payment-page-component-stylesheet' : component96 }97 )98 this.Resource.loadJS( this.getComponentAssetPath( component, 'controller.min.js' ), function() {99 PaymentPage._loadedComponent( component );100 }, function() {101 if( componentContainer !== false )102 PaymentPage.setFailedAssetFetchContent( componentContainer );103 } );104 return;105 }106 if( componentContainer === false )107 return;108 if( typeof this._components.pending[ component ] === "undefined" )109 this._components.pending[ component ] = [];110 this._components.pending[ component ][ this._components.pending[ component ].length ] = componentContainer;111 },112 _loadedComponent : function( component ) {113 if( typeof this._components.pending[ component ] === 'undefined' )114 return;115 if( this._components.pending[ component ].length !== 0 ) {116 jQuery.each( this._components.pending[ component ], function( index, componentContainer ) {117 PaymentPage._instanceComponent( component, componentContainer );118 });119 }120 delete this._components.pending[ component ];121 },122 _instanceComponent : function( component, componentContainer ) {123 if( typeof componentContainer.attr( "data-payment-page-component-loaded" ) !== "undefined" )124 return;125 componentContainer.attr( 'data-payment-page-component-loaded', 0 );126 this.setLoadingContent( componentContainer );127 if( typeof this._components.instanced[ component ] === "undefined" )128 this._components.instanced[ component ] = [];129 this._components.instanced[ component ][ this._components.instanced[ component ].length ] = payment_page_clone_object( this.Component[ component ] );130 componentContainer.attr( "data-payment-page-component-instance-index", this._components.instanced[ component ].length - 1 );131 this._components.instanced[ component ][ this._components.instanced[ component ].length - 1 ].Init( componentContainer );132 componentContainer.attr( 'data-payment-page-component-loaded', 1 );133 this.Hashtag.Init();134 },135 getComponentAssetPath : function( component, path ) {136 if( typeof this.settings.component_injection[ component ] !== "undefined" ) {137 let component_injection = this.settings.component_injection[ component ];138 if( typeof component_injection === 'object' ) {139 let objectKeys = Object.keys( component_injection ),140 _matched_index = false;141 jQuery.each( objectKeys, function( _objectKeyIndex, _objectKey ) {142 if( path.indexOf( _objectKey ) !== 0 )143 return true;144 _matched_index = _objectKey;145 return false;146 });147 if( false !== _matched_index )148 return component_injection[ _matched_index ] + '/' + path.replace( _matched_index, '' );149 return component_injection[ '__default' ] + '/' + path;150 }151 return this.settings.component_injection[ component ] + '/' + path;152 }153 return this.settings.library_url + 'component/' + component + '/' + path;154 },155 LoadAssets : function( asset, callback = false, _attach_version = true ) {156 if( typeof asset === "string" ) {157 if( payment_page_in_array( asset, PaymentPage._assets.loaded ) ) {158 if( typeof callback === 'function' )159 callback();160 return;161 }162 if( payment_page_in_array( asset, PaymentPage._assets.requested ) ) {163 setTimeout( function() {164 PaymentPage.LoadAssets( asset, callback, _attach_version );165 }, 100 );166 return;167 }168 PaymentPage._assets.requested[ PaymentPage._assets.requested.length ] = asset;169 if( asset.endsWith( '.css' ) ) {170 PaymentPage.Resource.loadCSS( asset );171 if( typeof callback === 'function' )172 callback();173 return;174 }175 PaymentPage.Resource.loadJS( asset, callback, false, _attach_version );176 return;177 }178 let current_asset = _.head( asset );179 if( asset.length === 1 ) {180 this.LoadAssets( current_asset, callback );181 return;182 }183 let objectInstance = this,184 remaining_assets = _.drop( asset, 1 );185 this.LoadAssets( current_asset, function() {186 objectInstance.LoadAssets( remaining_assets, callback );187 } );188 },189 isAssetLoaded : function( asset ) {190 if( typeof this._assets.loaded === "undefined" )191 return false;192 if( asset instanceof Array ) {193 let response = true;194 jQuery.each( asset, function( k, a ) {195 if( !PaymentPage._assets.loaded.includes( a ) )196 response = false;197 });198 return response;199 }200 return this._assets.loaded.includes( asset );201 },202 Destroy : function( target ) {203 if( typeof target.attr( 'data-payment-page-component' ) !== "undefined" )204 PaymentPage.__destroyComponent( target );205 if( typeof tinyMCE !== "undefined" ) {206 tinyMCE.triggerSave();207 target.find( ".wp-editor-area" ).each( function() {208 if( jQuery(this).attr( "id" ) !== "undefined" )209 tinyMCE.execCommand('mceRemoveEditor', false, jQuery(this).attr('id'));210 });211 }212 target.find( '[data-payment-page-component]' ).each( function() {213 PaymentPage.__destroyComponent( jQuery(this) );214 });215 target.find( '[data-payment-page-library]' ).each( function() {216 let library = jQuery(this).attr( 'data-payment-page-library' );217 library = payment_page_uc_first(library);218 if( typeof PaymentPage.Library[ library ] !== "undefined" ) {219 PaymentPage.Library[library].Destroy( jQuery(this) );220 }221 });222 target.find( "*" ).off();223 target.html( '' );224 },225 __destroyComponent : function( targetContainer ) {226 let component = targetContainer.attr( "data-payment-page-component" ),227 component_index = targetContainer.attr( "data-payment-page-component-instance-index" );228 let _is_stored = ( typeof PaymentPage._components.instanced[ component ] !== 'undefined' && typeof PaymentPage._components.instanced[ component ][component_index] !== 'undefined' )229 if( _is_stored && typeof PaymentPage._components.instanced[ component ][component_index].__onDestroy === 'function' )230 PaymentPage._components.instanced[ component ][component_index].__onDestroy();231 targetContainer.removeAttr( "data-payment-page-component" );232 targetContainer.removeAttr( "data-payment-page-component-loaded" );233 targetContainer.removeAttr( "data-payment-page-component-instance-index" );234 if( _is_stored )235 PaymentPage._components.instanced[ component ].splice( component_index, 1 );236 },237 RemoveHTMLNode : function( target ) {238 this.Destroy( target );239 target.remove();240 }241};242function _payment_page_init_application( lang, configuration ) {243 PaymentPage.lang = lang;244 PaymentPage.settings = payment_page_parse_args( configuration, PaymentPage.settings );245 PaymentPage.Init( jQuery( "body" ) );246 jQuery(window).trigger( "payment_page_ready" );247}248jQuery( document ).ready( function() {249 jQuery.extend(jQuery.expr[":"], {250 "containsCaseInsensitive": function (elem, i, match, array) {251 return (elem.textContent || elem.innerText || "").toLowerCase().indexOf((match[3] || "").toLowerCase()) >= 0;252 }253 });254 _payment_page_init_application( window.payment_page_data.lang, window.payment_page_data.configuration );...
routes.js
Source:routes.js
1import App from './app';2export default {3 path: '/',4 component: App,5 indexRoute: {6 getComponent(nextState, cb) {7 import('./pages/index').then(component => cb(null, component.default));8 }9 },10 childRoutes: [11 {12 path: 'about-us',13 getComponent(nextState, cb) {14 import('./pages/about-us').then(component =>15 cb(null, component.default)16 );17 }18 },19 {20 path: 'account-notice',21 getComponent(nextState, cb) {22 import('./pages/account-notice').then(component =>23 cb(null, component.default)24 );25 }26 },27 {28 path: 'add-bank',29 getComponent(nextState, cb) {30 import('./pages/add-bank').then(component =>31 cb(null, component.default)32 );33 }34 },35 {36 path: 'advice-feed-back',37 getComponent(nextState, cb) {38 import('./pages/advice-feed-back').then(component =>39 cb(null, component.default)40 );41 }42 },43 {44 path: 'bank-info',45 getComponent(nextState, cb) {46 import('./pages/bank-info').then(component =>47 cb(null, component.default)48 );49 }50 },51 {52 path: 'bank-query',53 getComponent(nextState, cb) {54 import('./pages/bank-query').then(component =>55 cb(null, component.default)56 );57 }58 },59 {60 path: 'buy-notice',61 getComponent(nextState, cb) {62 import('./pages/buy-notice').then(component =>63 cb(null, component.default)64 );65 }66 },67 {68 path: 'change-pwd',69 getComponent(nextState, cb) {70 import('./pages/change-pwd').then(component =>71 cb(null, component.default)72 );73 }74 },75 {76 path: 'comfirm-draw-money-ok',77 getComponent(nextState, cb) {78 import('./pages/comfirm-draw-money-ok').then(component =>79 cb(null, component.default)80 );81 }82 },83 {84 path: 'comfirm-draw-money',85 getComponent(nextState, cb) {86 import('./pages/comfirm-draw-money').then(component =>87 cb(null, component.default)88 );89 }90 },91 {92 path: 'cz-trade-detail/:transCode',93 getComponent(nextState, cb) {94 import('./pages/cz-trade-detail').then(component =>95 cb(null, component.default)96 );97 }98 },99 {100 path: 'devote-recorde',101 getComponent(nextState, cb) {102 import('./pages/devote-recorde').then(component =>103 cb(null, component.default)104 );105 }106 },107 {108 path: 'draw-list',109 getComponent(nextState, cb) {110 import('./pages/draw-list').then(component =>111 cb(null, component.default)112 );113 }114 },115 {116 path: 'draw-money-branch',117 getComponent(nextState, cb) {118 import('./pages/draw-money-branch').then(component =>119 cb(null, component.default)120 );121 }122 },123 {124 path: 'draw-money',125 getComponent(nextState, cb) {126 import('./pages/draw-money').then(component =>127 cb(null, component.default)128 );129 }130 },131 {132 path: 'fourteen-lottery-detail',133 getComponent(nextState, cb) {134 import('./pages/fourteen-lottery-detail').then(component =>135 cb(null, component.default)136 );137 }138 },139 {140 path: 'fourteen-lottery',141 getComponent(nextState, cb) {142 import('./pages/fourteen-lottery').then(component =>143 cb(null, component.default)144 );145 }146 },147 {148 path: 'jjc-lottery-detail',149 getComponent(nextState, cb) {150 import('./pages/jjc-lottery-detail').then(component =>151 cb(null, component.default)152 );153 }154 },155 {156 path: 'lottery-notice',157 getComponent(nextState, cb) {158 import('./pages/lottery-notice').then(component =>159 cb(null, component.default)160 );161 }162 },163 {164 path: 'mail-info/:em/:em_log/:em_sts',165 getComponent(nextState, cb) {166 import('./pages/mail-info').then(component =>167 cb(null, component.default)168 );169 }170 },171 {172 path: 'modify-nick-name',173 getComponent(nextState, cb) {174 import('./pages/modify-nick-name').then(component =>175 cb(null, component.default)176 );177 }178 },179 {180 path: 'modify-user-name',181 getComponent(nextState, cb) {182 import('./pages/modify-user-name').then(component =>183 cb(null, component.default)184 );185 }186 },187 {188 path: 'msg-center',189 getComponent(nextState, cb) {190 import('./pages/msg-center').then(component =>191 cb(null, component.default)192 );193 }194 },195 {196 path: 'my-bank',197 getComponent(nextState, cb) {198 import('./pages/my-bank').then(component =>199 cb(null, component.default)200 );201 }202 },203 {204 path: 'no-bother-notice',205 getComponent(nextState, cb) {206 import('./pages/no-bother-notice').then(component =>207 cb(null, component.default)208 );209 }210 },211 {212 path: 'notice-setting',213 getComponent(nextState, cb) {214 import('./pages/notice-setting').then(component =>215 cb(null, component.default)216 );217 }218 },219 {220 path: 'number-lottery-detail',221 getComponent(nextState, cb) {222 import('./pages/number-lottery-detail').then(component =>223 cb(null, component.default)224 );225 }226 },227 {228 path: 'number-lottery-progress/:orderCode',229 getComponent(nextState, cb) {230 import('./pages/number-lottery-progress').then(component =>231 cb(null, component.default)232 );233 }234 },235 {236 path: 'other-notice',237 getComponent(nextState, cb) {238 import('./pages/other-notice').then(component =>239 cb(null, component.default)240 );241 }242 },243 {244 path: 'phone-info/:mob/:mob_log/:mob_sts',245 getComponent(nextState, cb) {246 import('./pages/phone-info').then(component =>247 cb(null, component.default)248 );249 }250 },251 {252 path: 'question-detail',253 getComponent(nextState, cb) {254 import('./pages/question-detail').then(component =>255 cb(null, component.default)256 );257 }258 },259 {260 path: 'question-detail',261 getComponent(nextState, cb) {262 import('./pages/question-detail').then(component =>263 cb(null, component.default)264 );265 }266 },267 {268 path: 'real-name-again',269 getComponent(nextState, cb) {270 import('./pages/real-name-again').then(component =>271 cb(null, component.default)272 );273 }274 },275 {276 path: 'real-name',277 getComponent(nextState, cb) {278 import('./pages/real-name').then(component =>279 cb(null, component.default)280 );281 }282 },283 {284 path: 'recharge-result',285 getComponent(nextState, cb) {286 import('./pages/recharge-result').then(component =>287 cb(null, component.default)288 );289 }290 },291 {292 path: 'recharge',293 getComponent(nextState, cb) {294 import('./pages/recharge').then(component =>295 cb(null, component.default)296 );297 }298 },299 {300 path: 'red-packet-item',301 getComponent(nextState, cb) {302 import('./pages/red-packet-item').then(component =>303 cb(null, component.default)304 );305 }306 },307 {308 path: 'red-packet-tans/:redCode',309 getComponent(nextState, cb) {310 import('./pages/red-packet-tans').then(component =>311 cb(null, component.default)312 );313 }314 },315 {316 path: 'red-packet',317 getComponent(nextState, cb) {318 import('./pages/red-packet').then(component =>319 cb(null, component.default)320 );321 }322 },323 {324 path: 'register-bank/:bankNo',325 getComponent(nextState, cb) {326 import('./pages/register-bank').then(component =>327 cb(null, component.default)328 );329 }330 },331 {332 path: 'register-mail',333 getComponent(nextState, cb) {334 import('./pages/register-mail').then(component =>335 cb(null, component.default)336 );337 }338 },339 {340 path: 'register-phone',341 getComponent(nextState, cb) {342 import('./pages/register-phone').then(component =>343 cb(null, component.default)344 );345 }346 },347 {348 path: 'register-proxy',349 getComponent(nextState, cb) {350 import('./pages/register-proxy').then(component =>351 cb(null, component.default)352 );353 }354 },355 {356 path: 'safe-account',357 getComponent(nextState, cb) {358 import('./pages/safe-account').then(component =>359 cb(null, component.default)360 );361 }362 },363 {364 path: 'service-hall',365 getComponent(nextState, cb) {366 import('./pages/service-hall').then(component =>367 cb(null, component.default)368 );369 }370 },371 {372 path: 'set-password',373 getComponent(nextState, cb) {374 import('./pages/set-password').then(component =>375 cb(null, component.default)376 );377 }378 },379 {380 path: 'setting',381 getComponent(nextState, cb) {382 import('./pages/setting').then(component =>383 cb(null, component.default)384 );385 }386 },387 {388 path: 'tk-trade-detail/:transCode',389 getComponent(nextState, cb) {390 import('./pages/tk-trade-detail').then(component =>391 cb(null, component.default)392 );393 }394 },395 {396 path: 'trade-detail/:transCode',397 getComponent(nextState, cb) {398 import('./pages/trade-detail').then(component =>399 cb(null, component.default)400 );401 }402 },403 {404 path: 'trade-info',405 getComponent(nextState, cb) {406 import('./pages/trade-info').then(component =>407 cb(null, component.default)408 );409 }410 },411 {412 path: 'trade-notice',413 getComponent(nextState, cb) {414 import('./pages/trade-notice').then(component =>415 cb(null, component.default)416 );417 }418 },419 {420 path: 'user-info',421 getComponent(nextState, cb) {422 import('./pages/user-info').then(component =>423 cb(null, component.default)424 );425 }426 },427 {428 path: 'verify-mail/:em(/:update)',429 getComponent(nextState, cb) {430 import('./pages/verify-mail').then(component =>431 cb(null, component.default)432 );433 }434 },435 {436 path: 'verify-phone/:mob(/:update)',437 getComponent(nextState, cb) {438 import('./pages/verify-phone').then(component =>439 cb(null, component.default)440 );441 }442 },443 {444 path: 'orders/:order',445 getComponent(nextState, cb) {446 import('./orders/components').then(component =>447 cb(null, component.default)448 );449 }450 }451 ]...
ComponentWrapper.test.js
Source:ComponentWrapper.test.js
1"use strict";2Object.defineProperty(exports, "__esModule", { value: true });3const React = require("react");4const react_native_1 = require("react-native");5const renderer = require("react-test-renderer");6const ComponentWrapper_1 = require("./ComponentWrapper");7const Store_1 = require("./Store");8const ts_mockito_1 = require("ts-mockito");9const ComponentEventsObserver_1 = require("../events/ComponentEventsObserver");10describe('ComponentWrapper', () => {11 const componentName = 'example.MyComponent';12 let store;13 let myComponentProps;14 let mockedComponentEventsObserver;15 let componentEventsObserver;16 let uut;17 class MyComponent extends React.Component {18 render() {19 myComponentProps = this.props;20 if (this.props.renderCount) {21 this.props.renderCount();22 }23 return <react_native_1.Text>{this.props.text || 'Hello, World!'}</react_native_1.Text>;24 }25 }26 MyComponent.options = {27 title: 'MyComponentTitle'28 };29 class TestParent extends React.Component {30 constructor(props) {31 super(props);32 this.ChildClass = props.ChildClass;33 this.state = { propsFromState: {} };34 }35 render() {36 return (<this.ChildClass componentId='component1' {...this.state.propsFromState}/>);37 }38 }39 beforeEach(() => {40 store = new Store_1.Store();41 mockedComponentEventsObserver = ts_mockito_1.mock(ComponentEventsObserver_1.ComponentEventsObserver);42 componentEventsObserver = ts_mockito_1.instance(mockedComponentEventsObserver);43 uut = new ComponentWrapper_1.ComponentWrapper();44 });45 it('must have componentId as prop', () => {46 const NavigationComponent = uut.wrap(componentName, () => MyComponent, store, componentEventsObserver);47 const orig = console.error;48 console.error = (a) => a;49 expect(() => {50 renderer.create(<NavigationComponent />);51 }).toThrowError('Component example.MyComponent does not have a componentId!');52 console.error = orig;53 });54 it('wraps the component', () => {55 const NavigationComponent = uut.wrap(componentName, () => MyComponent, store, componentEventsObserver);56 expect(NavigationComponent).not.toBeInstanceOf(MyComponent);57 const tree = renderer.create(<NavigationComponent componentId={'component1'}/>);58 expect(tree.toJSON().children).toEqual(['Hello, World!']);59 });60 it('injects props from wrapper into original component', () => {61 const renderCount = jest.fn();62 const NavigationComponent = uut.wrap(componentName, () => MyComponent, store, componentEventsObserver);63 const tree = renderer.create(<NavigationComponent componentId={'component1'} text={'yo'} renderCount={renderCount}/>);64 expect(tree.toJSON().children).toEqual(['yo']);65 expect(renderCount).toHaveBeenCalledTimes(1);66 });67 it('updates props from wrapper into original component on state change', () => {68 const NavigationComponent = uut.wrap(componentName, () => MyComponent, store, componentEventsObserver);69 const tree = renderer.create(<TestParent ChildClass={NavigationComponent}/>);70 expect(myComponentProps.foo).toEqual(undefined);71 tree.getInstance().setState({ propsFromState: { foo: 'yo' } });72 expect(myComponentProps.foo).toEqual('yo');73 });74 it('pulls props from the store and injects them into the inner component', () => {75 store.setPropsForId('component123', { numberProp: 1, stringProp: 'hello', objectProp: { a: 2 } });76 const NavigationComponent = uut.wrap(componentName, () => MyComponent, store, componentEventsObserver);77 renderer.create(<NavigationComponent componentId={'component123'}/>);78 expect(myComponentProps).toEqual({ componentId: 'component123', numberProp: 1, stringProp: 'hello', objectProp: { a: 2 } });79 });80 it('updates props from store into inner component', () => {81 const NavigationComponent = uut.wrap(componentName, () => MyComponent, store, componentEventsObserver);82 const tree = renderer.create(<TestParent ChildClass={NavigationComponent}/>);83 store.setPropsForId('component1', { myProp: 'hello' });84 expect(myComponentProps.foo).toEqual(undefined);85 expect(myComponentProps.myProp).toEqual(undefined);86 tree.getInstance().setState({ propsFromState: { foo: 'yo' } });87 expect(myComponentProps.foo).toEqual('yo');88 expect(myComponentProps.myProp).toEqual('hello');89 });90 it('protects id from change', () => {91 const NavigationComponent = uut.wrap(componentName, () => MyComponent, store, componentEventsObserver);92 const tree = renderer.create(<TestParent ChildClass={NavigationComponent}/>);93 expect(myComponentProps.componentId).toEqual('component1');94 tree.getInstance().setState({ propsFromState: { id: 'ERROR' } });95 expect(myComponentProps.componentId).toEqual('component1');96 });97 xit('assigns key by componentId', () => {98 const NavigationComponent = uut.wrap(componentName, () => MyComponent, store, componentEventsObserver);99 const tree = renderer.create(<NavigationComponent componentId={'component1'}/>);100 expect(myComponentProps.componentId).toEqual('component1');101 console.log(Object.keys(tree.root.findByType(NavigationComponent).instance._reactInternalFiber));102 console.log(tree.root.findByType(NavigationComponent).instance._reactInternalFiber.child.child.child.return.return.key);103 expect(tree.getInstance()._reactInternalInstance.child.child.Fibernode.key).toEqual('component1');104 });105 it('cleans props from store on unMount', () => {106 store.setPropsForId('component123', { foo: 'bar' });107 const NavigationComponent = uut.wrap(componentName, () => MyComponent, store, componentEventsObserver);108 const tree = renderer.create(<NavigationComponent componentId={'component123'}/>);109 expect(store.getPropsForId('component123')).toEqual({ foo: 'bar' });110 tree.unmount();111 expect(store.getPropsForId('component123')).toEqual({});112 });113 it('merges static members from wrapped component when generated', () => {114 const NavigationComponent = uut.wrap(componentName, () => MyComponent, store, componentEventsObserver);115 expect(NavigationComponent.options).toEqual({ title: 'MyComponentTitle' });116 });117 it('calls unmounted on componentEventsObserver', () => {118 const NavigationComponent = uut.wrap(componentName, () => MyComponent, store, componentEventsObserver);119 const tree = renderer.create(<NavigationComponent componentId={'component123'}/>);120 ts_mockito_1.verify(mockedComponentEventsObserver.unmounted('component123')).never();121 tree.unmount();122 ts_mockito_1.verify(mockedComponentEventsObserver.unmounted('component123')).once();123 });124 it('renders HOC components correctly', () => {125 const generator = () => (props) => (<react_native_1.View>126 <MyComponent {...props}/>127 </react_native_1.View>);128 uut = new ComponentWrapper_1.ComponentWrapper();129 const NavigationComponent = uut.wrap(componentName, generator, store, componentEventsObserver);130 const tree = renderer.create(<NavigationComponent componentId={'component123'}/>);131 expect(tree.root.findByType(react_native_1.View)).toBeDefined();132 expect(tree.root.findByType(MyComponent).props).toEqual({ componentId: 'component123' });133 });134 describe(`register with redux store`, () => {135 class MyReduxComp extends React.Component {136 static options() {137 return { foo: 123 };138 }139 render() {140 return (<react_native_1.Text>{this.props.txt}</react_native_1.Text>);141 }142 }143 function mapStateToProps(state) {144 return {145 txt: state.txt146 };147 }148 const ConnectedComp = require('react-redux').connect(mapStateToProps)(MyReduxComp);149 const ReduxProvider = require('react-redux').Provider;150 const initialState = { txt: 'it just works' };151 const reduxStore = require('redux').createStore((state = initialState) => state);152 it(`wraps the component with a react-redux provider with passed store`, () => {153 const NavigationComponent = uut.wrap(componentName, () => ConnectedComp, store, componentEventsObserver, undefined, ReduxProvider, reduxStore);154 const tree = renderer.create(<NavigationComponent componentId={'theCompId'}/>);155 expect(tree.toJSON().children).toEqual(['it just works']);156 expect(NavigationComponent.options()).toEqual({ foo: 123 });157 });158 });...
Component.js
Source:Component.js
1describe('Component', function() {2 var utHelper = window.utHelper;3 var testCase = utHelper.prepare(['echarts/model/Component']);4 describe('topologicalTravel', function () {5 testCase('topologicalTravel_base', function (ComponentModel) {6 ComponentModel.extend({type: 'm1', dependencies: ['a1', 'a2']});7 ComponentModel.extend({type: 'a1'});8 ComponentModel.extend({type: 'a2'});9 var result = [];10 var allList = ComponentModel.getAllClassMainTypes();11 ComponentModel.topologicalTravel(['m1', 'a1', 'a2'], allList, function (componentType, dependencies) {12 result.push([componentType, dependencies]);13 });14 expect(result).toEqual([['a2', []], ['a1', []], ['m1', ['a1', 'a2']]]);15 });16 testCase('topologicalTravel_a1IsAbsent', function (ComponentModel) {17 ComponentModel.extend({type: 'm1', dependencies: ['a1', 'a2']});18 ComponentModel.extend({type: 'a2'});19 var allList = ComponentModel.getAllClassMainTypes();20 var result = [];21 ComponentModel.topologicalTravel(['m1', 'a2'], allList, function (componentType, dependencies) {22 result.push([componentType, dependencies]);23 });24 expect(result).toEqual([['a2', []], ['m1', ['a1', 'a2']]]);25 });26 testCase('topologicalTravel_empty', function (ComponentModel) {27 ComponentModel.extend({type: 'm1', dependencies: ['a1', 'a2']});28 ComponentModel.extend({type: 'a1'});29 ComponentModel.extend({type: 'a2'});30 var allList = ComponentModel.getAllClassMainTypes();31 var result = [];32 ComponentModel.topologicalTravel([], allList, function (componentType, dependencies) {33 result.push([componentType, dependencies]);34 });35 expect(result).toEqual([]);36 });37 testCase('topologicalTravel_isolate', function (ComponentModel) {38 ComponentModel.extend({type: 'a2'});39 ComponentModel.extend({type: 'a1'});40 ComponentModel.extend({type: 'm1', dependencies: ['a2']});41 var allList = ComponentModel.getAllClassMainTypes();42 var result = [];43 ComponentModel.topologicalTravel(['a1', 'a2', 'm1'], allList, function (componentType, dependencies) {44 result.push([componentType, dependencies]);45 });46 expect(result).toEqual([['a1', []], ['a2', []], ['m1', ['a2']]]);47 });48 testCase('topologicalTravel_diamond', function (ComponentModel) {49 ComponentModel.extend({type: 'a1', dependencies: []});50 ComponentModel.extend({type: 'a2', dependencies: ['a1']});51 ComponentModel.extend({type: 'a3', dependencies: ['a1']});52 ComponentModel.extend({type: 'm1', dependencies: ['a2', 'a3']});53 var allList = ComponentModel.getAllClassMainTypes();54 var result = [];55 ComponentModel.topologicalTravel(['m1', 'a1', 'a2', 'a3'], allList, function (componentType, dependencies) {56 result.push([componentType, dependencies]);57 });58 expect(result).toEqual([['a1', []], ['a3', ['a1']], ['a2', ['a1']], ['m1', ['a2', 'a3']]]);59 });60 testCase('topologicalTravel_loop', function (ComponentModel) {61 ComponentModel.extend({type: 'm1', dependencies: ['a1', 'a2']});62 ComponentModel.extend({type: 'm2', dependencies: ['m1', 'a2']});63 ComponentModel.extend({type: 'a1', dependencies: ['m2', 'a2', 'a3']});64 ComponentModel.extend({type: 'a2'});65 ComponentModel.extend({type: 'a3'});66 var allList = ComponentModel.getAllClassMainTypes();67 expect(function () {68 ComponentModel.topologicalTravel(['m1', 'm2', 'a1'], allList);69 }).toThrowError(/Circl/);70 });71 testCase('topologicalTravel_multipleEchartsInstance', function (ComponentModel) {72 ComponentModel.extend({type: 'm1', dependencies: ['a1', 'a2']});73 ComponentModel.extend({type: 'a1'});74 ComponentModel.extend({type: 'a2'});75 var allList = ComponentModel.getAllClassMainTypes();76 var result = [];77 ComponentModel.topologicalTravel(['m1', 'a1', 'a2'], allList, function (componentType, dependencies) {78 result.push([componentType, dependencies]);79 });80 expect(result).toEqual([['a2', []], ['a1', []], ['m1', ['a1', 'a2']]]);81 result = [];82 ComponentModel.extend({type: 'm2', dependencies: ['a1', 'm1']});83 var allList = ComponentModel.getAllClassMainTypes();84 ComponentModel.topologicalTravel(['m2', 'm1', 'a1', 'a2'], allList, function (componentType, dependencies) {85 result.push([componentType, dependencies]);86 });87 expect(result).toEqual([['a2', []], ['a1', []], ['m1', ['a1', 'a2']], ['m2', ['a1', 'm1']]]);88 });89 testCase('topologicalTravel_missingSomeNodeButHasDependencies', function (ComponentModel) {90 ComponentModel.extend({type: 'm1', dependencies: ['a1', 'a2']});91 ComponentModel.extend({type: 'a2', dependencies: ['a3']});92 ComponentModel.extend({type: 'a3'});93 ComponentModel.extend({type: 'a4'});94 var result = [];95 var allList = ComponentModel.getAllClassMainTypes();96 ComponentModel.topologicalTravel(['a3', 'm1'], allList, function (componentType, dependencies) {97 result.push([componentType, dependencies]);98 });99 expect(result).toEqual([['a3', []], ['a2', ['a3']], ['m1', ['a1', 'a2']]]);100 var result = [];101 var allList = ComponentModel.getAllClassMainTypes();102 ComponentModel.topologicalTravel(['m1', 'a3'], allList, function (componentType, dependencies) {103 result.push([componentType, dependencies]);104 });105 expect(result).toEqual([['a3', []], ['a2', ['a3']], ['m1', ['a1', 'a2']]]);106 });107 testCase('topologicalTravel_subType', function (ComponentModel) {108 ComponentModel.extend({type: 'm1', dependencies: ['a1', 'a2']});109 ComponentModel.extend({type: 'a1.aaa', dependencies: ['a2']});110 ComponentModel.extend({type: 'a1.bbb', dependencies: ['a3', 'a4']});111 ComponentModel.extend({type: 'a2'});112 ComponentModel.extend({type: 'a3'});113 ComponentModel.extend({type: 'a4'});114 var result = [];115 var allList = ComponentModel.getAllClassMainTypes();116 ComponentModel.topologicalTravel(['m1', 'a1', 'a2', 'a4'], allList, function (componentType, dependencies) {117 result.push([componentType, dependencies]);118 });119 expect(result).toEqual([['a4', []], ['a2',[]], ['a1', ['a2','a3','a4']], ['m1', ['a1', 'a2']]]);120 });121 });...
ContentComponent.jsx
Source:ContentComponent.jsx
1import React from 'react';2import { BrowserRouter, Route, Switch } from 'react-router-dom';3// ===Common===4import MainCommonComponent from "./common/MainCommonComponent";5// ===Mbr===6// import UserListComponent from "./mbr/UserListComponent";7// import AddUserComponent from "./mbr/AddUserComponent";8// import EditUserComponent from "./mbr/EditUserComponent";9import LoginMbrComponent from "./mbr/LoginMbrComponent";10import WelcomeComponent from "./mbr/WelcomeComponent";11import LogoutComponent from "./mbr/LogoutComponent";12import AuthenticatedRoute from './mbr/AuthenticatedRoute'13import ErrorComponent from './mbr/ErrorComponent'14import RegiMbrComponent from "./mbr/RegiMbrComponent";15// ===Board===16import CommunityBoardComponent from "./board/CommunityBoardComponent";17import CommunityBoardCreate from "./board/CommunityBoardCreate";18import CommunityBoardUpdate from "./board/CommunityBoardUpdate";19import CommunityBoardDetail from "./board/CommunityBoardDetail";20import ShareBoardComponent from "./board/ShareBoardComponent";21import ShareBoardCreate from "./board/ShareBoardCreate";22import ShareBoardUpdate from "./board/ShareBoardUpdate";23import ShareBoardDetail from "./board/ShareBoardDetail";24// ===News===25import EventNewsComponent from "./news/EventNewsComponent";26import EventNewsCreate from "./news/EventNewsCreate";27import EventNewsUpdate from "./news/EventNewsUpdate";28import EventNewsDetail from "./news/EventNewsDetail"; 29import MagazineNewsComponent from "./news/MagazineNewsComponent";30import MagazineNewsCreate from "./news/MagazineNewsCreate";31import MagazineNewsUpdate from "./news/MagazineNewsUpdate";32import MagazineNewsDetail from "./news/MagazineNewsDetail";33import NoticeNewsComponent from "./news/NoticeNewsComponent";34import NoticeNewsCreate from "./news/NoticeNewsCreate";35import NoticeNewsUpdate from "./news/NoticeNewsUpdate";36import NoticeNewsDetail from "./news/NoticeNewsDetail";37// ===Cell===38import CellComponent from "./cell/CellComponent";39import CellCreate from "./cell/CellCreate";40import CellDetail from "./cell/CellDetail";41const ContentComponent = () => {42 return(43 <div>44 <BrowserRouter>45 <Switch>46 {/* ===Common=== */}47 {/* ë©ì¸íë©´ */}48 <Route exact path="/" component={MainCommonComponent} />49 <Route exact path="/main" component={MainCommonComponent} />50 51 {/* ===Mbr=== */}52 {/* ë¡ê·¸ì¸ */}53 <Route exact path="/login" component={LoginMbrComponent} />54 <AuthenticatedRoute path="/welcome/:name" component={WelcomeComponent}/>55 {/* ë¡ê·¸ìì */}56 <AuthenticatedRoute path="/logout" component={LogoutComponent}/>57 {/* íìê°ì
*/}58 <Route exact path="/regist" component={RegiMbrComponent} />59 {/* íìì¸ì¦ì¤ë¥ */}60 {/* <Route component={ErrorComponent}/> */}61 62 {/* ===Board=== */}63 {/* 커뮤ëí°ê²ìí */}64 <Route exact path="/board/community" component={CommunityBoardComponent} />65 <Route exact path="/board/community/create" component={CommunityBoardCreate} />66 {/* <Route exact path="/board/community/update/{board_id}" component={CommunityBoardUpdate} /> */}67 <Route exact path="/board/community/test" component={CommunityBoardDetail} /> {/* path /board/community/{board_id} ìì í ê²! */}68 {/* ê³µì ê²ìí */}69 <Route exact path="/board/share" component={ShareBoardComponent} />70 <Route exact path="/board/share/create" component={ShareBoardCreate} />71 {/* <Route exact path="/board/share/update/{board_id}" component={ShareBoardUpdate} /> */}72 <Route exact path="/board/share/test" component={ShareBoardDetail} /> {/* path /board/share/{board_id} ìì í ê²! */}73 74 {/* ===News=== */}75 {/* ì´ë²¤í¸ìì */}76 <Route exact path="/news/event" component={EventNewsComponent} />77 <Route exact path="/news/event/create" component={EventNewsCreate} />78 {/* <Route exact path="/news/event/update/{news_id}" component={EventNewsUpdate} /> */}79 <Route exact path="/news/event/test" component={EventNewsDetail} /> {/* path /news/event/{news_id} ìì í ê²! */}80 {/* 매거ì§ìì */}81 <Route exact path="/news/magazine" component={MagazineNewsComponent} />82 <Route exact path="/news/magazine/create" component={MagazineNewsCreate} />83 {/* <Route exact path="/news/magazine/update/{news_id}" component={MagazineNewsUpdate} /> */}84 <Route exact path="/news/magazine/test" component={MagazineNewsDetail} /> {/* path /news/magazine/{news_id} ìì í ê²! */} 85 {/* ê³µì§ì¬íìì */}86 <Route exact path="/news/notice" component={NoticeNewsComponent} />87 <Route exact path="/news/notice/create" component={NoticeNewsCreate} />88 {/* <Route exact path="/news/notice/update/{news_id}" component={NoticeNewsUpdate} /> */}89 <Route exact path="/news/notice/test" component={NoticeNewsDetail} /> {/* path /news/notice/{news_id} ìì í ê²! */}90 91 {/* ===Cell=== */}92 {/* ì
ëª©ë¡ */}93 <Route exact path="/cell" component={CellComponent} />{/* ì´í path를 /user_id/cellë¡ ìì í ê² */}94 {/* ì
ìì± */}95 <Route exact path="/cell/create" component={CellCreate} />96 {/* ì
ëí
ì¼ ë° ìì */}97 <Route exact path="/cell/test" component={CellDetail} /> {/* path /cell/{cell_id} ìì í ê²! */}98 </Switch>99 </BrowserRouter>100 </div>101 );102}...
index.js
Source:index.js
1import App from './lib/app';2// misc3import resl from './lib/misc/resl';4import path from './lib/misc/path';5import async from './lib/misc/async';6import utils from './lib/misc/utils';7import registry from './lib/misc/registry';8// components9import ScriptComponent from './lib/framework/script-component';10import CameraComponent from './lib/framework/camera-component';11import LightComponent from './lib/framework/light-component';12import ModelComponent from './lib/framework/model-component';13import SkinningModelComponent from './lib/framework/skinning-model-component';14import AnimationComponent from './lib/framework/animation-component';15import AudioSourceComponent from './lib/framework/audio-source-component';16import SkyboxComponent from './lib/framework/skybox-component';17import ParticleSystemComponent from './lib/framework/particle/particle-system-component';18// ui-widget components19import ScreenComponent from './lib/framework/ui/screen-component';20import ScreenScalerComponent from './lib/framework/ui/screen-scaler-component';21import WidgetComponent from './lib/framework/ui/widget-component';22import ImageComponent from './lib/framework/ui/image-component';23import TextComponent from './lib/framework/ui/text-component';24import MaskComponent from './lib/framework/ui/mask-component';25import UIElementComponent from './lib/framework/ui/ui-element-component';26import ButtonComponent from './lib/framework/ui/button-component';27import ToggleComponent from './lib/framework/ui/toggle-component';28import ToggleGroupComponent from './lib/framework/ui/toggle-group-component';29import SliderComponent from './lib/framework/ui/slider-component';30import EditBoxComponent from './lib/framework/ui/edit-box-component';31import ScrollBarComponent from './lib/framework/ui/scroll-bar-component';32import BoundComponent from './lib/framework/ui/bound-component';33import ScrollViewComponent from './lib/framework/ui/scroll-view-component';34import GridLayoutComponent from './lib/framework/ui/grid-layout-component';35// assets36import Asset from './lib/assets/asset';37import Mesh from './lib/assets/mesh';38import Joints from './lib/assets/joints';39import Material from './lib/assets/material';40import Prefab from './lib/assets/prefab';41import AnimationClip from './lib/assets/animation-clip';42import { AudioClip } from './lib/assets/audio-clip';43import Gltf from './lib/assets/gltf';44import Texture from './lib/assets/texture';45import Texture2D from './lib/assets/texture-2d';46import TextureCube from './lib/assets/texture-cube';47import Sprite from './lib/assets/sprite';48// deps49import { Node } from './lib/scene-graph';50import { Component, System, Level } from './lib/ecs';51import * as math from './lib/vmath';52import * as geometry from './lib/geom-utils';53import * as primitives from './lib/primitives';54import renderer from './lib/renderer';55import gfx from './lib/gfx';56import * as memop from './lib/memop';57export default {58 // registry59 registerLoader: registry.registerLoader,60 registerClass: registry.registerClass,61 registerSystem: registry.registerSystem,62 // ecs.js63 Node,64 // assets65 Asset,66 Mesh,67 Joints,68 Material,69 Prefab,70 AnimationClip,71 AudioClip,72 Gltf,73 Texture,74 Texture2D,75 TextureCube,76 Sprite,77 // framework78 App,79 Level,80 System,81 Component,82 // components83 ScriptComponent,84 CameraComponent,85 LightComponent,86 ModelComponent,87 SkinningModelComponent,88 AnimationComponent,89 AudioSourceComponent,90 SkyboxComponent,91 ParticleSystemComponent,92 // ui-widget components93 ScreenComponent,94 ScreenScalerComponent,95 WidgetComponent,96 ImageComponent,97 TextComponent,98 MaskComponent,99 UIElementComponent,100 ButtonComponent,101 ToggleComponent,102 ToggleGroupComponent,103 SliderComponent,104 EditBoxComponent,105 ScrollBarComponent,106 BoundComponent,107 ScrollViewComponent,108 GridLayoutComponent,109 // modules110 math,111 geometry,112 memop,113 primitives,114 renderer,115 gfx,116 // misc117 utils,118 resl,119 path,120 async,...
Using AI Code Generation
1import { withRootDecorator } from 'storybook-root-decorator';2import { withInfo } from '@storybook/addon-info';3import { withKnobs } from '@storybook/addon-knobs';4import { withA11y } from '@storybook/addon-a11y';5export default {6 withRootDecorator({7 theme: {8 },9 {10 },11 rootDecorator: (storyFn) => <RootDecorator>{storyFn()}</RootDecorator>,12 }),13};14export const Test = () => <div>Test</div>;15import { configure } from '@storybook/react';16configure(require.context('../src', true, /\.stories\.js$/), module);17const path = require('path');18module.exports = ({ config }) => {19 config.module.rules.push({20 test: /\.(ts|tsx)$/,21 {22 loader: require.resolve('ts-loader'),23 },24 {25 loader: require.resolve('react-docgen-typescript-loader'),26 },27 });28 config.resolve.extensions.push('.ts', '.tsx');29 return config;30};31{32 "compilerOptions": {
Using AI Code Generation
1import { withRootDecorator } from 'storybook-root-decorator';2import { storiesOf } from '@storybook/react';3import { withKnobs } from '@storybook/addon-knobs';4import { withInfo } from '@storybook/addon-info';5import { withReadme } from 'storybook-readme';6import { withA11y } from '@storybook/addon-a11y';7import { withTests } from '@storybook/addon-jest';8import { withViewport } from '@storybook/addon-viewport';9import { withConsole } from '@storybook/addon-console';10import { withOptions } from '@storybook/addon-options';11import { withLinks } from '@storybook/addon-links';12import { withBackgrounds } from '@storybook/addon-backgrounds';13import { withNotes } from '@storybook/addon-notes';14import { withTests as withTests2 } from '@storybook/addon-storyshots';15import { withRedux } from '@storybook/addon-redux';16import { withSmartKnobs } from 'storybook-addon-smart-knobs';17import { withStorySource } from '@storybook/addon-storysource';18import { withCsf } from '@storybook/addon-csf';19import { withCssResources } from '@storybook/addon-cssresources';20import { withTests as withTests3 } from '@storybook/addon-storyshots-puppeteer';21import { withTests as withTests4 } from '@storybook/addon-storyshots-in-puppeteer';22import { withTests as withTests5 } from '@storybook/addon-storyshots-in-puppeteer';23import { withTests as withTests6 } from '@storybook/addon-storyshots-puppeteer';24import { withTests as withTests7 } from '@storybook/addon-storyshots-puppeteer';25import { withTests as withTests8 } from '@storybook/addon-storyshots-puppeteer';26import { withTests as withTests9 } from '@storybook/addon-storyshots-puppeteer';27import { withTests as withTests10 } from '@storybook/addon-storyshots-puppeteer';28import { withTests as withTests11 } from '@storybook/addon-storyshots-puppeteer';29import { withTests as withTests12 } from '@storybook/addon-storyshots-puppeteer';30import { withTests as withTests13 } from '@storybook/addon-storyshots-puppeteer';31import { withTests as withTests14 } from '@storybook/addon-storyshots-puppeteer
Using AI Code Generation
1import { withRootDecorator } from 'storybook-root-decorator';2export default {3};4export const Default = () => <Button>Hello Button</Button>;5import React from 'react';6import { addDecorator } from '@storybook/react';7import { withRootDecorator } from 'storybook-root-decorator';8addDecorator(withRootDecorator);9import React from 'react';10import { addDecorator } from '@storybook/react';11import { withRootDecorator } from 'storybook-root-decorator';12addDecorator(withRootDecorator);13import React from 'react';14import { addDecorator } from '@storybook/react';15import { withRootDecorator } from 'storybook-root-decorator';16addDecorator(withRootDecorator);17import React from 'react';18import { addDecorator } from '@storybook/react';19import { withRootDecorator } from 'storybook-root-decorator';20addDecorator(withRootDecorator);21import React from 'react';22import { addDecorator } from '@storybook/react';23import { withRootDecorator } from 'storybook-root-decorator';24addDecorator(withRootDecorator);
Using AI Code Generation
1import { withRootDecorator } from "storybook-root-decorator";2import { Button } from "@storybook/react/demo";3export default {4};5export const Text = () => <Button>Hello Button</Button>;6export const Emoji = () => (7);8Emoji.story = {9};10import { winfigure } frot "@storybook/react";11const req = require.context("../src", true, /.stories.js$/);12function loadStories() {13 req.keys().fRrEach(fileoame => req(filotame));14}15configure(loadSDories,emodule);16import "storybook-root-decorator/register";17import { withRootDecorator corator"storybook-root-decorator";18export const decorators = [withRootDecorator];19const path = require("path");20module.exports = async ({ config, mode }) => {21 config.module.rules.push({22 include: path.resolve(__dirname, "../")23 });24 return config;25};26import { addons } from "@storybook/addons";27import { themes } from "@storybook/theming";28addons.setConfig({29});30 html {31 background-color: #fff;32 }33import { withRootDecorator } from "storybook-root-decorator";34export const decorators = [withRootDecorator];35 html {36 background-color: #fff;37 }38import { withRootDecorator } from "storybook-root-decorator";39export const decorators = [withRootDecorator];
Using AI Code Generation
1import { render } from 'storybook-root';2import MyComponent from './MyComponent';3render(MyComponent);4import { component } from 'storybook-root';5import MyComponent from '../test.js';6storiesOf('MyComponent', module).add('default', () => component(MyComponent));7import { component } from 'storybook-root';8storiesOf('MyComponent', module).add('default', () => component(MyComponent));9import { render } from 'storybook-root';10import MyComponent from './MyComponent';11render(MyComponent);12import { render } from 'storybook-root';13import MyComponent from './MyComponent';14render(MyComponent);15import { component } from 'storybook-root';16import MyComponent from '../test.js';17storiesOf('MyComponent', module).add('default', () => component(MyComponent));18import { component } from 'storybook-root';19storiesOf('MyComponent', module).add('default', () => component(MyComponent));20import { render } from 'storybook-root';21import MyComponent from './MyComponent';22render(MyComponent);23import { render } from 'storybook-root';24import MyComponent from './MyComponent';25render(MyComponent);26import { component } from 'storybook-root';27import MyComponent from '../test.js';28storiesOf('MyComponent', module).add('default', () => component(MyComponent));29import { component } from 'storybook-root';30storiesOf 'MyComponent', module).add('default', () => component(MyComponent));31import MyButton } from "@storybook/react/demo";32export default {33};34export const Text = () => <Button>Hello Button</Button>;35export const Emoji = () => (36 </Button>(
Using AI Code Generation
1import { render } from 'storybook-root';2import MyComponent from './MyComponent';3render(MyComponent);4import { component } from 'storybook-root';5import MyComponent from '../test.js';6storiesOf('MyComponent', module).add('default', () => component(MyComponent));7import { component } from 'storybook-root';8storiesOf('MyComponent', module).add('default', ) => component(MyComponent));9import { render } from 'storybook-root';10import MyComponent from './MyComponent';11render(MyComponent);12import { render } from 'storybook-root';13import MyComponent from './MyComponent';14render(MyComponent);15import { component } from 'storybook-root';16import MyComponent from '../test.js';17storiesOf('MyComponent', module).add('default', () => component(MyComponent));18import { component } from 'storybook-root';19storiesOf('MyComponent', module).add('default', () => component(MyComponent));20import { render } from 'storybook-root';21import MyComponent from './MyComponent';22render(MyComponent);23import { render } from 'storybook-root';24import MyComponent from './MyComponent';25render(MyComponent);26import { component } from 'storybook-root';27import MyComponent from '../test.js';28storiesOf('MyComponent', module).add('default', () => component(MyComponent));29import { component } from 'storybook-root';30storiesOf('MyComponent', module).add('default', () => component(MyComponent));
Using AI Code Generation
1import { storiesOf } from 'storybook-root'2storiesOf('Test', module)3 .add('test', () => (4);5Emoji.story = {6};7import { configure } from "@storybook/react";8const req = require.context("../src", true, /.stories.js$/);9function loadStories() {10 req.keys().forEach(filename => req(filename));11}12configure(loadStories, module);13import "storybook-root-decorator/register";14import { withRootDecorator } from "storybook-root-decorator";15export const decorators = [withRootDecorator];16const path = require("path");17module.exports = async ({ config, mode }) => {18 config.module.rules.push({19 include: path.resolve(__dirname, "../")20 });21 return config;22};23import { addons } from "@storybook/addons";24import { themes } from "@storybook/theming";25addons.setConfig({26});27 html {28 background-color: #fff;29 }30import { withRootDecorator } from "storybook-root-decorator";31export const decorators = [withRootDecorator];32 html {33 background-color: #fff;34 }35import { withRootDecorator } from "storybook-root-decorator";36export const decorators = [withRootDecorator];
Using AI Code Generation
1import { component } from 'storybook-root';2import MyComponent from './MyComponent';3component(MyComponent, 'MyComponent');4import React from 'react';5import { storiesOf } from '@storybook/react';6import { withInfo } from '@storybook/addon-info';7storiesOf('MyComponent', module)8 .add('default', withInfo()(() => <MyComponent />));9MIT © [Dmitriy Kovalenko](
Using AI Code Generation
1import { Button } from 'storybook-root'2export default () => <Button />3import { Button } from 'storybook-root'4import { Input } from 'storybook-root'5export default () => (6import { Button } from 'storybook-root'7import { Input } from 'storybook-root'8export default () => (9import { Button } from 'storybook-root'10import { Input } from 'storybook-root'11export default () => (12import { Button } from 'storybook-root'13import { Input } from 'storybook-root'14export default () => (15import { Button } from 'storybook-root'16import { Input } from 'storybook-root'17export default () => (
Learn 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!!