Repository: facebook/react
Pull request: [tests] Disallow unasserted console.log

Followup from https://github.com/facebook/react/pull/28693 and https://github.com/facebook/react/pull/28680.

In CI, we fail the test for any unasserted console.log. In DEV, we don't fail, but you can still use the matchers and we'll assert on them.

Changed files:
filename: scripts/jest/matchers/__tests__/toWarnDev-test.js
patch: @@ -416,3 +416,20 @@ describe('toWarnDev', () => {
     });
   }
 });
+
+describe('toLogDev', () => {
+  it('does not fail if warnings do not include a stack', () => {
+    expect(() => {
+      if (__DEV__) {
+        console.log('Hello');
+      }
+    }).toLogDev('Hello');
+    expect(() => {
+      if (__DEV__) {
+        console.log('Hello');
+        console.log('Good day');
+        console.log('Bye');
+      }
+    }).toLogDev(['Hello', 'Good day', 'Bye']);
+  });
+});

filename: scripts/jest/matchers/toWarnDev.js
patch: @@ -178,7 +178,9 @@ const createMatcherFor = (consoleMethod, matcherName) =>
           };
         }
 
-        if (typeof withoutStack === 'number') {
+        if (consoleMethod === 'log') {
+          // We don't expect any console.log calls to have a stack.
+        } else if (typeof withoutStack === 'number') {
           // We're expecting a particular number of warnings without stacks.
           if (withoutStack !== warningsWithoutComponentStack.length) {
             return {
@@ -309,4 +311,5 @@ const createMatcherFor = (consoleMethod, matcherName) =>
 module.exports = {
   toWarnDev: createMatcherFor('warn', 'toWarnDev'),
   toErrorDev: createMatcherFor('error', 'toErrorDev'),
+  toLogDev: createMatcherFor('log', 'toLogDev'),
 };

filename: scripts/jest/setupTests.js
patch: @@ -116,28 +116,37 @@ if (process.env.REACT_CLASS_EQUIVALENCE_TEST) {
             .join('\n')}`
       );
 
+      const type = methodName === 'log' ? 'log' : 'warning';
       const message =
         `Expected test not to call ${chalk.bold(
           `console.${methodName}()`
         )}.\n\n` +
-        'If the warning is expected, test for it explicitly by:\n' +
+        `If the ${type} is expected, test for it explicitly by:\n` +
         `1. Using the ${chalk.bold('.' + expectedMatcher + '()')} ` +
         `matcher, or...\n` +
         `2. Mock it out using ${chalk.bold(
           'spyOnDev'
         )}(console, '${methodName}') or ${chalk.bold(
           'spyOnProd'
-        )}(console, '${methodName}'), and test that the warning occurs.`;
+        )}(console, '${methodName}'), and test that the ${type} occurs.`;
 
       throw new Error(`${message}\n\n${messages.join('\n\n')}`);
     }
   };
 
   const unexpectedErrorCallStacks = [];
   const unexpectedWarnCallStacks = [];
+  const unexpectedLogCallStacks = [];
 
   const errorMethod = patchConsoleMethod('error', unexpectedErrorCallStacks);
   const warnMethod = patchConsoleMethod('warn', unexpectedWarnCallStacks);
+  let logMethod;
+
+  // Only assert console.log isn't called in CI so you can debug tests in DEV.
+  // The matchers will still work in DEV, so you can assert locally.
+  if (process.env.CI) {
+    logMethod = patchConsoleMethod('log', unexpectedLogCallStacks);
+  }
 
   const flushAllUnexpectedConsoleCalls = () => {
     flushUnexpectedConsoleCalls(
@@ -152,13 +161,25 @@ if (process.env.REACT_CLASS_EQUIVALENCE_TEST) {
       'toWarnDev',
       unexpectedWarnCallStacks
     );
+    if (logMethod) {
+      flushUnexpectedConsoleCalls(
+        logMethod,
+        'log',
+        'toLogDev',
+        unexpectedLogCallStacks
+      );
+      unexpectedLogCallStacks.length = 0;
+    }
     unexpectedErrorCallStacks.length = 0;
     unexpectedWarnCallStacks.length = 0;
   };
 
   const resetAllUnexpectedConsoleCalls = () => {
     unexpectedErrorCallStacks.length = 0;
     unexpectedWarnCallStacks.length = 0;
+    if (logMethod) {
+      unexpectedLogCallStacks.length = 0;
+    }
   };
 
   beforeEach(resetAllUnexpectedConsoleCalls);