Skip to content

Commit d7cae03

Browse files
committed
Merge branch 'master' into feat/options.attachto
# Conflicts: # rollup.config.js # src/mount.ts # src/utils.ts
2 parents c62cd39 + 5b9cbd0 commit d7cae03

23 files changed

+927
-110
lines changed

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@vue/test-utils",
3-
"version": "2.0.0-alpha.1",
3+
"version": "2.0.0-alpha.2",
44
"license": "MIT",
55
"main": "dist/vue-test-utils.cjs.js",
66
"browser": "dist/vue-test-utils.esm.js",
@@ -13,6 +13,7 @@
1313
"dist/index.d.ts"
1414
],
1515
"dependencies": {
16+
"dom-event-types": "^1.0.0",
1617
"lodash": "^4.17.15"
1718
},
1819
"devDependencies": {

rollup.config.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,14 @@ function createEntry(options) {
2121
const config = {
2222
input,
2323
external: [
24-
'vue',
24+
'vue',
2525
'lodash/mergeWith',
2626
'lodash/camelCase',
2727
'lodash/upperFirst',
2828
'lodash/kebabCase',
29+
'lodash/flow',
2930
'lodash/isString',
30-
'lodash/flow'
31+
'dom-event-types'
3132
],
3233
plugins: [resolve()],
3334
output: {
@@ -38,7 +39,7 @@ function createEntry(options) {
3839
}
3940

4041
if (format === 'es') {
41-
config.output.file = isBrowser ? pkg.browser : pkg.module
42+
config.output.file = isBrowser ? pkg.browser : pkg.module
4243
}
4344
if (format === 'cjs') {
4445
config.output.file = pkg.main

src/config.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { GlobalMountOptions } from './types'
2+
3+
export const config: { global: GlobalMountOptions } = {
4+
global: {}
5+
}

src/create-dom-event.ts

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

src/dom-wrapper.ts

Lines changed: 61 additions & 12 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 { TriggerOptions, createDOMEvent } from './create-dom-event'
7+
68
export class DOMWrapper<ElementType extends Element> implements WrapperAPI {
79
element: ElementType
810

@@ -41,26 +43,47 @@ export class DOMWrapper<ElementType extends Element> implements WrapperAPI {
4143
return this.element.outerHTML
4244
}
4345

44-
find<T extends Element>(selector: string): DOMWrapper<T> | ErrorWrapper {
45-
const result = this.element.querySelector<T>(selector)
46+
find<K extends keyof HTMLElementTagNameMap>(
47+
selector: K
48+
): DOMWrapper<HTMLElementTagNameMap[K]> | ErrorWrapper
49+
find<K extends keyof SVGElementTagNameMap>(
50+
selector: K
51+
): DOMWrapper<SVGElementTagNameMap[K]> | ErrorWrapper
52+
find<T extends Element>(selector: string): DOMWrapper<T> | ErrorWrapper
53+
find(selector: string): DOMWrapper<Element> | ErrorWrapper {
54+
const result = this.element.querySelector(selector)
4655
if (result) {
47-
return new DOMWrapper<T>(result)
56+
return new DOMWrapper(result)
4857
}
4958

5059
return new ErrorWrapper({ selector })
5160
}
5261

53-
get<T extends Element>(selector: string): DOMWrapper<T> {
54-
const result = this.find<T>(selector)
62+
get<K extends keyof HTMLElementTagNameMap>(
63+
selector: K
64+
): DOMWrapper<HTMLElementTagNameMap[K]>
65+
get<K extends keyof SVGElementTagNameMap>(
66+
selector: K
67+
): DOMWrapper<SVGElementTagNameMap[K]>
68+
get<T extends Element>(selector: string): DOMWrapper<T>
69+
get(selector: string): DOMWrapper<Element> {
70+
const result = this.find(selector)
5571
if (result instanceof ErrorWrapper) {
5672
throw new Error(`Unable to find ${selector} within: ${this.html()}`)
5773
}
5874

5975
return result
6076
}
6177

62-
findAll<T extends Element>(selector: string): DOMWrapper<T>[] {
63-
return Array.from(this.element.querySelectorAll<T>(selector)).map(
78+
findAll<K extends keyof HTMLElementTagNameMap>(
79+
selector: K
80+
): DOMWrapper<HTMLElementTagNameMap[K]>[]
81+
findAll<K extends keyof SVGElementTagNameMap>(
82+
selector: K
83+
): DOMWrapper<SVGElementTagNameMap[K]>[]
84+
findAll<T extends Element>(selector: string): DOMWrapper<T>[]
85+
findAll(selector: string): DOMWrapper<Element>[] {
86+
return Array.from(this.element.querySelectorAll(selector)).map(
6487
(x) => new DOMWrapper(x)
6588
)
6689
}
@@ -134,12 +157,38 @@ export class DOMWrapper<ElementType extends Element> implements WrapperAPI {
134157
return new DOMWrapper(parentElement).trigger('change')
135158
}
136159

137-
async trigger(eventString: string) {
138-
const evt = document.createEvent('Event')
139-
evt.initEvent(eventString)
160+
async trigger(eventString: string, options?: TriggerOptions) {
161+
if (options && options['target']) {
162+
throw Error(
163+
`[vue-test-utils]: you cannot set the target value of an event. See the notes section ` +
164+
`of the docs for more details—` +
165+
`https://vue-test-utils.vuejs.org/api/wrapper/trigger.html`
166+
)
167+
}
168+
169+
const isDisabled = () => {
170+
const validTagsToBeDisabled = [
171+
'BUTTON',
172+
'COMMAND',
173+
'FIELDSET',
174+
'KEYGEN',
175+
'OPTGROUP',
176+
'OPTION',
177+
'SELECT',
178+
'TEXTAREA',
179+
'INPUT'
180+
]
181+
const hasDisabledAttribute = this.attributes().disabled !== undefined
182+
const elementCanBeDisabled = validTagsToBeDisabled.includes(
183+
this.element.tagName
184+
)
185+
186+
return hasDisabledAttribute && elementCanBeDisabled
187+
}
140188

141-
if (this.element) {
142-
this.element.dispatchEvent(evt)
189+
if (this.element && !isDisabled()) {
190+
const event = createDOMEvent(eventString, options)
191+
this.element.dispatchEvent(event)
143192
}
144193

145194
return nextTick

src/error-wrapper.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ export class ErrorWrapper {
3636
throw this.wrapperError('find')
3737
}
3838

39+
get(): never {
40+
throw this.wrapperError('get')
41+
}
42+
3943
findAll(): never {
4044
throw this.wrapperError('findAll')
4145
}
@@ -48,6 +52,10 @@ export class ErrorWrapper {
4852
throw this.wrapperError('setValue')
4953
}
5054

55+
props() {
56+
throw this.wrapperError('props')
57+
}
58+
5159
text() {
5260
throw this.wrapperError('text')
5361
}

src/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { mount } from './mount'
22
import { RouterLinkStub } from './components/RouterLinkStub'
3+
import { config } from './config'
34

4-
export { mount, RouterLinkStub }
5+
export { mount, RouterLinkStub, config }

src/mount.ts

Lines changed: 21 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,7 @@ import {
44
VNode,
55
defineComponent,
66
VNodeNormalizedChildren,
7-
ComponentOptions,
87
transformVNodeArgs,
9-
Plugin,
10-
Directive,
11-
Component,
128
reactive,
139
ComponentPublicInstance,
1410
ComponentOptionsWithObjectProps,
@@ -17,6 +13,9 @@ import {
1713
ExtractPropTypes
1814
} from 'vue'
1915

16+
import { config } from './config'
17+
import { GlobalMountOptions } from './types'
18+
import { mergeGlobalProperties, isString } from './utils'
2019
import { createWrapper, VueWrapper } from './vue-wrapper'
2120
import { attachEmitListener } from './emitMixin'
2221
import { createDataMixin } from './dataMixin'
@@ -26,7 +25,6 @@ import {
2625
MOUNT_PARENT_NAME
2726
} from './constants'
2827
import { stubComponents } from './stubs'
29-
import { isString } from './utils'
3028

3129
type Slot = VNode | string | { render: Function }
3230

@@ -37,17 +35,7 @@ interface MountingOptions<Props> {
3735
default?: Slot
3836
[key: string]: Slot
3937
}
40-
global?: {
41-
plugins?: Plugin[]
42-
mixins?: ComponentOptions[]
43-
mocks?: Record<string, any>
44-
stubs?: Record<any, any>
45-
provide?: Record<any, any>
46-
// TODO how to type `defineComponent`? Using `any` for now.
47-
components?: Record<string, Component | object>
48-
directives?: Record<string, Directive>
49-
}
50-
stubs?: Record<string, any>
38+
global?: GlobalMountOptions
5139
attachTo?: HTMLElement | string
5240
}
5341

@@ -152,11 +140,13 @@ export function mount(
152140
// create the app
153141
const app = createApp(Parent)
154142

143+
const global = mergeGlobalProperties(config.global, options?.global)
144+
155145
// global mocks mixin
156-
if (options?.global?.mocks) {
146+
if (global?.mocks) {
157147
const mixin = {
158148
beforeCreate() {
159-
for (const [k, v] of Object.entries(options.global?.mocks)) {
149+
for (const [k, v] of Object.entries(global.mocks)) {
160150
this[k] = v
161151
}
162152
}
@@ -166,30 +156,30 @@ export function mount(
166156
}
167157

168158
// use and plugins from mounting options
169-
if (options?.global?.plugins) {
170-
for (const use of options?.global?.plugins) app.use(use)
159+
if (global?.plugins) {
160+
for (const use of global.plugins) app.use(use)
171161
}
172162

173163
// use any mixins from mounting options
174-
if (options?.global?.mixins) {
175-
for (const mixin of options?.global?.mixins) app.mixin(mixin)
164+
if (global?.mixins) {
165+
for (const mixin of global.mixins) app.mixin(mixin)
176166
}
177167

178-
if (options?.global?.components) {
179-
for (const key of Object.keys(options?.global?.components))
180-
app.component(key, options.global.components[key])
168+
if (global?.components) {
169+
for (const key of Object.keys(global.components))
170+
app.component(key, global.components[key])
181171
}
182172

183-
if (options?.global?.directives) {
184-
for (const key of Object.keys(options?.global?.directives))
185-
app.directive(key, options.global.directives[key])
173+
if (global?.directives) {
174+
for (const key of Object.keys(global.directives))
175+
app.directive(key, global.directives[key])
186176
}
187177

188178
// provide any values passed via provides mounting option
189-
if (options?.global?.provide) {
190-
for (const key of Reflect.ownKeys(options.global.provide)) {
179+
if (global?.provide) {
180+
for (const key of Reflect.ownKeys(global.provide)) {
191181
// @ts-ignore: https://github.com/microsoft/TypeScript/issues/1863
192-
app.provide(key, options.global.provide[key])
182+
app.provide(key, global.provide[key])
193183
}
194184
}
195185

0 commit comments

Comments
 (0)