]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
test(vapor): refactor hydration tests to include compilation
authorEvan You <evan@vuejs.org>
Tue, 11 Mar 2025 11:06:10 +0000 (19:06 +0800)
committerEvan You <evan@vuejs.org>
Tue, 11 Mar 2025 11:06:10 +0000 (19:06 +0800)
packages/runtime-vapor/__tests__/hydration.spec.ts

index caa84b16b91e97102a42571388efd00e10b5e763..666f5a68f04a427832ab4bae05b04a8317db0750 100644 (file)
@@ -1,29 +1,76 @@
-// import { type SSRContext, renderToString } from '@vue/server-renderer'
-import {
-  child,
-  createComponent,
-  createVaporSSRApp,
-  delegateEvents,
-  next,
-  renderEffect,
-  setClass,
-  setInsertionState,
-  setText,
-  template,
-} from '../src'
-import { nextTick, ref, toDisplayString } from '@vue/runtime-dom'
-
-function mountWithHydration(html: string, setup: () => any) {
+import { createVaporSSRApp, delegateEvents } from '../src'
+import { nextTick, ref } from '@vue/runtime-dom'
+import { compileScript, parse } from '@vue/compiler-sfc'
+import * as runtimeVapor from '../src'
+import * as runtimeDom from '@vue/runtime-dom'
+import * as VueServerRenderer from '@vue/server-renderer'
+
+const Vue = { ...runtimeDom, ...runtimeVapor }
+
+function compile(
+  sfc: string,
+  data: runtimeDom.Ref<any>,
+  components: Record<string, any> = {},
+  ssr = false,
+) {
+  if (!sfc.includes(`<script`)) {
+    sfc =
+      `<script vapor>const data = _data; const components = _components;</script>` +
+      sfc
+  }
+  const descriptor = parse(sfc).descriptor
+
+  const script = compileScript(descriptor, {
+    id: 'x',
+    isProd: true,
+    inlineTemplate: true,
+    genDefaultAs: '__sfc__',
+    vapor: true,
+    templateOptions: {
+      ssr,
+    },
+  })
+
+  const code =
+    script.content
+      .replace(/\bimport {/g, 'const {')
+      .replace(/ as _/g, ': _')
+      .replace(/} from ['"]vue['"]/g, `} = Vue`)
+      .replace(/} from "vue\/server-renderer"/g, '} = VueServerRenderer') +
+    '\nreturn __sfc__'
+
+  return new Function('Vue', 'VueServerRenderer', '_data', '_components', code)(
+    Vue,
+    VueServerRenderer,
+    data,
+    components,
+  )
+}
+
+async function testHydration(
+  code: string,
+  components: Record<string, string> = {},
+) {
+  const data = ref('foo')
+  const ssrComponents: any = {}
+  const clientComponents: any = {}
+  for (const key in components) {
+    clientComponents[key] = compile(components[key], data, clientComponents)
+    ssrComponents[key] = compile(components[key], data, ssrComponents, true)
+  }
+
+  const serverComp = compile(code, data, ssrComponents, true)
+  const html = await VueServerRenderer.renderToString(
+    runtimeDom.createSSRApp(serverComp),
+  )
   const container = document.createElement('div')
   document.body.appendChild(container)
   container.innerHTML = html
-  const app = createVaporSSRApp({
-    setup,
-  })
+
+  const clientComp = compile(code, data, clientComponents)
+  const app = createVaporSSRApp(clientComp)
   app.mount(container)
-  return {
-    container,
-  }
+  return { data, container }
 }
 
 const triggerEvent = (type: string, el: Element) => {
@@ -31,7 +78,7 @@ const triggerEvent = (type: string, el: Element) => {
   el.dispatchEvent(event)
 }
 
-describe('SSR hydration', () => {
+describe('Vapor Mode hydration', () => {
   delegateEvents('click')
 
   beforeEach(() => {
@@ -39,281 +86,165 @@ describe('SSR hydration', () => {
   })
 
   test('root text', async () => {
-    const msg = ref('foo')
-    const t = template(' ')
-    const { container } = mountWithHydration('foo', () => {
-      const n = t()
-      renderEffect(() => setText(n as Text, msg.value))
-      return n
-    })
-    expect(container.textContent).toBe('foo')
-    msg.value = 'bar'
+    const { data, container } = await testHydration(`
+      <template>{{ data }}</template>
+    `)
+    expect(container.innerHTML).toMatchInlineSnapshot(`"foo"`)
+
+    data.value = 'bar'
     await nextTick()
-    expect(container.textContent).toBe('bar')
+    expect(container.innerHTML).toMatchInlineSnapshot(`"bar"`)
   })
 
-  test('root comment', () => {
-    const t0 = template('<!---->')
-    const { container } = mountWithHydration('<!---->', () => t0())
+  test('root comment', async () => {
+    const { container } = await testHydration(`
+      <template><!----></template>
+    `)
     expect(container.innerHTML).toBe('<!---->')
     expect(`Hydration children mismatch in <div>`).not.toHaveBeenWarned()
   })
 
   test('root with mixed element and text', async () => {
-    const t0 = template(' A')
-    const t1 = template('<span>foo bar</span>')
-    const t2 = template(' ')
-    const msg = ref('hello')
-    const { container } = mountWithHydration(
-      ' A<span>foo bar</span>hello',
-      () => {
-        const n0 = t0()
-        const n1 = t1()
-        const n2 = t2()
-        renderEffect(() => setText(n2 as Text, toDisplayString(msg.value)))
-        return [n0, n1, n2]
-      },
+    const { container, data } = await testHydration(`
+      <template> A<span>{{ data }}</span>{{ data }}</template>
+    `)
+    expect(container.innerHTML).toMatchInlineSnapshot(
+      `"<!--[--> A<span>foo</span>foo<!--]-->"`,
     )
-    expect(container.innerHTML).toBe(' A<span>foo bar</span>hello')
-    msg.value = 'bar'
+
+    data.value = 'bar'
     await nextTick()
-    expect(container.innerHTML).toBe(' A<span>foo bar</span>bar')
+    expect(container.innerHTML).toMatchInlineSnapshot(
+      `"<!--[--> A<span>bar</span>bar<!--]-->"`,
+    )
   })
 
   test('empty element', async () => {
-    const t0 = template('<div></div>', true)
-    const { container } = mountWithHydration('<div></div>', () => t0())
+    const { container } = await testHydration(`
+      <template><div/></template>
+    `)
     expect(container.innerHTML).toBe('<div></div>')
     expect(`Hydration children mismatch in <div>`).not.toHaveBeenWarned()
   })
 
-  test('element with text children', async () => {
-    const t0 = template('<div> </div>', true)
-    const msg = ref('foo')
-    const { container } = mountWithHydration(
-      '<div class="foo">foo</div>',
-      () => {
-        const n0 = t0() as Element
-        const x0 = child(n0) as Text
-        renderEffect(() => {
-          const _msg = msg.value
-
-          setText(x0, toDisplayString(_msg))
-          setClass(n0, _msg)
-        })
-        return n0
-      },
+  test('element with binding and text children', async () => {
+    const { container, data } = await testHydration(`
+      <template><div :class="data">{{ data }}</div></template>
+    `)
+    expect(container.innerHTML).toMatchInlineSnapshot(
+      `"<div class="foo">foo</div>"`,
     )
-    expect(container.innerHTML).toBe(`<div class="foo">foo</div>`)
-    msg.value = 'bar'
+
+    data.value = 'bar'
     await nextTick()
-    expect(container.innerHTML).toBe(`<div class="bar">bar</div>`)
+    expect(container.innerHTML).toMatchInlineSnapshot(
+      `"<div class="bar">bar</div>"`,
+    )
   })
 
   test('element with elements children', async () => {
-    const t0 = template('<div><span> </span><span></span></div>', true)
-    const msg = ref('foo')
-    const fn = vi.fn()
-    const { container } = mountWithHydration(
-      '<div><span>foo</span><span class="foo"></span></div>',
-      () => {
-        const n2 = t0() as Element
-        const n0 = child(n2) as Element
-        const n1 = next(n0) as Element
-        const x0 = child(n0) as Text
-        ;(n1 as any).$evtclick = fn
-        renderEffect(() => {
-          const _msg = msg.value
-
-          setText(x0, toDisplayString(_msg))
-          setClass(n1, _msg)
-        })
-        return n2
-      },
-    )
-    expect(container.innerHTML).toBe(
-      `<div><span>foo</span><span class="foo"></span></div>`,
+    const { container } = await testHydration(`
+      <template>
+        <div>
+          <span>{{ data }}</span>
+          <span :class="data" @click="data = 'bar'"/>
+        </div>
+      </template>
+    `)
+    expect(container.innerHTML).toMatchInlineSnapshot(
+      `"<div><span>foo</span><span class="foo"></span></div>"`,
     )
 
     // event handler
     triggerEvent('click', container.querySelector('.foo')!)
-    expect(fn).toHaveBeenCalled()
 
-    msg.value = 'bar'
     await nextTick()
-    expect(container.innerHTML).toBe(
-      `<div><span>bar</span><span class="bar"></span></div>`,
+    expect(container.innerHTML).toMatchInlineSnapshot(
+      `"<div><span>bar</span><span class="bar"></span></div>"`,
     )
   })
 
   test('basic component', async () => {
-    const t0 = template(' ')
-    const msg = ref('foo')
-    const Comp = {
-      setup() {
-        const n0 = t0() as Text
-        renderEffect(() => setText(n0, toDisplayString(msg.value)))
-        return n0
-      },
-    }
-
-    const t1 = template('<div><span></span></div>', true)
-    const { container } = mountWithHydration(
-      '<div><span></span>foo</div>',
-      () => {
-        const n1 = t1() as Element
-        setInsertionState(n1)
-        createComponent(Comp)
-        return n1
-      },
+    const { container, data } = await testHydration(
+      `
+      <template><div><span></span><components.Child/></div></template>
+      `,
+      { Child: `<template>{{ data }}</template>` },
+    )
+    expect(container.innerHTML).toMatchInlineSnapshot(
+      `"<div><span></span>foo</div>"`,
     )
 
-    expect(container.innerHTML).toBe(`<div><span></span>foo</div>`)
-
-    msg.value = 'bar'
+    data.value = 'bar'
     await nextTick()
-    expect(container.innerHTML).toBe(`<div><span></span>bar</div>`)
+    expect(container.innerHTML).toMatchInlineSnapshot(
+      `"<div><span></span>bar</div>"`,
+    )
   })
 
   test('fragment component', async () => {
-    const t0 = template('<div> </div>')
-    const t1 = template(' ')
-    const msg = ref('foo')
-    const Comp = {
-      setup() {
-        const n0 = t0() as Element
-        const n1 = t1() as Text
-        const x0 = child(n0) as Text
-        renderEffect(() => {
-          const _msg = msg.value
-
-          setText(x0, toDisplayString(_msg))
-          setText(n1, toDisplayString(_msg))
-        })
-        return [n0, n1]
-      },
-    }
-
-    const t2 = template('<div><span></span></div>', true)
-    const { container } = mountWithHydration(
-      '<div><span></span><!--[--><div>foo</div>foo<!--]--></div>',
-      () => {
-        const n1 = t2() as Element
-        setInsertionState(n1)
-        createComponent(Comp)
-        return n1
-      },
+    const { container, data } = await testHydration(
+      `
+      <template><div><span></span><components.Child/></div></template>
+      `,
+      { Child: `<template><div>{{ data }}</div>-{{ data }}-</template>` },
     )
-
-    expect(container.innerHTML).toBe(
-      `<div><span></span><!--[--><div>foo</div>foo<!--]--></div>`,
+    expect(container.innerHTML).toMatchInlineSnapshot(
+      `"<div><span></span><!--[--><div>foo</div>-foo-<!--]--></div>"`,
     )
 
-    msg.value = 'bar'
+    data.value = 'bar'
     await nextTick()
-    expect(container.innerHTML).toBe(
-      `<div><span></span><!--[--><div>bar</div>bar<!--]--></div>`,
+    expect(container.innerHTML).toMatchInlineSnapshot(
+      `"<div><span></span><!--[--><div>bar</div>-bar-<!--]--></div>"`,
     )
   })
 
   test('fragment component with prepend', async () => {
-    const t0 = template('<div> </div>')
-    const t1 = template(' ')
-    const msg = ref('foo')
-    const Comp = {
-      setup() {
-        const n0 = t0() as Element
-        const n1 = t1() as Text
-        const x0 = child(n0) as Text
-        renderEffect(() => {
-          const _msg = msg.value
-
-          setText(x0, toDisplayString(_msg))
-          setText(n1, toDisplayString(_msg))
-        })
-        return [n0, n1]
-      },
-    }
-
-    const t2 = template('<div><span></span></div>', true)
-    const { container } = mountWithHydration(
-      '<div><!--[--><div>foo</div>foo<!--]--><span></span></div>',
-      () => {
-        const n1 = t2() as Element
-        setInsertionState(n1, 0)
-        createComponent(Comp)
-        return n1
-      },
+    const { container, data } = await testHydration(
+      `
+      <template><div><components.Child/><span></span></div></template>
+      `,
+      { Child: `<template><div>{{ data }}</div>-{{ data }}-</template>` },
     )
-
-    expect(container.innerHTML).toBe(
-      `<div><!--[--><div>foo</div>foo<!--]--><span></span></div>`,
+    expect(container.innerHTML).toMatchInlineSnapshot(
+      `"<div><!--[--><div>foo</div>-foo-<!--]--><span></span></div>"`,
     )
 
-    msg.value = 'bar'
+    data.value = 'bar'
     await nextTick()
-    expect(container.innerHTML).toBe(
-      `<div><!--[--><div>bar</div>bar<!--]--><span></span></div>`,
+    expect(container.innerHTML).toMatchInlineSnapshot(
+      `"<div><!--[--><div>bar</div>-bar-<!--]--><span></span></div>"`,
     )
   })
 
   test('nested fragment components', async () => {
-    const t0 = template('<div> </div>')
-    const t1 = template(' ')
-    const msg = ref('foo')
-    const Comp = {
-      setup() {
-        const n0 = t0() as Element
-        const n1 = t1() as Text
-        const x0 = child(n0) as Text
-        renderEffect(() => {
-          const _msg = msg.value
-
-          setText(x0, toDisplayString(_msg))
-          setText(n1, toDisplayString(_msg))
-        })
-        return [n0, n1]
-      },
-    }
-
-    const t2 = template('<div></div>')
-    const Parent = {
-      setup() {
-        const n0 = t2()
-        const n1 = createComponent(Comp)
-        const n2 = t2()
-        return [n0, n1, n2]
-      },
-    }
-
-    const t3 = template('<div><span></span></div>', true)
-    const { container } = mountWithHydration(
-      '<div><!--[-->' +
-        '<div></div><!--[--><div>foo</div>foo<!--]--><div></div>' +
-        '<!--]--><span></span></div>',
-      () => {
-        const n1 = t3() as Element
-        setInsertionState(n1, 0)
-        createComponent(Parent)
-        return n1
+    const { container, data } = await testHydration(
+      `
+      <template><div><components.Parent/><span></span></div></template>
+      `,
+      {
+        Parent: `<template><div/><components.Child/><div/></template>`,
+        Child: `<template><div>{{ data }}</div>-{{ data }}-</template>`,
       },
     )
-
-    expect(container.innerHTML).toBe(
-      '<div><!--[-->' +
-        '<div></div><!--[--><div>foo</div>foo<!--]--><div></div>' +
-        '<!--]--><span></span></div>',
+    expect(container.innerHTML).toMatchInlineSnapshot(
+      `"<div><!--[--><div></div><!--[--><div>foo</div>-foo-<!--]--><div></div><!--]--><span></span></div>"`,
     )
 
-    msg.value = 'bar'
+    data.value = 'bar'
     await nextTick()
-    expect(container.innerHTML).toBe(
-      '<div><!--[-->' +
-        '<div></div><!--[--><div>bar</div>bar<!--]--><div></div>' +
-        '<!--]--><span></span></div>',
+    expect(container.innerHTML).toMatchInlineSnapshot(
+      `"<div><!--[--><div></div><!--[--><div>bar</div>-bar-<!--]--><div></div><!--]--><span></span></div>"`,
     )
   })
 
+  test.todo('if')
+
+  test.todo('for')
+
+  test.todo('slots')
+
   // test('element with ref', () => {
   //   const el = ref()
   //   const { vnode, container } = mountWithHydration('<div></div>', () =>
@@ -323,329 +254,6 @@ describe('SSR hydration', () => {
   //   expect(el.value).toBe(vnode.el)
   // })
 
-  // test('Fragment', async () => {
-  //   const msg = ref('foo')
-  //   const fn = vi.fn()
-  //   const { vnode, container } = mountWithHydration(
-  //     '<div><!--[--><span>foo</span><!--[--><span class="foo"></span><!--]--><!--]--></div>',
-  //     () =>
-  //       h('div', [
-  //         [
-  //           h('span', msg.value),
-  //           [h('span', { class: msg.value, onClick: fn })],
-  //         ],
-  //       ]),
-  //   )
-  //   expect(vnode.el).toBe(container.firstChild)
-
-  //   expect(vnode.el.innerHTML).toBe(
-  //     `<!--[--><span>foo</span><!--[--><span class="foo"></span><!--]--><!--]-->`,
-  //   )
-
-  //   // start fragment 1
-  //   const fragment1 = (vnode.children as VNode[])[0]
-  //   expect(fragment1.el).toBe(vnode.el.childNodes[0])
-  //   const fragment1Children = fragment1.children as VNode[]
-
-  //   // first <span>
-  //   expect(fragment1Children[0].el!.tagName).toBe('SPAN')
-  //   expect(fragment1Children[0].el).toBe(vnode.el.childNodes[1])
-
-  //   // start fragment 2
-  //   const fragment2 = fragment1Children[1]
-  //   expect(fragment2.el).toBe(vnode.el.childNodes[2])
-  //   const fragment2Children = fragment2.children as VNode[]
-
-  //   // second <span>
-  //   expect(fragment2Children[0].el!.tagName).toBe('SPAN')
-  //   expect(fragment2Children[0].el).toBe(vnode.el.childNodes[3])
-
-  //   // end fragment 2
-  //   expect(fragment2.anchor).toBe(vnode.el.childNodes[4])
-
-  //   // end fragment 1
-  //   expect(fragment1.anchor).toBe(vnode.el.childNodes[5])
-
-  //   // event handler
-  //   triggerEvent('click', vnode.el.querySelector('.foo')!)
-  //   expect(fn).toHaveBeenCalled()
-
-  //   msg.value = 'bar'
-  //   await nextTick()
-  //   expect(vnode.el.innerHTML).toBe(
-  //     `<!--[--><span>bar</span><!--[--><span class="bar"></span><!--]--><!--]-->`,
-  //   )
-  // })
-
-  // // #7285
-  // test('Fragment (multiple continuous text vnodes)', async () => {
-  //   // should no mismatch warning
-  //   const { container } = mountWithHydration('<!--[-->fooo<!--]-->', () => [
-  //     'fo',
-  //     createTextVNode('o'),
-  //     'o',
-  //   ])
-  //   expect(container.textContent).toBe('fooo')
-  // })
-
-  // test('Teleport', async () => {
-  //   const msg = ref('foo')
-  //   const fn = vi.fn()
-  //   const teleportContainer = document.createElement('div')
-  //   teleportContainer.id = 'teleport'
-  //   teleportContainer.innerHTML = `<!--teleport start anchor--><span>foo</span><span class="foo"></span><!--teleport anchor-->`
-  //   document.body.appendChild(teleportContainer)
-
-  //   const { vnode, container } = mountWithHydration(
-  //     '<!--teleport start--><!--teleport end-->',
-  //     () =>
-  //       h(Teleport, { to: '#teleport' }, [
-  //         h('span', msg.value),
-  //         h('span', { class: msg.value, onClick: fn }),
-  //       ]),
-  //   )
-
-  //   expect(vnode.el).toBe(container.firstChild)
-  //   expect(vnode.anchor).toBe(container.lastChild)
-
-  //   expect(vnode.target).toBe(teleportContainer)
-  //   expect(vnode.targetStart).toBe(teleportContainer.childNodes[0])
-  //   expect((vnode.children as VNode[])[0].el).toBe(
-  //     teleportContainer.childNodes[1],
-  //   )
-  //   expect((vnode.children as VNode[])[1].el).toBe(
-  //     teleportContainer.childNodes[2],
-  //   )
-  //   expect(vnode.targetAnchor).toBe(teleportContainer.childNodes[3])
-
-  //   // event handler
-  //   triggerEvent('click', teleportContainer.querySelector('.foo')!)
-  //   expect(fn).toHaveBeenCalled()
-
-  //   msg.value = 'bar'
-  //   await nextTick()
-  //   expect(teleportContainer.innerHTML).toBe(
-  //     `<!--teleport start anchor--><span>bar</span><span class="bar"></span><!--teleport anchor-->`,
-  //   )
-  // })
-
-  // test('Teleport (multiple + integration)', async () => {
-  //   const msg = ref('foo')
-  //   const fn1 = vi.fn()
-  //   const fn2 = vi.fn()
-
-  //   const Comp = () => [
-  //     h(Teleport, { to: '#teleport2' }, [
-  //       h('span', msg.value),
-  //       h('span', { class: msg.value, onClick: fn1 }),
-  //     ]),
-  //     h(Teleport, { to: '#teleport2' }, [
-  //       h('span', msg.value + '2'),
-  //       h('span', { class: msg.value + '2', onClick: fn2 }),
-  //     ]),
-  //   ]
-
-  //   const teleportContainer = document.createElement('div')
-  //   teleportContainer.id = 'teleport2'
-  //   const ctx: SSRContext = {}
-  //   const mainHtml = await renderToString(h(Comp), ctx)
-  //   expect(mainHtml).toMatchInlineSnapshot(
-  //     `"<!--[--><!--teleport start--><!--teleport end--><!--teleport start--><!--teleport end--><!--]-->"`,
-  //   )
-
-  //   const teleportHtml = ctx.teleports!['#teleport2']
-  //   expect(teleportHtml).toMatchInlineSnapshot(
-  //     `"<!--teleport start anchor--><span>foo</span><span class="foo"></span><!--teleport anchor--><!--teleport start anchor--><span>foo2</span><span class="foo2"></span><!--teleport anchor-->"`,
-  //   )
-
-  //   teleportContainer.innerHTML = teleportHtml
-  //   document.body.appendChild(teleportContainer)
-
-  //   const { vnode, container } = mountWithHydration(mainHtml, Comp)
-  //   expect(vnode.el).toBe(container.firstChild)
-  //   const teleportVnode1 = (vnode.children as VNode[])[0]
-  //   const teleportVnode2 = (vnode.children as VNode[])[1]
-  //   expect(teleportVnode1.el).toBe(container.childNodes[1])
-  //   expect(teleportVnode1.anchor).toBe(container.childNodes[2])
-  //   expect(teleportVnode2.el).toBe(container.childNodes[3])
-  //   expect(teleportVnode2.anchor).toBe(container.childNodes[4])
-
-  //   expect(teleportVnode1.target).toBe(teleportContainer)
-  //   expect(teleportVnode1.targetStart).toBe(teleportContainer.childNodes[0])
-  //   expect((teleportVnode1 as any).children[0].el).toBe(
-  //     teleportContainer.childNodes[1],
-  //   )
-  //   expect(teleportVnode1.targetAnchor).toBe(teleportContainer.childNodes[3])
-
-  //   expect(teleportVnode2.target).toBe(teleportContainer)
-  //   expect(teleportVnode2.targetStart).toBe(teleportContainer.childNodes[4])
-  //   expect((teleportVnode2 as any).children[0].el).toBe(
-  //     teleportContainer.childNodes[5],
-  //   )
-  //   expect(teleportVnode2.targetAnchor).toBe(teleportContainer.childNodes[7])
-
-  //   // // event handler
-  //   triggerEvent('click', teleportContainer.querySelector('.foo')!)
-  //   expect(fn1).toHaveBeenCalled()
-
-  //   triggerEvent('click', teleportContainer.querySelector('.foo2')!)
-  //   expect(fn2).toHaveBeenCalled()
-
-  //   msg.value = 'bar'
-  //   await nextTick()
-  //   expect(teleportContainer.innerHTML).toMatchInlineSnapshot(
-  //     `"<!--teleport start anchor--><span>bar</span><span class="bar"></span><!--teleport anchor--><!--teleport start anchor--><span>bar2</span><span class="bar2"></span><!--teleport anchor-->"`,
-  //   )
-  // })
-
-  // test('Teleport (disabled)', async () => {
-  //   const msg = ref('foo')
-  //   const fn1 = vi.fn()
-  //   const fn2 = vi.fn()
-
-  //   const Comp = () => [
-  //     h('div', 'foo'),
-  //     h(Teleport, { to: '#teleport3', disabled: true }, [
-  //       h('span', msg.value),
-  //       h('span', { class: msg.value, onClick: fn1 }),
-  //     ]),
-  //     h('div', { class: msg.value + '2', onClick: fn2 }, 'bar'),
-  //   ]
-
-  //   const teleportContainer = document.createElement('div')
-  //   teleportContainer.id = 'teleport3'
-  //   const ctx: SSRContext = {}
-  //   const mainHtml = await renderToString(h(Comp), ctx)
-  //   expect(mainHtml).toMatchInlineSnapshot(
-  //     `"<!--[--><div>foo</div><!--teleport start--><span>foo</span><span class="foo"></span><!--teleport end--><div class="foo2">bar</div><!--]-->"`,
-  //   )
-
-  //   const teleportHtml = ctx.teleports!['#teleport3']
-  //   expect(teleportHtml).toMatchInlineSnapshot(
-  //     `"<!--teleport start anchor--><!--teleport anchor-->"`,
-  //   )
-
-  //   teleportContainer.innerHTML = teleportHtml
-  //   document.body.appendChild(teleportContainer)
-
-  //   const { vnode, container } = mountWithHydration(mainHtml, Comp)
-  //   expect(vnode.el).toBe(container.firstChild)
-  //   const children = vnode.children as VNode[]
-
-  //   expect(children[0].el).toBe(container.childNodes[1])
-
-  //   const teleportVnode = children[1]
-  //   expect(teleportVnode.el).toBe(container.childNodes[2])
-  //   expect((teleportVnode.children as VNode[])[0].el).toBe(
-  //     container.childNodes[3],
-  //   )
-  //   expect((teleportVnode.children as VNode[])[1].el).toBe(
-  //     container.childNodes[4],
-  //   )
-  //   expect(teleportVnode.anchor).toBe(container.childNodes[5])
-  //   expect(children[2].el).toBe(container.childNodes[6])
-
-  //   expect(teleportVnode.target).toBe(teleportContainer)
-  //   expect(teleportVnode.targetStart).toBe(teleportContainer.childNodes[0])
-  //   expect(teleportVnode.targetAnchor).toBe(teleportContainer.childNodes[1])
-
-  //   // // event handler
-  //   triggerEvent('click', container.querySelector('.foo')!)
-  //   expect(fn1).toHaveBeenCalled()
-
-  //   triggerEvent('click', container.querySelector('.foo2')!)
-  //   expect(fn2).toHaveBeenCalled()
-
-  //   msg.value = 'bar'
-  //   await nextTick()
-  //   expect(container.innerHTML).toMatchInlineSnapshot(
-  //     `"<!--[--><div>foo</div><!--teleport start--><span>bar</span><span class="bar"></span><!--teleport end--><div class="bar2">bar</div><!--]-->"`,
-  //   )
-  // })
-
-  // // #6152
-  // test('Teleport (disabled + as component root)', () => {
-  //   const { container } = mountWithHydration(
-  //     '<!--[--><div>Parent fragment</div><!--teleport start--><div>Teleport content</div><!--teleport end--><!--]-->',
-  //     () => [
-  //       h('div', 'Parent fragment'),
-  //       h(() =>
-  //         h(Teleport, { to: 'body', disabled: true }, [
-  //           h('div', 'Teleport content'),
-  //         ]),
-  //       ),
-  //     ],
-  //   )
-  //   expect(document.body.innerHTML).toBe('')
-  //   expect(container.innerHTML).toBe(
-  //     '<!--[--><div>Parent fragment</div><!--teleport start--><div>Teleport content</div><!--teleport end--><!--]-->',
-  //   )
-  //   expect(
-  //     `Hydration completed but contains mismatches.`,
-  //   ).not.toHaveBeenWarned()
-  // })
-
-  // test('Teleport (as component root)', () => {
-  //   const teleportContainer = document.createElement('div')
-  //   teleportContainer.id = 'teleport4'
-  //   teleportContainer.innerHTML = `<!--teleport start anchor-->hello<!--teleport anchor-->`
-  //   document.body.appendChild(teleportContainer)
-
-  //   const wrapper = {
-  //     render() {
-  //       return h(Teleport, { to: '#teleport4' }, ['hello'])
-  //     },
-  //   }
-
-  //   const { vnode, container } = mountWithHydration(
-  //     '<div><!--teleport start--><!--teleport end--><div></div></div>',
-  //     () => h('div', [h(wrapper), h('div')]),
-  //   )
-  //   expect(vnode.el).toBe(container.firstChild)
-  //   // component el
-  //   const wrapperVNode = (vnode as any).children[0]
-  //   const tpStart = container.firstChild?.firstChild
-  //   const tpEnd = tpStart?.nextSibling
-  //   expect(wrapperVNode.el).toBe(tpStart)
-  //   expect(wrapperVNode.component.subTree.el).toBe(tpStart)
-  //   expect(wrapperVNode.component.subTree.anchor).toBe(tpEnd)
-  //   // next node hydrate properly
-  //   const nextVNode = (vnode as any).children[1]
-  //   expect(nextVNode.el).toBe(container.firstChild?.lastChild)
-  // })
-
-  // test('Teleport (nested)', () => {
-  //   const teleportContainer = document.createElement('div')
-  //   teleportContainer.id = 'teleport5'
-  //   teleportContainer.innerHTML = `<!--teleport start anchor--><div><!--teleport start--><!--teleport end--></div><!--teleport anchor--><!--teleport start anchor--><div>child</div><!--teleport anchor-->`
-  //   document.body.appendChild(teleportContainer)
-
-  //   const { vnode, container } = mountWithHydration(
-  //     '<!--teleport start--><!--teleport end-->',
-  //     () =>
-  //       h(Teleport, { to: '#teleport5' }, [
-  //         h('div', [h(Teleport, { to: '#teleport5' }, [h('div', 'child')])]),
-  //       ]),
-  //   )
-
-  //   expect(vnode.el).toBe(container.firstChild)
-  //   expect(vnode.anchor).toBe(container.lastChild)
-
-  //   const childDivVNode = (vnode as any).children[0]
-  //   const div = teleportContainer.childNodes[1]
-  //   expect(childDivVNode.el).toBe(div)
-  //   expect(vnode.targetAnchor).toBe(div?.nextSibling)
-
-  //   const childTeleportVNode = childDivVNode.children[0]
-  //   expect(childTeleportVNode.el).toBe(div?.firstChild)
-  //   expect(childTeleportVNode.anchor).toBe(div?.lastChild)
-
-  //   expect(childTeleportVNode.targetAnchor).toBe(teleportContainer.lastChild)
-  //   expect(childTeleportVNode.children[0].el).toBe(
-  //     teleportContainer.lastChild?.previousSibling,
-  //   )
-  // })
-
   // test('with data-allow-mismatch component when using onServerPrefetch', async () => {
   //   const Comp = {
   //     template: `
@@ -685,178 +293,6 @@ describe('SSR hydration', () => {
   //   )
   // })
 
-  // test('Teleport unmount (full integration)', async () => {
-  //   const Comp1 = {
-  //     template: `
-  //       <Teleport to="#target">
-  //         <span>Teleported Comp1</span>
-  //       </Teleport>
-  //     `,
-  //   }
-  //   const Comp2 = {
-  //     template: `
-  //       <div>Comp2</div>
-  //     `,
-  //   }
-
-  //   const toggle = ref(true)
-  //   const App = {
-  //     template: `
-  //       <div>
-  //         <Comp1 v-if="toggle"/>
-  //         <Comp2 v-else/>
-  //       </div>
-  //     `,
-  //     components: {
-  //       Comp1,
-  //       Comp2,
-  //     },
-  //     setup() {
-  //       return { toggle }
-  //     },
-  //   }
-
-  //   const container = document.createElement('div')
-  //   const teleportContainer = document.createElement('div')
-  //   teleportContainer.id = 'target'
-  //   document.body.appendChild(teleportContainer)
-
-  //   // server render
-  //   const ctx: SSRContext = {}
-  //   container.innerHTML = await renderToString(h(App), ctx)
-  //   expect(container.innerHTML).toBe(
-  //     '<div><!--teleport start--><!--teleport end--></div>',
-  //   )
-  //   teleportContainer.innerHTML = ctx.teleports!['#target']
-
-  //   // hydrate
-  //   createSSRApp(App).mount(container)
-  //   expect(container.innerHTML).toBe(
-  //     '<div><!--teleport start--><!--teleport end--></div>',
-  //   )
-  //   expect(teleportContainer.innerHTML).toBe(
-  //     '<!--teleport start anchor--><span>Teleported Comp1</span><!--teleport anchor-->',
-  //   )
-  //   expect(`Hydration children mismatch`).not.toHaveBeenWarned()
-
-  //   toggle.value = false
-  //   await nextTick()
-  //   expect(container.innerHTML).toBe('<div><div>Comp2</div></div>')
-  //   expect(teleportContainer.innerHTML).toBe('')
-  // })
-
-  // test('Teleport unmount (mismatch + full integration)', async () => {
-  //   const Comp1 = {
-  //     template: `
-  //       <Teleport to="#target">
-  //         <span>Teleported Comp1</span>
-  //       </Teleport>
-  //     `,
-  //   }
-  //   const Comp2 = {
-  //     template: `
-  //       <div>Comp2</div>
-  //     `,
-  //   }
-
-  //   const toggle = ref(true)
-  //   const App = {
-  //     template: `
-  //       <div>
-  //         <Comp1 v-if="toggle"/>
-  //         <Comp2 v-else/>
-  //       </div>
-  //     `,
-  //     components: {
-  //       Comp1,
-  //       Comp2,
-  //     },
-  //     setup() {
-  //       return { toggle }
-  //     },
-  //   }
-
-  //   const container = document.createElement('div')
-  //   const teleportContainer = document.createElement('div')
-  //   teleportContainer.id = 'target'
-  //   document.body.appendChild(teleportContainer)
-
-  //   // server render
-  //   container.innerHTML = await renderToString(h(App))
-  //   expect(container.innerHTML).toBe(
-  //     '<div><!--teleport start--><!--teleport end--></div>',
-  //   )
-  //   expect(teleportContainer.innerHTML).toBe('')
-
-  //   // hydrate
-  //   createSSRApp(App).mount(container)
-  //   expect(container.innerHTML).toBe(
-  //     '<div><!--teleport start--><!--teleport end--></div>',
-  //   )
-  //   expect(teleportContainer.innerHTML).toBe('<span>Teleported Comp1</span>')
-  //   expect(`Hydration children mismatch`).toHaveBeenWarned()
-
-  //   toggle.value = false
-  //   await nextTick()
-  //   expect(container.innerHTML).toBe('<div><div>Comp2</div></div>')
-  //   expect(teleportContainer.innerHTML).toBe('')
-  // })
-
-  // test('Teleport target change (mismatch + full integration)', async () => {
-  //   const target = ref('#target1')
-  //   const Comp = {
-  //     template: `
-  //       <Teleport :to="target">
-  //         <span>Teleported</span>
-  //       </Teleport>
-  //     `,
-  //     setup() {
-  //       return { target }
-  //     },
-  //   }
-
-  //   const App = {
-  //     template: `
-  //       <div>
-  //         <Comp />
-  //       </div>
-  //     `,
-  //     components: {
-  //       Comp,
-  //     },
-  //   }
-
-  //   const container = document.createElement('div')
-  //   const teleportContainer1 = document.createElement('div')
-  //   teleportContainer1.id = 'target1'
-  //   const teleportContainer2 = document.createElement('div')
-  //   teleportContainer2.id = 'target2'
-  //   document.body.appendChild(teleportContainer1)
-  //   document.body.appendChild(teleportContainer2)
-
-  //   // server render
-  //   container.innerHTML = await renderToString(h(App))
-  //   expect(container.innerHTML).toBe(
-  //     '<div><!--teleport start--><!--teleport end--></div>',
-  //   )
-  //   expect(teleportContainer1.innerHTML).toBe('')
-  //   expect(teleportContainer2.innerHTML).toBe('')
-
-  //   // hydrate
-  //   createSSRApp(App).mount(container)
-  //   expect(container.innerHTML).toBe(
-  //     '<div><!--teleport start--><!--teleport end--></div>',
-  //   )
-  //   expect(teleportContainer1.innerHTML).toBe('<span>Teleported</span>')
-  //   expect(teleportContainer2.innerHTML).toBe('')
-  //   expect(`Hydration children mismatch`).toHaveBeenWarned()
-
-  //   target.value = '#target2'
-  //   await nextTick()
-  //   expect(teleportContainer1.innerHTML).toBe('')
-  //   expect(teleportContainer2.innerHTML).toBe('<span>Teleported</span>')
-  // })
-
   // // compile SSR + client render fn from the same template & hydrate
   // test('full compiler integration', async () => {
   //   const mounted: string[] = []
@@ -1006,164 +442,6 @@ describe('SSR hydration', () => {
   //   expect(handler).toHaveBeenCalled()
   // })
 
-  // test('Suspense', async () => {
-  //   const AsyncChild = {
-  //     async setup() {
-  //       const count = ref(0)
-  //       return () =>
-  //         h(
-  //           'span',
-  //           {
-  //             onClick: () => {
-  //               count.value++
-  //             },
-  //           },
-  //           count.value,
-  //         )
-  //     },
-  //   }
-  //   const { vnode, container } = mountWithHydration('<span>0</span>', () =>
-  //     h(Suspense, () => h(AsyncChild)),
-  //   )
-  //   expect(vnode.el).toBe(container.firstChild)
-  //   // wait for hydration to finish
-  //   await new Promise(r => setTimeout(r))
-  //   triggerEvent('click', container.querySelector('span')!)
-  //   await nextTick()
-  //   expect(container.innerHTML).toBe(`<span>1</span>`)
-  // })
-
-  // // #6638
-  // test('Suspense + async component', async () => {
-  //   let isSuspenseResolved = false
-  //   let isSuspenseResolvedInChild: any
-  //   const AsyncChild = defineAsyncComponent(() =>
-  //     Promise.resolve(
-  //       defineComponent({
-  //         setup() {
-  //           isSuspenseResolvedInChild = isSuspenseResolved
-  //           const count = ref(0)
-  //           return () =>
-  //             h(
-  //               'span',
-  //               {
-  //                 onClick: () => {
-  //                   count.value++
-  //                 },
-  //               },
-  //               count.value,
-  //             )
-  //         },
-  //       }),
-  //     ),
-  //   )
-  //   const { vnode, container } = mountWithHydration('<span>0</span>', () =>
-  //     h(
-  //       Suspense,
-  //       {
-  //         onResolve() {
-  //           isSuspenseResolved = true
-  //         },
-  //       },
-  //       () => h(AsyncChild),
-  //     ),
-  //   )
-  //   expect(vnode.el).toBe(container.firstChild)
-  //   // wait for hydration to finish
-  //   await new Promise(r => setTimeout(r))
-
-  //   expect(isSuspenseResolvedInChild).toBe(false)
-  //   expect(isSuspenseResolved).toBe(true)
-
-  //   // assert interaction
-  //   triggerEvent('click', container.querySelector('span')!)
-  //   await nextTick()
-  //   expect(container.innerHTML).toBe(`<span>1</span>`)
-  // })
-
-  // test('Suspense (full integration)', async () => {
-  //   const mountedCalls: number[] = []
-  //   const asyncDeps: Promise<any>[] = []
-
-  //   const AsyncChild = defineComponent({
-  //     props: ['n'],
-  //     async setup(props) {
-  //       const count = ref(props.n)
-  //       onMounted(() => {
-  //         mountedCalls.push(props.n)
-  //       })
-  //       const p = new Promise(r => setTimeout(r, props.n * 10))
-  //       asyncDeps.push(p)
-  //       await p
-  //       return () =>
-  //         h(
-  //           'span',
-  //           {
-  //             onClick: () => {
-  //               count.value++
-  //             },
-  //           },
-  //           count.value,
-  //         )
-  //     },
-  //   })
-
-  //   const done = vi.fn()
-  //   const App = {
-  //     template: `
-  //     <Suspense @resolve="done">
-  //       <div>
-  //         <AsyncChild :n="1" />
-  //         <AsyncChild :n="2" />
-  //       </div>
-  //     </Suspense>`,
-  //     components: {
-  //       AsyncChild,
-  //     },
-  //     methods: {
-  //       done,
-  //     },
-  //   }
-
-  //   const container = document.createElement('div')
-  //   // server render
-  //   container.innerHTML = await renderToString(h(App))
-  //   expect(container.innerHTML).toMatchInlineSnapshot(
-  //     `"<div><span>1</span><span>2</span></div>"`,
-  //   )
-  //   // reset asyncDeps from ssr
-  //   asyncDeps.length = 0
-  //   // hydrate
-  //   createSSRApp(App).mount(container)
-
-  //   expect(mountedCalls.length).toBe(0)
-  //   expect(asyncDeps.length).toBe(2)
-
-  //   // wait for hydration to complete
-  //   await Promise.all(asyncDeps)
-  //   await new Promise(r => setTimeout(r))
-
-  //   // should flush buffered effects
-  //   expect(mountedCalls).toMatchObject([1, 2])
-  //   expect(container.innerHTML).toMatch(
-  //     `<div><span>1</span><span>2</span></div>`,
-  //   )
-
-  //   const span1 = container.querySelector('span')!
-  //   triggerEvent('click', span1)
-  //   await nextTick()
-  //   expect(container.innerHTML).toMatch(
-  //     `<div><span>2</span><span>2</span></div>`,
-  //   )
-
-  //   const span2 = span1.nextSibling as Element
-  //   triggerEvent('click', span2)
-  //   await nextTick()
-  //   expect(container.innerHTML).toMatch(
-  //     `<div><span>2</span><span>3</span></div>`,
-  //   )
-  // })
-
   // test('async component', async () => {
   //   const spy = vi.fn()
   //   const Comp = () =>
@@ -2464,4 +1742,7 @@ describe('SSR hydration', () => {
   //     expect(`Hydration attribute mismatch`).not.toHaveBeenWarned()
   //   })
   // })
+
+  test.todo('Teleport')
+  test.todo('Suspense')
 })