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.  * @flow
    
  8.  */
    
  9. 
    
  10. import {normalizeCodeLocInfo} from './utils';
    
  11. 
    
  12. let React;
    
  13. let ReactDOMClient;
    
  14. let act;
    
  15. let fakeConsole;
    
  16. let legacyRender;
    
  17. let mockError;
    
  18. let mockInfo;
    
  19. let mockGroup;
    
  20. let mockGroupCollapsed;
    
  21. let mockLog;
    
  22. let mockWarn;
    
  23. let patchConsole;
    
  24. let unpatchConsole;
    
  25. let rendererID;
    
  26. 
    
  27. describe('console', () => {
    
  28.   beforeEach(() => {
    
  29.     const Console = require('react-devtools-shared/src/backend/console');
    
  30. 
    
  31.     patchConsole = Console.patch;
    
  32.     unpatchConsole = Console.unpatch;
    
  33. 
    
  34.     // Patch a fake console so we can verify with tests below.
    
  35.     // Patching the real console is too complicated,
    
  36.     // because Jest itself has hooks into it as does our test env setup.
    
  37.     mockError = jest.fn();
    
  38.     mockInfo = jest.fn();
    
  39.     mockGroup = jest.fn();
    
  40.     mockGroupCollapsed = jest.fn();
    
  41.     mockLog = jest.fn();
    
  42.     mockWarn = jest.fn();
    
  43.     fakeConsole = {
    
  44.       error: mockError,
    
  45.       info: mockInfo,
    
  46.       log: mockLog,
    
  47.       warn: mockWarn,
    
  48.       group: mockGroup,
    
  49.       groupCollapsed: mockGroupCollapsed,
    
  50.     };
    
  51. 
    
  52.     Console.dangerous_setTargetConsoleForTesting(fakeConsole);
    
  53.     global.__REACT_DEVTOOLS_GLOBAL_HOOK__.dangerous_setTargetConsoleForTesting(
    
  54.       fakeConsole,
    
  55.     );
    
  56. 
    
  57.     const inject = global.__REACT_DEVTOOLS_GLOBAL_HOOK__.inject;
    
  58.     global.__REACT_DEVTOOLS_GLOBAL_HOOK__.inject = internals => {
    
  59.       rendererID = inject(internals);
    
  60. 
    
  61.       Console.registerRenderer(internals);
    
  62.       return rendererID;
    
  63.     };
    
  64. 
    
  65.     React = require('react');
    
  66.     ReactDOMClient = require('react-dom/client');
    
  67. 
    
  68.     const utils = require('./utils');
    
  69.     act = utils.act;
    
  70.     legacyRender = utils.legacyRender;
    
  71.   });
    
  72. 
    
  73.   // @reactVersion >=18.0
    
  74.   it('should not patch console methods that are not explicitly overridden', () => {
    
  75.     expect(fakeConsole.error).not.toBe(mockError);
    
  76.     expect(fakeConsole.info).toBe(mockInfo);
    
  77.     expect(fakeConsole.log).toBe(mockLog);
    
  78.     expect(fakeConsole.warn).not.toBe(mockWarn);
    
  79.     expect(fakeConsole.group).toBe(mockGroup);
    
  80.     expect(fakeConsole.groupCollapsed).toBe(mockGroupCollapsed);
    
  81.   });
    
  82. 
    
  83.   // @reactVersion >=18.0
    
  84.   it('should patch the console when appendComponentStack is enabled', () => {
    
  85.     unpatchConsole();
    
  86. 
    
  87.     expect(fakeConsole.error).toBe(mockError);
    
  88.     expect(fakeConsole.warn).toBe(mockWarn);
    
  89. 
    
  90.     patchConsole({
    
  91.       appendComponentStack: true,
    
  92.       breakOnConsoleErrors: false,
    
  93.       showInlineWarningsAndErrors: false,
    
  94.     });
    
  95. 
    
  96.     expect(fakeConsole.error).not.toBe(mockError);
    
  97.     expect(fakeConsole.warn).not.toBe(mockWarn);
    
  98.   });
    
  99. 
    
  100.   // @reactVersion >=18.0
    
  101.   it('should patch the console when breakOnConsoleErrors is enabled', () => {
    
  102.     unpatchConsole();
    
  103. 
    
  104.     expect(fakeConsole.error).toBe(mockError);
    
  105.     expect(fakeConsole.warn).toBe(mockWarn);
    
  106. 
    
  107.     patchConsole({
    
  108.       appendComponentStack: false,
    
  109.       breakOnConsoleErrors: true,
    
  110.       showInlineWarningsAndErrors: false,
    
  111.     });
    
  112. 
    
  113.     expect(fakeConsole.error).not.toBe(mockError);
    
  114.     expect(fakeConsole.warn).not.toBe(mockWarn);
    
  115.   });
    
  116. 
    
  117.   // @reactVersion >=18.0
    
  118.   it('should patch the console when showInlineWarningsAndErrors is enabled', () => {
    
  119.     unpatchConsole();
    
  120. 
    
  121.     expect(fakeConsole.error).toBe(mockError);
    
  122.     expect(fakeConsole.warn).toBe(mockWarn);
    
  123. 
    
  124.     patchConsole({
    
  125.       appendComponentStack: false,
    
  126.       breakOnConsoleErrors: false,
    
  127.       showInlineWarningsAndErrors: true,
    
  128.     });
    
  129. 
    
  130.     expect(fakeConsole.error).not.toBe(mockError);
    
  131.     expect(fakeConsole.warn).not.toBe(mockWarn);
    
  132.   });
    
  133. 
    
  134.   // @reactVersion >=18.0
    
  135.   it('should only patch the console once', () => {
    
  136.     const {error, warn} = fakeConsole;
    
  137. 
    
  138.     patchConsole({
    
  139.       appendComponentStack: true,
    
  140.       breakOnConsoleErrors: false,
    
  141.       showInlineWarningsAndErrors: false,
    
  142.     });
    
  143. 
    
  144.     expect(fakeConsole.error).toBe(error);
    
  145.     expect(fakeConsole.warn).toBe(warn);
    
  146.   });
    
  147. 
    
  148.   // @reactVersion >=18.0
    
  149.   it('should un-patch when requested', () => {
    
  150.     expect(fakeConsole.error).not.toBe(mockError);
    
  151.     expect(fakeConsole.warn).not.toBe(mockWarn);
    
  152. 
    
  153.     unpatchConsole();
    
  154. 
    
  155.     expect(fakeConsole.error).toBe(mockError);
    
  156.     expect(fakeConsole.warn).toBe(mockWarn);
    
  157.   });
    
  158. 
    
  159.   // @reactVersion >=18.0
    
  160.   it('should pass through logs when there is no current fiber', () => {
    
  161.     expect(mockLog).toHaveBeenCalledTimes(0);
    
  162.     expect(mockWarn).toHaveBeenCalledTimes(0);
    
  163.     expect(mockError).toHaveBeenCalledTimes(0);
    
  164.     fakeConsole.log('log');
    
  165.     fakeConsole.warn('warn');
    
  166.     fakeConsole.error('error');
    
  167.     expect(mockLog).toHaveBeenCalledTimes(1);
    
  168.     expect(mockLog.mock.calls[0]).toHaveLength(1);
    
  169.     expect(mockLog.mock.calls[0][0]).toBe('log');
    
  170.     expect(mockWarn).toHaveBeenCalledTimes(1);
    
  171.     expect(mockWarn.mock.calls[0]).toHaveLength(1);
    
  172.     expect(mockWarn.mock.calls[0][0]).toBe('warn');
    
  173.     expect(mockError).toHaveBeenCalledTimes(1);
    
  174.     expect(mockError.mock.calls[0]).toHaveLength(1);
    
  175.     expect(mockError.mock.calls[0][0]).toBe('error');
    
  176.   });
    
  177. 
    
  178.   // @reactVersion >=18.0
    
  179.   it('should not append multiple stacks', () => {
    
  180.     global.__REACT_DEVTOOLS_APPEND_COMPONENT_STACK__ = true;
    
  181. 
    
  182.     const Child = ({children}) => {
    
  183.       fakeConsole.warn('warn\n    in Child (at fake.js:123)');
    
  184.       fakeConsole.error('error', '\n    in Child (at fake.js:123)');
    
  185.       return null;
    
  186.     };
    
  187. 
    
  188.     act(() => legacyRender(<Child />, document.createElement('div')));
    
  189. 
    
  190.     expect(mockWarn).toHaveBeenCalledTimes(1);
    
  191.     expect(mockWarn.mock.calls[0]).toHaveLength(1);
    
  192.     expect(mockWarn.mock.calls[0][0]).toBe(
    
  193.       'warn\n    in Child (at fake.js:123)',
    
  194.     );
    
  195.     expect(mockError).toHaveBeenCalledTimes(1);
    
  196.     expect(mockError.mock.calls[0]).toHaveLength(2);
    
  197.     expect(mockError.mock.calls[0][0]).toBe('error');
    
  198.     expect(mockError.mock.calls[0][1]).toBe('\n    in Child (at fake.js:123)');
    
  199.   });
    
  200. 
    
  201.   // @reactVersion >=18.0
    
  202.   it('should append component stacks to errors and warnings logged during render', () => {
    
  203.     global.__REACT_DEVTOOLS_APPEND_COMPONENT_STACK__ = true;
    
  204. 
    
  205.     const Intermediate = ({children}) => children;
    
  206.     const Parent = ({children}) => (
    
  207.       <Intermediate>
    
  208.         <Child />
    
  209.       </Intermediate>
    
  210.     );
    
  211.     const Child = ({children}) => {
    
  212.       fakeConsole.error('error');
    
  213.       fakeConsole.log('log');
    
  214.       fakeConsole.warn('warn');
    
  215.       return null;
    
  216.     };
    
  217. 
    
  218.     act(() => legacyRender(<Parent />, document.createElement('div')));
    
  219. 
    
  220.     expect(mockLog).toHaveBeenCalledTimes(1);
    
  221.     expect(mockLog.mock.calls[0]).toHaveLength(1);
    
  222.     expect(mockLog.mock.calls[0][0]).toBe('log');
    
  223.     expect(mockWarn).toHaveBeenCalledTimes(1);
    
  224.     expect(mockWarn.mock.calls[0]).toHaveLength(2);
    
  225.     expect(mockWarn.mock.calls[0][0]).toBe('warn');
    
  226.     expect(normalizeCodeLocInfo(mockWarn.mock.calls[0][1])).toEqual(
    
  227.       '\n    in Child (at **)\n    in Intermediate (at **)\n    in Parent (at **)',
    
  228.     );
    
  229.     expect(mockError).toHaveBeenCalledTimes(1);
    
  230.     expect(mockError.mock.calls[0]).toHaveLength(2);
    
  231.     expect(mockError.mock.calls[0][0]).toBe('error');
    
  232.     expect(normalizeCodeLocInfo(mockError.mock.calls[0][1])).toBe(
    
  233.       '\n    in Child (at **)\n    in Intermediate (at **)\n    in Parent (at **)',
    
  234.     );
    
  235.   });
    
  236. 
    
  237.   // @reactVersion >=18.0
    
  238.   it('should append component stacks to errors and warnings logged from effects', () => {
    
  239.     const Intermediate = ({children}) => children;
    
  240.     const Parent = ({children}) => (
    
  241.       <Intermediate>
    
  242.         <Child />
    
  243.       </Intermediate>
    
  244.     );
    
  245.     const Child = ({children}) => {
    
  246.       React.useLayoutEffect(() => {
    
  247.         fakeConsole.error('active error');
    
  248.         fakeConsole.log('active log');
    
  249.         fakeConsole.warn('active warn');
    
  250.       });
    
  251.       React.useEffect(() => {
    
  252.         fakeConsole.error('passive error');
    
  253.         fakeConsole.log('passive log');
    
  254.         fakeConsole.warn('passive warn');
    
  255.       });
    
  256.       return null;
    
  257.     };
    
  258. 
    
  259.     act(() => legacyRender(<Parent />, document.createElement('div')));
    
  260. 
    
  261.     expect(mockLog).toHaveBeenCalledTimes(2);
    
  262.     expect(mockLog.mock.calls[0]).toHaveLength(1);
    
  263.     expect(mockLog.mock.calls[0][0]).toBe('active log');
    
  264.     expect(mockLog.mock.calls[1]).toHaveLength(1);
    
  265.     expect(mockLog.mock.calls[1][0]).toBe('passive log');
    
  266.     expect(mockWarn).toHaveBeenCalledTimes(2);
    
  267.     expect(mockWarn.mock.calls[0]).toHaveLength(2);
    
  268.     expect(mockWarn.mock.calls[0][0]).toBe('active warn');
    
  269.     expect(normalizeCodeLocInfo(mockWarn.mock.calls[0][1])).toEqual(
    
  270.       '\n    in Child (at **)\n    in Intermediate (at **)\n    in Parent (at **)',
    
  271.     );
    
  272.     expect(mockWarn.mock.calls[1]).toHaveLength(2);
    
  273.     expect(mockWarn.mock.calls[1][0]).toBe('passive warn');
    
  274.     expect(normalizeCodeLocInfo(mockWarn.mock.calls[1][1])).toEqual(
    
  275.       '\n    in Child (at **)\n    in Intermediate (at **)\n    in Parent (at **)',
    
  276.     );
    
  277.     expect(mockError).toHaveBeenCalledTimes(2);
    
  278.     expect(mockError.mock.calls[0]).toHaveLength(2);
    
  279.     expect(mockError.mock.calls[0][0]).toBe('active error');
    
  280.     expect(normalizeCodeLocInfo(mockError.mock.calls[0][1])).toBe(
    
  281.       '\n    in Child (at **)\n    in Intermediate (at **)\n    in Parent (at **)',
    
  282.     );
    
  283.     expect(mockError.mock.calls[1]).toHaveLength(2);
    
  284.     expect(mockError.mock.calls[1][0]).toBe('passive error');
    
  285.     expect(normalizeCodeLocInfo(mockError.mock.calls[1][1])).toBe(
    
  286.       '\n    in Child (at **)\n    in Intermediate (at **)\n    in Parent (at **)',
    
  287.     );
    
  288.   });
    
  289. 
    
  290.   // @reactVersion >=18.0
    
  291.   it('should append component stacks to errors and warnings logged from commit hooks', () => {
    
  292.     global.__REACT_DEVTOOLS_APPEND_COMPONENT_STACK__ = true;
    
  293. 
    
  294.     const Intermediate = ({children}) => children;
    
  295.     const Parent = ({children}) => (
    
  296.       <Intermediate>
    
  297.         <Child />
    
  298.       </Intermediate>
    
  299.     );
    
  300.     class Child extends React.Component<any> {
    
  301.       componentDidMount() {
    
  302.         fakeConsole.error('didMount error');
    
  303.         fakeConsole.log('didMount log');
    
  304.         fakeConsole.warn('didMount warn');
    
  305.       }
    
  306.       componentDidUpdate() {
    
  307.         fakeConsole.error('didUpdate error');
    
  308.         fakeConsole.log('didUpdate log');
    
  309.         fakeConsole.warn('didUpdate warn');
    
  310.       }
    
  311.       render() {
    
  312.         return null;
    
  313.       }
    
  314.     }
    
  315. 
    
  316.     const container = document.createElement('div');
    
  317.     act(() => legacyRender(<Parent />, container));
    
  318.     act(() => legacyRender(<Parent />, container));
    
  319. 
    
  320.     expect(mockLog).toHaveBeenCalledTimes(2);
    
  321.     expect(mockLog.mock.calls[0]).toHaveLength(1);
    
  322.     expect(mockLog.mock.calls[0][0]).toBe('didMount log');
    
  323.     expect(mockLog.mock.calls[1]).toHaveLength(1);
    
  324.     expect(mockLog.mock.calls[1][0]).toBe('didUpdate log');
    
  325.     expect(mockWarn).toHaveBeenCalledTimes(2);
    
  326.     expect(mockWarn.mock.calls[0]).toHaveLength(2);
    
  327.     expect(mockWarn.mock.calls[0][0]).toBe('didMount warn');
    
  328.     expect(normalizeCodeLocInfo(mockWarn.mock.calls[0][1])).toEqual(
    
  329.       '\n    in Child (at **)\n    in Intermediate (at **)\n    in Parent (at **)',
    
  330.     );
    
  331.     expect(mockWarn.mock.calls[1]).toHaveLength(2);
    
  332.     expect(mockWarn.mock.calls[1][0]).toBe('didUpdate warn');
    
  333.     expect(normalizeCodeLocInfo(mockWarn.mock.calls[1][1])).toEqual(
    
  334.       '\n    in Child (at **)\n    in Intermediate (at **)\n    in Parent (at **)',
    
  335.     );
    
  336.     expect(mockError).toHaveBeenCalledTimes(2);
    
  337.     expect(mockError.mock.calls[0]).toHaveLength(2);
    
  338.     expect(mockError.mock.calls[0][0]).toBe('didMount error');
    
  339.     expect(normalizeCodeLocInfo(mockError.mock.calls[0][1])).toBe(
    
  340.       '\n    in Child (at **)\n    in Intermediate (at **)\n    in Parent (at **)',
    
  341.     );
    
  342.     expect(mockError.mock.calls[1]).toHaveLength(2);
    
  343.     expect(mockError.mock.calls[1][0]).toBe('didUpdate error');
    
  344.     expect(normalizeCodeLocInfo(mockError.mock.calls[1][1])).toBe(
    
  345.       '\n    in Child (at **)\n    in Intermediate (at **)\n    in Parent (at **)',
    
  346.     );
    
  347.   });
    
  348. 
    
  349.   // @reactVersion >=18.0
    
  350.   it('should append component stacks to errors and warnings logged from gDSFP', () => {
    
  351.     const Intermediate = ({children}) => children;
    
  352.     const Parent = ({children}) => (
    
  353.       <Intermediate>
    
  354.         <Child />
    
  355.       </Intermediate>
    
  356.     );
    
  357.     class Child extends React.Component<any, any> {
    
  358.       state = {};
    
  359.       static getDerivedStateFromProps() {
    
  360.         fakeConsole.error('error');
    
  361.         fakeConsole.log('log');
    
  362.         fakeConsole.warn('warn');
    
  363.         return null;
    
  364.       }
    
  365.       render() {
    
  366.         return null;
    
  367.       }
    
  368.     }
    
  369. 
    
  370.     act(() => legacyRender(<Parent />, document.createElement('div')));
    
  371. 
    
  372.     expect(mockLog).toHaveBeenCalledTimes(1);
    
  373.     expect(mockLog.mock.calls[0]).toHaveLength(1);
    
  374.     expect(mockLog.mock.calls[0][0]).toBe('log');
    
  375.     expect(mockWarn).toHaveBeenCalledTimes(1);
    
  376.     expect(mockWarn.mock.calls[0]).toHaveLength(2);
    
  377.     expect(mockWarn.mock.calls[0][0]).toBe('warn');
    
  378.     expect(normalizeCodeLocInfo(mockWarn.mock.calls[0][1])).toEqual(
    
  379.       '\n    in Child (at **)\n    in Intermediate (at **)\n    in Parent (at **)',
    
  380.     );
    
  381.     expect(mockError).toHaveBeenCalledTimes(1);
    
  382.     expect(mockError.mock.calls[0]).toHaveLength(2);
    
  383.     expect(mockError.mock.calls[0][0]).toBe('error');
    
  384.     expect(normalizeCodeLocInfo(mockError.mock.calls[0][1])).toBe(
    
  385.       '\n    in Child (at **)\n    in Intermediate (at **)\n    in Parent (at **)',
    
  386.     );
    
  387.   });
    
  388. 
    
  389.   // @reactVersion >=18.0
    
  390.   it('should append stacks after being uninstalled and reinstalled', () => {
    
  391.     global.__REACT_DEVTOOLS_APPEND_COMPONENT_STACK__ = false;
    
  392. 
    
  393.     const Child = ({children}) => {
    
  394.       fakeConsole.warn('warn');
    
  395.       fakeConsole.error('error');
    
  396.       return null;
    
  397.     };
    
  398. 
    
  399.     act(() => legacyRender(<Child />, document.createElement('div')));
    
  400. 
    
  401.     expect(mockWarn).toHaveBeenCalledTimes(1);
    
  402.     expect(mockWarn.mock.calls[0]).toHaveLength(1);
    
  403.     expect(mockWarn.mock.calls[0][0]).toBe('warn');
    
  404.     expect(mockError).toHaveBeenCalledTimes(1);
    
  405.     expect(mockError.mock.calls[0]).toHaveLength(1);
    
  406.     expect(mockError.mock.calls[0][0]).toBe('error');
    
  407. 
    
  408.     patchConsole({
    
  409.       appendComponentStack: true,
    
  410.       breakOnConsoleErrors: false,
    
  411.       showInlineWarningsAndErrors: false,
    
  412.     });
    
  413.     act(() => legacyRender(<Child />, document.createElement('div')));
    
  414. 
    
  415.     expect(mockWarn).toHaveBeenCalledTimes(2);
    
  416.     expect(mockWarn.mock.calls[1]).toHaveLength(2);
    
  417.     expect(mockWarn.mock.calls[1][0]).toBe('warn');
    
  418.     expect(normalizeCodeLocInfo(mockWarn.mock.calls[1][1])).toEqual(
    
  419.       '\n    in Child (at **)',
    
  420.     );
    
  421.     expect(mockError).toHaveBeenCalledTimes(2);
    
  422.     expect(mockError.mock.calls[1]).toHaveLength(2);
    
  423.     expect(mockError.mock.calls[1][0]).toBe('error');
    
  424.     expect(normalizeCodeLocInfo(mockError.mock.calls[1][1])).toBe(
    
  425.       '\n    in Child (at **)',
    
  426.     );
    
  427.   });
    
  428. 
    
  429.   // @reactVersion >=18.0
    
  430.   it('should be resilient to prepareStackTrace', () => {
    
  431.     global.__REACT_DEVTOOLS_APPEND_COMPONENT_STACK__ = true;
    
  432. 
    
  433.     Error.prepareStackTrace = function (error, callsites) {
    
  434.       const stack = ['An error occurred:', error.message];
    
  435.       for (let i = 0; i < callsites.length; i++) {
    
  436.         const callsite = callsites[i];
    
  437.         stack.push(
    
  438.           '\t' + callsite.getFunctionName(),
    
  439.           '\t\tat ' + callsite.getFileName(),
    
  440.           '\t\ton line ' + callsite.getLineNumber(),
    
  441.         );
    
  442.       }
    
  443. 
    
  444.       return stack.join('\n');
    
  445.     };
    
  446. 
    
  447.     const Intermediate = ({children}) => children;
    
  448.     const Parent = ({children}) => (
    
  449.       <Intermediate>
    
  450.         <Child />
    
  451.       </Intermediate>
    
  452.     );
    
  453.     const Child = ({children}) => {
    
  454.       fakeConsole.error('error');
    
  455.       fakeConsole.log('log');
    
  456.       fakeConsole.warn('warn');
    
  457.       return null;
    
  458.     };
    
  459. 
    
  460.     act(() => legacyRender(<Parent />, document.createElement('div')));
    
  461. 
    
  462.     expect(mockLog).toHaveBeenCalledTimes(1);
    
  463.     expect(mockLog.mock.calls[0]).toHaveLength(1);
    
  464.     expect(mockLog.mock.calls[0][0]).toBe('log');
    
  465.     expect(mockWarn).toHaveBeenCalledTimes(1);
    
  466.     expect(mockWarn.mock.calls[0]).toHaveLength(2);
    
  467.     expect(mockWarn.mock.calls[0][0]).toBe('warn');
    
  468.     expect(normalizeCodeLocInfo(mockWarn.mock.calls[0][1])).toEqual(
    
  469.       '\n    in Child (at **)\n    in Intermediate (at **)\n    in Parent (at **)',
    
  470.     );
    
  471.     expect(mockError).toHaveBeenCalledTimes(1);
    
  472.     expect(mockError.mock.calls[0]).toHaveLength(2);
    
  473.     expect(mockError.mock.calls[0][0]).toBe('error');
    
  474.     expect(normalizeCodeLocInfo(mockError.mock.calls[0][1])).toBe(
    
  475.       '\n    in Child (at **)\n    in Intermediate (at **)\n    in Parent (at **)',
    
  476.     );
    
  477.   });
    
  478. 
    
  479.   // @reactVersion >=18.0
    
  480.   it('should correctly log Symbols', () => {
    
  481.     const Component = ({children}) => {
    
  482.       fakeConsole.warn('Symbol:', Symbol(''));
    
  483.       return null;
    
  484.     };
    
  485. 
    
  486.     act(() => legacyRender(<Component />, document.createElement('div')));
    
  487. 
    
  488.     expect(mockWarn).toHaveBeenCalledTimes(1);
    
  489.     expect(mockWarn.mock.calls[0][0]).toBe('Symbol:');
    
  490.   });
    
  491. 
    
  492.   it('should double log if hideConsoleLogsInStrictMode is disabled in Strict mode', () => {
    
  493.     global.__REACT_DEVTOOLS_APPEND_COMPONENT_STACK__ = false;
    
  494.     global.__REACT_DEVTOOLS_HIDE_CONSOLE_LOGS_IN_STRICT_MODE__ = false;
    
  495. 
    
  496.     const container = document.createElement('div');
    
  497.     const root = ReactDOMClient.createRoot(container);
    
  498. 
    
  499.     function App() {
    
  500.       fakeConsole.log('log');
    
  501.       fakeConsole.warn('warn');
    
  502.       fakeConsole.error('error');
    
  503.       fakeConsole.info('info');
    
  504.       fakeConsole.group('group');
    
  505.       fakeConsole.groupCollapsed('groupCollapsed');
    
  506.       return <div />;
    
  507.     }
    
  508. 
    
  509.     act(() =>
    
  510.       root.render(
    
  511.         <React.StrictMode>
    
  512.           <App />
    
  513.         </React.StrictMode>,
    
  514.       ),
    
  515.     );
    
  516.     expect(mockLog.mock.calls[0]).toHaveLength(1);
    
  517.     expect(mockLog.mock.calls[0][0]).toBe('log');
    
  518.     expect(mockLog.mock.calls[1]).toEqual([
    
  519.       '%c%s',
    
  520.       `color: ${process.env.DARK_MODE_DIMMED_LOG_COLOR}`,
    
  521.       'log',
    
  522.     ]);
    
  523. 
    
  524.     expect(mockWarn).toHaveBeenCalledTimes(2);
    
  525.     expect(mockWarn.mock.calls[0]).toHaveLength(1);
    
  526.     expect(mockWarn.mock.calls[0][0]).toBe('warn');
    
  527.     expect(mockWarn.mock.calls[1]).toHaveLength(3);
    
  528.     expect(mockWarn.mock.calls[1]).toEqual([
    
  529.       '%c%s',
    
  530.       `color: ${process.env.DARK_MODE_DIMMED_WARNING_COLOR}`,
    
  531.       'warn',
    
  532.     ]);
    
  533. 
    
  534.     expect(mockError).toHaveBeenCalledTimes(2);
    
  535.     expect(mockError.mock.calls[0]).toHaveLength(1);
    
  536.     expect(mockError.mock.calls[0][0]).toBe('error');
    
  537.     expect(mockError.mock.calls[1]).toHaveLength(3);
    
  538.     expect(mockError.mock.calls[1]).toEqual([
    
  539.       '%c%s',
    
  540.       `color: ${process.env.DARK_MODE_DIMMED_ERROR_COLOR}`,
    
  541.       'error',
    
  542.     ]);
    
  543. 
    
  544.     expect(mockInfo).toHaveBeenCalledTimes(2);
    
  545.     expect(mockInfo.mock.calls[0]).toHaveLength(1);
    
  546.     expect(mockInfo.mock.calls[0][0]).toBe('info');
    
  547.     expect(mockInfo.mock.calls[1]).toHaveLength(3);
    
  548.     expect(mockInfo.mock.calls[1]).toEqual([
    
  549.       '%c%s',
    
  550.       `color: ${process.env.DARK_MODE_DIMMED_LOG_COLOR}`,
    
  551.       'info',
    
  552.     ]);
    
  553. 
    
  554.     expect(mockGroup).toHaveBeenCalledTimes(2);
    
  555.     expect(mockGroup.mock.calls[0]).toHaveLength(1);
    
  556.     expect(mockGroup.mock.calls[0][0]).toBe('group');
    
  557.     expect(mockGroup.mock.calls[1]).toHaveLength(3);
    
  558.     expect(mockGroup.mock.calls[1]).toEqual([
    
  559.       '%c%s',
    
  560.       `color: ${process.env.DARK_MODE_DIMMED_LOG_COLOR}`,
    
  561.       'group',
    
  562.     ]);
    
  563. 
    
  564.     expect(mockGroupCollapsed).toHaveBeenCalledTimes(2);
    
  565.     expect(mockGroupCollapsed.mock.calls[0]).toHaveLength(1);
    
  566.     expect(mockGroupCollapsed.mock.calls[0][0]).toBe('groupCollapsed');
    
  567.     expect(mockGroupCollapsed.mock.calls[1]).toHaveLength(3);
    
  568.     expect(mockGroupCollapsed.mock.calls[1]).toEqual([
    
  569.       '%c%s',
    
  570.       `color: ${process.env.DARK_MODE_DIMMED_LOG_COLOR}`,
    
  571.       'groupCollapsed',
    
  572.     ]);
    
  573.   });
    
  574. 
    
  575.   it('should not double log if hideConsoleLogsInStrictMode is enabled in Strict mode', () => {
    
  576.     global.__REACT_DEVTOOLS_APPEND_COMPONENT_STACK__ = false;
    
  577.     global.__REACT_DEVTOOLS_HIDE_CONSOLE_LOGS_IN_STRICT_MODE__ = true;
    
  578. 
    
  579.     const container = document.createElement('div');
    
  580.     const root = ReactDOMClient.createRoot(container);
    
  581. 
    
  582.     function App() {
    
  583.       console.log(
    
  584.         'CALL',
    
  585.         global.__REACT_DEVTOOLS_HIDE_CONSOLE_LOGS_IN_STRICT_MODE__,
    
  586.       );
    
  587.       fakeConsole.log('log');
    
  588.       fakeConsole.warn('warn');
    
  589.       fakeConsole.error('error');
    
  590.       fakeConsole.info('info');
    
  591.       fakeConsole.group('group');
    
  592.       fakeConsole.groupCollapsed('groupCollapsed');
    
  593.       return <div />;
    
  594.     }
    
  595. 
    
  596.     act(() =>
    
  597.       root.render(
    
  598.         <React.StrictMode>
    
  599.           <App />
    
  600.         </React.StrictMode>,
    
  601.       ),
    
  602.     );
    
  603. 
    
  604.     expect(mockLog).toHaveBeenCalledTimes(1);
    
  605.     expect(mockLog.mock.calls[0]).toHaveLength(1);
    
  606.     expect(mockLog.mock.calls[0][0]).toBe('log');
    
  607. 
    
  608.     expect(mockWarn).toHaveBeenCalledTimes(1);
    
  609.     expect(mockWarn.mock.calls[0]).toHaveLength(1);
    
  610.     expect(mockWarn.mock.calls[0][0]).toBe('warn');
    
  611. 
    
  612.     expect(mockError).toHaveBeenCalledTimes(1);
    
  613.     expect(mockError.mock.calls[0]).toHaveLength(1);
    
  614.     expect(mockError.mock.calls[0][0]).toBe('error');
    
  615. 
    
  616.     expect(mockInfo).toHaveBeenCalledTimes(1);
    
  617.     expect(mockInfo.mock.calls[0]).toHaveLength(1);
    
  618.     expect(mockInfo.mock.calls[0][0]).toBe('info');
    
  619. 
    
  620.     expect(mockGroup).toHaveBeenCalledTimes(1);
    
  621.     expect(mockGroup.mock.calls[0]).toHaveLength(1);
    
  622.     expect(mockGroup.mock.calls[0][0]).toBe('group');
    
  623. 
    
  624.     expect(mockGroupCollapsed).toHaveBeenCalledTimes(1);
    
  625.     expect(mockGroupCollapsed.mock.calls[0]).toHaveLength(1);
    
  626.     expect(mockGroupCollapsed.mock.calls[0][0]).toBe('groupCollapsed');
    
  627.   });
    
  628. 
    
  629.   it('should double log in Strict mode initial render for extension', () => {
    
  630.     global.__REACT_DEVTOOLS_APPEND_COMPONENT_STACK__ = false;
    
  631.     global.__REACT_DEVTOOLS_HIDE_CONSOLE_LOGS_IN_STRICT_MODE__ = false;
    
  632. 
    
  633.     // This simulates a render that happens before React DevTools have finished
    
  634.     // their handshake to attach the React DOM renderer functions to DevTools
    
  635.     // In this case, we should still be able to mock the console in Strict mode
    
  636.     global.__REACT_DEVTOOLS_GLOBAL_HOOK__.rendererInterfaces.set(
    
  637.       rendererID,
    
  638.       null,
    
  639.     );
    
  640.     const container = document.createElement('div');
    
  641.     const root = ReactDOMClient.createRoot(container);
    
  642. 
    
  643.     function App() {
    
  644.       fakeConsole.log('log');
    
  645.       fakeConsole.warn('warn');
    
  646.       fakeConsole.error('error');
    
  647.       return <div />;
    
  648.     }
    
  649. 
    
  650.     act(() =>
    
  651.       root.render(
    
  652.         <React.StrictMode>
    
  653.           <App />
    
  654.         </React.StrictMode>,
    
  655.       ),
    
  656.     );
    
  657. 
    
  658.     expect(mockLog).toHaveBeenCalledTimes(2);
    
  659.     expect(mockLog.mock.calls[0]).toHaveLength(1);
    
  660.     expect(mockLog.mock.calls[0][0]).toBe('log');
    
  661.     expect(mockLog.mock.calls[1]).toHaveLength(3);
    
  662.     expect(mockLog.mock.calls[1]).toEqual([
    
  663.       '%c%s',
    
  664.       `color: ${process.env.DARK_MODE_DIMMED_LOG_COLOR}`,
    
  665.       'log',
    
  666.     ]);
    
  667. 
    
  668.     expect(mockWarn).toHaveBeenCalledTimes(2);
    
  669.     expect(mockWarn.mock.calls[0]).toHaveLength(1);
    
  670.     expect(mockWarn.mock.calls[0][0]).toBe('warn');
    
  671.     expect(mockWarn.mock.calls[1]).toHaveLength(3);
    
  672.     expect(mockWarn.mock.calls[1]).toEqual([
    
  673.       '%c%s',
    
  674.       `color: ${process.env.DARK_MODE_DIMMED_WARNING_COLOR}`,
    
  675.       'warn',
    
  676.     ]);
    
  677. 
    
  678.     expect(mockError).toHaveBeenCalledTimes(2);
    
  679.     expect(mockError.mock.calls[0]).toHaveLength(1);
    
  680.     expect(mockError.mock.calls[0][0]).toBe('error');
    
  681.     expect(mockError.mock.calls[1]).toHaveLength(3);
    
  682.     expect(mockError.mock.calls[1]).toEqual([
    
  683.       '%c%s',
    
  684.       `color: ${process.env.DARK_MODE_DIMMED_ERROR_COLOR}`,
    
  685.       'error',
    
  686.     ]);
    
  687.   });
    
  688. 
    
  689.   it('should not double log in Strict mode initial render for extension', () => {
    
  690.     global.__REACT_DEVTOOLS_APPEND_COMPONENT_STACK__ = false;
    
  691.     global.__REACT_DEVTOOLS_HIDE_CONSOLE_LOGS_IN_STRICT_MODE__ = true;
    
  692. 
    
  693.     // This simulates a render that happens before React DevTools have finished
    
  694.     // their handshake to attach the React DOM renderer functions to DevTools
    
  695.     // In this case, we should still be able to mock the console in Strict mode
    
  696.     global.__REACT_DEVTOOLS_GLOBAL_HOOK__.rendererInterfaces.set(
    
  697.       rendererID,
    
  698.       null,
    
  699.     );
    
  700.     const container = document.createElement('div');
    
  701.     const root = ReactDOMClient.createRoot(container);
    
  702. 
    
  703.     function App() {
    
  704.       fakeConsole.log('log');
    
  705.       fakeConsole.warn('warn');
    
  706.       fakeConsole.error('error');
    
  707.       return <div />;
    
  708.     }
    
  709. 
    
  710.     act(() =>
    
  711.       root.render(
    
  712.         <React.StrictMode>
    
  713.           <App />
    
  714.         </React.StrictMode>,
    
  715.       ),
    
  716.     );
    
  717.     expect(mockLog).toHaveBeenCalledTimes(1);
    
  718.     expect(mockLog.mock.calls[0]).toHaveLength(1);
    
  719.     expect(mockLog.mock.calls[0][0]).toBe('log');
    
  720. 
    
  721.     expect(mockWarn).toHaveBeenCalledTimes(1);
    
  722.     expect(mockWarn.mock.calls[0]).toHaveLength(1);
    
  723.     expect(mockWarn.mock.calls[0][0]).toBe('warn');
    
  724. 
    
  725.     expect(mockError).toHaveBeenCalledTimes(1);
    
  726.     expect(mockError.mock.calls[0]).toHaveLength(1);
    
  727.     expect(mockError.mock.calls[0][0]).toBe('error');
    
  728.   });
    
  729. 
    
  730.   it('should properly dim component stacks during strict mode double log', () => {
    
  731.     global.__REACT_DEVTOOLS_APPEND_COMPONENT_STACK__ = true;
    
  732.     global.__REACT_DEVTOOLS_HIDE_CONSOLE_LOGS_IN_STRICT_MODE__ = false;
    
  733. 
    
  734.     const container = document.createElement('div');
    
  735.     const root = ReactDOMClient.createRoot(container);
    
  736. 
    
  737.     const Intermediate = ({children}) => children;
    
  738.     const Parent = ({children}) => (
    
  739.       <Intermediate>
    
  740.         <Child />
    
  741.       </Intermediate>
    
  742.     );
    
  743.     const Child = ({children}) => {
    
  744.       fakeConsole.error('error');
    
  745.       fakeConsole.warn('warn');
    
  746.       return null;
    
  747.     };
    
  748. 
    
  749.     act(() =>
    
  750.       root.render(
    
  751.         <React.StrictMode>
    
  752.           <Parent />
    
  753.         </React.StrictMode>,
    
  754.       ),
    
  755.     );
    
  756. 
    
  757.     expect(mockWarn).toHaveBeenCalledTimes(2);
    
  758.     expect(mockWarn.mock.calls[0]).toHaveLength(2);
    
  759.     expect(normalizeCodeLocInfo(mockWarn.mock.calls[0][1])).toEqual(
    
  760.       '\n    in Child (at **)\n    in Intermediate (at **)\n    in Parent (at **)',
    
  761.     );
    
  762.     expect(mockWarn.mock.calls[1]).toHaveLength(4);
    
  763.     expect(mockWarn.mock.calls[1][0]).toEqual('%c%s %s');
    
  764.     expect(mockWarn.mock.calls[1][1]).toMatch('color: rgba(');
    
  765.     expect(mockWarn.mock.calls[1][2]).toEqual('warn');
    
  766.     expect(normalizeCodeLocInfo(mockWarn.mock.calls[1][3]).trim()).toEqual(
    
  767.       'in Child (at **)\n    in Intermediate (at **)\n    in Parent (at **)',
    
  768.     );
    
  769. 
    
  770.     expect(mockError).toHaveBeenCalledTimes(2);
    
  771.     expect(mockError.mock.calls[0]).toHaveLength(2);
    
  772.     expect(normalizeCodeLocInfo(mockError.mock.calls[0][1])).toEqual(
    
  773.       '\n    in Child (at **)\n    in Intermediate (at **)\n    in Parent (at **)',
    
  774.     );
    
  775.     expect(mockError.mock.calls[1]).toHaveLength(4);
    
  776.     expect(mockError.mock.calls[1][0]).toEqual('%c%s %s');
    
  777.     expect(mockError.mock.calls[1][1]).toMatch('color: rgba(');
    
  778.     expect(mockError.mock.calls[1][2]).toEqual('error');
    
  779.     expect(normalizeCodeLocInfo(mockError.mock.calls[1][3]).trim()).toEqual(
    
  780.       'in Child (at **)\n    in Intermediate (at **)\n    in Parent (at **)',
    
  781.     );
    
  782.   });
    
  783. });
    
  784. 
    
  785. describe('console error', () => {
    
  786.   beforeEach(() => {
    
  787.     jest.resetModules();
    
  788. 
    
  789.     const Console = require('react-devtools-shared/src/backend/console');
    
  790.     patchConsole = Console.patch;
    
  791.     unpatchConsole = Console.unpatch;
    
  792. 
    
  793.     // Patch a fake console so we can verify with tests below.
    
  794.     // Patching the real console is too complicated,
    
  795.     // because Jest itself has hooks into it as does our test env setup.
    
  796.     mockError = jest.fn();
    
  797.     mockInfo = jest.fn();
    
  798.     mockGroup = jest.fn();
    
  799.     mockGroupCollapsed = jest.fn();
    
  800.     mockLog = jest.fn();
    
  801.     mockWarn = jest.fn();
    
  802.     fakeConsole = {
    
  803.       error: mockError,
    
  804.       info: mockInfo,
    
  805.       log: mockLog,
    
  806.       warn: mockWarn,
    
  807.       group: mockGroup,
    
  808.       groupCollapsed: mockGroupCollapsed,
    
  809.     };
    
  810. 
    
  811.     Console.dangerous_setTargetConsoleForTesting(fakeConsole);
    
  812. 
    
  813.     const inject = global.__REACT_DEVTOOLS_GLOBAL_HOOK__.inject;
    
  814.     global.__REACT_DEVTOOLS_GLOBAL_HOOK__.inject = internals => {
    
  815.       inject(internals);
    
  816. 
    
  817.       Console.registerRenderer(internals, () => {
    
  818.         throw Error('foo');
    
  819.       });
    
  820.     };
    
  821. 
    
  822.     React = require('react');
    
  823.     ReactDOMClient = require('react-dom/client');
    
  824. 
    
  825.     const utils = require('./utils');
    
  826.     act = utils.act;
    
  827.     legacyRender = utils.legacyRender;
    
  828.   });
    
  829. 
    
  830.   // @reactVersion >=18.0
    
  831.   it('error in console log throws without interfering with logging', () => {
    
  832.     const container = document.createElement('div');
    
  833.     const root = ReactDOMClient.createRoot(container);
    
  834. 
    
  835.     function App() {
    
  836.       fakeConsole.log('log');
    
  837.       fakeConsole.warn('warn');
    
  838.       fakeConsole.error('error');
    
  839.       return <div />;
    
  840.     }
    
  841. 
    
  842.     patchConsole({
    
  843.       appendComponentStack: true,
    
  844.       breakOnConsoleErrors: false,
    
  845.       showInlineWarningsAndErrors: true,
    
  846.       hideConsoleLogsInStrictMode: false,
    
  847.     });
    
  848. 
    
  849.     expect(() => {
    
  850.       act(() => {
    
  851.         root.render(<App />);
    
  852.       });
    
  853.     }).toThrowError('foo');
    
  854. 
    
  855.     expect(mockLog).toHaveBeenCalledTimes(1);
    
  856.     expect(mockLog.mock.calls[0]).toHaveLength(1);
    
  857.     expect(mockLog.mock.calls[0][0]).toBe('log');
    
  858. 
    
  859.     expect(mockWarn).toHaveBeenCalledTimes(1);
    
  860.     expect(mockWarn.mock.calls[0]).toHaveLength(1);
    
  861.     expect(mockWarn.mock.calls[0][0]).toBe('warn');
    
  862. 
    
  863.     expect(mockError).toHaveBeenCalledTimes(1);
    
  864.     expect(mockError.mock.calls[0]).toHaveLength(1);
    
  865.     expect(mockError.mock.calls[0][0]).toBe('error');
    
  866.   });
    
  867. });