rendererFragment.spec.ts 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  1. import {
  2. h,
  3. createVNode,
  4. render,
  5. nodeOps,
  6. NodeTypes,
  7. TestElement,
  8. Fragment,
  9. PatchFlags,
  10. resetOps,
  11. dumpOps,
  12. NodeOpTypes,
  13. serializeInner
  14. } from '@vue/runtime-test'
  15. describe('renderer: fragment', () => {
  16. it('should allow returning multiple component root nodes', () => {
  17. const App = {
  18. render() {
  19. return [h('div', 'one'), 'two']
  20. }
  21. }
  22. const root = nodeOps.createElement('div')
  23. render(h(App), root)
  24. expect(serializeInner(root)).toBe(`<!----><div>one</div>two<!---->`)
  25. expect(root.children.length).toBe(4)
  26. expect(root.children[0]).toMatchObject({
  27. type: NodeTypes.COMMENT
  28. })
  29. expect(root.children[1]).toMatchObject({
  30. type: NodeTypes.ELEMENT,
  31. tag: 'div'
  32. })
  33. expect((root.children[1] as TestElement).children[0]).toMatchObject({
  34. type: NodeTypes.TEXT,
  35. text: 'one'
  36. })
  37. expect(root.children[2]).toMatchObject({
  38. type: NodeTypes.TEXT,
  39. text: 'two'
  40. })
  41. expect(root.children[3]).toMatchObject({
  42. type: NodeTypes.COMMENT
  43. })
  44. })
  45. it('explicitly create fragments', () => {
  46. const root = nodeOps.createElement('div')
  47. render(h('div', [h(Fragment, [h('div', 'one'), 'two'])]), root)
  48. const parent = root.children[0] as TestElement
  49. expect(serializeInner(parent)).toBe(`<!----><div>one</div>two<!---->`)
  50. })
  51. it('patch fragment children (manual, keyed)', () => {
  52. const root = nodeOps.createElement('div')
  53. render(
  54. h(Fragment, [h('div', { key: 1 }, 'one'), h('div', { key: 2 }, 'two')]),
  55. root
  56. )
  57. expect(serializeInner(root)).toBe(
  58. `<!----><div>one</div><div>two</div><!---->`
  59. )
  60. resetOps()
  61. render(
  62. h(Fragment, [h('div', { key: 2 }, 'two'), h('div', { key: 1 }, 'one')]),
  63. root
  64. )
  65. expect(serializeInner(root)).toBe(
  66. `<!----><div>two</div><div>one</div><!---->`
  67. )
  68. const ops = dumpOps()
  69. // should be moving nodes instead of re-creating or patching them
  70. expect(ops).toMatchObject([
  71. {
  72. type: NodeOpTypes.INSERT
  73. }
  74. ])
  75. })
  76. it('patch fragment children (manual, unkeyed)', () => {
  77. const root = nodeOps.createElement('div')
  78. render(h(Fragment, [h('div', 'one'), h('div', 'two')]), root)
  79. expect(serializeInner(root)).toBe(
  80. `<!----><div>one</div><div>two</div><!---->`
  81. )
  82. resetOps()
  83. render(h(Fragment, [h('div', 'two'), h('div', 'one')]), root)
  84. expect(serializeInner(root)).toBe(
  85. `<!----><div>two</div><div>one</div><!---->`
  86. )
  87. const ops = dumpOps()
  88. // should be patching nodes instead of moving or re-creating them
  89. expect(ops).toMatchObject([
  90. {
  91. type: NodeOpTypes.SET_ELEMENT_TEXT
  92. },
  93. {
  94. type: NodeOpTypes.SET_ELEMENT_TEXT
  95. }
  96. ])
  97. })
  98. it('patch fragment children (compiler generated, unkeyed)', () => {
  99. const root = nodeOps.createElement('div')
  100. render(
  101. createVNode(Fragment, 0, [h('div', 'one'), 'two'], PatchFlags.UNKEYED),
  102. root
  103. )
  104. expect(serializeInner(root)).toBe(`<!----><div>one</div>two<!---->`)
  105. render(
  106. createVNode(
  107. Fragment,
  108. 0,
  109. [h('div', 'foo'), 'bar', 'baz'],
  110. PatchFlags.UNKEYED
  111. ),
  112. root
  113. )
  114. expect(serializeInner(root)).toBe(`<!----><div>foo</div>barbaz<!---->`)
  115. })
  116. it('patch fragment children (compiler generated, keyed)', () => {
  117. const root = nodeOps.createElement('div')
  118. render(
  119. createVNode(
  120. Fragment,
  121. 0,
  122. [h('div', { key: 1 }, 'one'), h('div', { key: 2 }, 'two')],
  123. PatchFlags.KEYED
  124. ),
  125. root
  126. )
  127. expect(serializeInner(root)).toBe(
  128. `<!----><div>one</div><div>two</div><!---->`
  129. )
  130. resetOps()
  131. render(
  132. createVNode(
  133. Fragment,
  134. 0,
  135. [h('div', { key: 2 }, 'two'), h('div', { key: 1 }, 'one')],
  136. PatchFlags.KEYED
  137. ),
  138. root
  139. )
  140. expect(serializeInner(root)).toBe(
  141. `<!----><div>two</div><div>one</div><!---->`
  142. )
  143. const ops = dumpOps()
  144. // should be moving nodes instead of re-creating or patching them
  145. expect(ops).toMatchObject([
  146. {
  147. type: NodeOpTypes.INSERT
  148. }
  149. ])
  150. })
  151. it('move fragment', () => {
  152. const root = nodeOps.createElement('div')
  153. render(
  154. h('div', [
  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. root
  162. )
  163. expect(serializeInner(root)).toBe(
  164. `<div><div>outer</div><!----><div>one</div><div>two</div><!----></div>`
  165. )
  166. resetOps()
  167. render(
  168. h('div', [
  169. h(Fragment, { key: 2 }, [
  170. h('div', { key: 2 }, 'two'),
  171. h('div', { key: 1 }, 'one')
  172. ]),
  173. h('div', { key: 1 }, 'outer')
  174. ]),
  175. root
  176. )
  177. expect(serializeInner(root)).toBe(
  178. `<div><!----><div>two</div><div>one</div><!----><div>outer</div></div>`
  179. )
  180. const ops = dumpOps()
  181. // should be moving nodes instead of re-creating them
  182. expect(ops).toMatchObject([
  183. // 1. re-order inside the fragment
  184. { type: NodeOpTypes.INSERT, targetNode: { type: 'element' } },
  185. // 2. move entire fragment, including anchors
  186. // not the most efficient move, but this case is super rare
  187. // and optimizing for this special case complicates the algo quite a bit
  188. { type: NodeOpTypes.INSERT, targetNode: { type: 'comment' } },
  189. { type: NodeOpTypes.INSERT, targetNode: { type: 'element' } },
  190. { type: NodeOpTypes.INSERT, targetNode: { type: 'element' } },
  191. { type: NodeOpTypes.INSERT, targetNode: { type: 'comment' } }
  192. ])
  193. })
  194. it('handle nested fragments', () => {
  195. const root = nodeOps.createElement('div')
  196. render(
  197. h(Fragment, [
  198. h('div', { key: 1 }, 'outer'),
  199. h(Fragment, { key: 2 }, [
  200. h('div', { key: 1 }, 'one'),
  201. h('div', { key: 2 }, 'two')
  202. ])
  203. ]),
  204. root
  205. )
  206. expect(serializeInner(root)).toBe(
  207. `<!----><div>outer</div><!----><div>one</div><div>two</div><!----><!---->`
  208. )
  209. resetOps()
  210. render(
  211. h(Fragment, [
  212. h(Fragment, { key: 2 }, [
  213. h('div', { key: 2 }, 'two'),
  214. h('div', { key: 1 }, 'one')
  215. ]),
  216. h('div', { key: 1 }, 'outer')
  217. ]),
  218. root
  219. )
  220. expect(serializeInner(root)).toBe(
  221. `<!----><!----><div>two</div><div>one</div><!----><div>outer</div><!---->`
  222. )
  223. const ops = dumpOps()
  224. // should be moving nodes instead of re-creating them
  225. expect(ops).toMatchObject([
  226. { type: NodeOpTypes.INSERT, targetNode: { type: 'element' } },
  227. { type: NodeOpTypes.INSERT, targetNode: { type: 'comment' } },
  228. { type: NodeOpTypes.INSERT, targetNode: { type: 'element' } },
  229. { type: NodeOpTypes.INSERT, targetNode: { type: 'element' } },
  230. { type: NodeOpTypes.INSERT, targetNode: { type: 'comment' } }
  231. ])
  232. })
  233. })