Skip to content

Commit 959befd

Browse files
authored
Merge 9599683 into 9937f01
2 parents 9937f01 + 9599683 commit 959befd

File tree

5 files changed

+191
-174
lines changed

5 files changed

+191
-174
lines changed

src/__tests__/fire-event.js

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,11 +201,23 @@ test.each(['input', 'change'])(
201201

202202
expect(console.warn).toHaveBeenCalledTimes(1)
203203
expect(console.warn).toHaveBeenCalledWith(
204-
`Using fireEvent.${event}() may lead to unexpected results. Please use fireEvent.update() instead.`,
204+
`Using "fireEvent.${event}" may lead to unexpected results. Please use fireEvent.update() instead.`,
205205
)
206206
},
207207
)
208208

209+
test('does not warn when disabled via env var', async () => {
210+
process.env.VTL_SKIP_WARN_EVENT_UPDATE = 'true'
211+
212+
const {getByTestId} = render({
213+
template: `<input type="text" data-testid="test-update" />`,
214+
})
215+
216+
await fireEvent.input(getByTestId('test-update'), 'hello')
217+
218+
expect(console.warn).not.toHaveBeenCalled()
219+
})
220+
209221
test('fireEvent.update does not trigger warning messages', async () => {
210222
const {getByTestId} = render({
211223
template: `<input type="text" data-testid="test-update" />`,

src/__tests__/vue-apollo.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import '@testing-library/jest-dom'
22
import fetch from 'isomorphic-unfetch'
3-
import {render, fireEvent, screen} from '..'
43
import {DefaultApolloClient} from '@vue/apollo-composable'
54
import ApolloClient from 'apollo-boost'
65
import {setupServer} from 'msw/node'
76
import {graphql} from 'msw'
7+
import {render, fireEvent, screen} from '..'
88
import Component from './components/VueApollo.vue'
99

1010
// Since vue-apollo doesn't provide a MockProvider for Vue,

src/fire-event.js

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
/* eslint-disable testing-library/no-wait-for-empty-callback */
2+
import {waitFor, fireEvent as dtlFireEvent} from '@testing-library/dom'
3+
4+
// Vue Testing Lib's version of fireEvent will call DOM Testing Lib's
5+
// version of fireEvent. The reason is because we need to wait another
6+
// event loop tick to allow Vue to flush and update the DOM
7+
// More info: https://vuejs.org/v2/guide/reactivity.html#Async-Update-Queue
8+
9+
async function fireEvent(...args) {
10+
dtlFireEvent(...args)
11+
await waitFor(() => {})
12+
}
13+
14+
Object.keys(dtlFireEvent).forEach(key => {
15+
fireEvent[key] = async (...args) => {
16+
warnOnChangeOrInputEventCalledDirectly(args[1], key)
17+
18+
dtlFireEvent[key](...args)
19+
await waitFor(() => {})
20+
}
21+
})
22+
23+
fireEvent.touch = async elem => {
24+
await fireEvent.focus(elem)
25+
await fireEvent.blur(elem)
26+
}
27+
28+
// fireEvent.update is a small utility to provide a better experience when
29+
// working with v-model.
30+
// Related upstream issue: https://github.com/vuejs/vue-test-utils/issues/345#issuecomment-380588199
31+
// Examples: https://github.com/testing-library/vue-testing-library/blob/master/src/__tests__/form.js fireEvent.update = (elem, value) => {
32+
fireEvent.update = (elem, value) => {
33+
const tagName = elem.tagName
34+
const type = elem.type
35+
36+
switch (tagName) {
37+
case 'OPTION': {
38+
elem.selected = true
39+
40+
const parentSelectElement =
41+
elem.parentElement.tagName === 'OPTGROUP'
42+
? elem.parentElement.parentElement
43+
: elem.parentElement
44+
45+
return fireEvent.change(parentSelectElement)
46+
}
47+
48+
case 'INPUT': {
49+
if (['checkbox', 'radio'].includes(type)) {
50+
elem.checked = true
51+
return fireEvent.change(elem)
52+
} else if (type === 'file') {
53+
return fireEvent.change(elem)
54+
} else {
55+
elem.value = value
56+
return fireEvent.input(elem)
57+
}
58+
}
59+
60+
case 'TEXTAREA': {
61+
elem.value = value
62+
return fireEvent.input(elem)
63+
}
64+
65+
case 'SELECT': {
66+
elem.value = value
67+
return fireEvent.change(elem)
68+
}
69+
70+
default:
71+
// do nothing
72+
}
73+
74+
return null
75+
}
76+
77+
function warnOnChangeOrInputEventCalledDirectly(eventValue, eventKey) {
78+
if (process.env.VTL_SKIP_WARN_EVENT_UPDATE) return
79+
80+
if (eventValue && (eventKey === 'change' || eventKey === 'input')) {
81+
console.warn(
82+
`Using "fireEvent.${eventKey}" may lead to unexpected results. Please use fireEvent.update() instead.`,
83+
)
84+
}
85+
}
86+
87+
export {fireEvent}

src/index.js

Lines changed: 5 additions & 172 deletions
Original file line numberDiff line numberDiff line change
@@ -1,176 +1,8 @@
1-
/* eslint-disable testing-library/no-wait-for-empty-callback */
2-
import {mount} from '@vue/test-utils'
3-
4-
import {
5-
getQueriesForElement,
6-
prettyDOM,
7-
waitFor,
8-
fireEvent as dtlFireEvent,
9-
} from '@testing-library/dom'
10-
11-
const mountedWrappers = new Set()
12-
13-
function render(
14-
Component,
15-
{
16-
store = null,
17-
routes = null,
18-
container: customContainer,
19-
baseElement: customBaseElement,
20-
...mountOptions
21-
} = {},
22-
) {
23-
const div = document.createElement('div')
24-
const baseElement = customBaseElement || customContainer || document.body
25-
const container = customContainer || baseElement.appendChild(div)
26-
27-
const plugins = mountOptions.global?.plugins || []
28-
29-
if (store) {
30-
const {createStore} = require('vuex')
31-
plugins.push(createStore(store))
32-
}
33-
34-
if (routes) {
35-
const requiredRouter = require('vue-router')
36-
const {createRouter, createWebHistory} =
37-
requiredRouter.default || requiredRouter
38-
39-
const routerPlugin = createRouter({history: createWebHistory(), routes})
40-
plugins.push(routerPlugin)
41-
}
42-
43-
const wrapper = mount(Component, {
44-
...mountOptions,
45-
attachTo: container,
46-
global: {...mountOptions.global, plugins},
47-
})
48-
49-
// this removes the additional "data-v-app" div node from VTU:
50-
// https://github.com/vuejs/vue-test-utils-next/blob/master/src/mount.ts#L196-L213
51-
unwrapNode(wrapper.parentElement)
52-
53-
mountedWrappers.add(wrapper)
54-
55-
return {
56-
container,
57-
baseElement,
58-
debug: (el = baseElement, maxLength, options) =>
59-
Array.isArray(el)
60-
? el.forEach(e => console.log(prettyDOM(e, maxLength, options)))
61-
: console.log(prettyDOM(el, maxLength, options)),
62-
unmount: () => wrapper.unmount(),
63-
html: () => wrapper.html(),
64-
emitted: () => wrapper.emitted(),
65-
rerender: props => wrapper.setProps(props),
66-
...getQueriesForElement(baseElement),
67-
}
68-
}
69-
70-
function unwrapNode(node) {
71-
node.replaceWith(...node.childNodes)
72-
}
73-
74-
function cleanup() {
75-
mountedWrappers.forEach(cleanupAtWrapper)
76-
}
77-
78-
function cleanupAtWrapper(wrapper) {
79-
if (
80-
wrapper.element.parentNode &&
81-
wrapper.element.parentNode.parentNode === document.body
82-
) {
83-
document.body.removeChild(wrapper.element.parentNode)
84-
}
85-
86-
wrapper.unmount()
87-
mountedWrappers.delete(wrapper)
88-
}
89-
90-
// Vue Testing Library's version of fireEvent will call DOM Testing Library's
91-
// version of fireEvent plus wait for one tick of the event loop to allow Vue
92-
// to asynchronously handle the event.
93-
// More info: https://vuejs.org/v2/guide/reactivity.html#Async-Update-Queue
94-
async function fireEvent(...args) {
95-
dtlFireEvent(...args)
96-
await waitFor(() => {})
97-
}
98-
99-
function suggestUpdateIfNecessary(eventValue, eventKey) {
100-
const changeOrInputEventCalledDirectly =
101-
eventValue && (eventKey === 'change' || eventKey === 'input')
102-
103-
if (changeOrInputEventCalledDirectly) {
104-
console.warn(
105-
`Using fireEvent.${eventKey}() may lead to unexpected results. Please use fireEvent.update() instead.`,
106-
)
107-
}
108-
}
109-
110-
Object.keys(dtlFireEvent).forEach(key => {
111-
fireEvent[key] = async (...args) => {
112-
suggestUpdateIfNecessary(args[1], key)
113-
dtlFireEvent[key](...args)
114-
await waitFor(() => {})
115-
}
116-
})
117-
118-
fireEvent.touch = async elem => {
119-
await fireEvent.focus(elem)
120-
await fireEvent.blur(elem)
121-
}
122-
123-
// Small utility to provide a better experience when working with v-model.
124-
// Related upstream issue: https://github.com/vuejs/vue-test-utils/issues/345#issuecomment-380588199
125-
// Examples: https://github.com/testing-library/vue-testing-library/blob/master/src/__tests__/form.js
126-
fireEvent.update = (elem, value) => {
127-
const tagName = elem.tagName
128-
const type = elem.type
129-
130-
switch (tagName) {
131-
case 'OPTION': {
132-
elem.selected = true
133-
134-
const parentSelectElement =
135-
elem.parentElement.tagName === 'OPTGROUP'
136-
? elem.parentElement.parentElement
137-
: elem.parentElement
138-
139-
return fireEvent.change(parentSelectElement)
140-
}
141-
142-
case 'INPUT': {
143-
if (['checkbox', 'radio'].includes(type)) {
144-
elem.checked = true
145-
return fireEvent.change(elem)
146-
} else if (type === 'file') {
147-
return fireEvent.change(elem)
148-
} else {
149-
elem.value = value
150-
return fireEvent.input(elem)
151-
}
152-
}
153-
154-
case 'TEXTAREA': {
155-
elem.value = value
156-
return fireEvent.input(elem)
157-
}
158-
159-
case 'SELECT': {
160-
elem.value = value
161-
return fireEvent.change(elem)
162-
}
163-
164-
default:
165-
// do nothing
166-
}
167-
168-
return null
169-
}
1+
import {cleanup} from './render'
1702

1713
// If we're running in a test runner that supports afterEach then we'll
172-
// automatically run cleanup after each test. This ensures that tests run in
173-
// isolation from each other.
4+
// automatically run cleanup after each test.
5+
// This ensures that tests run in isolation from each other.
1746
// If you don't like this, set the VTL_SKIP_AUTO_CLEANUP variable to 'true'.
1757
if (typeof afterEach === 'function' && !process.env.VTL_SKIP_AUTO_CLEANUP) {
1768
afterEach(() => {
@@ -179,4 +11,5 @@ if (typeof afterEach === 'function' && !process.env.VTL_SKIP_AUTO_CLEANUP) {
17911
}
18012

18113
export * from '@testing-library/dom'
182-
export {cleanup, render, fireEvent}
14+
export {cleanup, render} from './render'
15+
export {fireEvent} from './fire-event'

src/render.js

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
/* eslint-disable testing-library/no-wait-for-empty-callback */
2+
import {mount} from '@vue/test-utils'
3+
4+
import {getQueriesForElement, prettyDOM} from '@testing-library/dom'
5+
6+
const mountedWrappers = new Set()
7+
8+
function render(
9+
Component,
10+
{
11+
store = null,
12+
routes = null,
13+
container: customContainer,
14+
baseElement: customBaseElement,
15+
...mountOptions
16+
} = {},
17+
) {
18+
const div = document.createElement('div')
19+
const baseElement = customBaseElement || customContainer || document.body
20+
const container = customContainer || baseElement.appendChild(div)
21+
22+
const plugins = mountOptions.global?.plugins || []
23+
24+
if (store) {
25+
const {createStore} = require('vuex')
26+
plugins.push(createStore(store))
27+
}
28+
29+
if (routes) {
30+
const requiredRouter = require('vue-router')
31+
const {createRouter, createWebHistory} =
32+
requiredRouter.default || requiredRouter
33+
34+
const routerPlugin = createRouter({history: createWebHistory(), routes})
35+
plugins.push(routerPlugin)
36+
}
37+
38+
const wrapper = mount(Component, {
39+
...mountOptions,
40+
attachTo: container,
41+
global: {...mountOptions.global, plugins},
42+
})
43+
44+
// this removes the additional "data-v-app" div node from VTU:
45+
// https://github.com/vuejs/vue-test-utils-next/blob/master/src/mount.ts#L196-L213
46+
unwrapNode(wrapper.parentElement)
47+
48+
mountedWrappers.add(wrapper)
49+
50+
return {
51+
container,
52+
baseElement,
53+
debug: (el = baseElement, maxLength, options) =>
54+
Array.isArray(el)
55+
? el.forEach(e => console.log(prettyDOM(e, maxLength, options)))
56+
: console.log(prettyDOM(el, maxLength, options)),
57+
unmount: () => wrapper.unmount(),
58+
html: () => wrapper.html(),
59+
emitted: () => wrapper.emitted(),
60+
rerender: props => wrapper.setProps(props),
61+
...getQueriesForElement(baseElement),
62+
}
63+
}
64+
65+
function unwrapNode(node) {
66+
node.replaceWith(...node.childNodes)
67+
}
68+
69+
function cleanup() {
70+
mountedWrappers.forEach(cleanupAtWrapper)
71+
}
72+
73+
function cleanupAtWrapper(wrapper) {
74+
if (
75+
wrapper.element.parentNode &&
76+
wrapper.element.parentNode.parentNode === document.body
77+
) {
78+
document.body.removeChild(wrapper.element.parentNode)
79+
}
80+
81+
wrapper.unmount()
82+
mountedWrappers.delete(wrapper)
83+
}
84+
85+
export {render, cleanup}

0 commit comments

Comments
 (0)