mirror of https://github.com/vuejs/core.git
fix(runtime-core): filter single root for nested DEV_ROOT_FRAGMENT (#8593)
close #5203 close #8581 close #10087
This commit is contained in:
parent
8d04205041
commit
d35b87725a
|
|
@ -8,6 +8,8 @@ import {
|
||||||
type FunctionalComponent,
|
type FunctionalComponent,
|
||||||
createBlock,
|
createBlock,
|
||||||
createCommentVNode,
|
createCommentVNode,
|
||||||
|
createElementBlock,
|
||||||
|
createElementVNode,
|
||||||
defineComponent,
|
defineComponent,
|
||||||
h,
|
h,
|
||||||
mergeProps,
|
mergeProps,
|
||||||
|
|
@ -673,6 +675,58 @@ describe('attribute fallthrough', () => {
|
||||||
expect(click).toHaveBeenCalled()
|
expect(click).toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should support fallthrough for nested dev root fragments', async () => {
|
||||||
|
const toggle = ref(false)
|
||||||
|
|
||||||
|
const Child = {
|
||||||
|
setup() {
|
||||||
|
return () => (
|
||||||
|
openBlock(),
|
||||||
|
createElementBlock(
|
||||||
|
Fragment,
|
||||||
|
null,
|
||||||
|
[
|
||||||
|
createCommentVNode(' comment A '),
|
||||||
|
toggle.value
|
||||||
|
? (openBlock(), createElementBlock('span', { key: 0 }, 'Foo'))
|
||||||
|
: (openBlock(),
|
||||||
|
createElementBlock(
|
||||||
|
Fragment,
|
||||||
|
{ key: 1 },
|
||||||
|
[
|
||||||
|
createCommentVNode(' comment B '),
|
||||||
|
createElementVNode('div', null, 'Bar'),
|
||||||
|
],
|
||||||
|
PatchFlags.STABLE_FRAGMENT | PatchFlags.DEV_ROOT_FRAGMENT,
|
||||||
|
)),
|
||||||
|
],
|
||||||
|
PatchFlags.STABLE_FRAGMENT | PatchFlags.DEV_ROOT_FRAGMENT,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const Root = {
|
||||||
|
setup() {
|
||||||
|
return () => (openBlock(), createBlock(Child, { class: 'red' }))
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const root = document.createElement('div')
|
||||||
|
document.body.appendChild(root)
|
||||||
|
render(h(Root), root)
|
||||||
|
|
||||||
|
expect(root.innerHTML).toBe(
|
||||||
|
`<!-- comment A --><!-- comment B --><div class="red">Bar</div>`,
|
||||||
|
)
|
||||||
|
|
||||||
|
toggle.value = true
|
||||||
|
await nextTick()
|
||||||
|
expect(root.innerHTML).toBe(
|
||||||
|
`<!-- comment A --><span class=\"red\">Foo</span>`,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
// #1989
|
// #1989
|
||||||
it('should not fallthrough v-model listeners with corresponding declared prop', () => {
|
it('should not fallthrough v-model listeners with corresponding declared prop', () => {
|
||||||
let textFoo = ''
|
let textFoo = ''
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,23 @@
|
||||||
import {
|
import {
|
||||||
|
Fragment,
|
||||||
|
createBlock,
|
||||||
|
createCommentVNode,
|
||||||
|
createVNode,
|
||||||
|
defineComponent,
|
||||||
h,
|
h,
|
||||||
|
nextTick,
|
||||||
nodeOps,
|
nodeOps,
|
||||||
|
openBlock,
|
||||||
popScopeId,
|
popScopeId,
|
||||||
pushScopeId,
|
pushScopeId,
|
||||||
|
ref,
|
||||||
render,
|
render,
|
||||||
renderSlot,
|
renderSlot,
|
||||||
serializeInner,
|
serializeInner,
|
||||||
withScopeId,
|
withScopeId,
|
||||||
} from '@vue/runtime-test'
|
} from '@vue/runtime-test'
|
||||||
import { withCtx } from '../src/componentRenderContext'
|
import { withCtx } from '../src/componentRenderContext'
|
||||||
|
import { PatchFlags } from '@vue/shared'
|
||||||
|
|
||||||
describe('scopeId runtime support', () => {
|
describe('scopeId runtime support', () => {
|
||||||
test('should attach scopeId', () => {
|
test('should attach scopeId', () => {
|
||||||
|
|
@ -184,6 +193,55 @@ describe('scopeId runtime support', () => {
|
||||||
|
|
||||||
expect(serializeInner(root)).toBe(`<div parent></div>`)
|
expect(serializeInner(root)).toBe(`<div parent></div>`)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('should inherit scopeId through nested DEV_ROOT_FRAGMENT with inheritAttrs: false', async () => {
|
||||||
|
const Parent = {
|
||||||
|
__scopeId: 'parent',
|
||||||
|
render() {
|
||||||
|
return h(Child, { class: 'foo' })
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const ok = ref(true)
|
||||||
|
const Child = defineComponent({
|
||||||
|
inheritAttrs: false,
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
openBlock(),
|
||||||
|
createBlock(
|
||||||
|
Fragment,
|
||||||
|
null,
|
||||||
|
[
|
||||||
|
createCommentVNode('comment1'),
|
||||||
|
ok.value
|
||||||
|
? (openBlock(), createBlock('div', { key: 0 }, 'div1'))
|
||||||
|
: (openBlock(),
|
||||||
|
createBlock(
|
||||||
|
Fragment,
|
||||||
|
{ key: 1 },
|
||||||
|
[
|
||||||
|
createCommentVNode('comment2'),
|
||||||
|
createVNode('div', null, 'div2'),
|
||||||
|
],
|
||||||
|
PatchFlags.STABLE_FRAGMENT | PatchFlags.DEV_ROOT_FRAGMENT,
|
||||||
|
)),
|
||||||
|
],
|
||||||
|
PatchFlags.STABLE_FRAGMENT | PatchFlags.DEV_ROOT_FRAGMENT,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const root = nodeOps.createElement('div')
|
||||||
|
render(h(Parent), root)
|
||||||
|
expect(serializeInner(root)).toBe(`<!--comment1--><div parent>div1</div>`)
|
||||||
|
|
||||||
|
ok.value = false
|
||||||
|
await nextTick()
|
||||||
|
expect(serializeInner(root)).toBe(
|
||||||
|
`<!--comment1--><!--comment2--><div parent>div2</div>`,
|
||||||
|
)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('backwards compat with <=3.0.7', () => {
|
describe('backwards compat with <=3.0.7', () => {
|
||||||
|
|
|
||||||
|
|
@ -266,10 +266,17 @@ export function renderComponentRoot(
|
||||||
const getChildRoot = (vnode: VNode): [VNode, SetRootFn] => {
|
const getChildRoot = (vnode: VNode): [VNode, SetRootFn] => {
|
||||||
const rawChildren = vnode.children as VNodeArrayChildren
|
const rawChildren = vnode.children as VNodeArrayChildren
|
||||||
const dynamicChildren = vnode.dynamicChildren
|
const dynamicChildren = vnode.dynamicChildren
|
||||||
const childRoot = filterSingleRoot(rawChildren)
|
const childRoot = filterSingleRoot(rawChildren, false)
|
||||||
if (!childRoot) {
|
if (!childRoot) {
|
||||||
return [vnode, undefined]
|
return [vnode, undefined]
|
||||||
|
} else if (
|
||||||
|
__DEV__ &&
|
||||||
|
childRoot.patchFlag > 0 &&
|
||||||
|
childRoot.patchFlag & PatchFlags.DEV_ROOT_FRAGMENT
|
||||||
|
) {
|
||||||
|
return getChildRoot(childRoot)
|
||||||
}
|
}
|
||||||
|
|
||||||
const index = rawChildren.indexOf(childRoot)
|
const index = rawChildren.indexOf(childRoot)
|
||||||
const dynamicIndex = dynamicChildren ? dynamicChildren.indexOf(childRoot) : -1
|
const dynamicIndex = dynamicChildren ? dynamicChildren.indexOf(childRoot) : -1
|
||||||
const setRoot: SetRootFn = (updatedRoot: VNode) => {
|
const setRoot: SetRootFn = (updatedRoot: VNode) => {
|
||||||
|
|
@ -287,6 +294,7 @@ const getChildRoot = (vnode: VNode): [VNode, SetRootFn] => {
|
||||||
|
|
||||||
export function filterSingleRoot(
|
export function filterSingleRoot(
|
||||||
children: VNodeArrayChildren,
|
children: VNodeArrayChildren,
|
||||||
|
recurse = true,
|
||||||
): VNode | undefined {
|
): VNode | undefined {
|
||||||
let singleRoot
|
let singleRoot
|
||||||
for (let i = 0; i < children.length; i++) {
|
for (let i = 0; i < children.length; i++) {
|
||||||
|
|
@ -299,6 +307,14 @@ export function filterSingleRoot(
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
singleRoot = child
|
singleRoot = child
|
||||||
|
if (
|
||||||
|
__DEV__ &&
|
||||||
|
recurse &&
|
||||||
|
singleRoot.patchFlag > 0 &&
|
||||||
|
singleRoot.patchFlag & PatchFlags.DEV_ROOT_FRAGMENT
|
||||||
|
) {
|
||||||
|
return filterSingleRoot(singleRoot.children as VNodeArrayChildren)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue