type FunctionalComponent,
createBlock,
createCommentVNode,
+ createElementBlock,
+ createElementVNode,
defineComponent,
h,
mergeProps,
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 = ''
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', () => {
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', () => {
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) => {
export function filterSingleRoot(
children: VNodeArrayChildren,
+ recurse = true,
): VNode | undefined {
let singleRoot
for (let i = 0; i < children.length; i++) {
return
} else {
singleRoot = child
+ if (
+ __DEV__ &&
+ recurse &&
+ singleRoot.patchFlag > 0 &&
+ singleRoot.patchFlag & PatchFlags.DEV_ROOT_FRAGMENT
+ ) {
+ return filterSingleRoot(singleRoot.children as VNodeArrayChildren)
+ }
}
}
} else {