diff --git a/packages/reactivity/__tests__/collections/Set.spec.ts b/packages/reactivity/__tests__/collections/Set.spec.ts index cdccd347999..8164081df5e 100644 --- a/packages/reactivity/__tests__/collections/Set.spec.ts +++ b/packages/reactivity/__tests__/collections/Set.spec.ts @@ -412,13 +412,13 @@ describe('reactivity/collections', () => { `Reactive Set contains both the raw and reactive` ).toHaveBeenWarned() }) - + it('thisArg', () => { - const raw = new Set([ 'value' ]) + const raw = new Set(['value']) const proxy = reactive(raw) const thisArg = {} let count = 0 - proxy.forEach(function (this :{}, value, _, set) { + proxy.forEach(function(this: {}, value, _, set) { ++count expect(this).toBe(thisArg) expect(value).toBe('value') diff --git a/packages/reactivity/src/baseHandlers.ts b/packages/reactivity/src/baseHandlers.ts index 5bebe564016..3d5d5635acb 100644 --- a/packages/reactivity/src/baseHandlers.ts +++ b/packages/reactivity/src/baseHandlers.ts @@ -49,7 +49,7 @@ function createGetter(isReadonly = false, shallow = false) { } const res = Reflect.get(target, key, receiver) - if (isSymbol(key) && builtInSymbols.has(key) || key === '__proto__') { + if ((isSymbol(key) && builtInSymbols.has(key)) || key === '__proto__') { return res } diff --git a/packages/runtime-core/__tests__/apiOptions.spec.ts b/packages/runtime-core/__tests__/apiOptions.spec.ts index da99dd4a3ab..0107848ec68 100644 --- a/packages/runtime-core/__tests__/apiOptions.spec.ts +++ b/packages/runtime-core/__tests__/apiOptions.spec.ts @@ -443,6 +443,11 @@ describe('api: options', () => { } } const mixinB = { + props: { + bP: { + type: String + } + }, data() { return { b: 2 @@ -452,40 +457,65 @@ describe('api: options', () => { calls.push('mixinB created') expect(this.a).toBe(1) expect(this.b).toBe(2) + expect(this.bP).toBeUndefined() expect(this.c).toBe(3) + expect(this.cP1).toBeUndefined() }, mounted() { calls.push('mixinB mounted') } } - const Comp = { - mixins: [mixinA, mixinB], + const mixinC = defineComponent({ + props: ['cP1', 'cP2'], data() { return { c: 3 } }, - created(this: any) { + created() { + calls.push('mixinC created') + expect(this.c).toBe(3) + expect(this.cP1).toBeUndefined() + }, + mounted() { + calls.push('mixinC mounted') + } + }) + const Comp = defineComponent({ + props: { + aaa: String + }, + mixins: [defineComponent(mixinA), defineComponent(mixinB), mixinC], + data() { + return { + z: 4 + } + }, + created() { calls.push('comp created') expect(this.a).toBe(1) expect(this.b).toBe(2) + expect(this.bP).toBeUndefined() expect(this.c).toBe(3) + expect(this.cP2).toBeUndefined() + expect(this.z).toBe(4) }, mounted() { calls.push('comp mounted') }, - render(this: any) { + render() { return `${this.a}${this.b}${this.c}` } - } - + }) expect(renderToString(h(Comp))).toBe(`123`) expect(calls).toEqual([ 'mixinA created', 'mixinB created', + 'mixinC created', 'comp created', 'mixinA mounted', 'mixinB mounted', + 'mixinC mounted', 'comp mounted' ]) }) @@ -498,12 +528,17 @@ describe('api: options', () => { a: 1 } }, - mounted() { + methods: { + sayA() {} + }, + mounted(this: any) { + expect(this.a).toBe(1) + expect(this.b).toBe(2) calls.push('base') } } - const Comp = { - extends: Base, + const Comp = defineComponent({ + extends: defineComponent(Base), data() { return { b: 2 @@ -512,15 +547,66 @@ describe('api: options', () => { mounted() { calls.push('comp') }, - render(this: any) { + render() { return `${this.a}${this.b}` } - } + }) expect(renderToString(h(Comp))).toBe(`12`) expect(calls).toEqual(['base', 'comp']) }) + test('extends with mixins', () => { + const calls: string[] = [] + const Base = { + data() { + return { + a: 1 + } + }, + methods: { + sayA() {} + }, + mounted(this: any) { + expect(this.a).toBe(1) + expect(this.b).toBeTruthy() + expect(this.c).toBe(2) + calls.push('base') + } + } + const Base2 = { + data() { + return { + b: true + } + }, + mounted(this: any) { + expect(this.a).toBe(1) + expect(this.b).toBeTruthy() + expect(this.c).toBe(2) + calls.push('base2') + } + } + const Comp = defineComponent({ + extends: defineComponent(Base), + mixins: [defineComponent(Base2)], + data() { + return { + c: 2 + } + }, + mounted() { + calls.push('comp') + }, + render() { + return `${this.a}${this.b}${this.c}` + } + }) + + expect(renderToString(h(Comp))).toBe(`1true2`) + expect(calls).toEqual(['base', 'base2', 'comp']) + }) + test('accessing setup() state from options', async () => { const Comp = defineComponent({ setup() { diff --git a/packages/runtime-core/src/apiDefineComponent.ts b/packages/runtime-core/src/apiDefineComponent.ts index fb945bcb437..959b10bdf1b 100644 --- a/packages/runtime-core/src/apiDefineComponent.ts +++ b/packages/runtime-core/src/apiDefineComponent.ts @@ -4,10 +4,14 @@ import { ComponentOptionsWithoutProps, ComponentOptionsWithArrayProps, ComponentOptionsWithObjectProps, + ComponentOptionsMixin, RenderFunction } from './componentOptions' import { SetupContext, FunctionalComponent } from './component' -import { ComponentPublicInstance } from './componentProxy' +import { + CreateComponentPublicInstance, + ComponentPublicInstanceConstructor +} from './componentProxy' import { ExtractPropTypes, ComponentPropsOptions } from './componentProps' import { EmitsOptions } from './componentEmits' import { isFunction } from '@vue/shared' @@ -25,17 +29,21 @@ export function defineComponent( props: Readonly, ctx: SetupContext ) => RawBindings | RenderFunction -): { - new (): ComponentPublicInstance< +): ComponentPublicInstanceConstructor< + CreateComponentPublicInstance< Props, RawBindings, {}, {}, {}, + {}, + {}, + {}, // public props VNodeProps & Props > -} & FunctionalComponent +> & + FunctionalComponent // overload 2: object format with no props // (uses user defined props interface) @@ -46,21 +54,46 @@ export function defineComponent< D = {}, C extends ComputedOptions = {}, M extends MethodOptions = {}, + Mixin extends ComponentOptionsMixin = ComponentOptionsMixin, + Extends extends ComponentOptionsMixin = ComponentOptionsMixin, E extends EmitsOptions = Record, EE extends string = string >( - options: ComponentOptionsWithoutProps -): { - new (): ComponentPublicInstance< + options: ComponentOptionsWithoutProps< Props, RawBindings, D, C, M, + Mixin, + Extends, + E, + EE + > +): ComponentPublicInstanceConstructor< + CreateComponentPublicInstance< + Props, + RawBindings, + D, + C, + M, + Mixin, + Extends, E, VNodeProps & Props > -} & ComponentOptionsWithoutProps +> & + ComponentOptionsWithoutProps< + Props, + RawBindings, + D, + C, + M, + Mixin, + Extends, + E, + EE + > // overload 3: object format with array props declaration // props inferred as { [key in PropNames]?: any } @@ -71,6 +104,8 @@ export function defineComponent< D, C extends ComputedOptions = {}, M extends MethodOptions = {}, + Mixin extends ComponentOptionsMixin = ComponentOptionsMixin, + Extends extends ComponentOptionsMixin = ComponentOptionsMixin, E extends EmitsOptions = Record, EE extends string = string >( @@ -80,13 +115,36 @@ export function defineComponent< D, C, M, + Mixin, + Extends, + E, + EE + > +): ComponentPublicInstanceConstructor< + // array props technically doesn't place any contraints on props in TSX before, + // but now we can export array props in TSX + CreateComponentPublicInstance< + Readonly<{ [key in PropNames]?: any }>, + RawBindings, + D, + C, + M, + Mixin, + Extends, + E + > +> & + ComponentOptionsWithArrayProps< + PropNames, + RawBindings, + D, + C, + M, + Mixin, + Extends, E, EE > -): { - // array props technically doesn't place any constraints on props in TSX - new (): ComponentPublicInstance -} & ComponentOptionsWithArrayProps // overload 4: object format with object props declaration // see `ExtractPropTypes` in ./componentProps.ts @@ -98,6 +156,8 @@ export function defineComponent< D, C extends ComputedOptions = {}, M extends MethodOptions = {}, + Mixin extends ComponentOptionsMixin = ComponentOptionsMixin, + Extends extends ComponentOptionsMixin = ComponentOptionsMixin, E extends EmitsOptions = Record, EE extends string = string >( @@ -107,20 +167,35 @@ export function defineComponent< D, C, M, + Mixin, + Extends, E, EE > -): { - new (): ComponentPublicInstance< +): ComponentPublicInstanceConstructor< + CreateComponentPublicInstance< ExtractPropTypes, RawBindings, D, C, M, + Mixin, + Extends, E, VNodeProps & ExtractPropTypes > -} & ComponentOptionsWithObjectProps +> & + ComponentOptionsWithObjectProps< + PropsOptions, + RawBindings, + D, + C, + M, + Mixin, + Extends, + E, + EE + > // implementation, close to no-op export function defineComponent(options: unknown) { diff --git a/packages/runtime-core/src/component.ts b/packages/runtime-core/src/component.ts index 89d9916f4a1..522e64570d4 100644 --- a/packages/runtime-core/src/component.ts +++ b/packages/runtime-core/src/component.ts @@ -7,6 +7,7 @@ import { shallowReadonly } from '@vue/reactivity' import { + CreateComponentPublicInstance, ComponentPublicInstance, PublicInstanceProxyHandlers, RuntimeCompiledPublicInstanceProxyHandlers, @@ -96,7 +97,15 @@ export type Component = ComponentOptions | FunctionalComponent // The constructor type is an artificial type returned by defineComponent(). export type PublicAPIComponent = | Component - | { new (...args: any[]): ComponentPublicInstance } + | { + new (...args: any[]): CreateComponentPublicInstance< + any, + any, + any, + any, + any + > + } export { ComponentOptions } diff --git a/packages/runtime-core/src/componentEmits.ts b/packages/runtime-core/src/componentEmits.ts index 7b91bf34692..28a6af654e5 100644 --- a/packages/runtime-core/src/componentEmits.ts +++ b/packages/runtime-core/src/componentEmits.ts @@ -12,6 +12,7 @@ import { ComponentInternalInstance } from './component' import { callWithAsyncErrorHandling, ErrorCodes } from './errorHandling' import { warn } from './warning' import { normalizePropsOptions } from './componentProps' +import { UnionToIntersection } from './helpers/typeUtils' export type ObjectEmitsOptions = Record< string, @@ -19,12 +20,6 @@ export type ObjectEmitsOptions = Record< > export type EmitsOptions = ObjectEmitsOptions | string[] -type UnionToIntersection = (U extends any - ? (k: U) => void - : never) extends ((k: infer I) => void) - ? I - : never - export type EmitFn< Options = ObjectEmitsOptions, Event extends keyof Options = keyof Options diff --git a/packages/runtime-core/src/componentOptions.ts b/packages/runtime-core/src/componentOptions.ts index b04b37fc803..0cab6fc0cc8 100644 --- a/packages/runtime-core/src/componentOptions.ts +++ b/packages/runtime-core/src/componentOptions.ts @@ -48,7 +48,10 @@ import { } from './componentProps' import { EmitsOptions } from './componentEmits' import { Directive } from './directives' -import { ComponentPublicInstance } from './componentProxy' +import { + CreateComponentPublicInstance, + ComponentPublicInstance +} from './componentProxy' import { warn } from './warning' import { VNodeChild } from './vnode' @@ -78,10 +81,12 @@ export interface ComponentOptionsBase< D, C extends ComputedOptions, M extends MethodOptions, + Mixin extends ComponentOptionsMixin, + Extends extends ComponentOptionsMixin, E extends EmitsOptions, EE extends string = string > - extends LegacyOptions, + extends LegacyOptions, SFCInternalOptions, ComponentCustomOptions { setup?: ( @@ -148,12 +153,24 @@ export type ComponentOptionsWithoutProps< D = {}, C extends ComputedOptions = {}, M extends MethodOptions = {}, + Mixin extends ComponentOptionsMixin = ComponentOptionsMixin, + Extends extends ComponentOptionsMixin = ComponentOptionsMixin, E extends EmitsOptions = EmitsOptions, EE extends string = string -> = ComponentOptionsBase & { +> = ComponentOptionsBase & { props?: undefined } & ThisType< - ComponentPublicInstance<{}, RawBindings, D, C, M, E, Readonly> + CreateComponentPublicInstance< + {}, + RawBindings, + D, + C, + M, + Mixin, + Extends, + E, + Readonly + > > export type ComponentOptionsWithArrayProps< @@ -162,12 +179,25 @@ export type ComponentOptionsWithArrayProps< D = {}, C extends ComputedOptions = {}, M extends MethodOptions = {}, + Mixin extends ComponentOptionsMixin = ComponentOptionsMixin, + Extends extends ComponentOptionsMixin = ComponentOptionsMixin, E extends EmitsOptions = EmitsOptions, EE extends string = string, Props = Readonly<{ [key in PropNames]?: any }> -> = ComponentOptionsBase & { +> = ComponentOptionsBase & { props: PropNames[] -} & ThisType> +} & ThisType< + CreateComponentPublicInstance< + Props, + RawBindings, + D, + C, + M, + Mixin, + Extends, + E + > + > export type ComponentOptionsWithObjectProps< PropsOptions = ComponentObjectPropsOptions, @@ -175,18 +205,43 @@ export type ComponentOptionsWithObjectProps< D = {}, C extends ComputedOptions = {}, M extends MethodOptions = {}, + Mixin extends ComponentOptionsMixin = ComponentOptionsMixin, + Extends extends ComponentOptionsMixin = ComponentOptionsMixin, E extends EmitsOptions = EmitsOptions, EE extends string = string, Props = Readonly> -> = ComponentOptionsBase & { +> = ComponentOptionsBase & { props: PropsOptions -} & ThisType> +} & ThisType< + CreateComponentPublicInstance< + Props, + RawBindings, + D, + C, + M, + Mixin, + Extends, + E + > + > export type ComponentOptions = | ComponentOptionsWithoutProps | ComponentOptionsWithObjectProps | ComponentOptionsWithArrayProps +export type ComponentOptionsMixin = ComponentOptionsBase< + any, + any, + any, + any, + any, + any, + any, + any, + any +> + export type ComputedOptions = Record< string, ComputedGetter | WritableComputedOptions @@ -222,7 +277,9 @@ interface LegacyOptions< Props, D, C extends ComputedOptions, - M extends MethodOptions + M extends MethodOptions, + Mixin extends ComponentOptionsMixin, + Extends extends ComponentOptionsMixin > { // allow any custom options [key: string]: any @@ -232,8 +289,8 @@ interface LegacyOptions< // since that leads to some sort of circular inference and breaks ThisType // for the entire component. data?: ( - this: ComponentPublicInstance, - vm: ComponentPublicInstance + this: CreateComponentPublicInstance, + vm: CreateComponentPublicInstance ) => D computed?: C methods?: M @@ -242,8 +299,8 @@ interface LegacyOptions< inject?: ComponentInjectOptions // composition - mixins?: ComponentOptions[] - extends?: ComponentOptions + mixins?: Mixin[] + extends?: Extends // lifecycle beforeCreate?(): void @@ -261,6 +318,22 @@ interface LegacyOptions< errorCaptured?: ErrorCapturedHook } +export type OptionTypesKeys = 'P' | 'B' | 'D' | 'C' | 'M' + +export type OptionTypesType< + P = {}, + B = {}, + D = {}, + C extends ComputedOptions = {}, + M extends MethodOptions = {} +> = { + P: P + B: B + D: D + C: C + M: M +} + const enum OptionTypes { PROPS = 'Props', DATA = 'Data', diff --git a/packages/runtime-core/src/componentProps.ts b/packages/runtime-core/src/componentProps.ts index c300e431b51..472d4dbaf0a 100644 --- a/packages/runtime-core/src/componentProps.ts +++ b/packages/runtime-core/src/componentProps.ts @@ -67,10 +67,12 @@ type OptionalKeys = Exclude< type InferPropType = T extends null ? any // null & true would fail to infer : T extends { type: null | true } - ? any // somehow `ObjectConstructor` when inferred from { (): T } becomes `any` + ? any // As TS issue https://github.com/Microsoft/TypeScript/issues/14829 // somehow `ObjectConstructor` when inferred from { (): T } becomes `any` // `BooleanConstructor` when inferred from PropConstructor(with PropMethod) becomes `Boolean` : T extends ObjectConstructor | { type: ObjectConstructor } ? { [key: string]: any } - : T extends Prop ? V : T + : T extends BooleanConstructor | { type: BooleanConstructor } + ? boolean + : T extends Prop ? V : T export type ExtractPropTypes< O, diff --git a/packages/runtime-core/src/componentProxy.ts b/packages/runtime-core/src/componentProxy.ts index ff895a61969..01c31897546 100644 --- a/packages/runtime-core/src/componentProxy.ts +++ b/packages/runtime-core/src/componentProxy.ts @@ -14,6 +14,9 @@ import { ComponentOptionsBase, ComputedOptions, MethodOptions, + ComponentOptionsMixin, + OptionTypesType, + OptionTypesKeys, resolveMergedOptions } from './componentOptions' import { normalizePropsOptions } from './componentProps' @@ -24,6 +27,7 @@ import { markAttrsAccessed } from './componentRenderUtils' import { warn } from './warning' +import { UnionToIntersection } from './helpers/typeUtils' /** * Custom properties added to component instances in any way and can be accessed through `this` @@ -52,6 +56,69 @@ import { warn } from './warning' */ export interface ComponentCustomProperties {} +type IsDefaultMixinComponent = T extends ComponentOptionsMixin + ? ComponentOptionsMixin extends T ? true : false + : false + +type MixinToOptionTypes = T extends ComponentOptionsBase< + infer P, + infer B, + infer D, + infer C, + infer M, + infer Mixin, + infer Extends, + any +> + ? OptionTypesType

& + IntersectionMixin & + IntersectionMixin + : never + +// ExtractMixin(map type) is used to resolve circularly references +type ExtractMixin = { + Mixin: MixinToOptionTypes +}[T extends ComponentOptionsMixin ? 'Mixin' : never] + +type IntersectionMixin = IsDefaultMixinComponent extends true + ? OptionTypesType<{}, {}, {}, {}, {}> + : UnionToIntersection> + +type UnwrapMixinsType< + T, + Type extends OptionTypesKeys +> = T extends OptionTypesType ? T[Type] : never + +type EnsureNonVoid = T extends void ? {} : T + +export type CreateComponentPublicInstance< + P = {}, + B = {}, + D = {}, + C extends ComputedOptions = {}, + M extends MethodOptions = {}, + Mixin extends ComponentOptionsMixin = ComponentOptionsMixin, + Extends extends ComponentOptionsMixin = ComponentOptionsMixin, + E extends EmitsOptions = {}, + PublicProps = P, + PublicMixin = IntersectionMixin & IntersectionMixin, + PublicP = UnwrapMixinsType & EnsureNonVoid

, + PublicB = UnwrapMixinsType & EnsureNonVoid, + PublicD = UnwrapMixinsType & EnsureNonVoid, + PublicC extends ComputedOptions = UnwrapMixinsType & + EnsureNonVoid, + PublicM extends MethodOptions = UnwrapMixinsType & + EnsureNonVoid +> = ComponentPublicInstance< + PublicP, + PublicB, + PublicD, + PublicC, + PublicM, + E, + PublicProps, + ComponentOptionsBase +> // public properties exposed on the proxy, which is used as the render context // in templates (as `this` in the render option) export type ComponentPublicInstance< @@ -61,11 +128,12 @@ export type ComponentPublicInstance< C extends ComputedOptions = {}, M extends MethodOptions = {}, E extends EmitsOptions = {}, - PublicProps = P + PublicProps = P, + Options = ComponentOptionsBase > = { $: ComponentInternalInstance $data: D - $props: PublicProps + $props: P & PublicProps $attrs: Data $refs: Data $slots: Slots @@ -73,7 +141,7 @@ export type ComponentPublicInstance< $parent: ComponentPublicInstance | null $emit: EmitFn $el: any - $options: ComponentOptionsBase + $options: Options $forceUpdate: ReactiveEffect $nextTick: typeof nextTick $watch: typeof instanceWatch @@ -84,6 +152,12 @@ export type ComponentPublicInstance< M & ComponentCustomProperties +export type ComponentPublicInstanceConstructor< + T extends ComponentPublicInstance +> = { + new (): T +} + const publicPropertiesMap: Record< string, (i: ComponentInternalInstance) => any diff --git a/packages/runtime-core/src/helpers/typeUtils.ts b/packages/runtime-core/src/helpers/typeUtils.ts new file mode 100644 index 00000000000..64bf5129385 --- /dev/null +++ b/packages/runtime-core/src/helpers/typeUtils.ts @@ -0,0 +1,5 @@ +export type UnionToIntersection = (U extends any + ? (k: U) => void + : never) extends ((k: infer I) => void) + ? I + : never diff --git a/test-dts/defineComponent.test-d.tsx b/test-dts/defineComponent.test-d.tsx index 542634e0f4a..bc1e5f98e54 100644 --- a/test-dts/defineComponent.test-d.tsx +++ b/test-dts/defineComponent.test-d.tsx @@ -23,6 +23,7 @@ describe('with object props', () => { ddd: string[] eee: () => { a: string } fff: (a: number, b: string) => { a: boolean } + hhh: boolean } type GT = string & { __brand: unknown } @@ -67,6 +68,10 @@ describe('with object props', () => { fff: { type: Function as PropType<(a: number, b: string) => { a: boolean }>, required: true + }, + hhh: { + type: Boolean, + required: true } }, setup(props) { @@ -83,6 +88,7 @@ describe('with object props', () => { expectType(props.ddd) expectType(props.eee) expectType(props.fff) + expectType(props.hhh) // @ts-expect-error props should be readonly expectError((props.a = 1)) @@ -112,6 +118,7 @@ describe('with object props', () => { expectType(props.ddd) expectType(props.eee) expectType(props.fff) + expectType(props.hhh) // @ts-expect-error props should be readonly expectError((props.a = 1)) @@ -129,6 +136,7 @@ describe('with object props', () => { expectType(this.ddd) expectType(this.eee) expectType(this.fff) + expectType(this.hhh) // @ts-expect-error props on `this` should be readonly expectError((this.a = 1)) @@ -159,6 +167,7 @@ describe('with object props', () => { ddd={['ddd']} eee={() => ({ a: 'eee' })} fff={(a, b) => ({ a: a > +b })} + hhh={false} // should allow extraneous as attrs class="bar" // should allow key @@ -210,7 +219,7 @@ describe('with object props', () => { // }) describe('type inference w/ array props declaration', () => { - defineComponent({ + const MyComponent = defineComponent({ props: ['a', 'b'], setup(props) { // @ts-expect-error props should be readonly @@ -231,6 +240,9 @@ describe('type inference w/ array props declaration', () => { expectType(this.c) } }) + expectType() + // @ts-expect-error + expectError() }) describe('type inference w/ options API', () => { @@ -296,6 +308,275 @@ describe('type inference w/ options API', () => { }) }) +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')