diff --git a/src/mount.ts b/src/mount.ts index 9d20d703c..2d83869b9 100644 --- a/src/mount.ts +++ b/src/mount.ts @@ -13,6 +13,10 @@ import { ComponentOptionsWithoutProps, ExtractPropTypes, Component, + WritableComputedOptions, + SetupContext, + RenderFunction, + ComponentPropsOptions, AppConfig, VNodeProps } from 'vue' @@ -37,8 +41,8 @@ type SlotDictionary = { [key: string]: Slot } -interface MountingOptions { - data?: () => Record +interface MountingOptions { + data?: () => Data extends object ? Partial : never props?: Props attrs?: Record slots?: SlotDictionary & { @@ -49,40 +53,116 @@ interface MountingOptions { shallow?: boolean } -// TODO improve the typings of the overloads - -type ExtractComponent = T extends { new (): infer PublicInstance } - ? PublicInstance - : any +export type ComputedOptions = Record< + string, + ((ctx?: any) => any) | WritableComputedOptions +> +export type ObjectEmitsOptions = Record< + string, + ((...args: any[]) => any) | null +> +export type EmitsOptions = ObjectEmitsOptions | string[] // Functional component -export function mount( +export function mount< + TestedComponent extends FunctionalComponent, + Props +>( originalComponent: TestedComponent, - options?: MountingOptions -): VueWrapper + options?: MountingOptions +): VueWrapper> + // Component declared with defineComponent export function mount( originalComponent: { new (): TestedComponent } & Component, - options?: MountingOptions + options?: MountingOptions ): VueWrapper -// Component declared with { props: { ... } } -export function mount( - originalComponent: TestedComponent, - options?: MountingOptions> -): VueWrapper> -// Component declared with { props: [] } -export function mount( - originalComponent: TestedComponent, - options?: MountingOptions> -): VueWrapper> + // Component declared with no props export function mount< - TestedComponent extends ComponentOptionsWithoutProps, - ComponentT extends ComponentOptionsWithoutProps & {} + Props = {}, + RawBindings = {}, + D = {}, + C extends ComputedOptions = {}, + M extends Record = {}, + E extends EmitsOptions = Record, + EE extends string = string +>( + componentOptions: ComponentOptionsWithoutProps< + Props, + RawBindings, + D, + C, + M, + E, + EE + >, + options?: MountingOptions +): VueWrapper< + ComponentPublicInstance +> + +// Component declared with { props: [] } +export function mount< + PropNames extends string, + RawBindings, + D, + C extends ComputedOptions = {}, + M extends Record = {}, + E extends EmitsOptions = Record, + EE extends string = string, + Props extends Readonly<{ [key in PropNames]?: any }> = Readonly< + { [key in PropNames]?: any } + > +>( + componentOptions: ComponentOptionsWithArrayProps< + PropNames, + RawBindings, + D, + C, + M, + E, + EE, + Props + >, + options?: MountingOptions +): VueWrapper> + +// Component declared with { props: { ... } } +export function mount< + // the Readonly constraint allows TS to treat the type of { required: true } + // as constant instead of boolean. + PropsOptions extends Readonly, + RawBindings, + D, + C extends ComputedOptions = {}, + M extends Record = {}, + E extends EmitsOptions = Record, + EE extends string = string >( - originalComponent: ComponentT extends { new (): any } ? never : ComponentT, - options?: MountingOptions -): VueWrapper> + componentOptions: ComponentOptionsWithObjectProps< + PropsOptions, + RawBindings, + D, + C, + M, + E, + EE + >, + options?: MountingOptions, D> +): VueWrapper< + ComponentPublicInstance< + ExtractPropTypes, + RawBindings, + D, + C, + M, + E, + VNodeProps & ExtractPropTypes + > +> + +// implementation export function mount( originalComponent: any, options?: MountingOptions diff --git a/test-dts/mount.d-test.ts b/test-dts/mount.d-test.ts index eda62aace..d8274e10d 100644 --- a/test-dts/mount.d-test.ts +++ b/test-dts/mount.d-test.ts @@ -13,12 +13,23 @@ const AppWithDefine = defineComponent({ template: '' }) -// accept props -let wrapper = mount(AppWithDefine, { - props: { a: 'Hello', b: 2 } -}) -// vm is properly typed -expectType(wrapper.vm.a) +// accept props- vm is properly typed +expectType( + mount(AppWithDefine, { + props: { a: 'Hello', b: 2 } + }).vm.a +) + +// no data provided +expectError( + mount(AppWithDefine, { + data() { + return { + myVal: 1 + } + } + }) +) // can receive extra props // ideally, it should not @@ -45,12 +56,12 @@ const AppWithProps = { template: '' } -// accept props -wrapper = mount(AppWithProps, { - props: { a: 'Hello' } -}) -// vm is properly typed -expectType(wrapper.vm.a) +// accept props - vm is properly typed +expectType( + mount(AppWithProps, { + props: { a: 'Hello' } + }).vm.a +) // can't receive extra props expectError( @@ -71,18 +82,35 @@ const AppWithArrayProps = { template: '' } -// accept props -wrapper = mount(AppWithArrayProps, { - props: { a: 'Hello' } -}) -// vm is properly typed -expectType(wrapper.vm.a) +// accept props - vm is properly typed +expectType( + mount(AppWithArrayProps, { + props: { a: 'Hello' } + }).vm.a +) // can receive extra props // as they are declared as `string[]` -mount(AppWithArrayProps, { - props: { a: 'Hello', b: 2 } -}) +expectType( + mount(AppWithArrayProps, { + props: { a: 'Hello', b: 2 } + }).vm.b +) + +// cannot receive extra props +// if they pass use object inside +expectError( + mount( + { + props: ['a'] + }, + { + props: { + b: 2 + } + } + ) +) const AppWithoutProps = { template: '' @@ -90,12 +118,29 @@ const AppWithoutProps = { // can't receive extra props expectError( - (wrapper = mount(AppWithoutProps, { + mount(AppWithoutProps, { props: { b: 'Hello' } - })) + }) ) // except if explicitly cast mount(AppWithoutProps, { props: { b: 'Hello' } as never }) + +// Functional tests + +// wrong props +expectError((props: { a: 1 }) => {}, { + props: { + a: '222' + } +}) + +expectType( + mount((props: { a: 1 }, ctx) => {}, { + props: { + a: 22 + } + }).vm.a +) diff --git a/test-dts/shallowMount.d-test.ts b/test-dts/shallowMount.d-test.ts index ade13dfdd..38326997f 100644 --- a/test-dts/shallowMount.d-test.ts +++ b/test-dts/shallowMount.d-test.ts @@ -46,11 +46,12 @@ const AppWithProps = { } // accept props -wrapper = shallowMount(AppWithProps, { - props: { a: 'Hello' } -}) // vm is properly typed -expectType(wrapper.vm.a) +expectType( + shallowMount(AppWithProps, { + props: { a: 'Hello' } + }).vm.a +) // can't receive extra props expectError(