diff --git a/.all-contributorsrc b/.all-contributorsrc
index 96277113..ac1795bc 100644
--- a/.all-contributorsrc
+++ b/.all-contributorsrc
@@ -139,6 +139,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"
+ ]
}
]
}
diff --git a/README.md b/README.md
index 192e6fe9..69809c09 100644
--- a/README.md
+++ b/README.md
@@ -16,7 +16,7 @@
[![downloads][downloads-badge]][npmtrends]
[![MIT License][license-badge]][license]
-[](#contributors)
+[](#contributors)
[![PRs Welcome][prs-badge]][prs]
[![Code of Conduct][coc-badge]][coc]
@@ -78,6 +78,7 @@ when a real user uses it.
* [`getByAltText(container: HTMLElement, text: TextMatch): HTMLElement`](#getbyalttextcontainer-htmlelement-text-textmatch-htmlelement)
* [`wait`](#wait)
* [`waitForElement`](#waitforelement)
+ * [`fireEvent(node: HTMLElement, event: Event)`](#fireeventnode-htmlelement-event-event)
* [Custom Jest Matchers](#custom-jest-matchers)
* [`toBeInTheDOM`](#tobeinthedom)
* [`toHaveTextContent`](#tohavetextcontent)
@@ -368,6 +369,34 @@ The default `timeout` is `4500ms` which will keep you under
additions and removals of child elements (including text nodes) in the `container` and any of its descendants.
It won't detect attribute changes unless you add `attributes: true` to the options.
+### `fireEvent(node: HTMLElement, event: Event)`
+
+Fire DOM events.
+
+```javascript
+//
+fireEvent(
+ getElementByText('Submit'),
+ new MouseEvent('click', {
+ bubbles: true,
+ cancelable: true,
+ }),
+)
+```
+
+#### `fireEvent[eventName](node: HTMLElement, eventProperties: Object)`
+
+Convenience methods for firing DOM events. Check out
+[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
+//
+const rightClick = {button: 2}
+fireEvent.click(getElementByText('Submit'), rightClick)
+// default `button` property for click events is set to `0` which is a left click.
+```
+
## Custom Jest Matchers
There are two simple API which extend the `expect` API of jest for making assertions easier.
@@ -662,7 +691,7 @@ Thanks goes to these people ([emoji key][emojis]):
| [
Kent C. Dodds](https://kentcdodds.com)
[π»](https://github.com/kentcdodds/dom-testing-library/commits?author=kentcdodds "Code") [π](https://github.com/kentcdodds/dom-testing-library/commits?author=kentcdodds "Documentation") [π](#infra-kentcdodds "Infrastructure (Hosting, Build-Tools, etc)") [β οΈ](https://github.com/kentcdodds/dom-testing-library/commits?author=kentcdodds "Tests") | [
Ryan Castner](http://audiolion.github.io)
[π](https://github.com/kentcdodds/dom-testing-library/commits?author=audiolion "Documentation") | [
Daniel Sandiego](https://www.dnlsandiego.com)
[π»](https://github.com/kentcdodds/dom-testing-library/commits?author=dnlsandiego "Code") | [
PaweΕ MikoΕajczyk](https://github.com/Miklet)
[π»](https://github.com/kentcdodds/dom-testing-library/commits?author=Miklet "Code") | [
Alejandro ΓÑñez Ortiz](http://co.linkedin.com/in/alejandronanez/)
[π](https://github.com/kentcdodds/dom-testing-library/commits?author=alejandronanez "Documentation") | [
Matt Parrish](https://github.com/pbomb)
[π](https://github.com/kentcdodds/dom-testing-library/issues?q=author%3Apbomb "Bug reports") [π»](https://github.com/kentcdodds/dom-testing-library/commits?author=pbomb "Code") [π](https://github.com/kentcdodds/dom-testing-library/commits?author=pbomb "Documentation") [β οΈ](https://github.com/kentcdodds/dom-testing-library/commits?author=pbomb "Tests") | [
Justin Hall](https://github.com/wKovacs64)
[π¦](#platform-wKovacs64 "Packaging/porting to new platform") |
| :---: | :---: | :---: | :---: | :---: | :---: | :---: |
-| [
Anto Aravinth](https://github.com/antoaravinth)
[π»](https://github.com/kentcdodds/dom-testing-library/commits?author=antoaravinth "Code") [β οΈ](https://github.com/kentcdodds/dom-testing-library/commits?author=antoaravinth "Tests") [π](https://github.com/kentcdodds/dom-testing-library/commits?author=antoaravinth "Documentation") | [
Jonah Moses](https://github.com/JonahMoses)
[π](https://github.com/kentcdodds/dom-testing-library/commits?author=JonahMoses "Documentation") | [
Εukasz Gandecki](http://team.thebrain.pro)
[π»](https://github.com/kentcdodds/dom-testing-library/commits?author=lgandecki "Code") [β οΈ](https://github.com/kentcdodds/dom-testing-library/commits?author=lgandecki "Tests") [π](https://github.com/kentcdodds/dom-testing-library/commits?author=lgandecki "Documentation") | [
Ivan Babak](https://sompylasar.github.io)
[π](https://github.com/kentcdodds/dom-testing-library/issues?q=author%3Asompylasar "Bug reports") [π€](#ideas-sompylasar "Ideas, Planning, & Feedback") [π»](https://github.com/kentcdodds/dom-testing-library/commits?author=sompylasar "Code") [π](https://github.com/kentcdodds/dom-testing-library/commits?author=sompylasar "Documentation") | [
Jesse Day](https://github.com/jday3)
[π»](https://github.com/kentcdodds/dom-testing-library/commits?author=jday3 "Code") | [
Ernesto GarcΓa](http://gnapse.github.io)
[π¬](#question-gnapse "Answering Questions") [π»](https://github.com/kentcdodds/dom-testing-library/commits?author=gnapse "Code") [π](https://github.com/kentcdodds/dom-testing-library/commits?author=gnapse "Documentation") |
+| [
Anto Aravinth](https://github.com/antoaravinth)
[π»](https://github.com/kentcdodds/dom-testing-library/commits?author=antoaravinth "Code") [β οΈ](https://github.com/kentcdodds/dom-testing-library/commits?author=antoaravinth "Tests") [π](https://github.com/kentcdodds/dom-testing-library/commits?author=antoaravinth "Documentation") | [
Jonah Moses](https://github.com/JonahMoses)
[π](https://github.com/kentcdodds/dom-testing-library/commits?author=JonahMoses "Documentation") | [
Εukasz Gandecki](http://team.thebrain.pro)
[π»](https://github.com/kentcdodds/dom-testing-library/commits?author=lgandecki "Code") [β οΈ](https://github.com/kentcdodds/dom-testing-library/commits?author=lgandecki "Tests") [π](https://github.com/kentcdodds/dom-testing-library/commits?author=lgandecki "Documentation") | [
Ivan Babak](https://sompylasar.github.io)
[π](https://github.com/kentcdodds/dom-testing-library/issues?q=author%3Asompylasar "Bug reports") [π€](#ideas-sompylasar "Ideas, Planning, & Feedback") [π»](https://github.com/kentcdodds/dom-testing-library/commits?author=sompylasar "Code") [π](https://github.com/kentcdodds/dom-testing-library/commits?author=sompylasar "Documentation") | [
Jesse Day](https://github.com/jday3)
[π»](https://github.com/kentcdodds/dom-testing-library/commits?author=jday3 "Code") | [
Ernesto GarcΓa](http://gnapse.github.io)
[π¬](#question-gnapse "Answering Questions") [π»](https://github.com/kentcdodds/dom-testing-library/commits?author=gnapse "Code") [π](https://github.com/kentcdodds/dom-testing-library/commits?author=gnapse "Documentation") | [
Josef Maxx Blake](http://jomaxx.com)
[π»](https://github.com/kentcdodds/dom-testing-library/commits?author=jomaxx "Code") [π](https://github.com/kentcdodds/dom-testing-library/commits?author=jomaxx "Documentation") [β οΈ](https://github.com/kentcdodds/dom-testing-library/commits?author=jomaxx "Tests") |
diff --git a/package.json b/package.json
index a374bbae..1ba1cfee 100644
--- a/package.json
+++ b/package.json
@@ -35,8 +35,8 @@
],
"dependencies": {
"jest-matcher-utils": "^22.4.3",
- "wait-for-expect": "^0.4.0",
- "mutationobserver-shim": "^0.3.2"
+ "mutationobserver-shim": "^0.3.2",
+ "wait-for-expect": "^0.4.0"
},
"devDependencies": {
"jest-in-case": "^1.0.2",
diff --git a/src/__tests__/events.js b/src/__tests__/events.js
new file mode 100644
index 00000000..b57e2f21
--- /dev/null
+++ b/src/__tests__/events.js
@@ -0,0 +1,151 @@
+import {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',
+ },
+ {
+ 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',
+ 'dblClick',
+ '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',
+ },
+]
+
+eventTypes.forEach(({type, events, elementType}) => {
+ describe(`${type} Events`, () => {
+ events.forEach(eventName => {
+ it(`fires ${eventName}`, () => {
+ const node = document.createElement(elementType)
+ const spy = jest.fn()
+ node.addEventListener(eventName.toLowerCase(), spy)
+ fireEvent[eventName](node)
+ expect(spy).toHaveBeenCalledTimes(1)
+ })
+ })
+ })
+})
+
+describe(`Aliased Events`, () => {
+ it(`fires doubleClick`, () => {
+ const node = document.createElement('div')
+ const spy = jest.fn()
+ node.addEventListener('dblclick', spy)
+ fireEvent.doubleClick(node)
+ expect(spy).toHaveBeenCalledTimes(1)
+ })
+})
diff --git a/src/events.js b/src/events.js
new file mode 100644
index 00000000..ad5a8e33
--- /dev/null
+++ b/src/events.js
@@ -0,0 +1,334 @@
+const {
+ AnimationEvent,
+ ClipboardEvent,
+ CompositionEvent,
+ DragEvent,
+ Event,
+ FocusEvent,
+ InputEvent,
+ KeyboardEvent,
+ MouseEvent,
+ ProgressEvent,
+ TouchEvent,
+ TransitionEvent,
+ UIEvent,
+ WheelEvent,
+} =
+ typeof window === 'undefined' ? /* istanbul ignore next */ global : window
+
+const eventMap = {
+ // Clipboard Events
+ copy: {
+ EventType: CompositionEvent,
+ defaultInit: {bubbles: true, cancelable: true},
+ },
+ cut: {
+ EventType: ClipboardEvent,
+ defaultInit: {bubbles: true, cancelable: true},
+ },
+ paste: {
+ EventType: ClipboardEvent,
+ defaultInit: {bubbles: true, cancelable: true},
+ },
+ // Composition Events
+ compositionEnd: {
+ EventType: CompositionEvent,
+ defaultInit: {bubbles: true, cancelable: true},
+ },
+ compositionStart: {
+ EventType: CompositionEvent,
+ defaultInit: {bubbles: true, cancelable: true},
+ },
+ compositionUpdate: {
+ EventType: CompositionEvent,
+ defaultInit: {bubbles: true, cancelable: false},
+ },
+ // Keyboard Events
+ keyDown: {
+ EventType: KeyboardEvent,
+ defaultInit: {bubbles: true, cancelable: true},
+ },
+ keyPress: {
+ EventType: KeyboardEvent,
+ defaultInit: {bubbles: true, cancelable: true},
+ },
+ keyUp: {
+ EventType: KeyboardEvent,
+ defaultInit: {bubbles: true, cancelable: true},
+ },
+ // Focus Events
+ focus: {
+ EventType: FocusEvent,
+ defaultInit: {bubbles: false, cancelable: false},
+ },
+ blur: {
+ EventType: FocusEvent,
+ defaultInit: {bubbles: false, cancelable: false},
+ },
+ // Form Events
+ change: {
+ EventType: InputEvent,
+ defaultInit: {bubbles: true, cancelable: true},
+ },
+ input: {
+ EventType: InputEvent,
+ defaultInit: {bubbles: true, cancelable: true},
+ },
+ invalid: {
+ EventType: Event,
+ defaultInit: {bubbles: false, cancelable: true},
+ },
+ submit: {
+ EventType: Event,
+ defaultInit: {bubbles: true, cancelable: true},
+ },
+ // Mouse Events
+ click: {
+ EventType: MouseEvent,
+ defaultInit: {bubbles: true, cancelable: true, button: 0},
+ },
+ contextMenu: {
+ EventType: MouseEvent,
+ defaultInit: {bubbles: true, cancelable: true},
+ },
+ dblClick: {
+ EventType: MouseEvent,
+ defaultInit: {bubbles: true, cancelable: true},
+ },
+ drag: {
+ EventType: DragEvent,
+ defaultInit: {bubbles: true, cancelable: true},
+ },
+ dragEnd: {
+ EventType: DragEvent,
+ defaultInit: {bubbles: true, cancelable: false},
+ },
+ dragEnter: {
+ EventType: DragEvent,
+ defaultInit: {bubbles: true, cancelable: true},
+ },
+ dragExit: {
+ EventType: DragEvent,
+ defaultInit: {bubbles: true, cancelable: false},
+ },
+ dragLeave: {
+ EventType: DragEvent,
+ defaultInit: {bubbles: true, cancelable: false},
+ },
+ dragOver: {
+ EventType: DragEvent,
+ defaultInit: {bubbles: true, cancelable: true},
+ },
+ dragStart: {
+ EventType: DragEvent,
+ defaultInit: {bubbles: true, cancelable: true},
+ },
+ drop: {
+ EventType: DragEvent,
+ defaultInit: {bubbles: true, cancelable: true},
+ },
+ mouseDown: {
+ EventType: MouseEvent,
+ defaultInit: {bubbles: true, cancelable: true},
+ },
+ mouseEnter: {
+ EventType: MouseEvent,
+ defaultInit: {bubbles: true, cancelable: true},
+ },
+ mouseLeave: {
+ EventType: MouseEvent,
+ defaultInit: {bubbles: true, cancelable: true},
+ },
+ mouseMove: {
+ EventType: MouseEvent,
+ defaultInit: {bubbles: true, cancelable: true},
+ },
+ mouseOut: {
+ EventType: MouseEvent,
+ defaultInit: {bubbles: true, cancelable: true},
+ },
+ mouseOver: {
+ EventType: MouseEvent,
+ defaultInit: {bubbles: true, cancelable: true},
+ },
+ mouseUp: {
+ EventType: MouseEvent,
+ defaultInit: {bubbles: true, cancelable: true},
+ },
+ // Selection Events
+ select: {
+ EventType: Event,
+ defaultInit: {bubbles: true, cancelable: false},
+ },
+ // Touch Events
+ touchCancel: {
+ EventType: TouchEvent,
+ defaultInit: {bubbles: true, cancelable: false},
+ },
+ touchEnd: {
+ EventType: TouchEvent,
+ defaultInit: {bubbles: true, cancelable: true},
+ },
+ touchMove: {
+ EventType: TouchEvent,
+ defaultInit: {bubbles: true, cancelable: true},
+ },
+ touchStart: {
+ EventType: TouchEvent,
+ defaultInit: {bubbles: true, cancelable: true},
+ },
+ // UI Events
+ scroll: {
+ EventType: UIEvent,
+ defaultInit: {bubbles: false, cancelable: false},
+ },
+ // Wheel Events
+ wheel: {
+ EventType: WheelEvent,
+ defaultInit: {bubbles: true, cancelable: true},
+ },
+ // Media Events
+ abort: {
+ EventType: Event,
+ defaultInit: {bubbles: false, cancelable: false},
+ },
+ canPlay: {
+ EventType: Event,
+ defaultInit: {bubbles: false, cancelable: false},
+ },
+ canPlayThrough: {
+ EventType: Event,
+ defaultInit: {bubbles: false, cancelable: false},
+ },
+ durationChange: {
+ EventType: Event,
+ defaultInit: {bubbles: false, cancelable: false},
+ },
+ emptied: {
+ EventType: Event,
+ defaultInit: {bubbles: false, cancelable: false},
+ },
+ encrypted: {
+ EventType: Event,
+ defaultInit: {bubbles: false, cancelable: false},
+ },
+ ended: {
+ EventType: Event,
+ defaultInit: {bubbles: false, cancelable: false},
+ },
+ // error: {
+ // EventType: Event,
+ // defaultInit: {bubbles: false, cancelable: false},
+ // },
+ loadedData: {
+ EventType: Event,
+ defaultInit: {bubbles: false, cancelable: false},
+ },
+ loadedMetadata: {
+ EventType: Event,
+ defaultInit: {bubbles: false, cancelable: false},
+ },
+ loadStart: {
+ EventType: ProgressEvent,
+ defaultInit: {bubbles: false, cancelable: false},
+ },
+ pause: {
+ EventType: Event,
+ defaultInit: {bubbles: false, cancelable: false},
+ },
+ play: {
+ EventType: Event,
+ defaultInit: {bubbles: false, cancelable: false},
+ },
+ playing: {
+ EventType: Event,
+ defaultInit: {bubbles: false, cancelable: false},
+ },
+ progress: {
+ EventType: ProgressEvent,
+ defaultInit: {bubbles: false, cancelable: false},
+ },
+ rateChange: {
+ EventType: Event,
+ defaultInit: {bubbles: false, cancelable: false},
+ },
+ seeked: {
+ EventType: Event,
+ defaultInit: {bubbles: false, cancelable: false},
+ },
+ seeking: {
+ EventType: Event,
+ defaultInit: {bubbles: false, cancelable: false},
+ },
+ stalled: {
+ EventType: Event,
+ defaultInit: {bubbles: false, cancelable: false},
+ },
+ suspend: {
+ EventType: Event,
+ defaultInit: {bubbles: false, cancelable: false},
+ },
+ timeUpdate: {
+ EventType: Event,
+ defaultInit: {bubbles: false, cancelable: false},
+ },
+ volumeChange: {
+ EventType: Event,
+ defaultInit: {bubbles: false, cancelable: false},
+ },
+ waiting: {
+ EventType: Event,
+ defaultInit: {bubbles: false, cancelable: false},
+ },
+ // Image Events
+ load: {
+ EventType: UIEvent,
+ defaultInit: {bubbles: false, cancelable: false},
+ },
+ error: {
+ EventType: Event,
+ defaultInit: {bubbles: false, cancelable: false},
+ },
+ // Animation Events
+ animationStart: {
+ EventType: AnimationEvent,
+ defaultInit: {bubbles: true, cancelable: false},
+ },
+ animationEnd: {
+ EventType: AnimationEvent,
+ defaultInit: {bubbles: true, cancelable: false},
+ },
+ animationIteration: {
+ EventType: AnimationEvent,
+ defaultInit: {bubbles: true, cancelable: false},
+ },
+ // Transition Events
+ transitionEnd: {
+ EventType: TransitionEvent,
+ defaultInit: {bubbles: true, cancelable: true},
+ },
+}
+
+const eventAliasMap = {
+ doubleClick: 'dblClick',
+}
+
+function fireEvent(element, event) {
+ return element.dispatchEvent(event)
+}
+
+Object.entries(eventMap).forEach(([key, {EventType = Event, defaultInit}]) => {
+ const eventName = key.toLowerCase()
+
+ fireEvent[key] = (node, init) => {
+ const eventInit = Object.assign({}, defaultInit, init)
+ const event = new EventType(eventName, eventInit)
+ return fireEvent(node, event)
+ }
+})
+
+Object.entries(eventAliasMap).forEach(([aliasKey, key]) => {
+ fireEvent[aliasKey] = (...args) => fireEvent[key](...args)
+})
+
+export {fireEvent}
diff --git a/src/index.js b/src/index.js
index 2a80220e..df1a0fd4 100644
--- a/src/index.js
+++ b/src/index.js
@@ -8,3 +8,4 @@ export * from './queries'
export * from './wait'
export * from './wait-for-element'
export * from './matches'
+export * from './events'