199 lines
5.5 KiB
TypeScript
199 lines
5.5 KiB
TypeScript
|
import {
|
||
|
h,
|
||
|
Component,
|
||
|
render,
|
||
|
nodeOps,
|
||
|
NodeTypes,
|
||
|
TestElement,
|
||
|
Fragment,
|
||
|
observable,
|
||
|
serialize,
|
||
|
ChildrenFlags,
|
||
|
nextTick,
|
||
|
resetOps,
|
||
|
dumpOps,
|
||
|
NodeOpTypes
|
||
|
} from '@vue/renderer-test'
|
||
|
|
||
|
describe('Fragments', () => {
|
||
|
it('should allow returning multiple component root nodes', () => {
|
||
|
class App extends Component {
|
||
|
render() {
|
||
|
return [h('div', 'one'), 'two']
|
||
|
}
|
||
|
}
|
||
|
const root = nodeOps.createElement('div')
|
||
|
render(h(App), root)
|
||
|
expect(serialize(root)).toBe(`<div><div>one</div>two</div>`)
|
||
|
expect(root.children.length).toBe(2)
|
||
|
expect(root.children[0]).toMatchObject({
|
||
|
type: NodeTypes.ELEMENT,
|
||
|
tag: 'div'
|
||
|
})
|
||
|
expect((root.children[0] as TestElement).children[0]).toMatchObject({
|
||
|
type: NodeTypes.TEXT,
|
||
|
text: 'one'
|
||
|
})
|
||
|
expect(root.children[1]).toMatchObject({
|
||
|
type: NodeTypes.TEXT,
|
||
|
text: 'two'
|
||
|
})
|
||
|
})
|
||
|
|
||
|
it('should be able to explicitly create fragments', () => {
|
||
|
class App extends Component {
|
||
|
render() {
|
||
|
return h('div', [h(Fragment, [h('div', 'one'), 'two'])])
|
||
|
}
|
||
|
}
|
||
|
const root = nodeOps.createElement('div')
|
||
|
render(h(App), root)
|
||
|
const parent = root.children[0] as TestElement
|
||
|
expect(serialize(parent)).toBe(`<div><div>one</div>two</div>`)
|
||
|
})
|
||
|
|
||
|
it('should be able to patch fragment children (unkeyed)', async () => {
|
||
|
const state = observable({ ok: true })
|
||
|
class App extends Component {
|
||
|
render() {
|
||
|
return state.ok
|
||
|
? h.f([h('div', 'one'), h.t('two')], ChildrenFlags.NONE_KEYED_VNODES)
|
||
|
: h.f(
|
||
|
[h('div', 'foo'), h.t('bar'), h.t('baz')],
|
||
|
ChildrenFlags.NONE_KEYED_VNODES
|
||
|
)
|
||
|
}
|
||
|
}
|
||
|
const root = nodeOps.createElement('div')
|
||
|
render(h(App), root)
|
||
|
|
||
|
expect(serialize(root)).toBe(`<div><div>one</div>two</div>`)
|
||
|
|
||
|
state.ok = false
|
||
|
await nextTick()
|
||
|
expect(serialize(root)).toBe(`<div><div>foo</div>barbaz</div>`)
|
||
|
})
|
||
|
|
||
|
it('should be able to patch fragment children (implicitly keyed)', async () => {
|
||
|
const state = observable({ ok: true })
|
||
|
class App extends Component {
|
||
|
render() {
|
||
|
return state.ok
|
||
|
? [h('div', 'one'), 'two']
|
||
|
: [h('div', 'foo'), 'bar', 'baz']
|
||
|
}
|
||
|
}
|
||
|
const root = nodeOps.createElement('div')
|
||
|
render(h(App), root)
|
||
|
|
||
|
expect(serialize(root)).toBe(`<div><div>one</div>two</div>`)
|
||
|
|
||
|
state.ok = false
|
||
|
await nextTick()
|
||
|
expect(serialize(root)).toBe(`<div><div>foo</div>barbaz</div>`)
|
||
|
})
|
||
|
|
||
|
it('should be able to patch fragment children (explcitly keyed)', async () => {
|
||
|
const state = observable({ ok: true })
|
||
|
class App extends Component {
|
||
|
render() {
|
||
|
return state.ok
|
||
|
? [h('div', { key: 1 }, 'one'), h('div', { key: 2 }, 'two')]
|
||
|
: [h('div', { key: 2 }, 'two'), h('div', { key: 1 }, 'one')]
|
||
|
}
|
||
|
}
|
||
|
const root = nodeOps.createElement('div')
|
||
|
render(h(App), root)
|
||
|
|
||
|
expect(serialize(root)).toBe(`<div><div>one</div><div>two</div></div>`)
|
||
|
|
||
|
resetOps()
|
||
|
state.ok = false
|
||
|
await nextTick()
|
||
|
expect(serialize(root)).toBe(`<div><div>two</div><div>one</div></div>`)
|
||
|
const ops = dumpOps()
|
||
|
// should be moving nodes instead of re-creating them
|
||
|
expect(ops.some(op => op.type === NodeOpTypes.CREATE)).toBe(false)
|
||
|
})
|
||
|
|
||
|
it('should be able to move fragment', async () => {
|
||
|
const state = observable({ ok: true })
|
||
|
class App extends Component {
|
||
|
render() {
|
||
|
return state.ok
|
||
|
? h('div', [
|
||
|
h('div', { key: 1 }, 'outer'),
|
||
|
h(Fragment, { key: 2 }, [
|
||
|
h('div', { key: 1 }, 'one'),
|
||
|
h('div', { key: 2 }, 'two')
|
||
|
])
|
||
|
])
|
||
|
: h('div', [
|
||
|
h(Fragment, { key: 2 }, [
|
||
|
h('div', { key: 2 }, 'two'),
|
||
|
h('div', { key: 1 }, 'one')
|
||
|
]),
|
||
|
h('div', { key: 1 }, 'outer')
|
||
|
])
|
||
|
}
|
||
|
}
|
||
|
const root = nodeOps.createElement('div')
|
||
|
render(h(App), root)
|
||
|
const parent = root.children[0] as TestElement
|
||
|
|
||
|
expect(serialize(parent)).toBe(
|
||
|
`<div><div>outer</div><div>one</div><div>two</div></div>`
|
||
|
)
|
||
|
|
||
|
resetOps()
|
||
|
state.ok = false
|
||
|
await nextTick()
|
||
|
expect(serialize(parent)).toBe(
|
||
|
`<div><div>two</div><div>one</div><div>outer</div></div>`
|
||
|
)
|
||
|
const ops = dumpOps()
|
||
|
// should be moving nodes instead of re-creating them
|
||
|
expect(ops.some(op => op.type === NodeOpTypes.CREATE)).toBe(false)
|
||
|
})
|
||
|
|
||
|
it('should be able to handle nested fragments', async () => {
|
||
|
const state = observable({ ok: true })
|
||
|
class App extends Component {
|
||
|
render() {
|
||
|
return state.ok
|
||
|
? [
|
||
|
h('div', { key: 1 }, 'outer'),
|
||
|
h(Fragment, { key: 2 }, [
|
||
|
h('div', { key: 1 }, 'one'),
|
||
|
h('div', { key: 2 }, 'two')
|
||
|
])
|
||
|
]
|
||
|
: [
|
||
|
h(Fragment, { key: 2 }, [
|
||
|
h('div', { key: 2 }, 'two'),
|
||
|
h('div', { key: 1 }, 'one')
|
||
|
]),
|
||
|
h('div', { key: 1 }, 'outer')
|
||
|
]
|
||
|
}
|
||
|
}
|
||
|
|
||
|
const root = nodeOps.createElement('div')
|
||
|
render(h(App), root)
|
||
|
|
||
|
expect(serialize(root)).toBe(
|
||
|
`<div><div>outer</div><div>one</div><div>two</div></div>`
|
||
|
)
|
||
|
|
||
|
resetOps()
|
||
|
state.ok = false
|
||
|
await nextTick()
|
||
|
expect(serialize(root)).toBe(
|
||
|
`<div><div>two</div><div>one</div><div>outer</div></div>`
|
||
|
)
|
||
|
const ops = dumpOps()
|
||
|
// should be moving nodes instead of re-creating them
|
||
|
expect(ops.some(op => op.type === NodeOpTypes.CREATE)).toBe(false)
|
||
|
})
|
||
|
})
|