import { VaporTeleport, child, createComponent, createPlainElement, createVaporSSRApp, defineVaporAsyncComponent, defineVaporComponent, delegateEvents, renderEffect, setStyle, template, useVaporCssVars, } from '../src' import { defineAsyncComponent, nextTick, reactive, ref } from '@vue/runtime-dom' import { isString } from '@vue/shared' import type { VaporComponentInstance } from '../src/component' import type { TeleportFragment } from '../src/components/Teleport' import { VueServerRenderer, compile, runtimeDom, runtimeVapor } from './_utils' const formatHtml = (raw: string) => { return raw .replace(//g, ']-->\n') .replace(/\n{2,}/g, '\n') } async function testWithVaporApp( code: string, components?: Record, data?: any, ) { return testHydration(code, components, data, { isVaporApp: true, interop: true, }) } async function testWithVDOMApp( code: string, components?: Record, data?: any, ) { return testHydration(code, components, data, { isVaporApp: false, interop: true, }) } function compileVaporComponent( code: string, data: runtimeDom.Ref = ref({}), components?: Record, ssr = false, ) { if (!code.includes(`${code}` } return compile(code, data, components, { vapor: true, ssr, }) } async function mountWithHydration( html: string, code: string, data: runtimeDom.Ref = ref({}), components?: Record, ) { const container = document.createElement('div') container.innerHTML = html document.body.appendChild(container) const clientComp = compileVaporComponent(code, data, components) const app = createVaporSSRApp(clientComp) app.mount(container) return { block: (app._instance! as VaporComponentInstance).block, container, } } async function testHydration( code: string, components: Record = {}, data: any = ref('foo'), { isVaporApp = true, interop = false } = {}, ) { const ssrComponents: any = {} const clientComponents: any = {} for (const key in components) { const comp = components[key] const code = isString(comp) ? comp : comp.code const isVaporComp = isString(comp) || !!comp.vapor clientComponents[key] = compile(code, data, clientComponents, { vapor: isVaporComp, ssr: false, }) ssrComponents[key] = compile(code, data, ssrComponents, { vapor: isVaporComp, ssr: true, }) } const serverComp = compile(code, data, ssrComponents, { vapor: isVaporApp, ssr: true, }) const html = await VueServerRenderer.renderToString( runtimeDom.createSSRApp(serverComp), ) const container = document.createElement('div') document.body.appendChild(container) container.innerHTML = html const clientComp = compile(code, data, clientComponents, { vapor: isVaporApp, ssr: false, }) let app if (isVaporApp) { app = createVaporSSRApp(clientComp) } else { app = runtimeDom.createSSRApp(clientComp) } if (interop) { app.use(runtimeVapor.vaporInteropPlugin) } app.mount(container) return { data, container } } const triggerEvent = (type: string, el: Element) => { const event = new Event(type, { bubbles: true }) el.dispatchEvent(event) } delegateEvents('click') beforeEach(() => { document.body.innerHTML = '' }) describe('Vapor Mode hydration', () => { describe('text', () => { test('root text', async () => { const { data, container } = await testHydration(` `) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(`"foo"`) data.value = 'bar' await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(`"bar"`) }) test('consecutive text nodes', async () => { const { data, container } = await testHydration(` `) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(`"foofoo"`) data.value = 'bar' await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(`"barbar"`) }) test('consecutive text nodes with insertion anchor', async () => { const { data, container } = await testHydration(` `) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` " foofoo " `, ) data.value = 'bar' await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` " barbar " `, ) }) test('mixed text nodes', async () => { const { data, container } = await testHydration(` `) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( `"fooAfooBfoo"`, ) data.value = 'bar' await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( `"barAbarBbar"`, ) }) test('mixed text nodes with insertion anchor', async () => { const { data, container } = await testHydration(` `) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` " fooAfooBfoo " `, ) data.value = 'bar' await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` " barAbarBbar " `, ) }) test('empty text node', async () => { const data = reactive({ txt: '' }) const { container } = await testHydration( ``, undefined, data, ) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( `"
"`, ) data.txt = 'foo' await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( `"
foo
"`, ) }) test('empty text node in slot', async () => { const data = reactive({ txt: '' }) const { container } = await testHydration( ``, { Child: ``, }, data, ) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` " " `, ) data.txt = 'foo' await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` " foo " `, ) }) }) describe('element', () => { test('root comment', async () => { const { container } = await testHydration(` `) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(`""`) expect(`mismatch in
`).not.toHaveBeenWarned() }) test('root with mixed element and text', async () => { const { container, data } = await testHydration(` `) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` " Afoofoo " `, ) data.value = 'bar' await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` " Abarbar " `, ) }) test('empty element', async () => { const { container } = await testHydration(` `) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( `"
"`, ) expect(`mismatch in
`).not.toHaveBeenWarned() }) test('element with binding and text children', async () => { const { container, data } = await testHydration(` `) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( `"
foo
"`, ) data.value = 'bar' await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( `"
bar
"`, ) }) test('element with elements children', async () => { const { container } = await testHydration(` `) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( `"
foo
"`, ) // event handler triggerEvent('click', container.querySelector('.foo')!) await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( `"
bar
"`, ) }) test('element with ref', async () => { const { data, container } = await testHydration( ` `, {}, ref(null), ) expect(data.value).toBe(container.firstChild) }) }) describe('component', () => { test('basic component', async () => { const { container, data } = await testHydration( ` `, { Child: `` }, ) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( `"
foo
"`, ) data.value = 'bar' await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( `"
bar
"`, ) }) test('fragment component', async () => { const { container, data } = await testHydration( ` `, { Child: `` }, ) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
foo
-foo-
" `, ) data.value = 'bar' await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
bar
-bar-
" `, ) }) test('fragment component with prepend', async () => { const { container, data } = await testHydration( ` `, { Child: `` }, ) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
foo
-foo-
" `, ) data.value = 'bar' await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
bar
-bar-
" `, ) }) test('nested fragment components', async () => { const { container, data } = await testHydration( ` `, { Parent: ``, Child: ``, }, ) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
foo
-foo-
" `, ) data.value = 'bar' await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
bar
-bar-
" `, ) }) test('component with insertion anchor', async () => { const { container, data } = await testHydration( ` `, { Child: ``, }, ) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( `"
foo
"`, ) data.value = 'bar' await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( `"
bar
"`, ) }) test('nested components with insertion anchor', async () => { const { container, data } = await testHydration( ` `, { Parent: ``, Child: ``, }, ) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( `"
foo
"`, ) data.value = 'bar' await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( `"
bar
"`, ) }) test('nested components with multi level anchor insertion', async () => { const { container, data } = await testHydration( ` `, { Parent: ``, Child: ``, }, ) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( `"
foo
"`, ) data.value = 'bar' await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( `"
bar
"`, ) }) test('consecutive components with insertion parent', async () => { const data = reactive({ foo: 'foo', bar: 'bar' }) const { container } = await testHydration( ` `, { Child1: ``, Child2: ``, }, data, ) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( `"
foobar
"`, ) data.foo = 'foo1' data.bar = 'bar1' await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( `"
foo1bar1
"`, ) }) test('nested consecutive components with insertion anchor', async () => { const { container, data } = await testHydration( ` `, { Parent: ``, Child: ``, }, ) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( `"
foo
foo
"`, ) data.value = 'bar' await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( `"
bar
bar
"`, ) }) test('nested consecutive components with multi level anchor insertion', async () => { const { container, data } = await testHydration( ` `, { Parent: ``, Child: ``, }, ) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( `"
foo
foo
"`, ) data.value = 'bar' await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( `"
bar
bar
"`, ) }) test('mixed component and element with insertion anchor', async () => { const { container, data } = await testHydration( ` `, { Child: ``, }, ) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( `"
foofoo
"`, ) data.value = 'bar' await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( `"
barbar
"`, ) }) test('fragment component with insertion anchor', async () => { const { container, data } = await testHydration( ` `, { Child: ``, }, ) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
foo
-foo
" `, ) data.value = 'bar' await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
bar
-bar
" `, ) }) test('nested fragment component with insertion anchor', async () => { const { container, data } = await testHydration( ` `, { Parent: ``, Child: ``, }, ) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
foo
-foo-
" `, ) data.value = 'bar' await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
bar
-bar-
" `, ) }) test('nested fragment component with multi level anchor insertion', async () => { const { container, data } = await testHydration( ` `, { Parent: ``, Child: ``, }, ) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
foo
-foo-
" `, ) data.value = 'bar' await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
bar
-bar-
" `, ) }) test('consecutive fragment components with insertion anchor', async () => { const { container, data } = await testHydration( ` `, { Child: ``, }, ) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
foo
-foo
foo
-foo
" `, ) data.value = 'bar' await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
bar
-bar
bar
-bar
" `, ) }) test('nested consecutive fragment components with insertion anchor', async () => { const { container, data } = await testHydration( ` `, { Parent: ``, Child: ``, }, ) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
foo
-foo-
foo
-foo-
" `, ) data.value = 'bar' await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
bar
-bar-
bar
-bar-
" `, ) }) test('nested consecutive fragment components with multi level anchor insertion', async () => { const { container, data } = await testHydration( ` `, { Parent: ``, Child: ``, }, ) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
foo
-foo-
foo
-foo-
" `, ) data.value = 'bar' await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
bar
-bar-
bar
-bar-
" `, ) }) test('nested consecutive fragment components with root level anchor insertion', async () => { const { container, data } = await testHydration( ` `, { Parent: ``, Child: ``, }, ) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
foo
-foo-
foo
-foo-
" `, ) data.value = 'bar' await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
bar
-bar-
bar
-bar-
" `, ) }) test('mixed fragment component and element with insertion anchor', async () => { const { container, data } = await testHydration( ` `, { Child: ``, }, ) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
foo
-foo
foo
-foo
" `, ) data.value = 'bar' await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
bar
-bar
bar
-bar
" `, ) }) test('mixed fragment component and text with insertion anchor', async () => { const { container, data } = await testHydration( ` `, { Child: ``, }, ) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
foo
-foo foo
foo
-foo
" `, ) data.value = 'bar' await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
bar
-bar bar
bar
-bar
" `, ) }) }) describe('dynamic component', () => { test('basic dynamic component', async () => { const { container, data } = await testHydration( ``, { foo: ``, bar: ``, }, ref('foo'), ) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( `"
foo
"`, ) data.value = 'bar' await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( `"
bar
"`, ) }) test('dynamic component with insertion anchor', async () => { const { container, data } = await testHydration( ``, { foo: ``, bar: ``, }, ref('foo'), ) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( `"
foo
"`, ) data.value = 'bar' await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( `"
bar
"`, ) }) test('consecutive dynamic components with insertion anchor', async () => { const { container, data } = await testHydration( ``, { foo: ``, bar: ``, }, ref('foo'), ) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( `"
foo
foo
"`, ) data.value = 'bar' await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( `"
bar
bar
"`, ) }) test('dynamic component fallback', async () => { const { container, data } = await testHydration( ``, {}, ref('foo'), ) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( `""`, ) data.value = 'bar' await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( `""`, ) }) test('in ssr slot vnode fallback', async () => { const { container, data } = await testHydration( ``, { Child: ` `, }, ref('foo'), ) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
foo
" `, ) data.value = 'bar' await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
bar
" `, ) }) test('dynamic component fallback with dynamic slots', async () => { const data = ref({ name: 'default', msg: 'foo', }) const { container } = await testHydration( ``, {}, data, ) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( `"
foo
"`, ) data.value.msg = 'bar' await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( `"
bar
"`, ) }) }) describe('if', () => { test('basic toggle - true -> false', async () => { const data = ref(true) const { container } = await testHydration( ``, undefined, data, ) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( `"
foo
"`, ) data.value = false await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( `""`, ) }) test('basic toggle - false -> true', async () => { const data = ref(false) const { container } = await testHydration( ``, undefined, data, ) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( `""`, ) data.value = true await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( `"
foo
"`, ) }) test('v-if on insertion parent', async () => { const data = ref(true) const { container } = await testHydration( ``, { Child: `` }, data, ) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( `"
foo
"`, ) data.value = false await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( `""`, ) data.value = true await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( `"
foo
"`, ) }) test('v-if/else-if/else chain - switch branches', async () => { const data = ref('a') const { container } = await testHydration( ``, undefined, data, ) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( `"
foo
"`, ) data.value = 'b' await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( `"
bar
"`, ) data.value = 'c' await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( `"
baz
"`, ) data.value = 'a' await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( `"
foo
"`, ) }) test('nested if', async () => { const data = reactive({ outer: true, inner: true }) const { container } = await testHydration( ``, undefined, data, ) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( `"
outer
inner
"`, ) data.inner = false await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( `"
outer
"`, ) data.outer = false await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( `""`, ) }) test('on component', async () => { const data = ref(true) const { container } = await testHydration( ``, { Child: `` }, data, ) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( `"foo"`, ) data.value = false await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( `""`, ) }) test('consecutive if node', async () => { const data = ref(true) const { container } = await testHydration( ``, { Child: `` }, data, ) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( `"
foo
"`, ) data.value = false await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( `""`, ) data.value = true await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( `"
foo
"`, ) }) test('mixed prepend and insertion anchor', async () => { const data = reactive({ show: true, foo: 'foo', bar: 'bar', qux: 'qux', }) const { container } = await testHydration( ``, { Child: ``, }, data, ) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( `"foobarbazquxquux"`, ) data.qux = 'qux1' await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( `"foobarbazqux1quux"`, ) data.foo = 'foo1' await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( `"foo1barbazqux1quux"`, ) }) test('v-if/else-if/else chain on component - switch branches', async () => { const data = ref('a') const { container } = await testHydration( ``, { Child1: ``, Child2: ``, Child3: ``, }, data, ) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( `"a child1"`, ) data.value = 'b' await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( `"b child2"`, ) data.value = 'c' await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( `"c child3"`, ) data.value = 'a' await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( `"a child1"`, ) }) test('on component with insertion anchor', async () => { const data = ref(true) const { container } = await testHydration( ``, { Child: `` }, data, ) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( `"
foo
"`, ) data.value = false await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( `"
"`, ) }) test('consecutive component with insertion parent', async () => { const data = reactive({ show: true, foo: 'foo', bar: 'bar', }) const { container } = await testHydration( ``, { Child: ``, Child2: ``, }, data, ) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( `"
foobar
"`, ) data.show = false await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( `""`, ) data.show = true await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( `"
foobar
"`, ) data.foo = 'foo1' data.bar = 'bar1' await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( `"
foo1bar1
"`, ) }) test('on fragment component', async () => { const data = ref(true) const { container } = await testHydration( ``, { Child: ``, }, data, ) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
true
-true-
" `, ) data.value = false await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
" `, ) data.value = true await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
true
-true-
" `, ) }) test('on fragment component with insertion anchor', async () => { const data = ref(true) const { container } = await testHydration( ``, { Child: ``, }, data, ) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
true
-true-
" `, ) data.value = false await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
" `, ) data.value = true await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(` "
true
-true-
" `) }) test('consecutive v-if on fragment component with insertion anchor', async () => { const data = ref(true) const { container } = await testHydration( ``, { Child: ``, }, data, ) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
true
-true-
true
-true-
" `, ) data.value = false await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
" `, ) data.value = true await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(` "
true
-true-
true
-true-
" `) }) test('on dynamic component with insertion anchor', async () => { const data = ref(true) const { container } = await testHydration( ``, { Child: `` }, data, ) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( `"
foo
"`, ) data.value = false await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( `"
"`, ) data.value = true await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( `"
foo
"`, ) }) }) describe('for', () => { test('basic v-for', async () => { const { container, data } = await testHydration( ``, undefined, ref(['a', 'b', 'c']), ) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` " abc " `, ) data.value.push('d') await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` " abcd " `, ) }) test('empty v-for', async () => { const { container, data } = await testHydration( ``, undefined, ref([]), ) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` " " `, ) data.value.push('a') await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` " a " `, ) }) test('v-for with insertion parent + sibling component', async () => { const { container, data } = await testHydration( ``, { Child: ``, }, ref(['a', 'b', 'c']), ) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
abc
3
" `, ) data.value.push('d') await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
abcd
4
" `, ) }) test('v-for with insertion anchor', async () => { const { container, data } = await testHydration( ``, undefined, ref(['a', 'b', 'c']), ) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
abc
" `, ) data.value.push('d') await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
abcd
" `, ) data.value.splice(0, 1) await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
bcd
" `, ) }) test('consecutive v-for with insertion anchor', async () => { const { container, data } = await testHydration( ``, undefined, ref(['a', 'b', 'c']), ) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
abc abc
" `, ) data.value.push('d') await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
abcd abcd
" `, ) data.value.splice(0, 2) await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
cd cd
" `, ) }) test('v-for on component', async () => { const { container, data } = await testHydration( ``, { Child: ``, }, ref(['a', 'b', 'c']), ) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
comp
comp
comp
" `, ) data.value.push('d') await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
comp
comp
comp
comp
" `, ) }) test('v-for on component with slots', async () => { const { container, data } = await testHydration( ``, { Child: ``, }, ref(['a', 'b', 'c']), ) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
a b c
" `, ) data.value.push('d') await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
a b c d
" `, ) }) test('on fragment component', async () => { const { container, data } = await testHydration( ``, { Child: ``, }, ref(['a', 'b', 'c']), ) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
foo
-bar-
foo
-bar-
foo
-bar-
" `, ) data.value.push('d') await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
foo
-bar-
foo
-bar-
foo
-bar-
foo
-bar-
" `, ) }) test('on component with non-hydration node', async () => { const data = ref({ show: true, msg: 'foo' }) const { container } = await testHydration( ``, { Child: ``, }, data, ) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
foo
non-hydration node
foo
non-hydration node
" `, ) data.value.msg = 'bar' await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
bar
non-hydration node
bar
non-hydration node
" `, ) data.value.show = false await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(` "
non-hydration node
non-hydration node
" `) data.value.show = true await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(` "
bar
non-hydration node
bar
non-hydration node
" `) }) test('with non-hydration node', async () => { const data = ref({ show: true, msg: 'foo' }) const { container } = await testHydration( ``, {}, data, ) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
foo
non-hydration node
foo
non-hydration node
" `, ) data.value.msg = 'bar' await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
bar
non-hydration node
bar
non-hydration node
" `, ) data.value.show = false await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(` "
non-hydration node
non-hydration node
" `) data.value.show = true await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(` "
bar
non-hydration node
bar
non-hydration node
" `) }) }) describe('slots', () => { test('basic slot', async () => { const { data, container } = await testHydration( ``, { Child: ``, }, ) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` " foo " `, ) data.value = 'bar' await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` " bar " `, ) }) test('named slot', async () => { const { data, container } = await testHydration( ``, { Child: ``, }, ) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` " foo " `, ) data.value = 'bar' await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` " bar " `, ) }) test('named slot with v-if', async () => { const { data, container } = await testHydration( ``, { Child: ``, }, ) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` " foo " `, ) data.value = false await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` " " `, ) data.value = true await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(` " true " `) }) test('named slot with v-if and v-for', async () => { const data = reactive({ show: true, items: ['a', 'b', 'c'], }) const { container } = await testHydration( ``, { Child: ``, }, data, ) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` " abc " `, ) data.show = false await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` " " `, ) data.show = true await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` " abc " `, ) }) test('with insertion anchor', async () => { const { data, container } = await testHydration( ``, { Child: ``, }, ) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` " foo " `, ) data.value = 'bar' await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` " bar " `, ) }) test('with multi level anchor insertion', async () => { const { data, container } = await testHydration( ``, { Child: ` `, }, ) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
foo
" `, ) data.value = 'bar' await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
bar
" `, ) }) test('mixed slot and text node', async () => { const data = reactive({ text: 'foo', msg: 'hi', }) const { container } = await testHydration( ``, { Child: ``, }, data, ) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
foo hi
" `, ) data.msg = 'bar' await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
foo bar
" `, ) }) test('mixed root slot and text node', async () => { const data = reactive({ text: 'foo', msg: 'hi', }) const { container } = await testHydration( ``, { Child: ``, }, data, ) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` " foo foo hi " `, ) data.msg = 'bar' await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` " foo foo bar " `, ) }) test('mixed consecutive slot and element', async () => { const data = reactive({ text: 'foo', msg: 'hi', }) const { container } = await testHydration( ``, { Child: ``, }, data, ) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
foo bar
hi
" `, ) data.msg = 'bar' await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
foo bar
bar
" `, ) }) test('mixed slot and element', async () => { const data = reactive({ text: 'foo', msg: 'hi', }) const { container } = await testHydration( ``, { Child: ``, }, data, ) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
foo
hi
" `, ) data.msg = 'bar' await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
foo
bar
" `, ) }) test('mixed slot and component', async () => { const data = reactive({ msg1: 'foo', msg2: 'bar', }) const { container } = await testHydration( ``, { Child: ` `, Child2: ` `, }, data, ) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
bar
foo
bar
" `, ) data.msg2 = 'hello' await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
hello
foo
hello
" `, ) }) test('mixed slot and fragment component', async () => { const data = reactive({ msg1: 'foo', msg2: 'bar', }) const { container } = await testHydration( ``, { Child: ` `, Child2: ` `, }, data, ) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
foo
bar foo
foo
bar
" `, ) data.msg1 = 'hello' data.msg2 = 'vapor' await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
hello
vapor hello
hello
vapor
" `, ) }) test('mixed slot and v-if', async () => { const data = reactive({ show: true, msg: 'foo', }) const { container } = await testHydration( ``, { Child: ` `, }, data, ) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
foo
foo
foo
" `, ) data.show = false await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` " foo " `, ) data.show = true await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(` "
foo
foo
foo
" `) }) test('mixed slot and v-for', async () => { const data = reactive({ items: ['a', 'b', 'c'], msg: 'foo', }) const { container } = await testHydration( ``, { Child: ` `, }, data, ) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
a
b
c
foo
a
b
c
" `, ) data.items.push('d') await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
a
b
c
d
foo
a
b
c
d
" `, ) }) test('consecutive slots', async () => { const data = reactive({ msg1: 'foo', msg2: 'bar', }) const { container } = await testHydration( ``, { Child: ``, }, data, ) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` " foo bar " `, ) data.msg1 = 'hello' data.msg2 = 'vapor' await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` " hello vapor " `, ) }) test('consecutive slots with insertion anchor', async () => { const data = reactive({ msg1: 'foo', msg2: 'bar', }) const { container } = await testHydration( ``, { Child: ``, }, data, ) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
foo bar
" `, ) data.msg1 = 'hello' data.msg2 = 'vapor' await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
hello vapor
" `, ) }) test('consecutive slots prepend', async () => { const data = reactive({ msg1: 'foo', msg2: 'bar', msg3: 'baz', }) const { container } = await testHydration( ``, { Child: ``, }, data, ) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
foo bar
baz
" `, ) data.msg1 = 'hello' data.msg2 = 'vapor' await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
hello vapor
baz
" `, ) }) test('slot fallback', async () => { const data = reactive({ foo: 'foo', }) const { container } = await testHydration( ``, { Child: ``, }, data, ) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` " foo " `, ) data.foo = 'bar' await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` " bar " `, ) }) test('forwarded slot', async () => { const data = reactive({ foo: 'foo', bar: 'bar', }) const { container } = await testHydration( ``, { Parent: ``, Child: ``, }, data, ) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
foo
bar
" `, ) data.foo = 'foo1' data.bar = 'bar1' await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
foo1
bar1
" `, ) }) test('forwarded slot with fallback', async () => { const data = reactive({ foo: 'foo', }) const { container } = await testHydration( ``, { Parent: ``, Child: ``, }, data, ) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
foo
" `, ) data.foo = 'foo1' await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
foo1
" `, ) }) test('forwarded slot with empty content', async () => { const data = reactive({ foo: 'foo', }) const { container } = await testHydration( ``, { Foo: ``, Bar: ``, Baz: ``, Qux: ``, }, data, ) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
foo
" `, ) data.foo = 'bar' await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
bar
" `, ) }) }) describe('transition', async () => { test('transition appear', async () => { const { container } = await testHydration( ``, ) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( `"
foo
"`, ) expect(`mismatch`).not.toHaveBeenWarned() }) test('transition appear work with pre-existing class', async () => { const { container } = await testHydration( ``, ) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( `"
foo
"`, ) expect(`mismatch`).not.toHaveBeenWarned() }) test('transition appear work with empty content', async () => { const data = ref(true) const { container } = await testHydration( ``, undefined, data, ) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( `""`, ) expect(`mismatch`).not.toHaveBeenWarned() data.value = false await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( `"foo"`, ) }) test('transition appear with v-if', async () => { const data = ref(false) const { container } = await testHydration( ``, undefined, data, ) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( `""`, ) expect(`mismatch`).not.toHaveBeenWarned() }) test('transition appear with v-show', async () => { const data = ref(false) const { container } = await testHydration( ``, undefined, data, ) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( `""`, ) expect(`mismatch`).not.toHaveBeenWarned() }) test('transition appear w/ event listener', async () => { const { container } = await testHydration( ` `, ) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( `""`, ) triggerEvent('click', container.querySelector('button')!) await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( `""`, ) }) }) describe('teleport', () => { test('basic', async () => { const data = ref({ msg: ref('foo'), disabled: ref(false), fn: vi.fn(), }) const teleportContainer = document.createElement('div') teleportContainer.id = 'teleport' teleportContainer.innerHTML = `` + `foo` + `` + `` document.body.appendChild(teleportContainer) const { block, container } = await mountWithHydration( '', ` {{data.msg}} `, data, ) const teleport = block as TeleportFragment expect(teleport.anchor).toBe(container.lastChild) expect(teleport.target).toBe(teleportContainer) expect(teleport.targetStart).toBe(teleportContainer.childNodes[0]) expect((teleport.nodes as Node[])[0]).toBe( teleportContainer.childNodes[1], ) expect((teleport.nodes as Node[])[1]).toBe( teleportContainer.childNodes[2], ) expect(teleport.targetAnchor).toBe(teleportContainer.childNodes[3]) expect(container.innerHTML).toMatchInlineSnapshot( `""`, ) // event handler triggerEvent('click', teleportContainer.querySelector('.foo')!) expect(data.value.fn).toHaveBeenCalled() data.value.msg = 'bar' await nextTick() expect(formatHtml(teleportContainer.innerHTML)).toBe( `` + `bar` + `` + ``, ) data.value.disabled = true await nextTick() expect(container.innerHTML).toBe( `` + `bar` + `` + ``, ) expect(formatHtml(teleportContainer.innerHTML)).toMatchInlineSnapshot( `""`, ) data.value.msg = 'baz' await nextTick() expect(container.innerHTML).toBe( `` + `baz` + `` + ``, ) data.value.disabled = false await nextTick() expect(container.innerHTML).toMatchInlineSnapshot( `""`, ) expect(formatHtml(teleportContainer.innerHTML)).toBe( `` + `baz` + `` + ``, ) }) test('multiple + integration', async () => { const data = ref({ msg: ref('foo'), fn1: vi.fn(), fn2: vi.fn(), }) const code = ` {{data.msg}} {{data.msg}}2 ` const SSRComp = compileVaporComponent(code, data, undefined, true) const teleportContainer = document.createElement('div') teleportContainer.id = 'teleport2' const ctx = {} as any const mainHtml = await VueServerRenderer.renderToString( runtimeDom.createSSRApp(SSRComp), ctx, ) expect(mainHtml).toBe( `` + `` + `` + ``, ) const teleportHtml = ctx.teleports!['#teleport2'] expect(teleportHtml).toBe( `` + `foo` + `` + `` + `foo2` + ``, ) teleportContainer.innerHTML = teleportHtml document.body.appendChild(teleportContainer) const { block, container } = await mountWithHydration( mainHtml, code, data, ) const teleports = block as any as TeleportFragment[] const teleport1 = teleports[0] const teleport2 = teleports[1] expect(teleport1.anchor).toBe(container.childNodes[2]) expect(teleport2.anchor).toBe(container.childNodes[4]) expect(teleport1.target).toBe(teleportContainer) expect(teleport1.targetStart).toBe(teleportContainer.childNodes[0]) expect((teleport1.nodes as Node[])[0]).toBe( teleportContainer.childNodes[1], ) expect(teleport1.targetAnchor).toBe(teleportContainer.childNodes[3]) expect(teleport2.target).toBe(teleportContainer) expect(teleport2.targetStart).toBe(teleportContainer.childNodes[4]) expect((teleport2.nodes as Node[])[0]).toBe( teleportContainer.childNodes[5], ) expect(teleport2.targetAnchor).toBe(teleportContainer.childNodes[7]) expect(container.innerHTML).toBe( `` + `` + `` + ``, ) // event handler triggerEvent('click', teleportContainer.querySelector('.foo')!) expect(data.value.fn1).toHaveBeenCalled() triggerEvent('click', teleportContainer.querySelector('.foo2')!) expect(data.value.fn2).toHaveBeenCalled() data.value.msg = 'bar' await nextTick() expect(teleportContainer.innerHTML).toBe( `` + `bar` + `` + `` + `` + `bar2` + `` + ``, ) }) test('disabled', async () => { const data = ref({ msg: ref('foo'), fn1: vi.fn(), fn2: vi.fn(), }) const code = `
foo
{{data.msg}}
bar
` const SSRComp = compileVaporComponent(code, data, undefined, true) const teleportContainer = document.createElement('div') teleportContainer.id = 'teleport3' const ctx = {} as any const mainHtml = await VueServerRenderer.renderToString( runtimeDom.createSSRApp(SSRComp), ctx, ) expect(mainHtml).toBe( `` + `
foo
` + `` + `foo` + `` + `` + `
bar
` + ``, ) const teleportHtml = ctx.teleports!['#teleport3'] expect(teleportHtml).toMatchInlineSnapshot( `""`, ) teleportContainer.innerHTML = teleportHtml document.body.appendChild(teleportContainer) const { block, container } = await mountWithHydration( mainHtml, code, data, ) const blocks = block as any[] expect(blocks[0]).toBe(container.childNodes[1]) const teleport = blocks[1] as TeleportFragment expect((teleport.nodes as Node[])[0]).toBe(container.childNodes[3]) expect((teleport.nodes as Node[])[1]).toBe(container.childNodes[4]) expect(teleport.anchor).toBe(container.childNodes[5]) expect(teleport.target).toBe(teleportContainer) expect(teleport.targetStart).toBe(teleportContainer.childNodes[0]) expect(teleport.targetAnchor).toBe(teleportContainer.childNodes[1]) expect(blocks[2]).toBe(container.childNodes[6]) expect(container.innerHTML).toBe( `` + `
foo
` + `` + `foo` + `` + `` + `
bar
` + ``, ) // event handler triggerEvent('click', container.querySelector('.foo')!) expect(data.value.fn1).toHaveBeenCalled() triggerEvent('click', container.querySelector('.foo2')!) expect(data.value.fn2).toHaveBeenCalled() data.value.msg = 'bar' await nextTick() expect(container.innerHTML).toBe( `` + `
foo
` + `` + `bar` + `` + `` + `
bar
` + ``, ) }) test('disabled + as component root', async () => { const { container } = await mountWithHydration( `` + `
Parent fragment
` + `
Teleport content
` + ``, `
Parent fragment
Teleport content
`, ) expect(container.innerHTML).toBe( `` + `
Parent fragment
` + `` + `
Teleport content
` + `` + ``, ) expect(`mismatch`).not.toHaveBeenWarned() }) test('as component root', async () => { const teleportContainer = document.createElement('div') teleportContainer.id = 'teleport4' teleportContainer.innerHTML = `hello` document.body.appendChild(teleportContainer) const { block, container } = await mountWithHydration( '', ``, undefined, { Wrapper: compileVaporComponent( `hello`, ), }, ) const teleport = (block as VaporComponentInstance) .block as TeleportFragment expect(teleport.anchor).toBe(container.childNodes[1]) expect(teleport.target).toBe(teleportContainer) expect(teleport.targetStart).toBe(teleportContainer.childNodes[0]) expect(teleport.nodes).toBe(teleportContainer.childNodes[1]) expect(teleport.targetAnchor).toBe(teleportContainer.childNodes[2]) }) test('nested', async () => { const teleportContainer = document.createElement('div') teleportContainer.id = 'teleport5' teleportContainer.innerHTML = `` + `` + `` + `` + `
child
` + `` document.body.appendChild(teleportContainer) const { block, container } = await mountWithHydration( '', `
child
`, ) const teleport = block as TeleportFragment expect(teleport.anchor).toBe(container.childNodes[1]) expect(teleport.targetStart).toBe(teleportContainer.childNodes[0]) expect(teleport.targetAnchor).toBe(teleportContainer.childNodes[3]) const childTeleport = teleport.nodes as TeleportFragment expect(childTeleport.anchor).toBe(teleportContainer.childNodes[2]) expect(childTeleport.targetStart).toBe(teleportContainer.childNodes[4]) expect(childTeleport.targetAnchor).toBe(teleportContainer.childNodes[6]) expect(childTeleport.nodes).toBe(teleportContainer.childNodes[5]) }) test('unmount (full integration)', async () => { const targetId = 'teleport6' const data = ref({ toggle: ref(true), }) const template1 = `Teleported Comp1` const Comp1 = compileVaporComponent(template1) const SSRComp1 = compileVaporComponent( template1, undefined, undefined, true, ) const template2 = `
Comp2
` const Comp2 = compileVaporComponent(template2) const SSRComp2 = compileVaporComponent( template2, undefined, undefined, true, ) const appCode = `
` const SSRApp = compileVaporComponent( appCode, data, { Comp1: SSRComp1, Comp2: SSRComp2, }, true, ) const teleportContainer = document.createElement('div') teleportContainer.id = targetId document.body.appendChild(teleportContainer) const ctx = {} as any const mainHtml = await VueServerRenderer.renderToString( runtimeDom.createSSRApp(SSRApp), ctx, ) expect(mainHtml).toBe( '
', ) teleportContainer.innerHTML = ctx.teleports![`#${targetId}`] const { container } = await mountWithHydration(mainHtml, appCode, data, { Comp1, Comp2, }) expect(container.innerHTML).toBe( '
', ) expect(teleportContainer.innerHTML).toBe( `` + `Teleported Comp1` + ``, ) expect(`mismatch`).not.toHaveBeenWarned() data.value.toggle = false await nextTick() expect(container.innerHTML).toBe('
Comp2
') expect(teleportContainer.innerHTML).toBe('') }) test('unmount (mismatch + full integration)', async () => { const targetId = 'teleport7' const data = ref({ toggle: ref(true), }) const template1 = `Teleported Comp1` const Comp1 = compileVaporComponent(template1) const SSRComp1 = compileVaporComponent( template1, undefined, undefined, true, ) const template2 = `
Comp2
` const Comp2 = compileVaporComponent(template2) const SSRComp2 = compileVaporComponent( template2, undefined, undefined, true, ) const appCode = `
` const SSRApp = compileVaporComponent( appCode, data, { Comp1: SSRComp1, Comp2: SSRComp2, }, true, ) const teleportContainer = document.createElement('div') teleportContainer.id = targetId document.body.appendChild(teleportContainer) const mainHtml = await VueServerRenderer.renderToString( runtimeDom.createSSRApp(SSRApp), ) expect(mainHtml).toBe( '
', ) expect(teleportContainer.innerHTML).toBe('') const { container } = await mountWithHydration(mainHtml, appCode, data, { Comp1, Comp2, }) expect(container.innerHTML).toBe( '
', ) expect(teleportContainer.innerHTML).toBe(`Teleported Comp1`) expect(`Hydration children mismatch`).toHaveBeenWarned() data.value.toggle = false await nextTick() expect(container.innerHTML).toBe('
Comp2
') expect(teleportContainer.innerHTML).toBe('') }) test('target change (mismatch + full integration)', async () => { const targetId1 = 'teleport8-1' const targetId2 = 'teleport8-2' const data = ref({ target: ref(targetId1), msg: ref('foo'), }) const template = `{{data.msg}}` const Comp = compileVaporComponent(template, data) const SSRComp = compileVaporComponent(template, data, undefined, true) const teleportContainer1 = document.createElement('div') teleportContainer1.id = targetId1 const teleportContainer2 = document.createElement('div') teleportContainer2.id = targetId2 document.body.appendChild(teleportContainer1) document.body.appendChild(teleportContainer2) // server render const mainHtml = await VueServerRenderer.renderToString( runtimeDom.createSSRApp(SSRComp), ) expect(mainHtml).toBe(``) expect(teleportContainer1.innerHTML).toBe('') expect(teleportContainer2.innerHTML).toBe('') // hydrate const { container } = await mountWithHydration(mainHtml, template, data, { Comp, }) expect(container.innerHTML).toBe( ``, ) expect(teleportContainer1.innerHTML).toBe(`foo`) expect(teleportContainer2.innerHTML).toBe('') expect(`Hydration children mismatch`).toHaveBeenWarned() data.value.target = targetId2 data.value.msg = 'bar' await nextTick() expect(container.innerHTML).toBe( ``, ) expect(teleportContainer1.innerHTML).toBe('') expect(teleportContainer2.innerHTML).toBe(`bar`) }) test('with disabled teleport + undefined target', async () => { const data = ref({ msg: ref('foo'), }) const { container } = await mountWithHydration( 'foo', ` {{data.msg}} `, data, ) expect(container.innerHTML).toBe( `foo`, ) data.value.msg = 'bar' await nextTick() expect(container.innerHTML).toBe( `bar`, ) }) test('should apply css vars after hydration', async () => { const state = reactive({ color: 'red' }) const teleportContainer = document.createElement('div') teleportContainer.id = 'teleport-css-vars' teleportContainer.innerHTML = `` + `content` + `` document.body.appendChild(teleportContainer) const App = defineVaporComponent({ setup() { useVaporCssVars(() => state) return createComponent( VaporTeleport, { to: () => '#teleport-css-vars' }, { default: () => template('content', true)() }, ) }, }) const container = document.createElement('div') container.innerHTML = '' document.body.appendChild(container) const app = createVaporSSRApp(App) app.mount(container) await nextTick() // css vars should be applied after hydration const span = teleportContainer.querySelector('span') as HTMLElement expect(span).toBeTruthy() expect(span.style.getPropertyValue('--color')).toBe('red') expect(span.hasAttribute('data-v-owner')).toBe(true) // css vars should update reactively state.color = 'green' await nextTick() expect(span.style.getPropertyValue('--color')).toBe('green') }) }) describe('async component', async () => { test('async component', async () => { const data = ref({ spy: vi.fn(), }) const compCode = `` const SSRComp = compileVaporComponent(compCode, data, undefined, true) let serverResolve: any // use defineAsyncComponent in SSR let AsyncComp = defineAsyncComponent( () => new Promise(r => { serverResolve = r }), ) const appCode = `helloworld` const SSRApp = compileVaporComponent(appCode, data, { AsyncComp }, true) // server render const htmlPromise = VueServerRenderer.renderToString( runtimeDom.createSSRApp(SSRApp), ) serverResolve(SSRComp) const html = await htmlPromise expect(html).toMatchInlineSnapshot( `"helloworld"`, ) // hydration let clientResolve: any AsyncComp = defineVaporAsyncComponent( () => new Promise(r => { clientResolve = r }), ) as any const Comp = compileVaporComponent(compCode, data) const App = compileVaporComponent(appCode, data, { AsyncComp }) const container = document.createElement('div') container.innerHTML = html document.body.appendChild(container) createVaporSSRApp(App).mount(container) // hydration not complete yet triggerEvent('click', container.querySelector('button')!) expect(data.value.spy).not.toHaveBeenCalled() // resolve clientResolve(Comp) await new Promise(r => setTimeout(r)) // should be hydrated now triggerEvent('click', container.querySelector('button')!) expect(data.value.spy).toHaveBeenCalled() }) // No longer needed, parent component updates in vapor mode no longer // cause child components to re-render // test.todo('update async wrapper before resolve', async () => {}) test('update async component after parent mount before async component resolve', async () => { const data = ref({ toggle: true, }) const compCode = ` ` const SSRComp = compileVaporComponent( compCode, undefined, undefined, true, ) let serverResolve: any // use defineAsyncComponent in SSR let AsyncComp = defineAsyncComponent( () => new Promise(r => { serverResolve = r }), ) const appCode = `` const SSRApp = compileVaporComponent(appCode, data, { AsyncComp }, true) // server render const htmlPromise = VueServerRenderer.renderToString( runtimeDom.createSSRApp(SSRApp), ) serverResolve(SSRComp) const html = await htmlPromise expect(html).toMatchInlineSnapshot(`"

Async component

"`) // hydration let clientResolve: any AsyncComp = defineVaporAsyncComponent( () => new Promise(r => { clientResolve = r }), ) as any const Comp = compileVaporComponent(compCode) const App = compileVaporComponent(appCode, data, { AsyncComp }) const container = document.createElement('div') container.innerHTML = html document.body.appendChild(container) createVaporSSRApp(App).mount(container) // update before resolve data.value.toggle = false await nextTick() // resolve clientResolve(Comp) await new Promise(r => setTimeout(r)) // prevent lazy hydration since the component has been patched expect('Skipping lazy hydration for component').toHaveBeenWarned() expect(`Hydration node mismatch`).not.toHaveBeenWarned() expect(container.innerHTML).toMatchInlineSnapshot( `"

Updated async component

"`, ) }) test('update async component (fragment root) after parent mount before async component resolve', async () => { const data = ref({ toggle: true, }) const compCode = ` ` const SSRComp = compileVaporComponent( compCode, undefined, undefined, true, ) let serverResolve: any // use defineAsyncComponent in SSR let AsyncComp = defineAsyncComponent( () => new Promise(r => { serverResolve = r }), ) const appCode = `` const SSRApp = compileVaporComponent(appCode, data, { AsyncComp }, true) // server render const htmlPromise = VueServerRenderer.renderToString( runtimeDom.createSSRApp(SSRApp), ) serverResolve(SSRComp) const html = await htmlPromise expect(html).toMatchInlineSnapshot( `"

Async component

fragment root

"`, ) // hydration let clientResolve: any AsyncComp = defineVaporAsyncComponent( () => new Promise(r => { clientResolve = r }), ) as any const Comp = compileVaporComponent(compCode) const App = compileVaporComponent(appCode, data, { AsyncComp }) const container = document.createElement('div') container.innerHTML = html document.body.appendChild(container) createVaporSSRApp(App).mount(container) // update before resolve data.value.toggle = false await nextTick() // resolve clientResolve(Comp) await new Promise(r => setTimeout(r)) // prevent lazy hydration since the component has been patched expect('Skipping lazy hydration for component').toHaveBeenWarned() expect(`Hydration node mismatch`).not.toHaveBeenWarned() expect(container.innerHTML).toMatchInlineSnapshot( `"

Updated async component

fragment root

"`, ) }) // required vapor Suspense test.todo('hydrate safely when property used by async setup changed before render', async () => {}) // required vapor Suspense test.todo('hydrate safely when property used by deep nested async setup changed before render', async () => {}) test('unmount async wrapper before load', async () => { const data = ref({ toggle: true, }) const compCode = `
async
` const appCode = `
hi
` // hydration let clientResolve: any const AsyncComp = defineVaporAsyncComponent( () => new Promise(r => { clientResolve = r }), ) const Comp = compileVaporComponent(compCode) const App = compileVaporComponent(appCode, data, { AsyncComp, }) const container = document.createElement('div') container.innerHTML = '
async
' createVaporSSRApp(App).mount(container) // unmount before resolve data.value.toggle = false await nextTick() expect(container.innerHTML).toBe(`
hi
`) // resolve clientResolve(Comp) await new Promise(r => setTimeout(r)) // should remain unmounted expect(container.innerHTML).toBe(`
hi
`) }) test('unmount async wrapper before load (fragment)', async () => { const data = ref({ toggle: true, }) const compCode = `
async
fragment
` const appCode = `
hi
` // hydration let clientResolve: any const AsyncComp = defineVaporAsyncComponent( () => new Promise(r => { clientResolve = r }), ) const Comp = compileVaporComponent(compCode) const App = compileVaporComponent(appCode, data, { AsyncComp, }) const container = document.createElement('div') container.innerHTML = '
async
fragment
' createVaporSSRApp(App).mount(container) // unmount before resolve data.value.toggle = false await nextTick() expect(container.innerHTML).toBe(`
hi
`) // resolve clientResolve(Comp) await new Promise(r => setTimeout(r)) // should remain unmounted expect(container.innerHTML).toBe(`
hi
`) }) test('nested async wrapper', async () => { const toggleCode = ` ` const SSRToggle = compileVaporComponent( toggleCode, undefined, undefined, true, ) const wrapperCode = `` const SSRWrapper = compileVaporComponent( wrapperCode, undefined, undefined, true, ) const data = ref({ count: 0, fn: vi.fn(), }) const childCode = ` ` const SSRChild = compileVaporComponent(childCode, data, undefined, true) const appCode = ` ` const SSRApp = compileVaporComponent( appCode, undefined, { Toggle: SSRToggle, Wrapper: SSRWrapper, Child: SSRChild, }, true, ) const root = document.createElement('div') // server render root.innerHTML = await VueServerRenderer.renderToString( runtimeDom.createSSRApp(SSRApp), ) expect(root.innerHTML).toMatchInlineSnapshot( `"
0
"`, ) const Toggle = compileVaporComponent(toggleCode) const Wrapper = compileVaporComponent(wrapperCode) const Child = compileVaporComponent(childCode, data) const App = compileVaporComponent(appCode, undefined, { Toggle, Wrapper, Child, }) // hydration createVaporSSRApp(App).mount(root) await nextTick() await nextTick() expect(root.innerHTML).toMatchInlineSnapshot( `"
1
"`, ) expect(data.value.fn).toBeCalledTimes(1) }) }) describe.todo('Suspense') describe('force hydrate prop', async () => { test('force hydrate prop with `.prop` modifier', async () => { const { container } = await mountWithHydration( '', ``, ) expect((container.firstChild! as any).indeterminate).toBe(true) }) test('force hydrate input v-model with non-string value bindings', async () => { const { container } = await mountWithHydration( '', ``, ) expect((container.firstChild as any)._trueValue).toBe(true) }) test('force hydrate checkbox with indeterminate', async () => { const { container } = await mountWithHydration( '', ``, ) expect((container.firstChild! as any).indeterminate).toBe(true) }) test('force hydrate select option with non-string value bindings', async () => { const { container } = await mountWithHydration( '', ``, ) expect((container.firstChild!.firstChild as any)._value).toBe(true) }) test('force hydrate v-bind with .prop modifiers', async () => { const { container } = await mountWithHydration( '
', `
`, ref({ '.foo': true }), ) expect((container.firstChild! as any).foo).toBe(true) }) test('force hydrate custom element with dynamic props', () => { class MyElement extends HTMLElement { foo = '' constructor() { super() } } customElements.define('my-element-7203', MyElement) const msg = ref('bar') const container = document.createElement('div') container.innerHTML = '' const app = createVaporSSRApp({ setup() { return createPlainElement('my-element-7203', { foo: () => msg.value }) }, }) app.mount(container) expect((container.firstChild as any).foo).toBe(msg.value) }) }) }) describe('mismatch handling', () => { test('text node', async () => { const foo = ref('bar') const { container } = await mountWithHydration(`foo`, `{{data}}`, foo) expect(container.textContent).toBe('bar') expect(`Hydration text mismatch`).toHaveBeenWarned() }) test('element text content', async () => { const data = ref({ textContent: 'bar' }) const { container } = await mountWithHydration( `
foo
`, `
`, data, ) expect(container.innerHTML).toBe('
bar
') expect(`Hydration text content mismatch`).toHaveBeenWarned() }) // test('not enough children', () => { // const { container } = mountWithHydration(`
`, () => // h('div', [h('span', 'foo'), h('span', 'bar')]), // ) // expect(container.innerHTML).toBe( // '
foobar
', // ) // expect(`Hydration children mismatch`).toHaveBeenWarned() // }) // test('too many children', () => { // const { container } = mountWithHydration( // `
foobar
`, // () => h('div', [h('span', 'foo')]), // ) // expect(container.innerHTML).toBe('
foo
') // expect(`Hydration children mismatch`).toHaveBeenWarned() // }) test('complete mismatch', async () => { const data = ref('span') const { container } = await mountWithHydration( `
foo
`, `foo`, data, ) expect(container.innerHTML).toBe('foo') expect(`Hydration node mismatch`).toHaveBeenWarned() }) // test('fragment mismatch removal', () => { // const { container } = mountWithHydration( // `
foo
bar
`, // () => h('div', [h('span', 'replaced')]), // ) // expect(container.innerHTML).toBe('
replaced
') // expect(`Hydration node mismatch`).toHaveBeenWarned() // }) // test('fragment not enough children', () => { // const { container } = mountWithHydration( // `
foo
baz
`, // () => h('div', [[h('div', 'foo'), h('div', 'bar')], h('div', 'baz')]), // ) // expect(container.innerHTML).toBe( // '
foo
bar
baz
', // ) // expect(`Hydration node mismatch`).toHaveBeenWarned() // }) // test('fragment too many children', () => { // const { container } = mountWithHydration( // `
foo
bar
baz
`, // () => h('div', [[h('div', 'foo')], h('div', 'baz')]), // ) // expect(container.innerHTML).toBe( // '
foo
baz
', // ) // // fragment ends early and attempts to hydrate the extra
bar
// // as 2nd fragment child. // expect(`Hydration text content mismatch`).toHaveBeenWarned() // // excessive children removal // expect(`Hydration children mismatch`).toHaveBeenWarned() // }) // test('Teleport target has empty children', () => { // const teleportContainer = document.createElement('div') // teleportContainer.id = 'teleport' // document.body.appendChild(teleportContainer) // mountWithHydration('', () => // h(Teleport, { to: '#teleport' }, [h('span', 'value')]), // ) // expect(teleportContainer.innerHTML).toBe(`value`) // expect(`Hydration children mismatch`).toHaveBeenWarned() // }) // test('comment mismatch (element)', () => { // const { container } = mountWithHydration(`
`, () => // h('div', [createCommentVNode('hi')]), // ) // expect(container.innerHTML).toBe('
') // expect(`Hydration node mismatch`).toHaveBeenWarned() // }) // test('comment mismatch (text)', () => { // const { container } = mountWithHydration(`
foobar
`, () => // h('div', [createCommentVNode('hi')]), // ) // expect(container.innerHTML).toBe('
') // expect(`Hydration node mismatch`).toHaveBeenWarned() // }) test('class mismatch', async () => { await mountWithHydration( `
`, `
`, ref(['foo', 'bar']), ) await mountWithHydration( `
`, `
`, ref({ foo: true, bar: true }), ) await mountWithHydration( `
`, `
`, ref('foo bar'), ) // svg classes await mountWithHydration( ``, ``, ref('foo bar'), ) // class with different order await mountWithHydration( `
`, `
`, ref('bar foo'), ) expect(`Hydration class mismatch`).not.toHaveBeenWarned() // single root mismatch const { container: root } = await mountWithHydration( `
`, `
`, ref('baz'), ) expect(root.innerHTML).toBe('
') expect(`Hydration class mismatch`).toHaveBeenWarned() // multiple root mismatch const { container } = await mountWithHydration( `
`, `
`, ref('foo'), ) expect(container.innerHTML).toBe('
') expect(`Hydration class mismatch`).toHaveBeenWarned() }) test('style mismatch', async () => { await mountWithHydration( `
`, `
`, ref({ color: 'red' }), ) await mountWithHydration( `
`, `
`, ref('color:red;'), ) // style with different order await mountWithHydration( `
`, `
`, ref(`font-size: 12px; color:red;`), ) expect(`Hydration style mismatch`).not.toHaveBeenWarned() // single root mismatch const { container: root } = await mountWithHydration( `
`, `
`, ref({ color: 'green' }), ) expect(root.innerHTML).toBe('
') expect(`Hydration style mismatch`).toHaveBeenWarned() // multiple root mismatch const { container } = await mountWithHydration( `
`, `
`, ref({ color: 'green' }), ) expect(container.innerHTML).toBe( '
', ) expect(`Hydration style mismatch`).toHaveBeenWarned() }) test('style mismatch when no style attribute is present', async () => { await mountWithHydration( `
`, `
`, ref({ color: 'red' }), ) expect(`Hydration style mismatch`).toHaveBeenWarnedTimes(1) }) test('style mismatch w/ v-show', async () => { await mountWithHydration( `
`, `
`, ref(false), ) expect(`Hydration style mismatch`).not.toHaveBeenWarned() // mismatch with single root const { container: root } = await mountWithHydration( `
`, `
`, ref(false), ) expect(root.innerHTML).toBe( '
', ) expect(`Hydration style mismatch`).toHaveBeenWarned() // mismatch with multiple root const { container } = await mountWithHydration( `
`, `
`, ref({ show: false, style: 'color: red' }), ) expect(container.innerHTML).toBe( '
', ) expect(`Hydration style mismatch`).toHaveBeenWarned() }) test('attr mismatch', async () => { await mountWithHydration( `
`, `
`, ref('foo'), ) await mountWithHydration( `
`, `
`, ref(''), ) await mountWithHydration( `
`, `
`, ref(undefined), ) // boolean await mountWithHydration( ``, ref(true), ) await mountWithHydration( ``, ref('multiple'), ) expect(`Hydration attribute mismatch`).not.toHaveBeenWarned() await mountWithHydration( `
`, `
`, ref('foo'), ) expect(`Hydration attribute mismatch`).toHaveBeenWarnedTimes(1) await mountWithHydration( `
`, `
`, ref('foo'), ) expect(`Hydration attribute mismatch`).toHaveBeenWarnedTimes(2) }) test('attr special case: textarea value', async () => { await mountWithHydration( ``, ``, ref('foo'), ) await mountWithHydration( ``, ``, ref(''), ) expect(`Hydration attribute mismatch`).not.toHaveBeenWarned() await mountWithHydration( ``, ``, ref('bar'), ) expect(`Hydration attribute mismatch`).toHaveBeenWarned() }) test('`, ``, ref('\nhello'), ) await mountWithHydration( ``, ``, ref('\nhello'), ) await mountWithHydration( ``, ``, ref({ textContent: '\nhello' }), ) expect(`Hydration text content mismatch`).not.toHaveBeenWarned() }) test('
 with newlines at the beginning', async () => {
    await mountWithHydration(`
\n
`, `
{{data}}
`, ref('\n')) await mountWithHydration( `
\n
`, `
`,
      ref('\n'),
    )

    await mountWithHydration(
      `
\n
`, `
`,
      ref({ textContent: '\n' }),
    )
    expect(`Hydration text content mismatch`).not.toHaveBeenWarned()
  })

  test('boolean attr handling', async () => {
    await mountWithHydration(
      ``,
      ``,
      ref(false),
    )

    await mountWithHydration(
      ``,
      ``,
      ref(true),
    )

    await mountWithHydration(
      ``,
      ``,
      ref(true),
    )
    expect(`Hydration attribute mismatch`).not.toHaveBeenWarned()
  })

  test('client value is null or undefined', async () => {
    await mountWithHydration(
      `
`, `
`, ref(undefined), ) expect(`Hydration attribute mismatch`).not.toHaveBeenWarned() await mountWithHydration(``, ``, ref(null)) expect(`Hydration attribute mismatch`).not.toHaveBeenWarned() }) test('should not warn against object values', async () => { await mountWithHydration(``, ``, ref({})) expect(`Hydration attribute mismatch`).not.toHaveBeenWarned() }) test('should not warn on falsy bindings of non-property keys', async () => { await mountWithHydration( ``, ``, ref(undefined), ) expect(`Hydration attribute mismatch`).not.toHaveBeenWarned() }) test('should not warn on non-renderable option values', async () => { await mountWithHydration( ``, ``, ref(['foo']), ) expect(`Hydration attribute mismatch`).not.toHaveBeenWarned() }) test('should not warn css v-bind', async () => { const container = document.createElement('div') container.innerHTML = `
` const app = createVaporSSRApp({ setup() { useVaporCssVars(() => ({ foo: 'red' })) const n0 = template('
', true)() as any renderEffect(() => setStyle(n0, { color: 'var(--foo)' })) return n0 }, }) app.mount(container) expect(`Hydration style mismatch`).not.toHaveBeenWarned() }) test('css vars should only be added to expected on component root dom', () => { const container = document.createElement('div') container.innerHTML = `
` const app = createVaporSSRApp({ setup() { useVaporCssVars(() => ({ foo: 'red' })) const n0 = template('
', true)() as any const n1 = child(n0) as any renderEffect(() => setStyle(n1, { color: 'var(--foo)' })) return n0 }, }) app.mount(container) expect(`Hydration style mismatch`).not.toHaveBeenWarned() }) test('css vars support fallthrough', () => { const container = document.createElement('div') container.innerHTML = `
` const app = createVaporSSRApp({ setup() { useVaporCssVars(() => ({ foo: 'red' })) return createComponent(Child) }, }) const Child = defineVaporComponent({ setup() { const n0 = template('
', true)() as any renderEffect(() => setStyle(n0, { padding: '4px' })) return n0 }, }) app.mount(container) expect(`Hydration style mismatch`).not.toHaveBeenWarned() }) // vapor directive does not have a created hook test('should not warn for directives that mutate DOM in created', () => { // const container = document.createElement('div') // container.innerHTML = `
` // const vColor: ObjectDirective = { // created(el, binding) { // el.classList.add(binding.value) // }, // } // const app = createSSRApp({ // setup() { // return () => // withDirectives(h('div', { class: 'test' }), [[vColor, 'red']]) // }, // }) // app.mount(container) // expect(`Hydration style mismatch`).not.toHaveBeenWarned() }) test('escape css var name', () => { const container = document.createElement('div') container.innerHTML = `
` const app = createVaporSSRApp({ setup() { useVaporCssVars(() => ({ 'foo.bar': 'red' })) return createComponent(Child) }, }) const Child = defineVaporComponent({ setup() { const n0 = template('
', true)() as any renderEffect(() => setStyle(n0, { padding: '4px' })) return n0 }, }) app.mount(container) expect(`Hydration style mismatch`).not.toHaveBeenWarned() }) }) describe('data-allow-mismatch', () => { test('element text content', async () => { const data = ref({ textContent: 'bar' }) const { container } = await mountWithHydration( `
foo
`, `
`, data, ) expect(container.innerHTML).toBe( '
bar
', ) expect(`Hydration text content mismatch`).not.toHaveBeenWarned() }) // test('not enough children', () => { // const { container } = mountWithHydration( // `
`, // () => h('div', [h('span', 'foo'), h('span', 'bar')]), // ) // expect(container.innerHTML).toBe( // '
foobar
', // ) // expect(`Hydration children mismatch`).not.toHaveBeenWarned() // }) // test('too many children', () => { // const { container } = mountWithHydration( // `
foobar
`, // () => h('div', [h('span', 'foo')]), // ) // expect(container.innerHTML).toBe( // '
foo
', // ) // expect(`Hydration children mismatch`).not.toHaveBeenWarned() // }) test('complete mismatch', async () => { const { container } = await mountWithHydration( `
foo
`, `
foo
`, ref('span'), ) expect(container.innerHTML).toBe( '
foo
', ) expect(`Hydration node mismatch`).not.toHaveBeenWarned() }) // test('fragment mismatch removal', () => { // const { container } = mountWithHydration( // `
foo
bar
`, // () => h('div', [h('span', 'replaced')]), // ) // expect(container.innerHTML).toBe( // '
replaced
', // ) // expect(`Hydration node mismatch`).not.toHaveBeenWarned() // }) // test('fragment not enough children', () => { // const { container } = mountWithHydration( // `
foo
baz
`, // () => h('div', [[h('div', 'foo'), h('div', 'bar')], h('div', 'baz')]), // ) // expect(container.innerHTML).toBe( // '
foo
bar
baz
', // ) // expect(`Hydration node mismatch`).not.toHaveBeenWarned() // }) // test('fragment too many children', () => { // const { container } = mountWithHydration( // `
foo
bar
baz
`, // () => h('div', [[h('div', 'foo')], h('div', 'baz')]), // ) // expect(container.innerHTML).toBe( // '
foo
baz
', // ) // // fragment ends early and attempts to hydrate the extra
bar
// // as 2nd fragment child. // expect(`Hydration text content mismatch`).not.toHaveBeenWarned() // // excessive children removal // expect(`Hydration children mismatch`).not.toHaveBeenWarned() // }) // test('comment mismatch (element)', () => { // const { container } = mountWithHydration( // `
`, // () => h('div', [createCommentVNode('hi')]), // ) // expect(container.innerHTML).toBe( // '
', // ) // expect(`Hydration node mismatch`).not.toHaveBeenWarned() // }) // test('comment mismatch (text)', () => { // const { container } = mountWithHydration( // `
foobar
`, // () => h('div', [createCommentVNode('hi')]), // ) // expect(container.innerHTML).toBe( // '
', // ) // expect(`Hydration node mismatch`).not.toHaveBeenWarned() // }) test('class mismatch', async () => { await mountWithHydration( `
`, `
`, ref('foo'), ) expect(`Hydration class mismatch`).not.toHaveBeenWarned() }) test('style mismatch', async () => { await mountWithHydration( `
`, `
`, ref({ color: 'green' }), ) expect(`Hydration style mismatch`).not.toHaveBeenWarned() }) test('attr mismatch', async () => { await mountWithHydration( `
`, `
`, ref('foo'), ) await mountWithHydration( `
`, `
`, ref('foo'), ) expect(`Hydration attribute mismatch`).not.toHaveBeenWarned() }) }) describe('VDOM interop', () => { test('basic render vapor component', async () => { const data = ref(true) const { container } = await testWithVDOMApp( ` `, { VaporChild: { code: ``, vapor: true, }, }, data, ) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(`"true"`) data.value = false await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(`"false"`) }) test('nested components (VDOM -> Vapor -> VDOM)', async () => { const data = ref(true) const { container } = await testWithVDOMApp( ` `, { VaporChild: { code: ``, vapor: true, }, VdomChild: { code: ` `, vapor: false, }, }, data, ) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(`"true"`) data.value = false await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(`"false"`) }) test('nested components (VDOM -> Vapor(multi-root) -> VDOM)', async () => { const data = ref('foo') const { container } = await testWithVDOMApp( ` `, { // Vapor component with multiple root nodes, VDOM child as first element // This ensures hydration starts at and tests skipFragmentAnchor VaporChild: { code: ``, vapor: true, }, VdomChild: { code: ` `, vapor: false, }, }, data, ) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` " foo
second
" `, ) expect(`Hydration node mismatch`).not.toHaveBeenWarned() data.value = 'bar' await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` " bar
second
" `, ) }) test('nested components (VDOM -> Vapor -> VDOM (with slot fallback))', async () => { const data = ref(true) const { container } = await testWithVDOMApp( ` `, { VaporChild: { code: ``, vapor: true, }, VdomChild: { code: ` `, vapor: false, }, }, data, ) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` " true " `, ) data.value = false await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` " false " `, ) }) test('nested components (VDOM -> Vapor(with slot content) -> VDOM)', async () => { const data = ref(true) const { container } = await testWithVDOMApp( ` `, { VaporChild: { code: ``, vapor: true, }, VdomChild: { code: ` `, vapor: false, }, }, data, ) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` " true vapor fallback " `, ) data.value = false await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` " false vapor fallback " `, ) }) test('nested components (VDOM -> Vapor(with slot content) -> Vapor)', async () => { const data = ref(true) const { container } = await testWithVDOMApp( ` `, { VaporChild: { code: ``, vapor: true, }, VaporChild2: { code: ``, vapor: true, }, }, data, ) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` " true vapor fallback " `, ) data.value = false await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` " false vapor fallback " `, ) }) test('vapor slot render vdom component', async () => { const data = ref(true) const { container } = await testWithVDOMApp( ` `, { VaporChild: { code: ``, vapor: true, }, VdomChild: { code: ` `, vapor: false, }, }, data, ) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
true
" `, ) data.value = false await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
false
" `, ) }) test('vapor slot render vdom component (multi-root slot content)', async () => { const data = ref('foo') const { container } = await testWithVaporApp( ` `, { VaporChild: { code: ``, vapor: true, }, VdomChild: { code: ` `, vapor: false, }, }, data, ) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
foo
vapor content
" `, ) expect(`Hydration node mismatch`).not.toHaveBeenWarned() data.value = 'bar' await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
bar
vapor content
" `, ) }) test('vapor slot render vdom component (render function)', async () => { const data = ref(true) const { container } = await testWithVaporApp( ` `, { VaporChild: { code: ``, vapor: true, }, }, data, ) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
true
" `, ) expect(`Hydration node mismatch`).not.toHaveBeenWarned() data.value = false await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
false
" `, ) }) test('hydrate VNode rendered via createDynamicComponent', async () => { const data = ref('foo') const { container } = await testWithVaporApp( ` `, { VaporChild: { code: ``, vapor: true, }, }, data, ) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
foo
" `, ) expect(`Hydration node mismatch`).not.toHaveBeenWarned() data.value = 'bar' await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
bar
" `, ) }) test('hydrate VDOM slot content', async () => { const data = ref('foo') const { container } = await testWithVaporApp( ` `, { VdomWrapper: { code: ` `, vapor: false, }, }, data, ) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
foo
" `, ) expect(`Hydration node mismatch`).not.toHaveBeenWarned() data.value = 'bar' await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
bar
" `, ) }) test('hydrate VDOM slot fallback', async () => { const data = ref('foo') const { container } = await testWithVaporApp( ` `, { VdomWrapper: { code: ` `, vapor: false, }, }, data, ) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
foo
" `, ) expect(`Hydration node mismatch`).not.toHaveBeenWarned() data.value = 'bar' await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
bar
" `, ) }) test('hydrate VDOM component returning Fragment', async () => { const data = ref('foo') const { container } = await testWithVaporApp( ` `, { // VDOM component that returns a Fragment (multiple root nodes) VdomFragmentComp: { code: ` `, vapor: false, }, }, data, ) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
first foo
second foo
" `, ) expect(`Hydration node mismatch`).not.toHaveBeenWarned() data.value = 'bar' await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
first bar
second bar
" `, ) }) })