diff --git a/packages/runtime-core/__tests__/rendererFragment.spec.ts b/packages/runtime-core/__tests__/rendererFragment.spec.ts index d0c9e5c79a7..fbd9af11857 100644 --- a/packages/runtime-core/__tests__/rendererFragment.spec.ts +++ b/packages/runtime-core/__tests__/rendererFragment.spec.ts @@ -13,9 +13,13 @@ import { createTextVNode, createBlock, openBlock, - createCommentVNode + createCommentVNode, + withCtx, + createElementBlock, + ref, + renderSlot } from '@vue/runtime-test' -import { PatchFlags } from '@vue/shared' +import { PatchFlags, SlotFlags } from '@vue/shared' import { renderList } from '../src/helpers/renderList' describe('renderer: fragment', () => { @@ -351,4 +355,121 @@ describe('renderer: fragment', () => { render(renderFn(['two', 'one']), root) expect(serializeInner(root)).toBe(`text
two
text
one
`) }) + + // #9200 + test('stable fragment in unstable slot', () => { + const root = nodeOps.createElement('div') + + const items = ref([{ field1: 'one', field2: 'two' as string | undefined }]) + + const textBlock = 'text-block' + const vIfBlock = 'v-if-block' + + const Comp = { + render(ctx: any) { + return ( + openBlock(true), + createElementBlock( + Fragment, + null, + renderList(items.value, (item, i) => { + return ( + openBlock(), + createElementBlock('div', { key: i }, [ + (openBlock(true), + createElementBlock( + Fragment, + null, + renderList(['field1', 'field2'] as const, field => { + return ( + openBlock(), + createElementBlock('span', { key: field }, [ + renderSlot( + ctx.$slots, + 'default', + { + value: item[field], + field: field + }, + () => [ + item[field] + ? (openBlock(), + createElementBlock( + Fragment, + { key: 0 }, + [createTextVNode('xxx')], + PatchFlags.STABLE_FRAGMENT + )) + : (openBlock(), + createElementBlock( + Fragment, + { key: 1 }, + [createTextVNode('yyy')], + PatchFlags.STABLE_FRAGMENT + )) + ] + ) + ]) + ) + }), + PatchFlags.KEYED_FRAGMENT + )) + ]) + ) + }), + PatchFlags.KEYED_FRAGMENT + ) + ) + } + } + + const hoisted1 = { key: 0 } + const hoisted2 = { key: 0 } + const hoisted3 = { key: 1 } + + const renderFn = () => { + return ( + openBlock(true), + createVNode(Comp, null, { + default: withCtx( + ({ field, value }: { field: string; value: any }) => [ + field === 'field1' + ? (openBlock(), createElementBlock('div', hoisted1, textBlock)) + : field === 'field2' + ? (openBlock(), + createElementBlock( + Fragment, + { key: 1 }, + [ + value + ? (openBlock(), + createElementBlock('div', hoisted2, vIfBlock)) + : createCommentVNode('v-if', true), + value + ? (openBlock(), + createElementBlock('div', hoisted3, vIfBlock)) + : createCommentVNode('v-if', true) + ], + PatchFlags.STABLE_FRAGMENT + )) + : createCommentVNode('v-if', true) + ] + ), + _: SlotFlags.STABLE + }) + ) + } + + render(renderFn(), root) + expect(serializeInner(root)).toBe( + `
${textBlock}
${vIfBlock}
${vIfBlock}
` + ) + + items.value = [{ field1: 'one', field2: undefined }] + + render(renderFn(), root) + expect(serializeInner(root)).toBe( + `
${textBlock}
yyy
` + ) + }) }) diff --git a/packages/runtime-core/src/renderer.ts b/packages/runtime-core/src/renderer.ts index 383e17fb0f5..25ca065e85a 100644 --- a/packages/runtime-core/src/renderer.ts +++ b/packages/runtime-core/src/renderer.ts @@ -1098,7 +1098,9 @@ function baseCreateRenderer( dynamicChildren && // #2715 the previous fragment could've been a BAILed one as a result // of renderSlot() with no valid children - n1.dynamicChildren + n1.dynamicChildren && + // #9200 in some case stable fragment in deep unstable slot + n1.dynamicChildren.length === dynamicChildren.length ) { // a stable fragment (template root or