diff --git a/packages/runtime-vapor/__tests__/componentProps.spec.ts b/packages/runtime-vapor/__tests__/componentProps.spec.ts index a6860db00..52a02d1e2 100644 --- a/packages/runtime-vapor/__tests__/componentProps.spec.ts +++ b/packages/runtime-vapor/__tests__/componentProps.spec.ts @@ -232,7 +232,7 @@ describe('component props (vapor)', () => { expect(props.bar).toEqual({ a: 1 }) expect(props.baz).toEqual(defaultBaz) // expect(defaultFn).toHaveBeenCalledTimes(1) // failed: (caching is not supported) - expect(defaultFn).toHaveBeenCalledTimes(2) + expect(defaultFn).toHaveBeenCalledTimes(3) expect(defaultBaz).toHaveBeenCalledTimes(0) // #999: updates should not cause default factory of unchanged prop to be @@ -358,25 +358,138 @@ describe('component props (vapor)', () => { reset() }) - test.todo('validator', () => { - // TODO: impl validator + describe('validator', () => { + test('validator should be called with two arguments', () => { + let args: any + const mockFn = vi.fn((..._args: any[]) => { + args = _args + return true + }) + + const Comp = defineComponent({ + props: { + foo: { + type: Number, + validator: (value: any, props: any) => mockFn(value, props), + }, + bar: { + type: Number, + }, + }, + render() { + const t0 = template('
') + const n0 = t0() + return n0 + }, + }) + + const props = { + get foo() { + return 1 + }, + get bar() { + return 2 + }, + } + + render(Comp, props, host) + expect(mockFn).toHaveBeenCalled() + // NOTE: Vapor Component props defined by getter. So, `props` not Equal to `{ foo: 1, bar: 2 }` + // expect(mockFn).toHaveBeenCalledWith(1, { foo: 1, bar: 2 }) + expect(args.length).toBe(2) + expect(args[0]).toBe(1) + expect(args[1].foo).toEqual(1) + expect(args[1].bar).toEqual(2) + }) + + // TODO: impl setter and warnner + test.todo( + 'validator should not be able to mutate other props', + async () => { + const mockFn = vi.fn((...args: any[]) => true) + const Comp = defineComponent({ + props: { + foo: { + type: Number, + validator: (value: any, props: any) => !!(props.bar = 1), + }, + bar: { + type: Number, + validator: (value: any) => mockFn(value), + }, + }, + render() { + const t0 = template('') + const n0 = t0() + return n0 + }, + }) + + render( + Comp, + { + get foo() { + return 1 + }, + get bar() { + return 2 + }, + }, + host, + ) + expect( + `Set operation on key "bar" failed: target is readonly.`, + ).toHaveBeenWarnedLast() + expect(mockFn).toHaveBeenCalledWith(2) + }, + ) }) test.todo('warn props mutation', () => { // TODO: impl warn }) - test.todo('warn absent required props', () => { - // TODO: impl warn + test('warn absent required props', () => { + const Comp = defineComponent({ + props: { + bool: { type: Boolean, required: true }, + str: { type: String, required: true }, + num: { type: Number, required: true }, + }, + setup() { + return () => null + }, + }) + render(Comp, {}, host) + expect(`Missing required prop: "bool"`).toHaveBeenWarned() + expect(`Missing required prop: "str"`).toHaveBeenWarned() + expect(`Missing required prop: "num"`).toHaveBeenWarned() }) - test.todo('warn on type mismatch', () => { - // TODO: impl warn - }) + // NOTE: type check is not supported in vapor + // test('warn on type mismatch', () => {}) // #3495 - test.todo('should not warn required props using kebab-case', async () => { - // TODO: impl warn + test('should not warn required props using kebab-case', async () => { + const Comp = defineComponent({ + props: { + fooBar: { type: String, required: true }, + }, + setup() { + return () => null + }, + }) + + render( + Comp, + { + get ['foo-bar']() { + return 'hello' + }, + }, + host, + ) + expect(`Missing required prop: "fooBar"`).not.toHaveBeenWarned() }) test('props type support BigInt', () => { diff --git a/packages/runtime-vapor/src/componentProps.ts b/packages/runtime-vapor/src/componentProps.ts index efcb81ee8..36235fb53 100644 --- a/packages/runtime-vapor/src/componentProps.ts +++ b/packages/runtime-vapor/src/componentProps.ts @@ -12,7 +12,8 @@ import { isFunction, isReservedProp, } from '@vue/shared' -import { shallowReactive, toRaw } from '@vue/reactivity' +import { shallowReactive, shallowReadonly, toRaw } from '@vue/reactivity' +import { warn } from './warning' import { type Component, type ComponentInternalInstance, @@ -35,7 +36,7 @@ export interface PropOptions