rendererFragment.spec.ts 7.2 KB

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