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,
|
||||
createBlock,
|
||||
createCommentVNode,
|
||||
createElementBlock,
|
||||
createElementVNode,
|
||||
defineComponent,
|
||||
h,
|
||||
mergeProps,
|
||||
|
@ -673,6 +675,58 @@ describe('attribute fallthrough', () => {
|
|||
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
|
||||
it('should not fallthrough v-model listeners with corresponding declared prop', () => {
|
||||
let textFoo = ''
|
||||
|
|
|
@ -1,14 +1,23 @@
|
|||
import {
|
||||
Fragment,
|
||||
createBlock,
|
||||
createCommentVNode,
|
||||
createVNode,
|
||||
defineComponent,
|
||||
h,
|
||||
nextTick,
|
||||
nodeOps,
|
||||
openBlock,
|
||||
popScopeId,
|
||||
pushScopeId,
|
||||
ref,
|
||||
render,
|
||||
renderSlot,
|
||||
serializeInner,
|
||||
withScopeId,
|
||||
} from '@vue/runtime-test'
|
||||
import { withCtx } from '../src/componentRenderContext'
|
||||
import { PatchFlags } from '@vue/shared'
|
||||
|
||||
describe('scopeId runtime support', () => {
|
||||
test('should attach scopeId', () => {
|
||||
|
@ -184,6 +193,55 @@ describe('scopeId runtime support', () => {
|
|||
|
||||
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', () => {
|
||||
|
|
|
@ -266,10 +266,17 @@ export function renderComponentRoot(
|
|||
const getChildRoot = (vnode: VNode): [VNode, SetRootFn] => {
|
||||
const rawChildren = vnode.children as VNodeArrayChildren
|
||||
const dynamicChildren = vnode.dynamicChildren
|
||||
const childRoot = filterSingleRoot(rawChildren)
|
||||
const childRoot = filterSingleRoot(rawChildren, false)
|
||||
if (!childRoot) {
|
||||
return [vnode, undefined]
|
||||
} else if (
|
||||
__DEV__ &&
|
||||
childRoot.patchFlag > 0 &&
|
||||
childRoot.patchFlag & PatchFlags.DEV_ROOT_FRAGMENT
|
||||
) {
|
||||
return getChildRoot(childRoot)
|
||||
}
|
||||
|
||||
const index = rawChildren.indexOf(childRoot)
|
||||
const dynamicIndex = dynamicChildren ? dynamicChildren.indexOf(childRoot) : -1
|
||||
const setRoot: SetRootFn = (updatedRoot: VNode) => {
|
||||
|
@ -287,6 +294,7 @@ const getChildRoot = (vnode: VNode): [VNode, SetRootFn] => {
|
|||
|
||||
export function filterSingleRoot(
|
||||
children: VNodeArrayChildren,
|
||||
recurse = true,
|
||||
): VNode | undefined {
|
||||
let singleRoot
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
|
@ -299,6 +307,14 @@ export function filterSingleRoot(
|
|||
return
|
||||
} else {
|
||||
singleRoot = child
|
||||
if (
|
||||
__DEV__ &&
|
||||
recurse &&
|
||||
singleRoot.patchFlag > 0 &&
|
||||
singleRoot.patchFlag & PatchFlags.DEV_ROOT_FRAGMENT
|
||||
) {
|
||||
return filterSingleRoot(singleRoot.children as VNodeArrayChildren)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
|
Loading…
Reference in New Issue