diff --git a/packages/runtime-core/__tests__/components/Suspense.spec.ts b/packages/runtime-core/__tests__/components/Suspense.spec.ts index 4bdb8bf72d8..d8cdf36ec42 100644 --- a/packages/runtime-core/__tests__/components/Suspense.spec.ts +++ b/packages/runtime-core/__tests__/components/Suspense.spec.ts @@ -1530,7 +1530,7 @@ describe('Suspense', () => { }) // #5844 - test('Suspense + Transition', async () => { + test('Suspense + Transition HTML DOM', async () => { const InnerA = defineComponent({ setup: () => { const el = ref(null) @@ -1577,7 +1577,8 @@ describe('Suspense', () => { Transition, { name: 'page', - mode: 'out-in' + mode: 'out-in', + duration: 1 }, { default: () => [ @@ -1601,15 +1602,120 @@ describe('Suspense', () => { const root = document.createElement('div') try { document.body.appendChild(root) - renderDOM(h(Comp), document.body) - outerToggle.value = true - await nextTick() + renderDOM(h(Comp), root) + expect(document.body.innerHTML).toContain('innerA') + outerToggle.value = true + // give some time for the transition to finish + await new Promise(r => setTimeout(r, 100)) + expect(document.body.innerHTML).toContain('innerB') outerToggle.value = false - // delay it more to error from happening on the CI - await new Promise(r => setTimeout(r, 10)) + // give some time for the transition to finish + await new Promise(r => setTimeout(r, 100)) + expect(document.body.innerHTML).toContain('innerA') + + outerToggle.value = true + + // give some time for the transition to finish + await new Promise(r => setTimeout(r, 100)) + expect(document.body.innerHTML).toContain('innerB') } finally { - document.body.innerHTML = '' + document.body.removeChild(root) } }) + + // #5844 + test('Suspense + Transition', async () => { + const expectInnerA = '
innerA
' + const expectInnerB = '
innerB
' + + const InnerA = defineComponent({ + setup: () => { + const el = ref(null) + onMounted(() => { + expect(el.value).toBeTruthy() + expect(serializeInner(root)).toBe(expectInnerA) + }) + return () => + h( + 'div', + { + ref: el, + id: 'innerA' + }, + 'innerA' + ) + } + }) + + const InnerB = defineComponent({ + setup: () => { + const el = ref(null) + onMounted(() => { + expect(el.value).toBeTruthy() + console.log('inner b', serializeInner(root)) + expect(serializeInner(root)).toBe(expectInnerB) + }) + return () => + h( + 'div', + { + ref: el, + id: 'innerB' + }, + 'innerB' + ) + } + }) + const outerToggle = ref(false) + + const Comp = defineComponent({ + setup() { + return () => + h( + Transition, + { + name: 'page', + mode: 'out-in', + duration: 1 + }, + { + default: () => [ + h(Suspense, null, { + default: [ + outerToggle.value + ? h(InnerB, { + key: 1 + }) + : h(InnerA, { + key: 2 + }) + ] + }) + ] + } + ) + } + }) + + const root = nodeOps.createElement('div') + render(h(Comp), root) + expect(serializeInner(root)).toBe(expectInnerA) + + outerToggle.value = true + // give some time for the transition to finish + await new Promise(r => setTimeout(r, 100)) + expect(serializeInner(root)).toContain('innerB') + outerToggle.value = false + // give some time for the transition to finish + await new Promise(r => setTimeout(r, 100)) + expect(serializeInner(root)).toContain('innerA') + + outerToggle.value = true + + // give some time for the transition to finish + await new Promise(r => setTimeout(r, 100)) + expect(serializeInner(root)).toContain('innerB') + // } + }) }) diff --git a/packages/runtime-core/src/components/BaseTransition.ts b/packages/runtime-core/src/components/BaseTransition.ts index 9cb80b94ef0..db0864c3736 100644 --- a/packages/runtime-core/src/components/BaseTransition.ts +++ b/packages/runtime-core/src/components/BaseTransition.ts @@ -511,6 +511,20 @@ export function getTransitionRawChildren( getTransitionRawChildren(child.children as VNode[], keepComment, key) ) } + // #5844 suspense children should be able to transition + else if (child.shapeFlag & ShapeFlags.SUSPENSE) { + ret = ret.concat( + getTransitionRawChildren( + Array.isArray(child.children) + ? (child.children as VNode[]) + : typeof child.children === 'object' + ? (child.children?.default as VNode[]) + : [], + keepComment, + key + ) + ) + } // comment placeholders should be skipped, e.g. v-if else if (keepComment || child.type !== Comment) { ret.push(key != null ? cloneVNode(child, { key }) : child) diff --git a/packages/runtime-test/src/nodeOps.ts b/packages/runtime-test/src/nodeOps.ts index a3a8012f280..65ac89c5d09 100644 --- a/packages/runtime-test/src/nodeOps.ts +++ b/packages/runtime-test/src/nodeOps.ts @@ -23,6 +23,10 @@ export interface TestElement { children: TestNode[] props: Record eventListeners: Record | null + classList: { + add: (c: string) => void + remove: (c: string) => void + } } export interface TestText { @@ -79,7 +83,11 @@ function createElement(tag: string): TestElement { children: [], props: {}, parentNode: null, - eventListeners: null + eventListeners: null, + classList: { + add(c: string) {}, + remove(c: string) {} + } } logNodeOp({ type: NodeOpTypes.CREATE,