expect(spy).toHaveBeenCalled()
})
- test('execute the updateComponent(AsyncComponentWrapper) before the async component is resolved', async () => {
+ test('update async wrapper before resolve', async () => {
const Comp = {
render() {
return h('h1', 'Async component')
)
})
+ // #3787
+ test('unmount async wrapper before load', async () => {
+ let resolve: any
+ const AsyncComp = defineAsyncComponent(
+ () =>
+ new Promise(r => {
+ resolve = r
+ })
+ )
+
+ const show = ref(true)
+ const root = document.createElement('div')
+ root.innerHTML = '<div><div>async</div></div>'
+
+ createSSRApp({
+ render() {
+ return h('div', [show.value ? h(AsyncComp) : h('div', 'hi')])
+ }
+ }).mount(root)
+
+ show.value = false
+ await nextTick()
+ expect(root.innerHTML).toBe('<div><div>hi</div></div>')
+ resolve({})
+ })
+
+ test('unmount async wrapper before load (fragment)', async () => {
+ let resolve: any
+ const AsyncComp = defineAsyncComponent(
+ () =>
+ new Promise(r => {
+ resolve = r
+ })
+ )
+
+ const show = ref(true)
+ const root = document.createElement('div')
+ root.innerHTML = '<div><!--[-->async<!--]--></div>'
+
+ createSSRApp({
+ render() {
+ return h('div', [show.value ? h(AsyncComp) : h('div', 'hi')])
+ }
+ }).mount(root)
+
+ show.value = false
+ await nextTick()
+ expect(root.innerHTML).toBe('<div><div>hi</div></div>')
+ resolve({})
+ })
+
test('elements with camel-case in svg ', () => {
const { vnode, container } = mountWithHydration(
'<animateTransform></animateTransform>',
Comment,
Static,
Fragment,
- VNodeHook
+ VNodeHook,
+ createVNode,
+ createTextVNode
} from './vnode'
import { flushPostFlushCbs } from './scheduler'
import { ComponentInternalInstance } from './component'
queueEffectWithSuspense
} from './components/Suspense'
import { TeleportImpl, TeleportVNode } from './components/Teleport'
+import { isAsyncWrapper } from './apiAsyncComponent'
export type RootHydrateFunction = (
vnode: VNode<Node, Element>,
isSVGContainer(container),
optimized
)
+
// component may be async, so in the case of fragments we cannot rely
// on component's rendered output to determine the end of the fragment
// instead, we do a lookahead to find the end anchor node.
nextNode = isFragmentStart
? locateClosingAsyncAnchor(node)
: nextSibling(node)
+
+ // #3787
+ // if component is async, it may get moved / unmounted before its
+ // inner component is loaded, so we need to give it a placeholder
+ // vnode that matches its adopted DOM.
+ if (isAsyncWrapper(vnode)) {
+ let subTree
+ if (isFragmentStart) {
+ subTree = createVNode(Fragment)
+ subTree.anchor = nextNode
+ ? nextNode.previousSibling
+ : container.lastChild
+ } else {
+ subTree =
+ node.nodeType === 3 ? createTextVNode('') : createVNode('div')
+ }
+ subTree.el = node
+ vnode.component!.subTree = subTree
+ }
} else if (shapeFlag & ShapeFlags.TELEPORT) {
if (domType !== DOMNodeTypes.COMMENT) {
nextNode = onMismatch()
// which means it won't track dependencies - but it's ok because
// a server-rendered async wrapper is already in resolved state
// and it will never need to change.
- hydrateSubTree
+ () => !instance.isUnmounted && hydrateSubTree()
)
} else {
hydrateSubTree()