mirror of
https://github.com/facebook/react.git
synced 2026-02-21 19:31:52 +00:00
[tests] Remove to*Dev matchers (#31989)
Based off: https://github.com/facebook/react/pull/31988 <img width="741" alt="Screenshot 2025-01-06 at 12 52 08 AM" src="https://github.com/user-attachments/assets/29b159ca-66d4-441f-8817-dd2db66d1edb" /> it is done
This commit is contained in:
@@ -303,7 +303,6 @@ module.exports = {
|
||||
ERROR,
|
||||
{isProductionUserAppCode: true},
|
||||
],
|
||||
'react-internal/no-to-warn-dev-within-to-throw': ERROR,
|
||||
'react-internal/warning-args': ERROR,
|
||||
'react-internal/no-production-logging': ERROR,
|
||||
},
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
const React = require('react');
|
||||
const stripAnsi = require('strip-ansi');
|
||||
const {startTransition, useDeferredValue} = React;
|
||||
const chalk = require('chalk');
|
||||
const ReactNoop = require('react-noop-renderer');
|
||||
const {
|
||||
waitFor,
|
||||
@@ -25,7 +24,7 @@ const {
|
||||
const act = require('internal-test-utils').act;
|
||||
const Scheduler = require('scheduler/unstable_mock');
|
||||
const {
|
||||
flushAllUnexpectedConsoleCalls,
|
||||
assertConsoleLogsCleared,
|
||||
resetAllUnexpectedConsoleCalls,
|
||||
patchConsoleMethods,
|
||||
} = require('../consoleMock');
|
||||
@@ -205,16 +204,17 @@ describe('ReactInternalTestUtils console mocks', () => {
|
||||
it('should fail if not asserted', () => {
|
||||
expect(() => {
|
||||
console.log('hit');
|
||||
flushAllUnexpectedConsoleCalls();
|
||||
}).toThrow(`Expected test not to call ${chalk.bold('console.log()')}.`);
|
||||
assertConsoleLogsCleared();
|
||||
}).toThrow(`console.log was called without assertConsoleLogDev`);
|
||||
});
|
||||
|
||||
// @gate __DEV__
|
||||
it('should not fail if mocked with spyOnDev', () => {
|
||||
spyOnDev(console, 'log').mockImplementation(() => {});
|
||||
expect(() => {
|
||||
console.log('hit');
|
||||
flushAllUnexpectedConsoleCalls();
|
||||
if (__DEV__) {
|
||||
console.log('hit');
|
||||
}
|
||||
assertConsoleLogsCleared();
|
||||
}).not.toThrow();
|
||||
});
|
||||
|
||||
@@ -223,7 +223,7 @@ describe('ReactInternalTestUtils console mocks', () => {
|
||||
spyOnProd(console, 'log').mockImplementation(() => {});
|
||||
expect(() => {
|
||||
console.log('hit');
|
||||
flushAllUnexpectedConsoleCalls();
|
||||
assertConsoleLogsCleared();
|
||||
}).not.toThrow();
|
||||
});
|
||||
|
||||
@@ -231,33 +231,26 @@ describe('ReactInternalTestUtils console mocks', () => {
|
||||
spyOnDevAndProd(console, 'log').mockImplementation(() => {});
|
||||
expect(() => {
|
||||
console.log('hit');
|
||||
flushAllUnexpectedConsoleCalls();
|
||||
assertConsoleLogsCleared();
|
||||
}).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()')}.`);
|
||||
assertConsoleLogsCleared();
|
||||
}).toThrow('console.warn was called without assertConsoleWarnDev');
|
||||
});
|
||||
|
||||
// @gate __DEV__
|
||||
it('should not fail if mocked with spyOnDev', () => {
|
||||
spyOnDev(console, 'warn').mockImplementation(() => {});
|
||||
expect(() => {
|
||||
console.warn('hit');
|
||||
flushAllUnexpectedConsoleCalls();
|
||||
if (__DEV__) {
|
||||
console.warn('hit');
|
||||
}
|
||||
assertConsoleLogsCleared();
|
||||
}).not.toThrow();
|
||||
});
|
||||
|
||||
@@ -266,7 +259,7 @@ describe('ReactInternalTestUtils console mocks', () => {
|
||||
spyOnProd(console, 'warn').mockImplementation(() => {});
|
||||
expect(() => {
|
||||
console.warn('hit');
|
||||
flushAllUnexpectedConsoleCalls();
|
||||
assertConsoleLogsCleared();
|
||||
}).not.toThrow();
|
||||
});
|
||||
|
||||
@@ -274,33 +267,26 @@ describe('ReactInternalTestUtils console mocks', () => {
|
||||
spyOnDevAndProd(console, 'warn').mockImplementation(() => {});
|
||||
expect(() => {
|
||||
console.warn('hit');
|
||||
flushAllUnexpectedConsoleCalls();
|
||||
assertConsoleLogsCleared();
|
||||
}).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()')}.`);
|
||||
assertConsoleLogsCleared();
|
||||
}).toThrow('console.error was called without assertConsoleErrorDev');
|
||||
});
|
||||
|
||||
// @gate __DEV__
|
||||
it('should not fail if mocked with spyOnDev', () => {
|
||||
spyOnDev(console, 'error').mockImplementation(() => {});
|
||||
expect(() => {
|
||||
console.error('hit');
|
||||
flushAllUnexpectedConsoleCalls();
|
||||
if (__DEV__) {
|
||||
console.error('hit');
|
||||
}
|
||||
assertConsoleLogsCleared();
|
||||
}).not.toThrow();
|
||||
});
|
||||
|
||||
@@ -309,7 +295,7 @@ describe('ReactInternalTestUtils console mocks', () => {
|
||||
spyOnProd(console, 'error').mockImplementation(() => {});
|
||||
expect(() => {
|
||||
console.error('hit');
|
||||
flushAllUnexpectedConsoleCalls();
|
||||
assertConsoleLogsCleared();
|
||||
}).not.toThrow();
|
||||
});
|
||||
|
||||
@@ -317,17 +303,9 @@ describe('ReactInternalTestUtils console mocks', () => {
|
||||
spyOnDevAndProd(console, 'error').mockImplementation(() => {});
|
||||
expect(() => {
|
||||
console.error('hit');
|
||||
flushAllUnexpectedConsoleCalls();
|
||||
assertConsoleLogsCleared();
|
||||
}).not.toThrow();
|
||||
});
|
||||
|
||||
// @gate __DEV__
|
||||
it('should not fail with toErrorDev', () => {
|
||||
expect(() => {
|
||||
console.error('hit');
|
||||
flushAllUnexpectedConsoleCalls();
|
||||
}).toErrorDev(['hit'], {withoutStack: true});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -361,17 +339,19 @@ describe('ReactInternalTestUtils console assertions', () => {
|
||||
});
|
||||
|
||||
describe('assertConsoleLogDev', () => {
|
||||
// @gate __DEV__
|
||||
it('passes for a single log', () => {
|
||||
console.log('Hello');
|
||||
if (__DEV__) {
|
||||
console.log('Hello');
|
||||
}
|
||||
assertConsoleLogDev(['Hello']);
|
||||
});
|
||||
|
||||
// @gate __DEV__
|
||||
it('passes for multiple logs', () => {
|
||||
console.log('Hello');
|
||||
console.log('Good day');
|
||||
console.log('Bye');
|
||||
if (__DEV__) {
|
||||
console.log('Hello');
|
||||
console.log('Good day');
|
||||
console.log('Bye');
|
||||
}
|
||||
assertConsoleLogDev(['Hello', 'Good day', 'Bye']);
|
||||
});
|
||||
|
||||
@@ -906,17 +886,19 @@ describe('ReactInternalTestUtils console assertions', () => {
|
||||
});
|
||||
|
||||
describe('assertConsoleWarnDev', () => {
|
||||
// @gate __DEV__
|
||||
it('passes if an warning contains a stack', () => {
|
||||
console.warn('Hello\n in div');
|
||||
if (__DEV__) {
|
||||
console.warn('Hello\n in div');
|
||||
}
|
||||
assertConsoleWarnDev(['Hello']);
|
||||
});
|
||||
|
||||
// @gate __DEV__
|
||||
it('passes if all warnings contain a stack', () => {
|
||||
console.warn('Hello\n in div');
|
||||
console.warn('Good day\n in div');
|
||||
console.warn('Bye\n in div');
|
||||
if (__DEV__) {
|
||||
console.warn('Hello\n in div');
|
||||
console.warn('Good day\n in div');
|
||||
console.warn('Bye\n in div');
|
||||
}
|
||||
assertConsoleWarnDev(['Hello', 'Good day', 'Bye']);
|
||||
});
|
||||
|
||||
@@ -1353,14 +1335,17 @@ describe('ReactInternalTestUtils console assertions', () => {
|
||||
});
|
||||
|
||||
describe('global withoutStack', () => {
|
||||
// @gate __DEV__
|
||||
it('passes if warnings without stack explicitly opt out', () => {
|
||||
console.warn('Hello');
|
||||
if (__DEV__) {
|
||||
console.warn('Hello');
|
||||
}
|
||||
assertConsoleWarnDev(['Hello'], {withoutStack: true});
|
||||
|
||||
console.warn('Hello');
|
||||
console.warn('Good day');
|
||||
console.warn('Bye');
|
||||
if (__DEV__) {
|
||||
console.warn('Hello');
|
||||
console.warn('Good day');
|
||||
console.warn('Bye');
|
||||
}
|
||||
|
||||
assertConsoleWarnDev(['Hello', 'Good day', 'Bye'], {
|
||||
withoutStack: true,
|
||||
@@ -1460,11 +1445,12 @@ describe('ReactInternalTestUtils console assertions', () => {
|
||||
});
|
||||
});
|
||||
describe('local withoutStack', () => {
|
||||
// @gate __DEV__
|
||||
it('passes when expected withoutStack logs matches the actual logs', () => {
|
||||
console.warn('Hello\n in div');
|
||||
console.warn('Good day');
|
||||
console.warn('Bye\n in div');
|
||||
if (__DEV__) {
|
||||
console.warn('Hello\n in div');
|
||||
console.warn('Good day');
|
||||
console.warn('Bye\n in div');
|
||||
}
|
||||
assertConsoleWarnDev([
|
||||
'Hello',
|
||||
['Good day', {withoutStack: true}],
|
||||
@@ -1981,17 +1967,19 @@ describe('ReactInternalTestUtils console assertions', () => {
|
||||
});
|
||||
|
||||
describe('assertConsoleErrorDev', () => {
|
||||
// @gate __DEV__
|
||||
it('passes if an error contains a stack', () => {
|
||||
console.error('Hello\n in div');
|
||||
if (__DEV__) {
|
||||
console.error('Hello\n in div');
|
||||
}
|
||||
assertConsoleErrorDev(['Hello']);
|
||||
});
|
||||
|
||||
// @gate __DEV__
|
||||
it('passes if all errors contain a stack', () => {
|
||||
console.error('Hello\n in div');
|
||||
console.error('Good day\n in div');
|
||||
console.error('Bye\n in div');
|
||||
if (__DEV__) {
|
||||
console.error('Hello\n in div');
|
||||
console.error('Good day\n in div');
|
||||
console.error('Bye\n in div');
|
||||
}
|
||||
assertConsoleErrorDev(['Hello', 'Good day', 'Bye']);
|
||||
});
|
||||
|
||||
@@ -2446,14 +2434,17 @@ describe('ReactInternalTestUtils console assertions', () => {
|
||||
});
|
||||
|
||||
describe('global withoutStack', () => {
|
||||
// @gate __DEV__
|
||||
it('passes if errors without stack explicitly opt out', () => {
|
||||
console.error('Hello');
|
||||
if (__DEV__) {
|
||||
console.error('Hello');
|
||||
}
|
||||
assertConsoleErrorDev(['Hello'], {withoutStack: true});
|
||||
|
||||
console.error('Hello');
|
||||
console.error('Good day');
|
||||
console.error('Bye');
|
||||
if (__DEV__) {
|
||||
console.error('Hello');
|
||||
console.error('Good day');
|
||||
console.error('Bye');
|
||||
}
|
||||
|
||||
assertConsoleErrorDev(['Hello', 'Good day', 'Bye'], {
|
||||
withoutStack: true,
|
||||
@@ -2553,11 +2544,12 @@ describe('ReactInternalTestUtils console assertions', () => {
|
||||
});
|
||||
});
|
||||
describe('local withoutStack', () => {
|
||||
// @gate __DEV__
|
||||
it('passes when expected withoutStack logs matches the actual logs', () => {
|
||||
console.error('Hello\n in div');
|
||||
console.error('Good day');
|
||||
console.error('Bye\n in div');
|
||||
if (__DEV__) {
|
||||
console.error('Hello\n in div');
|
||||
console.error('Good day');
|
||||
console.error('Bye\n in div');
|
||||
}
|
||||
assertConsoleErrorDev([
|
||||
'Hello',
|
||||
['Good day', {withoutStack: true}],
|
||||
|
||||
@@ -19,19 +19,7 @@ const loggedErrors = (global.__loggedErrors = global.__loggedErrors || []);
|
||||
const loggedWarns = (global.__loggedWarns = global.__loggedWarns || []);
|
||||
const loggedLogs = (global.__loggedLogs = global.__loggedLogs || []);
|
||||
|
||||
// TODO: delete these after code modding away from toWarnDev.
|
||||
const unexpectedErrorCallStacks = (global.__unexpectedErrorCallStacks =
|
||||
global.__unexpectedErrorCallStacks || []);
|
||||
const unexpectedWarnCallStacks = (global.__unexpectedWarnCallStacks =
|
||||
global.__unexpectedWarnCallStacks || []);
|
||||
const unexpectedLogCallStacks = (global.__unexpectedLogCallStacks =
|
||||
global.__unexpectedLogCallStacks || []);
|
||||
|
||||
const patchConsoleMethod = (
|
||||
methodName,
|
||||
unexpectedConsoleCallStacks,
|
||||
logged,
|
||||
) => {
|
||||
const patchConsoleMethod = (methodName, logged) => {
|
||||
const newMethod = function (format, ...args) {
|
||||
// Ignore uncaught errors reported by jsdom
|
||||
// and React addendums because they're too noisy.
|
||||
@@ -72,14 +60,6 @@ const patchConsoleMethod = (
|
||||
}
|
||||
}
|
||||
|
||||
// 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),
|
||||
]);
|
||||
logged.push([format, ...args]);
|
||||
};
|
||||
|
||||
@@ -88,123 +68,40 @@ const patchConsoleMethod = (
|
||||
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 ${chalk.bold(expectedMatcher + '()')} 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,
|
||||
loggedErrors,
|
||||
);
|
||||
warnMethod = patchConsoleMethod(
|
||||
'warn',
|
||||
unexpectedWarnCallStacks,
|
||||
loggedWarns,
|
||||
);
|
||||
patchConsoleMethod('error', loggedErrors);
|
||||
patchConsoleMethod('warn', loggedWarns);
|
||||
|
||||
// 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, loggedLogs);
|
||||
logMethod = patchConsoleMethod('log', loggedLogs);
|
||||
}
|
||||
}
|
||||
|
||||
export function flushAllUnexpectedConsoleCalls() {
|
||||
flushUnexpectedConsoleCalls(
|
||||
errorMethod,
|
||||
'error',
|
||||
'assertConsoleErrorDev',
|
||||
unexpectedErrorCallStacks,
|
||||
);
|
||||
flushUnexpectedConsoleCalls(
|
||||
warnMethod,
|
||||
'warn',
|
||||
'assertConsoleWarnDev',
|
||||
unexpectedWarnCallStacks,
|
||||
);
|
||||
if (logMethod) {
|
||||
flushUnexpectedConsoleCalls(
|
||||
logMethod,
|
||||
'log',
|
||||
'assertConsoleLogDev',
|
||||
unexpectedLogCallStacks,
|
||||
);
|
||||
unexpectedLogCallStacks.length = 0;
|
||||
}
|
||||
unexpectedErrorCallStacks.length = 0;
|
||||
unexpectedWarnCallStacks.length = 0;
|
||||
}
|
||||
|
||||
export function resetAllUnexpectedConsoleCalls() {
|
||||
loggedErrors.length = 0;
|
||||
loggedWarns.length = 0;
|
||||
unexpectedErrorCallStacks.length = 0;
|
||||
unexpectedWarnCallStacks.length = 0;
|
||||
if (logMethod) {
|
||||
loggedLogs.length = 0;
|
||||
unexpectedLogCallStacks.length = 0;
|
||||
}
|
||||
}
|
||||
|
||||
export function clearLogs() {
|
||||
const logs = Array.from(loggedLogs);
|
||||
unexpectedLogCallStacks.length = 0;
|
||||
loggedLogs.length = 0;
|
||||
return logs;
|
||||
}
|
||||
|
||||
export function clearWarnings() {
|
||||
const warnings = Array.from(loggedWarns);
|
||||
unexpectedWarnCallStacks.length = 0;
|
||||
loggedWarns.length = 0;
|
||||
return warnings;
|
||||
}
|
||||
|
||||
export function clearErrors() {
|
||||
const errors = Array.from(loggedErrors);
|
||||
unexpectedErrorCallStacks.length = 0;
|
||||
loggedErrors.length = 0;
|
||||
return errors;
|
||||
}
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @emails react-core
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const rule = require('../no-to-warn-dev-within-to-throw');
|
||||
const {RuleTester} = require('eslint');
|
||||
const ruleTester = new RuleTester();
|
||||
|
||||
ruleTester.run('eslint-rules/no-to-warn-dev-within-to-throw', rule, {
|
||||
valid: [
|
||||
'expect(callback).toWarnDev("warning");',
|
||||
'expect(function() { expect(callback).toThrow("error") }).toWarnDev("warning");',
|
||||
],
|
||||
invalid: [
|
||||
{
|
||||
code: 'expect(function() { expect(callback).toWarnDev("warning") }).toThrow("error");',
|
||||
errors: [
|
||||
{
|
||||
message: 'toWarnDev() matcher should not be nested',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
@@ -3,7 +3,6 @@
|
||||
module.exports = {
|
||||
rules: {
|
||||
'no-primitive-constructors': require('./no-primitive-constructors'),
|
||||
'no-to-warn-dev-within-to-throw': require('./no-to-warn-dev-within-to-throw'),
|
||||
'warning-args': require('./warning-args'),
|
||||
'prod-error-codes': require('./prod-error-codes'),
|
||||
'no-production-logging': require('./no-production-logging'),
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @emails react-core
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
meta: {
|
||||
schema: [],
|
||||
},
|
||||
create(context) {
|
||||
return {
|
||||
Identifier(node) {
|
||||
if (node.name === 'toWarnDev' || node.name === 'toErrorDev') {
|
||||
let current = node;
|
||||
while (current.parent) {
|
||||
if (current.type === 'CallExpression') {
|
||||
if (
|
||||
current &&
|
||||
current.callee &&
|
||||
current.callee.property &&
|
||||
current.callee.property.name === 'toThrow'
|
||||
) {
|
||||
context.report(
|
||||
node,
|
||||
node.name + '() matcher should not be nested'
|
||||
);
|
||||
}
|
||||
}
|
||||
current = current.parent;
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
@@ -1,435 +0,0 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @emails react-core
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
describe('toErrorDev', () => {
|
||||
it('does not fail if a warning contains a stack', () => {
|
||||
expect(() => {
|
||||
if (__DEV__) {
|
||||
console.error('Hello\n in div');
|
||||
}
|
||||
}).toErrorDev('Hello');
|
||||
});
|
||||
|
||||
it('does not fail if all warnings contain a stack', () => {
|
||||
expect(() => {
|
||||
if (__DEV__) {
|
||||
console.error('Hello\n in div');
|
||||
console.error('Good day\n in div');
|
||||
console.error('Bye\n in div');
|
||||
}
|
||||
}).toErrorDev(['Hello', 'Good day', 'Bye']);
|
||||
});
|
||||
|
||||
it('does not fail if warnings without stack explicitly opt out', () => {
|
||||
expect(() => {
|
||||
if (__DEV__) {
|
||||
console.error('Hello');
|
||||
}
|
||||
}).toErrorDev('Hello', {withoutStack: true});
|
||||
expect(() => {
|
||||
if (__DEV__) {
|
||||
console.error('Hello');
|
||||
console.error('Good day');
|
||||
console.error('Bye');
|
||||
}
|
||||
}).toErrorDev(['Hello', 'Good day', 'Bye'], {withoutStack: true});
|
||||
});
|
||||
|
||||
it('does not fail when expected stack-less warning number matches the actual one', () => {
|
||||
expect(() => {
|
||||
if (__DEV__) {
|
||||
console.error('Hello\n in div');
|
||||
console.error('Good day');
|
||||
console.error('Bye\n in div');
|
||||
}
|
||||
}).toErrorDev(['Hello', 'Good day', 'Bye'], {withoutStack: 1});
|
||||
});
|
||||
|
||||
if (__DEV__) {
|
||||
// Helper methods avoids invalid toWarn().toThrow() nesting
|
||||
// See no-to-warn-dev-within-to-throw
|
||||
const expectToWarnAndToThrow = (expectBlock, expectedErrorMessage) => {
|
||||
let caughtError;
|
||||
try {
|
||||
expectBlock();
|
||||
} catch (error) {
|
||||
caughtError = error;
|
||||
}
|
||||
expect(caughtError).toBeDefined();
|
||||
expect(caughtError.message).toContain(expectedErrorMessage);
|
||||
};
|
||||
|
||||
it('fails if a warning does not contain a stack', () => {
|
||||
expectToWarnAndToThrow(() => {
|
||||
expect(() => {
|
||||
console.error('Hello');
|
||||
}).toErrorDev('Hello');
|
||||
}, 'Received warning unexpectedly does not include a component stack');
|
||||
});
|
||||
|
||||
it('fails if some warnings do not contain a stack', () => {
|
||||
expectToWarnAndToThrow(() => {
|
||||
expect(() => {
|
||||
console.error('Hello\n in div');
|
||||
console.error('Good day\n in div');
|
||||
console.error('Bye');
|
||||
}).toErrorDev(['Hello', 'Good day', 'Bye']);
|
||||
}, 'Received warning unexpectedly does not include a component stack');
|
||||
expectToWarnAndToThrow(() => {
|
||||
expect(() => {
|
||||
console.error('Hello');
|
||||
console.error('Good day\n in div');
|
||||
console.error('Bye\n in div');
|
||||
}).toErrorDev(['Hello', 'Good day', 'Bye']);
|
||||
}, 'Received warning unexpectedly does not include a component stack');
|
||||
expectToWarnAndToThrow(() => {
|
||||
expect(() => {
|
||||
console.error('Hello\n in div');
|
||||
console.error('Good day');
|
||||
console.error('Bye\n in div');
|
||||
}).toErrorDev(['Hello', 'Good day', 'Bye']);
|
||||
}, 'Received warning unexpectedly does not include a component stack');
|
||||
expectToWarnAndToThrow(() => {
|
||||
expect(() => {
|
||||
console.error('Hello');
|
||||
console.error('Good day');
|
||||
console.error('Bye');
|
||||
}).toErrorDev(['Hello', 'Good day', 'Bye']);
|
||||
}, 'Received warning unexpectedly does not include a component stack');
|
||||
});
|
||||
|
||||
it('fails if warning is expected to not have a stack, but does', () => {
|
||||
expectToWarnAndToThrow(() => {
|
||||
expect(() => {
|
||||
console.error('Hello\n in div');
|
||||
}).toErrorDev('Hello', {withoutStack: true});
|
||||
}, 'Received warning unexpectedly includes a component stack');
|
||||
expectToWarnAndToThrow(() => {
|
||||
expect(() => {
|
||||
console.error('Hello\n in div');
|
||||
console.error('Good day');
|
||||
console.error('Bye\n in div');
|
||||
}).toErrorDev(['Hello', 'Good day', 'Bye'], {withoutStack: true});
|
||||
}, 'Received warning unexpectedly includes a component stack');
|
||||
});
|
||||
|
||||
it('fails if expected stack-less warning number does not match the actual one', () => {
|
||||
expectToWarnAndToThrow(() => {
|
||||
expect(() => {
|
||||
console.error('Hello\n in div');
|
||||
console.error('Good day');
|
||||
console.error('Bye\n in div');
|
||||
}).toErrorDev(['Hello', 'Good day', 'Bye'], {withoutStack: 4});
|
||||
}, 'Expected 4 warnings without a component stack but received 1');
|
||||
});
|
||||
|
||||
it('fails if withoutStack is invalid', () => {
|
||||
expectToWarnAndToThrow(() => {
|
||||
expect(() => {
|
||||
console.error('Hi');
|
||||
}).toErrorDev('Hi', {withoutStack: null});
|
||||
}, 'Instead received object');
|
||||
expectToWarnAndToThrow(() => {
|
||||
expect(() => {
|
||||
console.error('Hi');
|
||||
}).toErrorDev('Hi', {withoutStack: {}});
|
||||
}, 'Instead received object');
|
||||
expectToWarnAndToThrow(() => {
|
||||
expect(() => {
|
||||
console.error('Hi');
|
||||
}).toErrorDev('Hi', {withoutStack: 'haha'});
|
||||
}, 'Instead received string');
|
||||
});
|
||||
|
||||
it('fails if the argument number does not match', () => {
|
||||
expectToWarnAndToThrow(() => {
|
||||
expect(() => {
|
||||
console.error('Hi %s', 'Sara', 'extra');
|
||||
}).toErrorDev('Hi', {withoutStack: true});
|
||||
}, 'Received 2 arguments for a message with 1 placeholders');
|
||||
|
||||
expectToWarnAndToThrow(() => {
|
||||
expect(() => {
|
||||
console.error('Hi %s');
|
||||
}).toErrorDev('Hi', {withoutStack: true});
|
||||
}, 'Received 0 arguments for a message with 1 placeholders');
|
||||
});
|
||||
|
||||
it('fails if stack is passed twice', () => {
|
||||
expectToWarnAndToThrow(() => {
|
||||
expect(() => {
|
||||
console.error('Hi %s%s', '\n in div', '\n in div');
|
||||
}).toErrorDev('Hi');
|
||||
}, 'Received more than one component stack for a warning');
|
||||
});
|
||||
|
||||
it('fails if multiple strings are passed without an array wrapper', () => {
|
||||
expectToWarnAndToThrow(() => {
|
||||
expect(() => {
|
||||
console.error('Hi \n in div');
|
||||
}).toErrorDev('Hi', 'Bye');
|
||||
}, 'toErrorDev() second argument, when present, should be an object');
|
||||
expectToWarnAndToThrow(() => {
|
||||
expect(() => {
|
||||
console.error('Hi \n in div');
|
||||
console.error('Bye \n in div');
|
||||
}).toErrorDev('Hi', 'Bye');
|
||||
}, 'toErrorDev() second argument, when present, should be an object');
|
||||
expectToWarnAndToThrow(() => {
|
||||
expect(() => {
|
||||
console.error('Hi \n in div');
|
||||
console.error('Wow \n in div');
|
||||
console.error('Bye \n in div');
|
||||
}).toErrorDev('Hi', 'Bye');
|
||||
}, 'toErrorDev() second argument, when present, should be an object');
|
||||
expectToWarnAndToThrow(() => {
|
||||
expect(() => {
|
||||
console.error('Hi \n in div');
|
||||
console.error('Wow \n in div');
|
||||
console.error('Bye \n in div');
|
||||
}).toErrorDev('Hi', 'Wow', 'Bye');
|
||||
}, 'toErrorDev() second argument, when present, should be an object');
|
||||
});
|
||||
|
||||
it('fails on more than two arguments', () => {
|
||||
expectToWarnAndToThrow(() => {
|
||||
expect(() => {
|
||||
console.error('Hi \n in div');
|
||||
console.error('Wow \n in div');
|
||||
console.error('Bye \n in div');
|
||||
}).toErrorDev('Hi', undefined, 'Bye');
|
||||
}, 'toErrorDev() received more than two arguments.');
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
describe('toWarnDev', () => {
|
||||
it('does not fail if a warning contains a stack', () => {
|
||||
expect(() => {
|
||||
if (__DEV__) {
|
||||
console.warn('Hello\n in div');
|
||||
}
|
||||
}).toWarnDev('Hello');
|
||||
});
|
||||
|
||||
it('does not fail if all warnings contain a stack', () => {
|
||||
expect(() => {
|
||||
if (__DEV__) {
|
||||
console.warn('Hello\n in div');
|
||||
console.warn('Good day\n in div');
|
||||
console.warn('Bye\n in div');
|
||||
}
|
||||
}).toWarnDev(['Hello', 'Good day', 'Bye']);
|
||||
});
|
||||
|
||||
it('does not fail if warnings without stack explicitly opt out', () => {
|
||||
expect(() => {
|
||||
if (__DEV__) {
|
||||
console.warn('Hello');
|
||||
}
|
||||
}).toWarnDev('Hello', {withoutStack: true});
|
||||
expect(() => {
|
||||
if (__DEV__) {
|
||||
console.warn('Hello');
|
||||
console.warn('Good day');
|
||||
console.warn('Bye');
|
||||
}
|
||||
}).toWarnDev(['Hello', 'Good day', 'Bye'], {withoutStack: true});
|
||||
});
|
||||
|
||||
it('does not fail when expected stack-less warning number matches the actual one', () => {
|
||||
expect(() => {
|
||||
if (__DEV__) {
|
||||
console.warn('Hello\n in div');
|
||||
console.warn('Good day');
|
||||
console.warn('Bye\n in div');
|
||||
}
|
||||
}).toWarnDev(['Hello', 'Good day', 'Bye'], {withoutStack: 1});
|
||||
});
|
||||
|
||||
if (__DEV__) {
|
||||
// Helper methods avoids invalid toWarn().toThrow() nesting
|
||||
// See no-to-warn-dev-within-to-throw
|
||||
const expectToWarnAndToThrow = (expectBlock, expectedErrorMessage) => {
|
||||
let caughtError;
|
||||
try {
|
||||
expectBlock();
|
||||
} catch (error) {
|
||||
caughtError = error;
|
||||
}
|
||||
expect(caughtError).toBeDefined();
|
||||
expect(caughtError.message).toContain(expectedErrorMessage);
|
||||
};
|
||||
|
||||
it('fails if a warning does not contain a stack', () => {
|
||||
expectToWarnAndToThrow(() => {
|
||||
expect(() => {
|
||||
console.warn('Hello');
|
||||
}).toWarnDev('Hello');
|
||||
}, 'Received warning unexpectedly does not include a component stack');
|
||||
});
|
||||
|
||||
it('fails if some warnings do not contain a stack', () => {
|
||||
expectToWarnAndToThrow(() => {
|
||||
expect(() => {
|
||||
console.warn('Hello\n in div');
|
||||
console.warn('Good day\n in div');
|
||||
console.warn('Bye');
|
||||
}).toWarnDev(['Hello', 'Good day', 'Bye']);
|
||||
}, 'Received warning unexpectedly does not include a component stack');
|
||||
expectToWarnAndToThrow(() => {
|
||||
expect(() => {
|
||||
console.warn('Hello');
|
||||
console.warn('Good day\n in div');
|
||||
console.warn('Bye\n in div');
|
||||
}).toWarnDev(['Hello', 'Good day', 'Bye']);
|
||||
}, 'Received warning unexpectedly does not include a component stack');
|
||||
expectToWarnAndToThrow(() => {
|
||||
expect(() => {
|
||||
console.warn('Hello\n in div');
|
||||
console.warn('Good day');
|
||||
console.warn('Bye\n in div');
|
||||
}).toWarnDev(['Hello', 'Good day', 'Bye']);
|
||||
}, 'Received warning unexpectedly does not include a component stack');
|
||||
expectToWarnAndToThrow(() => {
|
||||
expect(() => {
|
||||
console.warn('Hello');
|
||||
console.warn('Good day');
|
||||
console.warn('Bye');
|
||||
}).toWarnDev(['Hello', 'Good day', 'Bye']);
|
||||
}, 'Received warning unexpectedly does not include a component stack');
|
||||
});
|
||||
|
||||
it('fails if warning is expected to not have a stack, but does', () => {
|
||||
expectToWarnAndToThrow(() => {
|
||||
expect(() => {
|
||||
console.warn('Hello\n in div');
|
||||
}).toWarnDev('Hello', {withoutStack: true});
|
||||
}, 'Received warning unexpectedly includes a component stack');
|
||||
expectToWarnAndToThrow(() => {
|
||||
expect(() => {
|
||||
console.warn('Hello\n in div');
|
||||
console.warn('Good day');
|
||||
console.warn('Bye\n in div');
|
||||
}).toWarnDev(['Hello', 'Good day', 'Bye'], {
|
||||
withoutStack: true,
|
||||
});
|
||||
}, 'Received warning unexpectedly includes a component stack');
|
||||
});
|
||||
|
||||
it('fails if expected stack-less warning number does not match the actual one', () => {
|
||||
expectToWarnAndToThrow(() => {
|
||||
expect(() => {
|
||||
console.warn('Hello\n in div');
|
||||
console.warn('Good day');
|
||||
console.warn('Bye\n in div');
|
||||
}).toWarnDev(['Hello', 'Good day', 'Bye'], {
|
||||
withoutStack: 4,
|
||||
});
|
||||
}, 'Expected 4 warnings without a component stack but received 1');
|
||||
});
|
||||
|
||||
it('fails if withoutStack is invalid', () => {
|
||||
expectToWarnAndToThrow(() => {
|
||||
expect(() => {
|
||||
console.warn('Hi');
|
||||
}).toWarnDev('Hi', {withoutStack: null});
|
||||
}, 'Instead received object');
|
||||
expectToWarnAndToThrow(() => {
|
||||
expect(() => {
|
||||
console.warn('Hi');
|
||||
}).toWarnDev('Hi', {withoutStack: {}});
|
||||
}, 'Instead received object');
|
||||
expectToWarnAndToThrow(() => {
|
||||
expect(() => {
|
||||
console.warn('Hi');
|
||||
}).toWarnDev('Hi', {withoutStack: 'haha'});
|
||||
}, 'Instead received string');
|
||||
});
|
||||
|
||||
it('fails if the argument number does not match', () => {
|
||||
expectToWarnAndToThrow(() => {
|
||||
expect(() => {
|
||||
console.warn('Hi %s', 'Sara', 'extra');
|
||||
}).toWarnDev('Hi', {withoutStack: true});
|
||||
}, 'Received 2 arguments for a message with 1 placeholders');
|
||||
|
||||
expectToWarnAndToThrow(() => {
|
||||
expect(() => {
|
||||
console.warn('Hi %s');
|
||||
}).toWarnDev('Hi', {withoutStack: true});
|
||||
}, 'Received 0 arguments for a message with 1 placeholders');
|
||||
});
|
||||
|
||||
it('fails if stack is passed twice', () => {
|
||||
expectToWarnAndToThrow(() => {
|
||||
expect(() => {
|
||||
console.warn('Hi %s%s', '\n in div', '\n in div');
|
||||
}).toWarnDev('Hi');
|
||||
}, 'Received more than one component stack for a warning');
|
||||
});
|
||||
|
||||
it('fails if multiple strings are passed without an array wrapper', () => {
|
||||
expectToWarnAndToThrow(() => {
|
||||
expect(() => {
|
||||
console.warn('Hi \n in div');
|
||||
}).toWarnDev('Hi', 'Bye');
|
||||
}, 'toWarnDev() second argument, when present, should be an object');
|
||||
expectToWarnAndToThrow(() => {
|
||||
expect(() => {
|
||||
console.warn('Hi \n in div');
|
||||
console.warn('Bye \n in div');
|
||||
}).toWarnDev('Hi', 'Bye');
|
||||
}, 'toWarnDev() second argument, when present, should be an object');
|
||||
expectToWarnAndToThrow(() => {
|
||||
expect(() => {
|
||||
console.warn('Hi \n in div');
|
||||
console.warn('Wow \n in div');
|
||||
console.warn('Bye \n in div');
|
||||
}).toWarnDev('Hi', 'Bye');
|
||||
}, 'toWarnDev() second argument, when present, should be an object');
|
||||
expectToWarnAndToThrow(() => {
|
||||
expect(() => {
|
||||
console.warn('Hi \n in div');
|
||||
console.warn('Wow \n in div');
|
||||
console.warn('Bye \n in div');
|
||||
}).toWarnDev('Hi', 'Wow', 'Bye');
|
||||
}, 'toWarnDev() second argument, when present, should be an object');
|
||||
});
|
||||
|
||||
it('fails on more than two arguments', () => {
|
||||
expectToWarnAndToThrow(() => {
|
||||
expect(() => {
|
||||
console.warn('Hi \n in div');
|
||||
console.warn('Wow \n in div');
|
||||
console.warn('Bye \n in div');
|
||||
}).toWarnDev('Hi', undefined, 'Bye');
|
||||
}, 'toWarnDev() received more than two arguments.');
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
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']);
|
||||
});
|
||||
});
|
||||
@@ -1,348 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const {diff: jestDiff} = require('jest-diff');
|
||||
const util = require('util');
|
||||
const shouldIgnoreConsoleError = require('internal-test-utils/shouldIgnoreConsoleError');
|
||||
|
||||
function normalizeCodeLocInfo(str) {
|
||||
if (typeof str !== 'string') {
|
||||
return str;
|
||||
}
|
||||
// This special case exists only for the special source location in
|
||||
// ReactElementValidator. That will go away if we remove source locations.
|
||||
str = str.replace(/Check your code at .+?:\d+/g, 'Check your code at **');
|
||||
// V8 format:
|
||||
// at Component (/path/filename.js:123:45)
|
||||
// React format:
|
||||
// in Component (at filename.js:123)
|
||||
return str.replace(/\n +(?:at|in) ([\S]+)[^\n]*/g, function (m, name) {
|
||||
if (name.endsWith('.render')) {
|
||||
// Class components will have the `render` method as part of their stack trace.
|
||||
// We strip that out in our normalization to make it look more like component stacks.
|
||||
name = name.slice(0, name.length - 7);
|
||||
}
|
||||
return '\n in ' + name + ' (at **)';
|
||||
});
|
||||
}
|
||||
|
||||
const createMatcherFor = (consoleMethod, matcherName) =>
|
||||
function matcher(callback, expectedMessages, options = {}) {
|
||||
if (__DEV__) {
|
||||
// Warn about incorrect usage of matcher.
|
||||
if (typeof expectedMessages === 'string') {
|
||||
expectedMessages = [expectedMessages];
|
||||
} else if (!Array.isArray(expectedMessages)) {
|
||||
throw Error(
|
||||
`${matcherName}() requires a parameter of type string or an array of strings ` +
|
||||
`but was given ${typeof expectedMessages}.`
|
||||
);
|
||||
}
|
||||
if (
|
||||
options != null &&
|
||||
(typeof options !== 'object' || Array.isArray(options))
|
||||
) {
|
||||
throw new Error(
|
||||
`${matcherName}() second argument, when present, should be an object. ` +
|
||||
'Did you forget to wrap the messages into an array?'
|
||||
);
|
||||
}
|
||||
if (arguments.length > 3) {
|
||||
// `matcher` comes from Jest, so it's more than 2 in practice
|
||||
throw new Error(
|
||||
`${matcherName}() received more than two arguments. ` +
|
||||
'Did you forget to wrap the messages into an array?'
|
||||
);
|
||||
}
|
||||
|
||||
const withoutStack = options.withoutStack;
|
||||
const logAllErrors = options.logAllErrors;
|
||||
const warningsWithoutComponentStack = [];
|
||||
const warningsWithComponentStack = [];
|
||||
const unexpectedWarnings = [];
|
||||
|
||||
let lastWarningWithMismatchingFormat = null;
|
||||
let lastWarningWithExtraComponentStack = null;
|
||||
|
||||
// Catch errors thrown by the callback,
|
||||
// But only rethrow them if all test expectations have been satisfied.
|
||||
// Otherwise an Error in the callback can mask a failed expectation,
|
||||
// and result in a test that passes when it shouldn't.
|
||||
let caughtError;
|
||||
|
||||
const isLikelyAComponentStack = message =>
|
||||
typeof message === 'string' &&
|
||||
(message.includes('\n in ') || message.includes('\n at '));
|
||||
|
||||
const consoleSpy = (format, ...args) => {
|
||||
// Ignore uncaught errors reported by jsdom
|
||||
// and React addendums because they're too noisy.
|
||||
if (!logAllErrors && shouldIgnoreConsoleError(format, args)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Append Component Stacks. Simulates a framework or DevTools appending them.
|
||||
if (
|
||||
typeof format === 'string' &&
|
||||
(consoleMethod === 'error' || consoleMethod === 'warn')
|
||||
) {
|
||||
const React = require('react');
|
||||
if (React.captureOwnerStack) {
|
||||
// enableOwnerStacks enabled. When it's always on, we can assume this case.
|
||||
const stack = React.captureOwnerStack();
|
||||
if (stack) {
|
||||
format += '%s';
|
||||
args.push(stack);
|
||||
}
|
||||
} else {
|
||||
// Otherwise we have to use internals to emulate parent stacks.
|
||||
const ReactSharedInternals =
|
||||
React.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE ||
|
||||
React.__SERVER_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE;
|
||||
if (ReactSharedInternals && ReactSharedInternals.getCurrentStack) {
|
||||
const stack = ReactSharedInternals.getCurrentStack();
|
||||
if (stack !== '') {
|
||||
format += '%s';
|
||||
args.push(stack);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const message = util.format(format, ...args);
|
||||
const normalizedMessage = normalizeCodeLocInfo(message);
|
||||
|
||||
// Remember if the number of %s interpolations
|
||||
// doesn't match the number of arguments.
|
||||
// We'll fail the test if it happens.
|
||||
let argIndex = 0;
|
||||
// console.* could have been called with a non-string e.g. `console.error(new Error())`
|
||||
String(format).replace(/%s/g, () => argIndex++);
|
||||
if (argIndex !== args.length) {
|
||||
lastWarningWithMismatchingFormat = {
|
||||
format,
|
||||
args,
|
||||
expectedArgCount: argIndex,
|
||||
};
|
||||
}
|
||||
|
||||
// Protect against accidentally passing a component stack
|
||||
// to warning() which already injects the component stack.
|
||||
if (
|
||||
args.length >= 2 &&
|
||||
isLikelyAComponentStack(args[args.length - 1]) &&
|
||||
isLikelyAComponentStack(args[args.length - 2])
|
||||
) {
|
||||
lastWarningWithExtraComponentStack = {
|
||||
format,
|
||||
};
|
||||
}
|
||||
|
||||
for (let index = 0; index < expectedMessages.length; index++) {
|
||||
const expectedMessage = expectedMessages[index];
|
||||
if (
|
||||
normalizedMessage === expectedMessage ||
|
||||
normalizedMessage.includes(expectedMessage)
|
||||
) {
|
||||
if (isLikelyAComponentStack(normalizedMessage)) {
|
||||
warningsWithComponentStack.push(normalizedMessage);
|
||||
} else {
|
||||
warningsWithoutComponentStack.push(normalizedMessage);
|
||||
}
|
||||
expectedMessages.splice(index, 1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let errorMessage;
|
||||
if (expectedMessages.length === 0) {
|
||||
errorMessage =
|
||||
'Unexpected warning recorded: ' +
|
||||
this.utils.printReceived(normalizedMessage);
|
||||
} else if (expectedMessages.length === 1) {
|
||||
errorMessage =
|
||||
'Unexpected warning recorded: ' +
|
||||
jestDiff(expectedMessages[0], normalizedMessage);
|
||||
} else {
|
||||
errorMessage =
|
||||
'Unexpected warning recorded: ' +
|
||||
jestDiff(expectedMessages, [normalizedMessage]);
|
||||
}
|
||||
|
||||
// Record the call stack for unexpected warnings.
|
||||
// We don't throw an Error here though,
|
||||
// Because it might be suppressed by ReactFiberScheduler.
|
||||
unexpectedWarnings.push(new Error(errorMessage));
|
||||
};
|
||||
|
||||
// TODO Decide whether we need to support nested toWarn* expectations.
|
||||
// If we don't need it, add a check here to see if this is already our spy,
|
||||
// And throw an error.
|
||||
const originalMethod = console[consoleMethod];
|
||||
|
||||
// Avoid using Jest's built-in spy since it can't be removed.
|
||||
console[consoleMethod] = consoleSpy;
|
||||
|
||||
const onFinally = () => {
|
||||
// Restore the unspied method so that unexpected errors fail tests.
|
||||
console[consoleMethod] = originalMethod;
|
||||
|
||||
// Any unexpected Errors thrown by the callback should fail the test.
|
||||
// This should take precedence since unexpected errors could block warnings.
|
||||
if (caughtError) {
|
||||
throw caughtError;
|
||||
}
|
||||
|
||||
// Any unexpected warnings should be treated as a failure.
|
||||
if (unexpectedWarnings.length > 0) {
|
||||
return {
|
||||
message: () => unexpectedWarnings[0].stack,
|
||||
pass: false,
|
||||
};
|
||||
}
|
||||
|
||||
// Any remaining messages indicate a failed expectations.
|
||||
if (expectedMessages.length > 0) {
|
||||
return {
|
||||
message: () =>
|
||||
`Expected warning was not recorded:\n ${this.utils.printReceived(
|
||||
expectedMessages[0]
|
||||
)}`,
|
||||
pass: false,
|
||||
};
|
||||
}
|
||||
|
||||
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 {
|
||||
message: () =>
|
||||
`Expected ${withoutStack} warnings without a component stack but received ${warningsWithoutComponentStack.length}:\n` +
|
||||
warningsWithoutComponentStack.map(warning =>
|
||||
this.utils.printReceived(warning)
|
||||
),
|
||||
pass: false,
|
||||
};
|
||||
}
|
||||
} else if (withoutStack === true) {
|
||||
// We're expecting that all warnings won't have the stack.
|
||||
// If some warnings have it, it's an error.
|
||||
if (warningsWithComponentStack.length > 0) {
|
||||
return {
|
||||
message: () =>
|
||||
`Received warning unexpectedly includes a component stack:\n ${this.utils.printReceived(
|
||||
warningsWithComponentStack[0]
|
||||
)}\nIf this warning intentionally includes the component stack, remove ` +
|
||||
`{withoutStack: true} from the ${matcherName}() call. If you have a mix of ` +
|
||||
`warnings with and without stack in one ${matcherName}() call, pass ` +
|
||||
`{withoutStack: N} where N is the number of warnings without stacks.`,
|
||||
pass: false,
|
||||
};
|
||||
}
|
||||
} else if (withoutStack === false || withoutStack === undefined) {
|
||||
// We're expecting that all warnings *do* have the stack (default).
|
||||
// If some warnings don't have it, it's an error.
|
||||
if (warningsWithoutComponentStack.length > 0) {
|
||||
return {
|
||||
message: () =>
|
||||
`Received warning unexpectedly does not include a component stack:\n ${this.utils.printReceived(
|
||||
warningsWithoutComponentStack[0]
|
||||
)}\nIf this warning intentionally omits the component stack, add ` +
|
||||
`{withoutStack: true} to the ${matcherName} call.`,
|
||||
pass: false,
|
||||
};
|
||||
}
|
||||
} else {
|
||||
throw Error(
|
||||
`The second argument for ${matcherName}(), when specified, must be an object. It may have a ` +
|
||||
`property called "withoutStack" whose value may be undefined, boolean, or a number. ` +
|
||||
`Instead received ${typeof withoutStack}.`
|
||||
);
|
||||
}
|
||||
|
||||
if (lastWarningWithMismatchingFormat !== null) {
|
||||
return {
|
||||
message: () =>
|
||||
`Received ${
|
||||
lastWarningWithMismatchingFormat.args.length
|
||||
} arguments for a message with ${
|
||||
lastWarningWithMismatchingFormat.expectedArgCount
|
||||
} placeholders:\n ${this.utils.printReceived(
|
||||
lastWarningWithMismatchingFormat.format
|
||||
)}`,
|
||||
pass: false,
|
||||
};
|
||||
}
|
||||
|
||||
if (lastWarningWithExtraComponentStack !== null) {
|
||||
return {
|
||||
message: () =>
|
||||
`Received more than one component stack for a warning:\n ${this.utils.printReceived(
|
||||
lastWarningWithExtraComponentStack.format
|
||||
)}\nDid you accidentally pass a stack to warning() as the last argument? ` +
|
||||
`Don't forget warning() already injects the component stack automatically.`,
|
||||
pass: false,
|
||||
};
|
||||
}
|
||||
|
||||
return {pass: true};
|
||||
};
|
||||
|
||||
let returnPromise = null;
|
||||
try {
|
||||
const result = callback();
|
||||
|
||||
if (
|
||||
typeof result === 'object' &&
|
||||
result !== null &&
|
||||
typeof result.then === 'function'
|
||||
) {
|
||||
// `act` returns a thenable that can't be chained.
|
||||
// Once `act(async () => {}).then(() => {}).then(() => {})` works
|
||||
// we can just return `result.then(onFinally, error => ...)`
|
||||
returnPromise = new Promise((resolve, reject) => {
|
||||
result
|
||||
.then(
|
||||
() => {
|
||||
resolve(onFinally());
|
||||
},
|
||||
error => {
|
||||
caughtError = error;
|
||||
return resolve(onFinally());
|
||||
}
|
||||
)
|
||||
// In case onFinally throws we need to reject from this matcher
|
||||
.catch(error => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
caughtError = error;
|
||||
} finally {
|
||||
return returnPromise === null ? onFinally() : returnPromise;
|
||||
}
|
||||
} else {
|
||||
// Any uncaught errors or warnings should fail tests in production mode.
|
||||
const result = callback();
|
||||
|
||||
if (
|
||||
typeof result === 'object' &&
|
||||
result !== null &&
|
||||
typeof result.then === 'function'
|
||||
) {
|
||||
return result.then(() => {
|
||||
return {pass: true};
|
||||
});
|
||||
} else {
|
||||
return {pass: true};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
toWarnDev: createMatcherFor('warn', 'toWarnDev'),
|
||||
toErrorDev: createMatcherFor('error', 'toErrorDev'),
|
||||
toLogDev: createMatcherFor('log', 'toLogDev'),
|
||||
};
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
const {getTestFlags} = require('./TestFlags');
|
||||
const {
|
||||
flushAllUnexpectedConsoleCalls,
|
||||
assertConsoleLogsCleared,
|
||||
resetAllUnexpectedConsoleCalls,
|
||||
patchConsoleMethods,
|
||||
} = require('internal-test-utils/consoleMock');
|
||||
@@ -44,7 +44,6 @@ if (process.env.REACT_CLASS_EQUIVALENCE_TEST) {
|
||||
expect.extend({
|
||||
...require('./matchers/reactTestMatchers'),
|
||||
...require('./matchers/toThrow'),
|
||||
...require('./matchers/toWarnDev'),
|
||||
});
|
||||
|
||||
// We have a Babel transform that inserts guards against infinite loops.
|
||||
@@ -66,7 +65,19 @@ if (process.env.REACT_CLASS_EQUIVALENCE_TEST) {
|
||||
// Patch the console to assert that all console error/warn/log calls assert.
|
||||
patchConsoleMethods({includeLog: !!process.env.CI});
|
||||
beforeEach(resetAllUnexpectedConsoleCalls);
|
||||
afterEach(flushAllUnexpectedConsoleCalls);
|
||||
afterEach(assertConsoleLogsCleared);
|
||||
|
||||
// TODO: enable this check so we don't forget to reset spyOnX mocks.
|
||||
// afterEach(() => {
|
||||
// if (
|
||||
// console[methodName] !== mockMethod &&
|
||||
// !jest.isMockFunction(console[methodName])
|
||||
// ) {
|
||||
// throw new Error(
|
||||
// `Test did not tear down console.${methodName} mock properly.`
|
||||
// );
|
||||
// }
|
||||
// });
|
||||
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
// In production, we strip error messages and turn them into codes.
|
||||
@@ -187,7 +198,7 @@ if (process.env.REACT_CLASS_EQUIVALENCE_TEST) {
|
||||
// Flush unexpected console calls inside the test itself, instead of in
|
||||
// `afterEach` like we normally do. `afterEach` is too late because if it
|
||||
// throws, we won't have captured it.
|
||||
flushAllUnexpectedConsoleCalls();
|
||||
assertConsoleLogsCleared();
|
||||
} catch (testError) {
|
||||
didError = true;
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
const {
|
||||
patchConsoleMethods,
|
||||
resetAllUnexpectedConsoleCalls,
|
||||
flushAllUnexpectedConsoleCalls,
|
||||
assertConsoleLogsCleared,
|
||||
} = require('internal-test-utils/consoleMock');
|
||||
const spyOn = jest.spyOn;
|
||||
|
||||
@@ -44,10 +44,21 @@ global.spyOnProd = function (...args) {
|
||||
// Patch the console to assert that all console error/warn/log calls assert.
|
||||
patchConsoleMethods({includeLog: !!process.env.CI});
|
||||
beforeEach(resetAllUnexpectedConsoleCalls);
|
||||
afterEach(flushAllUnexpectedConsoleCalls);
|
||||
afterEach(assertConsoleLogsCleared);
|
||||
|
||||
// TODO: enable this check so we don't forget to reset spyOnX mocks.
|
||||
// afterEach(() => {
|
||||
// if (
|
||||
// console[methodName] !== mockMethod &&
|
||||
// !jest.isMockFunction(console[methodName])
|
||||
// ) {
|
||||
// throw new Error(
|
||||
// `Test did not tear down console.${methodName} mock properly.`
|
||||
// );
|
||||
// }
|
||||
// });
|
||||
|
||||
expect.extend({
|
||||
...require('../matchers/reactTestMatchers'),
|
||||
...require('../matchers/toThrow'),
|
||||
...require('../matchers/toWarnDev'),
|
||||
});
|
||||
|
||||
84
scripts/jest/typescript/jest.d.ts
vendored
84
scripts/jest/typescript/jest.d.ts
vendored
@@ -8,7 +8,7 @@ declare function describe(name: string, fn: any): void;
|
||||
declare const it: {
|
||||
(name: string, fn: any): void;
|
||||
only: (name: string, fn: any) => void;
|
||||
}
|
||||
};
|
||||
declare function expect(val: any): Expect;
|
||||
declare const jest: Jest;
|
||||
declare function pit(name: string, fn: any): void;
|
||||
@@ -19,55 +19,53 @@ declare function xdescribe(name: string, fn: any): void;
|
||||
declare function xit(name: string, fn: any): void;
|
||||
|
||||
interface Expect {
|
||||
not: Expect
|
||||
toThrow(message?: string): void
|
||||
toThrowError(message?: string): void
|
||||
toErrorDev(message?: string | Array<string>, options?: any): void
|
||||
toWarnDev(message?: string | Array<string>, options?: any): void
|
||||
toBe(value: any): void
|
||||
toEqual(value: any): void
|
||||
toBeFalsy(): void
|
||||
toBeTruthy(): void
|
||||
toBeNull(): void
|
||||
toBeUndefined(): void
|
||||
toBeDefined(): void
|
||||
toMatch(regexp: RegExp): void
|
||||
toContain(string: string): void
|
||||
toBeCloseTo(number: number, delta: number): void
|
||||
toBeGreaterThan(number: number): void
|
||||
toBeLessThan(number: number): void
|
||||
toBeCalled(): void
|
||||
toBeCalledWith(...arguments): void
|
||||
lastCalledWith(...arguments): void
|
||||
not: Expect;
|
||||
toThrow(message?: string): void;
|
||||
toThrowError(message?: string): void;
|
||||
toBe(value: any): void;
|
||||
toEqual(value: any): void;
|
||||
toBeFalsy(): void;
|
||||
toBeTruthy(): void;
|
||||
toBeNull(): void;
|
||||
toBeUndefined(): void;
|
||||
toBeDefined(): void;
|
||||
toMatch(regexp: RegExp): void;
|
||||
toContain(string: string): void;
|
||||
toBeCloseTo(number: number, delta: number): void;
|
||||
toBeGreaterThan(number: number): void;
|
||||
toBeLessThan(number: number): void;
|
||||
toBeCalled(): void;
|
||||
toBeCalledWith(...arguments): void;
|
||||
lastCalledWith(...arguments): void;
|
||||
}
|
||||
|
||||
interface Jest {
|
||||
autoMockOff(): void
|
||||
autoMockOn(): void
|
||||
clearAllTimers(): void
|
||||
dontMock(moduleName: string): void
|
||||
genMockFromModule(moduleObj: Object): Object
|
||||
genMockFunction(): MockFunction
|
||||
genMockFn(): MockFunction
|
||||
mock(moduleName: string): void
|
||||
runAllTicks(): void
|
||||
runAllTimers(): void
|
||||
runOnlyPendingTimers(): void
|
||||
setMock(moduleName: string, moduleExports: Object): void
|
||||
autoMockOff(): void;
|
||||
autoMockOn(): void;
|
||||
clearAllTimers(): void;
|
||||
dontMock(moduleName: string): void;
|
||||
genMockFromModule(moduleObj: Object): Object;
|
||||
genMockFunction(): MockFunction;
|
||||
genMockFn(): MockFunction;
|
||||
mock(moduleName: string): void;
|
||||
runAllTicks(): void;
|
||||
runAllTimers(): void;
|
||||
runOnlyPendingTimers(): void;
|
||||
setMock(moduleName: string, moduleExports: Object): void;
|
||||
}
|
||||
|
||||
interface MockFunction {
|
||||
(...arguments): any
|
||||
(...arguments): any;
|
||||
mock: {
|
||||
calls: Array<Array<any>>
|
||||
instances: Array<Object>
|
||||
}
|
||||
mockClear(): void
|
||||
mockImplementation(fn: Function): MockFunction
|
||||
mockImpl(fn: Function): MockFunction
|
||||
mockReturnThis(): MockFunction
|
||||
mockReturnValue(value: any): MockFunction
|
||||
mockReturnValueOnce(value: any): MockFunction
|
||||
calls: Array<Array<any>>;
|
||||
instances: Array<Object>;
|
||||
};
|
||||
mockClear(): void;
|
||||
mockImplementation(fn: Function): MockFunction;
|
||||
mockImpl(fn: Function): MockFunction;
|
||||
mockReturnThis(): MockFunction;
|
||||
mockReturnValue(value: any): MockFunction;
|
||||
mockReturnValueOnce(value: any): MockFunction;
|
||||
}
|
||||
|
||||
declare const check: any;
|
||||
|
||||
Reference in New Issue
Block a user