Skip to content

Commit f462051

Browse files
authored
Merge pull request #75 from testing-library/immutable-fireevent
Use and expose a local copy of fireEvent
2 parents 0e2aa0a + c6a231f commit f462051

File tree

3 files changed

+201
-7
lines changed

3 files changed

+201
-7
lines changed

src/vue-testing-library.js

+17-6
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import {
44
getQueriesForElement,
55
logDOM,
66
wait,
7-
fireEvent
7+
fireEvent as dtlFireEvent
88
} from '@testing-library/dom'
99

1010
const mountedWrappers = new Set()
@@ -91,10 +91,18 @@ function cleanupAtWrapper(wrapper) {
9191
mountedWrappers.delete(wrapper)
9292
}
9393

94-
Object.keys(fireEvent).forEach(fn => {
95-
fireEvent[`_${fn}`] = fireEvent[fn]
96-
fireEvent[fn] = async (...params) => {
97-
fireEvent[`_${fn}`](...params)
94+
// Vue Testing Library's version of fireEvent will call DOM Testing Library's
95+
// version of fireEvent plus wait for one tick of the event loop to allow Vue
96+
// to asynchronously handle the event.
97+
// More info: https://vuejs.org/v2/guide/reactivity.html#Async-Update-Queue
98+
async function fireEvent(...args) {
99+
dtlFireEvent(...args)
100+
await wait()
101+
}
102+
103+
Object.keys(dtlFireEvent).forEach(key => {
104+
fireEvent[key] = async (...args) => {
105+
dtlFireEvent[key](...args)
98106
await wait()
99107
}
100108
})
@@ -104,6 +112,9 @@ fireEvent.touch = async elem => {
104112
await fireEvent.blur(elem)
105113
}
106114

115+
// Small utility to provide a better experience when working with v-model.
116+
// Related upstream issue: https://github.com/vuejs/vue-test-utils/issues/345#issuecomment-380588199
117+
// Examples: https://github.com/testing-library/vue-testing-library/blob/master/tests/__tests__/form.js
107118
fireEvent.update = async (elem, value) => {
108119
const tagName = elem.tagName
109120
const type = elem.type
@@ -143,4 +154,4 @@ fireEvent.update = async (elem, value) => {
143154
}
144155

145156
export * from '@testing-library/dom'
146-
export { cleanup, render }
157+
export { cleanup, render, fireEvent }

tests/__tests__/components/Button.vue

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ export default {
77
props: {
88
text: {
99
type: String,
10-
required: true
10+
default: 'Button Text'
1111
}
1212
},
1313
methods: {

tests/__tests__/fire-event.js

+183
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
import { render, fireEvent } from '@testing-library/vue'
2+
import Button from './components/Button'
3+
4+
const eventTypes = [
5+
{
6+
type: 'Clipboard',
7+
events: ['copy', 'paste']
8+
},
9+
{
10+
type: 'Composition',
11+
events: ['compositionEnd', 'compositionStart', 'compositionUpdate']
12+
},
13+
{
14+
type: 'Keyboard',
15+
events: ['keyDown', 'keyPress', 'keyUp'],
16+
init: { keyCode: 13 }
17+
},
18+
{
19+
type: 'Focus',
20+
events: ['focus', 'blur']
21+
},
22+
{
23+
type: 'Form',
24+
events: ['focus', 'blur']
25+
},
26+
{
27+
type: 'Focus',
28+
events: ['input', 'invalid']
29+
},
30+
{
31+
type: 'Focus',
32+
events: ['submit'],
33+
elementType: 'form'
34+
},
35+
{
36+
type: 'Mouse',
37+
events: [
38+
'click',
39+
'contextMenu',
40+
'drag',
41+
'dragEnd',
42+
'dragEnter',
43+
'dragExit',
44+
'dragLeave',
45+
'dragOver',
46+
'dragStart',
47+
'drop',
48+
'mouseDown',
49+
'mouseEnter',
50+
'mouseLeave',
51+
'mouseMove',
52+
'mouseOut',
53+
'mouseOver',
54+
'mouseUp'
55+
],
56+
elementType: 'button'
57+
},
58+
{
59+
type: 'Selection',
60+
events: ['select']
61+
},
62+
{
63+
type: 'Touch',
64+
events: ['touchCancel', 'touchEnd', 'touchMove', 'touchStart'],
65+
elementType: 'button'
66+
},
67+
{
68+
type: 'UI',
69+
events: ['scroll'],
70+
elementType: 'div'
71+
},
72+
{
73+
type: 'Wheel',
74+
events: ['wheel'],
75+
elementType: 'div'
76+
},
77+
{
78+
type: 'Media',
79+
events: [
80+
'abort',
81+
'canPlay',
82+
'canPlayThrough',
83+
'durationChange',
84+
'emptied',
85+
'encrypted',
86+
'ended',
87+
'error',
88+
'loadedData',
89+
'loadedMetadata',
90+
'loadStart',
91+
'pause',
92+
'play',
93+
'playing',
94+
'progress',
95+
'rateChange',
96+
'seeked',
97+
'seeking',
98+
'stalled',
99+
'suspend',
100+
'timeUpdate',
101+
'volumeChange',
102+
'waiting'
103+
],
104+
elementType: 'video'
105+
},
106+
{
107+
type: 'Image',
108+
events: ['load', 'error'],
109+
elementType: 'img'
110+
},
111+
{
112+
type: 'Animation',
113+
events: ['animationStart', 'animationEnd', 'animationIteration'],
114+
elementType: 'div'
115+
},
116+
{
117+
type: 'Transition',
118+
events: ['transitionEnd'],
119+
elementType: 'div'
120+
}
121+
]
122+
123+
// For each event type, we assert that the right events are being triggered
124+
// when the associated fireEvent method is called.
125+
eventTypes.forEach(({ type, events, elementType = 'input', init }) => {
126+
describe(`${type} Events`, () => {
127+
events.forEach(eventName => {
128+
it(`triggers ${eventName}`, async () => {
129+
const testId = `${type}-${eventName}`
130+
const spy = jest.fn()
131+
132+
// Render an element with a listener of the event under testing and a
133+
// test-id attribute, so that we can get the DOM node afterwards.
134+
const { getByTestId } = render({
135+
render(h) {
136+
return h(elementType, {
137+
on: {
138+
[eventName.toLowerCase()]: spy
139+
},
140+
attrs: {
141+
'data-testid': testId
142+
}
143+
})
144+
}
145+
})
146+
147+
const elem = getByTestId(testId)
148+
149+
await fireEvent[eventName](elem, init)
150+
expect(spy).toHaveBeenCalledTimes(1)
151+
})
152+
})
153+
})
154+
})
155+
156+
// The event is called `dblclick`, but fireEvent exposes a "doubleClick" method
157+
test('triggers dblclick on doubleClick', async () => {
158+
const spy = jest.fn()
159+
160+
const { getByRole } = render({
161+
render(h) {
162+
return h('input', {
163+
on: { dblclick: spy }
164+
})
165+
}
166+
})
167+
168+
const elem = getByRole('textbox')
169+
170+
await fireEvent.doubleClick(elem)
171+
expect(spy).toHaveBeenCalledTimes(1)
172+
})
173+
174+
// fireEvent(node, event) is also a valid API
175+
test('calling `fireEvent` directly works too', async () => {
176+
const { getByRole, emitted } = render(Button)
177+
178+
const button = getByRole('button')
179+
180+
await fireEvent(button, new Event('click'))
181+
182+
expect(emitted()).toHaveProperty('click')
183+
})

0 commit comments

Comments
 (0)