Skip to content

Commit

Permalink
Merge pull request #75 from testing-library/immutable-fireevent
Browse files Browse the repository at this point in the history
Use and expose a local copy of fireEvent
  • Loading branch information
afontcu authored Aug 13, 2019
2 parents 0e2aa0a + c6a231f commit f462051
Show file tree
Hide file tree
Showing 3 changed files with 201 additions and 7 deletions.
23 changes: 17 additions & 6 deletions src/vue-testing-library.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {
getQueriesForElement,
logDOM,
wait,
fireEvent
fireEvent as dtlFireEvent
} from '@testing-library/dom'

const mountedWrappers = new Set()
Expand Down Expand Up @@ -91,10 +91,18 @@ function cleanupAtWrapper(wrapper) {
mountedWrappers.delete(wrapper)
}

Object.keys(fireEvent).forEach(fn => {
fireEvent[`_${fn}`] = fireEvent[fn]
fireEvent[fn] = async (...params) => {
fireEvent[`_${fn}`](...params)
// Vue Testing Library's version of fireEvent will call DOM Testing Library's
// version of fireEvent plus wait for one tick of the event loop to allow Vue
// to asynchronously handle the event.
// More info: https://vuejs.org/v2/guide/reactivity.html#Async-Update-Queue
async function fireEvent(...args) {
dtlFireEvent(...args)
await wait()
}

Object.keys(dtlFireEvent).forEach(key => {
fireEvent[key] = async (...args) => {
dtlFireEvent[key](...args)
await wait()
}
})
Expand All @@ -104,6 +112,9 @@ fireEvent.touch = async elem => {
await fireEvent.blur(elem)
}

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

export * from '@testing-library/dom'
export { cleanup, render }
export { cleanup, render, fireEvent }
2 changes: 1 addition & 1 deletion tests/__tests__/components/Button.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export default {
props: {
text: {
type: String,
required: true
default: 'Button Text'
}
},
methods: {
Expand Down
183 changes: 183 additions & 0 deletions tests/__tests__/fire-event.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
import { render, fireEvent } from '@testing-library/vue'
import Button from './components/Button'

const eventTypes = [
{
type: 'Clipboard',
events: ['copy', 'paste']
},
{
type: 'Composition',
events: ['compositionEnd', 'compositionStart', 'compositionUpdate']
},
{
type: 'Keyboard',
events: ['keyDown', 'keyPress', 'keyUp'],
init: { keyCode: 13 }
},
{
type: 'Focus',
events: ['focus', 'blur']
},
{
type: 'Form',
events: ['focus', 'blur']
},
{
type: 'Focus',
events: ['input', 'invalid']
},
{
type: 'Focus',
events: ['submit'],
elementType: 'form'
},
{
type: 'Mouse',
events: [
'click',
'contextMenu',
'drag',
'dragEnd',
'dragEnter',
'dragExit',
'dragLeave',
'dragOver',
'dragStart',
'drop',
'mouseDown',
'mouseEnter',
'mouseLeave',
'mouseMove',
'mouseOut',
'mouseOver',
'mouseUp'
],
elementType: 'button'
},
{
type: 'Selection',
events: ['select']
},
{
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'
}
]

// For each event type, we assert that the right events are being triggered
// when the associated fireEvent method is called.
eventTypes.forEach(({ type, events, elementType = 'input', init }) => {
describe(`${type} Events`, () => {
events.forEach(eventName => {
it(`triggers ${eventName}`, async () => {
const testId = `${type}-${eventName}`
const spy = jest.fn()

// Render an element with a listener of the event under testing and a
// test-id attribute, so that we can get the DOM node afterwards.
const { getByTestId } = render({
render(h) {
return h(elementType, {
on: {
[eventName.toLowerCase()]: spy
},
attrs: {
'data-testid': testId
}
})
}
})

const elem = getByTestId(testId)

await fireEvent[eventName](elem, init)
expect(spy).toHaveBeenCalledTimes(1)
})
})
})
})

// The event is called `dblclick`, but fireEvent exposes a "doubleClick" method
test('triggers dblclick on doubleClick', async () => {
const spy = jest.fn()

const { getByRole } = render({
render(h) {
return h('input', {
on: { dblclick: spy }
})
}
})

const elem = getByRole('textbox')

await fireEvent.doubleClick(elem)
expect(spy).toHaveBeenCalledTimes(1)
})

// fireEvent(node, event) is also a valid API
test('calling `fireEvent` directly works too', async () => {
const { getByRole, emitted } = render(Button)

const button = getByRole('button')

await fireEvent(button, new Event('click'))

expect(emitted()).toHaveProperty('click')
})

0 comments on commit f462051

Please sign in to comment.