From d32fd24b3f9e3b9273c56f74699639c8666965bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Fontcuberta?= Date: Sat, 10 Aug 2019 11:38:01 +0200 Subject: [PATCH 1/4] Create local version of fireEvent --- src/vue-testing-library.js | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/src/vue-testing-library.js b/src/vue-testing-library.js index ac8dca64..37fa7d6e 100644 --- a/src/vue-testing-library.js +++ b/src/vue-testing-library.js @@ -4,7 +4,7 @@ import { getQueriesForElement, prettyDOM, wait, - fireEvent + fireEvent as dtlFireEvent } from '@testing-library/dom' const mountedWrappers = new Set() @@ -91,10 +91,16 @@ 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 so that... +async function fireEvent(...args) { + dtlFireEvent(...args) + await wait() +} + +Object.keys(dtlFireEvent).forEach(key => { + fireEvent[key] = async (...args) => { + dtlFireEvent[key](...args) await wait() } }) @@ -104,6 +110,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 @@ -143,4 +152,4 @@ fireEvent.update = async (elem, value) => { } export * from '@testing-library/dom' -export { cleanup, render } +export { cleanup, render, fireEvent } From 89845b6a01139ed348bf9c597835f49305b0fd42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Fontcuberta?= Date: Sat, 10 Aug 2019 11:39:19 +0200 Subject: [PATCH 2/4] Add tests for fireEvent --- tests/__tests__/components/Button.vue | 2 +- tests/__tests__/fire-event.js | 183 ++++++++++++++++++++++++++ 2 files changed, 184 insertions(+), 1 deletion(-) create mode 100644 tests/__tests__/fire-event.js diff --git a/tests/__tests__/components/Button.vue b/tests/__tests__/components/Button.vue index cfbf37f1..9adc0241 100644 --- a/tests/__tests__/components/Button.vue +++ b/tests/__tests__/components/Button.vue @@ -7,7 +7,7 @@ export default { props: { text: { type: String, - required: true + default: 'Button Text' } }, methods: { diff --git a/tests/__tests__/fire-event.js b/tests/__tests__/fire-event.js new file mode 100644 index 00000000..54773d77 --- /dev/null +++ b/tests/__tests__/fire-event.js @@ -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`, while fireEvent exposes a "doubleClick" +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') +}) From d1be1951398ab74bede84c6e9d371bdc2417a5b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Fontcuberta?= Date: Sat, 10 Aug 2019 11:50:15 +0200 Subject: [PATCH 3/4] Add missing word --- tests/__tests__/fire-event.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/__tests__/fire-event.js b/tests/__tests__/fire-event.js index 54773d77..cf3721e0 100644 --- a/tests/__tests__/fire-event.js +++ b/tests/__tests__/fire-event.js @@ -153,7 +153,7 @@ eventTypes.forEach(({ type, events, elementType = 'input', init }) => { }) }) -// The event is called `dblclick`, while fireEvent exposes a "doubleClick" +// The event is called `dblclick`, but fireEvent exposes a "doubleClick" method test('triggers dblclick on doubleClick', async () => { const spy = jest.fn() From 38ad28f81b5f1a4f9738bf6d571b8c77074e84b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Fontcuberta?= Date: Sat, 10 Aug 2019 18:36:17 +0200 Subject: [PATCH 4/4] Add async events explanation --- src/vue-testing-library.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/vue-testing-library.js b/src/vue-testing-library.js index 37fa7d6e..a96cadd5 100644 --- a/src/vue-testing-library.js +++ b/src/vue-testing-library.js @@ -92,7 +92,9 @@ function cleanupAtWrapper(wrapper) { } // 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 so that... +// 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()