rendererChildren.spec.ts 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623
  1. // reference: https://github.com/vuejs/vue/blob/dev/test/unit/modules/vdom/patch/children.spec.js
  2. import {
  3. h,
  4. render,
  5. nodeOps,
  6. NodeTypes,
  7. TestElement,
  8. serialize,
  9. serializeInner,
  10. mockWarn
  11. } from '@vue/runtime-test'
  12. mockWarn()
  13. function toSpan(content: any) {
  14. if (typeof content === 'string') {
  15. return h('span', content.toString())
  16. } else {
  17. return h('span', { key: content }, content.toString())
  18. }
  19. }
  20. const inner = (c: TestElement) => serializeInner(c)
  21. function shuffle(array: Array<any>) {
  22. let currentIndex = array.length
  23. let temporaryValue
  24. let randomIndex
  25. // while there remain elements to shuffle...
  26. while (currentIndex !== 0) {
  27. // pick a remaining element...
  28. randomIndex = Math.floor(Math.random() * currentIndex)
  29. currentIndex -= 1
  30. // and swap it with the current element.
  31. temporaryValue = array[currentIndex]
  32. array[currentIndex] = array[randomIndex]
  33. array[randomIndex] = temporaryValue
  34. }
  35. return array
  36. }
  37. it('should patch previously empty children', () => {
  38. const root = nodeOps.createElement('div')
  39. render(h('div', []), root)
  40. expect(inner(root)).toBe('<div></div>')
  41. render(h('div', ['hello']), root)
  42. expect(inner(root)).toBe('<div>hello</div>')
  43. })
  44. it('should patch previously null children', () => {
  45. const root = nodeOps.createElement('div')
  46. render(h('div'), root)
  47. expect(inner(root)).toBe('<div></div>')
  48. render(h('div', ['hello']), root)
  49. expect(inner(root)).toBe('<div>hello</div>')
  50. })
  51. describe('renderer: keyed children', () => {
  52. let root: TestElement
  53. let elm: TestElement
  54. const renderChildren = (arr: number[]) => {
  55. render(h('div', arr.map(toSpan)), root)
  56. return root.children[0] as TestElement
  57. }
  58. beforeEach(() => {
  59. root = nodeOps.createElement('div')
  60. render(h('div', { id: 1 }, 'hello'), root)
  61. })
  62. test('append', () => {
  63. elm = renderChildren([1])
  64. expect(elm.children.length).toBe(1)
  65. elm = renderChildren([1, 2, 3])
  66. expect(elm.children.length).toBe(3)
  67. expect(serialize(elm.children[1])).toBe('<span>2</span>')
  68. expect(serialize(elm.children[2])).toBe('<span>3</span>')
  69. })
  70. test('prepend', () => {
  71. elm = renderChildren([4, 5])
  72. expect(elm.children.length).toBe(2)
  73. elm = renderChildren([1, 2, 3, 4, 5])
  74. expect(elm.children.length).toBe(5)
  75. expect((elm.children as TestElement[]).map(inner)).toEqual([
  76. '1',
  77. '2',
  78. '3',
  79. '4',
  80. '5'
  81. ])
  82. })
  83. test('insert in middle', () => {
  84. elm = renderChildren([1, 2, 4, 5])
  85. expect(elm.children.length).toBe(4)
  86. elm = renderChildren([1, 2, 3, 4, 5])
  87. expect(elm.children.length).toBe(5)
  88. expect((elm.children as TestElement[]).map(inner)).toEqual([
  89. '1',
  90. '2',
  91. '3',
  92. '4',
  93. '5'
  94. ])
  95. })
  96. test('insert at beginning and end', () => {
  97. elm = renderChildren([2, 3, 4])
  98. expect(elm.children.length).toBe(3)
  99. elm = renderChildren([1, 2, 3, 4, 5])
  100. expect(elm.children.length).toBe(5)
  101. expect((elm.children as TestElement[]).map(inner)).toEqual([
  102. '1',
  103. '2',
  104. '3',
  105. '4',
  106. '5'
  107. ])
  108. })
  109. test('insert to empty parent', () => {
  110. elm = renderChildren([])
  111. expect(elm.children.length).toBe(0)
  112. elm = renderChildren([1, 2, 3, 4, 5])
  113. expect(elm.children.length).toBe(5)
  114. expect((elm.children as TestElement[]).map(inner)).toEqual([
  115. '1',
  116. '2',
  117. '3',
  118. '4',
  119. '5'
  120. ])
  121. })
  122. test('remove all children from parent', () => {
  123. elm = renderChildren([1, 2, 3, 4, 5])
  124. expect(elm.children.length).toBe(5)
  125. expect((elm.children as TestElement[]).map(inner)).toEqual([
  126. '1',
  127. '2',
  128. '3',
  129. '4',
  130. '5'
  131. ])
  132. render(h('div'), root)
  133. expect(elm.children.length).toBe(0)
  134. })
  135. test('remove from beginning', () => {
  136. elm = renderChildren([1, 2, 3, 4, 5])
  137. expect(elm.children.length).toBe(5)
  138. elm = renderChildren([3, 4, 5])
  139. expect(elm.children.length).toBe(3)
  140. expect((elm.children as TestElement[]).map(inner)).toEqual(['3', '4', '5'])
  141. })
  142. test('remove from end', () => {
  143. elm = renderChildren([1, 2, 3, 4, 5])
  144. expect(elm.children.length).toBe(5)
  145. elm = renderChildren([1, 2, 3])
  146. expect(elm.children.length).toBe(3)
  147. expect((elm.children as TestElement[]).map(inner)).toEqual(['1', '2', '3'])
  148. })
  149. test('remove from middle', () => {
  150. elm = renderChildren([1, 2, 3, 4, 5])
  151. expect(elm.children.length).toBe(5)
  152. elm = renderChildren([1, 2, 4, 5])
  153. expect(elm.children.length).toBe(4)
  154. expect((elm.children as TestElement[]).map(inner)).toEqual([
  155. '1',
  156. '2',
  157. '4',
  158. '5'
  159. ])
  160. })
  161. test('moving single child forward', () => {
  162. elm = renderChildren([1, 2, 3, 4])
  163. expect(elm.children.length).toBe(4)
  164. elm = renderChildren([2, 3, 1, 4])
  165. expect(elm.children.length).toBe(4)
  166. expect((elm.children as TestElement[]).map(inner)).toEqual([
  167. '2',
  168. '3',
  169. '1',
  170. '4'
  171. ])
  172. })
  173. test('moving single child backwards', () => {
  174. elm = renderChildren([1, 2, 3, 4])
  175. expect(elm.children.length).toBe(4)
  176. elm = renderChildren([1, 4, 2, 3])
  177. expect(elm.children.length).toBe(4)
  178. expect((elm.children as TestElement[]).map(inner)).toEqual([
  179. '1',
  180. '4',
  181. '2',
  182. '3'
  183. ])
  184. })
  185. test('moving single child to end', () => {
  186. elm = renderChildren([1, 2, 3])
  187. expect(elm.children.length).toBe(3)
  188. elm = renderChildren([2, 3, 1])
  189. expect(elm.children.length).toBe(3)
  190. expect((elm.children as TestElement[]).map(inner)).toEqual(['2', '3', '1'])
  191. })
  192. test('swap first and last', () => {
  193. elm = renderChildren([1, 2, 3, 4])
  194. expect(elm.children.length).toBe(4)
  195. elm = renderChildren([4, 2, 3, 1])
  196. expect(elm.children.length).toBe(4)
  197. expect((elm.children as TestElement[]).map(inner)).toEqual([
  198. '4',
  199. '2',
  200. '3',
  201. '1'
  202. ])
  203. })
  204. test('move to left & replace', () => {
  205. elm = renderChildren([1, 2, 3, 4, 5])
  206. expect(elm.children.length).toBe(5)
  207. elm = renderChildren([4, 1, 2, 3, 6])
  208. expect(elm.children.length).toBe(5)
  209. expect((elm.children as TestElement[]).map(inner)).toEqual([
  210. '4',
  211. '1',
  212. '2',
  213. '3',
  214. '6'
  215. ])
  216. })
  217. test('move to left and leaves hold', () => {
  218. elm = renderChildren([1, 4, 5])
  219. expect(elm.children.length).toBe(3)
  220. elm = renderChildren([4, 6])
  221. expect((elm.children as TestElement[]).map(inner)).toEqual(['4', '6'])
  222. })
  223. test('moved and set to undefined element ending at the end', () => {
  224. elm = renderChildren([2, 4, 5])
  225. expect(elm.children.length).toBe(3)
  226. elm = renderChildren([4, 5, 3])
  227. expect(elm.children.length).toBe(3)
  228. expect((elm.children as TestElement[]).map(inner)).toEqual(['4', '5', '3'])
  229. })
  230. test('reverse element', () => {
  231. elm = renderChildren([1, 2, 3, 4, 5, 6, 7, 8])
  232. expect(elm.children.length).toBe(8)
  233. elm = renderChildren([8, 7, 6, 5, 4, 3, 2, 1])
  234. expect((elm.children as TestElement[]).map(inner)).toEqual([
  235. '8',
  236. '7',
  237. '6',
  238. '5',
  239. '4',
  240. '3',
  241. '2',
  242. '1'
  243. ])
  244. })
  245. test('something', () => {
  246. elm = renderChildren([0, 1, 2, 3, 4, 5])
  247. expect(elm.children.length).toBe(6)
  248. elm = renderChildren([4, 3, 2, 1, 5, 0])
  249. expect((elm.children as TestElement[]).map(inner)).toEqual([
  250. '4',
  251. '3',
  252. '2',
  253. '1',
  254. '5',
  255. '0'
  256. ])
  257. })
  258. test('random shuffle', () => {
  259. const elms = 14
  260. const samples = 5
  261. const arr = [...Array(elms).keys()]
  262. const opacities: string[] = []
  263. function spanNumWithOpacity(n: number, o: string) {
  264. return h('span', { key: n, style: { opacity: o } }, n.toString())
  265. }
  266. for (let n = 0; n < samples; ++n) {
  267. render(h('span', arr.map(n => spanNumWithOpacity(n, '1'))), root)
  268. elm = root.children[0] as TestElement
  269. for (let i = 0; i < elms; ++i) {
  270. expect(serializeInner(elm.children[i] as TestElement)).toBe(
  271. i.toString()
  272. )
  273. opacities[i] = Math.random()
  274. .toFixed(5)
  275. .toString()
  276. }
  277. const shufArr = shuffle(arr.slice(0))
  278. render(
  279. h('span', arr.map(n => spanNumWithOpacity(shufArr[n], opacities[n]))),
  280. root
  281. )
  282. elm = root.children[0] as TestElement
  283. for (let i = 0; i < elms; ++i) {
  284. expect(serializeInner(elm.children[i] as TestElement)).toBe(
  285. shufArr[i].toString()
  286. )
  287. expect(elm.children[i]).toMatchObject({
  288. props: {
  289. style: {
  290. opacity: opacities[i]
  291. }
  292. }
  293. })
  294. }
  295. }
  296. })
  297. test('children with the same key but with different tag', () => {
  298. render(
  299. h('div', [
  300. h('div', { key: 1 }, 'one'),
  301. h('div', { key: 2 }, 'two'),
  302. h('div', { key: 3 }, 'three'),
  303. h('div', { key: 4 }, 'four')
  304. ]),
  305. root
  306. )
  307. elm = root.children[0] as TestElement
  308. expect((elm.children as TestElement[]).map(c => c.tag)).toEqual([
  309. 'div',
  310. 'div',
  311. 'div',
  312. 'div'
  313. ])
  314. expect((elm.children as TestElement[]).map(inner)).toEqual([
  315. 'one',
  316. 'two',
  317. 'three',
  318. 'four'
  319. ])
  320. render(
  321. h('div', [
  322. h('div', { key: 4 }, 'four'),
  323. h('span', { key: 3 }, 'three'),
  324. h('span', { key: 2 }, 'two'),
  325. h('div', { key: 1 }, 'one')
  326. ]),
  327. root
  328. )
  329. expect((elm.children as TestElement[]).map(c => c.tag)).toEqual([
  330. 'div',
  331. 'span',
  332. 'span',
  333. 'div'
  334. ])
  335. expect((elm.children as TestElement[]).map(inner)).toEqual([
  336. 'four',
  337. 'three',
  338. 'two',
  339. 'one'
  340. ])
  341. })
  342. test('children with the same tag, same key, but one with data and one without data', () => {
  343. render(h('div', [h('div', { class: 'hi' }, 'one')]), root)
  344. elm = root.children[0] as TestElement
  345. expect(elm.children[0]).toMatchObject({
  346. props: {
  347. class: 'hi'
  348. }
  349. })
  350. render(h('div', [h('div', 'four')]), root)
  351. elm = root.children[0] as TestElement
  352. expect(elm.children[0] as TestElement).toMatchObject({
  353. props: {
  354. // in the DOM renderer this will be ''
  355. // but the test renderer simply sets whatever value it receives.
  356. class: null
  357. }
  358. })
  359. expect(serialize(elm.children[0])).toBe(`<div>four</div>`)
  360. })
  361. test('should warn with duplicate keys', () => {
  362. renderChildren([1, 2, 3, 4, 5])
  363. renderChildren([1, 6, 6, 3, 5])
  364. expect(`Duplicate keys`).toHaveBeenWarned()
  365. })
  366. })
  367. describe('renderer: unkeyed children', () => {
  368. let root: TestElement
  369. let elm: TestElement
  370. const renderChildren = (arr: Array<number | string>) => {
  371. render(h('div', arr.map(toSpan)), root)
  372. return root.children[0] as TestElement
  373. }
  374. beforeEach(() => {
  375. root = nodeOps.createElement('div')
  376. render(h('div', { id: 1 }, 'hello'), root)
  377. })
  378. test('move a key in non-keyed nodes with a size up', () => {
  379. elm = renderChildren([1, 'a', 'b', 'c'])
  380. expect(elm.children.length).toBe(4)
  381. expect((elm.children as TestElement[]).map(inner)).toEqual([
  382. '1',
  383. 'a',
  384. 'b',
  385. 'c'
  386. ])
  387. elm = renderChildren(['d', 'a', 'b', 'c', 1, 'e'])
  388. expect(elm.children.length).toBe(6)
  389. expect((elm.children as TestElement[]).map(inner)).toEqual([
  390. 'd',
  391. 'a',
  392. 'b',
  393. 'c',
  394. '1',
  395. 'e'
  396. ])
  397. })
  398. test('append elements with updating children without keys', () => {
  399. elm = renderChildren(['hello'])
  400. expect((elm.children as TestElement[]).map(inner)).toEqual(['hello'])
  401. elm = renderChildren(['hello', 'world'])
  402. expect((elm.children as TestElement[]).map(inner)).toEqual([
  403. 'hello',
  404. 'world'
  405. ])
  406. })
  407. test('unmoved text nodes with updating children without keys', () => {
  408. render(h('div', ['text', h('span', ['hello'])]), root)
  409. elm = root.children[0] as TestElement
  410. expect(elm.children[0]).toMatchObject({
  411. type: NodeTypes.TEXT,
  412. text: 'text'
  413. })
  414. render(h('div', ['text', h('span', ['hello'])]), root)
  415. elm = root.children[0] as TestElement
  416. expect(elm.children[0]).toMatchObject({
  417. type: NodeTypes.TEXT,
  418. text: 'text'
  419. })
  420. })
  421. test('changing text children with updating children without keys', () => {
  422. render(h('div', ['text', h('span', ['hello'])]), root)
  423. elm = root.children[0] as TestElement
  424. expect(elm.children[0]).toMatchObject({
  425. type: NodeTypes.TEXT,
  426. text: 'text'
  427. })
  428. render(h('div', ['text2', h('span', ['hello'])]), root)
  429. elm = root.children[0] as TestElement
  430. expect(elm.children[0]).toMatchObject({
  431. type: NodeTypes.TEXT,
  432. text: 'text2'
  433. })
  434. })
  435. test('prepend element with updating children without keys', () => {
  436. render(h('div', [h('span', ['world'])]), root)
  437. elm = root.children[0] as TestElement
  438. expect((elm.children as TestElement[]).map(inner)).toEqual(['world'])
  439. render(h('div', [h('span', ['hello']), h('span', ['world'])]), root)
  440. expect((elm.children as TestElement[]).map(inner)).toEqual([
  441. 'hello',
  442. 'world'
  443. ])
  444. })
  445. test('prepend element of different tag type with updating children without keys', () => {
  446. render(h('div', [h('span', ['world'])]), root)
  447. elm = root.children[0] as TestElement
  448. expect((elm.children as TestElement[]).map(inner)).toEqual(['world'])
  449. render(h('div', [h('div', ['hello']), h('span', ['world'])]), root)
  450. expect((elm.children as TestElement[]).map(c => c.tag)).toEqual([
  451. 'div',
  452. 'span'
  453. ])
  454. expect((elm.children as TestElement[]).map(inner)).toEqual([
  455. 'hello',
  456. 'world'
  457. ])
  458. })
  459. test('remove elements with updating children without keys', () => {
  460. render(
  461. h('div', [h('span', ['one']), h('span', ['two']), h('span', ['three'])]),
  462. root
  463. )
  464. elm = root.children[0] as TestElement
  465. expect((elm.children as TestElement[]).map(inner)).toEqual([
  466. 'one',
  467. 'two',
  468. 'three'
  469. ])
  470. render(h('div', [h('span', ['one']), h('span', ['three'])]), root)
  471. elm = root.children[0] as TestElement
  472. expect((elm.children as TestElement[]).map(inner)).toEqual(['one', 'three'])
  473. })
  474. test('remove a single text node with updating children without keys', () => {
  475. render(h('div', ['one']), root)
  476. elm = root.children[0] as TestElement
  477. expect(serializeInner(elm)).toBe('one')
  478. render(h('div'), root)
  479. expect(serializeInner(elm)).toBe('')
  480. })
  481. test('remove a single text node when children are updated', () => {
  482. render(h('div', ['one']), root)
  483. elm = root.children[0] as TestElement
  484. expect(serializeInner(elm)).toBe('one')
  485. render(h('div', [h('div', ['two']), h('span', ['three'])]), root)
  486. elm = root.children[0] as TestElement
  487. expect((elm.children as TestElement[]).map(inner)).toEqual(['two', 'three'])
  488. })
  489. test('remove a text node among other elements', () => {
  490. render(h('div', ['one', h('span', ['two'])]), root)
  491. elm = root.children[0] as TestElement
  492. expect((elm.children as TestElement[]).map(c => serialize(c))).toEqual([
  493. 'one',
  494. '<span>two</span>'
  495. ])
  496. render(h('div', [h('div', ['three'])]), root)
  497. elm = root.children[0] as TestElement
  498. expect(elm.children.length).toBe(1)
  499. expect(serialize(elm.children[0])).toBe('<div>three</div>')
  500. })
  501. test('reorder elements', () => {
  502. render(
  503. h('div', [h('span', ['one']), h('div', ['two']), h('b', ['three'])]),
  504. root
  505. )
  506. elm = root.children[0] as TestElement
  507. expect((elm.children as TestElement[]).map(inner)).toEqual([
  508. 'one',
  509. 'two',
  510. 'three'
  511. ])
  512. render(
  513. h('div', [h('b', ['three']), h('div', ['two']), h('span', ['one'])]),
  514. root
  515. )
  516. elm = root.children[0] as TestElement
  517. expect((elm.children as TestElement[]).map(inner)).toEqual([
  518. 'three',
  519. 'two',
  520. 'one'
  521. ])
  522. })
  523. // #6502
  524. test('should not de-opt when both head and tail change', () => {
  525. render(h('div', [null, h('div'), null]), root)
  526. elm = root.children[0] as TestElement
  527. const original = elm.children[1]
  528. render(h('div', [h('p'), h('div'), h('p')]), root)
  529. elm = root.children[0] as TestElement
  530. const postPatch = elm.children[1]
  531. expect(postPatch).toBe(original)
  532. })
  533. })