|
|
@@ -0,0 +1,198 @@
|
|
|
+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)
|
|
|
+ })
|
|
|
+})
|