Skip to content

Commit 48065a6

Browse files
Merge branch 'master' into master
2 parents c9f14ce + c6fdcba commit 48065a6

24 files changed

+780
-143
lines changed

docs/API.md

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -187,23 +187,37 @@ export default {}
187187
```
188188

189189
```js
190-
test('installs a plugin via `plugins`', () => {
190+
test('installs plugins via `plugins`', () => {
191191
const installed = jest.fn()
192192
class Plugin {
193193
static install() {
194194
installed()
195195
}
196196
}
197+
198+
const installedWithOptions = jest.fn()
199+
class PluginWithOptions {
200+
static install(_app: App, ...args) {
201+
installedWithOptions(...args)
202+
}
203+
}
204+
197205
const Component = {
198-
render() { return h('div') }
206+
render() {
207+
return h('div')
208+
}
199209
}
200210
mount(Component, {
201211
global: {
202-
plugins: [Plugin]
212+
plugins: [Plugin, [PluginWithOptions, 'argument 1', 'another argument']]
203213
}
204214
})
205215

206216
expect(installed).toHaveBeenCalled()
217+
expect(installedWithOptions).toHaveBeenCalledWith(
218+
'argument 1',
219+
'another argument'
220+
)
207221
})
208222
```
209223

package.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@vue/test-utils",
3-
"version": "2.0.0-beta.2",
3+
"version": "2.0.0-beta.6",
44
"license": "MIT",
55
"main": "dist/vue-test-utils.cjs.js",
66
"browser": "dist/vue-test-utils.browser.js",
@@ -23,7 +23,7 @@
2323
"@types/estree": "^0.0.42",
2424
"@types/jest": "^24.9.1",
2525
"@types/node": "12.12.35",
26-
"@vue/compiler-sfc": "^3.0.0-rc.5",
26+
"@vue/compiler-sfc": "^3.0.0-rc.12",
2727
"babel-jest": "^25.2.3",
2828
"babel-preset-jest": "^25.2.1",
2929
"dom-event-types": "^1.0.0",
@@ -38,13 +38,13 @@
3838
"ts-jest": "^25.0.0",
3939
"tsd": "0.11.0",
4040
"typescript": "^3.7.5",
41-
"vue": "^3.0.0-rc.5",
41+
"vue": "^3.0.0-rc.12",
4242
"vue-jest": "vuejs/vue-jest#next",
4343
"vue-router": "^4.0.0-alpha.14",
4444
"vuex": "^4.0.0-beta.1"
4545
},
4646
"peerDependencies": {
47-
"vue": "^3.0.0-rc.5"
47+
"vue": "^3.0.0-rc.12"
4848
},
4949
"author": {
5050
"name": "Lachlan Miller",

src/config.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1+
import { ComponentPublicInstance } from 'vue'
12
import { GlobalMountOptions } from './types'
23
import { VueWrapper } from './vueWrapper'
3-
import { ComponentPublicInstance } from 'vue'
44

55
interface GlobalConfigOptions {
66
global: GlobalMountOptions
@@ -19,7 +19,7 @@ interface Plugin {
1919
}
2020

2121
class Pluggable {
22-
installedPlugins = [] as Array<Plugin>
22+
installedPlugins: Plugin[] = []
2323

2424
install(
2525
handler: (
@@ -55,7 +55,12 @@ class Pluggable {
5555
}
5656

5757
export const config: GlobalConfigOptions = {
58-
global: {},
58+
global: {
59+
stubs: {
60+
transition: true,
61+
'transition-group': true
62+
}
63+
},
5964
plugins: {
6065
VueWrapper: new Pluggable(),
6166
DOMWrapper: new Pluggable()

src/domWrapper.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { nextTick } from 'vue'
33
import { createWrapperError } from './errorWrapper'
44
import { TriggerOptions, createDOMEvent } from './createDomEvent'
55
import { config } from './config'
6+
import { isElementVisible } from './utils/isElementVisible'
67

78
export class DOMWrapper<ElementType extends Element> {
89
element: ElementType
@@ -39,6 +40,10 @@ export class DOMWrapper<ElementType extends Element> {
3940
return true
4041
}
4142

43+
isVisible() {
44+
return isElementVisible(this.element)
45+
}
46+
4247
text() {
4348
return this.element.textContent?.trim()
4449
}

src/emitMixin.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,23 @@ export const attachEmitListener = () => {
1010
events[event]
1111
? (events[event] = [...events[event], [...args]])
1212
: (events[event] = [[...args]])
13+
14+
// Vue will warn you if you emit an event that is not declared in `emits` and
15+
// if the parent is not listening for that event.
16+
// since we intercept the event, we are never listening for it explicitly on the
17+
// Parent component. Swallow those events then restore the console.warn.
18+
// TODO: find out if this is doable using `app.config.warnHandler` (does not appear
19+
// work right now). https://github.com/vuejs/vue-test-utils-next/issues/197
20+
const consoleWarnSave = console.warn
21+
console.warn = (msg: string, ...rest: unknown[]) => {
22+
if (msg.includes('[Vue warn]: Component emitted event')) {
23+
return
24+
} else {
25+
consoleWarnSave(msg, ...rest)
26+
}
27+
}
1328
originalEmit(event, ...args)
29+
console.warn = consoleWarnSave
1430
return [event, ...args]
1531
}
1632
}

src/mount.ts

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ import {
1414
ExtractPropTypes,
1515
Component,
1616
WritableComputedOptions,
17-
ComponentOptionsBase,
1817
ComponentPropsOptions,
1918
AppConfig,
2019
VNodeProps,
@@ -23,7 +22,7 @@ import {
2322

2423
import { config } from './config'
2524
import { GlobalMountOptions } from './types'
26-
import { mergeGlobalProperties } from './utils'
25+
import { mergeGlobalProperties, isFunctionalComponent } from './utils'
2726
import { processSlot } from './utils/compileSlots'
2827
import { createWrapper, VueWrapper } from './vueWrapper'
2928
import { attachEmitListener } from './emitMixin'
@@ -35,7 +34,7 @@ import {
3534
} from './constants'
3635
import { stubComponents } from './stubs'
3736

38-
type Slot = VNode | string | { render: Function } | Function
37+
type Slot = VNode | string | { render: Function } | Function | Component
3938

4039
type SlotDictionary = {
4140
[key: string]: Slot
@@ -48,6 +47,8 @@ interface MountingOptions<Props, Data = {}> {
4847
? Partial<Data>
4948
: never
5049
props?: Props
50+
/** @deprecated */
51+
propsData?: Props
5152
attrs?: Record<string, unknown>
5253
slots?: SlotDictionary & {
5354
default?: Slot
@@ -174,7 +175,7 @@ export function mount<
174175
C,
175176
M,
176177
E,
177-
VNodeProps & ExtractPropTypes<PropsOptions, false>
178+
VNodeProps & ExtractPropTypes<PropsOptions>
178179
>
179180
>
180181

@@ -259,6 +260,7 @@ export function mount(
259260
// Vue's reactivity system will cause a rerender.
260261
const props = reactive({
261262
...options?.attrs,
263+
...options?.propsData,
262264
...options?.props,
263265
ref: MOUNT_COMPONENT_REF
264266
})
@@ -312,7 +314,13 @@ export function mount(
312314

313315
// use and plugins from mounting options
314316
if (global.plugins) {
315-
for (const use of global.plugins) app.use(use)
317+
for (const plugin of global.plugins) {
318+
if (Array.isArray(plugin)) {
319+
app.use(plugin[0], ...plugin.slice(1))
320+
continue
321+
}
322+
app.use(plugin)
323+
}
316324
}
317325

318326
// use any mixins from mounting options
@@ -342,17 +350,22 @@ export function mount(
342350
app.mixin(attachEmitListener())
343351

344352
// stubs
345-
if (global.stubs || options?.shallow) {
346-
stubComponents(global.stubs, options?.shallow)
347-
} else {
348-
transformVNodeArgs()
349-
}
353+
// even if we are using `mount`, we will still
354+
// stub out Transition and Transition Group by default.
355+
stubComponents(global.stubs, options?.shallow)
350356

351357
// mount the app!
352358
const vm = app.mount(el)
353359

354360
const App = vm.$refs[MOUNT_COMPONENT_REF] as ComponentPublicInstance
355-
return createWrapper(app, App, setProps)
361+
return createWrapper(
362+
app,
363+
App,
364+
{
365+
isFunctionalComponent: isFunctionalComponent(originalComponent)
366+
},
367+
setProps
368+
)
356369
}
357370

358371
export const shallowMount: typeof mount = (component: any, options?: any) => {

src/stubs.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import {
22
transformVNodeArgs,
3+
Transition,
4+
TransitionGroup,
35
h,
46
ComponentPublicInstance,
57
Slots,
@@ -34,6 +36,17 @@ const createStub = ({ name, props }: StubOptions): ComponentOptions => {
3436
return defineComponent({ name: name || anonName, render, props })
3537
}
3638

39+
const createTransitionStub = ({
40+
name,
41+
props
42+
}: StubOptions): ComponentOptions => {
43+
const render = (ctx: ComponentPublicInstance) => {
44+
return h(name, {}, ctx.$slots)
45+
}
46+
47+
return defineComponent({ name, render, props })
48+
}
49+
3750
const resolveComponentStubByName = (
3851
componentName: string,
3952
stubs: Record<any, any>
@@ -78,6 +91,28 @@ export function stubComponents(
7891
transformVNodeArgs((args, instance: ComponentInternalInstance | null) => {
7992
const [nodeType, props, children, patchFlag, dynamicProps] = args
8093
const type = nodeType as VNodeTypes
94+
95+
// stub transition by default via config.global.stubs
96+
if (type === Transition && stubs['transition']) {
97+
return [
98+
createTransitionStub({ name: 'transition-stub', props: undefined }),
99+
undefined,
100+
children
101+
]
102+
}
103+
104+
// stub transition-group by default via config.global.stubs
105+
if (type === TransitionGroup && stubs['transition-group']) {
106+
return [
107+
createTransitionStub({
108+
name: 'transition-group-stub',
109+
props: undefined
110+
}),
111+
undefined,
112+
children
113+
]
114+
}
115+
81116
// args[0] can either be:
82117
// 1. a HTML tag (div, span...)
83118
// 2. An object of component options, such as { name: 'foo', render: [Function], props: {...} }

src/types.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,17 @@ export type FindComponentSelector = RefSelector | NameSelector | string
2020
export type FindAllComponentsSelector = NameSelector | string
2121

2222
export type GlobalMountOptions = {
23-
plugins?: Plugin[]
24-
config?: Omit<AppConfig, 'isNativeTag'> // isNativeTag is readonly, so we omit it
23+
plugins?: (Plugin | [Plugin, ...any[]])[]
24+
config?: Partial<Omit<AppConfig, 'isNativeTag'>> // isNativeTag is readonly, so we omit it
2525
mixins?: ComponentOptions[]
2626
mocks?: Record<string, any>
2727
provide?: Record<any, any>
2828
components?: Record<string, Component | object>
2929
directives?: Record<string, Directive>
3030
stubs?: Record<any, any>
31+
renderStubDefaultSlot?: boolean
32+
}
33+
34+
export interface VueWrapperMeta {
35+
isFunctionalComponent: boolean
3136
}

src/utils.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { GlobalMountOptions } from './types'
22
import { AppConfig } from 'vue'
3+
34
function mergeStubs(target: Record<string, any>, source: GlobalMountOptions) {
45
if (source.stubs) {
56
if (Array.isArray(source.stubs)) {
@@ -12,7 +13,7 @@ function mergeStubs(target: Record<string, any>, source: GlobalMountOptions) {
1213
}
1314
}
1415

15-
function mergeGlobalProperties(
16+
export function mergeGlobalProperties(
1617
configGlobal: GlobalMountOptions = {},
1718
mountGlobal: GlobalMountOptions = {}
1819
): GlobalMountOptions {
@@ -36,4 +37,6 @@ function mergeGlobalProperties(
3637
}
3738
}
3839

39-
export { mergeGlobalProperties }
40+
export function isFunctionalComponent(component: any) {
41+
return typeof component === 'function'
42+
}

src/utils/compileSlots.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import { compile } from '@vue/compiler-dom'
22
import * as vue from 'vue'
33

4-
export function processSlot(template = '', Vue = vue) {
4+
export function processSlot(source = '', Vue = vue) {
5+
let template = source.trim()
56
const hasWrappingTemplate = template && template.startsWith('<template')
67

78
// allow content without `template` tag, for easier testing

0 commit comments

Comments
 (0)