test: test suspense error handling

This commit is contained in:
Evan You 2019-09-12 01:52:14 -04:00
parent b378b17076
commit 3b1d87efbe
3 changed files with 102 additions and 32 deletions

View File

@ -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')

View File

@ -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(

View File

@ -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'
}