fragment.spec.ts 5.7 KB

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