diff --git a/README.md b/README.md
index ade2fa8b..4428ad81 100644
--- a/README.md
+++ b/README.md
@@ -2,10 +2,16 @@
react-testing-library
-
+
-Simple and complete React DOM testing utilities that encourage good testing practices.
+Simple and complete React DOM testing utilities that encourage good testing
+practices.
[**Read The Docs**](https://testing-library.com/react) |
[Edit the docs](https://github.com/alexkrolick/testing-library-docs)
@@ -30,9 +36,13 @@
## Table of Contents
@@ -102,7 +112,7 @@ test('Fetch makes an API call and displays the greeting when load-greeting is cl
)
// Act
- fireEvent.click(getByText('Load Greeting'))
+ fireEvent.click(getByText(/load greeting/i))
// Let's wait until our mocked `get` request promise resolves and
// the component calls setState and re-renders.
diff --git a/package.json b/package.json
index 3bb7c531..78091217 100644
--- a/package.json
+++ b/package.json
@@ -48,8 +48,8 @@
"jest-dom": "^2.0.4",
"jest-in-case": "^1.0.2",
"kcd-scripts": "^0.44.0",
- "react": "16.8.0",
- "react-dom": "16.8.0",
+ "react": "^16.8.0",
+ "react-dom": "^16.8.0",
"react-redux": "^5.0.7",
"react-router": "^4.3.1",
"react-router-dom": "^4.3.1",
diff --git a/src/__tests__/act.js b/src/__tests__/act.js
new file mode 100644
index 00000000..49729f3b
--- /dev/null
+++ b/src/__tests__/act.js
@@ -0,0 +1,42 @@
+import 'jest-dom/extend-expect'
+import React from 'react'
+import {render, cleanup, fireEvent} from '../'
+
+afterEach(cleanup)
+
+test('render calls useEffect immediately', () => {
+ const effectCb = jest.fn()
+ function MyUselessComponent() {
+ React.useEffect(effectCb)
+ return null
+ }
+ render()
+ expect(effectCb).toHaveBeenCalledTimes(1)
+})
+
+test('fireEvent triggers useEffect calls', () => {
+ const effectCb = jest.fn()
+ function Counter() {
+ React.useEffect(effectCb)
+ const [count, setCount] = React.useState(0)
+ return
+ }
+ const {
+ container: {firstChild: buttonNode},
+ } = render()
+
+ effectCb.mockClear()
+ fireEvent.click(buttonNode)
+ expect(buttonNode).toHaveTextContent('1')
+ expect(effectCb).toHaveBeenCalledTimes(1)
+})
+
+test('calls to hydrate will run useEffects', () => {
+ const effectCb = jest.fn()
+ function MyUselessComponent() {
+ React.useEffect(effectCb)
+ return null
+ }
+ render(, {hydrate: true})
+ expect(effectCb).toHaveBeenCalledTimes(1)
+})
diff --git a/src/__tests__/no-act.js b/src/__tests__/no-act.js
new file mode 100644
index 00000000..07ee091d
--- /dev/null
+++ b/src/__tests__/no-act.js
@@ -0,0 +1,10 @@
+import {act} from '..'
+
+jest.mock('react-dom/test-utils', () => ({}))
+
+test('act works even when there is no act from test utils', () => {
+ const callback = jest.fn()
+ act(callback)
+ expect(callback).toHaveBeenCalledTimes(1)
+ expect(callback).toHaveBeenCalledWith(/* nothing */)
+})
diff --git a/src/__tests__/render.js b/src/__tests__/render.js
index f2ec2351..5ee0dc6f 100644
--- a/src/__tests__/render.js
+++ b/src/__tests__/render.js
@@ -1,7 +1,7 @@
import 'jest-dom/extend-expect'
import React from 'react'
import ReactDOM from 'react-dom'
-import {render, cleanup, flushEffects} from '../'
+import {render, cleanup} from '../'
afterEach(cleanup)
@@ -90,10 +90,3 @@ it('supports fragments', () => {
cleanup()
expect(document.body.innerHTML).toBe('')
})
-
-test('flushEffects can be called without causing issues', () => {
- render()
- const preHtml = document.documentElement.innerHTML
- flushEffects()
- expect(document.documentElement.innerHTML).toBe(preHtml)
-})
diff --git a/src/__tests__/testHook.js b/src/__tests__/test-hook.js
similarity index 100%
rename from src/__tests__/testHook.js
rename to src/__tests__/test-hook.js
diff --git a/src/act-compat.js b/src/act-compat.js
new file mode 100644
index 00000000..65f85971
--- /dev/null
+++ b/src/act-compat.js
@@ -0,0 +1,12 @@
+import {act as reactAct} from 'react-dom/test-utils'
+
+// act is supported react-dom@16.8.0
+// and is only needed for versions higher than that
+// so we do nothing for versions that don't support act.
+const act = reactAct || (cb => cb())
+
+function rtlAct(...args) {
+ return act(...args)
+}
+
+export default rtlAct
diff --git a/src/index.js b/src/index.js
index 24b982ac..0f4c7d47 100644
--- a/src/index.js
+++ b/src/index.js
@@ -1,6 +1,11 @@
import React from 'react'
import ReactDOM from 'react-dom'
-import {getQueriesForElement, prettyDOM, fireEvent} from 'dom-testing-library'
+import {
+ getQueriesForElement,
+ prettyDOM,
+ fireEvent as dtlFireEvent,
+} from 'dom-testing-library'
+import act from './act-compat'
const mountedContainers = new Set()
@@ -21,9 +26,13 @@ function render(
mountedContainers.add(container)
if (hydrate) {
- ReactDOM.hydrate(ui, container)
+ act(() => {
+ ReactDOM.hydrate(ui, container)
+ })
} else {
- ReactDOM.render(ui, container)
+ act(() => {
+ ReactDOM.render(ui, container)
+ })
}
return {
container,
@@ -65,10 +74,6 @@ function cleanup() {
mountedContainers.forEach(cleanupAtContainer)
}
-function flushEffects() {
- ReactDOM.render(null, document.createElement('div'))
-}
-
// 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) {
@@ -79,6 +84,29 @@ function cleanupAtContainer(container) {
mountedContainers.delete(container)
}
+// react-testing-library's version of fireEvent will call
+// dom-testing-library's version of fireEvent wrapped inside
+// an "act" call so that after all event callbacks have been
+// been called, the resulting useEffect callbacks will also
+// be called.
+function fireEvent(...args) {
+ let returnValue
+ act(() => {
+ returnValue = dtlFireEvent(...args)
+ })
+ return returnValue
+}
+
+Object.keys(dtlFireEvent).forEach(key => {
+ fireEvent[key] = (...args) => {
+ let returnValue
+ act(() => {
+ returnValue = dtlFireEvent[key](...args)
+ })
+ return returnValue
+ }
+})
+
// React event system tracks native mouseOver/mouseOut events for
// running onMouseEnter/onMouseLeave handlers
// @link https://github.com/facebook/react/blob/b87aabdfe1b7461e7331abb3601d9e6bb27544bc/packages/react-dom/src/events/EnterLeaveEventPlugin.js#L24-L31
@@ -102,6 +130,6 @@ fireEvent.select = (node, init) => {
// just re-export everything from dom-testing-library
export * from 'dom-testing-library'
-export {render, testHook, cleanup, flushEffects}
+export {render, testHook, cleanup, fireEvent, act}
/* eslint func-name-matching:0 */
diff --git a/typings/index.d.ts b/typings/index.d.ts
index ce6408f3..c87c817c 100644
--- a/typings/index.d.ts
+++ b/typings/index.d.ts
@@ -51,6 +51,8 @@ export function testHook(callback: () => void): void
export function cleanup(): void
/**
- * Forces React's `useEffect` hook to run synchronously.
+ * Simply calls ReactDOMTestUtils.act(cb)
+ * If that's not available (older version of react) then it
+ * simply calls the given callback immediately
*/
-export function flushEffects(): void
+export function act(callback: () => void): void