mirror of https://github.com/vuejs/core.git
test(runtime-vapor): component props (#99)
Co-authored-by: 三咲智子 Kevin Deng <sxzz@sxzz.moe>
This commit is contained in:
parent
8dec243dc1
commit
ea5f7ec076
|
@ -711,7 +711,7 @@ describe('component props', () => {
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
// #691ef
|
// #6915
|
||||||
test('should not mutate original props long-form definition object', () => {
|
test('should not mutate original props long-form definition object', () => {
|
||||||
const props = {
|
const props = {
|
||||||
msg: {
|
msg: {
|
||||||
|
|
|
@ -0,0 +1,487 @@
|
||||||
|
// NOTE: This test is implemented based on the case of `runtime-core/__test__/componentProps.spec.ts`.
|
||||||
|
|
||||||
|
// NOTE: not supported
|
||||||
|
// mixins
|
||||||
|
// caching
|
||||||
|
|
||||||
|
import { type FunctionalComponent, setCurrentInstance } from '../src/component'
|
||||||
|
import {
|
||||||
|
children,
|
||||||
|
defineComponent,
|
||||||
|
getCurrentInstance,
|
||||||
|
nextTick,
|
||||||
|
ref,
|
||||||
|
render,
|
||||||
|
setText,
|
||||||
|
template,
|
||||||
|
watchEffect,
|
||||||
|
} from '../src'
|
||||||
|
|
||||||
|
let host: HTMLElement
|
||||||
|
const initHost = () => {
|
||||||
|
host = document.createElement('div')
|
||||||
|
host.setAttribute('id', 'host')
|
||||||
|
document.body.appendChild(host)
|
||||||
|
}
|
||||||
|
beforeEach(() => initHost())
|
||||||
|
afterEach(() => host.remove())
|
||||||
|
|
||||||
|
describe('component props (vapor)', () => {
|
||||||
|
test('stateful', () => {
|
||||||
|
let props: any
|
||||||
|
// TODO: attrs
|
||||||
|
|
||||||
|
const Comp = defineComponent({
|
||||||
|
props: ['fooBar', 'barBaz'],
|
||||||
|
render() {
|
||||||
|
const instance = getCurrentInstance()!
|
||||||
|
props = instance.props
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
render(
|
||||||
|
Comp,
|
||||||
|
{
|
||||||
|
get fooBar() {
|
||||||
|
return 1
|
||||||
|
},
|
||||||
|
},
|
||||||
|
host,
|
||||||
|
)
|
||||||
|
expect(props.fooBar).toEqual(1)
|
||||||
|
|
||||||
|
// test passing kebab-case and resolving to camelCase
|
||||||
|
render(
|
||||||
|
Comp,
|
||||||
|
{
|
||||||
|
get ['foo-bar']() {
|
||||||
|
return 2
|
||||||
|
},
|
||||||
|
},
|
||||||
|
host,
|
||||||
|
)
|
||||||
|
expect(props.fooBar).toEqual(2)
|
||||||
|
|
||||||
|
// test updating kebab-case should not delete it (#955)
|
||||||
|
render(
|
||||||
|
Comp,
|
||||||
|
{
|
||||||
|
get ['foo-bar']() {
|
||||||
|
return 3
|
||||||
|
},
|
||||||
|
get barBaz() {
|
||||||
|
return 5
|
||||||
|
},
|
||||||
|
},
|
||||||
|
host,
|
||||||
|
)
|
||||||
|
expect(props.fooBar).toEqual(3)
|
||||||
|
expect(props.barBaz).toEqual(5)
|
||||||
|
|
||||||
|
render(Comp, {}, host)
|
||||||
|
expect(props.fooBar).toBeUndefined()
|
||||||
|
expect(props.barBaz).toBeUndefined()
|
||||||
|
// expect(props.qux).toEqual(5) // TODO: attrs
|
||||||
|
})
|
||||||
|
|
||||||
|
test.todo('stateful with setup', () => {
|
||||||
|
// TODO:
|
||||||
|
})
|
||||||
|
|
||||||
|
test('functional with declaration', () => {
|
||||||
|
let props: any
|
||||||
|
// TODO: attrs
|
||||||
|
|
||||||
|
const Comp: FunctionalComponent = defineComponent((_props: any) => {
|
||||||
|
const instance = getCurrentInstance()!
|
||||||
|
props = instance.props
|
||||||
|
return {}
|
||||||
|
})
|
||||||
|
Comp.props = ['foo']
|
||||||
|
Comp.render = (() => {}) as any
|
||||||
|
|
||||||
|
render(
|
||||||
|
Comp,
|
||||||
|
{
|
||||||
|
get foo() {
|
||||||
|
return 1
|
||||||
|
},
|
||||||
|
},
|
||||||
|
host,
|
||||||
|
)
|
||||||
|
expect(props.foo).toEqual(1)
|
||||||
|
|
||||||
|
render(
|
||||||
|
Comp,
|
||||||
|
{
|
||||||
|
get foo() {
|
||||||
|
return 2
|
||||||
|
},
|
||||||
|
},
|
||||||
|
host,
|
||||||
|
)
|
||||||
|
expect(props.foo).toEqual(2)
|
||||||
|
|
||||||
|
render(Comp, {}, host)
|
||||||
|
expect(props.foo).toBeUndefined()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('functional without declaration', () => {
|
||||||
|
let props: any
|
||||||
|
// TODO: attrs
|
||||||
|
|
||||||
|
const Comp: FunctionalComponent = defineComponent((_props: any) => {
|
||||||
|
const instance = getCurrentInstance()!
|
||||||
|
props = instance.props
|
||||||
|
return {}
|
||||||
|
})
|
||||||
|
Comp.props = undefined as any
|
||||||
|
Comp.render = (() => {}) as any
|
||||||
|
|
||||||
|
render(
|
||||||
|
Comp,
|
||||||
|
{
|
||||||
|
get foo() {
|
||||||
|
return 1
|
||||||
|
},
|
||||||
|
},
|
||||||
|
host,
|
||||||
|
)
|
||||||
|
expect(props.foo).toBeUndefined()
|
||||||
|
|
||||||
|
render(
|
||||||
|
Comp,
|
||||||
|
{
|
||||||
|
get foo() {
|
||||||
|
return 2
|
||||||
|
},
|
||||||
|
},
|
||||||
|
host,
|
||||||
|
)
|
||||||
|
expect(props.foo).toBeUndefined()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('boolean casting', () => {
|
||||||
|
let props: any
|
||||||
|
const Comp = defineComponent({
|
||||||
|
props: {
|
||||||
|
foo: Boolean,
|
||||||
|
bar: Boolean,
|
||||||
|
baz: Boolean,
|
||||||
|
qux: Boolean,
|
||||||
|
},
|
||||||
|
render() {
|
||||||
|
const instance = getCurrentInstance()!
|
||||||
|
props = instance.props
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
render(
|
||||||
|
Comp,
|
||||||
|
{
|
||||||
|
// absent should cast to false
|
||||||
|
bar: '', // empty string should cast to true
|
||||||
|
baz: 'baz', // same string should cast to true
|
||||||
|
qux: 'ok', // other values should be left in-tact (but raise warning)
|
||||||
|
},
|
||||||
|
host,
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(props.foo).toBe(false)
|
||||||
|
expect(props.bar).toBe(true)
|
||||||
|
expect(props.baz).toBe(true)
|
||||||
|
expect(props.qux).toBe('ok')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('default value', () => {
|
||||||
|
let props: any
|
||||||
|
const defaultFn = vi.fn(() => ({ a: 1 }))
|
||||||
|
const defaultBaz = vi.fn(() => ({ b: 1 }))
|
||||||
|
|
||||||
|
const Comp = defineComponent({
|
||||||
|
props: {
|
||||||
|
foo: {
|
||||||
|
default: 1,
|
||||||
|
},
|
||||||
|
bar: {
|
||||||
|
default: defaultFn,
|
||||||
|
},
|
||||||
|
baz: {
|
||||||
|
type: Function,
|
||||||
|
default: defaultBaz,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
render() {
|
||||||
|
const instance = getCurrentInstance()!
|
||||||
|
props = instance.props
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
render(
|
||||||
|
Comp,
|
||||||
|
{
|
||||||
|
get foo() {
|
||||||
|
return 2
|
||||||
|
},
|
||||||
|
},
|
||||||
|
host,
|
||||||
|
)
|
||||||
|
expect(props.foo).toBe(2)
|
||||||
|
// const prevBar = props.bar
|
||||||
|
props.bar
|
||||||
|
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(defaultBaz).toHaveBeenCalledTimes(0)
|
||||||
|
|
||||||
|
// #999: updates should not cause default factory of unchanged prop to be
|
||||||
|
// called again
|
||||||
|
render(
|
||||||
|
Comp,
|
||||||
|
{
|
||||||
|
get foo() {
|
||||||
|
return 3
|
||||||
|
},
|
||||||
|
},
|
||||||
|
host,
|
||||||
|
)
|
||||||
|
expect(props.foo).toBe(3)
|
||||||
|
expect(props.bar).toEqual({ a: 1 })
|
||||||
|
// expect(props.bar).toBe(prevBar) // failed: (caching is not supported)
|
||||||
|
// expect(defaultFn).toHaveBeenCalledTimes(1) // failed: caching is not supported (called 3 times)
|
||||||
|
|
||||||
|
render(
|
||||||
|
Comp,
|
||||||
|
{
|
||||||
|
get bar() {
|
||||||
|
return { b: 2 }
|
||||||
|
},
|
||||||
|
},
|
||||||
|
host,
|
||||||
|
)
|
||||||
|
expect(props.foo).toBe(1)
|
||||||
|
expect(props.bar).toEqual({ b: 2 })
|
||||||
|
// expect(defaultFn).toHaveBeenCalledTimes(1) // failed: caching is not supported (called 3 times)
|
||||||
|
|
||||||
|
render(
|
||||||
|
Comp,
|
||||||
|
{
|
||||||
|
get foo() {
|
||||||
|
return 3
|
||||||
|
},
|
||||||
|
get bar() {
|
||||||
|
return { b: 3 }
|
||||||
|
},
|
||||||
|
},
|
||||||
|
host,
|
||||||
|
)
|
||||||
|
expect(props.foo).toBe(3)
|
||||||
|
expect(props.bar).toEqual({ b: 3 })
|
||||||
|
// expect(defaultFn).toHaveBeenCalledTimes(1) // failed: caching is not supported (called 3 times)
|
||||||
|
|
||||||
|
render(
|
||||||
|
Comp,
|
||||||
|
{
|
||||||
|
get bar() {
|
||||||
|
return { b: 4 }
|
||||||
|
},
|
||||||
|
},
|
||||||
|
host,
|
||||||
|
)
|
||||||
|
expect(props.foo).toBe(1)
|
||||||
|
expect(props.bar).toEqual({ b: 4 })
|
||||||
|
// expect(defaultFn).toHaveBeenCalledTimes(1) // failed: caching is not supported (called 3 times)
|
||||||
|
})
|
||||||
|
|
||||||
|
test.todo('using inject in default value factory', () => {
|
||||||
|
// TODO: impl inject
|
||||||
|
})
|
||||||
|
|
||||||
|
// NOTE: maybe it's unnecessary
|
||||||
|
// https://github.com/vuejs/core-vapor/pull/99#discussion_r1472647377
|
||||||
|
test('optimized props updates', async () => {
|
||||||
|
const Child = defineComponent({
|
||||||
|
props: ['foo'],
|
||||||
|
render() {
|
||||||
|
const instance = getCurrentInstance()!
|
||||||
|
const t0 = template('<div><!></div>')
|
||||||
|
const n0 = t0()
|
||||||
|
const {
|
||||||
|
0: [n1],
|
||||||
|
} = children(n0)
|
||||||
|
watchEffect(() => {
|
||||||
|
setText(n1, instance.props.foo)
|
||||||
|
})
|
||||||
|
return n0
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const foo = ref(1)
|
||||||
|
const id = ref('a')
|
||||||
|
const Comp = defineComponent({
|
||||||
|
setup() {
|
||||||
|
return { foo, id }
|
||||||
|
},
|
||||||
|
render(_ctx: Record<string, any>) {
|
||||||
|
const t0 = template('')
|
||||||
|
const n0 = t0()
|
||||||
|
render(
|
||||||
|
Child,
|
||||||
|
{
|
||||||
|
get foo() {
|
||||||
|
return _ctx.foo
|
||||||
|
},
|
||||||
|
get id() {
|
||||||
|
return _ctx.id
|
||||||
|
},
|
||||||
|
},
|
||||||
|
n0 as any, // TODO: type
|
||||||
|
)
|
||||||
|
return n0
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const instace = render(Comp, {}, host)
|
||||||
|
const reset = setCurrentInstance(instace)
|
||||||
|
// expect(host.innerHTML).toBe('<div id="a">1</div>') // TODO: Fallthrough Attributes
|
||||||
|
expect(host.innerHTML).toBe('<div>1</div>')
|
||||||
|
|
||||||
|
foo.value++
|
||||||
|
await nextTick()
|
||||||
|
// expect(host.innerHTML).toBe('<div id="a">2</div>') // TODO: Fallthrough Attributes
|
||||||
|
expect(host.innerHTML).toBe('<div>2</div>')
|
||||||
|
|
||||||
|
// id.value = 'b'
|
||||||
|
// await nextTick()
|
||||||
|
// expect(host.innerHTML).toBe('<div id="b">2</div>') // TODO: Fallthrough Attributes
|
||||||
|
reset()
|
||||||
|
})
|
||||||
|
|
||||||
|
test.todo('validator', () => {
|
||||||
|
// TODO: impl validator
|
||||||
|
})
|
||||||
|
|
||||||
|
test.todo('warn props mutation', () => {
|
||||||
|
// TODO: impl warn
|
||||||
|
})
|
||||||
|
|
||||||
|
test.todo('warn absent required props', () => {
|
||||||
|
// TODO: impl warn
|
||||||
|
})
|
||||||
|
|
||||||
|
test.todo('warn on type mismatch', () => {
|
||||||
|
// TODO: impl warn
|
||||||
|
})
|
||||||
|
|
||||||
|
// #3495
|
||||||
|
test.todo('should not warn required props using kebab-case', async () => {
|
||||||
|
// TODO: impl warn
|
||||||
|
})
|
||||||
|
|
||||||
|
test('props type support BigInt', () => {
|
||||||
|
const Comp = defineComponent({
|
||||||
|
props: {
|
||||||
|
foo: BigInt,
|
||||||
|
},
|
||||||
|
render() {
|
||||||
|
const instance = getCurrentInstance()!
|
||||||
|
const t0 = template('<div></div>')
|
||||||
|
const n0 = t0()
|
||||||
|
const {
|
||||||
|
0: [n1],
|
||||||
|
} = children(n0)
|
||||||
|
watchEffect(() => {
|
||||||
|
setText(n1, instance.props.foo)
|
||||||
|
})
|
||||||
|
return n0
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
render(
|
||||||
|
Comp,
|
||||||
|
{
|
||||||
|
get foo() {
|
||||||
|
return (
|
||||||
|
BigInt(BigInt(100000111)) + BigInt(2000000000) * BigInt(30000000)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'#host',
|
||||||
|
)
|
||||||
|
expect(host.innerHTML).toBe('<div>60000000100000111</div>')
|
||||||
|
})
|
||||||
|
|
||||||
|
// #3288
|
||||||
|
test.todo(
|
||||||
|
'declared prop key should be present even if not passed',
|
||||||
|
async () => {
|
||||||
|
// let initialKeys: string[] = []
|
||||||
|
// const changeSpy = vi.fn()
|
||||||
|
// const passFoo = ref(false)
|
||||||
|
// const Comp = {
|
||||||
|
// props: ['foo'],
|
||||||
|
// setup() {
|
||||||
|
// const instance = getCurrentInstance()!
|
||||||
|
// initialKeys = Object.keys(instance.props)
|
||||||
|
// watchEffect(changeSpy)
|
||||||
|
// return {}
|
||||||
|
// },
|
||||||
|
// render() {
|
||||||
|
// return {}
|
||||||
|
// },
|
||||||
|
// }
|
||||||
|
// const Parent = createIf(
|
||||||
|
// () => passFoo.value,
|
||||||
|
// () => {
|
||||||
|
// return render(Comp , { foo: 1 }, host) // TODO: createComponent fn
|
||||||
|
// },
|
||||||
|
// )
|
||||||
|
// // expect(changeSpy).toHaveBeenCalledTimes(1)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
// #3371
|
||||||
|
test.todo(`avoid double-setting props when casting`, async () => {
|
||||||
|
// TODO: proide, slots
|
||||||
|
})
|
||||||
|
|
||||||
|
test('support null in required + multiple-type declarations', () => {
|
||||||
|
const Comp = defineComponent({
|
||||||
|
props: {
|
||||||
|
foo: { type: [Function, null], required: true },
|
||||||
|
},
|
||||||
|
render() {},
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(() => {
|
||||||
|
render(Comp, { foo: () => {} }, host)
|
||||||
|
}).not.toThrow()
|
||||||
|
|
||||||
|
expect(() => {
|
||||||
|
render(Comp, { foo: null }, host)
|
||||||
|
}).not.toThrow()
|
||||||
|
})
|
||||||
|
|
||||||
|
// #5016
|
||||||
|
test.todo('handling attr with undefined value', () => {
|
||||||
|
// TODO: attrs
|
||||||
|
})
|
||||||
|
|
||||||
|
// #6915
|
||||||
|
test('should not mutate original props long-form definition object', () => {
|
||||||
|
const props = {
|
||||||
|
msg: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
const Comp = defineComponent({
|
||||||
|
props,
|
||||||
|
render() {},
|
||||||
|
})
|
||||||
|
|
||||||
|
render(Comp, { msg: 'test' }, host)
|
||||||
|
|
||||||
|
expect(Object.keys(props.msg).length).toBe(1)
|
||||||
|
})
|
||||||
|
})
|
|
@ -13,7 +13,11 @@ import {
|
||||||
isReservedProp,
|
isReservedProp,
|
||||||
} from '@vue/shared'
|
} from '@vue/shared'
|
||||||
import { shallowReactive, toRaw } from '@vue/reactivity'
|
import { shallowReactive, toRaw } from '@vue/reactivity'
|
||||||
import type { Component, ComponentInternalInstance } from './component'
|
import {
|
||||||
|
type Component,
|
||||||
|
type ComponentInternalInstance,
|
||||||
|
setCurrentInstance,
|
||||||
|
} from './component'
|
||||||
|
|
||||||
export type ComponentPropsOptions<P = Data> =
|
export type ComponentPropsOptions<P = Data> =
|
||||||
| ComponentObjectPropsOptions<P>
|
| ComponentObjectPropsOptions<P>
|
||||||
|
@ -165,15 +169,9 @@ function resolvePropValue(
|
||||||
// if (key in propsDefaults) {
|
// if (key in propsDefaults) {
|
||||||
// value = propsDefaults[key]
|
// value = propsDefaults[key]
|
||||||
// } else {
|
// } else {
|
||||||
// setCurrentInstance(instance)
|
const reset = setCurrentInstance(instance)
|
||||||
// value = propsDefaults[key] = defaultValue.call(
|
value = defaultValue.call(null, props)
|
||||||
// __COMPAT__ &&
|
reset()
|
||||||
// isCompatEnabled(DeprecationTypes.PROPS_DEFAULT_THIS, instance)
|
|
||||||
// ? createPropsDefaultThis(instance, props, key)
|
|
||||||
// : null,
|
|
||||||
// props,
|
|
||||||
// )
|
|
||||||
// unsetCurrentInstance()
|
|
||||||
// }
|
// }
|
||||||
} else {
|
} else {
|
||||||
value = defaultValue
|
value = defaultValue
|
||||||
|
|
Loading…
Reference in New Issue