From: Evan You Date: Sun, 9 Mar 2025 12:14:03 +0000 (+0800) Subject: wip(vapor): text hydration tests X-Git-Tag: v3.6.0-alpha.1~16^2~45 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=a2415de7bf1b889cc2f1ed79d9f2773a004ab5df;p=thirdparty%2Fvuejs%2Fcore.git wip(vapor): text hydration tests --- diff --git a/packages/compiler-vapor/src/transforms/transformChildren.ts b/packages/compiler-vapor/src/transforms/transformChildren.ts index 12bf743208..9b76d86f32 100644 --- a/packages/compiler-vapor/src/transforms/transformChildren.ts +++ b/packages/compiler-vapor/src/transforms/transformChildren.ts @@ -69,7 +69,6 @@ function processDynamicChildren(context: TransformContext) { 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!), diff --git a/packages/runtime-vapor/__tests__/hydration.spec.ts b/packages/runtime-vapor/__tests__/hydration.spec.ts index 7422f6e6dc..8abf1570d5 100644 --- a/packages/runtime-vapor/__tests__/hydration.spec.ts +++ b/packages/runtime-vapor/__tests__/hydration.spec.ts @@ -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('
', true) - const { container } = mountWithHydration('
', () => t0()) - expect(container.innerHTML).toBe('
') - expect(`Hydration children mismatch in
`).not.toHaveBeenWarned() - }) - - test('comment', () => { + test('root comment', () => { const t0 = template('') const { container } = mountWithHydration('', () => t0()) expect(container.innerHTML).toBe('') expect(`Hydration children mismatch in
`).not.toHaveBeenWarned() }) - // test('static before text', () => { - // const t0 = template(' A ') - // const t1 = template('foo bar') - // const t2 = template(' ') - // const msg = ref('hello') - // const { container } = mountWithHydration( - // ' A foo barhello', - // () => { - // 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 = '
hello' - // const html = `
hi
` + staticContent + `
ho
` - - // 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 foo B` - // const { vnode, container } = mountWithHydration(html, () => - // createStaticVNode(` A foo 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 foo 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( - // '
foo
', - // () => 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(`
bar
`) - // }) + test('root with mixed element and text', async () => { + const t0 = template(' A') + const t1 = template('foo bar') + const t2 = template(' ') + const msg = ref('hello') + const { container } = mountWithHydration( + ' Afoo barhello', + () => { + 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(' Afoo barhello') + msg.value = 'bar' + await nextTick() + expect(container.innerHTML).toBe(' Afoo barbar') + }) - // // #7285 - // test('element with multiple continuous text vnodes', async () => { - // // should no mismatch warning - // const { container } = mountWithHydration('
foo0o
', () => - // h('div', ['fo', createTextVNode('o'), 0, 'o']), - // ) - // expect(container.textContent).toBe('foo0o') - // }) + test('empty element', async () => { + const t0 = template('
', true) + const { container } = mountWithHydration('
', () => t0()) + expect(container.innerHTML).toBe('
') + expect(`Hydration children mismatch in
`).not.toHaveBeenWarned() + }) - // test('element with elements children', async () => { - // const msg = ref('foo') - // const fn = vi.fn() - // const { vnode, container } = mountWithHydration( - // '
foo
', - // () => - // 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('
', true) + const msg = ref('foo') + const { container } = mountWithHydration( + '
foo
', + () => { + 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(`
foo
`) + msg.value = 'bar' + await nextTick() + expect(container.innerHTML).toBe(`
bar
`) + }) - // // event handler - // triggerEvent('click', vnode.el.querySelector('.foo')!) - // expect(fn).toHaveBeenCalled() + test('element with elements children', async () => { + const t0 = template('
', true) + const msg = ref('foo') + const fn = vi.fn() + const { container } = mountWithHydration( + '
foo
', + () => { + 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( + `
foo
`, + ) + + // event handler + triggerEvent('click', container.querySelector('.foo')!) + expect(fn).toHaveBeenCalled() - // msg.value = 'bar' - // await nextTick() - // expect(vnode.el.innerHTML).toBe(`bar`) - // }) + msg.value = 'bar' + await nextTick() + expect(container.innerHTML).toBe( + `
bar
`, + ) + }) // test('element with ref', () => { // const el = ref() diff --git a/packages/runtime-vapor/src/dom/hydration.ts b/packages/runtime-vapor/src/dom/hydration.ts index 3bfc19cbe8..af6fe0ec18 100644 --- a/packages/runtime-vapor/src/dom/hydration.ts +++ b/packages/runtime-vapor/src/dom/hydration.ts @@ -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!') } }