2
0

fragment.spec.ts 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. import {
  2. h,
  3. Component,
  4. render,
  5. nodeOps,
  6. NodeTypes,
  7. TestElement,
  8. Fragment,
  9. observable,
  10. serialize,
  11. ChildrenFlags,
  12. nextTick,
  13. resetOps,
  14. dumpOps,
  15. NodeOpTypes
  16. } from '@vue/runtime-test'
  17. describe('Fragments', () => {
  18. it('should allow returning multiple component root nodes', async () => {
  19. class App extends Component {
  20. render() {
  21. return [h('div', 'one'), 'two']
  22. }
  23. }
  24. const root = nodeOps.createElement('div')
  25. await render(h(App), root)
  26. expect(serialize(root)).toBe(`<div><div>one</div>two</div>`)
  27. expect(root.children.length).toBe(2)
  28. expect(root.children[0]).toMatchObject({
  29. type: NodeTypes.ELEMENT,
  30. tag: 'div'
  31. })
  32. expect((root.children[0] as TestElement).children[0]).toMatchObject({
  33. type: NodeTypes.TEXT,
  34. text: 'one'
  35. })
  36. expect(root.children[1]).toMatchObject({
  37. type: NodeTypes.TEXT,
  38. text: 'two'
  39. })
  40. })
  41. it('should be able to explicitly create fragments', async () => {
  42. class App extends Component {
  43. render() {
  44. return h('div', [h(Fragment, [h('div', 'one'), 'two'])])
  45. }
  46. }
  47. const root = nodeOps.createElement('div')
  48. await render(h(App), root)
  49. const parent = root.children[0] as TestElement
  50. expect(serialize(parent)).toBe(`<div><div>one</div>two</div>`)
  51. })
  52. it('should be able to patch fragment children (unkeyed)', async () => {
  53. const state = observable({ ok: true })
  54. class App extends Component {
  55. render() {
  56. return state.ok
  57. ? h.f([h('div', 'one'), h.t('two')], ChildrenFlags.NONE_KEYED_VNODES)
  58. : h.f(
  59. [h('div', 'foo'), h.t('bar'), h.t('baz')],
  60. ChildrenFlags.NONE_KEYED_VNODES
  61. )
  62. }
  63. }
  64. const root = nodeOps.createElement('div')
  65. await render(h(App), root)
  66. expect(serialize(root)).toBe(`<div><div>one</div>two</div>`)
  67. state.ok = false
  68. await nextTick()
  69. expect(serialize(root)).toBe(`<div><div>foo</div>barbaz</div>`)
  70. })
  71. it('should be able to patch fragment children (implicitly keyed)', async () => {
  72. const state = observable({ ok: true })
  73. class App extends Component {
  74. render() {
  75. return state.ok
  76. ? [h('div', 'one'), 'two']
  77. : [h('pre', 'foo'), 'bar', 'baz']
  78. }
  79. }
  80. const root = nodeOps.createElement('div')
  81. await await render(h(App), root)
  82. expect(serialize(root)).toBe(`<div><div>one</div>two</div>`)
  83. state.ok = false
  84. await nextTick()
  85. expect(serialize(root)).toBe(`<div><pre>foo</pre>barbaz</div>`)
  86. })
  87. it('should be able to patch fragment children (explcitly keyed)', async () => {
  88. const state = observable({ ok: true })
  89. class App extends Component {
  90. render() {
  91. return state.ok
  92. ? [h('div', { key: 1 }, 'one'), h('div', { key: 2 }, 'two')]
  93. : [h('div', { key: 2 }, 'two'), h('div', { key: 1 }, 'one')]
  94. }
  95. }
  96. const root = nodeOps.createElement('div')
  97. await render(h(App), root)
  98. expect(serialize(root)).toBe(`<div><div>one</div><div>two</div></div>`)
  99. resetOps()
  100. state.ok = false
  101. await nextTick()
  102. expect(serialize(root)).toBe(`<div><div>two</div><div>one</div></div>`)
  103. const ops = dumpOps()
  104. // should be moving nodes instead of re-creating them
  105. expect(ops.some(op => op.type === NodeOpTypes.CREATE)).toBe(false)
  106. })
  107. it('should be able to move fragment', async () => {
  108. const state = observable({ ok: true })
  109. class App extends Component {
  110. render() {
  111. return state.ok
  112. ? h('div', [
  113. h('div', { key: 1 }, 'outer'),
  114. h(Fragment, { key: 2 }, [
  115. h('div', { key: 1 }, 'one'),
  116. h('div', { key: 2 }, 'two')
  117. ])
  118. ])
  119. : h('div', [
  120. h(Fragment, { key: 2 }, [
  121. h('div', { key: 2 }, 'two'),
  122. h('div', { key: 1 }, 'one')
  123. ]),
  124. h('div', { key: 1 }, 'outer')
  125. ])
  126. }
  127. }
  128. const root = nodeOps.createElement('div')
  129. await render(h(App), root)
  130. const parent = root.children[0] as TestElement
  131. expect(serialize(parent)).toBe(
  132. `<div><div>outer</div><div>one</div><div>two</div></div>`
  133. )
  134. resetOps()
  135. state.ok = false
  136. await nextTick()
  137. expect(serialize(parent)).toBe(
  138. `<div><div>two</div><div>one</div><div>outer</div></div>`
  139. )
  140. const ops = dumpOps()
  141. // should be moving nodes instead of re-creating them
  142. expect(ops.some(op => op.type === NodeOpTypes.CREATE)).toBe(false)
  143. })
  144. it('should be able to handle nested fragments', async () => {
  145. const state = observable({ ok: true })
  146. class App extends Component {
  147. render() {
  148. return state.ok
  149. ? [
  150. h('div', { key: 1 }, 'outer'),
  151. h(Fragment, { key: 2 }, [
  152. h('div', { key: 1 }, 'one'),
  153. h('div', { key: 2 }, 'two')
  154. ])
  155. ]
  156. : [
  157. h(Fragment, { key: 2 }, [
  158. h('div', { key: 2 }, 'two'),
  159. h('div', { key: 1 }, 'one')
  160. ]),
  161. h('div', { key: 1 }, 'outer')
  162. ]
  163. }
  164. }
  165. const root = nodeOps.createElement('div')
  166. await render(h(App), root)
  167. expect(serialize(root)).toBe(
  168. `<div><div>outer</div><div>one</div><div>two</div></div>`
  169. )
  170. resetOps()
  171. state.ok = false
  172. await nextTick()
  173. expect(serialize(root)).toBe(
  174. `<div><div>two</div><div>one</div><div>outer</div></div>`
  175. )
  176. const ops = dumpOps()
  177. // should be moving nodes instead of re-creating them
  178. expect(ops.some(op => op.type === NodeOpTypes.CREATE)).toBe(false)
  179. })
  180. })