Skip to content

Commit 34adf55

Browse files
rootroot
authored andcommitted
feat: add modifiers to trigger function
1 parent fc96ca3 commit 34adf55

File tree

7 files changed

+247
-36
lines changed

7 files changed

+247
-36
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
"@vue/compiler-sfc": "^3.0.0-alpha.13",
2828
"babel-jest": "^25.2.3",
2929
"babel-preset-jest": "^25.2.1",
30+
"dom-event-types": "^1.0.0",
3031
"flush-promises": "^1.0.2",
3132
"husky": "^4.2.3",
3233
"jest": "^25.1.0",

src/create-dom-event.ts

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import eventTypes from 'dom-event-types'
2+
3+
const keyCodesByKeyName = {
4+
backspace: 8,
5+
tab: 9,
6+
enter: 13,
7+
esc: 27,
8+
space: 32,
9+
pageup: 33,
10+
pagedown: 34,
11+
end: 35,
12+
home: 36,
13+
left: 37,
14+
up: 38,
15+
right: 39,
16+
down: 40,
17+
insert: 45,
18+
delete: 46
19+
}
20+
21+
function getEventProperties(eventParams) {
22+
const { modifier, meta, options } = eventParams
23+
const keyCode = keyCodesByKeyName[modifier] || options.key || options.keyCode || options.code
24+
25+
return {
26+
...options, // What the user passed in as the second argument to #trigger
27+
bubbles: meta.bubbles,
28+
meta: meta.cancelable,
29+
// Any derived options should go here
30+
key: keyCode,
31+
keyCode,
32+
code: keyCode
33+
}
34+
}
35+
36+
function createEvent(eventParams) {
37+
const { eventType, meta } = eventParams
38+
const metaEventInterface = window[meta.eventInterface]
39+
40+
const SupportedEventInterface =
41+
typeof metaEventInterface === 'function'
42+
? metaEventInterface
43+
: window.Event
44+
45+
const eventProperties = getEventProperties(eventParams)
46+
47+
const event = new SupportedEventInterface(
48+
eventType,
49+
// event properties can only be added when the event is instantiated
50+
// custom properties must be added after the event has been instantiated
51+
eventProperties
52+
)
53+
54+
return event
55+
}
56+
57+
function createEventForOldBrowsers(eventParams) {
58+
const { eventType, modifier, meta } = eventParams
59+
const { bubbles, cancelable } = meta
60+
61+
const event = document.createEvent('Event')
62+
event.initEvent(eventType, bubbles, cancelable)
63+
event['keyCode'] = keyCodesByKeyName[modifier]
64+
return event
65+
}
66+
67+
export default function createDOMEvent(eventString: String, options: Object = {}) {
68+
const [eventType, modifier] = eventString.split('.')
69+
const meta = eventTypes[eventType]
70+
|| { eventInterface: 'Event', cancelable: true, bubbles: true }
71+
72+
const eventParams = { eventType, modifier, meta, options }
73+
74+
const event =
75+
typeof window.Event === 'function'
76+
? createEvent(eventParams)
77+
: createEventForOldBrowsers(eventParams) // Fallback for IE10,11 - https://stackoverflow.com/questions/26596123
78+
79+
const eventPrototype = Object.getPrototypeOf(event)
80+
81+
Object.keys(options).forEach(key => {
82+
const propertyDescriptor = Object.getOwnPropertyDescriptor(
83+
eventPrototype,
84+
key
85+
)
86+
87+
const canSetProperty = !(
88+
propertyDescriptor && propertyDescriptor.set === undefined
89+
)
90+
if (canSetProperty) {
91+
event[key] = options[key]
92+
}
93+
})
94+
95+
return event
96+
}

src/dom-wrapper.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import { nextTick } from 'vue'
33
import { WrapperAPI } from './types'
44
import { ErrorWrapper } from './error-wrapper'
55

