import { type Component, type ComponentOptions, type ComponentPublicInstance, type PropType, type SetupContext, type Slots, type SlotsType, type VNode, createApp, defineComponent, h, reactive, ref, withKeys, withModifiers, } from 'vue' import { type IsAny, type IsUnion, describe, expectType } from './utils' describe('with object props', () => { interface ExpectedProps { a?: number | undefined aa: number aaa: number | null aaaa: 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 props = { a: Number, aa: { type: Number as PropType, default: 1, }, aaa: { type: Number as PropType, default: 1, }, aaaa: { type: Number as PropType, // `as const` prevents widening to `boolean` (keeps literal `true` type) required: true as const, }, // required should make property non-void b: { type: String, required: true as 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 as 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 as true, }, // required + object return eee: { type: Function as PropType<() => { a: string }>, required: true as true, }, // required + arguments + object return fff: { type: Function as PropType<(a: number, b: string) => { a: boolean }>, required: true as true, }, hhh: { type: Boolean, required: true as 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 as true, }, kkk: null, validated: { type: String, // validator requires explicit annotation validator: (val: unknown) => val !== '', }, date: Date, l: [Date], ll: [Date, Number], lll: [String, Number], } const MyComponent = defineComponent({ props, setup(props) { // type assertion. See https://github.com/SamVerschueren/tsd expectType(props.a) expectType(props.aa) expectType(props.aaa) // @ts-expect-error should included `undefined` expectType(props.aaaa) expectType(props.aaaa) 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) expectType(props.l) expectType(props.ll) expectType(props.lll) // @ts-expect-error props should be readonly props.a = 1 // setup context return { c: ref(1), d: { e: ref('hi'), }, f: reactive({ g: ref('hello' as GT), }), } }, provide() { return {} }, render() { const props = this.$props expectType(props.a) expectType(props.aa) expectType(props.aaa) 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 props.a = 1 // should also expose declared props on `this` expectType(this.a) expectType(this.aa) expectType(this.aaa) 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 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 null }, }) 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={() => ''} // should allow class/style as attrs class="bar" style={{ color: 'red' }} // should allow key key={'foo'} // should allow ref ref={'foo'} ref_for={true} />, ) expectType( ({ a: 'eee' })} fff={(a, b) => ({ a: a > +b })} hhh={false} jjj={() => ''} />, ) // @ts-expect-error missing required props let c = // @ts-expect-error wrong prop types c = // @ts-expect-error wrong prop types c = // @ts-expect-error ; // `this` should be void inside of prop validators and prop default factories defineComponent({ props: { myProp: { type: Number, validator(val: unknown): boolean { // @ts-expect-error return val !== this.otherProp }, default(): number { // @ts-expect-error 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 ; // @ts-expect-error ; }) describe('type inference w/ direct setup function', () => { const MyComponent = defineComponent((_props: { msg: string }) => () => {}) expectType() // @ts-expect-error ; // @ts-expect-error ; }) describe('type inference w/ array props declaration', () => { const MyComponent = defineComponent({ props: ['a', 'b'], setup(props) { // @ts-expect-error props should be readonly props.a = 1 expectType(props.a) expectType(props.b) return { c: 1, } }, render() { expectType(this.$props.a) expectType(this.$props.b) // @ts-expect-error this.$props.a = 1 expectType(this.a) expectType(this.b) expectType(this.c) }, }) expectType() // @ts-expect-error ; }) 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) }, }) }) // #4051 describe('type inference w/ empty prop object', () => { const MyComponent = defineComponent({ props: {}, setup(props) { return {} }, render() {}, }) expectType() // AllowedComponentProps expectType() // ComponentCustomProps expectType() // VNodeProps expectType() // @ts-expect-error expectError() }) 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 MixinA 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 MixinA 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 this.aP1 = 'new' // @ts-expect-error this.z = 1 // props on `this` should be readonly // @ts-expect-error this.bP1 = 1 // string value can not assigned to number type value // @ts-expect-error this.c = '1' // setup context properties should be mutable this.d = 5 return null }, }) // Test TSX expectType( , ) // missing required props // @ts-expect-error ; // wrong prop types // @ts-expect-error ; // @ts-expect-error ; }) 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 ; // wrong prop types // @ts-expect-error ; // @ts-expect-error ; }) 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 null }, }) // Test TSX expectType() // mP1, mP2, p1, and p2 have default value. these are not required expectType() // missing required props // @ts-expect-error ; // missing required props from mixin // @ts-expect-error ; // missing required props from extends // @ts-expect-error ; // wrong prop types // @ts-expect-error ; // @ts-expect-error ; // #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', () => { describe('should accept components defined with defineComponent', () => { const comp = defineComponent({}) defineComponent({ components: { comp }, }) }) describe('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, Focus: (f: boolean) => !!f, }, setup(props, { emit }) { expectType<((n: number) => boolean) | undefined>(props.onClick) expectType<((b: string) => boolean) | undefined>(props.onInput) expectType<((f: boolean) => boolean) | undefined>(props.onFocus) emit('click', 1) emit('input', 'foo') emit('Focus', true) // @ts-expect-error emit('nope') // @ts-expect-error emit('click') // @ts-expect-error emit('click', 'foo') // @ts-expect-error emit('input') // @ts-expect-error emit('input', 1) // @ts-expect-error emit('focus') // @ts-expect-error emit('focus', true) }, created() { this.$emit('click', 1) this.$emit('input', 'foo') // @ts-expect-error this.$emit('nope') // @ts-expect-error this.$emit('click') // @ts-expect-error this.$emit('click', 'foo') // @ts-expect-error this.$emit('input') // @ts-expect-error this.$emit('input', 1) // @ts-expect-error this.$emit('focus') // @ts-expect-error this.$emit('focus', true) }, mounted() { // #3599 this.$nextTick(function () { // this should be bound to this instance this.$emit('click', 1) this.$emit('input', 'foo') // @ts-expect-error this.$emit('nope') // @ts-expect-error this.$emit('click') // @ts-expect-error this.$emit('click', 'foo') // @ts-expect-error this.$emit('input') // @ts-expect-error this.$emit('input', 1) // @ts-expect-error this.$emit('focus') // @ts-expect-error this.$emit('focus', true) }) }, }) // 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 emit('nope') }, created() { this.$emit('foo') this.$emit('foo', 123) this.$emit('bar') // @ts-expect-error 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 emit('click') // @ts-expect-error emit('click', 'foo') }, }) defineComponent({ render() { return ( { return n + 1 }} /> ) }, }) // #11803 manual props annotation in setup() const Hello = defineComponent({ name: 'HelloWorld', inheritAttrs: false, props: { foo: String }, emits: { customClick: (args: string) => typeof args === 'string', }, setup(props: { foo?: string }) {}, }) ; {}} /> // 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('inject', () => { // with object inject defineComponent({ props: { a: String, }, inject: { foo: 'foo', bar: 'bar', }, created() { expectType(this.foo) expectType(this.bar) // @ts-expect-error this.foobar = 1 }, }) // with array inject defineComponent({ props: ['a', 'b'], inject: ['foo', 'bar'], created() { expectType(this.foo) expectType(this.bar) // @ts-expect-error this.foobar = 1 }, }) // with no props defineComponent({ inject: { foo: { from: 'pfoo', default: 'foo', }, bar: { from: 'pbar', default: 'bar', }, }, created() { expectType(this.foo) expectType(this.bar) // @ts-expect-error this.foobar = 1 }, }) // without inject defineComponent({ props: ['a', 'b'], created() { // @ts-expect-error this.foo = 1 // @ts-expect-error this.bar = 1 }, }) }) describe('componentOptions setup should be `SetupContext`', () => { expectType( {} 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 compA.a = true // @ts-expect-error compA.b = 'foo' // @ts-expect-error compA.c = 1 // @ts-expect-error compA.mA = 'foo' // @ts-expect-error 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 }) // #5948 describe('DefineComponent should infer correct types when assigning to Component', () => { let component: Component component = defineComponent({ setup(_, { attrs, slots }) { // @ts-expect-error should not be any expectType<[]>(attrs) // @ts-expect-error should not be any expectType<[]>(slots) }, }) expectType(component) }) // #5969 describe('should allow to assign props', () => { const Child = defineComponent({ props: { bar: String, }, }) const Parent = defineComponent({ props: { ...Child.props, foo: String, }, }) const child = new Child() expectType() }) // #6052 describe('prop starting with `on*` is broken', () => { defineComponent({ props: { onX: { type: Function as PropType<(a: 1) => void>, required: true, }, }, setup(props) { expectType<(a: 1) => void>(props.onX) props.onX(1) }, }) defineComponent({ props: { onX: { type: Function as PropType<(a: 1) => void>, required: true, }, }, emits: { test: (a: 1) => true, }, setup(props) { expectType<(a: 1) => void>(props.onX) expectType any)>(props.onTest) }, }) }) describe('function syntax w/ generics', () => { const Comp = defineComponent( // TODO: babel plugin to auto infer runtime props options from type // similar to defineProps<{...}>() (props: { msg: T; list: T[] }) => { // use Composition API here like in