wip: pass attrs fallthrough tests
This commit is contained in:
parent
7fae3ebaf3
commit
daf67397ae
@ -1,6 +1,6 @@
|
|||||||
import { OperationTypes } from './operations'
|
import { OperationTypes } from './operations'
|
||||||
import { Dep, targetMap } from './reactive'
|
import { Dep, targetMap } from './reactive'
|
||||||
import { EMPTY_OBJ } from '@vue/shared'
|
import { EMPTY_OBJ, extend } from '@vue/shared'
|
||||||
|
|
||||||
export interface ReactiveEffect {
|
export interface ReactiveEffect {
|
||||||
(): any
|
(): any
|
||||||
@ -203,7 +203,7 @@ function scheduleRun(
|
|||||||
) {
|
) {
|
||||||
if (__DEV__ && effect.onTrigger) {
|
if (__DEV__ && effect.onTrigger) {
|
||||||
effect.onTrigger(
|
effect.onTrigger(
|
||||||
Object.assign(
|
extend(
|
||||||
{
|
{
|
||||||
effect,
|
effect,
|
||||||
target,
|
target,
|
||||||
|
@ -5,7 +5,8 @@ import {
|
|||||||
nextTick,
|
nextTick,
|
||||||
mergeProps,
|
mergeProps,
|
||||||
ref,
|
ref,
|
||||||
onUpdated
|
onUpdated,
|
||||||
|
createComponent
|
||||||
} from '@vue/runtime-dom'
|
} from '@vue/runtime-dom'
|
||||||
|
|
||||||
describe('attribute fallthrough', () => {
|
describe('attribute fallthrough', () => {
|
||||||
@ -74,154 +75,156 @@ describe('attribute fallthrough', () => {
|
|||||||
expect(node.style.fontWeight).toBe('bold')
|
expect(node.style.fontWeight).toBe('bold')
|
||||||
})
|
})
|
||||||
|
|
||||||
// it('should separate in attrs when component has declared props', async () => {
|
it('should separate in attrs when component has declared props', async () => {
|
||||||
// const click = jest.fn()
|
const click = jest.fn()
|
||||||
// const childUpdated = jest.fn()
|
const childUpdated = jest.fn()
|
||||||
|
|
||||||
// class Hello extends Component {
|
const Hello = {
|
||||||
// count = 0
|
setup() {
|
||||||
// inc() {
|
const count = ref(0)
|
||||||
// this.count++
|
|
||||||
// click()
|
|
||||||
// }
|
|
||||||
// render() {
|
|
||||||
// return h(Child, {
|
|
||||||
// foo: 123,
|
|
||||||
// id: 'test',
|
|
||||||
// class: 'c' + this.count,
|
|
||||||
// style: { color: this.count ? 'red' : 'green' },
|
|
||||||
// onClick: this.inc
|
|
||||||
// })
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// class Child extends Component<{ [key: string]: any; foo: number }> {
|
function inc() {
|
||||||
// static props = {
|
count.value++
|
||||||
// foo: Number
|
click()
|
||||||
// }
|
}
|
||||||
// updated() {
|
|
||||||
// childUpdated()
|
|
||||||
// }
|
|
||||||
// render() {
|
|
||||||
// return cloneVNode(
|
|
||||||
// h(
|
|
||||||
// 'div',
|
|
||||||
// {
|
|
||||||
// class: 'c2',
|
|
||||||
// style: { fontWeight: 'bold' }
|
|
||||||
// },
|
|
||||||
// this.$props.foo
|
|
||||||
// ),
|
|
||||||
// this.$attrs
|
|
||||||
// )
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// const root = document.createElement('div')
|
return () =>
|
||||||
// document.body.appendChild(root)
|
h(Child, {
|
||||||
// await render(h(Hello), root)
|
foo: 1,
|
||||||
|
id: 'test',
|
||||||
// const node = root.children[0] as HTMLElement
|
class: 'c' + count.value,
|
||||||
|
style: { color: count.value ? 'red' : 'green' },
|
||||||
// // with declared props, any parent attr that isn't a prop falls through
|
onClick: inc
|
||||||
// expect(node.getAttribute('id')).toBe('test')
|
})
|
||||||
// expect(node.getAttribute('class')).toBe('c2 c0')
|
}
|
||||||
// expect(node.style.color).toBe('green')
|
}
|
||||||
// expect(node.style.fontWeight).toBe('bold')
|
|
||||||
// node.dispatchEvent(new CustomEvent('click'))
|
const Child = createComponent({
|
||||||
// expect(click).toHaveBeenCalled()
|
props: {
|
||||||
|
foo: Number
|
||||||
// // ...while declared ones remain props
|
},
|
||||||
// expect(node.hasAttribute('foo')).toBe(false)
|
setup(props, { attrs }) {
|
||||||
|
onUpdated(childUpdated)
|
||||||
// await nextTick()
|
return () =>
|
||||||
// expect(childUpdated).toHaveBeenCalled()
|
h(
|
||||||
// expect(node.getAttribute('id')).toBe('test')
|
'div',
|
||||||
// expect(node.getAttribute('class')).toBe('c2 c1')
|
mergeProps(
|
||||||
// expect(node.style.color).toBe('red')
|
{
|
||||||
// expect(node.style.fontWeight).toBe('bold')
|
class: 'c2',
|
||||||
|
style: { fontWeight: 'bold' }
|
||||||
// expect(node.hasAttribute('foo')).toBe(false)
|
},
|
||||||
// })
|
attrs
|
||||||
|
),
|
||||||
// it('should fallthrough on multi-nested components', async () => {
|
props.foo
|
||||||
// const click = jest.fn()
|
)
|
||||||
// const childUpdated = jest.fn()
|
}
|
||||||
// const grandChildUpdated = jest.fn()
|
})
|
||||||
|
|
||||||
// class Hello extends Component {
|
const root = document.createElement('div')
|
||||||
// count = 0
|
document.body.appendChild(root)
|
||||||
// inc() {
|
render(h(Hello), root)
|
||||||
// this.count++
|
|
||||||
// click()
|
const node = root.children[0] as HTMLElement
|
||||||
// }
|
|
||||||
// render() {
|
// with declared props, any parent attr that isn't a prop falls through
|
||||||
// return h(Child, {
|
expect(node.getAttribute('id')).toBe('test')
|
||||||
// foo: 1,
|
expect(node.getAttribute('class')).toBe('c2 c0')
|
||||||
// id: 'test',
|
expect(node.style.color).toBe('green')
|
||||||
// class: 'c' + this.count,
|
expect(node.style.fontWeight).toBe('bold')
|
||||||
// style: { color: this.count ? 'red' : 'green' },
|
node.dispatchEvent(new CustomEvent('click'))
|
||||||
// onClick: this.inc
|
expect(click).toHaveBeenCalled()
|
||||||
// })
|
|
||||||
// }
|
// ...while declared ones remain props
|
||||||
// }
|
expect(node.hasAttribute('foo')).toBe(false)
|
||||||
|
|
||||||
// class Child extends Component<{ [key: string]: any; foo: number }> {
|
await nextTick()
|
||||||
// updated() {
|
expect(childUpdated).toHaveBeenCalled()
|
||||||
// childUpdated()
|
expect(node.getAttribute('id')).toBe('test')
|
||||||
// }
|
expect(node.getAttribute('class')).toBe('c2 c1')
|
||||||
// render() {
|
expect(node.style.color).toBe('red')
|
||||||
// return h(GrandChild, this.$props)
|
expect(node.style.fontWeight).toBe('bold')
|
||||||
// }
|
|
||||||
// }
|
expect(node.hasAttribute('foo')).toBe(false)
|
||||||
|
})
|
||||||
// class GrandChild extends Component<{ [key: string]: any; foo: number }> {
|
|
||||||
// static props = {
|
it('should fallthrough on multi-nested components', async () => {
|
||||||
// foo: Number
|
const click = jest.fn()
|
||||||
// }
|
const childUpdated = jest.fn()
|
||||||
// updated() {
|
const grandChildUpdated = jest.fn()
|
||||||
// grandChildUpdated()
|
|
||||||
// }
|
const Hello = {
|
||||||
// render(props: any) {
|
setup() {
|
||||||
// return cloneVNode(
|
const count = ref(0)
|
||||||
// h(
|
|
||||||
// 'div',
|
function inc() {
|
||||||
// {
|
count.value++
|
||||||
// class: 'c2',
|
click()
|
||||||
// style: { fontWeight: 'bold' }
|
}
|
||||||
// },
|
|
||||||
// props.foo
|
return () =>
|
||||||
// ),
|
h(Child, {
|
||||||
// this.$attrs
|
foo: 1,
|
||||||
// )
|
id: 'test',
|
||||||
// }
|
class: 'c' + count.value,
|
||||||
// }
|
style: { color: count.value ? 'red' : 'green' },
|
||||||
|
onClick: inc
|
||||||
// const root = document.createElement('div')
|
})
|
||||||
// document.body.appendChild(root)
|
}
|
||||||
// await render(h(Hello), root)
|
}
|
||||||
|
|
||||||
// const node = root.children[0] as HTMLElement
|
const Child = {
|
||||||
|
setup(props: any) {
|
||||||
// // with declared props, any parent attr that isn't a prop falls through
|
onUpdated(childUpdated)
|
||||||
// expect(node.getAttribute('id')).toBe('test')
|
return () => h(GrandChild, props)
|
||||||
// expect(node.getAttribute('class')).toBe('c2 c0')
|
}
|
||||||
// expect(node.style.color).toBe('green')
|
}
|
||||||
// expect(node.style.fontWeight).toBe('bold')
|
|
||||||
// node.dispatchEvent(new CustomEvent('click'))
|
const GrandChild = createComponent({
|
||||||
// expect(click).toHaveBeenCalled()
|
props: {
|
||||||
|
foo: Number
|
||||||
// // ...while declared ones remain props
|
},
|
||||||
// expect(node.hasAttribute('foo')).toBe(false)
|
setup(props, { attrs }) {
|
||||||
|
onUpdated(grandChildUpdated)
|
||||||
// await nextTick()
|
return () =>
|
||||||
// expect(childUpdated).toHaveBeenCalled()
|
h(
|
||||||
// expect(grandChildUpdated).toHaveBeenCalled()
|
'div',
|
||||||
// expect(node.getAttribute('id')).toBe('test')
|
mergeProps(
|
||||||
// expect(node.getAttribute('class')).toBe('c2 c1')
|
{
|
||||||
// expect(node.style.color).toBe('red')
|
class: 'c2',
|
||||||
// expect(node.style.fontWeight).toBe('bold')
|
style: { fontWeight: 'bold' }
|
||||||
|
},
|
||||||
// expect(node.hasAttribute('foo')).toBe(false)
|
attrs
|
||||||
// })
|
),
|
||||||
|
props.foo
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const root = document.createElement('div')
|
||||||
|
document.body.appendChild(root)
|
||||||
|
render(h(Hello), root)
|
||||||
|
|
||||||
|
const node = root.children[0] as HTMLElement
|
||||||
|
|
||||||
|
// with declared props, any parent attr that isn't a prop falls through
|
||||||
|
expect(node.getAttribute('id')).toBe('test')
|
||||||
|
expect(node.getAttribute('class')).toBe('c2 c0')
|
||||||
|
expect(node.style.color).toBe('green')
|
||||||
|
expect(node.style.fontWeight).toBe('bold')
|
||||||
|
node.dispatchEvent(new CustomEvent('click'))
|
||||||
|
expect(click).toHaveBeenCalled()
|
||||||
|
|
||||||
|
// ...while declared ones remain props
|
||||||
|
expect(node.hasAttribute('foo')).toBe(false)
|
||||||
|
|
||||||
|
await nextTick()
|
||||||
|
expect(childUpdated).toHaveBeenCalled()
|
||||||
|
expect(grandChildUpdated).toHaveBeenCalled()
|
||||||
|
expect(node.getAttribute('id')).toBe('test')
|
||||||
|
expect(node.getAttribute('class')).toBe('c2 c1')
|
||||||
|
expect(node.style.color).toBe('red')
|
||||||
|
expect(node.style.fontWeight).toBe('bold')
|
||||||
|
|
||||||
|
expect(node.hasAttribute('foo')).toBe(false)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
@ -262,12 +262,19 @@ export function setupStatefulComponent(instance: ComponentInstance) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// used to identify a setup context proxy
|
||||||
|
export const SetupProxySymbol = Symbol()
|
||||||
|
|
||||||
const SetupProxyHandlers: { [key: string]: ProxyHandler<any> } = {}
|
const SetupProxyHandlers: { [key: string]: ProxyHandler<any> } = {}
|
||||||
;['attrs', 'slots', 'refs'].forEach((type: string) => {
|
;['attrs', 'slots', 'refs'].forEach((type: string) => {
|
||||||
SetupProxyHandlers[type] = {
|
SetupProxyHandlers[type] = {
|
||||||
get: (instance: any, key: string) => (instance[type] as any)[key],
|
get: (instance, key) => (instance[type] as any)[key],
|
||||||
has: (instance: any, key: string) => key in (instance[type] as any),
|
has: (instance, key) =>
|
||||||
ownKeys: (instance: any) => Object.keys(instance[type] as any),
|
key === SetupProxySymbol || key in (instance[type] as any),
|
||||||
|
ownKeys: instance => Reflect.ownKeys(instance[type] as any),
|
||||||
|
// this is necessary for ownKeys to work properly
|
||||||
|
getOwnPropertyDescriptor: (instance, key) =>
|
||||||
|
Reflect.getOwnPropertyDescriptor(instance[type], key),
|
||||||
set: () => false,
|
set: () => false,
|
||||||
deleteProperty: () => false
|
deleteProperty: () => false
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,17 @@
|
|||||||
import { isArray, isFunction, isString, isObject, EMPTY_ARR } from '@vue/shared'
|
import {
|
||||||
import { ComponentInstance, Data } from './component'
|
isArray,
|
||||||
|
isFunction,
|
||||||
|
isString,
|
||||||
|
isObject,
|
||||||
|
EMPTY_ARR,
|
||||||
|
extend
|
||||||
|
} from '@vue/shared'
|
||||||
|
import { ComponentInstance, Data, SetupProxySymbol } from './component'
|
||||||
import { HostNode } from './createRenderer'
|
import { HostNode } from './createRenderer'
|
||||||
import { RawSlots } from './componentSlots'
|
import { RawSlots } from './componentSlots'
|
||||||
import { PatchFlags } from './patchFlags'
|
import { PatchFlags } from './patchFlags'
|
||||||
import { ShapeFlags } from './shapeFlags'
|
import { ShapeFlags } from './shapeFlags'
|
||||||
|
import { isReactive } from '@vue/reactivity'
|
||||||
|
|
||||||
export const Fragment = Symbol('Fragment')
|
export const Fragment = Symbol('Fragment')
|
||||||
export const Text = Symbol('Text')
|
export const Text = Symbol('Text')
|
||||||
@ -100,6 +108,28 @@ export function createVNode(
|
|||||||
// Allow passing 0 for props, this can save bytes on generated code.
|
// Allow passing 0 for props, this can save bytes on generated code.
|
||||||
props = props || null
|
props = props || null
|
||||||
|
|
||||||
|
// class & style normalization.
|
||||||
|
if (props !== null) {
|
||||||
|
// for reactive or proxy objects, we need to clone it to enable mutation.
|
||||||
|
if (isReactive(props) || SetupProxySymbol in props) {
|
||||||
|
props = extend({}, props)
|
||||||
|
}
|
||||||
|
// class normalization only needed if the vnode isn't generated by
|
||||||
|
// compiler-optimized code
|
||||||
|
if (props.class != null && !(patchFlag & PatchFlags.CLASS)) {
|
||||||
|
props.class = normalizeClass(props.class)
|
||||||
|
}
|
||||||
|
let { style } = props
|
||||||
|
if (style != null) {
|
||||||
|
// reactive state objects need to be cloned since they are likely to be
|
||||||
|
// mutated
|
||||||
|
if (isReactive(style) && !isArray(style)) {
|
||||||
|
style = extend({}, style)
|
||||||
|
}
|
||||||
|
props.style = normalizeStyle(style)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// encode the vnode type information into a bitmap
|
// encode the vnode type information into a bitmap
|
||||||
const shapeFlag = isString(type)
|
const shapeFlag = isString(type)
|
||||||
? ShapeFlags.ELEMENT
|
? ShapeFlags.ELEMENT
|
||||||
@ -127,18 +157,6 @@ export function createVNode(
|
|||||||
|
|
||||||
normalizeChildren(vnode, children)
|
normalizeChildren(vnode, children)
|
||||||
|
|
||||||
// class & style normalization.
|
|
||||||
if (props !== null) {
|
|
||||||
// class normalization only needed if the vnode isn't generated by
|
|
||||||
// compiler-optimized code
|
|
||||||
if (props.class != null && !(patchFlag & PatchFlags.CLASS)) {
|
|
||||||
props.class = normalizeClass(props.class)
|
|
||||||
}
|
|
||||||
if (props.style != null) {
|
|
||||||
props.style = normalizeStyle(props.style)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// presence of a patch flag indicates this node is dynamic
|
// presence of a patch flag indicates this node is dynamic
|
||||||
// component nodes also should always be tracked, because even if the
|
// component nodes also should always be tracked, because even if the
|
||||||
// component doesn't need to update, it needs to persist the instance on to
|
// component doesn't need to update, it needs to persist the instance on to
|
||||||
@ -257,9 +275,7 @@ const handlersRE = /^on|^vnode/
|
|||||||
|
|
||||||
export function mergeProps(...args: Data[]) {
|
export function mergeProps(...args: Data[]) {
|
||||||
const ret: Data = {}
|
const ret: Data = {}
|
||||||
for (const key in args[0]) {
|
extend(ret, args[0])
|
||||||
ret[key] = args[0][key]
|
|
||||||
}
|
|
||||||
for (let i = 1; i < args.length; i++) {
|
for (let i = 1; i < args.length; i++) {
|
||||||
const toMerge = args[i]
|
const toMerge = args[i]
|
||||||
for (const key in toMerge) {
|
for (const key in toMerge) {
|
||||||
|
@ -7,6 +7,16 @@ export const reservedPropRE = /^(?:key|ref|slots)$|^vnode/
|
|||||||
|
|
||||||
export const isOn = (key: string) => key[0] === 'o' && key[1] === 'n'
|
export const isOn = (key: string) => key[0] === 'o' && key[1] === 'n'
|
||||||
|
|
||||||
|
export const extend = <T extends object, U extends object>(
|
||||||
|
a: T,
|
||||||
|
b: U
|
||||||
|
): T & U => {
|
||||||
|
for (const key in b) {
|
||||||
|
;(a as any)[key] = b[key]
|
||||||
|
}
|
||||||
|
return a as any
|
||||||
|
}
|
||||||
|
|
||||||
export const isArray = Array.isArray
|
export const isArray = Array.isArray
|
||||||
export const isFunction = (val: any): val is Function =>
|
export const isFunction = (val: any): val is Function =>
|
||||||
typeof val === 'function'
|
typeof val === 'function'
|
||||||
|
Loading…
x
Reference in New Issue
Block a user