6+
import createDOMEvent from './create-dom-event'
7+
68
export class DOMWrapper<ElementType extends Element> implements WrapperAPI {
79
element: ElementType
810

@@ -134,12 +136,10 @@ export class DOMWrapper<ElementType extends Element> implements WrapperAPI {
134136
return new DOMWrapper(parentElement).trigger('change')
135137
}
136138

137-
async trigger(eventString: string) {
138-
const evt = document.createEvent('Event')
139-
evt.initEvent(eventString)
140-
139+
async trigger(eventString: string, options: Object = {}) {
141140
if (this.element) {
142-
this.element.dispatchEvent(evt)
141+
const event = createDOMEvent(eventString, options)
142+
this.element.dispatchEvent(event)
143143
}
144144

145145
return nextTick

src/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,5 @@ export interface WrapperAPI {
1010
findAll<T extends Element>(selector: string): DOMWrapper<T>[]
1111
html: () => string
1212
text: () => string
13-
trigger: (eventString: string) => Promise<(fn?: () => void) => Promise<void>>
13+
trigger: (eventString: string, options?: Object) => Promise<(fn?: () => void) => Promise<void>>
1414
}

src/vue-wrapper.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,9 +105,9 @@ export class VueWrapper<T extends ComponentPublicInstance>
105105
return nextTick()
106106
}
107107

108-
trigger(eventString: string) {
108+
trigger(eventString: string, options: Object = {}) {
109109
const rootElementWrapper = new DOMWrapper(this.element)
110-
return rootElementWrapper.trigger(eventString)
110+
return rootElementWrapper.trigger(eventString, options)
111111
}
112112
}
113113

tests/trigger.spec.ts

Lines changed: 137 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -3,44 +3,153 @@ import { defineComponent, h, ref } from 'vue'
33
import { mount } from '../src'
44

55
describe('trigger', () => {
6-
it('works on the root element', async () => {
7-
const Component = defineComponent({
8-
setup() {
9-
return {
10-
count: ref(0)
11-
}
12-
},
136

14-
render() {
15-
return h('div', { onClick: () => this.count++ }, `Count: ${this.count}`)
16-
}
7+
describe('on click', () => {
8+
it('works on the root element', async () => {
9+
const Component = defineComponent({
10+
setup() {
11+
return {
12+
count: ref(0)
13+
}
14+
},
15+
16+
render() {
17+
return h('div', { onClick: () => this.count++ }, `Count: ${this.count}`)
18+
}
19+
})
20+
21+
const wrapper = mount(Component)
22+
await wrapper.trigger('click')
23+
24+
expect(wrapper.text()).toBe('Count: 1')
25+
})
26+
27+
it('works on a nested element', async () => {
28+
const Component = defineComponent({
29+
setup() {
30+
return {
31+
count: ref(0)
32+
}
33+
},
34+
35+
render() {
36+
return h('div', {}, [
37+
h('p', {}, `Count: ${this.count}`),
38+
h('button', { onClick: () => this.count++ })
39+
])
40+
}
41+
})
42+
43+
const wrapper = mount(Component)
44+
await wrapper.find('button').trigger('click')
45+
46+
expect(wrapper.find('p').text()).toBe('Count: 1')
1747
})
1848

19-
const wrapper = mount(Component)
20-
await wrapper.trigger('click')
49+
it('causes DOM to update after a click handler method that changes components data is called', async () => {
50+
const Component = defineComponent({
51+
setup() {
52+
return {
53+
isActive: ref(false)
54+
}
55+
},
2156

22-
expect(wrapper.text()).toBe('Count: 1')
57+
render() {
58+
return h('div', {
59+
onClick: () => this.isActive = !this.isActive,
60+
class: { active: this.isActive }
61+
})
62+
}
63+
})
64+
const wrapper = mount(Component, {})
65+
66+
expect(wrapper.classes()).not.toContain('active')
67+
await wrapper.trigger('click')
68+
expect(wrapper.classes()).toContain('active')
69+
})
2370
})
2471

25-
it('works on a nested element', async () => {
26-
const Component = defineComponent({
27-
setup() {
28-
return {
29-
count: ref(0)
30-
}
31-
},
72+
describe('on keydown', () => {
73+
it('causes keydown handler to fire when "keydown" is triggered', async () => {
74+
const keydownHandler = jest.fn()
75+
const Component = {
76+
template: '<input @keydown="keydownHandler" />',
77+
methods: {
78+
keydownHandler
79+
},
80+
}
81+
const wrapper = mount(Component, {})
82+
await wrapper.trigger('keydown')
83+
84+
expect(keydownHandler).toHaveBeenCalledTimes(1)
85+
})
86+
87+
it('causes keydown handler to fire when "keydown.enter" is triggered', async () => {
88+
const keydownHandler = jest.fn()
89+
const Component = {
90+
template: '<input @keydown.enter="keydownHandler" />',
91+
methods: {
92+
keydownHandler
93+
},
94+
}
95+
const wrapper = mount(Component, {})
96+
await wrapper.trigger('keydown', { key: 'Enter' })
97+
98+
expect(keydownHandler).toHaveBeenCalledTimes(1)
99+
})
32100

33-
render() {
34-
return h('div', {}, [
35-
h('p', {}, `Count: ${this.count}`),
36-
h('button', { onClick: () => this.count++ })
37-
])
101+
it('causes keydown handler to fire with the appropiate keyCode when wrapper.trigger("keydown", { keyCode: 65 }) is fired', async () => {
102+
const keydownHandler = jest.fn()
103+
const Component = {
104+
template: '<input @keydown="keydownHandler" />',
105+
methods: {
106+
keydownHandler
107+
},
38108
}
109+
const wrapper = mount(Component, {})
110+
await wrapper.trigger('keydown', { keyCode: 65 })
111+
112+
expect(keydownHandler).toHaveBeenCalledTimes(1)
113+
expect(keydownHandler.mock.calls[0][0]['keyCode']).toBe(65)
39114
})
40115

41-
const wrapper = mount(Component)
42-
await wrapper.find('button').trigger('click')
116+
it('causes keydown handler to fire converting keyName in an apropiate keyCode when wrapper.trigger("keydown.${keyName}") is fired', async () => {
117+
let keydownHandler = jest.fn()
118+
119+
const keyCodesByKeyName = {
120+
backspace: 8,
121+
tab: 9,
122+
enter: 13,
123+
esc: 27,
124+
space: 32,
125+
pageup: 33,
126+
pagedown: 34,
127+
end: 35,
128+
home: 36,
129+
left: 37,
130+
up: 38,
131+
right: 39,
132+
down: 40,
133+
insert: 45,
134+
delete: 46
135+
}
43136

44-
expect(wrapper.find('p').text()).toBe('Count: 1')
137+
const Component = {
138+
template: '<input @keydown="keydownHandler" />',
139+
methods: {
140+
keydownHandler
141+
},
142+
}
143+
const wrapper = mount(Component, {})
144+
145+
for (const keyName in keyCodesByKeyName) {
146+
const keyCode = keyCodesByKeyName[keyName]
147+
wrapper.trigger(`keydown.${keyName}`)
148+
149+
const calls = keydownHandler.mock.calls
150+
expect(calls[calls.length-1][0].keyCode).toEqual(keyCode)
151+
}
152+
})
45153
})
46154
})
155+

yarn.lock

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2232,6 +2232,11 @@ dir-glob@^2.2.2:
22322232
dependencies:
22332233
path-type "^3.0.0"
22342234

2235+
dom-event-types@^1.0.0:
2236+
version "1.0.0"
2237+
resolved "https://registry.yarnpkg.com/dom-event-types/-/dom-event-types-1.0.0.tgz#5830a0a29e1bf837fe50a70cd80a597232813cae"
2238+
integrity sha512-2G2Vwi2zXTHBGqXHsJ4+ak/iP0N8Ar+G8a7LiD2oup5o4sQWytwqqrZu/O6hIMV0KMID2PL69OhpshLO0n7UJQ==
2239+
22352240
domexception@^1.0.1:
22362241
version "1.0.1"
22372242
resolved "https://registry.yarnpkg.com/domexception/-/domexception-1.0.1.tgz#937442644ca6a31261ef36e3ec677fe805582c90"

0 commit comments

Comments
 (0)