rendererOptimizedMode.spec.ts 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401
  1. import {
  2. h,
  3. Fragment,
  4. createVNode,
  5. openBlock,
  6. createBlock,
  7. render,
  8. nodeOps,
  9. TestElement,
  10. serialize,
  11. serializeInner as inner,
  12. VNode,
  13. ref,
  14. nextTick
  15. } from '@vue/runtime-test'
  16. import { PatchFlags } from '@vue/shared'
  17. describe('renderer: optimized mode', () => {
  18. let root: TestElement
  19. let block: VNode | null = null
  20. beforeEach(() => {
  21. root = nodeOps.createElement('div')
  22. block = null
  23. })
  24. const renderWithBlock = (renderChildren: () => VNode[]) => {
  25. render(
  26. (openBlock(), (block = createBlock('div', null, renderChildren()))),
  27. root
  28. )
  29. }
  30. test('basic use of block', () => {
  31. render((openBlock(), (block = createBlock('p', null, 'foo'))), root)
  32. expect(block.dynamicChildren!.length).toBe(0)
  33. expect(inner(root)).toBe('<p>foo</p>')
  34. })
  35. test('block can appear anywhere in the vdom tree', () => {
  36. render(
  37. h('div', (openBlock(), (block = createBlock('p', null, 'foo')))),
  38. root
  39. )
  40. expect(block.dynamicChildren!.length).toBe(0)
  41. expect(inner(root)).toBe('<div><p>foo</p></div>')
  42. })
  43. test('block should collect dynamic vnodes', () => {
  44. renderWithBlock(() => [
  45. createVNode('p', null, 'foo', PatchFlags.TEXT),
  46. createVNode('i')
  47. ])
  48. expect(block!.dynamicChildren!.length).toBe(1)
  49. expect(serialize(block!.dynamicChildren![0].el as TestElement)).toBe(
  50. '<p>foo</p>'
  51. )
  52. })
  53. test('block can disable tracking', () => {
  54. render(
  55. // disable tracking
  56. (openBlock(true),
  57. (block = createBlock('div', null, [
  58. createVNode('p', null, 'foo', PatchFlags.TEXT)
  59. ]))),
  60. root
  61. )
  62. expect(block.dynamicChildren!.length).toBe(0)
  63. })
  64. test('block as dynamic children', () => {
  65. renderWithBlock(() => [
  66. (openBlock(), createBlock('div', { key: 0 }, [h('p')]))
  67. ])
  68. expect(block!.dynamicChildren!.length).toBe(1)
  69. expect(block!.dynamicChildren![0].dynamicChildren!.length).toBe(0)
  70. expect(serialize(block!.dynamicChildren![0].el as TestElement)).toBe(
  71. '<div><p></p></div>'
  72. )
  73. renderWithBlock(() => [
  74. (openBlock(), createBlock('div', { key: 1 }, [h('i')]))
  75. ])
  76. expect(block!.dynamicChildren!.length).toBe(1)
  77. expect(block!.dynamicChildren![0].dynamicChildren!.length).toBe(0)
  78. expect(serialize(block!.dynamicChildren![0].el as TestElement)).toBe(
  79. '<div><i></i></div>'
  80. )
  81. })
  82. test('PatchFlags: PatchFlags.TEXT', async () => {
  83. renderWithBlock(() => [createVNode('p', null, 'foo', PatchFlags.TEXT)])
  84. expect(inner(root)).toBe('<div><p>foo</p></div>')
  85. expect(block!.dynamicChildren!.length).toBe(1)
  86. expect(serialize(block!.dynamicChildren![0].el as TestElement)).toBe(
  87. '<p>foo</p>'
  88. )
  89. renderWithBlock(() => [createVNode('p', null, 'bar', PatchFlags.TEXT)])
  90. expect(inner(root)).toBe('<div><p>bar</p></div>')
  91. expect(block!.dynamicChildren!.length).toBe(1)
  92. expect(serialize(block!.dynamicChildren![0].el as TestElement)).toBe(
  93. '<p>bar</p>'
  94. )
  95. })
  96. test('PatchFlags: PatchFlags.CLASS', async () => {
  97. renderWithBlock(() => [
  98. createVNode('p', { class: 'foo' }, '', PatchFlags.CLASS)
  99. ])
  100. expect(inner(root)).toBe('<div><p class="foo"></p></div>')
  101. expect(block!.dynamicChildren!.length).toBe(1)
  102. expect(serialize(block!.dynamicChildren![0].el as TestElement)).toBe(
  103. '<p class="foo"></p>'
  104. )
  105. renderWithBlock(() => [
  106. createVNode('p', { class: 'bar' }, '', PatchFlags.CLASS)
  107. ])
  108. expect(inner(root)).toBe('<div><p class="bar"></p></div>')
  109. expect(block!.dynamicChildren!.length).toBe(1)
  110. expect(serialize(block!.dynamicChildren![0].el as TestElement)).toBe(
  111. '<p class="bar"></p>'
  112. )
  113. })
  114. test('PatchFlags: PatchFlags.STYLE', async () => {
  115. renderWithBlock(() => [
  116. createVNode('p', { style: 'color: red' }, '', PatchFlags.STYLE)
  117. ])
  118. expect(inner(root)).toBe('<div><p style="color: red"></p></div>')
  119. expect(block!.dynamicChildren!.length).toBe(1)
  120. expect(serialize(block!.dynamicChildren![0].el as TestElement)).toBe(
  121. '<p style="color: red"></p>'
  122. )
  123. renderWithBlock(() => [
  124. createVNode('p', { style: 'color: green' }, '', PatchFlags.STYLE)
  125. ])
  126. expect(inner(root)).toBe('<div><p style="color: green"></p></div>')
  127. expect(block!.dynamicChildren!.length).toBe(1)
  128. expect(serialize(block!.dynamicChildren![0].el as TestElement)).toBe(
  129. '<p style="color: green"></p>'
  130. )
  131. })
  132. test('PatchFlags: PatchFlags.PROPS', async () => {
  133. renderWithBlock(() => [
  134. createVNode('p', { id: 'foo' }, '', PatchFlags.PROPS, ['id'])
  135. ])
  136. expect(inner(root)).toBe('<div><p id="foo"></p></div>')
  137. expect(block!.dynamicChildren!.length).toBe(1)
  138. expect(serialize(block!.dynamicChildren![0].el as TestElement)).toBe(
  139. '<p id="foo"></p>'
  140. )
  141. renderWithBlock(() => [
  142. createVNode('p', { id: 'bar' }, '', PatchFlags.PROPS, ['id'])
  143. ])
  144. expect(inner(root)).toBe('<div><p id="bar"></p></div>')
  145. expect(block!.dynamicChildren!.length).toBe(1)
  146. expect(serialize(block!.dynamicChildren![0].el as TestElement)).toBe(
  147. '<p id="bar"></p>'
  148. )
  149. })
  150. test('PatchFlags: PatchFlags.FULL_PROPS', async () => {
  151. let propName = 'foo'
  152. renderWithBlock(() => [
  153. createVNode('p', { [propName]: 'dynamic' }, '', PatchFlags.FULL_PROPS)
  154. ])
  155. expect(inner(root)).toBe('<div><p foo="dynamic"></p></div>')
  156. expect(block!.dynamicChildren!.length).toBe(1)
  157. expect(serialize(block!.dynamicChildren![0].el as TestElement)).toBe(
  158. '<p foo="dynamic"></p>'
  159. )
  160. propName = 'bar'
  161. renderWithBlock(() => [
  162. createVNode('p', { [propName]: 'dynamic' }, '', PatchFlags.FULL_PROPS)
  163. ])
  164. expect(inner(root)).toBe('<div><p bar="dynamic"></p></div>')
  165. expect(block!.dynamicChildren!.length).toBe(1)
  166. expect(serialize(block!.dynamicChildren![0].el as TestElement)).toBe(
  167. '<p bar="dynamic"></p>'
  168. )
  169. })
  170. // the order and length of the list will not change
  171. test('PatchFlags: PatchFlags.STABLE_FRAGMENT', async () => {
  172. let list = ['foo', 'bar']
  173. render(
  174. (openBlock(),
  175. (block = createBlock(
  176. Fragment,
  177. null,
  178. list.map(item => {
  179. return createVNode('p', null, item, PatchFlags.TEXT)
  180. }),
  181. PatchFlags.STABLE_FRAGMENT
  182. ))),
  183. root
  184. )
  185. expect(inner(root)).toBe('<p>foo</p><p>bar</p>')
  186. expect(block.dynamicChildren!.length).toBe(2)
  187. expect(serialize(block.dynamicChildren![0].el as TestElement)).toBe(
  188. '<p>foo</p>'
  189. )
  190. expect(serialize(block.dynamicChildren![1].el as TestElement)).toBe(
  191. '<p>bar</p>'
  192. )
  193. list = list.map(item => item.repeat(2))
  194. render(
  195. (openBlock(),
  196. createBlock(
  197. Fragment,
  198. null,
  199. list.map(item => {
  200. return createVNode('p', null, item, PatchFlags.TEXT)
  201. }),
  202. PatchFlags.STABLE_FRAGMENT
  203. )),
  204. root
  205. )
  206. expect(inner(root)).toBe('<p>foofoo</p><p>barbar</p>')
  207. expect(block.dynamicChildren!.length).toBe(2)
  208. expect(serialize(block.dynamicChildren![0].el as TestElement)).toBe(
  209. '<p>foofoo</p>'
  210. )
  211. expect(serialize(block.dynamicChildren![1].el as TestElement)).toBe(
  212. '<p>barbar</p>'
  213. )
  214. })
  215. // A Fragment with `UNKEYED_FRAGMENT` flag will always patch its children,
  216. // so there's no need for tracking dynamicChildren.
  217. test('PatchFlags: PatchFlags.UNKEYED_FRAGMENT', async () => {
  218. const list = [{ tag: 'p', text: 'foo' }]
  219. render(
  220. (openBlock(true),
  221. (block = createBlock(
  222. Fragment,
  223. null,
  224. list.map(item => {
  225. return createVNode(item.tag, null, item.text)
  226. }),
  227. PatchFlags.UNKEYED_FRAGMENT
  228. ))),
  229. root
  230. )
  231. expect(inner(root)).toBe('<p>foo</p>')
  232. expect(block.dynamicChildren!.length).toBe(0)
  233. list.unshift({ tag: 'i', text: 'bar' })
  234. render(
  235. (openBlock(true),
  236. createBlock(
  237. Fragment,
  238. null,
  239. list.map(item => {
  240. return createVNode(item.tag, null, item.text)
  241. }),
  242. PatchFlags.UNKEYED_FRAGMENT
  243. )),
  244. root
  245. )
  246. expect(inner(root)).toBe('<i>bar</i><p>foo</p>')
  247. expect(block.dynamicChildren!.length).toBe(0)
  248. })
  249. // A Fragment with `KEYED_FRAGMENT` will always patch its children,
  250. // so there's no need for tracking dynamicChildren.
  251. test('PatchFlags: PatchFlags.KEYED_FRAGMENT', async () => {
  252. const list = [{ tag: 'p', text: 'foo' }]
  253. render(
  254. (openBlock(true),
  255. (block = createBlock(
  256. Fragment,
  257. null,
  258. list.map(item => {
  259. return createVNode(item.tag, { key: item.tag }, item.text)
  260. }),
  261. PatchFlags.KEYED_FRAGMENT
  262. ))),
  263. root
  264. )
  265. expect(inner(root)).toBe('<p>foo</p>')
  266. expect(block.dynamicChildren!.length).toBe(0)
  267. list.unshift({ tag: 'i', text: 'bar' })
  268. render(
  269. (openBlock(true),
  270. createBlock(
  271. Fragment,
  272. null,
  273. list.map(item => {
  274. return createVNode(item.tag, { key: item.tag }, item.text)
  275. }),
  276. PatchFlags.KEYED_FRAGMENT
  277. )),
  278. root
  279. )
  280. expect(inner(root)).toBe('<i>bar</i><p>foo</p>')
  281. expect(block.dynamicChildren!.length).toBe(0)
  282. })
  283. test('PatchFlags: PatchFlags.NEED_PATCH', async () => {
  284. const spyMounted = jest.fn()
  285. const spyUpdated = jest.fn()
  286. const count = ref(0)
  287. const Comp = {
  288. setup() {
  289. return () => {
  290. count.value
  291. return (
  292. openBlock(),
  293. (block = createBlock('div', null, [
  294. createVNode(
  295. 'p',
  296. { onVnodeMounted: spyMounted, onVnodeBeforeUpdate: spyUpdated },
  297. '',
  298. PatchFlags.NEED_PATCH
  299. )
  300. ]))
  301. )
  302. }
  303. }
  304. }
  305. render(h(Comp), root)
  306. expect(inner(root)).toBe('<div><p></p></div>')
  307. expect(block!.dynamicChildren!.length).toBe(1)
  308. expect(serialize(block!.dynamicChildren![0].el as TestElement)).toBe(
  309. '<p></p>'
  310. )
  311. expect(spyMounted).toHaveBeenCalledTimes(1)
  312. expect(spyUpdated).toHaveBeenCalledTimes(0)
  313. count.value++
  314. await nextTick()
  315. expect(inner(root)).toBe('<div><p></p></div>')
  316. expect(block!.dynamicChildren!.length).toBe(1)
  317. expect(serialize(block!.dynamicChildren![0].el as TestElement)).toBe(
  318. '<p></p>'
  319. )
  320. expect(spyMounted).toHaveBeenCalledTimes(1)
  321. expect(spyUpdated).toHaveBeenCalledTimes(1)
  322. })
  323. test('PatchFlags: PatchFlags.BAIL', async () => {
  324. render(
  325. (openBlock(),
  326. (block = createBlock('div', null, [createVNode('p', null, 'foo')]))),
  327. root
  328. )
  329. expect(inner(root)).toBe('<div><p>foo</p></div>')
  330. expect(block!.dynamicChildren!.length).toBe(0)
  331. render(
  332. (openBlock(),
  333. (block = createBlock(
  334. 'div',
  335. null,
  336. [createVNode('i', null, 'bar')],
  337. PatchFlags.BAIL
  338. ))),
  339. root
  340. )
  341. expect(inner(root)).toBe('<div><i>bar</i></div>')
  342. expect(block!.dynamicChildren).toBe(null)
  343. })
  344. })