Skip to content

Commit dac5950

Browse files
committed
fix: stricter props types
At the moment, `mount()` offers [strong typing][1] of props, but this strong typing is lost when dealing with the `VueWrapper` through either the `props()` or `setProps()` methods. This change strengthens the typing of these methods to help raise compile-time errors when trying to get or set incorrect props. [1]: https://github.com/vuejs/test-utils/blob/11b34745e8e66fc747881dfb1ce94cef537c455e/src/types.ts#L44
1 parent a5f861b commit dac5950

File tree

5 files changed

+50
-10
lines changed

5 files changed

+50
-10
lines changed

src/mount.ts

+7-1
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,13 @@ export function mount<
4747
>(
4848
originalComponent: T,
4949
options?: ComponentMountingOptions<C>
50-
): VueWrapper<ComponentExposed<C> & ComponentProps<C> & ComponentData<C>>
50+
): VueWrapper<
51+
ComponentProps<C> & ComponentData<C> & ComponentExposed<C>,
52+
ComponentPublicInstance<
53+
ComponentProps<C>,
54+
ComponentData<C> & ComponentExposed<C>
55+
>
56+
>
5157

5258
// implementation
5359
export function mount(

src/vueWrapper.ts

+9-5
Original file line numberDiff line numberDiff line change
@@ -215,10 +215,14 @@ export class VueWrapper<
215215
return this.componentVM
216216
}
217217

218-
props(): { [key: string]: any }
219-
props(selector: string): any
220-
props(selector?: string): { [key: string]: any } | any {
221-
const props = this.componentVM.$props as { [key: string]: any }
218+
props(): T['$props']
219+
props<Selector extends keyof T['$props']>(
220+
selector: Selector
221+
): T['$props'][Selector]
222+
props<Selector extends keyof T['$props']>(
223+
selector?: Selector
224+
): T['$props'] | T['$props'][Selector] {
225+
const props = this.componentVM.$props as T['$props']
222226
return selector ? props[selector] : props
223227
}
224228

@@ -240,7 +244,7 @@ export class VueWrapper<
240244
return nextTick()
241245
}
242246

243-
setProps(props: Record<string, unknown>): Promise<void> {
247+
setProps(props: T['$props']): Promise<void> {
244248
// if this VM's parent is not the root or if setProps does not exist, error out
245249
if (this.vm.$parent !== this.rootVM || !this.__setProps) {
246250
throw Error('You can only use setProps on your mounted component')

test-dts/wrapper.d-test.ts

+26-1
Original file line numberDiff line numberDiff line change
@@ -116,4 +116,29 @@ expectType<boolean>(domWrapper.classes('class'))
116116

117117
// props
118118
expectType<{ [key: string]: any }>(wrapper.props())
119-
expectType<any>(wrapper.props('prop'))
119+
120+
const ComponentWithProps = defineComponent({
121+
props: {
122+
foo: String,
123+
bar: Number,
124+
},
125+
})
126+
127+
const propsWrapper = mount(ComponentWithProps);
128+
129+
propsWrapper.setProps({foo: 'abc'})
130+
propsWrapper.setProps({foo: 'abc', bar: 123})
131+
// @ts-expect-error :: should require string
132+
propsWrapper.setProps({foo: 123})
133+
// @ts-expect-error :: unknown prop
134+
propsWrapper.setProps({badProp: true})
135+
136+
expectType<string | undefined>(propsWrapper.props().foo)
137+
expectType<number | undefined>(propsWrapper.props().bar)
138+
// @ts-expect-error :: unknown prop
139+
propsWrapper.props().badProp;
140+
141+
expectType<string | undefined>(propsWrapper.props('foo'))
142+
expectType<number | undefined>(propsWrapper.props('bar'))
143+
// @ts-expect-error :: unknown prop
144+
propsWrapper.props('badProp')

tests/getComponent.spec.ts

+7-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { describe, expect, it, vi } from 'vitest'
2-
import { DefineComponent, defineComponent } from 'vue'
2+
import { defineComponent } from 'vue'
33
import { mount, RouterLinkStub, shallowMount } from '../src'
44
import Issue425 from './components/Issue425.vue'
55

@@ -70,15 +70,19 @@ describe('getComponent', () => {
7070
// https://github.com/vuejs/test-utils/issues/425
7171
it('works with router-link and mount', () => {
7272
const wrapper = mount(Issue425, options)
73-
expect(wrapper.getComponent<DefineComponent>('.link').props('to')).toEqual({
73+
expect(
74+
wrapper.getComponent<typeof RouterLinkStub>('.link').props('to')
75+
).toEqual({
7476
name
7577
})
7678
})
7779

7880
// https://github.com/vuejs/test-utils/issues/425
7981
it('works with router-link and shallowMount', () => {
8082
const wrapper = shallowMount(Issue425, options)
81-
expect(wrapper.getComponent<DefineComponent>('.link').props('to')).toEqual({
83+
expect(
84+
wrapper.getComponent<typeof RouterLinkStub>('.link').props('to')
85+
).toEqual({
8286
name
8387
})
8488
})

tests/props.spec.ts

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ describe('props', () => {
2020

2121
it('returns undefined if props does not exist', () => {
2222
const wrapper = mount(WithProps, { props: { msg: 'ABC' } })
23+
// @ts-expect-error :: non-existent prop
2324
expect(wrapper.props('foo')).toEqual(undefined)
2425
})
2526

0 commit comments

Comments
 (0)