rendererOptimizedMode.spec.ts 12 KB

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