import { type Component, type ComponentPublicInstance, type EmitsOptions, type FunctionalComponent, type PropType, type Ref, type SetupContext, type ShallowUnwrapRef, defineComponent, ref, toRefs, } from 'vue' import { type IsAny, describe, expectAssignable, expectType } from './utils' declare function extractComponentOptions< Props, RawBindings, Emits extends EmitsOptions | Record, Slots extends Record, >( obj: Component, ): { props: Props emits: Emits slots: Slots rawBindings: RawBindings setup: ShallowUnwrapRef } describe('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 object?: object } interface ExpectedRefs { a: Ref b: Ref e: Ref bb: Ref bbb: Ref cc: Ref dd: Ref<{ n: 1 }> ee: Ref<(() => string) | undefined> ff: Ref<((a: number, b: string) => { a: boolean }) | undefined> ccc: Ref ddd: Ref eee: Ref<() => { a: string }> fff: Ref<(a: number, b: string) => { a: boolean }> hhh: Ref ggg: Ref<'foo' | 'bar'> ffff: Ref<(a: number, b: string) => { a: boolean }> validated: Ref object: Ref zzz: any } describe('defineComponent', () => { const MyComponent = 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 + 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: true }), }, validated: { type: String, // validator requires explicit annotation validator: (val: unknown) => val !== '', }, object: Object as PropType, zzz: Object as PropType, }, setup(props) { const refs = toRefs(props) expectType(refs.a) expectType(refs.b) expectType(refs.e) expectType(refs.bb) expectType(refs.bbb) expectType(refs.cc) expectType(refs.dd) expectType(refs.ee) expectType(refs.ff) expectType(refs.ccc) expectType(refs.ddd) expectType(refs.eee) expectType(refs.fff) expectType(refs.hhh) expectType(refs.ggg) expectType(refs.ffff) expectType(refs.validated) expectType(refs.object) expectType>(true) return { setupA: 1, setupB: ref(1), setupC: { a: ref(2), }, setupD: undefined as Ref | undefined, setupProps: props, } }, }) const { props, rawBindings, setup } = extractComponentOptions(MyComponent) // props expectType(props.a) expectType(props.b) expectType(props.e) expectType(props.bb) expectType(props.bbb) 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) expectType(props.validated) expectType(props.object) // raw bindings expectType(rawBindings.setupA) expectType>(rawBindings.setupB) expectType>(rawBindings.setupC.a) expectType | undefined>(rawBindings.setupD) // raw bindings props expectType(rawBindings.setupProps.a) expectType(rawBindings.setupProps.b) expectType(rawBindings.setupProps.e) expectType(rawBindings.setupProps.bb) expectType(rawBindings.setupProps.bbb) expectType(rawBindings.setupProps.cc) expectType(rawBindings.setupProps.dd) expectType(rawBindings.setupProps.ee) expectType(rawBindings.setupProps.ff) expectType(rawBindings.setupProps.ccc) expectType(rawBindings.setupProps.ddd) expectType(rawBindings.setupProps.eee) expectType(rawBindings.setupProps.fff) expectType(rawBindings.setupProps.hhh) expectType(rawBindings.setupProps.ggg) expectType(rawBindings.setupProps.ffff) expectType(rawBindings.setupProps.validated) // setup expectType(setup.setupA) expectType(setup.setupB) expectType>(setup.setupC.a) expectType(setup.setupD) // raw bindings props expectType(setup.setupProps.a) expectType(setup.setupProps.b) expectType(setup.setupProps.e) expectType(setup.setupProps.bb) expectType(setup.setupProps.bbb) expectType(setup.setupProps.cc) expectType(setup.setupProps.dd) expectType(setup.setupProps.ee) expectType(setup.setupProps.ff) expectType(setup.setupProps.ccc) expectType(setup.setupProps.ddd) expectType(setup.setupProps.eee) expectType(setup.setupProps.fff) expectType(setup.setupProps.hhh) expectType(setup.setupProps.ggg) expectType(setup.setupProps.ffff) expectType(setup.setupProps.validated) // instance const instance = new MyComponent() expectType(instance.setupA) expectType(instance.setupD) // @ts-expect-error instance.notExist }) describe('options', () => { const MyComponent = { 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 + 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: true }), }, validated: { type: String, // validator requires explicit annotation validator: (val: unknown) => val !== '', }, object: Object as PropType, }, setup() { return { setupA: 1, } }, } as const const { props, rawBindings, setup } = extractComponentOptions(MyComponent) // props expectType(props.a) expectType(props.b) expectType(props.e) expectType(props.bb) expectType(props.bbb) 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) // todo fix expectType(props.validated) expectType(props.object) // rawBindings expectType(rawBindings.setupA) //setup expectType(setup.setupA) }) }) describe('array props', () => { describe('defineComponent', () => { const MyComponent = defineComponent({ props: ['a', 'b'], setup() { return { c: 1, } }, }) const { props, rawBindings, setup } = extractComponentOptions(MyComponent) // @ts-expect-error props should be readonly props.a = 1 expectType(props.a) expectType(props.b) expectType(rawBindings.c) expectType(setup.c) }) describe('options', () => { const MyComponent = { props: ['a', 'b'] as const, setup() { return { c: 1, } }, } const { props, rawBindings, setup } = extractComponentOptions(MyComponent) // @ts-expect-error props should be readonly props.a = 1 // TODO infer the correct keys // expectType(props.a) // expectType(props.b) expectType(rawBindings.c) expectType(setup.c) }) }) describe('no props', () => { describe('defineComponent', () => { const MyComponent = defineComponent({ setup() { return { setupA: 1, } }, }) const { rawBindings, setup } = extractComponentOptions(MyComponent) expectType(rawBindings.setupA) expectType(setup.setupA) // instance const instance = new MyComponent() expectType(instance.setupA) // @ts-expect-error instance.notExist }) describe('options', () => { const MyComponent = { setup() { return { setupA: 1, } }, } const { rawBindings, setup } = extractComponentOptions(MyComponent) expectType(rawBindings.setupA) expectType(setup.setupA) }) }) describe('functional', () => { // TODO `props.foo` is `number|undefined` // describe('defineComponent', () => { // const MyComponent = defineComponent((props: { foo: number }) => {}) // const { props } = extractComponentOptions(MyComponent) // expectType(props.foo) // }) describe('function', () => { const MyComponent = (props: { foo: number }) => props.foo const { props } = extractComponentOptions(MyComponent) expectType(props.foo) }) describe('typed', () => { type Props = { foo: number } type Emits = { change: [value: string]; inc: [value: number] } type Slots = { default: (scope: { foo: string }) => any } const MyComponent: FunctionalComponent = ( props, { emit, slots }, ) => { expectType(props) expectType<{ (event: 'change', value: string): void (event: 'inc', value: number): void }>(emit) expectType(slots) } const { props, emits, slots } = extractComponentOptions(MyComponent) expectType(props) expectType(emits) expectType(slots) }) }) declare type VueClass = { new (): ComponentPublicInstance } describe('class', () => { const MyComponent: VueClass<{ foo: number }> = {} as any const { props } = extractComponentOptions(MyComponent) expectType(props.foo) }) describe('SetupContext', () => { describe('can assign', () => { const wider: SetupContext<{ a: () => true; b: () => true }> = {} as any expectAssignable true }>>(wider) }) describe('short emits', () => { const { emit, }: SetupContext<{ a: [val: string] b: [val: number] }> = {} as any expectType<{ (event: 'a', val: string): void (event: 'b', val: number): void }>(emit) }) })