]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(Suspense): calling hooks before the transition finishes (#9388)
authorCarlos Rodrigues <carlos@hypermob.co.uk>
Sat, 21 Oct 2023 13:24:30 +0000 (14:24 +0100)
committerGitHub <noreply@github.com>
Sat, 21 Oct 2023 13:24:30 +0000 (21:24 +0800)
close #5844
close #5952

packages/runtime-core/src/components/Suspense.ts
packages/vue/__tests__/e2e/Transition.spec.ts

index 0fa07d9beecc154bf768db828f644a82a126e252..3640733d7343f1c20ea6531b437d49b3d76e4aa9 100644 (file)
@@ -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 = []
index 326eaa57e3da21a6ff68a98add2190d14b0cb95f..38fdf53cf4f69ae6a74f8bf6d75023fce2da62d7 100644 (file)
@@ -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: `
+          <div id="container">
+            <transition mode="out-in">
+              <Suspense>
+                <Foo v-if="toggle" />
+                <Bar v-else />
+              </Suspense>
+            </transition>
+          </div>
+          <button id="toggleBtn" @click="click">button</button>
+        `,
+          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('<div id="foo">Foo</div>')
+      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('<div id="bar" class="">Bar</div>')
+
+      expect(barMountSpy).toBeCalledTimes(1)
+      expect(barMountSpy).toHaveBeenNthCalledWith(1, true, false, true)
+    })
   })
 
   describe('transition with v-show', () => {