Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(runtime-core): filter single root for nested DEV_ROOT_FRAGMENT #8593

Merged
merged 13 commits into from
Jan 12, 2024
54 changes: 54 additions & 0 deletions packages/runtime-core/__tests__/rendererAttrsFallthrough.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import {
type FunctionalComponent,
createBlock,
createCommentVNode,
createElementBlock,
createElementVNode,
defineComponent,
h,
mergeProps,
Expand Down Expand Up @@ -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 = ''
Expand Down
58 changes: 58 additions & 0 deletions packages/runtime-core/__tests__/scopeId.spec.ts
Original file line number Diff line number Diff line change
@@ -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', () => {
Expand Down Expand Up @@ -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', () => {
Expand Down
18 changes: 17 additions & 1 deletion packages/runtime-core/src/componentRenderUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand All @@ -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++) {
Expand All @@ -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 {
Expand Down