mirror of
https://github.com/zebrajr/react.git
synced 2026-01-15 12:15:22 +00:00
Move console mocks to internal-test-utils (#28710)
Moving this to `internal-test-utils` so I can add helpers in the next PR for: - assertLogDev - assertWarnDev - assertErrorDev Which will be exported from `internal-test-utils`. This isn't strictly necessary, but it makes the factoring nicer, so internal-test-until doesn't need to depend on `scripts/jest`.
This commit is contained in:
@@ -12,6 +12,7 @@
|
||||
|
||||
const React = require('react');
|
||||
const {startTransition, useDeferredValue} = React;
|
||||
const chalk = require('chalk');
|
||||
const ReactNoop = require('react-noop-renderer');
|
||||
const {
|
||||
waitFor,
|
||||
@@ -22,6 +23,11 @@ const {
|
||||
} = require('internal-test-utils');
|
||||
const act = require('internal-test-utils').act;
|
||||
const Scheduler = require('scheduler/unstable_mock');
|
||||
const {
|
||||
flushAllUnexpectedConsoleCalls,
|
||||
resetAllUnexpectedConsoleCalls,
|
||||
patchConsoleMethods,
|
||||
} = require('../consoleMock');
|
||||
|
||||
describe('ReactInternalTestUtils', () => {
|
||||
test('waitFor', async () => {
|
||||
@@ -154,3 +160,144 @@ describe('ReactInternalTestUtils', () => {
|
||||
assertLog(['A', 'B', 'C']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('ReactInternalTestUtils console mocks', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
patchConsoleMethods({includeLog: true});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
resetAllUnexpectedConsoleCalls();
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
describe('console.log', () => {
|
||||
it('should fail if not asserted', () => {
|
||||
expect(() => {
|
||||
console.log('hit');
|
||||
flushAllUnexpectedConsoleCalls();
|
||||
}).toThrow(`Expected test not to call ${chalk.bold('console.log()')}.`);
|
||||
});
|
||||
|
||||
// @gate __DEV__
|
||||
it('should not fail if mocked with spyOnDev', () => {
|
||||
spyOnDev(console, 'log').mockImplementation(() => {});
|
||||
expect(() => {
|
||||
console.log('hit');
|
||||
flushAllUnexpectedConsoleCalls();
|
||||
}).not.toThrow();
|
||||
});
|
||||
|
||||
// @gate !__DEV__
|
||||
it('should not fail if mocked with spyOnProd', () => {
|
||||
spyOnProd(console, 'log').mockImplementation(() => {});
|
||||
expect(() => {
|
||||
console.log('hit');
|
||||
flushAllUnexpectedConsoleCalls();
|
||||
}).not.toThrow();
|
||||
});
|
||||
|
||||
it('should not fail if mocked with spyOnDevAndProd', () => {
|
||||
spyOnDevAndProd(console, 'log').mockImplementation(() => {});
|
||||
expect(() => {
|
||||
console.log('hit');
|
||||
flushAllUnexpectedConsoleCalls();
|
||||
}).not.toThrow();
|
||||
});
|
||||
|
||||
// @gate __DEV__
|
||||
it('should not fail with toLogDev', () => {
|
||||
expect(() => {
|
||||
console.log('hit');
|
||||
flushAllUnexpectedConsoleCalls();
|
||||
}).toLogDev(['hit']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('console.warn', () => {
|
||||
it('should fail if not asserted', () => {
|
||||
expect(() => {
|
||||
console.warn('hit');
|
||||
flushAllUnexpectedConsoleCalls();
|
||||
}).toThrow(`Expected test not to call ${chalk.bold('console.warn()')}.`);
|
||||
});
|
||||
|
||||
// @gate __DEV__
|
||||
it('should not fail if mocked with spyOnDev', () => {
|
||||
spyOnDev(console, 'warn').mockImplementation(() => {});
|
||||
expect(() => {
|
||||
console.warn('hit');
|
||||
flushAllUnexpectedConsoleCalls();
|
||||
}).not.toThrow();
|
||||
});
|
||||
|
||||
// @gate !__DEV__
|
||||
it('should not fail if mocked with spyOnProd', () => {
|
||||
spyOnProd(console, 'warn').mockImplementation(() => {});
|
||||
expect(() => {
|
||||
console.warn('hit');
|
||||
flushAllUnexpectedConsoleCalls();
|
||||
}).not.toThrow();
|
||||
});
|
||||
|
||||
it('should not fail if mocked with spyOnDevAndProd', () => {
|
||||
spyOnDevAndProd(console, 'warn').mockImplementation(() => {});
|
||||
expect(() => {
|
||||
console.warn('hit');
|
||||
flushAllUnexpectedConsoleCalls();
|
||||
}).not.toThrow();
|
||||
});
|
||||
|
||||
// @gate __DEV__
|
||||
it('should not fail with toWarnDev', () => {
|
||||
expect(() => {
|
||||
console.warn('hit');
|
||||
flushAllUnexpectedConsoleCalls();
|
||||
}).toWarnDev(['hit'], {withoutStack: true});
|
||||
});
|
||||
});
|
||||
|
||||
describe('console.error', () => {
|
||||
it('should fail if console.error is not asserted', () => {
|
||||
expect(() => {
|
||||
console.error('hit');
|
||||
flushAllUnexpectedConsoleCalls();
|
||||
}).toThrow(`Expected test not to call ${chalk.bold('console.error()')}.`);
|
||||
});
|
||||
|
||||
// @gate __DEV__
|
||||
it('should not fail if mocked with spyOnDev', () => {
|
||||
spyOnDev(console, 'error').mockImplementation(() => {});
|
||||
expect(() => {
|
||||
console.error('hit');
|
||||
flushAllUnexpectedConsoleCalls();
|
||||
}).not.toThrow();
|
||||
});
|
||||
|
||||
// @gate !__DEV__
|
||||
it('should not fail if mocked with spyOnProd', () => {
|
||||
spyOnProd(console, 'error').mockImplementation(() => {});
|
||||
expect(() => {
|
||||
console.error('hit');
|
||||
flushAllUnexpectedConsoleCalls();
|
||||
}).not.toThrow();
|
||||
});
|
||||
|
||||
it('should not fail if mocked with spyOnDevAndProd', () => {
|
||||
spyOnDevAndProd(console, 'error').mockImplementation(() => {});
|
||||
expect(() => {
|
||||
console.error('hit');
|
||||
flushAllUnexpectedConsoleCalls();
|
||||
}).not.toThrow();
|
||||
});
|
||||
|
||||
// @gate __DEV__
|
||||
it('should not fail with toErrorDev', () => {
|
||||
expect(() => {
|
||||
console.error('hit');
|
||||
flushAllUnexpectedConsoleCalls();
|
||||
}).toErrorDev(['hit'], {withoutStack: true});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
136
packages/internal-test-utils/consoleMock.js
Normal file
136
packages/internal-test-utils/consoleMock.js
Normal file
@@ -0,0 +1,136 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/* eslint-disable react-internal/no-production-logging */
|
||||
const chalk = require('chalk');
|
||||
const util = require('util');
|
||||
const shouldIgnoreConsoleError = require('./shouldIgnoreConsoleError');
|
||||
const shouldIgnoreConsoleWarn = require('./shouldIgnoreConsoleWarn');
|
||||
|
||||
const unexpectedErrorCallStacks = [];
|
||||
const unexpectedWarnCallStacks = [];
|
||||
const unexpectedLogCallStacks = [];
|
||||
|
||||
// TODO: Consider consolidating this with `yieldValue`. In both cases, tests
|
||||
// should not be allowed to exit without asserting on the entire log.
|
||||
const patchConsoleMethod = (methodName, unexpectedConsoleCallStacks) => {
|
||||
const newMethod = function (format, ...args) {
|
||||
// Ignore uncaught errors reported by jsdom
|
||||
// and React addendums because they're too noisy.
|
||||
if (shouldIgnoreConsoleError(format, args)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Ignore certain React warnings causing test failures
|
||||
if (methodName === 'warn' && shouldIgnoreConsoleWarn(format)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Capture the call stack now so we can warn about it later.
|
||||
// The call stack has helpful information for the test author.
|
||||
// Don't throw yet though b'c it might be accidentally caught and suppressed.
|
||||
const stack = new Error().stack;
|
||||
unexpectedConsoleCallStacks.push([
|
||||
stack.slice(stack.indexOf('\n') + 1),
|
||||
util.format(format, ...args),
|
||||
]);
|
||||
};
|
||||
|
||||
console[methodName] = newMethod;
|
||||
|
||||
return newMethod;
|
||||
};
|
||||
|
||||
const flushUnexpectedConsoleCalls = (
|
||||
mockMethod,
|
||||
methodName,
|
||||
expectedMatcher,
|
||||
unexpectedConsoleCallStacks,
|
||||
) => {
|
||||
if (
|
||||
console[methodName] !== mockMethod &&
|
||||
!jest.isMockFunction(console[methodName])
|
||||
) {
|
||||
// throw new Error(
|
||||
// `Test did not tear down console.${methodName} mock properly.`
|
||||
// );
|
||||
}
|
||||
if (unexpectedConsoleCallStacks.length > 0) {
|
||||
const messages = unexpectedConsoleCallStacks.map(
|
||||
([stack, message]) =>
|
||||
`${chalk.red(message)}\n` +
|
||||
`${stack
|
||||
.split('\n')
|
||||
.map(line => chalk.gray(line))
|
||||
.join('\n')}`,
|
||||
);
|
||||
|
||||
const type = methodName === 'log' ? 'log' : 'warning';
|
||||
const message =
|
||||
`Expected test not to call ${chalk.bold(
|
||||
`console.${methodName}()`,
|
||||
)}.\n\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 ${type} occurs.`;
|
||||
|
||||
throw new Error(`${message}\n\n${messages.join('\n\n')}`);
|
||||
}
|
||||
};
|
||||
|
||||
let errorMethod;
|
||||
let warnMethod;
|
||||
let logMethod;
|
||||
export function patchConsoleMethods({includeLog} = {includeLog: false}) {
|
||||
errorMethod = patchConsoleMethod('error', unexpectedErrorCallStacks);
|
||||
warnMethod = patchConsoleMethod('warn', unexpectedWarnCallStacks);
|
||||
|
||||
// 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 (includeLog) {
|
||||
logMethod = patchConsoleMethod('log', unexpectedLogCallStacks);
|
||||
}
|
||||
}
|
||||
|
||||
export function flushAllUnexpectedConsoleCalls() {
|
||||
flushUnexpectedConsoleCalls(
|
||||
errorMethod,
|
||||
'error',
|
||||
'toErrorDev',
|
||||
unexpectedErrorCallStacks,
|
||||
);
|
||||
flushUnexpectedConsoleCalls(
|
||||
warnMethod,
|
||||
'warn',
|
||||
'toWarnDev',
|
||||
unexpectedWarnCallStacks,
|
||||
);
|
||||
if (logMethod) {
|
||||
flushUnexpectedConsoleCalls(
|
||||
logMethod,
|
||||
'log',
|
||||
'toLogDev',
|
||||
unexpectedLogCallStacks,
|
||||
);
|
||||
unexpectedLogCallStacks.length = 0;
|
||||
}
|
||||
unexpectedErrorCallStacks.length = 0;
|
||||
unexpectedWarnCallStacks.length = 0;
|
||||
}
|
||||
|
||||
export function resetAllUnexpectedConsoleCalls() {
|
||||
unexpectedErrorCallStacks.length = 0;
|
||||
unexpectedWarnCallStacks.length = 0;
|
||||
if (logMethod) {
|
||||
unexpectedLogCallStacks.length = 0;
|
||||
}
|
||||
}
|
||||
@@ -19,10 +19,10 @@ module.exports = function shouldIgnoreConsoleError(format, args) {
|
||||
format.indexOf('ReactDOM.render was removed in React 19') !== -1 ||
|
||||
format.indexOf('ReactDOM.hydrate was removed in React 19') !== -1 ||
|
||||
format.indexOf(
|
||||
'ReactDOM.render has not been supported since React 18'
|
||||
'ReactDOM.render has not been supported since React 18',
|
||||
) !== -1 ||
|
||||
format.indexOf(
|
||||
'ReactDOM.hydrate has not been supported since React 18'
|
||||
'ReactDOM.hydrate has not been supported since React 18',
|
||||
) !== -1
|
||||
) {
|
||||
// We haven't finished migrating our tests to use createRoot.
|
||||
@@ -10,7 +10,7 @@
|
||||
'use strict';
|
||||
|
||||
const stream = require('stream');
|
||||
const shouldIgnoreConsoleError = require('../../../../../scripts/jest/shouldIgnoreConsoleError');
|
||||
const shouldIgnoreConsoleError = require('internal-test-utils/shouldIgnoreConsoleError');
|
||||
|
||||
module.exports = function (initModules) {
|
||||
let ReactDOM;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
const {diff: jestDiff} = require('jest-diff');
|
||||
const util = require('util');
|
||||
const shouldIgnoreConsoleError = require('../shouldIgnoreConsoleError');
|
||||
const shouldIgnoreConsoleError = require('internal-test-utils/shouldIgnoreConsoleError');
|
||||
|
||||
function normalizeCodeLocInfo(str) {
|
||||
if (typeof str !== 'string') {
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
'use strict';
|
||||
|
||||
const chalk = require('chalk');
|
||||
const util = require('util');
|
||||
const shouldIgnoreConsoleError = require('./shouldIgnoreConsoleError');
|
||||
const shouldIgnoreConsoleWarn = require('./shouldIgnoreConsoleWarn');
|
||||
const {getTestFlags} = require('./TestFlags');
|
||||
const {
|
||||
flushAllUnexpectedConsoleCalls,
|
||||
resetAllUnexpectedConsoleCalls,
|
||||
patchConsoleMethods,
|
||||
} = require('internal-test-utils/consoleMock');
|
||||
|
||||
if (process.env.REACT_CLASS_EQUIVALENCE_TEST) {
|
||||
// Inside the class equivalence tester, we have a custom environment, let's
|
||||
@@ -62,126 +63,8 @@ if (process.env.REACT_CLASS_EQUIVALENCE_TEST) {
|
||||
}
|
||||
});
|
||||
|
||||
// TODO: Consider consolidating this with `yieldValue`. In both cases, tests
|
||||
// should not be allowed to exit without asserting on the entire log.
|
||||
const patchConsoleMethod = (methodName, unexpectedConsoleCallStacks) => {
|
||||
const newMethod = function (format, ...args) {
|
||||
// Ignore uncaught errors reported by jsdom
|
||||
// and React addendums because they're too noisy.
|
||||
if (shouldIgnoreConsoleError(format, args)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Ignore certain React warnings causing test failures
|
||||
if (methodName === 'warn' && shouldIgnoreConsoleWarn(format)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Capture the call stack now so we can warn about it later.
|
||||
// The call stack has helpful information for the test author.
|
||||
// Don't throw yet though b'c it might be accidentally caught and suppressed.
|
||||
const stack = new Error().stack;
|
||||
unexpectedConsoleCallStacks.push([
|
||||
stack.slice(stack.indexOf('\n') + 1),
|
||||
util.format(format, ...args),
|
||||
]);
|
||||
};
|
||||
|
||||
console[methodName] = newMethod;
|
||||
|
||||
return newMethod;
|
||||
};
|
||||
|
||||
const flushUnexpectedConsoleCalls = (
|
||||
mockMethod,
|
||||
methodName,
|
||||
expectedMatcher,
|
||||
unexpectedConsoleCallStacks
|
||||
) => {
|
||||
if (
|
||||
console[methodName] !== mockMethod &&
|
||||
!jest.isMockFunction(console[methodName])
|
||||
) {
|
||||
// throw new Error(
|
||||
// `Test did not tear down console.${methodName} mock properly.`
|
||||
// );
|
||||
}
|
||||
if (unexpectedConsoleCallStacks.length > 0) {
|
||||
const messages = unexpectedConsoleCallStacks.map(
|
||||
([stack, message]) =>
|
||||
`${chalk.red(message)}\n` +
|
||||
`${stack
|
||||
.split('\n')
|
||||
.map(line => chalk.gray(line))
|
||||
.join('\n')}`
|
||||
);
|
||||
|
||||
const type = methodName === 'log' ? 'log' : 'warning';
|
||||
const message =
|
||||
`Expected test not to call ${chalk.bold(
|
||||
`console.${methodName}()`
|
||||
)}.\n\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 ${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(
|
||||
errorMethod,
|
||||
'error',
|
||||
'toErrorDev',
|
||||
unexpectedErrorCallStacks
|
||||
);
|
||||
flushUnexpectedConsoleCalls(
|
||||
warnMethod,
|
||||
'warn',
|
||||
'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;
|
||||
}
|
||||
};
|
||||
|
||||
// Patch the console to assert that all console error/warn/log calls assert.
|
||||
patchConsoleMethods({includeLog: !!process.env.CI});
|
||||
beforeEach(resetAllUnexpectedConsoleCalls);
|
||||
afterEach(flushAllUnexpectedConsoleCalls);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user