Console Test

You might also like

Download as txt, pdf, or txt
Download as txt, pdf, or txt
You are on page 1of 15

/**

* Copyright (c) Meta Platforms, Inc. and affiliates.


*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/

import {normalizeCodeLocInfo} from './utils';

let React;
let ReactDOMClient;
let act;
let fakeConsole;
let legacyRender;
let mockError;
let mockInfo;
let mockGroup;
let mockGroupCollapsed;
let mockLog;
let mockWarn;
let patchConsole;
let unpatchConsole;
let rendererID;

describe('console', () => {
beforeEach(() => {
const Console = require('react-devtools-shared/src/backend/console');

patchConsole = Console.patch;
unpatchConsole = Console.unpatch;

// Patch a fake console so we can verify with tests below.


// Patching the real console is too complicated,
// because Jest itself has hooks into it as does our test env setup.
mockError = jest.fn();
mockInfo = jest.fn();
mockGroup = jest.fn();
mockGroupCollapsed = jest.fn();
mockLog = jest.fn();
mockWarn = jest.fn();
fakeConsole = {
error: mockError,
info: mockInfo,
log: mockLog,
warn: mockWarn,
group: mockGroup,
groupCollapsed: mockGroupCollapsed,
};

Console.dangerous_setTargetConsoleForTesting(fakeConsole);
global.__REACT_DEVTOOLS_GLOBAL_HOOK__.dangerous_setTargetConsoleForTesting(
fakeConsole,
);

const inject = global.__REACT_DEVTOOLS_GLOBAL_HOOK__.inject;


global.__REACT_DEVTOOLS_GLOBAL_HOOK__.inject = internals => {
rendererID = inject(internals);
Console.registerRenderer(internals);
return rendererID;
};

React = require('react');
ReactDOMClient = require('react-dom/client');

const utils = require('./utils');


act = utils.act;
legacyRender = utils.legacyRender;
});

// @reactVersion >=18.0
it('should not patch console methods that are not explicitly overridden', () => {
expect(fakeConsole.error).not.toBe(mockError);
expect(fakeConsole.info).toBe(mockInfo);
expect(fakeConsole.log).toBe(mockLog);
expect(fakeConsole.warn).not.toBe(mockWarn);
expect(fakeConsole.group).toBe(mockGroup);
expect(fakeConsole.groupCollapsed).toBe(mockGroupCollapsed);
});

// @reactVersion >=18.0
it('should patch the console when appendComponentStack is enabled', () => {
unpatchConsole();

expect(fakeConsole.error).toBe(mockError);
expect(fakeConsole.warn).toBe(mockWarn);

patchConsole({
appendComponentStack: true,
breakOnConsoleErrors: false,
showInlineWarningsAndErrors: false,
});

expect(fakeConsole.error).not.toBe(mockError);
expect(fakeConsole.warn).not.toBe(mockWarn);
});

// @reactVersion >=18.0
it('should patch the console when breakOnConsoleErrors is enabled', () => {
unpatchConsole();

expect(fakeConsole.error).toBe(mockError);
expect(fakeConsole.warn).toBe(mockWarn);

patchConsole({
appendComponentStack: false,
breakOnConsoleErrors: true,
showInlineWarningsAndErrors: false,
});

expect(fakeConsole.error).not.toBe(mockError);
expect(fakeConsole.warn).not.toBe(mockWarn);
});

// @reactVersion >=18.0
it('should patch the console when showInlineWarningsAndErrors is enabled', () =>
{
unpatchConsole();

expect(fakeConsole.error).toBe(mockError);
expect(fakeConsole.warn).toBe(mockWarn);

patchConsole({
appendComponentStack: false,
breakOnConsoleErrors: false,
showInlineWarningsAndErrors: true,
});

expect(fakeConsole.error).not.toBe(mockError);
expect(fakeConsole.warn).not.toBe(mockWarn);
});

// @reactVersion >=18.0
it('should only patch the console once', () => {
const {error, warn} = fakeConsole;

patchConsole({
appendComponentStack: true,
breakOnConsoleErrors: false,
showInlineWarningsAndErrors: false,
});

expect(fakeConsole.error).toBe(error);
expect(fakeConsole.warn).toBe(warn);
});

// @reactVersion >=18.0
it('should un-patch when requested', () => {
expect(fakeConsole.error).not.toBe(mockError);
expect(fakeConsole.warn).not.toBe(mockWarn);

unpatchConsole();

expect(fakeConsole.error).toBe(mockError);
expect(fakeConsole.warn).toBe(mockWarn);
});

// @reactVersion >=18.0
it('should pass through logs when there is no current fiber', () => {
expect(mockLog).toHaveBeenCalledTimes(0);
expect(mockWarn).toHaveBeenCalledTimes(0);
expect(mockError).toHaveBeenCalledTimes(0);
fakeConsole.log('log');
fakeConsole.warn('warn');
fakeConsole.error('error');
expect(mockLog).toHaveBeenCalledTimes(1);
expect(mockLog.mock.calls[0]).toHaveLength(1);
expect(mockLog.mock.calls[0][0]).toBe('log');
expect(mockWarn).toHaveBeenCalledTimes(1);
expect(mockWarn.mock.calls[0]).toHaveLength(1);
expect(mockWarn.mock.calls[0][0]).toBe('warn');
expect(mockError).toHaveBeenCalledTimes(1);
expect(mockError.mock.calls[0]).toHaveLength(1);
expect(mockError.mock.calls[0][0]).toBe('error');
});
// @reactVersion >=18.0
it('should not append multiple stacks', () => {
global.__REACT_DEVTOOLS_APPEND_COMPONENT_STACK__ = true;

const Child = ({children}) => {


fakeConsole.warn('warn\n in Child (at fake.js:123)');
fakeConsole.error('error', '\n in Child (at fake.js:123)');
return null;
};

act(() => legacyRender(<Child />, document.createElement('div')));

expect(mockWarn).toHaveBeenCalledTimes(1);
expect(mockWarn.mock.calls[0]).toHaveLength(1);
expect(mockWarn.mock.calls[0][0]).toBe(
'warn\n in Child (at fake.js:123)',
);
expect(mockError).toHaveBeenCalledTimes(1);
expect(mockError.mock.calls[0]).toHaveLength(2);
expect(mockError.mock.calls[0][0]).toBe('error');
expect(mockError.mock.calls[0][1]).toBe('\n in Child (at fake.js:123)');
});

// @reactVersion >=18.0
it('should append component stacks to errors and warnings logged during render',
() => {
global.__REACT_DEVTOOLS_APPEND_COMPONENT_STACK__ = true;

const Intermediate = ({children}) => children;


const Parent = ({children}) => (
<Intermediate>
<Child />
</Intermediate>
);
const Child = ({children}) => {
fakeConsole.error('error');
fakeConsole.log('log');
fakeConsole.warn('warn');
return null;
};

act(() => legacyRender(<Parent />, document.createElement('div')));

expect(mockLog).toHaveBeenCalledTimes(1);
expect(mockLog.mock.calls[0]).toHaveLength(1);
expect(mockLog.mock.calls[0][0]).toBe('log');
expect(mockWarn).toHaveBeenCalledTimes(1);
expect(mockWarn.mock.calls[0]).toHaveLength(2);
expect(mockWarn.mock.calls[0][0]).toBe('warn');
expect(normalizeCodeLocInfo(mockWarn.mock.calls[0][1])).toEqual(
'\n in Child (at **)\n in Intermediate (at **)\n in Parent (at **)',
);
expect(mockError).toHaveBeenCalledTimes(1);
expect(mockError.mock.calls[0]).toHaveLength(2);
expect(mockError.mock.calls[0][0]).toBe('error');
expect(normalizeCodeLocInfo(mockError.mock.calls[0][1])).toBe(
'\n in Child (at **)\n in Intermediate (at **)\n in Parent (at **)',
);
});

// @reactVersion >=18.0
it('should append component stacks to errors and warnings logged from effects',
() => {
const Intermediate = ({children}) => children;
const Parent = ({children}) => (
<Intermediate>
<Child />
</Intermediate>
);
const Child = ({children}) => {
React.useLayoutEffect(() => {
fakeConsole.error('active error');
fakeConsole.log('active log');
fakeConsole.warn('active warn');
});
React.useEffect(() => {
fakeConsole.error('passive error');
fakeConsole.log('passive log');
fakeConsole.warn('passive warn');
});
return null;
};

act(() => legacyRender(<Parent />, document.createElement('div')));

expect(mockLog).toHaveBeenCalledTimes(2);
expect(mockLog.mock.calls[0]).toHaveLength(1);
expect(mockLog.mock.calls[0][0]).toBe('active log');
expect(mockLog.mock.calls[1]).toHaveLength(1);
expect(mockLog.mock.calls[1][0]).toBe('passive log');
expect(mockWarn).toHaveBeenCalledTimes(2);
expect(mockWarn.mock.calls[0]).toHaveLength(2);
expect(mockWarn.mock.calls[0][0]).toBe('active warn');
expect(normalizeCodeLocInfo(mockWarn.mock.calls[0][1])).toEqual(
'\n in Child (at **)\n in Intermediate (at **)\n in Parent (at **)',
);
expect(mockWarn.mock.calls[1]).toHaveLength(2);
expect(mockWarn.mock.calls[1][0]).toBe('passive warn');
expect(normalizeCodeLocInfo(mockWarn.mock.calls[1][1])).toEqual(
'\n in Child (at **)\n in Intermediate (at **)\n in Parent (at **)',
);
expect(mockError).toHaveBeenCalledTimes(2);
expect(mockError.mock.calls[0]).toHaveLength(2);
expect(mockError.mock.calls[0][0]).toBe('active error');
expect(normalizeCodeLocInfo(mockError.mock.calls[0][1])).toBe(
'\n in Child (at **)\n in Intermediate (at **)\n in Parent (at **)',
);
expect(mockError.mock.calls[1]).toHaveLength(2);
expect(mockError.mock.calls[1][0]).toBe('passive error');
expect(normalizeCodeLocInfo(mockError.mock.calls[1][1])).toBe(
'\n in Child (at **)\n in Intermediate (at **)\n in Parent (at **)',
);
});

// @reactVersion >=18.0
it('should append component stacks to errors and warnings logged from commit
hooks', () => {
global.__REACT_DEVTOOLS_APPEND_COMPONENT_STACK__ = true;

const Intermediate = ({children}) => children;


const Parent = ({children}) => (
<Intermediate>
<Child />
</Intermediate>
);
class Child extends React.Component<any> {
componentDidMount() {
fakeConsole.error('didMount error');
fakeConsole.log('didMount log');
fakeConsole.warn('didMount warn');
}
componentDidUpdate() {
fakeConsole.error('didUpdate error');
fakeConsole.log('didUpdate log');
fakeConsole.warn('didUpdate warn');
}
render() {
return null;
}
}

const container = document.createElement('div');


act(() => legacyRender(<Parent />, container));
act(() => legacyRender(<Parent />, container));

expect(mockLog).toHaveBeenCalledTimes(2);
expect(mockLog.mock.calls[0]).toHaveLength(1);
expect(mockLog.mock.calls[0][0]).toBe('didMount log');
expect(mockLog.mock.calls[1]).toHaveLength(1);
expect(mockLog.mock.calls[1][0]).toBe('didUpdate log');
expect(mockWarn).toHaveBeenCalledTimes(2);
expect(mockWarn.mock.calls[0]).toHaveLength(2);
expect(mockWarn.mock.calls[0][0]).toBe('didMount warn');
expect(normalizeCodeLocInfo(mockWarn.mock.calls[0][1])).toEqual(
'\n in Child (at **)\n in Intermediate (at **)\n in Parent (at **)',
);
expect(mockWarn.mock.calls[1]).toHaveLength(2);
expect(mockWarn.mock.calls[1][0]).toBe('didUpdate warn');
expect(normalizeCodeLocInfo(mockWarn.mock.calls[1][1])).toEqual(
'\n in Child (at **)\n in Intermediate (at **)\n in Parent (at **)',
);
expect(mockError).toHaveBeenCalledTimes(2);
expect(mockError.mock.calls[0]).toHaveLength(2);
expect(mockError.mock.calls[0][0]).toBe('didMount error');
expect(normalizeCodeLocInfo(mockError.mock.calls[0][1])).toBe(
'\n in Child (at **)\n in Intermediate (at **)\n in Parent (at **)',
);
expect(mockError.mock.calls[1]).toHaveLength(2);
expect(mockError.mock.calls[1][0]).toBe('didUpdate error');
expect(normalizeCodeLocInfo(mockError.mock.calls[1][1])).toBe(
'\n in Child (at **)\n in Intermediate (at **)\n in Parent (at **)',
);
});

// @reactVersion >=18.0
it('should append component stacks to errors and warnings logged from gDSFP', ()
=> {
const Intermediate = ({children}) => children;
const Parent = ({children}) => (
<Intermediate>
<Child />
</Intermediate>
);
class Child extends React.Component<any, any> {
state = {};
static getDerivedStateFromProps() {
fakeConsole.error('error');
fakeConsole.log('log');
fakeConsole.warn('warn');
return null;
}
render() {
return null;
}
}

act(() => legacyRender(<Parent />, document.createElement('div')));

expect(mockLog).toHaveBeenCalledTimes(1);
expect(mockLog.mock.calls[0]).toHaveLength(1);
expect(mockLog.mock.calls[0][0]).toBe('log');
expect(mockWarn).toHaveBeenCalledTimes(1);
expect(mockWarn.mock.calls[0]).toHaveLength(2);
expect(mockWarn.mock.calls[0][0]).toBe('warn');
expect(normalizeCodeLocInfo(mockWarn.mock.calls[0][1])).toEqual(
'\n in Child (at **)\n in Intermediate (at **)\n in Parent (at **)',
);
expect(mockError).toHaveBeenCalledTimes(1);
expect(mockError.mock.calls[0]).toHaveLength(2);
expect(mockError.mock.calls[0][0]).toBe('error');
expect(normalizeCodeLocInfo(mockError.mock.calls[0][1])).toBe(
'\n in Child (at **)\n in Intermediate (at **)\n in Parent (at **)',
);
});

// @reactVersion >=18.0
it('should append stacks after being uninstalled and reinstalled', () => {
global.__REACT_DEVTOOLS_APPEND_COMPONENT_STACK__ = false;

const Child = ({children}) => {


fakeConsole.warn('warn');
fakeConsole.error('error');
return null;
};

act(() => legacyRender(<Child />, document.createElement('div')));

expect(mockWarn).toHaveBeenCalledTimes(1);
expect(mockWarn.mock.calls[0]).toHaveLength(1);
expect(mockWarn.mock.calls[0][0]).toBe('warn');
expect(mockError).toHaveBeenCalledTimes(1);
expect(mockError.mock.calls[0]).toHaveLength(1);
expect(mockError.mock.calls[0][0]).toBe('error');

patchConsole({
appendComponentStack: true,
breakOnConsoleErrors: false,
showInlineWarningsAndErrors: false,
});
act(() => legacyRender(<Child />, document.createElement('div')));

expect(mockWarn).toHaveBeenCalledTimes(2);
expect(mockWarn.mock.calls[1]).toHaveLength(2);
expect(mockWarn.mock.calls[1][0]).toBe('warn');
expect(normalizeCodeLocInfo(mockWarn.mock.calls[1][1])).toEqual(
'\n in Child (at **)',
);
expect(mockError).toHaveBeenCalledTimes(2);
expect(mockError.mock.calls[1]).toHaveLength(2);
expect(mockError.mock.calls[1][0]).toBe('error');
expect(normalizeCodeLocInfo(mockError.mock.calls[1][1])).toBe(
'\n in Child (at **)',
);
});

// @reactVersion >=18.0
it('should be resilient to prepareStackTrace', () => {
global.__REACT_DEVTOOLS_APPEND_COMPONENT_STACK__ = true;

Error.prepareStackTrace = function (error, callsites) {


const stack = ['An error occurred:', error.message];
for (let i = 0; i < callsites.length; i++) {
const callsite = callsites[i];
stack.push(
'\t' + callsite.getFunctionName(),
'\t\tat ' + callsite.getFileName(),
'\t\ton line ' + callsite.getLineNumber(),
);
}

return stack.join('\n');
};

const Intermediate = ({children}) => children;


const Parent = ({children}) => (
<Intermediate>
<Child />
</Intermediate>
);
const Child = ({children}) => {
fakeConsole.error('error');
fakeConsole.log('log');
fakeConsole.warn('warn');
return null;
};

act(() => legacyRender(<Parent />, document.createElement('div')));

expect(mockLog).toHaveBeenCalledTimes(1);
expect(mockLog.mock.calls[0]).toHaveLength(1);
expect(mockLog.mock.calls[0][0]).toBe('log');
expect(mockWarn).toHaveBeenCalledTimes(1);
expect(mockWarn.mock.calls[0]).toHaveLength(2);
expect(mockWarn.mock.calls[0][0]).toBe('warn');
expect(normalizeCodeLocInfo(mockWarn.mock.calls[0][1])).toEqual(
'\n in Child (at **)\n in Intermediate (at **)\n in Parent (at **)',
);
expect(mockError).toHaveBeenCalledTimes(1);
expect(mockError.mock.calls[0]).toHaveLength(2);
expect(mockError.mock.calls[0][0]).toBe('error');
expect(normalizeCodeLocInfo(mockError.mock.calls[0][1])).toBe(
'\n in Child (at **)\n in Intermediate (at **)\n in Parent (at **)',
);
});

// @reactVersion >=18.0
it('should correctly log Symbols', () => {
const Component = ({children}) => {
fakeConsole.warn('Symbol:', Symbol(''));
return null;
};

act(() => legacyRender(<Component />, document.createElement('div')));

expect(mockWarn).toHaveBeenCalledTimes(1);
expect(mockWarn.mock.calls[0][0]).toBe('Symbol:');
});

it('should double log if hideConsoleLogsInStrictMode is disabled in Strict mode',


() => {
global.__REACT_DEVTOOLS_APPEND_COMPONENT_STACK__ = false;
global.__REACT_DEVTOOLS_HIDE_CONSOLE_LOGS_IN_STRICT_MODE__ = false;

const container = document.createElement('div');


const root = ReactDOMClient.createRoot(container);

function App() {
fakeConsole.log('log');
fakeConsole.warn('warn');
fakeConsole.error('error');
fakeConsole.info('info');
fakeConsole.group('group');
fakeConsole.groupCollapsed('groupCollapsed');
return <div />;
}

act(() =>
root.render(
<React.StrictMode>
<App />
</React.StrictMode>,
),
);
expect(mockLog.mock.calls[0]).toHaveLength(1);
expect(mockLog.mock.calls[0][0]).toBe('log');
expect(mockLog.mock.calls[1]).toEqual([
'%c%s',
`color: ${process.env.DARK_MODE_DIMMED_LOG_COLOR}`,
'log',
]);

expect(mockWarn).toHaveBeenCalledTimes(2);
expect(mockWarn.mock.calls[0]).toHaveLength(1);
expect(mockWarn.mock.calls[0][0]).toBe('warn');
expect(mockWarn.mock.calls[1]).toHaveLength(3);
expect(mockWarn.mock.calls[1]).toEqual([
'%c%s',
`color: ${process.env.DARK_MODE_DIMMED_WARNING_COLOR}`,
'warn',
]);

expect(mockError).toHaveBeenCalledTimes(2);
expect(mockError.mock.calls[0]).toHaveLength(1);
expect(mockError.mock.calls[0][0]).toBe('error');
expect(mockError.mock.calls[1]).toHaveLength(3);
expect(mockError.mock.calls[1]).toEqual([
'%c%s',
`color: ${process.env.DARK_MODE_DIMMED_ERROR_COLOR}`,
'error',
]);

expect(mockInfo).toHaveBeenCalledTimes(2);
expect(mockInfo.mock.calls[0]).toHaveLength(1);
expect(mockInfo.mock.calls[0][0]).toBe('info');
expect(mockInfo.mock.calls[1]).toHaveLength(3);
expect(mockInfo.mock.calls[1]).toEqual([
'%c%s',
`color: ${process.env.DARK_MODE_DIMMED_LOG_COLOR}`,
'info',
]);

expect(mockGroup).toHaveBeenCalledTimes(2);
expect(mockGroup.mock.calls[0]).toHaveLength(1);
expect(mockGroup.mock.calls[0][0]).toBe('group');
expect(mockGroup.mock.calls[1]).toHaveLength(3);
expect(mockGroup.mock.calls[1]).toEqual([
'%c%s',
`color: ${process.env.DARK_MODE_DIMMED_LOG_COLOR}`,
'group',
]);

expect(mockGroupCollapsed).toHaveBeenCalledTimes(2);
expect(mockGroupCollapsed.mock.calls[0]).toHaveLength(1);
expect(mockGroupCollapsed.mock.calls[0][0]).toBe('groupCollapsed');
expect(mockGroupCollapsed.mock.calls[1]).toHaveLength(3);
expect(mockGroupCollapsed.mock.calls[1]).toEqual([
'%c%s',
`color: ${process.env.DARK_MODE_DIMMED_LOG_COLOR}`,
'groupCollapsed',
]);
});

it('should not double log if hideConsoleLogsInStrictMode is enabled in Strict


mode', () => {
global.__REACT_DEVTOOLS_APPEND_COMPONENT_STACK__ = false;
global.__REACT_DEVTOOLS_HIDE_CONSOLE_LOGS_IN_STRICT_MODE__ = true;

const container = document.createElement('div');


const root = ReactDOMClient.createRoot(container);

function App() {
console.log(
'CALL',
global.__REACT_DEVTOOLS_HIDE_CONSOLE_LOGS_IN_STRICT_MODE__,
);
fakeConsole.log('log');
fakeConsole.warn('warn');
fakeConsole.error('error');
fakeConsole.info('info');
fakeConsole.group('group');
fakeConsole.groupCollapsed('groupCollapsed');
return <div />;
}

act(() =>
root.render(
<React.StrictMode>
<App />
</React.StrictMode>,
),
);

expect(mockLog).toHaveBeenCalledTimes(1);
expect(mockLog.mock.calls[0]).toHaveLength(1);
expect(mockLog.mock.calls[0][0]).toBe('log');

expect(mockWarn).toHaveBeenCalledTimes(1);
expect(mockWarn.mock.calls[0]).toHaveLength(1);
expect(mockWarn.mock.calls[0][0]).toBe('warn');

expect(mockError).toHaveBeenCalledTimes(1);
expect(mockError.mock.calls[0]).toHaveLength(1);
expect(mockError.mock.calls[0][0]).toBe('error');

expect(mockInfo).toHaveBeenCalledTimes(1);
expect(mockInfo.mock.calls[0]).toHaveLength(1);
expect(mockInfo.mock.calls[0][0]).toBe('info');

expect(mockGroup).toHaveBeenCalledTimes(1);
expect(mockGroup.mock.calls[0]).toHaveLength(1);
expect(mockGroup.mock.calls[0][0]).toBe('group');

expect(mockGroupCollapsed).toHaveBeenCalledTimes(1);
expect(mockGroupCollapsed.mock.calls[0]).toHaveLength(1);
expect(mockGroupCollapsed.mock.calls[0][0]).toBe('groupCollapsed');
});

it('should double log in Strict mode initial render for extension', () => {
global.__REACT_DEVTOOLS_APPEND_COMPONENT_STACK__ = false;
global.__REACT_DEVTOOLS_HIDE_CONSOLE_LOGS_IN_STRICT_MODE__ = false;

// This simulates a render that happens before React DevTools have finished
// their handshake to attach the React DOM renderer functions to DevTools
// In this case, we should still be able to mock the console in Strict mode
global.__REACT_DEVTOOLS_GLOBAL_HOOK__.rendererInterfaces.set(
rendererID,
null,
);
const container = document.createElement('div');
const root = ReactDOMClient.createRoot(container);
function App() {
fakeConsole.log('log');
fakeConsole.warn('warn');
fakeConsole.error('error');
return <div />;
}

act(() =>
root.render(
<React.StrictMode>
<App />
</React.StrictMode>,
),
);

expect(mockLog).toHaveBeenCalledTimes(2);
expect(mockLog.mock.calls[0]).toHaveLength(1);
expect(mockLog.mock.calls[0][0]).toBe('log');
expect(mockLog.mock.calls[1]).toHaveLength(3);
expect(mockLog.mock.calls[1]).toEqual([
'%c%s',
`color: ${process.env.DARK_MODE_DIMMED_LOG_COLOR}`,
'log',
]);

expect(mockWarn).toHaveBeenCalledTimes(2);
expect(mockWarn.mock.calls[0]).toHaveLength(1);
expect(mockWarn.mock.calls[0][0]).toBe('warn');
expect(mockWarn.mock.calls[1]).toHaveLength(3);
expect(mockWarn.mock.calls[1]).toEqual([
'%c%s',
`color: ${process.env.DARK_MODE_DIMMED_WARNING_COLOR}`,
'warn',
]);

expect(mockError).toHaveBeenCalledTimes(2);
expect(mockError.mock.calls[0]).toHaveLength(1);
expect(mockError.mock.calls[0][0]).toBe('error');
expect(mockError.mock.calls[1]).toHaveLength(3);
expect(mockError.mock.calls[1]).toEqual([
'%c%s',
`color: ${process.env.DARK_MODE_DIMMED_ERROR_COLOR}`,
'error',
]);
});

it('should not double log in Strict mode initial render for extension', () => {
global.__REACT_DEVTOOLS_APPEND_COMPONENT_STACK__ = false;
global.__REACT_DEVTOOLS_HIDE_CONSOLE_LOGS_IN_STRICT_MODE__ = true;

// This simulates a render that happens before React DevTools have finished
// their handshake to attach the React DOM renderer functions to DevTools
// In this case, we should still be able to mock the console in Strict mode
global.__REACT_DEVTOOLS_GLOBAL_HOOK__.rendererInterfaces.set(
rendererID,
null,
);
const container = document.createElement('div');
const root = ReactDOMClient.createRoot(container);
function App() {
fakeConsole.log('log');
fakeConsole.warn('warn');
fakeConsole.error('error');
return <div />;
}

act(() =>
root.render(
<React.StrictMode>
<App />
</React.StrictMode>,
),
);
expect(mockLog).toHaveBeenCalledTimes(1);
expect(mockLog.mock.calls[0]).toHaveLength(1);
expect(mockLog.mock.calls[0][0]).toBe('log');

expect(mockWarn).toHaveBeenCalledTimes(1);
expect(mockWarn.mock.calls[0]).toHaveLength(1);
expect(mockWarn.mock.calls[0][0]).toBe('warn');

expect(mockError).toHaveBeenCalledTimes(1);
expect(mockError.mock.calls[0]).toHaveLength(1);
expect(mockError.mock.calls[0][0]).toBe('error');
});

it('should properly dim component stacks during strict mode double log', () => {
global.__REACT_DEVTOOLS_APPEND_COMPONENT_STACK__ = true;
global.__REACT_DEVTOOLS_HIDE_CONSOLE_LOGS_IN_STRICT_MODE__ = false;

const container = document.createElement('div');


const root = ReactDOMClient.createRoot(container);

const Intermediate = ({children}) => children;


const Parent = ({children}) => (
<Intermediate>
<Child />
</Intermediate>
);
const Child = ({children}) => {
fakeConsole.error('error');
fakeConsole.warn('warn');
return null;
};

act(() =>
root.render(
<React.StrictMode>
<Parent />
</React.StrictMode>,
),
);

expect(mockWarn).toHaveBeenCalledTimes(2);
expect(mockWarn.mock.calls[0]).toHaveLength(2);
expect(normalizeCodeLocInfo(mockWarn.mock.calls[0][1])).toEqual(
'\n in Child (at **)\n in Intermediate (at **)\n in Parent (at **)',
);
expect(mockWarn.mock.calls[1]).toHaveLength(4);
expect(mockWarn.mock.calls[1][0]).toEqual('%c%s %s');
expect(mockWarn.mock.calls[1][1]).toMatch('color: rgba(');
expect(mockWarn.mock.calls[1][2]).toEqual('warn');
expect(normalizeCodeLocInfo(mockWarn.mock.calls[1][3]).trim()).toEqual(
'in Child (at **)\n in Intermediate (at **)\n in Parent (at **)',
);

expect(mockError).toHaveBeenCalledTimes(2);
expect(mockError.mock.calls[0]).toHaveLength(2);
expect(normalizeCodeLocInfo(mockError.mock.calls[0][1])).toEqual(
'\n in Child (at **)\n in Intermediate (at **)\n in Parent (at **)',
);
expect(mockError.mock.calls[1]).toHaveLength(4);
expect(mockError.mock.calls[1][0]).toEqual('%c%s %s');
expect(mockError.mock.calls[1][1]).toMatch('color: rgba(');
expect(mockError.mock.calls[1][2]).toEqual('error');
expect(normalizeCodeLocInfo(mockError.mock.calls[1][3]).trim()).toEqual(
'in Child (at **)\n in Intermediate (at **)\n in Parent (at **)',
);
});
});

describe('console error', () => {


beforeEach(() => {
jest.resetModules();

const Console = require('react-devtools-shared/src/backend/console');


patchConsole = Console.patch;
unpatchConsole = Console.unpatch;

// Patch a fake console so we can verify with tests below.


// Patching the real console is too complicated,
// because Jest itself has hooks into it as does our test env setup.
mockError = jest.fn();
mockInfo = jest.fn();
mockGroup = jest.fn();
mockGroupCollapsed = jest.fn();
mockLog = jest.fn();
mockWarn = jest.fn();
fakeConsole = {
error: mockError,
info: mockInfo,
log: mockLog,
warn: mockWarn,
group: mockGroup,
groupCollapsed: mockGroupCollapsed,
};

Console.dangerous_setTargetConsoleForTesting(fakeConsole);

const inject = global.__REACT_DEVTOOLS_GLOBAL_HOOK__.inject;


global.__REACT_DEVTOOLS_GLOBAL_HOOK__.inject = internals => {
inject(internals);

Console.registerRenderer(internals, () => {
throw Error('foo');
});
};

React = require('react');
ReactDOMClient = require('react-dom/client');

const utils = require('./utils');


act = utils.act;
legacyRender = utils.legacyRender;
});

// @reactVersion >=18.0
it('error in console log throws without interfering with logging', () => {
const container = document.createElement('div');
const root = ReactDOMClient.createRoot(container);

function App() {
fakeConsole.log('log');
fakeConsole.warn('warn');
fakeConsole.error('error');
return <div />;
}

patchConsole({
appendComponentStack: true,
breakOnConsoleErrors: false,
showInlineWarningsAndErrors: true,
hideConsoleLogsInStrictMode: false,
});

expect(() => {
act(() => {
root.render(<App />);
});
}).toThrowError('foo');

expect(mockLog).toHaveBeenCalledTimes(1);
expect(mockLog.mock.calls[0]).toHaveLength(1);
expect(mockLog.mock.calls[0][0]).toBe('log');

expect(mockWarn).toHaveBeenCalledTimes(1);
expect(mockWarn.mock.calls[0]).toHaveLength(1);
expect(mockWarn.mock.calls[0][0]).toBe('warn');

expect(mockError).toHaveBeenCalledTimes(1);
expect(mockError.mock.calls[0]).toHaveLength(1);
expect(mockError.mock.calls[0][0]).toBe('error');
});
});

You might also like