]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
wip(vapor): text hydration tests
authorEvan You <evan@vuejs.org>
Sun, 9 Mar 2025 12:14:03 +0000 (20:14 +0800)
committerEvan You <evan@vuejs.org>
Sun, 9 Mar 2025 12:14:03 +0000 (20:14 +0800)
packages/compiler-vapor/src/transforms/transformChildren.ts
packages/runtime-vapor/__tests__/hydration.spec.ts
packages/runtime-vapor/src/dom/hydration.ts

index 12bf743208e9942896c74d822fb122cf03ee04a1..9b76d86f3283bcbb79c7d706192745542bc3f5a9 100644 (file)
@@ -69,7 +69,6 @@ function processDynamicChildren(context: TransformContext<ElementNode>) {
 
           prevDynamics[0].flags -= DynamicFlag.NON_TEMPLATE
           const anchor = (prevDynamics[0].anchor = context.increaseId())
-
           context.registerOperation({
             type: IRNodeTypes.INSERT_NODE,
             elements: prevDynamics.map(child => child.id!),
index 7422f6e6dcf112c0376d7beaab38f9128cd91694..8abf1570d52afce2cbf75a17150ef090b0bdbba8 100644 (file)
@@ -1,9 +1,19 @@
 // import { type SSRContext, renderToString } from '@vue/server-renderer'
-import { createVaporSSRApp, renderEffect, setText, template } from '../src'
-import { nextTick, ref } from '@vue/runtime-dom'
+import {
+  child,
+  createVaporSSRApp,
+  delegateEvents,
+  next,
+  renderEffect,
+  setClass,
+  setText,
+  template,
+} from '../src'
+import { nextTick, ref, toDisplayString } from '@vue/runtime-dom'
 
 function mountWithHydration(html: string, setup: () => any) {
   const container = document.createElement('div')
+  document.body.appendChild(container)
   container.innerHTML = html
   const app = createVaporSSRApp({
     setup,
@@ -14,17 +24,19 @@ function mountWithHydration(html: string, setup: () => any) {
   }
 }
 
-// const triggerEvent = (type: string, el: Element) => {
-//   const event = new Event(type)
-//   el.dispatchEvent(event)
-// }
+const triggerEvent = (type: string, el: Element) => {
+  const event = new Event(type, { bubbles: true })
+  el.dispatchEvent(event)
+}
 
 describe('SSR hydration', () => {
+  delegateEvents('click')
+
   beforeEach(() => {
     document.body.innerHTML = ''
   })
 
-  test('text', async () => {
+  test('root text', async () => {
     const msg = ref('foo')
     const t = template(' ')
     const { container } = mountWithHydration('foo', () => {
@@ -38,128 +50,99 @@ describe('SSR hydration', () => {
     expect(container.textContent).toBe('bar')
   })
 
-  test('empty text', async () => {
-    const t0 = template('<div></div>', true)
-    const { container } = mountWithHydration('<div></div>', () => t0())
-    expect(container.innerHTML).toBe('<div></div>')
-    expect(`Hydration children mismatch in <div>`).not.toHaveBeenWarned()
-  })
-
-  test('comment', () => {
+  test('root comment', () => {
     const t0 = template('<!---->')
     const { container } = mountWithHydration('<!---->', () => t0())
     expect(container.innerHTML).toBe('<!---->')
     expect(`Hydration children mismatch in <div>`).not.toHaveBeenWarned()
   })
 
-  // test('static before text', () => {
-  //   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()
-  //       const n3 = createTextNode()
-  //       renderEffect(() => setText(n3, toDisplayString(msg.value)))
-  //       return [n0, n1, n2, n3]
-  //     },
-  //   )
-  // })
-
-  // test('static (multiple elements)', () => {
-  //   const staticContent = '<div></div><span>hello</span>'
-  //   const html = `<div><div>hi</div>` + staticContent + `<div>ho</div></div>`
-
-  //   const n1 = h('div', 'hi')
-  //   const s = createStaticVNode('', 2)
-  //   const n2 = h('div', 'ho')
-
-  //   const { container } = mountWithHydration(html, () => h('div', [n1, s, n2]))
-
-  //   const div = container.firstChild!
-
-  //   expect(n1.el).toBe(div.firstChild)
-  //   expect(n2.el).toBe(div.lastChild)
-  //   expect(s.el).toBe(div.childNodes[1])
-  //   expect(s.anchor).toBe(div.childNodes[2])
-  //   expect(s.children).toBe(staticContent)
-  // })
-
-  // // #6008
-  // test('static (with text node as starting node)', () => {
-  //   const html = ` A <span>foo</span> B`
-  //   const { vnode, container } = mountWithHydration(html, () =>
-  //     createStaticVNode(` A <span>foo</span> B`, 3),
-  //   )
-  //   expect(vnode.el).toBe(container.firstChild)
-  //   expect(vnode.anchor).toBe(container.lastChild)
-  //   expect(`Hydration node mismatch`).not.toHaveBeenWarned()
-  // })
-
-  // test('static with content adoption', () => {
-  //   const html = ` A <span>foo</span> B`
-  //   const { vnode, container } = mountWithHydration(html, () =>
-  //     createStaticVNode(``, 3),
-  //   )
-  //   expect(vnode.el).toBe(container.firstChild)
-  //   expect(vnode.anchor).toBe(container.lastChild)
-  //   expect(vnode.children).toBe(html)
-  //   expect(`Hydration node mismatch`).not.toHaveBeenWarned()
-  // })
-
-  // test('element with text children', async () => {
-  //   const msg = ref('foo')
-  //   const { vnode, container } = mountWithHydration(
-  //     '<div class="foo">foo</div>',
-  //     () => h('div', { class: msg.value }, msg.value),
-  //   )
-  //   expect(vnode.el).toBe(container.firstChild)
-  //   expect(container.firstChild!.textContent).toBe('foo')
-  //   msg.value = 'bar'
-  //   await nextTick()
-  //   expect(container.innerHTML).toBe(`<div class="bar">bar</div>`)
-  // })
+  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]
+      },
+    )
+    expect(container.innerHTML).toBe(' A<span>foo bar</span>hello')
+    msg.value = 'bar'
+    await nextTick()
+    expect(container.innerHTML).toBe(' A<span>foo bar</span>bar')
+  })
 
-  // // #7285
-  // test('element with multiple continuous text vnodes', async () => {
-  //   // should no mismatch warning
-  //   const { container } = mountWithHydration('<div>foo0o</div>', () =>
-  //     h('div', ['fo', createTextVNode('o'), 0, 'o']),
-  //   )
-  //   expect(container.textContent).toBe('foo0o')
-  // })
+  test('empty element', async () => {
+    const t0 = template('<div></div>', true)
+    const { container } = mountWithHydration('<div></div>', () => t0())
+    expect(container.innerHTML).toBe('<div></div>')
+    expect(`Hydration children mismatch in <div>`).not.toHaveBeenWarned()
+  })
 
-  // test('element with elements children', 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.children as VNode[])[0].el).toBe(
-  //     container.firstChild!.childNodes[0],
-  //   )
-  //   expect((vnode.children as VNode[])[1].el).toBe(
-  //     container.firstChild!.childNodes[1],
-  //   )
+  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
+      },
+    )
+    expect(container.innerHTML).toBe(`<div class="foo">foo</div>`)
+    msg.value = 'bar'
+    await nextTick()
+    expect(container.innerHTML).toBe(`<div class="bar">bar</div>`)
+  })
 
-  //   // event handler
-  //   triggerEvent('click', vnode.el.querySelector('.foo')!)
-  //   expect(fn).toHaveBeenCalled()
+  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>`,
+    )
+
+    // event handler
+    triggerEvent('click', container.querySelector('.foo')!)
+    expect(fn).toHaveBeenCalled()
 
-  //   msg.value = 'bar'
-  //   await nextTick()
-  //   expect(vnode.el.innerHTML).toBe(`<span>bar</span><span class="bar"></span>`)
-  // })
+    msg.value = 'bar'
+    await nextTick()
+    expect(container.innerHTML).toBe(
+      `<div><span>bar</span><span class="bar"></span></div>`,
+    )
+  })
 
   // test('element with ref', () => {
   //   const el = ref()
index 3bfc19cbe83232e7a3d1dca8feb7429740ea47e5..af6fe0ec18951a0380e88a5abd794bcf2f1e2ade 100644 (file)
@@ -116,6 +116,8 @@ function adoptHydrationNodeImpl(
         !template.startsWith((adopted as Text).data))
     ) {
       // TODO recover and provide more info
+      console.error(`adopted: `, adopted)
+      console.error(`template: ${template}`)
       throw new Error('hydration mismatch!')
     }
   }