diff --git a/package.json b/package.json
index b326a1a6..84e734d3 100644
--- a/package.json
+++ b/package.json
@@ -24,7 +24,8 @@
"files": [
"dist",
"typings",
- "cleanup-after-each.js"
+ "cleanup-after-each.js",
+ "pure.js"
],
"keywords": [
"testing",
@@ -42,12 +43,12 @@
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.5.5",
- "@testing-library/dom": "^5.6.1"
+ "@testing-library/dom": "^6.0.0"
},
"devDependencies": {
"@reach/router": "^1.2.1",
"@testing-library/jest-dom": "^4.0.0",
- "@types/react": "^16.8.25",
+ "@types/react": "^16.9.1",
"@types/react-dom": "^16.8.5",
"kcd-scripts": "^1.5.2",
"react": "^16.9.0",
diff --git a/pure.js b/pure.js
new file mode 100644
index 00000000..75dc0452
--- /dev/null
+++ b/pure.js
@@ -0,0 +1,2 @@
+// makes it so people can import from '@testing-library/react/pure'
+module.exports = require('./dist/pure')
diff --git a/src/__tests__/auto-cleanup-skip.js b/src/__tests__/auto-cleanup-skip.js
new file mode 100644
index 00000000..e5ef35ae
--- /dev/null
+++ b/src/__tests__/auto-cleanup-skip.js
@@ -0,0 +1,18 @@
+import React from 'react'
+
+let render
+beforeAll(() => {
+ process.env.RTL_SKIP_AUTO_CLEANUP = 'true'
+ const rtl = require('../')
+ render = rtl.render
+})
+
+// This one verifies that if RTL_SKIP_AUTO_CLEANUP is set
+// then we DON'T auto-wire up the afterEach for folks
+test('first', () => {
+ render(
hi
)
+})
+
+test('second', () => {
+ expect(document.body.innerHTML).toEqual('')
+})
diff --git a/src/__tests__/auto-cleanup.js b/src/__tests__/auto-cleanup.js
new file mode 100644
index 00000000..1d0a7954
--- /dev/null
+++ b/src/__tests__/auto-cleanup.js
@@ -0,0 +1,13 @@
+import React from 'react'
+import {render} from '../'
+
+// This just verifies that by importing RTL in an
+// environment which supports afterEach (like jest)
+// we'll get automatic cleanup between tests.
+test('first', () => {
+ render(hi
)
+})
+
+test('second', () => {
+ expect(document.body.innerHTML).toEqual('')
+})
diff --git a/src/index.js b/src/index.js
index 4565074b..5e05b725 100644
--- a/src/index.js
+++ b/src/index.js
@@ -1,153 +1,16 @@
-import React from 'react'
-import ReactDOM from 'react-dom'
-import {
- getQueriesForElement,
- prettyDOM,
- fireEvent as dtlFireEvent,
- configure as configureDTL,
-} from '@testing-library/dom'
-import act, {asyncAct} from './act-compat'
-
-configureDTL({
- asyncWrapper: async cb => {
- let result
- await asyncAct(async () => {
- result = await cb()
- })
- return result
- },
-})
-
-const mountedContainers = new Set()
-
-function render(
- ui,
- {
- container,
- baseElement = container,
- queries,
- hydrate = false,
- wrapper: WrapperComponent,
- } = {},
-) {
- if (!baseElement) {
- // default to document.body instead of documentElement to avoid output of potentially-large
- // head elements (such as JSS style blocks) in debug output
- baseElement = document.body
- }
- if (!container) {
- container = baseElement.appendChild(document.createElement('div'))
- }
-
- // we'll add it to the mounted containers regardless of whether it's actually
- // added to document.body so the cleanup method works regardless of whether
- // they're passing us a custom container or not.
- mountedContainers.add(container)
-
- const wrapUiIfNeeded = innerElement =>
- WrapperComponent
- ? React.createElement(WrapperComponent, null, innerElement)
- : innerElement
-
- act(() => {
- if (hydrate) {
- ReactDOM.hydrate(wrapUiIfNeeded(ui), container)
- } else {
- ReactDOM.render(wrapUiIfNeeded(ui), container)
- }
- })
-
- return {
- container,
- baseElement,
- // eslint-disable-next-line no-console
- debug: (el = baseElement) => console.log(prettyDOM(el)),
- unmount: () => ReactDOM.unmountComponentAtNode(container),
- rerender: rerenderUi => {
- render(wrapUiIfNeeded(rerenderUi), {container, baseElement})
- // Intentionally do not return anything to avoid unnecessarily complicating the API.
- // folks can use all the same utilities we return in the first place that are bound to the container
- },
- asFragment: () => {
- /* istanbul ignore if (jsdom limitation) */
- if (typeof document.createRange === 'function') {
- return document
- .createRange()
- .createContextualFragment(container.innerHTML)
- }
-
- const template = document.createElement('template')
- template.innerHTML = container.innerHTML
- return template.content
- },
- ...getQueriesForElement(baseElement, queries),
- }
-}
-
-function cleanup() {
- mountedContainers.forEach(cleanupAtContainer)
-}
-
-// 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)
- if (container.parentNode === document.body) {
- document.body.removeChild(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)
+import {asyncAct} from './act-compat'
+import {cleanup} from './pure'
+
+// if we're running in a test runner that supports afterEach
+// then we'll automatically run cleanup afterEach test
+// this ensures that tests run in isolation from each other
+// if you don't like this then either import the `pure` module
+// or set the RTL_SKIP_AUTO_CLEANUP env variable to 'true'.
+if (typeof afterEach === 'function' && !process.env.RTL_SKIP_AUTO_CLEANUP) {
+ afterEach(async () => {
+ await asyncAct(async () => {})
+ cleanup()
})
- 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
-fireEvent.mouseEnter = fireEvent.mouseOver
-fireEvent.mouseLeave = fireEvent.mouseOut
-
-fireEvent.select = (node, init) => {
- // React tracks this event only on focused inputs
- node.focus()
-
- // React creates this event when one of the following native events happens
- // - contextMenu
- // - mouseUp
- // - dragEnd
- // - keyUp
- // - keyDown
- // so we can use any here
- // @link https://github.com/facebook/react/blob/b87aabdfe1b7461e7331abb3601d9e6bb27544bc/packages/react-dom/src/events/SelectEventPlugin.js#L203-L224
- fireEvent.keyUp(node, init)
-}
-
-// just re-export everything from dom-testing-library
-export * from '@testing-library/dom'
-export {render, cleanup, fireEvent, act}
-
-// NOTE: we're not going to export asyncAct because that's our own compatibility
-// thing for people using react-dom@16.8.0. Anyone else doesn't need it and
-// people should just upgrade anyway.
-
-/* eslint func-name-matching:0 */
+export * from './pure'
diff --git a/src/pure.js b/src/pure.js
new file mode 100644
index 00000000..4565074b
--- /dev/null
+++ b/src/pure.js
@@ -0,0 +1,153 @@
+import React from 'react'
+import ReactDOM from 'react-dom'
+import {
+ getQueriesForElement,
+ prettyDOM,
+ fireEvent as dtlFireEvent,
+ configure as configureDTL,
+} from '@testing-library/dom'
+import act, {asyncAct} from './act-compat'
+
+configureDTL({
+ asyncWrapper: async cb => {
+ let result
+ await asyncAct(async () => {
+ result = await cb()
+ })
+ return result
+ },
+})
+
+const mountedContainers = new Set()
+
+function render(
+ ui,
+ {
+ container,
+ baseElement = container,
+ queries,
+ hydrate = false,
+ wrapper: WrapperComponent,
+ } = {},
+) {
+ if (!baseElement) {
+ // default to document.body instead of documentElement to avoid output of potentially-large
+ // head elements (such as JSS style blocks) in debug output
+ baseElement = document.body
+ }
+ if (!container) {
+ container = baseElement.appendChild(document.createElement('div'))
+ }
+
+ // we'll add it to the mounted containers regardless of whether it's actually
+ // added to document.body so the cleanup method works regardless of whether
+ // they're passing us a custom container or not.
+ mountedContainers.add(container)
+
+ const wrapUiIfNeeded = innerElement =>
+ WrapperComponent
+ ? React.createElement(WrapperComponent, null, innerElement)
+ : innerElement
+
+ act(() => {
+ if (hydrate) {
+ ReactDOM.hydrate(wrapUiIfNeeded(ui), container)
+ } else {
+ ReactDOM.render(wrapUiIfNeeded(ui), container)
+ }
+ })
+
+ return {
+ container,
+ baseElement,
+ // eslint-disable-next-line no-console
+ debug: (el = baseElement) => console.log(prettyDOM(el)),
+ unmount: () => ReactDOM.unmountComponentAtNode(container),
+ rerender: rerenderUi => {
+ render(wrapUiIfNeeded(rerenderUi), {container, baseElement})
+ // Intentionally do not return anything to avoid unnecessarily complicating the API.
+ // folks can use all the same utilities we return in the first place that are bound to the container
+ },
+ asFragment: () => {
+ /* istanbul ignore if (jsdom limitation) */
+ if (typeof document.createRange === 'function') {
+ return document
+ .createRange()
+ .createContextualFragment(container.innerHTML)
+ }
+
+ const template = document.createElement('template')
+ template.innerHTML = container.innerHTML
+ return template.content
+ },
+ ...getQueriesForElement(baseElement, queries),
+ }
+}
+
+function cleanup() {
+ mountedContainers.forEach(cleanupAtContainer)
+}
+
+// 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)
+ if (container.parentNode === document.body) {
+ document.body.removeChild(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
+fireEvent.mouseEnter = fireEvent.mouseOver
+fireEvent.mouseLeave = fireEvent.mouseOut
+
+fireEvent.select = (node, init) => {
+ // React tracks this event only on focused inputs
+ node.focus()
+
+ // React creates this event when one of the following native events happens
+ // - contextMenu
+ // - mouseUp
+ // - dragEnd
+ // - keyUp
+ // - keyDown
+ // so we can use any here
+ // @link https://github.com/facebook/react/blob/b87aabdfe1b7461e7331abb3601d9e6bb27544bc/packages/react-dom/src/events/SelectEventPlugin.js#L203-L224
+ fireEvent.keyUp(node, init)
+}
+
+// just re-export everything from dom-testing-library
+export * from '@testing-library/dom'
+export {render, cleanup, fireEvent, act}
+
+// NOTE: we're not going to export asyncAct because that's our own compatibility
+// thing for people using react-dom@16.8.0. Anyone else doesn't need it and
+// people should just upgrade anyway.
+
+/* eslint func-name-matching:0 */
diff --git a/tests/setup-env.js b/tests/setup-env.js
index d1d6d891..264828a9 100644
--- a/tests/setup-env.js
+++ b/tests/setup-env.js
@@ -1,6 +1 @@
import '@testing-library/jest-dom/extend-expect'
-
-afterEach(() => {
- // have to do a dynamic import so we don't mess up jest mocking for old-act.js
- require('../src').cleanup()
-})