vdomFragment.spec.ts 5.7 KB

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