test: test suspense error handling
This commit is contained in:
parent
b378b17076
commit
3b1d87efbe
@ -9,7 +9,8 @@ import {
|
||||
nextTick,
|
||||
onMounted,
|
||||
watch,
|
||||
onUnmounted
|
||||
onUnmounted,
|
||||
onErrorCaptured
|
||||
} from '@vue/runtime-test'
|
||||
|
||||
describe('renderer: suspense', () => {
|
||||
@ -543,7 +544,39 @@ describe('renderer: suspense', () => {
|
||||
expect(calls).toEqual([`inner mounted`, `outer mounted`])
|
||||
})
|
||||
|
||||
test.todo('error handling')
|
||||
test('error handling', async () => {
|
||||
const Async = {
|
||||
async setup() {
|
||||
throw new Error('oops')
|
||||
}
|
||||
}
|
||||
|
||||
const Comp = {
|
||||
setup() {
|
||||
const error = ref<any>(null)
|
||||
onErrorCaptured(e => {
|
||||
error.value = e
|
||||
return true
|
||||
})
|
||||
|
||||
return () =>
|
||||
error.value
|
||||
? h('div', error.value.message)
|
||||
: h(Suspense, null, {
|
||||
default: h(Async),
|
||||
fallback: h('div', 'fallback')
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const root = nodeOps.createElement('div')
|
||||
render(h(Comp), root)
|
||||
expect(serializeInner(root)).toBe(`<div>fallback</div>`)
|
||||
|
||||
await Promise.all(deps)
|
||||
await nextTick()
|
||||
expect(serializeInner(root)).toBe(`<div>oops</div>`)
|
||||
})
|
||||
|
||||
test.todo('new async dep after resolve should cause suspense to restart')
|
||||
|
||||
|
@ -49,6 +49,7 @@ import {
|
||||
createSuspenseBoundary,
|
||||
normalizeSuspenseChildren
|
||||
} from './suspense'
|
||||
import { handleError, ErrorCodes } from './errorHandling'
|
||||
|
||||
const prodEffectOptions = {
|
||||
scheduler: queueJob
|
||||
@ -919,7 +920,7 @@ export function createRenderer<
|
||||
if (__DEV__) {
|
||||
pushWarningContext(n2)
|
||||
}
|
||||
updateComponentPropsAndSlots(instance, n2)
|
||||
updateComponentPreRender(instance, n2)
|
||||
if (__DEV__) {
|
||||
popWarningContext()
|
||||
}
|
||||
@ -985,28 +986,21 @@ export function createRenderer<
|
||||
// state again
|
||||
}
|
||||
parentSuspense.deps++
|
||||
instance.asyncDep.then(asyncSetupResult => {
|
||||
// unmounted before resolve
|
||||
if (instance.isUnmounted || parentSuspense.isUnmounted) {
|
||||
return
|
||||
}
|
||||
parentSuspense.deps--
|
||||
// retry from this component
|
||||
instance.asyncResolved = true
|
||||
handleSetupResult(instance, asyncSetupResult, parentSuspense)
|
||||
setupRenderEffect(
|
||||
instance,
|
||||
parentSuspense,
|
||||
initialVNode,
|
||||
container,
|
||||
anchor,
|
||||
isSVG
|
||||
)
|
||||
updateHOCHostEl(instance, initialVNode.el as HostNode)
|
||||
if (parentSuspense.deps === 0) {
|
||||
resolveSuspense(parentSuspense)
|
||||
}
|
||||
})
|
||||
instance.asyncDep
|
||||
.catch(err => {
|
||||
handleError(err, instance, ErrorCodes.SETUP_FUNCTION)
|
||||
})
|
||||
.then(asyncSetupResult => {
|
||||
// component may be unmounted before resolve
|
||||
if (!instance.isUnmounted && !parentSuspense.isUnmounted) {
|
||||
retryAsyncComponent(
|
||||
instance,
|
||||
asyncSetupResult,
|
||||
parentSuspense,
|
||||
isSVG
|
||||
)
|
||||
}
|
||||
})
|
||||
// give it a placeholder
|
||||
const placeholder = (instance.subTree = createVNode(Empty))
|
||||
processEmptyNode(null, placeholder, container, anchor)
|
||||
@ -1028,6 +1022,38 @@ export function createRenderer<
|
||||
}
|
||||
}
|
||||
|
||||
function retryAsyncComponent(
|
||||
instance: ComponentInternalInstance,
|
||||
asyncSetupResult: unknown,
|
||||
parentSuspense: HostSuspsenseBoundary,
|
||||
isSVG: boolean
|
||||
) {
|
||||
parentSuspense.deps--
|
||||
// retry from this component
|
||||
instance.asyncResolved = true
|
||||
const { vnode } = instance
|
||||
if (__DEV__) {
|
||||
pushWarningContext(vnode)
|
||||
}
|
||||
handleSetupResult(instance, asyncSetupResult, parentSuspense)
|
||||
setupRenderEffect(
|
||||
instance,
|
||||
parentSuspense,
|
||||
vnode,
|
||||
// component may have been moved before resolve
|
||||
hostParentNode(instance.subTree.el) as HostElement,
|
||||
getNextHostNode(instance.subTree),
|
||||
isSVG
|
||||
)
|
||||
updateHOCHostEl(instance, vnode.el as HostNode)
|
||||
if (__DEV__) {
|
||||
popWarningContext()
|
||||
}
|
||||
if (parentSuspense.deps === 0) {
|
||||
resolveSuspense(parentSuspense)
|
||||
}
|
||||
}
|
||||
|
||||
function setupRenderEffect(
|
||||
instance: ComponentInternalInstance,
|
||||
parentSuspense: HostSuspsenseBoundary | null,
|
||||
@ -1063,7 +1089,7 @@ export function createRenderer<
|
||||
}
|
||||
|
||||
if (next !== null) {
|
||||
updateComponentPropsAndSlots(instance, next)
|
||||
updateComponentPreRender(instance, next)
|
||||
}
|
||||
const prevTree = instance.subTree
|
||||
const nextTree = (instance.subTree = renderComponentRoot(instance))
|
||||
@ -1107,7 +1133,7 @@ export function createRenderer<
|
||||
}, __DEV__ ? createDevEffectOptions(instance) : prodEffectOptions)
|
||||
}
|
||||
|
||||
function updateComponentPropsAndSlots(
|
||||
function updateComponentPreRender(
|
||||
instance: ComponentInternalInstance,
|
||||
nextVNode: HostVNode
|
||||
) {
|
||||
@ -1679,10 +1705,21 @@ export function createRenderer<
|
||||
}
|
||||
}
|
||||
|
||||
function getNextHostNode(vnode: HostVNode): HostNode | null {
|
||||
return vnode.component === null
|
||||
? hostNextSibling((vnode.anchor || vnode.el) as HostNode)
|
||||
: getNextHostNode(vnode.component.subTree)
|
||||
function getNextHostNode({
|
||||
component,
|
||||
suspense,
|
||||
anchor,
|
||||
el
|
||||
}: HostVNode): HostNode | null {
|
||||
if (component !== null) {
|
||||
return getNextHostNode(component.subTree)
|
||||
}
|
||||
if (__FEATURE_SUSPENSE__ && suspense !== null) {
|
||||
return getNextHostNode(
|
||||
suspense.isResolved ? suspense.subTree : suspense.fallbackTree
|
||||
)
|
||||
}
|
||||
return hostNextSibling((anchor || el) as HostNode)
|
||||
}
|
||||
|
||||
function setRef(
|
||||
|
@ -43,7 +43,7 @@ export const ErrorTypeStrings: Record<number | string, string> = {
|
||||
[ErrorCodes.APP_ERROR_HANDLER]: 'app errorHandler',
|
||||
[ErrorCodes.APP_WARN_HANDLER]: 'app warnHandler',
|
||||
[ErrorCodes.SCHEDULER]:
|
||||
'scheduler flush. This may be a Vue internals bug. ' +
|
||||
'scheduler flush. This is likely a Vue internals bug. ' +
|
||||
'Please open an issue at https://new-issue.vuejs.org/?repo=vuejs/vue'
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user