]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
test: add more tests
authordaiwei <daiwei521@126.com>
Tue, 20 May 2025 09:27:22 +0000 (17:27 +0800)
committerdaiwei <daiwei521@126.com>
Tue, 20 May 2025 09:27:22 +0000 (17:27 +0800)
packages/runtime-core/src/renderer.ts
packages/runtime-vapor/__tests__/components/Suspense.spec.ts
packages/runtime-vapor/src/vdomInterop.ts

index 5930d6d5111860f93c1ea4e6ca02ee1bd3a56290..3a68b4493ee3ef4fdf4291d504740356e7270b10 100644 (file)
@@ -2387,6 +2387,24 @@ function baseCreateRenderer(
       instance.isUnmounted = true
     }, parentSuspense)
 
+    // A component with async dep inside a pending suspense is unmounted before
+    // its async dep resolves. This should remove the dep from the suspense, and
+    // cause the suspense to resolve immediately if that was the last dep.
+    if (
+      __FEATURE_SUSPENSE__ &&
+      parentSuspense &&
+      parentSuspense.pendingBranch &&
+      !parentSuspense.isUnmounted &&
+      instance.asyncDep &&
+      !instance.asyncResolved &&
+      instance.suspenseId === parentSuspense.pendingId
+    ) {
+      parentSuspense.deps--
+      if (parentSuspense.deps === 0) {
+        parentSuspense.resolve()
+      }
+    }
+
     if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
       devtoolsComponentRemoved(instance)
     }
index 36cafc55f35d117e6b2158b32664c3513f81f2d9..18d33795a03e140ca741d13a23efbec4808889f9 100644 (file)
@@ -1,4 +1,4 @@
-import { nextTick } from 'vue'
+import { nextTick, reactive } from 'vue'
 import { compile, runtimeDom, runtimeVapor } from '../_utils'
 
 describe.todo('VaporSuspense', () => {})
@@ -111,4 +111,134 @@ describe('vapor / vdom interop', () => {
     await nextTick()
     expect(container.innerHTML).toBe(`<div>inner</div>`)
   })
+
+  test('vdom suspense: content update before suspense resolve', async () => {
+    const data = reactive({ msg: 'foo', deps: [] })
+    const { container } = await testSuspense(
+      `<script setup>
+        const data = _data;
+        const components = _components;
+      </script>
+      <template>
+        <Suspense>
+          <components.VaporChild/>
+          <template #fallback>
+            <span>fallback {{data.msg}}</span>
+          </template>
+        </Suspense>
+      </template>`,
+      {
+        VaporChild: withAsyncScript(
+          `<template><div>{{data.msg}}</div></template>`,
+        ),
+      },
+      data,
+    )
+
+    expect(container.innerHTML).toBe(`<span>fallback foo</span>`)
+
+    data.msg = 'bar'
+    await nextTick()
+    expect(container.innerHTML).toBe(`<span>fallback bar</span>`)
+
+    await Promise.all(data.deps)
+    await nextTick()
+    expect(container.innerHTML).toBe(`<div>bar</div>`)
+  })
+
+  test('vdom suspense: unmount before suspense resolve', async () => {
+    const data = reactive({ show: true, deps: [] })
+    const { container } = await testSuspense(
+      `<script setup>
+        const data = _data;
+        const components = _components;
+      </script>
+      <template>
+        <Suspense>
+          <components.VaporChild v-if="data.show"/>
+          <template #fallback>
+            <span>fallback</span>
+          </template>
+        </Suspense>
+      </template>`,
+      {
+        VaporChild: withAsyncScript(`<template><div>child</div></template>`),
+      },
+      data,
+    )
+
+    expect(container.innerHTML).toBe(`<span>fallback</span>`)
+
+    data.show = false
+    await nextTick()
+    expect(container.innerHTML).toBe(`<!--v-if-->`)
+
+    await Promise.all(data.deps)
+    await nextTick()
+    expect(container.innerHTML).toBe(`<!--v-if-->`)
+  })
+
+  test('vdom suspense: unmount suspense after resolve', async () => {
+    const data = reactive({ show: true, deps: [] })
+    const { container } = await testSuspense(
+      `<script setup>
+        const data = _data;
+        const components = _components;
+      </script>
+      <template>
+        <Suspense v-if="data.show">
+          <components.VaporChild/>
+          <template #fallback>
+            <span>fallback</span>
+          </template>
+        </Suspense>
+      </template>`,
+      {
+        VaporChild: withAsyncScript(`<template><div>child</div></template>`),
+      },
+      data,
+    )
+
+    expect(container.innerHTML).toBe(`<span>fallback</span>`)
+
+    await Promise.all(data.deps)
+    await nextTick()
+    expect(container.innerHTML).toBe(`<div>child</div>`)
+
+    data.show = false
+    await nextTick()
+    expect(container.innerHTML).toBe(`<!--v-if-->`)
+  })
+
+  test('vdom suspense: unmount suspense before resolve', async () => {
+    const data = reactive({ show: true, deps: [] })
+    const { container } = await testSuspense(
+      `<script setup>
+        const data = _data;
+        const components = _components;
+      </script>
+      <template>
+        <Suspense v-if="data.show">
+          <components.VaporChild/>
+          <template #fallback>
+            <span>fallback</span>
+          </template>
+        </Suspense>
+      </template>`,
+      {
+        VaporChild: withAsyncScript(`<template><div>child</div></template>`),
+      },
+      data,
+    )
+
+    expect(container.innerHTML).toBe(`<span>fallback</span>`)
+
+    data.show = false
+    await nextTick()
+    expect(container.innerHTML).toBe(`<!--v-if-->`)
+
+    await Promise.all(data.deps)
+    await nextTick()
+    expect(container.innerHTML).toBe(`<!--v-if-->`)
+  })
 })
index fed9666e0a04534950e8eff201bcb3e74b17abe0..b93eb903f65ebd4605d1eb6ebc97559c1cda5fa2 100644 (file)
@@ -85,8 +85,30 @@ const vaporInteropImpl: Omit<
 
   unmount(vnode, doRemove) {
     const container = doRemove ? vnode.anchor!.parentNode : undefined
-    if (vnode.component) {
-      unmountComponent(vnode.component as any, container)
+    const instance = vnode.component as any as VaporComponentInstance
+    if (instance) {
+      // the async component may not be resolved yet, block is null
+      if (instance.block) {
+        unmountComponent(instance, container)
+      }
+      // A component with async dep inside a pending suspense is unmounted before
+      // its async dep resolves. This should remove the dep from the suspense, and
+      // cause the suspense to resolve immediately if that was the last dep.
+      const parentSuspense = instance.suspense
+      if (
+        __FEATURE_SUSPENSE__ &&
+        parentSuspense &&
+        parentSuspense.pendingBranch &&
+        !parentSuspense.isUnmounted &&
+        instance.asyncDep &&
+        !instance.asyncResolved &&
+        instance.suspenseId === parentSuspense.pendingId
+      ) {
+        parentSuspense.deps--
+        if (parentSuspense.deps === 0) {
+          parentSuspense.resolve()
+        }
+      }
     } else if (vnode.vb) {
       remove(vnode.vb, container)
     }