rendererFragment.spec.ts 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256
  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(
  102. Fragment,
  103. null,
  104. [h('div', 'one'), 'two'],
  105. PatchFlags.UNKEYED_FRAGMENT
  106. ),
  107. root
  108. )
  109. expect(serializeInner(root)).toBe(`<!----><div>one</div>two<!---->`)
  110. render(
  111. createVNode(
  112. Fragment,
  113. null,
  114. [h('div', 'foo'), 'bar', 'baz'],
  115. PatchFlags.KEYED_FRAGMENT
  116. ),
  117. root
  118. )
  119. expect(serializeInner(root)).toBe(`<!----><div>foo</div>barbaz<!---->`)
  120. })
  121. it('patch fragment children (compiler generated, keyed)', () => {
  122. const root = nodeOps.createElement('div')
  123. render(
  124. createVNode(
  125. Fragment,
  126. null,
  127. [h('div', { key: 1 }, 'one'), h('div', { key: 2 }, 'two')],
  128. PatchFlags.KEYED_FRAGMENT
  129. ),
  130. root
  131. )
  132. expect(serializeInner(root)).toBe(
  133. `<!----><div>one</div><div>two</div><!---->`
  134. )
  135. resetOps()
  136. render(
  137. createVNode(
  138. Fragment,
  139. null,
  140. [h('div', { key: 2 }, 'two'), h('div', { key: 1 }, 'one')],
  141. PatchFlags.KEYED_FRAGMENT
  142. ),
  143. root
  144. )
  145. expect(serializeInner(root)).toBe(
  146. `<!----><div>two</div><div>one</div><!---->`
  147. )
  148. const ops = dumpOps()
  149. // should be moving nodes instead of re-creating or patching them
  150. expect(ops).toMatchObject([
  151. {
  152. type: NodeOpTypes.INSERT
  153. }
  154. ])
  155. })
  156. it('move fragment', () => {
  157. const root = nodeOps.createElement('div')
  158. render(
  159. h('div', [
  160. h('div', { key: 1 }, 'outer'),
  161. h(Fragment, { key: 2 }, [
  162. h('div', { key: 1 }, 'one'),
  163. h('div', { key: 2 }, 'two')
  164. ])
  165. ]),
  166. root
  167. )
  168. expect(serializeInner(root)).toBe(
  169. `<div><div>outer</div><!----><div>one</div><div>two</div><!----></div>`
  170. )
  171. resetOps()
  172. render(
  173. h('div', [
  174. h(Fragment, { key: 2 }, [
  175. h('div', { key: 2 }, 'two'),
  176. h('div', { key: 1 }, 'one')
  177. ]),
  178. h('div', { key: 1 }, 'outer')
  179. ]),
  180. root
  181. )
  182. expect(serializeInner(root)).toBe(
  183. `<div><!----><div>two</div><div>one</div><!----><div>outer</div></div>`
  184. )
  185. const ops = dumpOps()
  186. // should be moving nodes instead of re-creating them
  187. expect(ops).toMatchObject([
  188. // 1. re-order inside the fragment
  189. { type: NodeOpTypes.INSERT, targetNode: { type: 'element' } },
  190. // 2. move entire fragment, including anchors
  191. // not the most efficient move, but this case is super rare
  192. // and optimizing for this special case complicates the algo quite a bit
  193. { type: NodeOpTypes.INSERT, targetNode: { type: 'comment' } },
  194. { type: NodeOpTypes.INSERT, targetNode: { type: 'element' } },
  195. { type: NodeOpTypes.INSERT, targetNode: { type: 'element' } },
  196. { type: NodeOpTypes.INSERT, targetNode: { type: 'comment' } }
  197. ])
  198. })
  199. it('handle nested fragments', () => {
  200. const root = nodeOps.createElement('div')
  201. render(
  202. h(Fragment, [
  203. h('div', { key: 1 }, 'outer'),
  204. h(Fragment, { key: 2 }, [
  205. h('div', { key: 1 }, 'one'),
  206. h('div', { key: 2 }, 'two')
  207. ])
  208. ]),
  209. root
  210. )
  211. expect(serializeInner(root)).toBe(
  212. `<!----><div>outer</div><!----><div>one</div><div>two</div><!----><!---->`
  213. )
  214. resetOps()
  215. render(
  216. h(Fragment, [
  217. h(Fragment, { key: 2 }, [
  218. h('div', { key: 2 }, 'two'),
  219. h('div', { key: 1 }, 'one')
  220. ]),
  221. h('div', { key: 1 }, 'outer')
  222. ]),
  223. root
  224. )
  225. expect(serializeInner(root)).toBe(
  226. `<!----><!----><div>two</div><div>one</div><!----><div>outer</div><!---->`
  227. )
  228. const ops = dumpOps()
  229. // should be moving nodes instead of re-creating them
  230. expect(ops).toMatchObject([
  231. { type: NodeOpTypes.INSERT, targetNode: { type: 'element' } },
  232. { type: NodeOpTypes.INSERT, targetNode: { type: 'comment' } },
  233. { type: NodeOpTypes.INSERT, targetNode: { type: 'element' } },
  234. { type: NodeOpTypes.INSERT, targetNode: { type: 'element' } },
  235. { type: NodeOpTypes.INSERT, targetNode: { type: 'comment' } }
  236. ])
  237. })
  238. })