Skip to content

Commit c177579

Browse files
authored
Merge pull request #185 from vuejs/feature/add-warning
chore: add warning about emitted on functional components
2 parents b1ff235 + af7a4c5 commit c177579

File tree

5 files changed

+72
-12
lines changed

5 files changed

+72
-12
lines changed

src/mount.ts

Lines changed: 9 additions & 3 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'
@@ -358,7 +357,14 @@ export function mount(
358357
const vm = app.mount(el)
359358

360359
const App = vm.$refs[MOUNT_COMPONENT_REF] as ComponentPublicInstance
361-
return createWrapper(app, App, setProps)
360+
return createWrapper(
361+
app,
362+
App,
363+
{
364+
isFunctionalComponent: isFunctionalComponent(originalComponent)
365+
},
366+
setProps
367+
)
362368
}
363369

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

src/types.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,7 @@ export type GlobalMountOptions = {
2929
directives?: Record<string, Directive>
3030
stubs?: Record<any, any>
3131
}
32+
33+
export interface VueWrapperMeta {
34+
isFunctionalComponent: boolean
35+
}

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/vueWrapper.ts

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,36 @@
11
import { ComponentPublicInstance, nextTick, App } from 'vue'
22
import { ShapeFlags } from '@vue/shared'
3-
import { config } from './config'
43

4+
import { config } from './config'
55
import { DOMWrapper } from './domWrapper'
6-
import { FindAllComponentsSelector, FindComponentSelector } from './types'
6+
import {
7+
FindAllComponentsSelector,
8+
FindComponentSelector,
9+
VueWrapperMeta
10+
} from './types'
711
import { createWrapperError } from './errorWrapper'
812
import { TriggerOptions } from './createDomEvent'
913
import { find } from './utils/find'
14+
import { isFunctionalComponent } from './utils'
1015

1116
export class VueWrapper<T extends ComponentPublicInstance> {
1217
private componentVM: T
1318
private rootVM: ComponentPublicInstance
1419
private __app: App | null
1520
private __setProps: ((props: Record<string, any>) => void) | undefined
21+
private __isFunctionalComponent: boolean
1622

1723
constructor(
1824
app: App | null,
1925
vm: ComponentPublicInstance,
20-
setProps?: (props: Record<string, any>) => void
26+
setProps?: (props: Record<string, any>) => void,
27+
meta?: VueWrapperMeta
2128
) {
2229
this.__app = app
2330
this.rootVM = vm.$root!
2431
this.componentVM = vm as T
2532
this.__setProps = setProps
33+
this.__isFunctionalComponent = meta.isFunctionalComponent
2634
// plugins hook
2735
config.plugins.VueWrapper.extend(this)
2836
}
@@ -71,10 +79,17 @@ export class VueWrapper<T extends ComponentPublicInstance> {
7179
emitted<T = unknown>(): Record<string, T[]>
7280
emitted<T = unknown>(eventName?: string): T[]
7381
emitted<T = unknown>(eventName?: string): T[] | Record<string, T[]> {
82+
if (this.__isFunctionalComponent) {
83+
console.warn(
84+
'[Vue Test Utils]: capture events emitted from functional components is currently not supported.'
85+
)
86+
}
87+
7488
if (eventName) {
7589
const emitted = (this.vm['__emitted'] as Record<string, T[]>)[eventName]
7690
return emitted
7791
}
92+
7893
return this.vm['__emitted'] as Record<string, T[]>
7994
}
8095

@@ -136,13 +151,17 @@ export class VueWrapper<T extends ComponentPublicInstance> {
136151
if (typeof selector === 'object' && 'ref' in selector) {
137152
const result = this.vm.$refs[selector.ref]
138153
if (result) {
139-
return createWrapper(null, result as T)
154+
return createWrapper(null, result as T, {
155+
isFunctionalComponent: isFunctionalComponent(result)
156+
})
140157
}
141158
}
142159

143160
const result = find(this.vm.$.subTree, selector)
144161
if (result.length) {
145-
return createWrapper(null, result[0])
162+
return createWrapper(null, result[0], {
163+
isFunctionalComponent: isFunctionalComponent(result)
164+
})
146165
}
147166

148167
return createWrapperError('VueWrapper')
@@ -178,7 +197,11 @@ export class VueWrapper<T extends ComponentPublicInstance> {
178197
}
179198

180199
findAllComponents(selector: FindAllComponentsSelector): VueWrapper<T>[] {
181-
return find(this.vm.$.subTree, selector).map((c) => createWrapper(null, c))
200+
return find(this.vm.$.subTree, selector).map((c) =>
201+
createWrapper(null, c, {
202+
isFunctionalComponent: isFunctionalComponent(c)
203+
})
204+
)
182205
}
183206

184207
findAll<K extends keyof HTMLElementTagNameMap>(
@@ -228,7 +251,8 @@ export class VueWrapper<T extends ComponentPublicInstance> {
228251
export function createWrapper<T extends ComponentPublicInstance>(
229252
app: App | null,
230253
vm: ComponentPublicInstance,
254+
meta: VueWrapperMeta,
231255
setProps?: (props: Record<string, any>) => void
232256
): VueWrapper<T> {
233-
return new VueWrapper<T>(app, vm, setProps)
257+
return new VueWrapper<T>(app, vm, setProps, meta)
234258
}

tests/emit.spec.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,17 @@ import { defineComponent, h } from 'vue'
33
import { mount } from '../src'
44

55
describe('emitted', () => {
6+
let consoleWarnSave = console.info
7+
8+
beforeEach(() => {
9+
consoleWarnSave = console.warn
10+
console.warn = jest.fn()
11+
})
12+
13+
afterEach(() => {
14+
console.warn = consoleWarnSave
15+
})
16+
617
it('captures events emitted via this.$emit', () => {
718
const Component = defineComponent({
819
render() {
@@ -121,4 +132,16 @@ describe('emitted', () => {
121132

122133
expect(wrapper.emitted('hello')).toHaveLength(2)
123134
})
135+
136+
it('gives a useful warning for functional components', () => {
137+
const Component = (_, ctx) => {
138+
return h('button', { onClick: () => ctx.emit('hello', 'foo', 'bar') })
139+
}
140+
141+
mount(Component).emitted()
142+
143+
expect(console.warn).toHaveBeenCalledWith(
144+
'[Vue Test Utils]: capture events emitted from functional components is currently not supported.'
145+
)
146+
})
124147
})

0 commit comments

Comments
 (0)