diff --git a/src/__tests__/cleanup.js b/src/__tests__/cleanup.js
index c0f1676d..4b67814a 100644
--- a/src/__tests__/cleanup.js
+++ b/src/__tests__/cleanup.js
@@ -1,7 +1,7 @@
import React from 'react'
import {render, cleanup} from '../'
-test('cleans up the document', async () => {
+test('cleans up the document', () => {
const spy = jest.fn()
const divId = 'my-div'
@@ -17,17 +17,17 @@ test('cleans up the document', async () => {
}
render()
- await cleanup()
+ cleanup()
expect(document.body).toBeEmptyDOMElement()
expect(spy).toHaveBeenCalledTimes(1)
})
-test('cleanup does not error when an element is not a child', async () => {
+test('cleanup does not error when an element is not a child', () => {
render(
, {container: document.createElement('div')})
- await cleanup()
+ cleanup()
})
-test('cleanup runs effect cleanup functions', async () => {
+test('cleanup runs effect cleanup functions', () => {
const spy = jest.fn()
const Test = () => {
@@ -37,6 +37,87 @@ test('cleanup runs effect cleanup functions', async () => {
}
render()
- await cleanup()
+ cleanup()
expect(spy).toHaveBeenCalledTimes(1)
})
+
+describe('fake timers and missing act warnings', () => {
+ beforeEach(() => {
+ jest.resetAllMocks()
+ jest.spyOn(console, 'error').mockImplementation(() => {
+ // assert messages explicitly
+ })
+ jest.useFakeTimers()
+ })
+
+ afterEach(() => {
+ jest.useRealTimers()
+ })
+
+ test('cleanup does not flush immediates', () => {
+ const microTaskSpy = jest.fn()
+ function Test() {
+ const counter = 1
+ const [, setDeferredCounter] = React.useState(null)
+ React.useEffect(() => {
+ let cancelled = false
+ setImmediate(() => {
+ microTaskSpy()
+ if (!cancelled) {
+ setDeferredCounter(counter)
+ }
+ })
+
+ return () => {
+ cancelled = true
+ }
+ }, [counter])
+
+ return null
+ }
+ render()
+
+ cleanup()
+
+ expect(microTaskSpy).toHaveBeenCalledTimes(0)
+ // console.error is mocked
+ // eslint-disable-next-line no-console
+ expect(console.error).toHaveBeenCalledTimes(0)
+ })
+
+ test('cleanup does not swallow missing act warnings', () => {
+ const deferredStateUpdateSpy = jest.fn()
+ function Test() {
+ const counter = 1
+ const [, setDeferredCounter] = React.useState(null)
+ React.useEffect(() => {
+ let cancelled = false
+ setImmediate(() => {
+ deferredStateUpdateSpy()
+ if (!cancelled) {
+ setDeferredCounter(counter)
+ }
+ })
+
+ return () => {
+ cancelled = true
+ }
+ }, [counter])
+
+ return null
+ }
+ render()
+
+ jest.runAllImmediates()
+ cleanup()
+
+ expect(deferredStateUpdateSpy).toHaveBeenCalledTimes(1)
+ // console.error is mocked
+ // eslint-disable-next-line no-console
+ expect(console.error).toHaveBeenCalledTimes(1)
+ // eslint-disable-next-line no-console
+ expect(console.error.mock.calls[0][0]).toMatch(
+ 'a test was not wrapped in act(...)',
+ )
+ })
+})
diff --git a/src/flush-microtasks.js b/src/flush-microtasks.js
deleted file mode 100644
index e1d8fe6f..00000000
--- a/src/flush-microtasks.js
+++ /dev/null
@@ -1,67 +0,0 @@
-/* istanbul ignore file */
-// the part of this file that we need tested is definitely being run
-// and the part that is not cannot easily have useful tests written
-// anyway. So we're just going to ignore coverage for this file
-/**
- * copied and modified from React's enqueueTask.js
- */
-
-function getIsUsingFakeTimers() {
- return (
- typeof jest !== 'undefined' &&
- typeof setTimeout !== 'undefined' &&
- (setTimeout.hasOwnProperty('_isMockFunction') ||
- setTimeout.hasOwnProperty('clock'))
- )
-}
-
-let didWarnAboutMessageChannel = false
-let enqueueTask
-
-try {
- // read require off the module object to get around the bundlers.
- // we don't want them to detect a require and bundle a Node polyfill.
- const requireString = `require${Math.random()}`.slice(0, 7)
- const nodeRequire = module && module[requireString]
- // assuming we're in node, let's try to get node's
- // version of setImmediate, bypassing fake timers if any.
- enqueueTask = nodeRequire.call(module, 'timers').setImmediate
-} catch (_err) {
- // we're in a browser
- // we can't use regular timers because they may still be faked
- // so we try MessageChannel+postMessage instead
- enqueueTask = callback => {
- const supportsMessageChannel = typeof MessageChannel === 'function'
- if (supportsMessageChannel) {
- const channel = new MessageChannel()
- channel.port1.onmessage = callback
- channel.port2.postMessage(undefined)
- } else if (didWarnAboutMessageChannel === false) {
- didWarnAboutMessageChannel = true
-
- // eslint-disable-next-line no-console
- console.error(
- 'This browser does not have a MessageChannel implementation, ' +
- 'so enqueuing tasks via await act(async () => ...) will fail. ' +
- 'Please file an issue at https://github.com/facebook/react/issues ' +
- 'if you encounter this warning.',
- )
- }
- }
-}
-
-export default function flushMicroTasks() {
- return {
- then(resolve) {
- if (getIsUsingFakeTimers()) {
- // without this, a test using fake timers would never get microtasks
- // actually flushed. I spent several days on this... Really hard to
- // reproduce the problem, so there's no test for it. But it works!
- jest.advanceTimersByTime(0)
- resolve()
- } else {
- enqueueTask(resolve)
- }
- },
- }
-}
diff --git a/src/pure.js b/src/pure.js
index f2f3438f..8a062038 100644
--- a/src/pure.js
+++ b/src/pure.js
@@ -7,7 +7,6 @@ import {
} from '@testing-library/dom'
import act, {asyncAct} from './act-compat'
import {fireEvent} from './fire-event'
-import flush from './flush-microtasks'
configureDTL({
asyncWrapper: async cb => {
@@ -100,17 +99,16 @@ function render(
}
}
-async function cleanup() {
+function cleanup() {
mountedContainers.forEach(cleanupAtContainer)
- // flush microtask queue after unmounting in case
- // unmount sequence generates new microtasks
- await flush()
}
// maybe one day we'll expose this (perhaps even as a utility returned by render).
// but let's wait until someone asks for it.
function cleanupAtContainer(container) {
- ReactDOM.unmountComponentAtNode(container)
+ act(() => {
+ ReactDOM.unmountComponentAtNode(container)
+ })
if (container.parentNode === document.body) {
document.body.removeChild(container)
}