From b980175426a25400a5d0ba9f684eee44901fae65 Mon Sep 17 00:00:00 2001 From: ZHAO Jinxiang Date: Mon, 8 Nov 2021 03:56:00 +0800 Subject: [PATCH] feat: add component $emit typing support (#846) --- src/apis/createApp.ts | 2 +- src/apis/createElement.ts | 4 +- src/component/componentOptions.ts | 43 +- src/component/componentProxy.ts | 58 +- src/component/defineAsyncComponent.ts | 11 +- src/component/defineComponent.ts | 94 +- ...t-d.ts => defineAsyncComponent.test-d.tsx} | 0 test-dts/defineComponent.test-d.ts | 725 ----------- test-dts/defineComponent.test-d.tsx | 1152 +++++++++++++++++ test-dts/index.d.ts | 7 + ...readonly.test-d.ts => readonly.test-d.tsx} | 0 test-dts/{ref.test-d.ts => ref.test-d.tsx} | 0 test-dts/tsconfig.json | 5 +- .../{watch.test-d.ts => watch.test-d.tsx} | 0 14 files changed, 1327 insertions(+), 774 deletions(-) rename test-dts/{defineAsyncComponent.test-d.ts => defineAsyncComponent.test-d.tsx} (100%) delete mode 100644 test-dts/defineComponent.test-d.ts create mode 100644 test-dts/defineComponent.test-d.tsx rename test-dts/{readonly.test-d.ts => readonly.test-d.tsx} (100%) rename test-dts/{ref.test-d.ts => ref.test-d.tsx} (100%) rename test-dts/{watch.test-d.ts => watch.test-d.tsx} (100%) diff --git a/src/apis/createApp.ts b/src/apis/createApp.ts index ff69cf4f..4e927176 100644 --- a/src/apis/createApp.ts +++ b/src/apis/createApp.ts @@ -1,5 +1,5 @@ import type Vue from 'vue' -import { VueConstructor } from 'vue/types/umd' +import { VueConstructor } from 'vue' import { getVueConstructor } from '../runtimeContext' import { warn } from '../utils' diff --git a/src/apis/createElement.ts b/src/apis/createElement.ts index 6302405e..79ee9abc 100644 --- a/src/apis/createElement.ts +++ b/src/apis/createElement.ts @@ -1,10 +1,8 @@ -import Vue from 'vue' +import type { CreateElement } from 'vue' import { getVueConstructor, getCurrentInstance } from '../runtimeContext' import { defineComponentInstance } from '../utils/helper' import { warn } from '../utils' -type CreateElement = Vue['$createElement'] - let fallbackCreateElement: CreateElement export const createElement = function createElement(...args: any) { diff --git a/src/component/componentOptions.ts b/src/component/componentOptions.ts index cc778625..dd3de46b 100644 --- a/src/component/componentOptions.ts +++ b/src/component/componentOptions.ts @@ -1,5 +1,5 @@ import Vue, { VNode, ComponentOptions as Vue2ComponentOptions } from 'vue' -import { SetupContext } from '../runtimeContext' +import { EmitsOptions, SetupContext } from '../runtimeContext' import { Data } from './common' import { ComponentPropsOptions, ExtractPropTypes } from './componentProps' import { ComponentRenderProxy } from './componentProxy' @@ -8,13 +8,6 @@ export { ComponentPropsOptions } from './componentProps' export type ComputedGetter = (ctx?: any) => T export type ComputedSetter = (v: T) => void -export type ObjectEmitsOptions = Record< - string, - ((...args: any[]) => any) | null -> - -export type EmitsOptions = ObjectEmitsOptions | string[] - export interface WritableComputedOptions { get: ComputedGetter set: ComputedSetter @@ -39,7 +32,10 @@ interface ComponentOptionsBase< Props, D = Data, C extends ComputedOptions = {}, - M extends MethodOptions = {} + M extends MethodOptions = {}, + Mixin = {}, + Extends = {}, + Emits extends EmitsOptions = {} > extends Omit< Vue2ComponentOptions, 'data' | 'computed' | 'method' | 'setup' | 'props' @@ -67,12 +63,17 @@ export type ComponentOptionsWithProps< D = Data, C extends ComputedOptions = {}, M extends MethodOptions = {}, + Mixin = {}, + Extends = {}, + Emits extends EmitsOptions = {}, Props = ExtractPropTypes > = ComponentOptionsBase & { props?: PropsOptions - emits?: (EmitsOptions | string[]) & ThisType + emits?: Emits & ThisType setup?: SetupFunction -} & ThisType> +} & ThisType< + ComponentRenderProxy + > export type ComponentOptionsWithArrayProps< PropNames extends string = string, @@ -80,24 +81,34 @@ export type ComponentOptionsWithArrayProps< D = Data, C extends ComputedOptions = {}, M extends MethodOptions = {}, + Mixin = {}, + Extends = {}, + Emits extends EmitsOptions = {}, Props = Readonly<{ [key in PropNames]?: any }> > = ComponentOptionsBase & { props?: PropNames[] - emits?: (EmitsOptions | string[]) & ThisType + emits?: Emits & ThisType setup?: SetupFunction -} & ThisType> +} & ThisType< + ComponentRenderProxy + > export type ComponentOptionsWithoutProps< Props = {}, RawBindings = Data, D = Data, C extends ComputedOptions = {}, - M extends MethodOptions = {} + M extends MethodOptions = {}, + Mixin = {}, + Extends = {}, + Emits extends EmitsOptions = {} > = ComponentOptionsBase & { props?: undefined - emits?: (EmitsOptions | string[]) & ThisType + emits?: Emits & ThisType setup?: SetupFunction -} & ThisType> +} & ThisType< + ComponentRenderProxy + > export type WithLegacyAPI = T & Omit, keyof T> diff --git a/src/component/componentProxy.ts b/src/component/componentProxy.ts index fbf81d4c..39202732 100644 --- a/src/component/componentProxy.ts +++ b/src/component/componentProxy.ts @@ -21,9 +21,29 @@ import { ComponentInternalInstance, EmitFn, EmitsOptions, + ObjectEmitsOptions, Slots, } from '../runtimeContext' +type EmitsToProps = T extends string[] + ? { + [K in string & `on${Capitalize}`]?: (...args: any[]) => any + } + : T extends ObjectEmitsOptions + ? { + [K in string & + `on${Capitalize}`]?: K extends `on${infer C}` + ? T[Uncapitalize] extends null + ? (...args: any[]) => any + : ( + ...args: T[Uncapitalize] extends (...args: infer P) => any + ? P + : never + ) => any + : never + } + : {} + export type ComponentInstance = InstanceType // public properties exposed on the proxy, which is used as the render context @@ -34,6 +54,9 @@ export type ComponentRenderProxy< D = {}, // return from data() C extends ComputedOptions = {}, M extends MethodOptions = {}, + Mixin = {}, + Extends = {}, + Emits extends EmitsOptions = {}, PublicProps = P, Defaults = {}, MakeDefaultsOptional extends boolean = false @@ -45,12 +68,13 @@ export type ComponentRenderProxy< : P & PublicProps > $attrs: Data + $emit: EmitFn } & Readonly

& ShallowUnwrapRef & D & M & ExtractComputedReturns & - Omit + Omit // for Vetur and TSX support type VueConstructorProxy< @@ -58,15 +82,23 @@ type VueConstructorProxy< RawBindings, Data, Computed extends ComputedOptions, - Methods extends MethodOptions -> = VueConstructor & { + Methods extends MethodOptions, + Mixin = {}, + Extends = {}, + Emits extends EmitsOptions = {}, + Props = ExtractPropTypes & + ({} extends Emits ? {} : EmitsToProps) +> = Omit & { new (...args: any[]): ComponentRenderProxy< - ExtractPropTypes, + Props, ShallowUnwrapRef, Data, Computed, Methods, - ExtractPropTypes, + Mixin, + Extends, + Emits, + Props, ExtractDefaultPropTypes, true > @@ -81,7 +113,10 @@ export type VueProxy< RawBindings, Data = DefaultData, Computed extends ComputedOptions = DefaultComputed, - Methods extends MethodOptions = DefaultMethods + Methods extends MethodOptions = DefaultMethods, + Mixin = {}, + Extends = {}, + Emits extends EmitsOptions = {} > = Vue2ComponentOptions< Vue, ShallowUnwrapRef & Data, @@ -90,7 +125,16 @@ export type VueProxy< PropsOptions, ExtractPropTypes > & - VueConstructorProxy + VueConstructorProxy< + PropsOptions, + RawBindings, + Data, + Computed, + Methods, + Mixin, + Extends, + Emits + > // public properties exposed on the proxy, which is used as the render context // in templates (as `this` in the render option) diff --git a/src/component/defineAsyncComponent.ts b/src/component/defineAsyncComponent.ts index 2dd24db2..a5c40894 100644 --- a/src/component/defineAsyncComponent.ts +++ b/src/component/defineAsyncComponent.ts @@ -8,15 +8,16 @@ import { ComponentOptionsWithProps, } from './componentOptions' -type ComponentOptions = +type Component = VueProxy + +type ComponentOrComponentOptions = + // Component + | Component + // ComponentOptions | ComponentOptionsWithoutProps | ComponentOptionsWithArrayProps | ComponentOptionsWithProps -type Component = VueProxy - -type ComponentOrComponentOptions = ComponentOptions | Component - export type AsyncComponentResolveResult = | T | { default: T } // es modules diff --git a/src/component/defineComponent.ts b/src/component/defineComponent.ts index 36359147..a6a035f7 100644 --- a/src/component/defineComponent.ts +++ b/src/component/defineComponent.ts @@ -9,45 +9,109 @@ import { import { VueProxy } from './componentProxy' import { Data } from './common' import { HasDefined } from '../types/basic' +import { EmitsOptions } from '../runtimeContext' -// overload 1: object format with no props +/** + * overload 1: object format with no props + */ export function defineComponent< RawBindings, D = Data, C extends ComputedOptions = {}, - M extends MethodOptions = {} + M extends MethodOptions = {}, + Mixin = {}, + Extends = {}, + Emits extends EmitsOptions = {} >( - options: ComponentOptionsWithoutProps<{}, RawBindings, D, C, M> -): VueProxy<{}, RawBindings, D, C, M> - -// overload 2: object format with array props declaration -// props inferred as { [key in PropNames]?: any } -// return type is for Vetur and TSX support + options: ComponentOptionsWithoutProps< + {}, + RawBindings, + D, + C, + M, + Mixin, + Extends, + Emits + > +): VueProxy<{}, RawBindings, D, C, M, Mixin, Extends, Emits> +/** + * overload 2: object format with array props declaration + * props inferred as `{ [key in PropNames]?: any }` + * + * return type is for Vetur and TSX support + */ export function defineComponent< PropNames extends string, RawBindings = Data, D = Data, C extends ComputedOptions = {}, M extends MethodOptions = {}, + Mixin = {}, + Extends = {}, + Emits extends EmitsOptions = {}, PropsOptions extends ComponentPropsOptions = ComponentPropsOptions >( - options: ComponentOptionsWithArrayProps -): VueProxy, RawBindings, D, C, M> + options: ComponentOptionsWithArrayProps< + PropNames, + RawBindings, + D, + C, + M, + Mixin, + Extends, + Emits + > +): VueProxy< + Readonly<{ [key in PropNames]?: any }>, + RawBindings, + D, + C, + M, + Mixin, + Extends, + Emits +> -// overload 3: object format with object props declaration -// see `ExtractPropTypes` in ./componentProps.ts +/** + * overload 3: object format with object props declaration + * + * see `ExtractPropTypes` in './componentProps.ts' + */ export function defineComponent< Props, RawBindings = Data, D = Data, C extends ComputedOptions = {}, M extends MethodOptions = {}, + Mixin = {}, + Extends = {}, + Emits extends EmitsOptions = {}, PropsOptions extends ComponentPropsOptions = ComponentPropsOptions >( options: HasDefined extends true - ? ComponentOptionsWithProps - : ComponentOptionsWithProps -): VueProxy + ? ComponentOptionsWithProps< + PropsOptions, + RawBindings, + D, + C, + M, + Mixin, + Extends, + Emits, + Props + > + : ComponentOptionsWithProps< + PropsOptions, + RawBindings, + D, + C, + M, + Mixin, + Extends, + Emits + > +): VueProxy + // implementation, close to no-op export function defineComponent(options: any) { return options as any diff --git a/test-dts/defineAsyncComponent.test-d.ts b/test-dts/defineAsyncComponent.test-d.tsx similarity index 100% rename from test-dts/defineAsyncComponent.test-d.ts rename to test-dts/defineAsyncComponent.test-d.tsx diff --git a/test-dts/defineComponent.test-d.ts b/test-dts/defineComponent.test-d.ts deleted file mode 100644 index dd5f7ac9..00000000 --- a/test-dts/defineComponent.test-d.ts +++ /dev/null @@ -1,725 +0,0 @@ -import { - ref, - reactive, - expectType, - expectError, - isNotAnyOrUndefined, - defineComponent, - PropType, - h, -} from './index' - -describe('with object props', () => { - interface ExpectedProps { - a?: number | undefined - b: string - e?: Function - bb: string - bbb: string - cc?: string[] | undefined - dd: { n: 1 } - ee?: () => string - ff?: (a: number, b: string) => { a: boolean } - ccc?: string[] | undefined - ddd: string[] - eee: () => { a: string } - fff: (a: number, b: string) => { a: boolean } - hhh: boolean - ggg: 'foo' | 'bar' - ffff: (a: number, b: string) => { a: boolean } - validated?: string - date: Date - unknown: unknown - } - - type GT = string & { __brand: unknown } - - defineComponent({ - props: { - a: Number, - // required should make property non-void - b: { - type: String, - required: true, - }, - e: Function, - // default value should infer type and make it non-void - bb: { - default: 'hello', - }, - bbb: { - // Note: default function value requires arrow syntax + explicit - // annotation - default: (props: any) => (props.bb as string) || 'foo', - }, - // explicit type casting - cc: Array as PropType, - // required + type casting - dd: { - type: Object as PropType<{ n: 1 }>, - required: true, - }, - // return type - ee: Function as PropType<() => string>, - // arguments + object return - ff: Function as PropType<(a: number, b: string) => { a: boolean }>, - // explicit type casting with constructor - ccc: Array as () => string[], - // required + contructor type casting - ddd: { - type: Array as () => string[], - required: true, - }, - // required + object return - eee: { - type: Function as PropType<() => { a: string }>, - required: true, - }, - // required + arguments + object return - fff: { - type: Function as PropType<(a: number, b: string) => { a: boolean }>, - required: true, - }, - // default + type casting - ggg: { - type: String as PropType<'foo' | 'bar'>, - default: 'foo', - }, - hhh: { - type: Boolean, - }, - // default + function - ffff: { - type: Function as PropType<(a: number, b: string) => { a: boolean }>, - default: (_a: number, _b: string) => ({ a: true }), - }, - validated: { - type: String, - // validator requires explicit annotation - validator: (val: unknown) => val !== '', - }, - date: { - type: Date, - required: true, - }, - unknown: { - type: Object as PropType, - default: null, - }, - }, - setup(props) { - // type assertion. See https://github.com/SamVerschueren/tsd - expectType(props.a) - expectType(props.b) - expectType(props.e) - expectType(props.bb) - expectType(props.cc) - expectType(props.dd) - expectType(props.ee) - expectType(props.ff) - expectType(props.bbb) - expectType(props.ccc) - expectType(props.ddd) - expectType(props.eee) - expectType(props.fff) - expectType(props.ggg) - expectType(props.hhh) - expectType(props.ffff) - expectType(props.validated) - expectType(props.date) - // FIXME: vue 3 bug - // @ts-ignore - expectType({} as ExpectedProps['unknown']) - - isNotAnyOrUndefined(props.a) - isNotAnyOrUndefined(props.b) - isNotAnyOrUndefined(props.e) - isNotAnyOrUndefined(props.bb) - isNotAnyOrUndefined(props.cc) - isNotAnyOrUndefined(props.dd) - isNotAnyOrUndefined(props.ee) - isNotAnyOrUndefined(props.ff) - isNotAnyOrUndefined(props.bbb) - isNotAnyOrUndefined(props.ccc) - isNotAnyOrUndefined(props.ddd) - isNotAnyOrUndefined(props.eee) - isNotAnyOrUndefined(props.fff) - isNotAnyOrUndefined(props.ggg) - isNotAnyOrUndefined(props.hhh) - isNotAnyOrUndefined(props.ffff) - - // @ts-expect-error - expectError((props.a = 1)) - - // setup context - return { - c: ref(1), - d: { - e: ref('hi'), - }, - f: reactive({ - g: ref('hello' as GT), - }), - } - }, - render() { - const props = this.$props - expectType(props.a) - expectType(props.b) - expectType(props.e) - expectType(props.bb) - expectType(props.cc) - expectType(props.dd) - expectType(props.ee) - expectType(props.ff) - expectType(props.bbb) - expectType(props.ccc) - expectType(props.ddd) - expectType(props.eee) - expectType(props.fff) - expectType(props.ggg) - expectType(props.hhh) - expectType(props.ffff) - expectType(props.validated) - // FIXME: vue 3 bug - // @ts-ignore - expectType({} as ExpectedProps['unknown']) - - // @ts-expect-error props should be readonly - expectError((props.a = 1)) - - // should also expose declared props on `this` - expectType(this.a) - expectType(this.b) - expectType(this.e) - expectType(this.bb) - expectType(this.cc) - expectType(this.dd) - expectType(this.ee) - expectType(this.ff) - expectType(this.bbb) - expectType(this.ccc) - expectType(this.ddd) - expectType(this.eee) - expectType(this.fff) - expectType(this.ggg) - expectType(this.ffff) - expectType(this.hhh) - - // @ts-expect-error props on `this` should be readonly - expectError((this.a = 1)) - - // assert setup context unwrapping - expectType(this.c) - expectType(this.d.e.value) - expectType(this.f.g) - - // setup context properties should be mutable - this.c = 2 - - return h('div') - }, - }) -}) - -describe('type inference w/ array props declaration', () => { - defineComponent({ - props: ['a', 'b'], - setup(props) { - // @ts-expect-error props should be readonly - expectError((props.a = 1)) - expectType(props.a) - expectType(props.b) - return { - c: 1, - } - }, - render() { - expectType(this.$props.a) - expectType(this.$props.b) - // @ts-expect-error - expectError((this.$props.a = 1)) - expectType(this.a) - expectType(this.b) - expectType(this.c) - - return h('div') - }, - }) -}) - -describe('type inference w/ options API', () => { - defineComponent({ - props: { a: Number }, - setup() { - return { - b: 123, - } - }, - data() { - // Limitation: we cannot expose the return result of setup() on `this` - // here in data() - somehow that would mess up the inference - expectType(this.a) - expectType(this.$emit) - return { - c: this.a || 123, - } - }, - computed: { - d(): number { - expectType(this.b) - return this.b + 1 - }, - }, - watch: { - a() { - expectType(this.b) - this.b + 1 - }, - }, - created() { - // props - expectType(this.a) - // returned from setup() - expectType(this.b) - // returned from data() - expectType(this.c) - // computed - expectType(this.d) - }, - methods: { - doSomething() { - // props - expectType(this.a) - // returned from setup() - expectType(this.b) - // returned from data() - expectType(this.c) - // computed - expectType(this.d) - }, - }, - render() { - // props - expectType(this.a) - // returned from setup() - expectType(this.b) - // returned from data() - expectType(this.c) - // computed - expectType(this.d) - - return h('div') - }, - }) -}) -describe('with mixins', () => { - /* - const MixinA = defineComponent({ - props: { - aP1: { - type: String, - default: 'aP1' - }, - aP2: Boolean - }, - data() { - return { - a: 1 - } - } - }) - const MixinB = defineComponent({ - props: ['bP1', 'bP2'], - data() { - return { - b: 2 - } - } - }) - const MixinC = defineComponent({ - data() { - return { - c: 3 - } - } - }) - const MixinD = defineComponent({ - mixins: [MixinA], - data() { - return { - d: 4 - } - }, - computed: { - dC1(): number { - return this.d + this.a - }, - dC2(): string { - return this.aP1 + 'dC2' - } - } - }) - const MyComponent = defineComponent({ - mixins: [MixinA, MixinB, MixinC, MixinD], - props: { - // required should make property non-void - z: { - type: String, - required: true - } - }, - render() { - const props = this.$props - // props - expectType(props.aP1) - expectType(props.aP2) - expectType(props.bP1) - expectType(props.bP2) - expectType(props.z) - - const data = this.$data - expectType(data.a) - expectType(data.b) - expectType(data.c) - expectType(data.d) - - // should also expose declared props on `this` - expectType(this.a) - expectType(this.aP1) - expectType(this.aP2) - expectType(this.b) - expectType(this.bP1) - expectType(this.c) - expectType(this.d) - expectType(this.dC1) - expectType(this.dC2) - - // props should be readonly - // @ts-expect-error - expectError((this.aP1 = 'new')) - // @ts-expect-error - expectError((this.z = 1)) - - // props on `this` should be readonly - // @ts-expect-error - expectError((this.bP1 = 1)) - - // string value can not assigned to number type value - // @ts-expect-error - expectError((this.c = '1')) - - // setup context properties should be mutable - this.d = 5 - - return null - } - }) - - // Test TSX - expectType( - - ) - - // missing required props - // @ts-expect-error - expectError() - - // wrong prop types - // @ts-expect-error - expectError() - // @ts-expect-error - expectError() - */ -}) - -describe('with extends', () => { - /* - const Base = defineComponent({ - props: { - aP1: Boolean, - aP2: { - type: Number, - default: 2 - } - }, - data() { - return { - a: 1 - } - }, - computed: { - c(): number { - return this.aP2 + this.a - } - } - }) - const MyComponent = defineComponent({ - extends: Base, - props: { - // required should make property non-void - z: { - type: String, - required: true - } - }, - render() { - const props = this.$props - // props - expectType(props.aP1) - expectType(props.aP2) - expectType(props.z) - - const data = this.$data - expectType(data.a) - - // should also expose declared props on `this` - expectType(this.a) - expectType(this.aP1) - expectType(this.aP2) - - // setup context properties should be mutable - this.a = 5 - - return null - } - }) - - // Test TSX - expectType() - - // missing required props - // @ts-expect-error - expectError() - - // wrong prop types - // @ts-expect-error - expectError() - // @ts-expect-error - expectError() - */ -}) -describe('extends with mixins', () => { - /* - const Mixin = defineComponent({ - props: { - mP1: { - type: String, - default: 'mP1' - }, - mP2: Boolean - }, - data() { - return { - a: 1 - } - } - }) - const Base = defineComponent({ - props: { - p1: Boolean, - p2: { - type: Number, - default: 2 - } - }, - data() { - return { - b: 2 - } - }, - computed: { - c(): number { - return this.p2 + this.b - } - } - }) - const MyComponent = defineComponent({ - extends: Base, - mixins: [Mixin], - props: { - // required should make property non-void - z: { - type: String, - required: true - } - }, - render() { - const props = this.$props - // props - expectType(props.p1) - expectType(props.p2) - expectType(props.z) - expectType(props.mP1) - expectType(props.mP2) - - const data = this.$data - expectType(data.a) - expectType(data.b) - - // should also expose declared props on `this` - expectType(this.a) - expectType(this.b) - expectType(this.p1) - expectType(this.p2) - expectType(this.mP1) - expectType(this.mP2) - - // setup context properties should be mutable - this.a = 5 - - return null - } - }) - - // Test TSX - expectType() - - // missing required props - // @ts-expect-error - expectError() - - // wrong prop types - // @ts-expect-error - expectError() - // @ts-expect-error - expectError() - */ -}) - -describe('compatibility w/ createApp', () => { - /* - const comp = defineComponent({}) - createApp(comp).mount('#hello') - - const comp2 = defineComponent({ - props: { foo: String } - }) - createApp(comp2).mount('#hello') - - const comp3 = defineComponent({ - setup() { - return { - a: 1 - } - } - }) - createApp(comp3).mount('#hello') - */ -}) - -describe('defineComponent', () => { - test('should accept components defined with defineComponent', () => { - const comp = defineComponent({}) - defineComponent({ - components: { comp }, - }) - }) -}) - -describe('emits', () => { - /* - - // Note: for TSX inference, ideally we want to map emits to onXXX props, - // but that requires type-level string constant concatenation as suggested in - // https://github.com/Microsoft/TypeScript/issues/12754 - - // The workaround for TSX users is instead of using emits, declare onXXX props - // and call them instead. Since `v-on:click` compiles to an `onClick` prop, - // this would also support other users consuming the component in templates - // with `v-on` listeners. - - // with object emits - defineComponent({ - emits: { - click: (n: number) => typeof n === 'number', - input: (b: string) => null - }, - setup(props, { emit }) { - emit('click', 1) - emit('input', 'foo') - // @ts-expect-error - expectError(emit('nope')) - // @ts-expect-error - expectError(emit('click')) - // @ts-expect-error - expectError(emit('click', 'foo')) - // @ts-expect-error - expectError(emit('input')) - // @ts-expect-error - expectError(emit('input', 1)) - }, - created() { - this.$emit('click', 1) - this.$emit('input', 'foo') - // @ts-expect-error - expectError(this.$emit('nope')) - // @ts-expect-error - expectError(this.$emit('click')) - // @ts-expect-error - expectError(this.$emit('click', 'foo')) - // @ts-expect-error - expectError(this.$emit('input')) - // @ts-expect-error - expectError(this.$emit('input', 1)) - } - }) - - // with array emits - defineComponent({ - emits: ['foo', 'bar'], - setup(props, { emit }) { - emit('foo') - emit('foo', 123) - emit('bar') - // @ts-expect-error - expectError(emit('nope')) - }, - created() { - this.$emit('foo') - this.$emit('foo', 123) - this.$emit('bar') - // @ts-expect-error - expectError(this.$emit('nope')) - } - }) - */ -}) - -describe('vetur', () => { - // #609 - it('should have access to options API', () => { - const Comp = defineComponent({ - data() { - return { - a: 1, - } - }, - - computed: { - ac() { - return 1 - }, - }, - - methods: { - callA(b: number) { - return b - }, - }, - - setup() { - return { - sa: '1', - } - }, - }) - - const comp = new Comp() - - expectType(comp.a) - expectType(comp.ac) - expectType(comp.sa) - expectType<(b: number) => number>(comp.callA) - }) -}) diff --git a/test-dts/defineComponent.test-d.tsx b/test-dts/defineComponent.test-d.tsx new file mode 100644 index 00000000..2f68a4e0 --- /dev/null +++ b/test-dts/defineComponent.test-d.tsx @@ -0,0 +1,1152 @@ +import { + describe, + // Component, + defineComponent, + PropType, + ref, + reactive, + createApp, + expectError, + expectType, + ComponentPublicInstance, + // ComponentOptions, + // SetupContext, + IsUnion, + h, +} from './index' + +describe('with object props', () => { + interface ExpectedProps { + a?: number | undefined + b: string + e?: Function + h: boolean + j: undefined | (() => string | undefined) + bb: string + bbb: string + bbbb: string | undefined + bbbbb: string | undefined + cc?: string[] | undefined + dd: { n: 1 } + ee?: () => string + ff?: (a: number, b: string) => { a: boolean } + ccc?: string[] | undefined + ddd: string[] + eee: () => { a: string } + fff: (a: number, b: string) => { a: boolean } + hhh: boolean + ggg: 'foo' | 'bar' + ffff: (a: number, b: string) => { a: boolean } + iii?: (() => string) | (() => number) + jjj: ((arg1: string) => string) | ((arg1: string, arg2: string) => string) + kkk?: any + validated?: string + date?: Date + l?: Date + ll?: Date | number + lll?: string | number + } + + type GT = string & { __brand: unknown } + + const MyComponent = defineComponent({ + props: { + a: Number, + // required should make property non-void + b: { + type: String, + required: true, + }, + e: Function, + h: Boolean, + j: Function as PropType string | undefined)>, + // default value should infer type and make it non-void + bb: { + default: 'hello', + }, + bbb: { + // Note: default function value requires arrow syntax + explicit + // annotation + default: (props: any) => (props.bb as string) || 'foo', + }, + bbbb: { + type: String, + default: undefined, + }, + bbbbb: { + type: String, + default: () => undefined, + }, + // explicit type casting + cc: Array as PropType, + // required + type casting + dd: { + type: Object as PropType<{ n: 1 }>, + required: true, + }, + // return type + ee: Function as PropType<() => string>, + // arguments + object return + ff: Function as PropType<(a: number, b: string) => { a: boolean }>, + // explicit type casting with constructor + ccc: Array as () => string[], + // required + constructor type casting + ddd: { + type: Array as () => string[], + required: true, + }, + // required + object return + eee: { + type: Function as PropType<() => { a: string }>, + required: true, + }, + // required + arguments + object return + fff: { + type: Function as PropType<(a: number, b: string) => { a: boolean }>, + required: true, + }, + hhh: { + type: Boolean, + required: true, + }, + // default + type casting + ggg: { + type: String as PropType<'foo' | 'bar'>, + default: 'foo', + }, + // default + function + ffff: { + type: Function as PropType<(a: number, b: string) => { a: boolean }>, + default: (a: number, b: string) => ({ a: a > +b }), + }, + // union + function with different return types + iii: Function as PropType<(() => string) | (() => number)>, + // union + function with different args & same return type + jjj: { + type: Function as PropType< + ((arg1: string) => string) | ((arg1: string, arg2: string) => string) + >, + required: true, + }, + kkk: null, + validated: { + type: String, + // validator requires explicit annotation + validator: (val: unknown) => val !== '', + }, + date: Date, + l: [Date], + ll: [Date, Number], + lll: [String, Number], + }, + setup(props) { + // type assertion. See https://github.com/SamVerschueren/tsd + expectType(props.a) + expectType(props.b) + expectType(props.e) + expectType(props.h) + expectType(props.j) + expectType(props.bb) + expectType(props.bbb) + expectType(props.bbbb) + expectType(props.bbbbb) + expectType(props.cc) + expectType(props.dd) + expectType(props.ee) + expectType(props.ff) + expectType(props.ccc) + expectType(props.ddd) + expectType(props.eee) + expectType(props.fff) + expectType(props.hhh) + expectType(props.ggg) + expectType(props.ffff) + if (typeof props.iii !== 'function') { + expectType(props.iii) + } + expectType(props.iii) + expectType>(true) + expectType(props.jjj) + expectType(props.kkk) + expectType(props.validated) + expectType(props.date) + // FIXME: add Date support + // @ts-ignore + expectType(props.l) + // FIXME: add Date support + // @ts-ignore + expectType(props.ll) + expectType(props.lll) + + // @ts-expect-error props should be readonly + expectError((props.a = 1)) + + // setup context + return { + c: ref(1), + d: { + e: ref('hi'), + }, + f: reactive({ + g: ref('hello' as GT), + }), + } + }, + render() { + const props = this.$props + expectType(props.a) + expectType(props.b) + expectType(props.e) + expectType(props.h) + expectType(props.bb) + expectType(props.cc) + expectType(props.dd) + expectType(props.ee) + expectType(props.ff) + expectType(props.ccc) + expectType(props.ddd) + expectType(props.eee) + expectType(props.fff) + expectType(props.hhh) + expectType(props.ggg) + if (typeof props.iii !== 'function') { + expectType(props.iii) + } + expectType(props.iii) + expectType>(true) + expectType(props.jjj) + expectType(props.kkk) + + // @ts-expect-error props should be readonly + expectError((props.a = 1)) + + // should also expose declared props on `this` + expectType(this.a) + expectType(this.b) + expectType(this.e) + expectType(this.h) + expectType(this.bb) + expectType(this.cc) + expectType(this.dd) + expectType(this.ee) + expectType(this.ff) + expectType(this.ccc) + expectType(this.ddd) + expectType(this.eee) + expectType(this.fff) + expectType(this.hhh) + expectType(this.ggg) + if (typeof this.iii !== 'function') { + expectType(this.iii) + } + expectType(this.iii) + const { jjj } = this + expectType>(true) + expectType(this.jjj) + expectType(this.kkk) + + // @ts-expect-error props on `this` should be readonly + expectError((this.a = 1)) + + // assert setup context unwrapping + expectType(this.c) + expectType(this.d.e.value) + expectType(this.f.g) + + // setup context properties should be mutable + this.c = 2 + + return h('div') + }, + }) + + // expectType(MyComponent) + + // Test TSX + expectType( + {}} + cc={['cc']} + dd={{ n: 1 }} + ee={() => 'ee'} + ccc={['ccc']} + ddd={['ddd']} + eee={() => ({ a: 'eee' })} + fff={(a, b) => ({ a: a > +b })} + hhh={false} + ggg="foo" + jjj={() => ''} + // FIXME + // @ts-ignore + // should allow class/style as attrs + class="bar" + style={{ color: 'red' }} + // should allow key + key={'foo'} + // should allow ref + ref={'foo'} + /> + ) + + // expectType( + // ({ a: 'eee' })} + // fff={(a, b) => ({ a: a > +b })} + // hhh={false} + // jjj={() => ''} + // /> + // ) + + // @ts-expect-error missing required props + expectError() + + expectError( + // @ts-expect-error wrong prop types + + ) + expectError( + // @ts-expect-error wrong prop types + + ) + // @ts-expect-error + expectError() + + // `this` should be void inside of prop validators and prop default factories + defineComponent({ + props: { + myProp: { + type: Number, + validator(val: unknown): boolean { + // FIXME + // @ts-ignore + return val !== this.otherProp + }, + default(): number { + // FIXME + // @ts-ignore + return this.otherProp + 1 + }, + }, + otherProp: { + type: Number, + required: true, + }, + }, + }) +}) + +// describe('type inference w/ optional props declaration', () => { +// const MyComponent = defineComponent<{ a: string[]; msg: string }>({ +// setup(props) { +// expectType(props.msg) +// expectType(props.a) +// return { +// b: 1, +// } +// }, +// }) + +// expectType() +// // @ts-expect-error +// expectError() +// // @ts-expect-error +// expectError() +// }) + +// describe('type inference w/ direct setup function', () => { +// const MyComponent = defineComponent((_props: { msg: string }) => {}) +// expectType() +// // @ts-expect-error +// expectError() +// expectError() +// }) + +// describe('type inference w/ array props declaration', () => { +// const MyComponent = defineComponent({ +// props: ['a', 'b'], +// setup(props) { +// // @ts-expect-error props should be readonly +// expectError((props.a = 1)) +// expectType(props.a) +// expectType(props.b) +// return { +// c: 1, +// } +// }, +// render() { +// expectType(this.$props.a) +// expectType(this.$props.b) +// // @ts-expect-error +// expectError((this.$props.a = 1)) +// expectType(this.a) +// expectType(this.b) +// expectType(this.c) +// }, +// }) +// expectType() +// // @ts-expect-error +// expectError() +// }) + +// describe('type inference w/ options API', () => { +// defineComponent({ +// props: { a: Number }, +// setup() { +// return { +// b: 123, +// } +// }, +// data() { +// // Limitation: we cannot expose the return result of setup() on `this` +// // here in data() - somehow that would mess up the inference +// expectType(this.a) +// return { +// c: this.a || 123, +// someRef: ref(0), +// } +// }, +// computed: { +// d() { +// expectType(this.b) +// return this.b + 1 +// }, +// e: { +// get() { +// expectType(this.b) +// expectType(this.d) + +// return this.b + this.d +// }, +// set(v: number) { +// expectType(this.b) +// expectType(this.d) +// expectType(v) +// }, +// }, +// }, +// watch: { +// a() { +// expectType(this.b) +// this.b + 1 +// }, +// }, +// created() { +// // props +// expectType(this.a) +// // returned from setup() +// expectType(this.b) +// // returned from data() +// expectType(this.c) +// // computed +// expectType(this.d) +// // computed get/set +// expectType(this.e) +// expectType(this.someRef) +// }, +// methods: { +// doSomething() { +// // props +// expectType(this.a) +// // returned from setup() +// expectType(this.b) +// // returned from data() +// expectType(this.c) +// // computed +// expectType(this.d) +// // computed get/set +// expectType(this.e) +// }, +// returnSomething() { +// return this.a +// }, +// }, +// render() { +// // props +// expectType(this.a) +// // returned from setup() +// expectType(this.b) +// // returned from data() +// expectType(this.c) +// // computed +// expectType(this.d) +// // computed get/set +// expectType(this.e) +// // method +// expectType<() => number | undefined>(this.returnSomething) +// }, +// }) +// }) + +// describe('with mixins', () => { +// const MixinA = defineComponent({ +// emits: ['bar'], +// props: { +// aP1: { +// type: String, +// default: 'aP1', +// }, +// aP2: Boolean, +// }, +// data() { +// return { +// a: 1, +// } +// }, +// }) +// const MixinB = defineComponent({ +// props: ['bP1', 'bP2'], +// data() { +// return { +// b: 2, +// } +// }, +// }) +// const MixinC = defineComponent({ +// data() { +// return { +// c: 3, +// } +// }, +// }) +// const MixinD = defineComponent({ +// mixins: [MixinA], +// data() { +// //@ts-expect-error computed are not available on data() +// expectError(this.dC1) +// //@ts-expect-error computed are not available on data() +// expectError(this.dC2) + +// return { +// d: 4, +// } +// }, +// setup(props) { +// expectType(props.aP1) +// }, +// computed: { +// dC1() { +// return this.d + this.a +// }, +// dC2() { +// return this.aP1 + 'dC2' +// }, +// }, +// }) +// const MyComponent = defineComponent({ +// mixins: [MixinA, MixinB, MixinC, MixinD], +// emits: ['click'], +// props: { +// // required should make property non-void +// z: { +// type: String, +// required: true, +// }, +// }, + +// data(vm) { +// expectType(vm.a) +// expectType(vm.b) +// expectType(vm.c) +// expectType(vm.d) + +// // should also expose declared props on `this` +// expectType(this.a) +// expectType(this.aP1) +// expectType(this.aP2) +// expectType(this.b) +// expectType(this.bP1) +// expectType(this.c) +// expectType(this.d) + +// return {} +// }, + +// setup(props) { +// expectType(props.z) +// // props +// expectType<((...args: any[]) => any) | undefined>(props.onClick) +// // from Base +// expectType<((...args: any[]) => any) | undefined>(props.onBar) +// expectType(props.aP1) +// expectType(props.aP2) +// expectType(props.bP1) +// expectType(props.bP2) +// expectType(props.z) +// }, +// render() { +// const props = this.$props +// // props +// expectType<((...args: any[]) => any) | undefined>(props.onClick) +// // from Base +// expectType<((...args: any[]) => any) | undefined>(props.onBar) +// expectType(props.aP1) +// expectType(props.aP2) +// expectType(props.bP1) +// expectType(props.bP2) +// expectType(props.z) + +// const data = this.$data +// expectType(data.a) +// expectType(data.b) +// expectType(data.c) +// expectType(data.d) + +// // should also expose declared props on `this` +// expectType(this.a) +// expectType(this.aP1) +// expectType(this.aP2) +// expectType(this.b) +// expectType(this.bP1) +// expectType(this.c) +// expectType(this.d) +// expectType(this.dC1) +// expectType(this.dC2) + +// // props should be readonly +// // @ts-expect-error +// expectError((this.aP1 = 'new')) +// // @ts-expect-error +// expectError((this.z = 1)) + +// // props on `this` should be readonly +// // @ts-expect-error +// expectError((this.bP1 = 1)) + +// // string value can not assigned to number type value +// // @ts-expect-error +// expectError((this.c = '1')) + +// // setup context properties should be mutable +// this.d = 5 + +// return h('div') +// }, +// }) + +// // Test TSX +// expectType( +// +// ) + +// // missing required props +// // @ts-expect-error +// expectError() + +// // wrong prop types +// // @ts-expect-error +// expectError() +// // @ts-expect-error +// expectError() +// }) + +// describe('with extends', () => { +// const Base = defineComponent({ +// props: { +// aP1: Boolean, +// aP2: { +// type: Number, +// default: 2, +// }, +// }, +// data() { +// return { +// a: 1, +// } +// }, +// computed: { +// c(): number { +// return this.aP2 + this.a +// }, +// }, +// }) +// const MyComponent = defineComponent({ +// extends: Base, +// props: { +// // required should make property non-void +// z: { +// type: String, +// required: true, +// }, +// }, +// render() { +// const props = this.$props +// // props +// expectType(props.aP1) +// expectType(props.aP2) +// expectType(props.z) + +// const data = this.$data +// expectType(data.a) + +// // should also expose declared props on `this` +// expectType(this.a) +// expectType(this.aP1) +// expectType(this.aP2) + +// // setup context properties should be mutable +// this.a = 5 + +// return h('div') +// }, +// }) + +// // Test TSX +// expectType() + +// // missing required props +// // @ts-expect-error +// expectError() + +// // wrong prop types +// // @ts-expect-error +// expectError() +// // @ts-expect-error +// expectError() +// }) + +// describe('extends with mixins', () => { +// const Mixin = defineComponent({ +// emits: ['bar'], +// props: { +// mP1: { +// type: String, +// default: 'mP1', +// }, +// mP2: Boolean, +// mP3: { +// type: Boolean, +// required: true, +// }, +// }, +// data() { +// return { +// a: 1, +// } +// }, +// }) +// const Base = defineComponent({ +// emits: ['foo'], +// props: { +// p1: Boolean, +// p2: { +// type: Number, +// default: 2, +// }, +// p3: { +// type: Boolean, +// required: true, +// }, +// }, +// data() { +// return { +// b: 2, +// } +// }, +// computed: { +// c(): number { +// return this.p2 + this.b +// }, +// }, +// }) +// const MyComponent = defineComponent({ +// extends: Base, +// mixins: [Mixin], +// emits: ['click'], +// props: { +// // required should make property non-void +// z: { +// type: String, +// required: true, +// }, +// }, +// render() { +// const props = this.$props +// // props +// expectType<((...args: any[]) => any) | undefined>(props.onClick) +// // from Mixin +// expectType<((...args: any[]) => any) | undefined>(props.onBar) +// // from Base +// expectType<((...args: any[]) => any) | undefined>(props.onFoo) +// expectType(props.p1) +// expectType(props.p2) +// expectType(props.z) +// expectType(props.mP1) +// expectType(props.mP2) + +// const data = this.$data +// expectType(data.a) +// expectType(data.b) + +// // should also expose declared props on `this` +// expectType(this.a) +// expectType(this.b) +// expectType(this.p1) +// expectType(this.p2) +// expectType(this.mP1) +// expectType(this.mP2) + +// // setup context properties should be mutable +// this.a = 5 + +// return h('div') +// }, +// }) + +// // Test TSX +// expectType() + +// // mP1, mP2, p1, and p2 have default value. these are not required +// expectType() + +// // missing required props +// // @ts-expect-error +// expectError() +// // missing required props from mixin +// // @ts-expect-error +// expectError() +// // missing required props from extends +// // @ts-expect-error +// expectError() + +// // wrong prop types +// // @ts-expect-error +// expectError() +// // @ts-expect-error +// expectError() + +// // #3468 +// const CompWithD = defineComponent({ +// data() { +// return { foo: 1 } +// }, +// }) +// const CompWithC = defineComponent({ +// computed: { +// foo() { +// return 1 +// }, +// }, +// }) +// const CompWithM = defineComponent({ methods: { foo() {} } }) +// const CompEmpty = defineComponent({}) + +// defineComponent({ +// mixins: [CompWithD, CompEmpty], +// mounted() { +// expectType(this.foo) +// }, +// }) +// defineComponent({ +// mixins: [CompWithC, CompEmpty], +// mounted() { +// expectType(this.foo) +// }, +// }) +// defineComponent({ +// mixins: [CompWithM, CompEmpty], +// mounted() { +// expectType<() => void>(this.foo) +// }, +// }) +// }) + +describe('compatibility w/ createApp', () => { + const comp = defineComponent({}) + createApp(comp).mount('#hello') + + const comp2 = defineComponent({ + props: { foo: String }, + }) + createApp(comp2).mount('#hello') + + const comp3 = defineComponent({ + setup() { + return { + a: 1, + } + }, + }) + createApp(comp3).mount('#hello') +}) + +describe('defineComponent', () => { + test('should accept components defined with defineComponent', () => { + const comp = defineComponent({}) + defineComponent({ + components: { comp }, + }) + }) + + test('should accept class components with receiving constructor arguments', () => { + // class Comp { + // static __vccOpts = {} + // constructor(_props: { foo: string }) {} + // } + // defineComponent({ + // components: { Comp }, + // }) + }) +}) + +describe('emits', () => { + // Note: for TSX inference, ideally we want to map emits to onXXX props, + // but that requires type-level string constant concatenation as suggested in + // https://github.com/Microsoft/TypeScript/issues/12754 + + // The workaround for TSX users is instead of using emits, declare onXXX props + // and call them instead. Since `v-on:click` compiles to an `onClick` prop, + // this would also support other users consuming the component in templates + // with `v-on` listeners. + + // with object emits + defineComponent({ + emits: { + click: (n: number) => typeof n === 'number', + input: (b: string) => b.length > 1, + }, + // setup(props, { emit }) { + // expectType<((n: number) => boolean) | undefined>(props.onClick) + // expectType<((b: string) => boolean) | undefined>(props.onInput) + // emit('click', 1) + // emit('input', 'foo') + // // @ts-expect-error + // expectError(emit('nope')) + // // @ts-expect-error + // expectError(emit('click')) + // // @ts-expect-error + // expectError(emit('click', 'foo')) + // // @ts-expect-error + // expectError(emit('input')) + // // @ts-expect-error + // expectError(emit('input', 1)) + // }, + created() { + this.$emit('click', 1) + this.$emit('input', 'foo') + // @ts-expect-error + expectError(this.$emit('nope')) + // @ts-expect-error + expectError(this.$emit('click')) + // @ts-expect-error + expectError(this.$emit('click', 'foo')) + // @ts-expect-error + expectError(this.$emit('input')) + // @ts-expect-error + expectError(this.$emit('input', 1)) + }, + // mounted() { + // // #3599 + // this.$nextTick(function () { + // // this should be bound to this instance + + // this.$emit('click', 1) + // this.$emit('input', 'foo') + // // @ts-expect-error + // expectError(this.$emit('nope')) + // // @ts-expect-error + // expectError(this.$emit('click')) + // // @ts-expect-error + // expectError(this.$emit('click', 'foo')) + // // @ts-expect-error + // expectError(this.$emit('input')) + // // @ts-expect-error + // expectError(this.$emit('input', 1)) + // }) + // }, + }) + + // with array emits + // defineComponent({ + // emits: ['foo', 'bar'], + // setup(props, { emit }) { + // expectType<((...args: any[]) => any) | undefined>(props.onFoo) + // expectType<((...args: any[]) => any) | undefined>(props.onBar) + // emit('foo') + // emit('foo', 123) + // emit('bar') + // // @ts-expect-error + // expectError(emit('nope')) + // }, + // created() { + // this.$emit('foo') + // this.$emit('foo', 123) + // this.$emit('bar') + // // @ts-expect-error + // expectError(this.$emit('nope')) + // }, + // }) + + // with tsx + // const Component = defineComponent({ + // emits: { + // click: (n: number) => typeof n === 'number', + // }, + // // setup(props, { emit }) { + // // expectType<((n: number) => any) | undefined>(props.onClick) + // // emit('click', 1) + // // // @ts-expect-error + // // expectError(emit('click')) + // // // @ts-expect-error + // // expectError(emit('click', 'foo')) + // // }, + // }) + // + // defineComponent({ + // render() { + // return ( + // { + // return n + 1 + // }} + // /> + // ) + // }, + // }) + + // without emits + defineComponent({ + setup(props, { emit }) { + emit('test', 1) + emit('test') + }, + }) + + // emit should be valid when ComponentPublicInstance is used. + const instance = {} as ComponentPublicInstance + instance.$emit('test', 1) + instance.$emit('test') + + // `this` should be void inside of emits validators + defineComponent({ + props: ['bar'], + emits: { + foo(): boolean { + // @ts-expect-error + return this.bar === 3 + }, + }, + }) +}) + +// describe('componentOptions setup should be `SetupContext`', () => { +// expect( +// {} as (props: Record, ctx: SetupContext) => any +// ) +// }) + +// describe('extract instance type', () => { +// const Base = defineComponent({ +// props: { +// baseA: { +// type: Number, +// default: 1, +// }, +// }, +// }) +// const MixinA = defineComponent({ +// props: { +// mA: { +// type: String, +// default: '', +// }, +// }, +// }) +// const CompA = defineComponent({ +// extends: Base, +// mixins: [MixinA], +// props: { +// a: { +// type: Boolean, +// default: false, +// }, +// b: { +// type: String, +// required: true, +// }, +// c: Number, +// }, +// }) + +// const compA = {} as InstanceType + +// expectType(compA.a) +// expectType(compA.b) +// expectType(compA.c) +// // mixins +// expectType(compA.mA) +// // extends +// expectType(compA.baseA) + +// // @ts-expect-error +// expectError((compA.a = true)) +// // @ts-expect-error +// expectError((compA.b = 'foo')) +// // @ts-expect-error +// expectError((compA.c = 1)) +// // @ts-expect-error +// expectError((compA.mA = 'foo')) +// // @ts-expect-error +// expectError((compA.baseA = 1)) +// }) + +// describe('async setup', () => { +// type GT = string & { __brand: unknown } +// const Comp = defineComponent({ +// async setup() { +// // setup context +// return { +// a: ref(1), +// b: { +// c: ref('hi'), +// }, +// d: reactive({ +// e: ref('hello' as GT), +// }), +// } +// }, +// render() { +// // assert setup context unwrapping +// expectType(this.a) +// expectType(this.b.c.value) +// expectType(this.d.e) + +// // setup context properties should be mutable +// this.a = 2 +// }, +// }) + +// const vm = {} as InstanceType +// // assert setup context unwrapping +// expectType(vm.a) +// expectType(vm.b.c.value) +// expectType(vm.d.e) + +// // setup context properties should be mutable +// vm.a = 2 +// }) + +// check if defineComponent can be exported +export default { + // function components + a: defineComponent((props: any) => h('div')), + // no props + b: defineComponent({ + data() { + return {} + }, + }), + c: defineComponent({ + props: ['a'], + }), + d: defineComponent({ + props: { + a: Number, + }, + }), +} diff --git a/test-dts/index.d.ts b/test-dts/index.d.ts index 1bf8d54a..8ba1def4 100644 --- a/test-dts/index.d.ts +++ b/test-dts/index.d.ts @@ -1,3 +1,4 @@ +import type {} from '@vue/runtime-dom' export * from '@vue/composition-api' // export * from 'vue3' @@ -11,3 +12,9 @@ export function expectAssignable(value: T2): void type IfNotAny = 0 extends 1 & T ? never : T type IfNotUndefined = Exclude extends never ? never : T export function isNotAnyOrUndefined(value: IfNotAny>): void + +export type IsUnion = ( + T extends any ? (U extends T ? false : true) : never +) extends false + ? false + : true diff --git a/test-dts/readonly.test-d.ts b/test-dts/readonly.test-d.tsx similarity index 100% rename from test-dts/readonly.test-d.ts rename to test-dts/readonly.test-d.tsx diff --git a/test-dts/ref.test-d.ts b/test-dts/ref.test-d.tsx similarity index 100% rename from test-dts/ref.test-d.ts rename to test-dts/ref.test-d.tsx diff --git a/test-dts/tsconfig.json b/test-dts/tsconfig.json index e7d37a47..17a568b7 100644 --- a/test-dts/tsconfig.json +++ b/test-dts/tsconfig.json @@ -4,10 +4,11 @@ "noEmit": true, "declaration": true, "baseUrl": ".", + "jsx": "preserve", "paths": { "@vue/composition-api": ["../src"] } }, "exclude": ["../test"], - "include": ["../src", "./*.ts"] -} \ No newline at end of file + "include": ["../src", "./*.ts", "./*.tsx"] +} diff --git a/test-dts/watch.test-d.ts b/test-dts/watch.test-d.tsx similarity index 100% rename from test-dts/watch.test-d.ts rename to test-dts/watch.test-d.tsx