Skip to content

Commit

Permalink
feat(fireEvent): add fireEvent from dom-testing-library (#48)
Browse files Browse the repository at this point in the history
* add fireEvent from dom-testing-library

* add contributor

* added docs

* use document for synthetic events

* added renderIntoDocument and clearDocument

* added docs

* update tests and README.md

* fix typos

* fix typo

* update readme

* fix typo

* use beforeEach

* added cleanup, removed clearDocument

* use afterEach for cleanup

* Update README.md
  • Loading branch information
jomaxx authored and Kent C. Dodds committed Apr 10, 2018
1 parent 6d9c368 commit 86d3508
Show file tree
Hide file tree
Showing 5 changed files with 308 additions and 4 deletions.
11 changes: 11 additions & 0 deletions .all-contributorsrc
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,17 @@
"code",
"doc"
]
},
{
"login": "jomaxx",
"name": "Josef Maxx Blake",
"avatar_url": "https://avatars2.githubusercontent.com/u/2747424?v=4",
"profile": "http://jomaxx.com",
"contributions": [
"code",
"doc",
"test"
]
}
]
}
88 changes: 86 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
[![downloads][downloads-badge]][npmtrends]
[![MIT License][license-badge]][license]

[![All Contributors](https://img.shields.io/badge/all_contributors-13-orange.svg?style=flat-square)](#contributors)
[![All Contributors](https://img.shields.io/badge/all_contributors-14-orange.svg?style=flat-square)](#contributors)
[![PRs Welcome][prs-badge]][prs]
[![Code of Conduct][coc-badge]][coc]

Expand Down Expand Up @@ -81,8 +81,11 @@ facilitate testing implementation details). Read more about this in
* [Installation](#installation)
* [Usage](#usage)
* [`render`](#render)
* [`renderIntoDocument`](#renderintodocument)
* [`cleanup`](#cleanup)
* [`Simulate`](#simulate)
* [`wait`](#wait)
* [`fireEvent(node: HTMLElement, event: Event)`](#fireeventnode-htmlelement-event-event)
* [`TextMatch`](#textmatch)
* [`query` APIs](#query-apis)
* [Examples](#examples)
Expand Down Expand Up @@ -264,6 +267,31 @@ const usernameInputElement = getByTestId('username-input')
> Learn more about `data-testid`s from the blog post
> ["Making your UI tests resilient to change"][data-testid-blog-post]
### `renderIntoDocument`

Render into `document.body`. Should be used with [cleanup](#cleanup)

```javascript
renderIntoDocument(<div>)
```

### `cleanup`

Unmounts React trees that were mounted with [renderIntoDocument](#renderintodocument).

```javascript
afterEach(cleanup)

test('renders into document', () => {
renderIntoDocument(<div>)
// ...
})
```

Failing to call `cleanup` when you've called `renderIntoDocument` could
result in a memory leak and tests which are not `idempotent` (which can
lead to difficult to debug errors in your tests).

### `Simulate`

This is simply a re-export from the `Simulate` utility from
Expand Down Expand Up @@ -313,6 +341,62 @@ The default `interval` is `50ms`. However it will run your callback immediately
on the next tick of the event loop (in a `setTimeout`) before starting the
intervals.

### `fireEvent(node: HTMLElement, event: Event)`

Fire DOM events.

React attaches an event handler on the `document` and handles some DOM events
via event delegation (events bubbling up from a `target` to an ancestor). Because
of this, your `node` must be in the `document.body` for `fireEvent` to work with
React. You can render into the document using the
[renderIntoDocument](#renderintodocument) utility. This is an alternative to
simulating Synthetic React Events via [Simulate](#simulate). The benefit of
using `fireEvent` over `Simulate` is that you are testing real DOM events
instead of Synthetic Events. This aligns better with
[the Guiding Principles](#guiding-principles).

> NOTE: If you don't like having to render into the document to get `fireEvent`
> working, then feel free to try to chip into making it possible for React
> to attach event handlers to the rendered node rather than the `document`.
> Learn more here:
> [facebook/react#2043](https://github.com/facebook/react/issues/2043)

```javascript
import { renderIntoDocument, cleanup, render, fireEvent }
// don't forget to clean up the document.body
afterEach(cleanup)
test('clicks submit button', () => {
const spy = jest.fn();
const { unmount, getByText } = renderIntoDocument(<button onClick={spy}>Submit</button>)
fireEvent(
getByText('Submit'),
new MouseEvent('click', {
bubbles: true, // click events must bubble for React to see it
cancelable: true,
})
)
expect(spy).toHaveBeenCalledTimes(1)
})
```

#### `fireEvent[eventName](node: HTMLElement, eventProperties: Object)`

Convenience methods for firing DOM events. Check out
[dom-testing-library/src/events.js](https://github.com/kentcdodds/dom-testing-library/blob/master/src/events.js)
for a full list as well as default `eventProperties`.

```javascript
// similar to the above example
// click will bubble for React to see it
const rightClick = {button: 2}
fireEvent.click(getElementByText('Submit'), rightClick)
// default `button` property for click events is set to `0` which is a left click.
```

## `TextMatch`

Several APIs accept a `TextMatch` which can be a `string`, `regex` or a
Expand Down Expand Up @@ -635,7 +719,7 @@ Thanks goes to these people ([emoji key][emojis]):
<!-- prettier-ignore -->
| [<img src="https://avatars.githubusercontent.com/u/1500684?v=3" width="100px;"/><br /><sub><b>Kent C. Dodds</b></sub>](https://kentcdodds.com)<br />[馃捇](https://github.com/kentcdodds/react-testing-library/commits?author=kentcdodds "Code") [馃摉](https://github.com/kentcdodds/react-testing-library/commits?author=kentcdodds "Documentation") [馃殗](#infra-kentcdodds "Infrastructure (Hosting, Build-Tools, etc)") [鈿狅笍](https://github.com/kentcdodds/react-testing-library/commits?author=kentcdodds "Tests") | [<img src="https://avatars1.githubusercontent.com/u/2430381?v=4" width="100px;"/><br /><sub><b>Ryan Castner</b></sub>](http://audiolion.github.io)<br />[馃摉](https://github.com/kentcdodds/react-testing-library/commits?author=audiolion "Documentation") | [<img src="https://avatars0.githubusercontent.com/u/8008023?v=4" width="100px;"/><br /><sub><b>Daniel Sandiego</b></sub>](https://www.dnlsandiego.com)<br />[馃捇](https://github.com/kentcdodds/react-testing-library/commits?author=dnlsandiego "Code") | [<img src="https://avatars2.githubusercontent.com/u/12592677?v=4" width="100px;"/><br /><sub><b>Pawe艂 Miko艂ajczyk</b></sub>](https://github.com/Miklet)<br />[馃捇](https://github.com/kentcdodds/react-testing-library/commits?author=Miklet "Code") | [<img src="https://avatars3.githubusercontent.com/u/464978?v=4" width="100px;"/><br /><sub><b>Alejandro 脩谩帽ez Ortiz</b></sub>](http://co.linkedin.com/in/alejandronanez/)<br />[馃摉](https://github.com/kentcdodds/react-testing-library/commits?author=alejandronanez "Documentation") | [<img src="https://avatars0.githubusercontent.com/u/1402095?v=4" width="100px;"/><br /><sub><b>Matt Parrish</b></sub>](https://github.com/pbomb)<br />[馃悰](https://github.com/kentcdodds/react-testing-library/issues?q=author%3Apbomb "Bug reports") [馃捇](https://github.com/kentcdodds/react-testing-library/commits?author=pbomb "Code") [馃摉](https://github.com/kentcdodds/react-testing-library/commits?author=pbomb "Documentation") [鈿狅笍](https://github.com/kentcdodds/react-testing-library/commits?author=pbomb "Tests") | [<img src="https://avatars1.githubusercontent.com/u/1288694?v=4" width="100px;"/><br /><sub><b>Justin Hall</b></sub>](https://github.com/wKovacs64)<br />[馃摝](#platform-wKovacs64 "Packaging/porting to new platform") |
| :---: | :---: | :---: | :---: | :---: | :---: | :---: |
| [<img src="https://avatars1.githubusercontent.com/u/1241511?s=460&v=4" width="100px;"/><br /><sub><b>Anto Aravinth</b></sub>](https://github.com/antoaravinth)<br />[馃捇](https://github.com/kentcdodds/react-testing-library/commits?author=antoaravinth "Code") [鈿狅笍](https://github.com/kentcdodds/react-testing-library/commits?author=antoaravinth "Tests") [馃摉](https://github.com/kentcdodds/react-testing-library/commits?author=antoaravinth "Documentation") | [<img src="https://avatars2.githubusercontent.com/u/3462296?v=4" width="100px;"/><br /><sub><b>Jonah Moses</b></sub>](https://github.com/JonahMoses)<br />[馃摉](https://github.com/kentcdodds/react-testing-library/commits?author=JonahMoses "Documentation") | [<img src="https://avatars1.githubusercontent.com/u/4002543?v=4" width="100px;"/><br /><sub><b>艁ukasz Gandecki</b></sub>](http://team.thebrain.pro)<br />[馃捇](https://github.com/kentcdodds/react-testing-library/commits?author=lgandecki "Code") [鈿狅笍](https://github.com/kentcdodds/react-testing-library/commits?author=lgandecki "Tests") [馃摉](https://github.com/kentcdodds/react-testing-library/commits?author=lgandecki "Documentation") | [<img src="https://avatars2.githubusercontent.com/u/498274?v=4" width="100px;"/><br /><sub><b>Ivan Babak</b></sub>](https://sompylasar.github.io)<br />[馃悰](https://github.com/kentcdodds/react-testing-library/issues?q=author%3Asompylasar "Bug reports") [馃](#ideas-sompylasar "Ideas, Planning, & Feedback") | [<img src="https://avatars3.githubusercontent.com/u/4439618?v=4" width="100px;"/><br /><sub><b>Jesse Day</b></sub>](https://github.com/jday3)<br />[馃捇](https://github.com/kentcdodds/react-testing-library/commits?author=jday3 "Code") | [<img src="https://avatars0.githubusercontent.com/u/15199?v=4" width="100px;"/><br /><sub><b>Ernesto Garc铆a</b></sub>](http://gnapse.github.io)<br />[馃挰](#question-gnapse "Answering Questions") [馃捇](https://github.com/kentcdodds/react-testing-library/commits?author=gnapse "Code") [馃摉](https://github.com/kentcdodds/react-testing-library/commits?author=gnapse "Documentation") |
| [<img src="https://avatars1.githubusercontent.com/u/1241511?s=460&v=4" width="100px;"/><br /><sub><b>Anto Aravinth</b></sub>](https://github.com/antoaravinth)<br />[馃捇](https://github.com/kentcdodds/react-testing-library/commits?author=antoaravinth "Code") [鈿狅笍](https://github.com/kentcdodds/react-testing-library/commits?author=antoaravinth "Tests") [馃摉](https://github.com/kentcdodds/react-testing-library/commits?author=antoaravinth "Documentation") | [<img src="https://avatars2.githubusercontent.com/u/3462296?v=4" width="100px;"/><br /><sub><b>Jonah Moses</b></sub>](https://github.com/JonahMoses)<br />[馃摉](https://github.com/kentcdodds/react-testing-library/commits?author=JonahMoses "Documentation") | [<img src="https://avatars1.githubusercontent.com/u/4002543?v=4" width="100px;"/><br /><sub><b>艁ukasz Gandecki</b></sub>](http://team.thebrain.pro)<br />[馃捇](https://github.com/kentcdodds/react-testing-library/commits?author=lgandecki "Code") [鈿狅笍](https://github.com/kentcdodds/react-testing-library/commits?author=lgandecki "Tests") [馃摉](https://github.com/kentcdodds/react-testing-library/commits?author=lgandecki "Documentation") | [<img src="https://avatars2.githubusercontent.com/u/498274?v=4" width="100px;"/><br /><sub><b>Ivan Babak</b></sub>](https://sompylasar.github.io)<br />[馃悰](https://github.com/kentcdodds/react-testing-library/issues?q=author%3Asompylasar "Bug reports") [馃](#ideas-sompylasar "Ideas, Planning, & Feedback") | [<img src="https://avatars3.githubusercontent.com/u/4439618?v=4" width="100px;"/><br /><sub><b>Jesse Day</b></sub>](https://github.com/jday3)<br />[馃捇](https://github.com/kentcdodds/react-testing-library/commits?author=jday3 "Code") | [<img src="https://avatars0.githubusercontent.com/u/15199?v=4" width="100px;"/><br /><sub><b>Ernesto Garc铆a</b></sub>](http://gnapse.github.io)<br />[馃挰](#question-gnapse "Answering Questions") [馃捇](https://github.com/kentcdodds/react-testing-library/commits?author=gnapse "Code") [馃摉](https://github.com/kentcdodds/react-testing-library/commits?author=gnapse "Documentation") | [<img src="https://avatars2.githubusercontent.com/u/2747424?v=4" width="100px;"/><br /><sub><b>Josef Maxx Blake</b></sub>](http://jomaxx.com)<br />[馃捇](https://github.com/kentcdodds/react-testing-library/commits?author=jomaxx "Code") [馃摉](https://github.com/kentcdodds/react-testing-library/commits?author=jomaxx "Documentation") [鈿狅笍](https://github.com/kentcdodds/react-testing-library/commits?author=jomaxx "Tests") |

<!-- ALL-CONTRIBUTORS-LIST:END -->

Expand Down
156 changes: 156 additions & 0 deletions src/__tests__/events.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
import React from 'react'
import {renderIntoDocument, cleanup, fireEvent} from '../'

const eventTypes = [
{
type: 'Clipboard',
events: ['copy', 'paste'],
elementType: 'input',
},
{
type: 'Composition',
events: ['compositionEnd', 'compositionStart', 'compositionUpdate'],
elementType: 'input',
},
{
type: 'Keyboard',
events: ['keyDown', 'keyPress', 'keyUp'],
elementType: 'input',
init: {keyCode: 13},
},
{
type: 'Focus',
events: ['focus', 'blur'],
elementType: 'input',
},
{
type: 'Form',
events: ['focus', 'blur'],
elementType: 'input',
},
{
type: 'Focus',
events: ['change', 'input', 'invalid'],
elementType: 'input',
},
{
type: 'Focus',
events: ['submit'],
elementType: 'form',
},
{
type: 'Mouse',
events: [
'click',
'contextMenu',
'doubleClick',
'drag',
'dragEnd',
'dragEnter',
'dragExit',
'dragLeave',
'dragOver',
'dragStart',
'drop',
'mouseDown',
'mouseEnter',
'mouseLeave',
'mouseMove',
'mouseOut',
'mouseOver',
'mouseUp',
],
elementType: 'button',
},
{
type: 'Selection',
events: ['select'],
elementType: 'input',
},
{
type: 'Touch',
events: ['touchCancel', 'touchEnd', 'touchMove', 'touchStart'],
elementType: 'button',
},
{
type: 'UI',
events: ['scroll'],
elementType: 'div',
},
{
type: 'Wheel',
events: ['wheel'],
elementType: 'div',
},
{
type: 'Media',
events: [
'abort',
'canPlay',
'canPlayThrough',
'durationChange',
'emptied',
'encrypted',
'ended',
'error',
'loadedData',
'loadedMetadata',
'loadStart',
'pause',
'play',
'playing',
'progress',
'rateChange',
'seeked',
'seeking',
'stalled',
'suspend',
'timeUpdate',
'volumeChange',
'waiting',
],
elementType: 'video',
},
{
type: 'Image',
events: ['load', 'error'],
elementType: 'img',
},
{
type: 'Animation',
events: ['animationStart', 'animationEnd', 'animationIteration'],
elementType: 'div',
},
{
type: 'Transition',
events: ['transitionEnd'],
elementType: 'div',
},
]

afterEach(cleanup)

eventTypes.forEach(({type, events, elementType, init}) => {
describe(`${type} Events`, () => {
events.forEach(eventName => {
const propName = `on${eventName.charAt(0).toUpperCase()}${eventName.slice(
1,
)}`

it(`triggers ${propName}`, () => {
const ref = React.createRef()
const spy = jest.fn()

renderIntoDocument(
React.createElement(elementType, {
[propName]: spy,
ref,
}),
)

fireEvent[eventName](ref.current, init)
expect(spy).toHaveBeenCalledTimes(1)
})
})
})
})
29 changes: 29 additions & 0 deletions src/__tests__/render-into-document.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import React from 'react'
import {renderIntoDocument, cleanup} from '../'

afterEach(cleanup)

it('renders button into document', () => {
const ref = React.createRef()
const {container} = renderIntoDocument(<div ref={ref} />)
expect(container.firstChild).toBe(ref.current)
})

it('cleansup document', () => {
const spy = jest.fn()

class Test extends React.Component {
componentWillUnmount() {
spy()
}

render() {
return <div />
}
}

renderIntoDocument(<Test />)
cleanup()
expect(document.body.innerHTML).toBe('')
expect(spy).toHaveBeenCalledTimes(1)
})
28 changes: 26 additions & 2 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import ReactDOM from 'react-dom'
import {Simulate} from 'react-dom/test-utils'
import {queries, wait} from 'dom-testing-library'
import {queries, wait, fireEvent} from 'dom-testing-library'

function render(ui, {container = document.createElement('div')} = {}) {
ReactDOM.render(ui, container)
Expand All @@ -18,4 +18,28 @@ function render(ui, {container = document.createElement('div')} = {}) {
}
}

export {render, Simulate, wait}
const mountedContainers = new Set()

function renderIntoDocument(ui) {
const container = document.body.appendChild(document.createElement('div'))
mountedContainers.add(container)
return render(ui, {container})
}

function cleanup() {
mountedContainers.forEach(container => {
document.body.removeChild(container)
ReactDOM.unmountComponentAtNode(container)
mountedContainers.delete(container)
})
}

// fallback to synthetic events for React events that the DOM doesn't support
const syntheticEvents = ['change', 'select', 'mouseEnter', 'mouseLeave']
syntheticEvents.forEach(eventName => {
document.addEventListener(eventName.toLowerCase(), e => {
Simulate[eventName](e.target, e)
})
})

export {render, Simulate, wait, fireEvent, renderIntoDocument, cleanup}

0 comments on commit 86d3508

Please sign in to comment.