Skip to content

Commit 18ca422

Browse files
rootroot
authored andcommitted
feat: prevent trigger on disabled items
1 parent 6ac1314 commit 18ca422

File tree

3 files changed

+172
-23
lines changed

3 files changed

+172
-23
lines changed

src/create-dom-event.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,18 +20,13 @@ const keyCodesByKeyName = {
2020

2121
function getEventProperties(eventParams) {
2222
const { modifier, meta, options } = eventParams
23-
const keyCode =
24-
keyCodesByKeyName[modifier] ||
25-
options.key ||
26-
options.keyCode ||
27-
options.code
23+
const keyCode = keyCodesByKeyName[modifier] || options.keyCode || options.code
2824

2925
return {
3026
...options, // What the user passed in as the second argument to #trigger
3127
bubbles: meta.bubbles,
3228
meta: meta.cancelable,
3329
// Any derived options should go here
34-
key: keyCode,
3530
keyCode,
3631
code: keyCode
3732
}
@@ -95,6 +90,7 @@ export default function createDOMEvent(
9590
const canSetProperty = !(
9691
propertyDescriptor && propertyDescriptor.set === undefined
9792
)
93+
9894
if (canSetProperty) {
9995
event[key] = options[key]
10096
}

src/dom-wrapper.ts

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,35 @@ export class DOMWrapper<ElementType extends Element> implements WrapperAPI {
137137
}
138138

139139
async trigger(eventString: string, options: Object = {}) {
140-
if (this.element) {
140+
if (options['target']) {
141+
throw Error(
142+
`[vue-test-utils]: you cannot set the target value of an event. See the notes section ` +
143+
`of the docs for more details—` +
144+
`https://vue-test-utils.vuejs.org/api/wrapper/trigger.html`
145+
)
146+
}
147+
148+
const isDisabled = () => {
149+
const validTagsToBeDisabled = [
150+
'BUTTON',
151+
'COMMAND',
152+
'FIELDSET',
153+
'KEYGEN',
154+
'OPTGROUP',
155+
'OPTION',
156+
'SELECT',
157+
'TEXTAREA',
158+
'INPUT'
159+
]
160+
const hasDisabledAttribute = this.attributes().disabled !== undefined
161+
const elementCanBeDisabled = validTagsToBeDisabled.includes(
162+
this.element.tagName
163+
)
164+
165+
return hasDisabledAttribute && elementCanBeDisabled
166+
}
167+
168+
if (this.element && !isDisabled()) {
141169
const event = createDOMEvent(eventString, options)
142170
this.element.dispatchEvent(event)
143171
}

tests/trigger.spec.ts

Lines changed: 141 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -77,43 +77,65 @@ describe('trigger', () => {
7777
const keydownHandler = jest.fn()
7878
const Component = {
7979
template: '<input @keydown="keydownHandler" />',
80-
methods: {
81-
keydownHandler
82-
}
80+
methods: { keydownHandler }
8381
}
8482
const wrapper = mount(Component, {})
85-
await wrapper.trigger('keydown')
8683

84+
// is not fired when a diferent event is triggered
85+
await wrapper.trigger('click')
86+
expect(keydownHandler).not.toHaveBeenCalled()
87+
88+
// is called when 'keydown' is triggered
89+
await wrapper.trigger('keydown')
8790
expect(keydownHandler).toHaveBeenCalledTimes(1)
91+
92+
// is called when 'keydown' is triggered with a modificator
93+
await wrapper.trigger('keydown.enter')
94+
expect(keydownHandler).toHaveBeenCalledTimes(2)
95+
96+
// is called when 'keydown' is triggered with an option
97+
await wrapper.trigger('keydown', { key: 'K' })
98+
expect(keydownHandler).toHaveBeenCalledTimes(3)
8899
})
89100

90101
it('causes keydown handler to fire when "keydown.enter" is triggered', async () => {
91102
const keydownHandler = jest.fn()
92103
const Component = {
93104
template: '<input @keydown.enter="keydownHandler" />',
94-
methods: {
95-
keydownHandler
96-
}
105+
methods: { keydownHandler }
97106
}
98107
const wrapper = mount(Component, {})
99-
await wrapper.trigger('keydown', { key: 'Enter' })
100108

109+
// is not called when key is not 'enter'
110+
await wrapper.trigger('keydown', { key: 'Backspace' })
111+
expect(keydownHandler).not.toHaveBeenCalled()
112+
113+
// is not called when key is uppercase 'ENTER'
114+
await wrapper.trigger('keydown', { key: 'ENTER' })
115+
expect(keydownHandler).not.toHaveBeenCalled()
116+
117+
// is called when key is lowercase 'enter'
118+
await wrapper.trigger('keydown', { key: 'enter' })
101119
expect(keydownHandler).toHaveBeenCalledTimes(1)
120+
expect(keydownHandler.mock.calls[0][0].key).toBe('enter')
121+
122+
// is called when key is titlecase 'Enter'
123+
await wrapper.trigger('keydown', { key: 'Enter' })
124+
expect(keydownHandler).toHaveBeenCalledTimes(2)
125+
expect(keydownHandler.mock.calls[1][0].key).toBe('Enter')
102126
})
103127

104128
it('causes keydown handler to fire with the appropiate keyCode when wrapper.trigger("keydown", { keyCode: 65 }) is fired', async () => {
105129
const keydownHandler = jest.fn()
106130
const Component = {
107131
template: '<input @keydown="keydownHandler" />',
108-
methods: {
109-
keydownHandler
110-
}
132+
methods: { keydownHandler }
111133
}
112134
const wrapper = mount(Component, {})
113135
await wrapper.trigger('keydown', { keyCode: 65 })
114136

115137
expect(keydownHandler).toHaveBeenCalledTimes(1)
116-
expect(keydownHandler.mock.calls[0][0]['keyCode']).toBe(65)
138+
expect(keydownHandler.mock.calls[0][0].keyCode).toBe(65)
117139
})
118140

119141
it('causes keydown handler to fire converting keyName in an apropiate keyCode when wrapper.trigger("keydown.${keyName}") is fired', async () => {
@@ -139,9 +161,7 @@ describe('trigger', () => {
139161

140162
const Component = {
141163
template: '<input @keydown="keydownHandler" />',
142-
methods: {
143-
keydownHandler
144-
}
164+
methods: { keydownHandler }
145165
}
146166
const wrapper = mount(Component, {})
147167

@@ -150,8 +170,113 @@ describe('trigger', () => {
150170
wrapper.trigger(`keydown.${keyName}`)
151171

152172
const calls = keydownHandler.mock.calls
153-
expect(calls[calls.length - 1][0].keyCode).toEqual(keyCode)
173+
const currentCall = calls[calls.length - 1][0]
174+
175+
// expect(currentCall.key).toBe('')
176+
expect(currentCall.keyCode).toBe(keyCode)
177+
// expect(currentCall.code).toBe(keyCode.toString())
154178
}
155179
})
156180
})
181+
182+
describe('on disabled elements', () => {
183+
it('does not fires when trigger is called on a valid disabled element', async () => {
184+
const validElementsToBeDisabled = [
185+
'button',
186+
'fieldset',
187+
'optgroup',
188+
'option',
189+
'select',
190+
'textarea',
191+
'input'
192+
]
193+
194+
for (let element of validElementsToBeDisabled) {
195+
const clickHandler = jest.fn()
196+
const Component = {
197+
template: `<${element} disabled @click="clickHandler" />`,
198+
methods: { clickHandler }
199+
}
200+
const wrapper = mount(Component, {})
201+
await wrapper.trigger('click')
202+
203+
expect(clickHandler).not.toHaveBeenCalled()
204+
}
205+
})
206+
207+
it('is fired when trigger is called on a element set as disabled but who is invalid to be disabled', async () => {
208+
const invalidElementsToBeDisabled = ['div', 'span', 'a']
209+
210+
for (let element of invalidElementsToBeDisabled) {
211+
const clickHandler = jest.fn()
212+
const Component = {
213+
template: `<${element} disabled @click="clickHandler" />`,
214+
methods: { clickHandler }
215+
}
216+
const wrapper = mount(Component, {})
217+
await wrapper.trigger('click')
218+
219+
expect(clickHandler).toHaveBeenCalledTimes(1)
220+
}
221+
})
222+
})
223+
224+
describe('event modifiers', () => {
225+
const eventModifiers = [
226+
'stop',
227+
'prevent',
228+
'capture',
229+
'self',
230+
'once',
231+
'passive'
232+
]
233+
for (let modifier of eventModifiers) {
234+
it(`handles .${modifier}`, async () => {
235+
const keydownHandler = jest.fn()
236+
const Component = {
237+
template: `<input @keydown.${modifier}="keydownHandler" />`,
238+
methods: { keydownHandler }
239+
}
240+
const wrapper = mount(Component, {})
241+
242+
await wrapper.trigger('keydown')
243+
244+
expect(keydownHandler).toHaveBeenCalledTimes(1)
245+
})
246+
}
247+
})
248+
249+
describe('custom data', () => {
250+
it('adds custom data to events', () => {
251+
const updateHandler = jest.fn()
252+
const Component = {
253+
template: '<div @update="updateHandler" />',
254+
methods: { updateHandler }
255+
}
256+
const wrapper = mount(Component, {})
257+
258+
wrapper.trigger('update', { customData: 123 })
259+
expect(updateHandler).toHaveBeenCalledTimes(1)
260+
expect(updateHandler.mock.calls[0][0].customData).toBe(123)
261+
})
262+
})
263+
264+
describe('errors', () => {
265+
it('throws error if options contains a target value', () => {
266+
const expectedErrorMessage =
267+
'[vue-test-utils]: you cannot set the target value of an event. See the notes section of the docs for more details—https://vue-test-utils.vuejs.org/api/wrapper/trigger.html'
268+
269+
const clickHandler = jest.fn()
270+
const Component = {
271+
template: '<div @click="clickHandler" />',
272+
methods: { clickHandler }
273+
}
274+
const wrapper = mount(Component, {})
275+
276+
const fn = wrapper.trigger('click', { target: 'something' })
277+
expect(fn).rejects.toThrowError(expectedErrorMessage)
278+
279+
expect(clickHandler).not.toHaveBeenCalled()
280+
})
281+
})
157282
})

0 commit comments

Comments
 (0)