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)
}
-import { nextTick } from 'vue'
+import { nextTick, reactive } from 'vue'
import { compile, runtimeDom, runtimeVapor } from '../_utils'
describe.todo('VaporSuspense', () => {})
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-->`)
+ })
})
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)
}