Repository: kentcdodds/dom-testing-library
Pull request #13 (2018-04-09 21:14:36)

added fireEvent util
<!--
Thanks for your interest in the project. Bugs filed and PRs submitted are appreciated!

Please make sure that you are familiar with and follow the Code of Conduct for
this project (found in the CODE_OF_CONDUCT.md file).

Also, please make sure you're familiar with and follow the instructions in the
contributing guidelines (found in the CONTRIBUTING.md file).

If you're new to contributing to open source projects, you might find this free
video course helpful: http://kcd.im/pull-request

Please fill out the information below to expedite the review and (hopefully)
merge of your pull request!
-->

<!-- What changes are being made? (What feature/bug is being fixed here?) -->

**What**: added fireEvent utility

<!-- Why are these changes necessary? -->

**Why**: https://github.com/kentcdodds/dom-testing-library/issues/5

<!-- How were these changes implemented? -->

**How**: src/events.js

<!-- Have you done all of these things?  -->

**Checklist**:

<!-- add "N/A" to the end of each line that's irrelevant to your changes -->

<!-- to check an item, place an "x" in the box like so: "- [x] Documentation" -->

* [x] Documentation
* [x] Tests
* [x] Ready to be merged <!-- In your opinion, is this ready to be merged as soon as it's reviewed? -->
* [x] Added myself to contributors table <!-- this is optional, see the contributing guidelines for instructions -->

<!-- feel free to add additional comments -->


sompylasar: > there are a couple events we'll need to fallback to triggering synthetic events for (change, select, mouseenter, mouseleave) so I'll add that logic there

@jomaxx Could you please clarify about synthetic events? How does Cypress handles these events from within the browser?
jomaxx: @sompylasar let's move discussion about synthetic events to https://github.com/kentcdodds/react-testing-library/pull/48

Patch:
diff --git a/.all-contributorsrc b/.all-contributorsrc
index 9627711..ac1795b 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 192e6fe..69809c0 100644
--- a/README.md
+++ b/README.md
@@ -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]
 
@@ -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
+// <button>Submit</button>
+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
+// <button>Submit</button>
+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]):
 <!-- 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/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") | [<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/dom-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/dom-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/dom-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/dom-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/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") | [<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/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") | [<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/dom-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/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") | [<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/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") | [<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/dom-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/dom-testing-library/commits?author=gnapse "Code") [📖](https://github.com/kentcdodds/dom-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/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") | [<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/dom-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/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") | [<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/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") | [<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/dom-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/dom-testing-library/commits?author=gnapse "Code") [📖](https://github.com/kentcdodds/dom-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/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") |
 
 <!-- ALL-CONTRIBUTORS-LIST:END -->
 
diff --git a/package.json b/package.json
index a374bba..1ba1cfe 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/events.js b/src/events.js
new file mode 100644
index 0000000..ad5a8e3
--- /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 2a80220..df1a0fd 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'


Test patch:
diff --git a/src/__tests__/events.js b/src/__tests__/events.js
new file mode 100644
index 0000000..b57e2f2
--- /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)
+  })
+})