Text,
createApp,
defineComponent,
- h,
markRaw,
nextTick,
nodeOps,
+ h as originalH,
ref,
render,
serializeInner,
withDirectives,
} from '@vue/runtime-test'
import { Fragment, createCommentVNode, createVNode } from '../../src/vnode'
-import { compile, render as domRender } from 'vue'
+import { compile, createApp as createDOMApp, render as domRender } from 'vue'
describe('renderer: teleport', () => {
- test('should work', () => {
- const target = nodeOps.createElement('div')
- const root = nodeOps.createElement('div')
-
- render(
- h(() => [
- h(Teleport, { to: target }, h('div', 'teleported')),
- h('div', 'root'),
- ]),
- root,
- )
-
- expect(serializeInner(root)).toMatchInlineSnapshot(
- `"<!--teleport start--><!--teleport end--><div>root</div>"`,
- )
- expect(serializeInner(target)).toMatchInlineSnapshot(
- `"<div>teleported</div>"`,
- )
+ describe('eager mode', () => {
+ runSharedTests(false)
})
- test('should work with SVG', async () => {
- const root = document.createElement('div')
- const svg = ref()
- const circle = ref()
+ describe('defer mode', () => {
+ runSharedTests(true)
- const Comp = defineComponent({
- setup() {
- return {
- svg,
- circle,
- }
- },
- template: `
- <svg ref="svg"></svg>
- <teleport :to="svg" v-if="svg">
- <circle ref="circle"></circle>
- </teleport>`,
- })
+ const h = originalH
- domRender(h(Comp), root)
+ test('should be able to target content appearing later than the teleport with defer', () => {
+ const root = document.createElement('div')
+ document.body.appendChild(root)
- await nextTick()
+ createDOMApp({
+ render() {
+ return [
+ h(Teleport, { to: '#target', defer: true }, h('div', 'teleported')),
+ h('div', { id: 'target' }),
+ ]
+ },
+ }).mount(root)
- expect(root.innerHTML).toMatchInlineSnapshot(
- `"<svg><circle></circle></svg><!--teleport start--><!--teleport end-->"`,
- )
+ expect(root.innerHTML).toMatchInlineSnapshot(
+ `"<!--teleport start--><!--teleport end--><div id="target"><div>teleported</div></div>"`,
+ )
+ })
- expect(svg.value.namespaceURI).toBe('http://www.w3.org/2000/svg')
- expect(circle.value.namespaceURI).toBe('http://www.w3.org/2000/svg')
+ test('defer mode should work inside suspense', async () => {
+ const root = document.createElement('div')
+ document.body.appendChild(root)
+
+ let p: Promise<any>
+
+ const Comp = defineComponent({
+ template: `
+ <suspense>
+ <div>
+ <async />
+ <teleport defer to="#target-suspense">
+ <div ref="tel">teleported</div>
+ </teleport>
+ <div id="target-suspense" />
+ </div>
+ </suspense>`,
+ components: {
+ async: {
+ setup() {
+ p = Promise.resolve(() => 'async')
+ return p
+ },
+ },
+ },
+ })
+
+ domRender(h(Comp), root)
+ expect(root.innerHTML).toBe(`<!---->`)
+
+ await p!.then(() => Promise.resolve())
+ await nextTick()
+ expect(root.innerHTML).toBe(
+ `<div>` +
+ `async` +
+ `<!--teleport start--><!--teleport end-->` +
+ `<div id="target-suspense"><div>teleported</div></div>` +
+ `</div>`,
+ )
+ })
})
- test('should update target', async () => {
- const targetA = nodeOps.createElement('div')
- const targetB = nodeOps.createElement('div')
- const target = ref(targetA)
- const root = nodeOps.createElement('div')
-
- render(
- h(() => [
- h(Teleport, { to: target.value }, h('div', 'teleported')),
- h('div', 'root'),
- ]),
- root,
- )
-
- expect(serializeInner(root)).toMatchInlineSnapshot(
- `"<!--teleport start--><!--teleport end--><div>root</div>"`,
- )
- expect(serializeInner(targetA)).toMatchInlineSnapshot(
- `"<div>teleported</div>"`,
- )
- expect(serializeInner(targetB)).toMatchInlineSnapshot(`""`)
-
- target.value = targetB
- await nextTick()
-
- expect(serializeInner(root)).toMatchInlineSnapshot(
- `"<!--teleport start--><!--teleport end--><div>root</div>"`,
- )
- expect(serializeInner(targetA)).toMatchInlineSnapshot(`""`)
- expect(serializeInner(targetB)).toMatchInlineSnapshot(
- `"<div>teleported</div>"`,
- )
- })
+ function runSharedTests(deferMode: boolean) {
+ const h = (deferMode
+ ? (type: any, props: any, ...args: any[]) => {
+ if (type === Teleport) {
+ props.defer = true
+ }
+ return originalH(type, props, ...args)
+ }
+ : originalH) as unknown as typeof originalH
- test('should update children', async () => {
- const target = nodeOps.createElement('div')
- const root = nodeOps.createElement('div')
- const children = ref([h('div', 'teleported')])
+ test('should work', () => {
+ const target = nodeOps.createElement('div')
+ const root = nodeOps.createElement('div')
- render(
- h(() => h(Teleport, { to: target }, children.value)),
- root,
- )
- expect(serializeInner(target)).toMatchInlineSnapshot(
- `"<div>teleported</div>"`,
- )
+ render(
+ h(() => [
+ h(Teleport, { to: target }, h('div', 'teleported')),
+ h('div', 'root'),
+ ]),
+ root,
+ )
- children.value = []
- await nextTick()
+ expect(serializeInner(root)).toMatchInlineSnapshot(
+ `"<!--teleport start--><!--teleport end--><div>root</div>"`,
+ )
+ expect(serializeInner(target)).toMatchInlineSnapshot(
+ `"<div>teleported</div>"`,
+ )
+ })
- expect(serializeInner(target)).toMatchInlineSnapshot(`""`)
+ test('should work with SVG', async () => {
+ const root = document.createElement('div')
+ const svg = ref()
+ const circle = ref()
+
+ const Comp = defineComponent({
+ setup() {
+ return {
+ svg,
+ circle,
+ }
+ },
+ template: `
+ <svg ref="svg"></svg>
+ <teleport :to="svg" v-if="svg">
+ <circle ref="circle"></circle>
+ </teleport>`,
+ })
+
+ domRender(h(Comp), root)
+
+ await nextTick()
+
+ expect(root.innerHTML).toMatchInlineSnapshot(
+ `"<svg><circle></circle></svg><!--teleport start--><!--teleport end-->"`,
+ )
- children.value = [createVNode(Text, null, 'teleported')]
- await nextTick()
+ expect(svg.value.namespaceURI).toBe('http://www.w3.org/2000/svg')
+ expect(circle.value.namespaceURI).toBe('http://www.w3.org/2000/svg')
+ })
- expect(serializeInner(target)).toMatchInlineSnapshot(`"teleported"`)
- })
+ test('should update target', async () => {
+ const targetA = nodeOps.createElement('div')
+ const targetB = nodeOps.createElement('div')
+ const target = ref(targetA)
+ const root = nodeOps.createElement('div')
- test('should remove children when unmounted', () => {
- const target = nodeOps.createElement('div')
- const root = nodeOps.createElement('div')
+ render(
+ h(() => [
+ h(Teleport, { to: target.value }, h('div', 'teleported')),
+ h('div', 'root'),
+ ]),
+ root,
+ )
+
+ expect(serializeInner(root)).toMatchInlineSnapshot(
+ `"<!--teleport start--><!--teleport end--><div>root</div>"`,
+ )
+ expect(serializeInner(targetA)).toMatchInlineSnapshot(
+ `"<div>teleported</div>"`,
+ )
+ expect(serializeInner(targetB)).toMatchInlineSnapshot(`""`)
+
+ target.value = targetB
+ await nextTick()
+
+ expect(serializeInner(root)).toMatchInlineSnapshot(
+ `"<!--teleport start--><!--teleport end--><div>root</div>"`,
+ )
+ expect(serializeInner(targetA)).toMatchInlineSnapshot(`""`)
+ expect(serializeInner(targetB)).toMatchInlineSnapshot(
+ `"<div>teleported</div>"`,
+ )
+ })
+
+ test('should update children', async () => {
+ const target = nodeOps.createElement('div')
+ const root = nodeOps.createElement('div')
+ const children = ref([h('div', 'teleported')])
- function testUnmount(props: any) {
render(
- h(() => [h(Teleport, props, h('div', 'teleported')), h('div', 'root')]),
+ h(() => h(Teleport, { to: target }, children.value)),
root,
)
expect(serializeInner(target)).toMatchInlineSnapshot(
- props.disabled ? `""` : `"<div>teleported</div>"`,
+ `"<div>teleported</div>"`,
+ )
+
+ children.value = []
+ await nextTick()
+
+ expect(serializeInner(target)).toMatchInlineSnapshot(`""`)
+
+ children.value = [createVNode(Text, null, 'teleported')]
+ await nextTick()
+
+ expect(serializeInner(target)).toMatchInlineSnapshot(`"teleported"`)
+ })
+
+ test('should remove children when unmounted', () => {
+ const target = nodeOps.createElement('div')
+ const root = nodeOps.createElement('div')
+
+ function testUnmount(props: any) {
+ render(
+ h(() => [
+ h(Teleport, props, h('div', 'teleported')),
+ h('div', 'root'),
+ ]),
+ root,
+ )
+ expect(serializeInner(target)).toMatchInlineSnapshot(
+ props.disabled ? `""` : `"<div>teleported</div>"`,
+ )
+
+ render(null, root)
+ expect(serializeInner(target)).toBe('')
+ expect(target.children.length).toBe(0)
+ }
+
+ testUnmount({ to: target, disabled: false })
+ testUnmount({ to: target, disabled: true })
+ testUnmount({ to: null, disabled: true })
+ })
+
+ test('component with multi roots should be removed when unmounted', () => {
+ const target = nodeOps.createElement('div')
+ const root = nodeOps.createElement('div')
+
+ const Comp = {
+ render() {
+ return [h('p'), h('p')]
+ },
+ }
+
+ render(
+ h(() => [h(Teleport, { to: target }, h(Comp)), h('div', 'root')]),
+ root,
)
+ expect(serializeInner(target)).toMatchInlineSnapshot(`"<p></p><p></p>"`)
render(null, root)
expect(serializeInner(target)).toBe('')
- expect(target.children.length).toBe(0)
- }
+ })
- testUnmount({ to: target, disabled: false })
- testUnmount({ to: target, disabled: true })
- testUnmount({ to: null, disabled: true })
- })
+ // #6347
+ test('descendent component should be unmounted when teleport is disabled and unmounted', () => {
+ const root = nodeOps.createElement('div')
- test('component with multi roots should be removed when unmounted', () => {
- const target = nodeOps.createElement('div')
- const root = nodeOps.createElement('div')
+ const CompWithHook = {
+ render() {
+ return [h('p'), h('p')]
+ },
+ beforeUnmount: vi.fn(),
+ unmounted: vi.fn(),
+ }
- const Comp = {
- render() {
- return [h('p'), h('p')]
- },
- }
+ render(
+ h(() => [h(Teleport, { to: null, disabled: true }, h(CompWithHook))]),
+ root,
+ )
+ expect(CompWithHook.beforeUnmount).toBeCalledTimes(0)
+ expect(CompWithHook.unmounted).toBeCalledTimes(0)
- render(
- h(() => [h(Teleport, { to: target }, h(Comp)), h('div', 'root')]),
- root,
- )
- expect(serializeInner(target)).toMatchInlineSnapshot(`"<p></p><p></p>"`)
+ render(null, root)
- render(null, root)
- expect(serializeInner(target)).toBe('')
- })
+ expect(CompWithHook.beforeUnmount).toBeCalledTimes(1)
+ expect(CompWithHook.unmounted).toBeCalledTimes(1)
+ })
- // #6347
- test('descendent component should be unmounted when teleport is disabled and unmounted', () => {
- const root = nodeOps.createElement('div')
-
- const CompWithHook = {
- render() {
- return [h('p'), h('p')]
- },
- beforeUnmount: vi.fn(),
- unmounted: vi.fn(),
- }
-
- render(
- h(() => [h(Teleport, { to: null, disabled: true }, h(CompWithHook))]),
- root,
- )
- expect(CompWithHook.beforeUnmount).toBeCalledTimes(0)
- expect(CompWithHook.unmounted).toBeCalledTimes(0)
-
- render(null, root)
-
- expect(CompWithHook.beforeUnmount).toBeCalledTimes(1)
- expect(CompWithHook.unmounted).toBeCalledTimes(1)
- })
+ test('multiple teleport with same target', () => {
+ const target = nodeOps.createElement('div')
+ const root = nodeOps.createElement('div')
- test('multiple teleport with same target', () => {
- const target = nodeOps.createElement('div')
- const root = nodeOps.createElement('div')
-
- render(
- h('div', [
- h(Teleport, { to: target }, h('div', 'one')),
- h(Teleport, { to: target }, 'two'),
- ]),
- root,
- )
-
- expect(serializeInner(root)).toMatchInlineSnapshot(
- `"<div><!--teleport start--><!--teleport end--><!--teleport start--><!--teleport end--></div>"`,
- )
- expect(serializeInner(target)).toMatchInlineSnapshot(`"<div>one</div>two"`)
-
- // update existing content
- render(
- h('div', [
- h(Teleport, { to: target }, [h('div', 'one'), h('div', 'two')]),
- h(Teleport, { to: target }, 'three'),
- ]),
- root,
- )
- expect(serializeInner(target)).toMatchInlineSnapshot(
- `"<div>one</div><div>two</div>three"`,
- )
-
- // toggling
- render(h('div', [null, h(Teleport, { to: target }, 'three')]), root)
- expect(serializeInner(root)).toMatchInlineSnapshot(
- `"<div><!----><!--teleport start--><!--teleport end--></div>"`,
- )
- expect(serializeInner(target)).toMatchInlineSnapshot(`"three"`)
-
- // toggle back
- render(
- h('div', [
- h(Teleport, { to: target }, [h('div', 'one'), h('div', 'two')]),
- h(Teleport, { to: target }, 'three'),
- ]),
- root,
- )
- expect(serializeInner(root)).toMatchInlineSnapshot(
- `"<div><!--teleport start--><!--teleport end--><!--teleport start--><!--teleport end--></div>"`,
- )
- // should append
- expect(serializeInner(target)).toMatchInlineSnapshot(
- `"three<div>one</div><div>two</div>"`,
- )
-
- // toggle the other teleport
- render(
- h('div', [
- h(Teleport, { to: target }, [h('div', 'one'), h('div', 'two')]),
- null,
- ]),
- root,
- )
- expect(serializeInner(root)).toMatchInlineSnapshot(
- `"<div><!--teleport start--><!--teleport end--><!----></div>"`,
- )
- expect(serializeInner(target)).toMatchInlineSnapshot(
- `"<div>one</div><div>two</div>"`,
- )
- })
+ render(
+ h('div', [
+ h(Teleport, { to: target }, h('div', 'one')),
+ h(Teleport, { to: target }, 'two'),
+ ]),
+ root,
+ )
- test('should work when using template ref as target', async () => {
- const root = nodeOps.createElement('div')
- const target = ref(null)
- const disabled = ref(true)
-
- const App = {
- setup() {
- return () =>
- h(Fragment, [
- h('div', { ref: target }),
- h(
- Teleport,
- { to: target.value, disabled: disabled.value },
- h('div', 'teleported'),
- ),
- ])
- },
- }
- render(h(App), root)
- expect(serializeInner(root)).toMatchInlineSnapshot(
- `"<div></div><!--teleport start--><div>teleported</div><!--teleport end-->"`,
- )
-
- disabled.value = false
- await nextTick()
- expect(serializeInner(root)).toMatchInlineSnapshot(
- `"<div><div>teleported</div></div><!--teleport start--><!--teleport end-->"`,
- )
- })
+ expect(serializeInner(root)).toMatchInlineSnapshot(
+ `"<div><!--teleport start--><!--teleport end--><!--teleport start--><!--teleport end--></div>"`,
+ )
+ expect(serializeInner(target)).toMatchInlineSnapshot(
+ `"<div>one</div>two"`,
+ )
- test('disabled', () => {
- const target = nodeOps.createElement('div')
- const root = nodeOps.createElement('div')
-
- const renderWithDisabled = (disabled: boolean) => {
- return h(Fragment, [
- h(Teleport, { to: target, disabled }, h('div', 'teleported')),
- h('div', 'root'),
- ])
- }
-
- render(renderWithDisabled(false), root)
- expect(serializeInner(root)).toMatchInlineSnapshot(
- `"<!--teleport start--><!--teleport end--><div>root</div>"`,
- )
- expect(serializeInner(target)).toMatchInlineSnapshot(
- `"<div>teleported</div>"`,
- )
-
- render(renderWithDisabled(true), root)
- expect(serializeInner(root)).toMatchInlineSnapshot(
- `"<!--teleport start--><div>teleported</div><!--teleport end--><div>root</div>"`,
- )
- expect(serializeInner(target)).toBe(``)
-
- // toggle back
- render(renderWithDisabled(false), root)
- expect(serializeInner(root)).toMatchInlineSnapshot(
- `"<!--teleport start--><!--teleport end--><div>root</div>"`,
- )
- expect(serializeInner(target)).toMatchInlineSnapshot(
- `"<div>teleported</div>"`,
- )
- })
+ // update existing content
+ render(
+ h('div', [
+ h(Teleport, { to: target }, [h('div', 'one'), h('div', 'two')]),
+ h(Teleport, { to: target }, 'three'),
+ ]),
+ root,
+ )
+ expect(serializeInner(target)).toMatchInlineSnapshot(
+ `"<div>one</div><div>two</div>three"`,
+ )
- test('moving teleport while enabled', () => {
- const target = nodeOps.createElement('div')
- const root = nodeOps.createElement('div')
-
- render(
- h(Fragment, [
- h(Teleport, { to: target }, h('div', 'teleported')),
- h('div', 'root'),
- ]),
- root,
- )
- expect(serializeInner(root)).toMatchInlineSnapshot(
- `"<!--teleport start--><!--teleport end--><div>root</div>"`,
- )
- expect(serializeInner(target)).toMatchInlineSnapshot(
- `"<div>teleported</div>"`,
- )
-
- render(
- h(Fragment, [
- h('div', 'root'),
- h(Teleport, { to: target }, h('div', 'teleported')),
- ]),
- root,
- )
- expect(serializeInner(root)).toMatchInlineSnapshot(
- `"<div>root</div><!--teleport start--><!--teleport end-->"`,
- )
- expect(serializeInner(target)).toMatchInlineSnapshot(
- `"<div>teleported</div>"`,
- )
-
- render(
- h(Fragment, [
- h(Teleport, { to: target }, h('div', 'teleported')),
- h('div', 'root'),
- ]),
- root,
- )
- expect(serializeInner(root)).toMatchInlineSnapshot(
- `"<!--teleport start--><!--teleport end--><div>root</div>"`,
- )
- expect(serializeInner(target)).toMatchInlineSnapshot(
- `"<div>teleported</div>"`,
- )
- })
+ // toggling
+ render(h('div', [null, h(Teleport, { to: target }, 'three')]), root)
+ expect(serializeInner(root)).toMatchInlineSnapshot(
+ `"<div><!----><!--teleport start--><!--teleport end--></div>"`,
+ )
+ expect(serializeInner(target)).toMatchInlineSnapshot(`"three"`)
- test('moving teleport while disabled', () => {
- const target = nodeOps.createElement('div')
- const root = nodeOps.createElement('div')
-
- render(
- h(Fragment, [
- h(Teleport, { to: target, disabled: true }, h('div', 'teleported')),
- h('div', 'root'),
- ]),
- root,
- )
- expect(serializeInner(root)).toMatchInlineSnapshot(
- `"<!--teleport start--><div>teleported</div><!--teleport end--><div>root</div>"`,
- )
- expect(serializeInner(target)).toBe('')
-
- render(
- h(Fragment, [
- h('div', 'root'),
- h(Teleport, { to: target, disabled: true }, h('div', 'teleported')),
- ]),
- root,
- )
- expect(serializeInner(root)).toMatchInlineSnapshot(
- `"<div>root</div><!--teleport start--><div>teleported</div><!--teleport end-->"`,
- )
- expect(serializeInner(target)).toBe('')
-
- render(
- h(Fragment, [
- h(Teleport, { to: target, disabled: true }, h('div', 'teleported')),
- h('div', 'root'),
- ]),
- root,
- )
- expect(serializeInner(root)).toMatchInlineSnapshot(
- `"<!--teleport start--><div>teleported</div><!--teleport end--><div>root</div>"`,
- )
- expect(serializeInner(target)).toBe('')
- })
+ // toggle back
+ render(
+ h('div', [
+ h(Teleport, { to: target }, [h('div', 'one'), h('div', 'two')]),
+ h(Teleport, { to: target }, 'three'),
+ ]),
+ root,
+ )
+ expect(serializeInner(root)).toMatchInlineSnapshot(
+ `"<div><!--teleport start--><!--teleport end--><!--teleport start--><!--teleport end--></div>"`,
+ )
+ // should append
+ expect(serializeInner(target)).toMatchInlineSnapshot(
+ `"three<div>one</div><div>two</div>"`,
+ )
- test('should work with block tree', async () => {
- const target = nodeOps.createElement('div')
- const root = nodeOps.createElement('div')
- const disabled = ref(false)
+ // toggle the other teleport
+ render(
+ h('div', [
+ h(Teleport, { to: target }, [h('div', 'one'), h('div', 'two')]),
+ null,
+ ]),
+ root,
+ )
+ expect(serializeInner(root)).toMatchInlineSnapshot(
+ `"<div><!--teleport start--><!--teleport end--><!----></div>"`,
+ )
+ expect(serializeInner(target)).toMatchInlineSnapshot(
+ `"<div>one</div><div>two</div>"`,
+ )
+ })
- const App = {
- setup() {
- return {
- target: markRaw(target),
- disabled,
- }
- },
- render: compile(`
- <teleport :to="target" :disabled="disabled">
- <div>teleported</div><span>{{ disabled }}</span><span v-if="disabled"/>
- </teleport>
- <div>root</div>
- `),
- }
- render(h(App), root)
- expect(serializeInner(root)).toMatchInlineSnapshot(
- `"<!--teleport start--><!--teleport end--><div>root</div>"`,
- )
- expect(serializeInner(target)).toMatchInlineSnapshot(
- `"<div>teleported</div><span>false</span><!--v-if-->"`,
- )
-
- disabled.value = true
- await nextTick()
- expect(serializeInner(root)).toMatchInlineSnapshot(
- `"<!--teleport start--><div>teleported</div><span>true</span><span></span><!--teleport end--><div>root</div>"`,
- )
- expect(serializeInner(target)).toBe(``)
-
- // toggle back
- disabled.value = false
- await nextTick()
- expect(serializeInner(root)).toMatchInlineSnapshot(
- `"<!--teleport start--><!--teleport end--><div>root</div>"`,
- )
- expect(serializeInner(target)).toMatchInlineSnapshot(
- `"<div>teleported</div><span>false</span><!--v-if-->"`,
- )
- })
+ test('should work when using template ref as target', async () => {
+ const root = nodeOps.createElement('div')
+ const target = ref(null)
+ const disabled = ref(true)
+
+ const App = {
+ setup() {
+ return () =>
+ h(Fragment, [
+ h('div', { ref: target }),
+ h(
+ Teleport,
+ { to: target.value, disabled: disabled.value },
+ h('div', 'teleported'),
+ ),
+ ])
+ },
+ }
+ render(h(App), root)
+ expect(serializeInner(root)).toMatchInlineSnapshot(
+ `"<div></div><!--teleport start--><div>teleported</div><!--teleport end-->"`,
+ )
- // #3497
- test(`the dir hooks of the Teleport's children should be called correctly`, async () => {
- const target = nodeOps.createElement('div')
- const root = nodeOps.createElement('div')
- const toggle = ref(true)
- const dir = {
- mounted: vi.fn(),
- unmounted: vi.fn(),
- }
-
- const app = createApp({
- setup() {
- return () => {
- return toggle.value
- ? h(Teleport, { to: target }, [
- withDirectives(h('div', ['foo']), [[dir]]),
- ])
- : null
- }
- },
+ disabled.value = false
+ await nextTick()
+ expect(serializeInner(root)).toMatchInlineSnapshot(
+ `"<div><div>teleported</div></div><!--teleport start--><!--teleport end-->"`,
+ )
})
- app.mount(root)
-
- expect(serializeInner(root)).toMatchInlineSnapshot(
- `"<!--teleport start--><!--teleport end-->"`,
- )
- expect(serializeInner(target)).toMatchInlineSnapshot(`"<div>foo</div>"`)
- expect(dir.mounted).toHaveBeenCalledTimes(1)
- expect(dir.unmounted).toHaveBeenCalledTimes(0)
-
- toggle.value = false
- await nextTick()
- expect(serializeInner(root)).toMatchInlineSnapshot(`"<!---->"`)
- expect(serializeInner(target)).toMatchInlineSnapshot(`""`)
- expect(dir.mounted).toHaveBeenCalledTimes(1)
- expect(dir.unmounted).toHaveBeenCalledTimes(1)
- })
- // #7835
- test(`ensure that target changes when disabled are updated correctly when enabled`, async () => {
- const root = nodeOps.createElement('div')
- const target1 = nodeOps.createElement('div')
- const target2 = nodeOps.createElement('div')
- const target3 = nodeOps.createElement('div')
- const target = ref(target1)
- const disabled = ref(true)
-
- const App = {
- setup() {
- return () =>
- h(Fragment, [
- h(
- Teleport,
- { to: target.value, disabled: disabled.value },
- h('div', 'teleported'),
- ),
- ])
- },
- }
- render(h(App), root)
- disabled.value = false
- await nextTick()
- expect(serializeInner(target1)).toMatchInlineSnapshot(
- `"<div>teleported</div>"`,
- )
- expect(serializeInner(target2)).toMatchInlineSnapshot(`""`)
- expect(serializeInner(target3)).toMatchInlineSnapshot(`""`)
-
- disabled.value = true
- await nextTick()
- target.value = target2
- await nextTick()
- expect(serializeInner(target1)).toMatchInlineSnapshot(`""`)
- expect(serializeInner(target2)).toMatchInlineSnapshot(`""`)
- expect(serializeInner(target3)).toMatchInlineSnapshot(`""`)
-
- target.value = target3
- await nextTick()
- expect(serializeInner(target1)).toMatchInlineSnapshot(`""`)
- expect(serializeInner(target2)).toMatchInlineSnapshot(`""`)
- expect(serializeInner(target3)).toMatchInlineSnapshot(`""`)
-
- disabled.value = false
- await nextTick()
- expect(serializeInner(target1)).toMatchInlineSnapshot(`""`)
- expect(serializeInner(target2)).toMatchInlineSnapshot(`""`)
- expect(serializeInner(target3)).toMatchInlineSnapshot(
- `"<div>teleported</div>"`,
- )
- })
+ test('disabled', () => {
+ const target = nodeOps.createElement('div')
+ const root = nodeOps.createElement('div')
- //#9071
- test('toggle sibling node inside target node', async () => {
- const root = document.createElement('div')
- const show = ref(false)
- const App = defineComponent({
- setup() {
- return () => {
- return show.value
- ? h(Teleport, { to: root }, [h('div', 'teleported')])
- : h('div', 'foo')
- }
- },
+ const renderWithDisabled = (disabled: boolean) => {
+ return h(Fragment, [
+ h(Teleport, { to: target, disabled }, h('div', 'teleported')),
+ h('div', 'root'),
+ ])
+ }
+
+ render(renderWithDisabled(false), root)
+ expect(serializeInner(root)).toMatchInlineSnapshot(
+ `"<!--teleport start--><!--teleport end--><div>root</div>"`,
+ )
+ expect(serializeInner(target)).toMatchInlineSnapshot(
+ `"<div>teleported</div>"`,
+ )
+
+ render(renderWithDisabled(true), root)
+ expect(serializeInner(root)).toMatchInlineSnapshot(
+ `"<!--teleport start--><div>teleported</div><!--teleport end--><div>root</div>"`,
+ )
+ expect(serializeInner(target)).toBe(``)
+
+ // toggle back
+ render(renderWithDisabled(false), root)
+ expect(serializeInner(root)).toMatchInlineSnapshot(
+ `"<!--teleport start--><!--teleport end--><div>root</div>"`,
+ )
+ expect(serializeInner(target)).toMatchInlineSnapshot(
+ `"<div>teleported</div>"`,
+ )
})
- domRender(h(App), root)
- expect(root.innerHTML).toMatchInlineSnapshot('"<div>foo</div>"')
+ test('moving teleport while enabled', () => {
+ const target = nodeOps.createElement('div')
+ const root = nodeOps.createElement('div')
- show.value = true
- await nextTick()
+ render(
+ h(Fragment, [
+ h(Teleport, { to: target }, h('div', 'teleported')),
+ h('div', 'root'),
+ ]),
+ root,
+ )
+ expect(serializeInner(root)).toMatchInlineSnapshot(
+ `"<!--teleport start--><!--teleport end--><div>root</div>"`,
+ )
+ expect(serializeInner(target)).toMatchInlineSnapshot(
+ `"<div>teleported</div>"`,
+ )
- expect(root.innerHTML).toMatchInlineSnapshot(
- '"<!--teleport start--><!--teleport end--><div>teleported</div>"',
- )
+ render(
+ h(Fragment, [
+ h('div', 'root'),
+ h(Teleport, { to: target }, h('div', 'teleported')),
+ ]),
+ root,
+ )
+ expect(serializeInner(root)).toMatchInlineSnapshot(
+ `"<div>root</div><!--teleport start--><!--teleport end-->"`,
+ )
+ expect(serializeInner(target)).toMatchInlineSnapshot(
+ `"<div>teleported</div>"`,
+ )
- show.value = false
- await nextTick()
+ render(
+ h(Fragment, [
+ h(Teleport, { to: target }, h('div', 'teleported')),
+ h('div', 'root'),
+ ]),
+ root,
+ )
+ expect(serializeInner(root)).toMatchInlineSnapshot(
+ `"<!--teleport start--><!--teleport end--><div>root</div>"`,
+ )
+ expect(serializeInner(target)).toMatchInlineSnapshot(
+ `"<div>teleported</div>"`,
+ )
+ })
- expect(root.innerHTML).toMatchInlineSnapshot('"<div>foo</div>"')
- })
+ test('moving teleport while disabled', () => {
+ const target = nodeOps.createElement('div')
+ const root = nodeOps.createElement('div')
- test('unmount previous sibling node inside target node', async () => {
- const root = document.createElement('div')
- const parentShow = ref(false)
- const childShow = ref(true)
-
- const Comp = {
- setup() {
- return () => h(Teleport, { to: root }, [h('div', 'foo')])
- },
- }
-
- const App = defineComponent({
- setup() {
- return () => {
- return parentShow.value
- ? h(Fragment, { key: 0 }, [
- childShow.value ? h(Comp) : createCommentVNode('v-if'),
- ])
- : createCommentVNode('v-if')
- }
- },
+ render(
+ h(Fragment, [
+ h(Teleport, { to: target, disabled: true }, h('div', 'teleported')),
+ h('div', 'root'),
+ ]),
+ root,
+ )
+ expect(serializeInner(root)).toMatchInlineSnapshot(
+ `"<!--teleport start--><div>teleported</div><!--teleport end--><div>root</div>"`,
+ )
+ expect(serializeInner(target)).toBe('')
+
+ render(
+ h(Fragment, [
+ h('div', 'root'),
+ h(Teleport, { to: target, disabled: true }, h('div', 'teleported')),
+ ]),
+ root,
+ )
+ expect(serializeInner(root)).toMatchInlineSnapshot(
+ `"<div>root</div><!--teleport start--><div>teleported</div><!--teleport end-->"`,
+ )
+ expect(serializeInner(target)).toBe('')
+
+ render(
+ h(Fragment, [
+ h(Teleport, { to: target, disabled: true }, h('div', 'teleported')),
+ h('div', 'root'),
+ ]),
+ root,
+ )
+ expect(serializeInner(root)).toMatchInlineSnapshot(
+ `"<!--teleport start--><div>teleported</div><!--teleport end--><div>root</div>"`,
+ )
+ expect(serializeInner(target)).toBe('')
})
- domRender(h(App), root)
- expect(root.innerHTML).toMatchInlineSnapshot('"<!--v-if-->"')
+ test('should work with block tree', async () => {
+ const target = nodeOps.createElement('div')
+ const root = nodeOps.createElement('div')
+ const disabled = ref(false)
+
+ const App = {
+ setup() {
+ return {
+ target: markRaw(target),
+ disabled,
+ }
+ },
+ render: compile(`
+ <teleport :to="target" :disabled="disabled">
+ <div>teleported</div><span>{{ disabled }}</span><span v-if="disabled"/>
+ </teleport>
+ <div>root</div>
+ `),
+ }
+ render(h(App), root)
+ expect(serializeInner(root)).toMatchInlineSnapshot(
+ `"<!--teleport start--><!--teleport end--><div>root</div>"`,
+ )
+ expect(serializeInner(target)).toMatchInlineSnapshot(
+ `"<div>teleported</div><span>false</span><!--v-if-->"`,
+ )
- parentShow.value = true
- await nextTick()
- expect(root.innerHTML).toMatchInlineSnapshot(
- '"<!--teleport start--><!--teleport end--><div>foo</div>"',
- )
+ disabled.value = true
+ await nextTick()
+ expect(serializeInner(root)).toMatchInlineSnapshot(
+ `"<!--teleport start--><div>teleported</div><span>true</span><span></span><!--teleport end--><div>root</div>"`,
+ )
+ expect(serializeInner(target)).toBe(``)
- parentShow.value = false
- await nextTick()
- expect(root.innerHTML).toMatchInlineSnapshot('"<!--v-if-->"')
- })
+ // toggle back
+ disabled.value = false
+ await nextTick()
+ expect(serializeInner(root)).toMatchInlineSnapshot(
+ `"<!--teleport start--><!--teleport end--><div>root</div>"`,
+ )
+ expect(serializeInner(target)).toMatchInlineSnapshot(
+ `"<div>teleported</div><span>false</span><!--v-if-->"`,
+ )
+ })
+
+ // #3497
+ test(`the dir hooks of the Teleport's children should be called correctly`, async () => {
+ const target = nodeOps.createElement('div')
+ const root = nodeOps.createElement('div')
+ const toggle = ref(true)
+ const dir = {
+ mounted: vi.fn(),
+ unmounted: vi.fn(),
+ }
+
+ const app = createApp({
+ setup() {
+ return () => {
+ return toggle.value
+ ? h(Teleport, { to: target }, [
+ withDirectives(h('div', ['foo']), [[dir]]),
+ ])
+ : null
+ }
+ },
+ })
+ app.mount(root)
+
+ expect(serializeInner(root)).toMatchInlineSnapshot(
+ `"<!--teleport start--><!--teleport end-->"`,
+ )
+ expect(serializeInner(target)).toMatchInlineSnapshot(`"<div>foo</div>"`)
+ await nextTick()
+ expect(dir.mounted).toHaveBeenCalledTimes(1)
+ expect(dir.unmounted).toHaveBeenCalledTimes(0)
+
+ toggle.value = false
+ await nextTick()
+ expect(serializeInner(root)).toMatchInlineSnapshot(`"<!---->"`)
+ expect(serializeInner(target)).toMatchInlineSnapshot(`""`)
+ expect(dir.mounted).toHaveBeenCalledTimes(1)
+ expect(dir.unmounted).toHaveBeenCalledTimes(1)
+ })
+
+ // #7835
+ test(`ensure that target changes when disabled are updated correctly when enabled`, async () => {
+ const root = nodeOps.createElement('div')
+ const target1 = nodeOps.createElement('div')
+ const target2 = nodeOps.createElement('div')
+ const target3 = nodeOps.createElement('div')
+ const target = ref(target1)
+ const disabled = ref(true)
+
+ const App = {
+ setup() {
+ return () =>
+ h(Fragment, [
+ h(
+ Teleport,
+ { to: target.value, disabled: disabled.value },
+ h('div', 'teleported'),
+ ),
+ ])
+ },
+ }
+ render(h(App), root)
+ disabled.value = false
+ await nextTick()
+ expect(serializeInner(target1)).toMatchInlineSnapshot(
+ `"<div>teleported</div>"`,
+ )
+ expect(serializeInner(target2)).toMatchInlineSnapshot(`""`)
+ expect(serializeInner(target3)).toMatchInlineSnapshot(`""`)
+
+ disabled.value = true
+ await nextTick()
+ target.value = target2
+ await nextTick()
+ expect(serializeInner(target1)).toMatchInlineSnapshot(`""`)
+ expect(serializeInner(target2)).toMatchInlineSnapshot(`""`)
+ expect(serializeInner(target3)).toMatchInlineSnapshot(`""`)
+
+ target.value = target3
+ await nextTick()
+ expect(serializeInner(target1)).toMatchInlineSnapshot(`""`)
+ expect(serializeInner(target2)).toMatchInlineSnapshot(`""`)
+ expect(serializeInner(target3)).toMatchInlineSnapshot(`""`)
+
+ disabled.value = false
+ await nextTick()
+ expect(serializeInner(target1)).toMatchInlineSnapshot(`""`)
+ expect(serializeInner(target2)).toMatchInlineSnapshot(`""`)
+ expect(serializeInner(target3)).toMatchInlineSnapshot(
+ `"<div>teleported</div>"`,
+ )
+ })
+
+ //#9071
+ test('toggle sibling node inside target node', async () => {
+ const root = document.createElement('div')
+ const show = ref(false)
+ const App = defineComponent({
+ setup() {
+ return () => {
+ return show.value
+ ? h(Teleport, { to: root }, [h('div', 'teleported')])
+ : h('div', 'foo')
+ }
+ },
+ })
+
+ domRender(h(App), root)
+ expect(root.innerHTML).toMatchInlineSnapshot('"<div>foo</div>"')
+
+ show.value = true
+ await nextTick()
+
+ expect(root.innerHTML).toMatchInlineSnapshot(
+ '"<!--teleport start--><!--teleport end--><div>teleported</div>"',
+ )
+
+ show.value = false
+ await nextTick()
+
+ expect(root.innerHTML).toMatchInlineSnapshot('"<div>foo</div>"')
+ })
+
+ test('unmount previous sibling node inside target node', async () => {
+ const root = document.createElement('div')
+ const parentShow = ref(false)
+ const childShow = ref(true)
+
+ const Comp = {
+ setup() {
+ return () => h(Teleport, { to: root }, [h('div', 'foo')])
+ },
+ }
+
+ const App = defineComponent({
+ setup() {
+ return () => {
+ return parentShow.value
+ ? h(Fragment, { key: 0 }, [
+ childShow.value ? h(Comp) : createCommentVNode('v-if'),
+ ])
+ : createCommentVNode('v-if')
+ }
+ },
+ })
+
+ domRender(h(App), root)
+ expect(root.innerHTML).toMatchInlineSnapshot('"<!--v-if-->"')
+
+ parentShow.value = true
+ await nextTick()
+ expect(root.innerHTML).toMatchInlineSnapshot(
+ '"<!--teleport start--><!--teleport end--><div>foo</div>"',
+ )
+
+ parentShow.value = false
+ await nextTick()
+ expect(root.innerHTML).toMatchInlineSnapshot('"<!--v-if-->"')
+ })
+ }
})