import * as runtimeVapor from '../src'
import * as runtimeDom from '@vue/runtime-dom'
import * as VueServerRenderer from '@vue/server-renderer'
-import { DYNAMIC_COMPONENT_ANCHOR_LABEL, IF_ANCHOR_LABEL } from '@vue/shared'
+import {
+ DYNAMIC_COMPONENT_ANCHOR_LABEL,
+ FOR_ANCHOR_LABEL,
+ IF_ANCHOR_LABEL,
+ SLOT_ANCHOR_LABEL,
+} from '@vue/shared'
const Vue = { ...runtimeDom, ...runtimeVapor }
})
describe('for', () => {
+ const forAnchorLabel = FOR_ANCHOR_LABEL
+ const slotAnchorLabel = SLOT_ANCHOR_LABEL
+
test('basic v-for', async () => {
const { container, data } = await testHydration(
`<template>
`<span>a</span>` +
`<span>b</span>` +
`<span>c</span>` +
- `<!--]-->` +
+ `<!--]--><!--${forAnchorLabel}-->` +
`</div>`,
)
`<span>a</span>` +
`<span>b</span>` +
`<span>c</span>` +
- `<span>d</span>` +
`<!--]-->` +
+ `<span>d</span>` +
+ `<!--${forAnchorLabel}-->` +
`</div>`,
)
})
ref(['a', 'b', 'c']),
)
expect(container.innerHTML).toBe(
- `<div><!--[--><span>a</span><span>b</span><span>c</span><!--]--></div>`,
+ `<div>` +
+ `<!--[-->` +
+ `<span>a</span><span>b</span><span>c</span>` +
+ `<!--]--><!--${forAnchorLabel}-->` +
+ `</div>`,
)
data.value.push('d')
await nextTick()
expect(container.innerHTML).toBe(
- `<div><!--[--><span>a</span><span>b</span><span>c</span><span>d</span><!--]--></div>`,
+ `<div>` +
+ `<!--[-->` +
+ `<span>a</span><span>b</span><span>c</span>` +
+ `<!--]-->` +
+ `<span>d</span>` +
+ `<!--${forAnchorLabel}-->` +
+ `</div>`,
)
})
`<span>a</span>` +
`<span>b</span>` +
`<span>c</span>` +
- `<!--]-->` +
+ `<!--]--><!--${forAnchorLabel}-->` +
`<span></span>` +
`</div>`,
)
`<span>a</span>` +
`<span>b</span>` +
`<span>c</span>` +
- `<span>d</span>` +
`<!--]-->` +
+ `<span>d</span>` +
+ `<!--${forAnchorLabel}-->` +
`<span></span>` +
`</div>`,
)
`<!--[-->` +
`<span>b</span>` +
`<span>c</span>` +
- `<span>d</span>` +
`<!--]-->` +
+ `<span>d</span>` +
+ `<!--${forAnchorLabel}-->` +
`<span></span>` +
`</div>`,
)
`<span>a</span>` +
`<span>b</span>` +
`<span>c</span>` +
- `<!--]-->` +
+ `<!--]--><!--${forAnchorLabel}-->` +
`<!--[-->` +
`<span>a</span>` +
`<span>b</span>` +
`<span>c</span>` +
- `<!--]-->` +
+ `<!--]--><!--${forAnchorLabel}-->` +
`<span></span>` +
`</div>`,
)
`<span>a</span>` +
`<span>b</span>` +
`<span>c</span>` +
- `<span>d</span>` +
`<!--]-->` +
+ `<span>d</span>` +
+ `<!--${forAnchorLabel}-->` +
`<!--[-->` +
`<span>a</span>` +
`<span>b</span>` +
`<span>c</span>` +
- `<span>d</span>` +
`<!--]-->` +
+ `<span>d</span>` +
+ `<!--${forAnchorLabel}-->` +
`<span></span>` +
`</div>`,
)
`<span></span>` +
`<!--[-->` +
`<span>c</span>` +
- `<span>d</span>` +
`<!--]-->` +
+ `<span>d</span>` +
+ `<!--${forAnchorLabel}-->` +
`<!--[-->` +
`<span>c</span>` +
- `<span>d</span>` +
`<!--]-->` +
+ `<span>d</span>` +
+ `<!--${forAnchorLabel}-->` +
`<span></span>` +
`</div>`,
)
`<div>comp</div>` +
`<div>comp</div>` +
`<div>comp</div>` +
- `<!--]-->` +
+ `<!--]--><!--${forAnchorLabel}-->` +
`</div>`,
)
`<div>comp</div>` +
`<div>comp</div>` +
`<div>comp</div>` +
- `<div>comp</div>` +
`<!--]-->` +
+ `<div>comp</div>` +
+ `<!--${forAnchorLabel}-->` +
`</div>`,
)
})
expect(container.innerHTML).toBe(
`<div>` +
`<!--[-->` +
- `<!--[--><span>a</span><!--]--><!--slot-->` +
- `<!--[--><span>b</span><!--]--><!--slot-->` +
- `<!--[--><span>c</span><!--]--><!--slot-->` +
- `<!--]-->` +
+ `<!--[--><span>a</span><!--]--><!--${slotAnchorLabel}-->` +
+ `<!--[--><span>b</span><!--]--><!--${slotAnchorLabel}-->` +
+ `<!--[--><span>c</span><!--]--><!--${slotAnchorLabel}-->` +
+ `<!--]--><!--${forAnchorLabel}-->` +
`</div>`,
)
expect(container.innerHTML).toBe(
`<div>` +
`<!--[-->` +
- `<!--[--><span>a</span><!--]--><!--slot-->` +
- `<!--[--><span>b</span><!--]--><!--slot-->` +
- `<!--[--><span>c</span><!--]--><!--slot-->` +
- `<span>d</span><!--slot-->` +
+ `<!--[--><span>a</span><!--]--><!--${slotAnchorLabel}-->` +
+ `<!--[--><span>b</span><!--]--><!--${slotAnchorLabel}-->` +
+ `<!--[--><span>c</span><!--]--><!--${slotAnchorLabel}-->` +
`<!--]-->` +
+ `<span>d</span><!--${slotAnchorLabel}-->` +
+ `<!--${forAnchorLabel}-->` +
`</div>`,
)
})
`<!--[--><div>foo</div>-bar-<!--]-->` +
`<!--[--><div>foo</div>-bar-<!--]-->` +
`<!--[--><div>foo</div>-bar-<!--]-->` +
- `<!--]-->` +
+ `<!--]--><!--${forAnchorLabel}-->` +
`</div>`,
)
`<!--[--><div>foo</div>-bar-<!--]-->` +
`<!--[--><div>foo</div>-bar-<!--]-->` +
`<!--[--><div>foo</div>-bar-<!--]-->` +
- `<div>foo</div>-bar-` +
`<!--]-->` +
+ `<div>foo</div>-bar-` +
+ `<!--${forAnchorLabel}-->` +
`</div>`,
)
})
-
- // TODO wait for vapor TransitionGroup support
- // v-for inside TransitionGroup does not render as a fragment
- test.todo('v-for in TransitionGroup', async () => {})
})
describe('slots', () => {
+ const slotAnchorLabel = SLOT_ANCHOR_LABEL
+ const forAnchorLabel = FOR_ANCHOR_LABEL
test('basic slot', async () => {
const { data, container } = await testHydration(
`<template>
},
)
expect(container.innerHTML).toBe(
- `<!--[--><span>foo</span><!--]--><!--slot-->`,
+ `<!--[--><span>foo</span><!--]--><!--${slotAnchorLabel}-->`,
)
data.value = 'bar'
await nextTick()
expect(container.innerHTML).toBe(
- `<!--[--><span>bar</span><!--]--><!--slot-->`,
+ `<!--[--><span>bar</span><!--]--><!--${slotAnchorLabel}-->`,
)
})
},
)
expect(container.innerHTML).toBe(
- `<!--[--><span>foo</span><!--]--><!--slot-->`,
+ `<!--[--><span>foo</span><!--]--><!--${slotAnchorLabel}-->`,
)
data.value = 'bar'
await nextTick()
expect(container.innerHTML).toBe(
- `<!--[--><span>bar</span><!--]--><!--slot-->`,
+ `<!--[--><span>bar</span><!--]--><!--${slotAnchorLabel}-->`,
)
})
},
)
expect(container.innerHTML).toBe(
- `<!--[--><span>foo</span><!--]--><!--slot-->`,
+ `<!--[--><span>foo</span><!--]--><!--${slotAnchorLabel}-->`,
)
data.value = false
await nextTick()
- expect(container.innerHTML).toBe(`<!--[--><!--]--><!--slot-->`)
+ expect(container.innerHTML).toBe(
+ `<!--[--><!--]--><!--${slotAnchorLabel}-->`,
+ )
})
test('named slot with v-if and v-for', async () => {
)
expect(container.innerHTML).toBe(
`<!--[-->` +
- `<!--[--><span>a</span><span>b</span><span>c</span><!--]-->` +
+ `<!--[--><span>a</span><span>b</span><span>c</span><!--]--><!--${forAnchorLabel}-->` +
`<!--]-->` +
- `<!--slot-->`,
+ `<!--${slotAnchorLabel}-->`,
)
data.show = false
await nextTick()
expect(container.innerHTML).toBe(
- `<!--[--><!--[--><!--]--><!--]--><!--slot-->`,
+ `<!--[--><!--[--><!--]--><!--]--><!--${slotAnchorLabel}-->`,
)
})
`<span>foo</span>` +
`<span></span>` +
`<!--]-->` +
- `<!--slot-->`,
+ `<!--${slotAnchorLabel}-->`,
)
data.value = 'bar'
`<span>bar</span>` +
`<span></span>` +
`<!--]-->` +
- `<!--slot-->`,
+ `<!--${slotAnchorLabel}-->`,
)
})
`<span>foo</span>` +
`<span></span>` +
`<!--]-->` +
- `<!--slot-->` +
+ `<!--${slotAnchorLabel}-->` +
`<div></div>` +
`<!--]-->`,
)
`<span>bar</span>` +
`<span></span>` +
`<!--]-->` +
- `<!--slot-->` +
+ `<!--${slotAnchorLabel}-->` +
`<div></div>` +
`<!--]-->`,
)
})
- // problem is next child is incorrect after slot
- test.todo('mixed slot and text node', async () => {
+ test('mixed slot and text node', async () => {
const data = reactive({
text: 'foo',
msg: 'hi',
)
expect(container.innerHTML).toMatchInlineSnapshot(
- `"<div><!--[--><span>foo</span><!--]--><!--slot-->hi</div>"`,
+ `"<div><!--[--><span>foo</span><!--]--><!--${slotAnchorLabel}-->hi</div>"`,
)
})
- test.todo('mixed slot and element', async () => {
+ test('mixed slot and element', async () => {
const data = reactive({
text: 'foo',
msg: 'hi',
)
expect(container.innerHTML).toMatchInlineSnapshot(
- `"<div><!--hi--><span>foo</span><!--]--><!--slot--><div>hi</div></div>"`,
+ `"<div><!--[--><span>foo</span><!--]--><!--${slotAnchorLabel}--><div>hi</div></div>"`,
+ )
+ })
+
+ test('mixed slot and component', async () => {
+ const data = reactive({
+ msg1: 'foo',
+ msg2: 'bar',
+ })
+ const { container } = await testHydration(
+ `<template>
+ <components.Child>
+ <span>{{data.msg1}}</span>
+ </components.Child>
+ </template>`,
+ {
+ Child: `
+ <template>
+ <div>
+ <components.Child2/>
+ <slot/>
+ <components.Child2/>
+ </div>
+ </template>`,
+ Child2: `
+ <template>
+ <div>{{data.msg2}}</div>
+ </template>`,
+ },
+ data,
+ )
+ expect(container.innerHTML).toBe(
+ `<div>` +
+ `<div>bar</div>` +
+ `<!--[--><span>foo</span><!--]--><!--${slotAnchorLabel}-->` +
+ `<div>bar</div>` +
+ `</div>`,
+ )
+ data.msg2 = 'hello'
+ await nextTick()
+ expect(container.innerHTML).toBe(
+ `<div>` +
+ `<div>hello</div>` +
+ `<!--[--><span>foo</span><!--]--><!--${slotAnchorLabel}-->` +
+ `<div>hello</div>` +
+ `</div>`,
+ )
+ })
+
+ test('mixed slot and fragment component', async () => {
+ const data = reactive({
+ msg1: 'foo',
+ msg2: 'bar',
+ })
+ const { container } = await testHydration(
+ `<template>
+ <components.Child>
+ <span>{{data.msg1}}</span>
+ </components.Child>
+ </template>`,
+ {
+ Child: `
+ <template>
+ <div>
+ <components.Child2/>
+ <slot/>
+ <components.Child2/>
+ </div>
+ </template>`,
+ Child2: `
+ <template>
+ <div>{{data.msg1}}</div> {{data.msg2}}
+ </template>`,
+ },
+ data,
+ )
+ expect(container.innerHTML).toBe(
+ `<div>` +
+ `<!--[--><div>foo</div> bar<!--]-->` +
+ `<!--[--><span>foo</span><!--]--><!--${slotAnchorLabel}-->` +
+ `<!--[--><div>foo</div> bar<!--]-->` +
+ `</div>`,
+ )
+
+ data.msg1 = 'hello'
+ data.msg2 = 'vapor'
+ await nextTick()
+ expect(container.innerHTML).toBe(
+ `<div>` +
+ `<!--[--><div>hello</div> vapor<!--]-->` +
+ `<!--[--><span>hello</span><!--]--><!--${slotAnchorLabel}-->` +
+ `<!--[--><div>hello</div> vapor<!--]-->` +
+ `</div>`,
)
})
- // mixed slot and component
- // mixed slot and fragment component
- // mixed slot and v-if
- // mixed slot and v-for
+ test('mixed slot and v-if', async () => {
+ const data = reactive({
+ show: true,
+ msg: 'foo',
+ })
+ const { container } = await testHydration(
+ `<template>
+ <components.Child>
+ <span>{{data.msg}}</span>
+ </components.Child>
+ </template>`,
+ {
+ Child: `
+ <template>
+ <div v-if="data.show">{{data.msg}}</div>
+ <slot/>
+ <div v-if="data.show">{{data.msg}}</div>
+ </template>`,
+ },
+ data,
+ )
+
+ expect(container.innerHTML).toBe(
+ `<!--[-->` +
+ `<div>foo</div><!--if-->` +
+ `<!--[--><span>foo</span><!--]--><!--${slotAnchorLabel}-->` +
+ `<div>foo</div><!--if-->` +
+ `<!--]-->`,
+ )
+
+ data.show = false
+ await nextTick()
+ expect(container.innerHTML).toBe(
+ `<!--[-->` +
+ `<!--if-->` +
+ `<!--[--><span>foo</span><!--]--><!--${slotAnchorLabel}-->` +
+ `<!--if-->` +
+ `<!--]-->`,
+ )
+ })
+
+ test('mixed slot and v-for', async () => {
+ const data = reactive({
+ items: ['a', 'b', 'c'],
+ msg: 'foo',
+ })
+ const { container } = await testHydration(
+ `<template>
+ <components.Child>
+ <span>{{data.msg}}</span>
+ </components.Child>
+ </template>`,
+ {
+ Child: `
+ <template>
+ <div v-for="item in data.items" :key="item">{{item}}</div>
+ <slot/>
+ <div v-for="item in data.items" :key="item">{{item}}</div>
+ </template>`,
+ },
+ data,
+ )
+
+ expect(container.innerHTML).toBe(
+ `<!--[-->` +
+ `<!--[--><div>a</div><div>b</div><div>c</div><!--]--><!--${forAnchorLabel}-->` +
+ `<!--[--><span>foo</span><!--]--><!--${slotAnchorLabel}-->` +
+ `<!--[--><div>a</div><div>b</div><div>c</div><!--]--><!--${forAnchorLabel}-->` +
+ `<!--]-->`,
+ )
+
+ data.items.push('d')
+ await nextTick()
+ expect(container.innerHTML).toBe(
+ `<!--[-->` +
+ `<!--[--><div>a</div><div>b</div><div>c</div><!--]--><div>d</div><!--${forAnchorLabel}-->` +
+ `<!--[--><span>foo</span><!--]--><!--${slotAnchorLabel}-->` +
+ `<!--[--><div>a</div><div>b</div><div>c</div><!--]--><div>d</div><!--${forAnchorLabel}-->` +
+ `<!--]-->`,
+ )
+ })
+
+ test('consecutive slots', async () => {
+ const data = reactive({
+ msg1: 'foo',
+ msg2: 'bar',
+ })
+
+ const { container } = await testHydration(
+ `<template>
+ <components.Child>
+ <span>{{data.msg1}}</span>
+ <template #bar>
+ <span>{{data.msg2}}</span>
+ </template>
+ </components.Child>
+ </template>`,
+ {
+ Child: `<template><slot/><slot name="bar"/></template>`,
+ },
+ data,
+ )
+
+ expect(container.innerHTML).toBe(
+ `<!--[-->` +
+ `<!--[--><span>foo</span><!--]--><!--${slotAnchorLabel}-->` +
+ `<!--[--><span>bar</span><!--]--><!--${slotAnchorLabel}-->` +
+ `<!--]-->`,
+ )
+
+ data.msg1 = 'hello'
+ data.msg2 = 'vapor'
+ await nextTick()
+ expect(container.innerHTML).toBe(
+ `<!--[-->` +
+ `<!--[--><span>hello</span><!--]--><!--${slotAnchorLabel}-->` +
+ `<!--[--><span>vapor</span><!--]--><!--${slotAnchorLabel}-->` +
+ `<!--]-->`,
+ )
+ })
+
+ test('consecutive slots with anchor insertion', async () => {
+ const data = reactive({
+ msg1: 'foo',
+ msg2: 'bar',
+ })
+
+ const { container } = await testHydration(
+ `<template>
+ <components.Child>
+ <span>{{data.msg1}}</span>
+ <template #bar>
+ <span>{{data.msg2}}</span>
+ </template>
+ </components.Child>
+ </template>`,
+ {
+ Child: `<template>
+ <div>
+ <span/>
+ <slot/>
+ <slot name="bar"/>
+ <span/>
+ </div>
+ </template>`,
+ },
+ data,
+ )
+
+ expect(container.innerHTML).toBe(
+ `<div>` +
+ `<span></span>` +
+ `<!--[--><span>foo</span><!--]--><!--${slotAnchorLabel}-->` +
+ `<!--[--><span>bar</span><!--]--><!--${slotAnchorLabel}-->` +
+ `<span></span>` +
+ `</div>`,
+ )
+
+ data.msg1 = 'hello'
+ data.msg2 = 'vapor'
+ await nextTick()
+ expect(container.innerHTML).toBe(
+ `<div>` +
+ `<span></span>` +
+ `<!--[--><span>hello</span><!--]--><!--${slotAnchorLabel}-->` +
+ `<!--[--><span>vapor</span><!--]--><!--${slotAnchorLabel}-->` +
+ `<span></span>` +
+ `</div>`,
+ )
+ })
})
// test('element with ref', () => {
}
/*! #__NO_SIDE_EFFECTS__ */
-export function child(node: ParentNode): Node {
+export function _child(node: ParentNode): Node {
return node.firstChild!
}
+/*! #__NO_SIDE_EFFECTS__ */
+export function __child(node: ParentNode): Node {
+ /**
+ * During hydration, the first child of a node not be the expected
+ * if the first child is slot
+ *
+ * for template code: `div><slot />{{ data }}</div>`
+ * - slot: 'slot',
+ * - data: 'hi',
+ *
+ * client side:
+ * const n2 = _template("<div> </div>")()
+ * const n1 = _child(n2) -> the text node
+ * _setInsertionState(n2, 0) -> slot fragment
+ *
+ * during hydration:
+ * const n2 = _template("<div><!--[-->slot<!--]--><!--slot-->Hi</div>")()
+ * const n1 = _child(n2) -> should be `Hi` instead of the slot fragment
+ * _setInsertionState(n2, 0) -> slot fragment
+ */
+ let n = node.firstChild!
+
+ if (isComment(n, '[')) {
+ n = locateEndAnchor(n)!.nextSibling!
+ }
+
+ while (n && isVaporFragmentEndAnchor(n)) {
+ n = n.nextSibling!
+ }
+ return n
+}
+
/*! #__NO_SIDE_EFFECTS__ */
export function _nthChild(node: Node, i: number): Node {
return node.childNodes[i]
return n
}
+type ChildFn = (node: ParentNode) => Node
type NextFn = (node: Node) => Node
type NthChildFn = (node: Node, i: number) => Node
+interface DelegatedChildFunction extends ChildFn {
+ impl: ChildFn
+}
interface DelegatedNextFunction extends NextFn {
impl: NextFn
}
impl: NthChildFn
}
+/*! #__NO_SIDE_EFFECTS__ */
+export const child: DelegatedChildFunction = node => {
+ return child.impl(node)
+}
+child.impl = _child
+
/*! #__NO_SIDE_EFFECTS__ */
export const next: DelegatedNextFunction = node => {
return next.impl(node)
// of `next` and `nthChild`. After hydration is complete, their implementations
// are restored to the original versions.
export function enableHydrationNodeLookup(): void {
+ child.impl = __child
next.impl = __next
nthChild.impl = __nthChild
}
export function disableHydrationNodeLookup(): void {
+ child.impl = _child
next.impl = _next
nthChild.impl = _nthChild
}
)
}
-export function nextVaporFragmentAnchor(
+export function findVaporFragmentAnchor(
node: Node,
anchorLabel: string,
): Comment | null {
- node = handleWrappedNode(node)
- if (isComment(node, anchorLabel)) {
- return node as Comment
- }
-
let n = node.nextSibling
while (n) {
if (isComment(n, anchorLabel)) return n