Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions packages/runtime-core/__tests__/components/Suspense.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2570,6 +2570,44 @@ describe('Suspense', () => {
expect(serializeInner(target)).toBe(``)
})

// #14701
test('should not crash when moving disabled teleport with component children inside suspense', async () => {
const target = nodeOps.createElement('div')

const Comp = {
render() {
return h('div', 'comp')
},
}

const Async = defineAsyncComponent({
render() {
// Multi-root fragment: element + disabled teleport with component child
return [
h('div', 'content'),
h(Teleport, { to: target, disabled: true }, h(Comp)),
]
},
})

const root = nodeOps.createElement('div')
render(
h(Suspense, null, {
default: h(Async),
fallback: h('div', 'fallback'),
}),
root,
)
expect(serializeInner(root)).toBe(`<div>fallback</div>`)

await Promise.all(deps)
await nextTick()
await nextTick()
expect(serializeInner(root)).toBe(
`<div>content</div><!--teleport start--><div>comp</div><!--teleport end-->`,
)
})

//#11617
test('update async component before resolve then update again', async () => {
const arr: boolean[] = []
Expand Down
11 changes: 8 additions & 3 deletions packages/runtime-core/src/components/Teleport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ export const TeleportImpl = {
mc: mountChildren,
pc: patchChildren,
pbc: patchBlockChildren,
o: { insert, querySelector, createText, createComment },
o: { insert, querySelector, createText, createComment, parentNode },
} = internals

const disabled = isTeleportDisabled(n2.props)
Expand Down Expand Up @@ -162,7 +162,11 @@ export const TeleportImpl = {
if (pendingMounts.get(vnode) !== mountJob) return
pendingMounts.delete(vnode)
if (isTeleportDisabled(vnode.props)) {
mount(vnode, container, vnode.anchor!)
// Use the current parent of the placeholder instead of the
// captured `container`, which may be stale if Suspense has moved
// the branch to a different container during resolve.
const mountContainer = parentNode(vnode.el!) || container
mount(vnode, mountContainer, vnode.anchor!)
updateCssVars(vnode, true)
}
mountToTarget(vnode)
Expand Down Expand Up @@ -389,7 +393,8 @@ function moveTeleport(
// if this is a re-order and teleport is enabled (content is in target)
// do not move children. So the opposite is: only move children if this
// is not a reorder, or the teleport is disabled
if (!isReorder || isTeleportDisabled(props)) {
// #14701 don't move children if in pending mount
if (!pendingMounts.has(vnode) && (!isReorder || isTeleportDisabled(props))) {
// Teleport has either Array children or no children.
if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
for (let i = 0; i < (children as VNode[]).length; i++) {
Expand Down
Loading