diff --git a/packages/runtime-core/__tests__/components/Suspense.spec.ts b/packages/runtime-core/__tests__/components/Suspense.spec.ts index 4228bc8401c..421bc0a8e96 100644 --- a/packages/runtime-core/__tests__/components/Suspense.spec.ts +++ b/packages/runtime-core/__tests__/components/Suspense.spec.ts @@ -69,6 +69,70 @@ describe('Suspense', () => { expect(serializeInner(root)).toBe(`
async
`) }) + test('emits events', async () => { + const Async = defineAsyncComponent({ + render() { + return h('div', 'async') + } + }) + + const onFallback = jest.fn() + const onResolve = jest.fn() + const onPending = jest.fn() + + const show = ref(true) + const Comp = { + setup() { + return () => + h( + Suspense, + { + onFallback, + onResolve, + onPending, + // force displaying the fallback right away + timeout: 0 + }, + { + default: () => (show.value ? h(Async) : null), + fallback: h('div', 'fallback') + } + ) + } + } + + const root = nodeOps.createElement('div') + render(h(Comp), root) + expect(onFallback).toHaveBeenCalledTimes(1) + expect(onPending).toHaveBeenCalledTimes(1) + expect(onResolve).toHaveBeenCalledTimes(0) + + await Promise.all(deps) + await nextTick() + expect(onFallback).toHaveBeenCalledTimes(1) + expect(onPending).toHaveBeenCalledTimes(1) + expect(onResolve).toHaveBeenCalledTimes(1) + + show.value = false + await nextTick() + expect(onFallback).toHaveBeenCalledTimes(1) + expect(onPending).toHaveBeenCalledTimes(2) + expect(onResolve).toHaveBeenCalledTimes(2) + + deps.length = 0 + show.value = true + await nextTick() + expect(onFallback).toHaveBeenCalledTimes(2) + expect(onPending).toHaveBeenCalledTimes(3) + expect(onResolve).toHaveBeenCalledTimes(2) + + await Promise.all(deps) + await nextTick() + expect(onFallback).toHaveBeenCalledTimes(2) + expect(onPending).toHaveBeenCalledTimes(3) + expect(onResolve).toHaveBeenCalledTimes(3) + }) + test('nested async deps', async () => { const calls: string[] = [] diff --git a/packages/runtime-core/src/components/Suspense.ts b/packages/runtime-core/src/components/Suspense.ts index d5d544469f3..5ef07a2ac5f 100644 --- a/packages/runtime-core/src/components/Suspense.ts +++ b/packages/runtime-core/src/components/Suspense.ts @@ -94,6 +94,16 @@ export const Suspense = ((__FEATURE_SUSPENSE__ new (): { $props: VNodeProps & SuspenseProps } } +function triggerEvent( + vnode: VNode, + name: 'onResolve' | 'onPending' | 'onFallback' +) { + const eventListener = vnode.props && vnode.props[name] + if (isFunction(eventListener)) { + eventListener() + } +} + function mountSuspense( vnode: VNode, container: RendererElement, @@ -137,6 +147,10 @@ function mountSuspense( // now check if we have encountered any async deps if (suspense.deps > 0) { // has async + // invoke @fallback event + triggerEvent(vnode, 'onPending') + triggerEvent(vnode, 'onFallback') + // mount the fallback tree patch( null, @@ -304,10 +318,7 @@ function patchSuspense( } else { // root node toggled // invoke @pending event - const onPending = n2.props && n2.props.onPending - if (isFunction(onPending)) { - onPending() - } + triggerEvent(n2, 'onPending') // mount pending branch in off-dom container suspense.pendingBranch = newBranch suspense.pendingId++ @@ -501,10 +512,7 @@ function createSuspenseBoundary( suspense.effects = [] // invoke @resolve event - const onResolve = vnode.props && vnode.props.onResolve - if (isFunction(onResolve)) { - onResolve() - } + triggerEvent(vnode, 'onResolve') }, fallback(fallbackVNode) { @@ -521,10 +529,7 @@ function createSuspenseBoundary( } = suspense // invoke @fallback event - const onFallback = vnode.props && vnode.props.onFallback - if (isFunction(onFallback)) { - onFallback() - } + triggerEvent(vnode, 'onFallback') const anchor = next(activeBranch!) const mountFallback = () => {