Repository: facebook/react
Pull request: Handle info, group, and groupCollapsed in Strict Mode logging

## Summary

While working on the new Next.js router which heavily relies on useReducer I noticed that `group` and `groupCollapsed` which both take labels were showing as-is in the console for the second render/dispatch in Strict Mode logs. While looking at the code I found that `info` was also not instrumented.

I've added additional handling for:
- `info`
- `group`
- `groupCollapsed`

<!--
  Thanks for submitting a pull request!
  We appreciate you spending the time to work on these changes. Please provide enough information so that others can review your pull request. The three fields below are mandatory.

  Before submitting a pull request, please make sure the following is done:

  1. Fork [the repository](https://github.com/facebook/react) and create your branch from `main`.
  2. Run `yarn` in the repository root.
  3. If you've fixed a bug or added code that should be tested, add tests!
  4. Ensure the test suite passes (`yarn test`). Tip: `yarn test --watch TestName` is helpful in development.
  5. Run `yarn test --prod` to test in the production environment. It supports the same options as `yarn test`.
  6. If you need a debugger, run `yarn debug-test --watch TestName`, open `chrome://inspect`, and press "Inspect".
  7. Format your code with [prettier](https://github.com/prettier/prettier) (`yarn prettier`).
  8. Make sure your code lints (`yarn lint`). Tip: `yarn linc` to only check changed files.
  9. Run the [Flow](https://flowtype.org/) type checks (`yarn flow`).
  10. If you haven't already, complete the CLA.

  Learn more about contributing: https://reactjs.org/docs/how-to-contribute.html
-->


<!--
 Explain the **motivation** for making this change. What existing problem does the pull request solve?
-->

## How did you test this change?

Looked at earlier PRs and copied the blocks for the `error` case in the same file to cover the other console methods.

Example of the change. `prefetch` is before, `navigate` is after:

<img width="105" alt="Screen Shot 2022-09-01 at 17 40 36" src="https://user-images.githubusercontent.com/6324199/187985758-fec8afb5-e344-4d3d-a87a-08ca7fd526ae.png">

<!--
  Demonstrate the code is solid. Example: The exact commands you ran and their output, screenshots / videos if the pull request changes the user interface.
  How exactly did you verify that your PR solves the issue you wanted to solve?
  If you leave this empty, your PR will very likely be closed.
-->


Changed files:
filename: packages/react-devtools-shared/src/__tests__/console-test.js
patch: @@ -16,6 +16,8 @@ let fakeConsole;
 let legacyRender;
 let mockError;
 let mockInfo;
+let mockGroup;
+let mockGroupCollapsed;
 let mockLog;
 let mockWarn;
 let patchConsole;
@@ -25,6 +27,7 @@ let rendererID;
 describe('console', () => {
   beforeEach(() => {
     const Console = require('react-devtools-shared/src/backend/console');
+
     patchConsole = Console.patch;
     unpatchConsole = Console.unpatch;
 
@@ -33,13 +36,17 @@ describe('console', () => {
     // 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);
@@ -69,6 +76,8 @@ describe('console', () => {
     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
@@ -491,6 +500,9 @@ describe('console', () => {
       fakeConsole.log('log');
       fakeConsole.warn('warn');
       fakeConsole.error('error');
+      fakeConsole.info('info');
+      fakeConsole.group('group');
+      fakeConsole.groupCollapsed('groupCollapsed');
       return <div />;
     }
 
@@ -528,6 +540,36 @@ describe('console', () => {
       `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', () => {
@@ -538,9 +580,16 @@ describe('console', () => {
     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 />;
     }
 
@@ -563,6 +612,18 @@ describe('console', () => {
     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', () => {
@@ -734,13 +795,17 @@ describe('console error', () => {
     // 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);

filename: packages/react-devtools-shared/src/backend/console.js
patch: @@ -301,7 +301,15 @@ let unpatchForStrictModeFn: null | (() => void) = null;
 // NOTE: KEEP IN SYNC with src/hook.js:patchConsoleForInitialRenderInStrictMode
 export function patchForStrictMode() {
   if (consoleManagedByDevToolsDuringStrictMode) {
-    const overrideConsoleMethods = ['error', 'trace', 'warn', 'log'];
+    const overrideConsoleMethods = [
+      'error',
+      'group',
+      'groupCollapsed',
+      'info',
+      'log',
+      'trace',
+      'warn',
+    ];
 
     if (unpatchForStrictModeFn !== null) {
       // Don't patch twice.

filename: packages/react-devtools-shared/src/hook.js
patch: @@ -228,7 +228,15 @@ export function installHook(target: any): DevToolsHook | null {
     hideConsoleLogsInStrictMode: boolean,
     browserTheme: BrowserTheme,
   }) {
-    const overrideConsoleMethods = ['error', 'trace', 'warn', 'log'];
+    const overrideConsoleMethods = [
+      'error',
+      'group',
+      'groupCollapsed',
+      'info',
+      'log',
+      'trace',
+      'warn',
+    ];
 
     if (unpatchFn !== null) {
       // Don't patch twice.