1. /**
    
  2.  * Copyright (c) Meta Platforms, Inc. and affiliates.
    
  3.  *
    
  4.  * This source code is licensed under the MIT license found in the
    
  5.  * LICENSE file in the root directory of this source tree.
    
  6.  *
    
  7.  * @emails react-core
    
  8.  */
    
  9. 
    
  10. 'use strict';
    
  11. 
    
  12. let ReactErrorUtils;
    
  13. 
    
  14. describe('ReactErrorUtils', () => {
    
  15.   beforeEach(() => {
    
  16.     // TODO: can we express this test with only public API?
    
  17.     ReactErrorUtils = require('shared/ReactErrorUtils');
    
  18.   });
    
  19. 
    
  20.   it(`it should rethrow caught errors`, () => {
    
  21.     const err = new Error('foo');
    
  22.     const callback = function () {
    
  23.       throw err;
    
  24.     };
    
  25.     ReactErrorUtils.invokeGuardedCallbackAndCatchFirstError(
    
  26.       'foo',
    
  27.       callback,
    
  28.       null,
    
  29.     );
    
  30.     expect(ReactErrorUtils.hasCaughtError()).toBe(false);
    
  31.     expect(() => ReactErrorUtils.rethrowCaughtError()).toThrow(err);
    
  32.   });
    
  33. 
    
  34.   it(`should call the callback the passed arguments`, () => {
    
  35.     const callback = jest.fn();
    
  36.     ReactErrorUtils.invokeGuardedCallback(
    
  37.       'foo',
    
  38.       callback,
    
  39.       null,
    
  40.       'arg1',
    
  41.       'arg2',
    
  42.     );
    
  43.     expect(callback).toBeCalledWith('arg1', 'arg2');
    
  44.   });
    
  45. 
    
  46.   it(`should call the callback with the provided context`, () => {
    
  47.     const context = {didCall: false};
    
  48.     ReactErrorUtils.invokeGuardedCallback(
    
  49.       'foo',
    
  50.       function () {
    
  51.         this.didCall = true;
    
  52.       },
    
  53.       context,
    
  54.     );
    
  55.     expect(context.didCall).toBe(true);
    
  56.   });
    
  57. 
    
  58.   it(`should catch errors`, () => {
    
  59.     const error = new Error();
    
  60.     const returnValue = ReactErrorUtils.invokeGuardedCallback(
    
  61.       'foo',
    
  62.       function () {
    
  63.         throw error;
    
  64.       },
    
  65.       null,
    
  66.       'arg1',
    
  67.       'arg2',
    
  68.     );
    
  69.     expect(returnValue).toBe(undefined);
    
  70.     expect(ReactErrorUtils.hasCaughtError()).toBe(true);
    
  71.     expect(ReactErrorUtils.clearCaughtError()).toBe(error);
    
  72.   });
    
  73. 
    
  74.   it(`should return false from clearCaughtError if no error was thrown`, () => {
    
  75.     const callback = jest.fn();
    
  76.     ReactErrorUtils.invokeGuardedCallback('foo', callback, null);
    
  77.     expect(ReactErrorUtils.hasCaughtError()).toBe(false);
    
  78.     expect(ReactErrorUtils.clearCaughtError).toThrow('no error was captured');
    
  79.   });
    
  80. 
    
  81.   it(`can nest with same debug name`, () => {
    
  82.     const err1 = new Error();
    
  83.     let err2;
    
  84.     const err3 = new Error();
    
  85.     ReactErrorUtils.invokeGuardedCallback(
    
  86.       'foo',
    
  87.       function () {
    
  88.         ReactErrorUtils.invokeGuardedCallback(
    
  89.           'foo',
    
  90.           function () {
    
  91.             throw err1;
    
  92.           },
    
  93.           null,
    
  94.         );
    
  95.         err2 = ReactErrorUtils.clearCaughtError();
    
  96.         throw err3;
    
  97.       },
    
  98.       null,
    
  99.     );
    
  100.     const err4 = ReactErrorUtils.clearCaughtError();
    
  101. 
    
  102.     expect(err2).toBe(err1);
    
  103.     expect(err4).toBe(err3);
    
  104.   });
    
  105. 
    
  106.   it(`handles nested errors`, () => {
    
  107.     const err1 = new Error();
    
  108.     let err2;
    
  109.     ReactErrorUtils.invokeGuardedCallback(
    
  110.       'foo',
    
  111.       function () {
    
  112.         ReactErrorUtils.invokeGuardedCallback(
    
  113.           'foo',
    
  114.           function () {
    
  115.             throw err1;
    
  116.           },
    
  117.           null,
    
  118.         );
    
  119.         err2 = ReactErrorUtils.clearCaughtError();
    
  120.       },
    
  121.       null,
    
  122.     );
    
  123.     // Returns null because inner error was already captured
    
  124.     expect(ReactErrorUtils.hasCaughtError()).toBe(false);
    
  125. 
    
  126.     expect(err2).toBe(err1);
    
  127.   });
    
  128. 
    
  129.   it('handles nested errors in separate renderers', () => {
    
  130.     const ReactErrorUtils1 = require('shared/ReactErrorUtils');
    
  131.     jest.resetModules();
    
  132.     const ReactErrorUtils2 = require('shared/ReactErrorUtils');
    
  133.     expect(ReactErrorUtils1).not.toEqual(ReactErrorUtils2);
    
  134. 
    
  135.     const ops = [];
    
  136. 
    
  137.     ReactErrorUtils1.invokeGuardedCallback(
    
  138.       null,
    
  139.       () => {
    
  140.         ReactErrorUtils2.invokeGuardedCallback(
    
  141.           null,
    
  142.           () => {
    
  143.             throw new Error('nested error');
    
  144.           },
    
  145.           null,
    
  146.         );
    
  147.         // ReactErrorUtils2 should catch the error
    
  148.         ops.push(ReactErrorUtils2.hasCaughtError());
    
  149.         ops.push(ReactErrorUtils2.clearCaughtError().message);
    
  150.       },
    
  151.       null,
    
  152.     );
    
  153. 
    
  154.     // ReactErrorUtils1 should not catch the error
    
  155.     ops.push(ReactErrorUtils1.hasCaughtError());
    
  156. 
    
  157.     expect(ops).toEqual([true, 'nested error', false]);
    
  158.   });
    
  159. 
    
  160.   if (!__DEV__) {
    
  161.     // jsdom doesn't handle this properly, but Chrome and Firefox should. Test
    
  162.     // this with a fixture.
    
  163.     it('catches null values', () => {
    
  164.       ReactErrorUtils.invokeGuardedCallback(
    
  165.         null,
    
  166.         function () {
    
  167.           throw null; // eslint-disable-line no-throw-literal
    
  168.         },
    
  169.         null,
    
  170.       );
    
  171.       expect(ReactErrorUtils.hasCaughtError()).toBe(true);
    
  172.       expect(ReactErrorUtils.clearCaughtError()).toBe(null);
    
  173.     });
    
  174.   }
    
  175. 
    
  176.   it(`can be shimmed`, () => {
    
  177.     const ops = [];
    
  178.     jest.resetModules();
    
  179.     jest.mock(
    
  180.       'shared/invokeGuardedCallbackImpl',
    
  181.       () =>
    
  182.         function invokeGuardedCallback(name, func, context, a) {
    
  183.           ops.push(a);
    
  184.           try {
    
  185.             func.call(context, a);
    
  186.           } catch (error) {
    
  187.             this.onError(error);
    
  188.           }
    
  189.         },
    
  190.     );
    
  191.     ReactErrorUtils = require('shared/ReactErrorUtils');
    
  192. 
    
  193.     try {
    
  194.       const err = new Error('foo');
    
  195.       const callback = function () {
    
  196.         throw err;
    
  197.       };
    
  198.       ReactErrorUtils.invokeGuardedCallbackAndCatchFirstError(
    
  199.         'foo',
    
  200.         callback,
    
  201.         null,
    
  202.         'somearg',
    
  203.       );
    
  204.       expect(() => ReactErrorUtils.rethrowCaughtError()).toThrow(err);
    
  205.       expect(ops).toEqual(['somearg']);
    
  206.     } finally {
    
  207.       jest.unmock('shared/invokeGuardedCallbackImpl');
    
  208.     }
    
  209.   });
    
  210. });