Skip to content

Commit

Permalink
feat(unmount): add unmount to render object
Browse files Browse the repository at this point in the history
  • Loading branch information
Kent C. Dodds committed Mar 19, 2018
1 parent 03b17c3 commit 9d0a07f
Show file tree
Hide file tree
Showing 3 changed files with 78 additions and 1 deletion.
19 changes: 18 additions & 1 deletion README.md
Expand Up @@ -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(<Login />)
unmount()
// your component has been unmounted and now: container.innerHTML === ''
```

#### `queryByTestId`

A shortcut to `` container.querySelector(`[data-testid="${yourId}"]`) ``. Read
Expand Down Expand Up @@ -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}`)
```
Expand Down
59 changes: 59 additions & 0 deletions 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 (
<div>
<span>{lapse}ms</span>
<button onClick={this.handleRunClick} data-testid="start-stop-button">
{running ? 'Stop' : 'Start'}
</button>
<button onClick={this.handleClearClick} data-testid="clear-button">
Clear
</button>
</div>
)
}
}

const wait = time => new Promise(resolve => setTimeout(resolve, time))

test('unmounts a component', async () => {
jest.spyOn(console, 'error').mockImplementation(() => {})
const {unmount, queryByTestId, container} = render(<StopWatch />)
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()
})
1 change: 1 addition & 0 deletions src/index.js
Expand Up @@ -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),
}
}
Expand Down

0 comments on commit 9d0a07f

Please sign in to comment.