diff --git a/packages/runtime-core/__tests__/rendererOptimizedMode.spec.ts b/packages/runtime-core/__tests__/rendererOptimizedMode.spec.ts index 958c1274806..efdd6df01e8 100644 --- a/packages/runtime-core/__tests__/rendererOptimizedMode.spec.ts +++ b/packages/runtime-core/__tests__/rendererOptimizedMode.spec.ts @@ -29,7 +29,7 @@ import { setBlockTracking, withCtx, } from '@vue/runtime-test' -import { PatchFlags, SlotFlags } from '@vue/shared' +import { PatchFlags, SlotFlags, toDisplayString } from '@vue/shared' import { SuspenseImpl } from '../src/components/Suspense' describe('renderer: optimized mode', () => { @@ -1294,4 +1294,62 @@ describe('renderer: optimized mode', () => { expect(inner(root)).toBe('') expect(beforeUnmountSpy).toHaveBeenCalledTimes(1) }) + + // #12411 + test('handle patch stable fragment with non-reactive v-for source', async () => { + const count = ref(0) + const foo: any = [] + function updateFoo() { + for (let n = 0; n < 3; n++) { + foo[n] = n + 1 + '_foo' + } + } + const Comp = { + setup() { + return () => { + //
{{ count }}
+ //
{{ item }}
+ return ( + openBlock(), + createElementBlock( + Fragment, + null, + [ + createElementVNode( + 'div', + null, + toDisplayString(count.value), + PatchFlags.TEXT, + ), + (openBlock(), + createElementBlock( + Fragment, + null, + renderList(foo, item => { + return createElementVNode( + 'div', + null, + toDisplayString(item), + PatchFlags.TEXT, + ) + }), + PatchFlags.STABLE_FRAGMENT, + )), + ], + PatchFlags.STABLE_FRAGMENT, + ) + ) + } + }, + } + + render(h(Comp), root) + expect(inner(root)).toBe('
0
') + updateFoo() + count.value++ + await nextTick() + expect(inner(root)).toBe( + '
1
1_foo
2_foo
3_foo
', + ) + }) }) diff --git a/packages/runtime-core/src/renderer.ts b/packages/runtime-core/src/renderer.ts index 90cc22f5470..98f59f8e107 100644 --- a/packages/runtime-core/src/renderer.ts +++ b/packages/runtime-core/src/renderer.ts @@ -1079,7 +1079,8 @@ 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 && + n1.dynamicChildren.length === dynamicChildren.length ) { // a stable fragment (template root or