From 4c055cdcaf4932cca26dfb94a95147fc185a88d5 Mon Sep 17 00:00:00 2001 From: Alec Gibson <12036746+alecgibson@users.noreply.github.com> Date: Tue, 25 Jul 2023 11:58:02 +0100 Subject: [PATCH] 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 --- src/vueWrapper.ts | 14 +++++++++----- test-dts/wrapper.d-test.ts | 27 ++++++++++++++++++++++++++- tests/getComponent.spec.ts | 6 +++--- tests/props.spec.ts | 5 +++-- 4 files changed, 41 insertions(+), 11 deletions(-) diff --git a/src/vueWrapper.ts b/src/vueWrapper.ts index 27d5f9744f..e1d1e774eb 100644 --- a/src/vueWrapper.ts +++ b/src/vueWrapper.ts @@ -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: Selector + ): T['$props'][Selector] + props( + selector?: Selector + ): T['$props'] | T['$props'][Selector] { + const props = this.componentVM.$props as T['$props'] return selector ? props[selector] : props } @@ -240,7 +244,7 @@ export class VueWrapper< return nextTick() } - setProps(props: Record): Promise { + setProps(props: T['$props']): Promise { // 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') diff --git a/test-dts/wrapper.d-test.ts b/test-dts/wrapper.d-test.ts index fcd3fc21ce..811376ab72 100644 --- a/test-dts/wrapper.d-test.ts +++ b/test-dts/wrapper.d-test.ts @@ -116,4 +116,29 @@ expectType(domWrapper.classes('class')) // props expectType<{ [key: string]: any }>(wrapper.props()) -expectType(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(propsWrapper.props().foo) +expectType(propsWrapper.props().bar) +// @ts-expect-error :: unknown prop +propsWrapper.props().badProp; + +expectType(propsWrapper.props('foo')) +expectType(propsWrapper.props('bar')) +// @ts-expect-error :: unknown prop +propsWrapper.props('badProp') diff --git a/tests/getComponent.spec.ts b/tests/getComponent.spec.ts index d58057de4b..b26c9a1426 100644 --- a/tests/getComponent.spec.ts +++ b/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' @@ -70,7 +70,7 @@ 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('.link').props('to')).toEqual({ + expect(wrapper.getComponent('.link').props('to')).toEqual({ name }) }) @@ -78,7 +78,7 @@ describe('getComponent', () => { // https://github.com/vuejs/test-utils/issues/425 it('works with router-link and shallowMount', () => { const wrapper = shallowMount(Issue425, options) - expect(wrapper.getComponent('.link').props('to')).toEqual({ + expect(wrapper.getComponent('.link').props('to')).toEqual({ name }) }) diff --git a/tests/props.spec.ts b/tests/props.spec.ts index 690b7700e3..282165fea6 100644 --- a/tests/props.spec.ts +++ b/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' @@ -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) }) @@ -342,7 +343,7 @@ describe('props', () => { }) it('should get props from functional component', async () => { - const wrapper = mount(Title, { + const wrapper: VueWrapper = mount(Title, { props: { title: 'nickname' }