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