From: Carlos Rodrigues Date: Sat, 21 Oct 2023 13:24:30 +0000 (+0100) Subject: fix(Suspense): calling hooks before the transition finishes (#9388) X-Git-Tag: v3.3.7~10 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=00de3e61ed7a55e7d6c2e1987551d66ad0f909ff;p=thirdparty%2Fvuejs%2Fcore.git fix(Suspense): calling hooks before the transition finishes (#9388) close #5844 close #5952 --- diff --git a/packages/runtime-core/src/components/Suspense.ts b/packages/runtime-core/src/components/Suspense.ts index 0fa07d9bee..3640733d73 100644 --- a/packages/runtime-core/src/components/Suspense.ts +++ b/packages/runtime-core/src/components/Suspense.ts @@ -491,10 +491,12 @@ function createSuspenseBoundary( container } = suspense + // if there's a transition happening we need to wait it to finish. + let delayEnter: boolean | null = false if (suspense.isHydrating) { suspense.isHydrating = false } else if (!resume) { - const delayEnter = + delayEnter = activeBranch && pendingBranch!.transition && pendingBranch!.transition.mode === 'out-in' @@ -502,6 +504,7 @@ function createSuspenseBoundary( activeBranch!.transition!.afterLeave = () => { if (pendingId === suspense.pendingId) { move(pendingBranch!, container, anchor, MoveType.ENTER) + queuePostFlushCb(effects) } } } @@ -538,8 +541,8 @@ function createSuspenseBoundary( } parent = parent.parent } - // no pending parent suspense, flush all jobs - if (!hasUnresolvedAncestor) { + // no pending parent suspense nor transition, flush all jobs + if (!hasUnresolvedAncestor && !delayEnter) { queuePostFlushCb(effects) } suspense.effects = [] diff --git a/packages/vue/__tests__/e2e/Transition.spec.ts b/packages/vue/__tests__/e2e/Transition.spec.ts index 326eaa57e3..38fdf53cf4 100644 --- a/packages/vue/__tests__/e2e/Transition.spec.ts +++ b/packages/vue/__tests__/e2e/Transition.spec.ts @@ -1498,6 +1498,94 @@ describe('e2e: Transition', () => { }, E2E_TIMEOUT ) + + // #5844 + test('children mount should be called after html changes', async () => { + const fooMountSpy = vi.fn() + const barMountSpy = vi.fn() + + await page().exposeFunction('fooMountSpy', fooMountSpy) + await page().exposeFunction('barMountSpy', barMountSpy) + + await page().evaluate(() => { + const { fooMountSpy, barMountSpy } = window as any + const { createApp, ref, h, onMounted } = (window as any).Vue + createApp({ + template: ` +
+ + + + + + +
+ + `, + components: { + Foo: { + setup() { + const el = ref(null) + onMounted(() => { + fooMountSpy( + !!el.value, + !!document.getElementById('foo'), + !!document.getElementById('bar') + ) + }) + + return () => h('div', { ref: el, id: 'foo' }, 'Foo') + } + }, + Bar: { + setup() { + const el = ref(null) + onMounted(() => { + barMountSpy( + !!el.value, + !!document.getElementById('foo'), + !!document.getElementById('bar') + ) + }) + + return () => h('div', { ref: el, id: 'bar' }, 'Bar') + } + } + }, + setup: () => { + const toggle = ref(true) + const click = () => (toggle.value = !toggle.value) + return { toggle, click } + } + }).mount('#app') + }) + + await nextFrame() + expect(await html('#container')).toBe('
Foo
') + await transitionFinish() + + expect(fooMountSpy).toBeCalledTimes(1) + expect(fooMountSpy).toHaveBeenNthCalledWith(1, true, true, false) + + await page().evaluate(async () => { + ;(document.querySelector('#toggleBtn') as any)!.click() + // nextTrick for patch start + await Promise.resolve() + // nextTrick for Suspense resolve + await Promise.resolve() + // nextTrick for dom transition start + await Promise.resolve() + return document.querySelector('#container div')!.className.split(/\s+/g) + }) + + await nextFrame() + await transitionFinish() + + expect(await html('#container')).toBe('
Bar
') + + expect(barMountSpy).toBeCalledTimes(1) + expect(barMountSpy).toHaveBeenNthCalledWith(1, true, false, true) + }) }) describe('transition with v-show', () => {