Skip to content

Commit

Permalink
fix: stricter props types
Browse files Browse the repository at this point in the history
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
  • Loading branch information
alecgibson committed Jul 25, 2023
1 parent 11b3474 commit 4c055cd
Show file tree
Hide file tree
Showing 4 changed files with 41 additions and 11 deletions.
14 changes: 9 additions & 5 deletions src/vueWrapper.ts
Expand Up @@ -215,10 +215,14 @@ export class VueWrapper<
return this.componentVM
}

props(): { [key: string]: any }
props(selector: string): any
props(selector?: string): { [key: string]: any } | any {
const props = this.componentVM.$props as { [key: string]: any }
props(): T['$props']
props<Selector extends keyof T['$props']>(
selector: Selector
): T['$props'][Selector]
props<Selector extends keyof T['$props']>(
selector?: Selector
): T['$props'] | T['$props'][Selector] {
const props = this.componentVM.$props as T['$props']
return selector ? props[selector] : props
}

Expand All @@ -240,7 +244,7 @@ export class VueWrapper<
return nextTick()
}

setProps(props: Record<string, unknown>): Promise<void> {
setProps(props: T['$props']): Promise<void> {
// if this VM's parent is not the root or if setProps does not exist, error out
if (this.vm.$parent !== this.rootVM || !this.__setProps) {
throw Error('You can only use setProps on your mounted component')
Expand Down
27 changes: 26 additions & 1 deletion test-dts/wrapper.d-test.ts
Expand Up @@ -116,4 +116,29 @@ expectType<boolean>(domWrapper.classes('class'))

// props
expectType<{ [key: string]: any }>(wrapper.props())
expectType<any>(wrapper.props('prop'))

const ComponentWithProps = defineComponent({
props: {
foo: String,
bar: Number,
},
})

const propsWrapper = mount(ComponentWithProps);

propsWrapper.setProps({foo: 'abc'})
propsWrapper.setProps({foo: 'abc', bar: 123})
// @ts-expect-error :: should require string
propsWrapper.setProps({foo: 123})
// @ts-expect-error :: unknown prop
propsWrapper.setProps({badProp: true})

expectType<string | undefined>(propsWrapper.props().foo)
expectType<number | undefined>(propsWrapper.props().bar)
// @ts-expect-error :: unknown prop
propsWrapper.props().badProp;

expectType<string | undefined>(propsWrapper.props('foo'))
expectType<number | undefined>(propsWrapper.props('bar'))
// @ts-expect-error :: unknown prop
propsWrapper.props('badProp')
6 changes: 3 additions & 3 deletions tests/getComponent.spec.ts
@@ -1,5 +1,5 @@
import { describe, expect, it, vi } from 'vitest'
import { DefineComponent, defineComponent } from 'vue'
import { defineComponent } from 'vue'
import { mount, RouterLinkStub, shallowMount } from '../src'
import Issue425 from './components/Issue425.vue'

Expand Down Expand Up @@ -70,15 +70,15 @@ describe('getComponent', () => {
// https://github.com/vuejs/test-utils/issues/425
it('works with router-link and mount', () => {
const wrapper = mount(Issue425, options)
expect(wrapper.getComponent<DefineComponent>('.link').props('to')).toEqual({
expect(wrapper.getComponent<typeof RouterLinkStub>('.link').props('to')).toEqual({

Check failure on line 73 in tests/getComponent.spec.ts

View workflow job for this annotation

GitHub Actions / build (16)

Replace `wrapper.getComponent<typeof·RouterLinkStub>('.link').props('to')` with `⏎······wrapper.getComponent<typeof·RouterLinkStub>('.link').props('to')⏎····`

Check failure on line 73 in tests/getComponent.spec.ts

View workflow job for this annotation

GitHub Actions / build (18)

Replace `wrapper.getComponent<typeof·RouterLinkStub>('.link').props('to')` with `⏎······wrapper.getComponent<typeof·RouterLinkStub>('.link').props('to')⏎····`
name
})
})

// https://github.com/vuejs/test-utils/issues/425
it('works with router-link and shallowMount', () => {
const wrapper = shallowMount(Issue425, options)
expect(wrapper.getComponent<DefineComponent>('.link').props('to')).toEqual({
expect(wrapper.getComponent<typeof RouterLinkStub>('.link').props('to')).toEqual({

Check failure on line 81 in tests/getComponent.spec.ts

View workflow job for this annotation

GitHub Actions / build (16)

Replace `wrapper.getComponent<typeof·RouterLinkStub>('.link').props('to')` with `⏎······wrapper.getComponent<typeof·RouterLinkStub>('.link').props('to')⏎····`

Check failure on line 81 in tests/getComponent.spec.ts

View workflow job for this annotation

GitHub Actions / build (18)

Replace `wrapper.getComponent<typeof·RouterLinkStub>('.link').props('to')` with `⏎······wrapper.getComponent<typeof·RouterLinkStub>('.link').props('to')⏎····`
name
})
})
Expand Down
5 changes: 3 additions & 2 deletions tests/props.spec.ts
@@ -1,5 +1,5 @@
import { describe, expect, it } from 'vitest'
import { mount, shallowMount } from '../src'
import { VueWrapper, mount, shallowMount } from '../src'
import WithProps from './components/WithProps.vue'
import PropWithSymbol from './components/PropWithSymbol.vue'
import Hello from './components/Hello.vue'
Expand All @@ -20,6 +20,7 @@ describe('props', () => {

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

Expand Down Expand Up @@ -342,7 +343,7 @@ describe('props', () => {
})

it('should get props from functional component', async () => {
const wrapper = mount(Title, {
const wrapper: VueWrapper<any> = mount(Title, {
props: {
title: 'nickname'
}
Expand Down

0 comments on commit 4c055cd

Please sign in to comment.