expect(teleportContainer.innerHTML).toBe('')
})
+ test('Teleport unmount (disabled + full integration)', async () => {
+ const disabled = ref(true)
+ const target = ref('#teleport001')
+ const toggle = ref(true)
+
+ const Comp = {
+ template: `
+ <div>
+ <div id="teleport001">
+ <Teleport
+ :to="target"
+ :disabled="disabled"
+ >
+ <template v-for="section in order">
+ <div>{{section}}</div>
+ </template>
+ </Teleport>
+ </div>
+ <div id="teleport002"></div>
+ </div>
+ `,
+ setup() {
+ const order = ref(['A', 'B', 'C'])
+ return { target, disabled, order }
+ },
+ }
+ const App = {
+ template: `<Comp v-if="toggle"/>`,
+ components: {
+ Comp,
+ },
+ setup() {
+ return { toggle }
+ },
+ }
+
+ const container = document.createElement('div')
+ document.body.appendChild(container)
+
+ // server render
+ container.innerHTML = await renderToString(h(App))
+ expect(container.innerHTML).toBe(
+ `<div>` +
+ `<div id="teleport001">` +
+ `<!--teleport start-->` +
+ `<!--[--><div>A</div><div>B</div><div>C</div><!--]-->` +
+ `<!--teleport end-->` +
+ `</div>` +
+ `<div id="teleport002"></div>` +
+ `</div>`,
+ )
+
+ // hydrate
+ createSSRApp(App).mount(container)
+ expect(`Hydration children mismatch`).not.toHaveBeenWarned()
+
+ target.value = '#teleport002'
+ disabled.value = false
+ await nextTick()
+ expect(container.querySelector('#teleport001')!.innerHTML).toBe(
+ '<!--teleport start--><!--teleport end-->',
+ )
+ expect(container.querySelector('#teleport002')!.innerHTML).toBe(
+ '<!--[--><div>A</div><div>B</div><div>C</div><!--]-->',
+ )
+
+ toggle.value = false
+ await nextTick()
+ expect(container.innerHTML).toBe('<!--v-if-->')
+ })
+
test('Teleport target change (mismatch + full integration)', async () => {
const target = ref('#target1')
const Comp = {
optimized: boolean,
) => Node | null,
): Node | null {
- function hydrateDisabledTeleport(
- node: Node,
- vnode: VNode,
- targetStart: Node | null,
- targetAnchor: Node | null,
+ // lookahead until we find the target anchor
+ // we cannot rely on return value of hydrateChildren() because there
+ // could be nested teleports
+ function hydrateAnchor(
+ target: TeleportTargetElement,
+ targetNode: Node | null,
) {
+ let targetAnchor = targetNode
+ while (targetAnchor) {
+ if (targetAnchor && targetAnchor.nodeType === 8) {
+ if ((targetAnchor as Comment).data === 'teleport start anchor') {
+ vnode.targetStart = targetAnchor
+ } else if ((targetAnchor as Comment).data === 'teleport anchor') {
+ vnode.targetAnchor = targetAnchor
+ target._lpa =
+ vnode.targetAnchor && nextSibling(vnode.targetAnchor as Node)
+ break
+ }
+ }
+ targetAnchor = nextSibling(targetAnchor)
+ }
+ }
+
+ function hydrateDisabledTeleport(node: Node, vnode: VNode) {
vnode.anchor = hydrateChildren(
nextSibling(node),
vnode,
slotScopeIds,
optimized,
)
- vnode.targetStart = targetStart
- vnode.targetAnchor = targetAnchor
}
const target = (vnode.target = resolveTarget<Element>(
(target as TeleportTargetElement)._lpa || target.firstChild
if (vnode.shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
if (disabled) {
- hydrateDisabledTeleport(
- node,
- vnode,
- targetNode,
- targetNode && nextSibling(targetNode),
- )
+ hydrateDisabledTeleport(node, vnode)
+ hydrateAnchor(target as TeleportTargetElement, targetNode)
+ if (!vnode.targetAnchor) {
+ prepareAnchor(
+ target,
+ vnode,
+ createText,
+ insert,
+ // if target is the same as the main view, insert anchors before current node
+ // to avoid hydrating mismatch
+ parentNode(node)! === target ? node : null,
+ )
+ }
} else {
vnode.anchor = nextSibling(node)
-
- // lookahead until we find the target anchor
- // we cannot rely on return value of hydrateChildren() because there
- // could be nested teleports
- let targetAnchor = targetNode
- while (targetAnchor) {
- if (targetAnchor && targetAnchor.nodeType === 8) {
- if ((targetAnchor as Comment).data === 'teleport start anchor') {
- vnode.targetStart = targetAnchor
- } else if ((targetAnchor as Comment).data === 'teleport anchor') {
- vnode.targetAnchor = targetAnchor
- ;(target as TeleportTargetElement)._lpa =
- vnode.targetAnchor && nextSibling(vnode.targetAnchor as Node)
- break
- }
- }
- targetAnchor = nextSibling(targetAnchor)
- }
-
+ hydrateAnchor(target as TeleportTargetElement, targetNode)
// #11400 if the HTML corresponding to Teleport is not embedded in the
// correct position on the final page during SSR. the targetAnchor will
// always be null, we need to manually add targetAnchor to ensure
updateCssVars(vnode, disabled)
} else if (disabled) {
if (vnode.shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
- hydrateDisabledTeleport(node, vnode, node, nextSibling(node))
+ hydrateDisabledTeleport(node, vnode)
+ vnode.targetStart = node
+ vnode.targetAnchor = nextSibling(node)
}
}
return vnode.anchor && nextSibling(vnode.anchor as Node)
vnode: TeleportVNode,
createText: RendererOptions['createText'],
insert: RendererOptions['insert'],
+ anchor: RendererNode | null = null,
) {
const targetStart = (vnode.targetStart = createText(''))
const targetAnchor = (vnode.targetAnchor = createText(''))
targetStart[TeleportEndKey] = targetAnchor
if (target) {
- insert(targetStart, target)
- insert(targetAnchor, target)
+ insert(targetStart, target, anchor)
+ insert(targetAnchor, target, anchor)
}
return targetAnchor