diff --git a/README.md b/README.md
index 26d9980e..1854178f 100644
--- a/README.md
+++ b/README.md
@@ -127,6 +127,21 @@ The containing DOM node of your rendered React Element (rendered using
> Tip: To get the root element of your rendered element, use `container.firstChild`.
+#### `unmount`
+
+This will cause the rendered component to be unmounted. This is useful for
+testing what happens when your component is removed from the page (like testing
+that you don't leave event handlers hanging around causing memory leaks).
+
+> This method is a pretty small abstraction over
+> `ReactDOM.unmountComponentAtNode`
+
+```javascript
+const {container, unmount} = render()
+unmount()
+// your component has been unmounted and now: container.innerHTML === ''
+```
+
#### `queryByTestId`
A shortcut to `` container.querySelector(`[data-testid="${yourId}"]`) ``. Read
@@ -270,7 +285,9 @@ Or you could include the index or an ID in your attribute:
And then you could use the `queryByTestId`:
```javascript
-const items = [/* your items */]
+const items = [
+ /* your items */
+]
const {queryByTestId} = render(/* your component with the items */)
const thirdItem = queryByTestId(`item-${items[2].id}`)
```
diff --git a/src/__tests__/stopwatch.js b/src/__tests__/stopwatch.js
new file mode 100644
index 00000000..e4b15c37
--- /dev/null
+++ b/src/__tests__/stopwatch.js
@@ -0,0 +1,59 @@
+import React from 'react'
+import {render, Simulate} from '../'
+
+class StopWatch extends React.Component {
+ state = {lapse: 0, running: false}
+ handleRunClick = () => {
+ this.setState(state => {
+ if (state.running) {
+ clearInterval(this.timer)
+ } else {
+ const startTime = Date.now() - this.state.lapse
+ this.timer = setInterval(() => {
+ this.setState({lapse: Date.now() - startTime})
+ })
+ }
+ return {running: !state.running}
+ })
+ }
+ handleClearClick = () => {
+ clearInterval(this.timer)
+ this.setState({lapse: 0, running: false})
+ }
+ componentWillUnmount() {
+ clearInterval(this.timer)
+ }
+ render() {
+ const {lapse, running} = this.state
+ return (
+
+ {lapse}ms
+
+
+
+ )
+ }
+}
+
+const wait = time => new Promise(resolve => setTimeout(resolve, time))
+
+test('unmounts a component', async () => {
+ jest.spyOn(console, 'error').mockImplementation(() => {})
+ const {unmount, queryByTestId, container} = render()
+ Simulate.click(queryByTestId('start-stop-button'))
+ unmount()
+ // hey there reader! You don't need to have an assertion like this one
+ // this is just me making sure that the unmount function works.
+ // You don't need to do this in your apps. Just rely on the fact that this works.
+ expect(container.innerHTML).toBe('')
+ // just wait to see if the interval is cleared or not
+ // if it's not, then we'll call setState on an unmounted component
+ // and get an error.
+ await wait()
+ // eslint-disable-next-line no-console
+ expect(console.error).not.toHaveBeenCalled()
+})
diff --git a/src/index.js b/src/index.js
index 7d99459d..b1258b96 100644
--- a/src/index.js
+++ b/src/index.js
@@ -15,6 +15,7 @@ function render(ui, {container = document.createElement('div')} = {}) {
ReactDOM.render(ui, container)
return {
container,
+ unmount: () => ReactDOM.unmountComponentAtNode(container),
queryByTestId: queryDivByTestId.bind(null, container),
}
}