diff --git a/package.json b/package.json
index 4cba00fd..d2dd6a97 100644
--- a/package.json
+++ b/package.json
@@ -51,7 +51,9 @@
},
"devDependencies": {
"@testing-library/jest-dom": "^5.11.6",
+ "chalk": "^4.1.2",
"dotenv-cli": "^4.0.0",
+ "jest-diff": "^27.5.1",
"kcd-scripts": "^11.1.0",
"npm-run-all": "^4.1.5",
"react": "^18.0.0",
diff --git a/src/__tests__/cleanup.js b/src/__tests__/cleanup.js
index 0dcbac12..4517c098 100644
--- a/src/__tests__/cleanup.js
+++ b/src/__tests__/cleanup.js
@@ -51,6 +51,7 @@ describe('fake timers and missing act warnings', () => {
})
afterEach(() => {
+ jest.restoreAllMocks()
jest.useRealTimers()
})
diff --git a/src/__tests__/new-act.js b/src/__tests__/new-act.js
index 05f9d45a..4909d4a6 100644
--- a/src/__tests__/new-act.js
+++ b/src/__tests__/new-act.js
@@ -13,7 +13,7 @@ beforeEach(() => {
})
afterEach(() => {
- console.error.mockRestore()
+ jest.restoreAllMocks()
})
test('async act works when it does not exist (older versions of react)', async () => {
diff --git a/src/__tests__/render.js b/src/__tests__/render.js
index 88e2b98d..46925f49 100644
--- a/src/__tests__/render.js
+++ b/src/__tests__/render.js
@@ -3,12 +3,6 @@ import ReactDOM from 'react-dom'
import ReactDOMServer from 'react-dom/server'
import {fireEvent, render, screen} from '../'
-afterEach(() => {
- if (console.error.mockRestore !== undefined) {
- console.error.mockRestore()
- }
-})
-
test('renders div into document', () => {
const ref = React.createRef()
const {container} = render(
)
@@ -126,7 +120,6 @@ test('can be called multiple times on the same container', () => {
})
test('hydrate will make the UI interactive', () => {
- jest.spyOn(console, 'error').mockImplementation(() => {})
function App() {
const [clicked, handleClick] = React.useReducer(n => n + 1, 0)
@@ -145,8 +138,6 @@ test('hydrate will make the UI interactive', () => {
render(ui, {container, hydrate: true})
- expect(console.error).not.toHaveBeenCalled()
-
fireEvent.click(container.querySelector('button'))
expect(container).toHaveTextContent('clicked:1')
@@ -172,26 +163,26 @@ test('hydrate can have a wrapper', () => {
})
test('legacyRoot uses legacy ReactDOM.render', () => {
- jest.spyOn(console, 'error').mockImplementation(() => {})
- render(, {legacyRoot: true})
-
- expect(console.error).toHaveBeenCalledTimes(1)
- expect(console.error).toHaveBeenNthCalledWith(
- 1,
- "Warning: ReactDOM.render is no longer supported in React 18. Use createRoot instead. Until you switch to the new API, your app will behave as if it's running React 17. Learn more: https://reactjs.org/link/switch-to-createroot",
+ expect(() => {
+ render(, {legacyRoot: true})
+ }).toErrorDev(
+ [
+ "Warning: ReactDOM.render is no longer supported in React 18. Use createRoot instead. Until you switch to the new API, your app will behave as if it's running React 17. Learn more: https://reactjs.org/link/switch-to-createroot",
+ ],
+ {withoutStack: true},
)
})
test('legacyRoot uses legacy ReactDOM.hydrate', () => {
- jest.spyOn(console, 'error').mockImplementation(() => {})
const ui =
const container = document.createElement('div')
container.innerHTML = ReactDOMServer.renderToString(ui)
- render(ui, {container, hydrate: true, legacyRoot: true})
-
- expect(console.error).toHaveBeenCalledTimes(1)
- expect(console.error).toHaveBeenNthCalledWith(
- 1,
- "Warning: ReactDOM.hydrate is no longer supported in React 18. Use hydrateRoot instead. Until you switch to the new API, your app will behave as if it's running React 17. Learn more: https://reactjs.org/link/switch-to-createroot",
+ expect(() => {
+ render(ui, {container, hydrate: true, legacyRoot: true})
+ }).toErrorDev(
+ [
+ "Warning: ReactDOM.hydrate is no longer supported in React 18. Use hydrateRoot instead. Until you switch to the new API, your app will behave as if it's running React 17. Learn more: https://reactjs.org/link/switch-to-createroot",
+ ],
+ {withoutStack: true},
)
})
diff --git a/src/__tests__/renderHook.js b/src/__tests__/renderHook.js
index 92bc47ed..f6b7a343 100644
--- a/src/__tests__/renderHook.js
+++ b/src/__tests__/renderHook.js
@@ -62,27 +62,26 @@ test('allows wrapper components', async () => {
})
test('legacyRoot uses legacy ReactDOM.render', () => {
- jest.spyOn(console, 'error').mockImplementation(() => {})
-
const Context = React.createContext('default')
function Wrapper({children}) {
return {children}
}
- const {result} = renderHook(
- () => {
- return React.useContext(Context)
- },
- {
- wrapper: Wrapper,
- legacyRoot: true,
- },
+ let result
+ expect(() => {
+ result = renderHook(
+ () => {
+ return React.useContext(Context)
+ },
+ {
+ wrapper: Wrapper,
+ legacyRoot: true,
+ },
+ ).result
+ }).toErrorDev(
+ [
+ "Warning: ReactDOM.render is no longer supported in React 18. Use createRoot instead. Until you switch to the new API, your app will behave as if it's running React 17. Learn more: https://reactjs.org/link/switch-to-createroot",
+ ],
+ {withoutStack: true},
)
-
expect(result.current).toEqual('provided')
-
- expect(console.error).toHaveBeenCalledTimes(1)
- expect(console.error).toHaveBeenNthCalledWith(
- 1,
- "Warning: ReactDOM.render is no longer supported in React 18. Use createRoot instead. Until you switch to the new API, your app will behave as if it's running React 17. Learn more: https://reactjs.org/link/switch-to-createroot",
- )
})
diff --git a/tests/failOnUnexpectedConsoleCalls.js b/tests/failOnUnexpectedConsoleCalls.js
new file mode 100644
index 00000000..83e0c641
--- /dev/null
+++ b/tests/failOnUnexpectedConsoleCalls.js
@@ -0,0 +1,129 @@
+// Fork of https://github.com/facebook/react/blob/513417d6951fa3ff5729302b7990b84604b11afa/scripts/jest/setupTests.js#L71-L161
+/**
+MIT License
+
+Copyright (c) Facebook, Inc. and its affiliates.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+ */
+/* eslint-disable prefer-template */
+/* eslint-disable func-names */
+const util = require('util')
+const chalk = require('chalk')
+const shouldIgnoreConsoleError = require('./shouldIgnoreConsoleError')
+
+const patchConsoleMethod = (methodName, unexpectedConsoleCallStacks) => {
+ const newMethod = function (format, ...args) {
+ // Ignore uncaught errors reported by jsdom
+ // and React addendums because they're too noisy.
+ if (methodName === 'error' && shouldIgnoreConsoleError(format, args)) {
+ 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.substr(stack.indexOf('\n') + 1),
+ util.format(format, ...args),
+ ])
+ }
+
+ console[methodName] = newMethod
+
+ return newMethod
+}
+
+const isSpy = spy =>
+ (spy.calls && typeof spy.calls.count === 'function') ||
+ spy._isMockFunction === true
+
+const flushUnexpectedConsoleCalls = (
+ mockMethod,
+ methodName,
+ expectedMatcher,
+ unexpectedConsoleCallStacks,
+) => {
+ if (console[methodName] !== mockMethod && !isSpy(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 message =
+ `Expected test not to call ${chalk.bold(
+ `console.${methodName}()`,
+ )}.\n\n` +
+ 'If the warning 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.`
+
+ throw new Error(`${message}\n\n${messages.join('\n\n')}`)
+ }
+}
+
+const unexpectedErrorCallStacks = []
+const unexpectedWarnCallStacks = []
+
+const errorMethod = patchConsoleMethod('error', unexpectedErrorCallStacks)
+const warnMethod = patchConsoleMethod('warn', unexpectedWarnCallStacks)
+
+const flushAllUnexpectedConsoleCalls = () => {
+ flushUnexpectedConsoleCalls(
+ errorMethod,
+ 'error',
+ 'toErrorDev',
+ unexpectedErrorCallStacks,
+ )
+ flushUnexpectedConsoleCalls(
+ warnMethod,
+ 'warn',
+ 'toWarnDev',
+ unexpectedWarnCallStacks,
+ )
+ unexpectedErrorCallStacks.length = 0
+ unexpectedWarnCallStacks.length = 0
+}
+
+const resetAllUnexpectedConsoleCalls = () => {
+ unexpectedErrorCallStacks.length = 0
+ unexpectedWarnCallStacks.length = 0
+}
+
+expect.extend({
+ ...require('./toWarnDev'),
+})
+
+beforeEach(resetAllUnexpectedConsoleCalls)
+afterEach(flushAllUnexpectedConsoleCalls)
diff --git a/tests/setup-env.js b/tests/setup-env.js
index 264828a9..a4ddfa17 100644
--- a/tests/setup-env.js
+++ b/tests/setup-env.js
@@ -1 +1,2 @@
import '@testing-library/jest-dom/extend-expect'
+import './failOnUnexpectedConsoleCalls'
diff --git a/tests/shouldIgnoreConsoleError.js b/tests/shouldIgnoreConsoleError.js
new file mode 100644
index 00000000..75528267
--- /dev/null
+++ b/tests/shouldIgnoreConsoleError.js
@@ -0,0 +1,43 @@
+// Fork of https://github.com/facebook/react/blob/513417d6951fa3ff5729302b7990b84604b11afa/scripts/jest/shouldIgnoreConsoleError.js
+/**
+MIT License
+
+Copyright (c) Facebook, Inc. and its affiliates.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+ */
+
+module.exports = function shouldIgnoreConsoleError(format) {
+ if (process.env.NODE_ENV !== 'production') {
+ if (typeof format === 'string') {
+ if (format.indexOf('Error: Uncaught [') === 0) {
+ // This looks like an uncaught error from invokeGuardedCallback() wrapper
+ // in development that is reported by jsdom. Ignore because it's noisy.
+ return true
+ }
+ if (format.indexOf('The above error occurred') === 0) {
+ // This looks like an error addendum from ReactFiberErrorLogger.
+ // Ignore it too.
+ return true
+ }
+ }
+ }
+ // Looks legit
+ return false
+}
diff --git a/tests/toWarnDev.js b/tests/toWarnDev.js
new file mode 100644
index 00000000..ac5f1b19
--- /dev/null
+++ b/tests/toWarnDev.js
@@ -0,0 +1,303 @@
+// Fork of https://github.com/facebook/react/blob/513417d6951fa3ff5729302b7990b84604b11afa/scripts/jest/matchers/toWarnDev.js
+/**
+MIT License
+
+Copyright (c) Facebook, Inc. and its affiliates.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+ */
+/* eslint-disable no-unsafe-finally */
+/* eslint-disable no-negated-condition */
+/* eslint-disable @babel/no-invalid-this */
+/* eslint-disable prefer-template */
+/* eslint-disable func-names */
+/* eslint-disable complexity */
+const util = require('util')
+const jestDiff = require('jest-diff').default
+const shouldIgnoreConsoleError = require('./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)
+ // eslint-disable-next-line prefer-arrow-callback
+ return str.replace(/\n +(?:at|in) ([\S]+)[^\n]*/g, function (m, name) {
+ return '\n in ' + name + ' (at **)'
+ })
+}
+
+const createMatcherFor = (consoleMethod, matcherName) =>
+ function matcher(callback, expectedMessages, options = {}) {
+ if (process.env.NODE_ENV !== 'production') {
+ // 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 &&
+ consoleMethod === 'error' &&
+ shouldIgnoreConsoleError(format, args)
+ ) {
+ return
+ }
+
+ 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
+ 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
+
+ try {
+ callback()
+ } catch (error) {
+ caughtError = error
+ } finally {
+ // 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 (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}
+ }
+ } else {
+ // Any uncaught errors or warnings should fail tests in production mode.
+ callback()
+
+ return {pass: true}
+ }
+ }
+
+module.exports = {
+ toWarnDev: createMatcherFor('warn', 'toWarnDev'),
+ toErrorDev: createMatcherFor('error', 'toErrorDev'),
+}