children.spec.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489
  1. import { patch } from 'web/runtime/patch'
  2. import VNode from 'core/vdom/vnode'
  3. function prop (name) {
  4. return obj => { return obj[name] }
  5. }
  6. function map (fn, list) {
  7. const ret = []
  8. for (let i = 0; i < list.length; i++) {
  9. ret[i] = fn(list[i])
  10. }
  11. return ret
  12. }
  13. function spanNum (n) {
  14. if (typeof n === 'string') {
  15. return new VNode('span', {}, undefined, n)
  16. } else {
  17. return new VNode('span', { key: n }, undefined, n.toString())
  18. }
  19. }
  20. function shuffle (array) {
  21. let currentIndex = array.length
  22. let temporaryValue
  23. let randomIndex
  24. // while there remain elements to shuffle...
  25. while (currentIndex !== 0) {
  26. // pick a remaining element...
  27. randomIndex = Math.floor(Math.random() * currentIndex)
  28. currentIndex -= 1
  29. // and swap it with the current element.
  30. temporaryValue = array[currentIndex]
  31. array[currentIndex] = array[randomIndex]
  32. array[randomIndex] = temporaryValue
  33. }
  34. return array
  35. }
  36. const inner = prop('innerHTML')
  37. const tag = prop('tagName')
  38. describe('vdom patch: children', () => {
  39. let vnode0
  40. beforeEach(() => {
  41. vnode0 = new VNode('p', { attrs: { id: '1' }}, [createTextVNode('hello world')])
  42. patch(null, vnode0)
  43. })
  44. it('should appends elements', () => {
  45. const vnode1 = new VNode('p', {}, [1].map(spanNum))
  46. const vnode2 = new VNode('p', {}, [1, 2, 3].map(spanNum))
  47. let elm = patch(vnode0, vnode1)
  48. expect(elm.children.length).toBe(1)
  49. elm = patch(vnode1, vnode2)
  50. expect(elm.children.length).toBe(3)
  51. expect(elm.children[1].innerHTML).toBe('2')
  52. expect(elm.children[2].innerHTML).toBe('3')
  53. })
  54. it('should prepends elements', () => {
  55. const vnode1 = new VNode('p', {}, [4, 5].map(spanNum))
  56. const vnode2 = new VNode('p', {}, [1, 2, 3, 4, 5].map(spanNum))
  57. let elm = patch(vnode0, vnode1)
  58. expect(elm.children.length).toBe(2)
  59. elm = patch(vnode1, vnode2)
  60. expect(map(inner, elm.children)).toEqual(['1', '2', '3', '4', '5'])
  61. })
  62. it('should add elements in the middle', () => {
  63. const vnode1 = new VNode('p', {}, [1, 2, 4, 5].map(spanNum))
  64. const vnode2 = new VNode('p', {}, [1, 2, 3, 4, 5].map(spanNum))
  65. let elm = patch(vnode0, vnode1)
  66. expect(elm.children.length).toBe(4)
  67. elm = patch(vnode1, vnode2)
  68. expect(map(inner, elm.children)).toEqual(['1', '2', '3', '4', '5'])
  69. })
  70. it('should add elements at begin and end', () => {
  71. const vnode1 = new VNode('p', {}, [2, 3, 4].map(spanNum))
  72. const vnode2 = new VNode('p', {}, [1, 2, 3, 4, 5].map(spanNum))
  73. let elm = patch(vnode0, vnode1)
  74. expect(elm.children.length).toBe(3)
  75. elm = patch(vnode1, vnode2)
  76. expect(map(inner, elm.children)).toEqual(['1', '2', '3', '4', '5'])
  77. })
  78. it('should add children to parent with no children', () => {
  79. const vnode1 = new VNode('p', { key: 'p' })
  80. const vnode2 = new VNode('p', { key: 'p' }, [1, 2, 3].map(spanNum))
  81. let elm = patch(vnode0, vnode1)
  82. expect(elm.children.length).toBe(0)
  83. elm = patch(vnode1, vnode2)
  84. expect(map(inner, elm.children)).toEqual(['1', '2', '3'])
  85. })
  86. it('should remove all children from parent', () => {
  87. const vnode1 = new VNode('p', { key: 'p' }, [1, 2, 3].map(spanNum))
  88. const vnode2 = new VNode('p', { key: 'p' })
  89. let elm = patch(vnode0, vnode1)
  90. expect(map(inner, elm.children)).toEqual(['1', '2', '3'])
  91. elm = patch(vnode1, vnode2)
  92. expect(elm.children.length).toBe(0)
  93. })
  94. it('should remove elements from the beginning', () => {
  95. const vnode1 = new VNode('p', {}, [1, 2, 3, 4, 5].map(spanNum))
  96. const vnode2 = new VNode('p', {}, [3, 4, 5].map(spanNum))
  97. let elm = patch(vnode0, vnode1)
  98. expect(elm.children.length).toBe(5)
  99. elm = patch(vnode1, vnode2)
  100. expect(map(inner, elm.children)).toEqual(['3', '4', '5'])
  101. })
  102. it('should removes elements from end', () => {
  103. const vnode1 = new VNode('p', {}, [1, 2, 3, 4, 5].map(spanNum))
  104. const vnode2 = new VNode('p', {}, [1, 2, 3].map(spanNum))
  105. let elm = patch(vnode0, vnode1)
  106. expect(elm.children.length).toBe(5)
  107. elm = patch(vnode1, vnode2)
  108. expect(elm.children.length).toBe(3)
  109. expect(map(inner, elm.children)).toEqual(['1', '2', '3'])
  110. })
  111. it('should remove elements from the middle', () => {
  112. const vnode1 = new VNode('p', {}, [1, 2, 3, 4, 5].map(spanNum))
  113. const vnode2 = new VNode('p', {}, [1, 2, 4, 5].map(spanNum))
  114. let elm = patch(vnode0, vnode1)
  115. expect(elm.children.length).toBe(5)
  116. elm = patch(vnode1, vnode2)
  117. expect(elm.children.length).toBe(4)
  118. expect(map(inner, elm.children)).toEqual(['1', '2', '4', '5'])
  119. })
  120. it('should moves element forward', () => {
  121. const vnode1 = new VNode('p', {}, [1, 2, 3, 4].map(spanNum))
  122. const vnode2 = new VNode('p', {}, [2, 3, 1, 4].map(spanNum))
  123. let elm = patch(vnode0, vnode1)
  124. expect(elm.children.length).toBe(4)
  125. elm = patch(vnode1, vnode2)
  126. expect(elm.children.length).toBe(4)
  127. expect(map(inner, elm.children)).toEqual(['2', '3', '1', '4'])
  128. })
  129. it('should move elements to end', () => {
  130. const vnode1 = new VNode('p', {}, [1, 2, 3].map(spanNum))
  131. const vnode2 = new VNode('p', {}, [2, 3, 1].map(spanNum))
  132. let elm = patch(vnode0, vnode1)
  133. expect(elm.children.length).toBe(3)
  134. elm = patch(vnode1, vnode2)
  135. expect(elm.children.length).toBe(3)
  136. expect(map(inner, elm.children)).toEqual(['2', '3', '1'])
  137. })
  138. it('should move element backwards', () => {
  139. const vnode1 = new VNode('p', {}, [1, 2, 3, 4].map(spanNum))
  140. const vnode2 = new VNode('p', {}, [1, 4, 2, 3].map(spanNum))
  141. let elm = patch(vnode0, vnode1)
  142. expect(elm.children.length).toBe(4)
  143. elm = patch(vnode1, vnode2)
  144. expect(elm.children.length).toBe(4)
  145. expect(map(inner, elm.children)).toEqual(['1', '4', '2', '3'])
  146. })
  147. it('should swap first and last', () => {
  148. const vnode1 = new VNode('p', {}, [1, 2, 3, 4].map(spanNum))
  149. const vnode2 = new VNode('p', {}, [4, 2, 3, 1].map(spanNum))
  150. let elm = patch(vnode0, vnode1)
  151. expect(elm.children.length).toBe(4)
  152. elm = patch(vnode1, vnode2)
  153. expect(elm.children.length).toBe(4)
  154. expect(map(inner, elm.children)).toEqual(['4', '2', '3', '1'])
  155. })
  156. it('should move to left and replace', () => {
  157. const vnode1 = new VNode('p', {}, [1, 2, 3, 4, 5].map(spanNum))
  158. const vnode2 = new VNode('p', {}, [4, 1, 2, 3, 6].map(spanNum))
  159. let elm = patch(vnode0, vnode1)
  160. expect(elm.children.length).toBe(5)
  161. elm = patch(vnode1, vnode2)
  162. expect(elm.children.length).toBe(5)
  163. expect(map(inner, elm.children)).toEqual(['4', '1', '2', '3', '6'])
  164. })
  165. it('should move to left and leaves hold', () => {
  166. const vnode1 = new VNode('p', {}, [1, 4, 5].map(spanNum))
  167. const vnode2 = new VNode('p', {}, [4, 6].map(spanNum))
  168. let elm = patch(vnode0, vnode1)
  169. expect(elm.children.length).toBe(3)
  170. elm = patch(vnode1, vnode2)
  171. expect(map(inner, elm.children)).toEqual(['4', '6'])
  172. })
  173. it('should handle moved and set to undefined element ending at the end', () => {
  174. const vnode1 = new VNode('p', {}, [2, 4, 5].map(spanNum))
  175. const vnode2 = new VNode('p', {}, [4, 5, 3].map(spanNum))
  176. let elm = patch(vnode0, vnode1)
  177. expect(elm.children.length).toBe(3)
  178. elm = patch(vnode1, vnode2)
  179. expect(elm.children.length).toBe(3)
  180. expect(map(inner, elm.children)).toEqual(['4', '5', '3'])
  181. })
  182. it('should move a key in non-keyed nodes with a size up', () => {
  183. const vnode1 = new VNode('p', {}, [1, 'a', 'b', 'c'].map(spanNum))
  184. const vnode2 = new VNode('p', {}, ['d', 'a', 'b', 'c', 1, 'e'].map(spanNum))
  185. let elm = patch(vnode0, vnode1)
  186. expect(elm.children.length).toBe(4)
  187. expect(elm.textContent, '1abc')
  188. elm = patch(vnode1, vnode2)
  189. expect(elm.children.length).toBe(6)
  190. expect(elm.textContent, 'dabc1e')
  191. })
  192. it('should reverse element', () => {
  193. const vnode1 = new VNode('p', {}, [1, 2, 3, 4, 5, 6, 7, 8].map(spanNum))
  194. const vnode2 = new VNode('p', {}, [8, 7, 6, 5, 4, 3, 2, 1].map(spanNum))
  195. let elm = patch(vnode0, vnode1)
  196. expect(elm.children.length).toBe(8)
  197. elm = patch(vnode1, vnode2)
  198. expect(map(inner, elm.children)).toEqual(['8', '7', '6', '5', '4', '3', '2', '1'])
  199. })
  200. it('something', () => {
  201. const vnode1 = new VNode('p', {}, [0, 1, 2, 3, 4, 5].map(spanNum))
  202. const vnode2 = new VNode('p', {}, [4, 3, 2, 1, 5, 0].map(spanNum))
  203. let elm = patch(vnode0, vnode1)
  204. expect(elm.children.length).toBe(6)
  205. elm = patch(vnode1, vnode2)
  206. expect(map(inner, elm.children)).toEqual(['4', '3', '2', '1', '5', '0'])
  207. })
  208. it('should handle random shuffle', () => {
  209. let n
  210. let i
  211. const arr = []
  212. const opacities = []
  213. const elms = 14
  214. const samples = 5
  215. function spanNumWithOpacity (n, o) {
  216. return new VNode('span', { key: n, style: { opacity: o }}, undefined, n.toString())
  217. }
  218. for (n = 0; n < elms; ++n) { arr[n] = n }
  219. for (n = 0; n < samples; ++n) {
  220. const vnode1 = new VNode('span', {}, arr.map(n => {
  221. return spanNumWithOpacity(n, '1')
  222. }))
  223. const shufArr = shuffle(arr.slice(0))
  224. let elm = patch(vnode0, vnode1)
  225. for (i = 0; i < elms; ++i) {
  226. expect(elm.children[i].innerHTML).toBe(i.toString())
  227. opacities[i] = Math.random().toFixed(5).toString()
  228. }
  229. const vnode2 = new VNode('span', {}, arr.map(n => {
  230. return spanNumWithOpacity(shufArr[n], opacities[n])
  231. }))
  232. elm = patch(vnode1, vnode2)
  233. for (i = 0; i < elms; ++i) {
  234. expect(elm.children[i].innerHTML).toBe(shufArr[i].toString())
  235. expect(opacities[i].indexOf(elm.children[i].style.opacity)).toBe(0)
  236. }
  237. }
  238. })
  239. it('should append elements with updating children without keys', () => {
  240. const vnode1 = new VNode('div', {}, [
  241. new VNode('span', {}, undefined, 'hello')
  242. ])
  243. const vnode2 = new VNode('div', {}, [
  244. new VNode('span', {}, undefined, 'hello'),
  245. new VNode('span', {}, undefined, 'world')
  246. ])
  247. let elm = patch(vnode0, vnode1)
  248. expect(map(inner, elm.children)).toEqual(['hello'])
  249. elm = patch(vnode1, vnode2)
  250. expect(map(inner, elm.children)).toEqual(['hello', 'world'])
  251. })
  252. it('should handle unmoved text nodes with updating children without keys', () => {
  253. const vnode1 = new VNode('div', {}, [
  254. createTextVNode('text'),
  255. new VNode('span', {}, undefined, 'hello')
  256. ])
  257. const vnode2 = new VNode('div', {}, [
  258. createTextVNode('text'),
  259. new VNode('span', {}, undefined, 'hello')
  260. ])
  261. let elm = patch(vnode0, vnode1)
  262. expect(elm.childNodes[0].textContent).toBe('text')
  263. elm = patch(vnode1, vnode2)
  264. expect(elm.childNodes[0].textContent).toBe('text')
  265. })
  266. it('should handle changing text children with updating children without keys', () => {
  267. const vnode1 = new VNode('div', {}, [
  268. createTextVNode('text'),
  269. new VNode('span', {}, undefined, 'hello')
  270. ])
  271. const vnode2 = new VNode('div', {}, [
  272. createTextVNode('text2'),
  273. new VNode('span', {}, undefined, 'hello')
  274. ])
  275. let elm = patch(vnode0, vnode1)
  276. expect(elm.childNodes[0].textContent).toBe('text')
  277. elm = patch(vnode1, vnode2)
  278. expect(elm.childNodes[0].textContent).toBe('text2')
  279. })
  280. it('should prepend element with updating children without keys', () => {
  281. const vnode1 = new VNode('div', {}, [
  282. new VNode('span', {}, undefined, 'world')
  283. ])
  284. const vnode2 = new VNode('div', {}, [
  285. new VNode('span', {}, undefined, 'hello'),
  286. new VNode('span', {}, undefined, 'world')
  287. ])
  288. let elm = patch(vnode0, vnode1)
  289. expect(map(inner, elm.children)).toEqual(['world'])
  290. elm = patch(vnode1, vnode2)
  291. expect(map(inner, elm.children)).toEqual(['hello', 'world'])
  292. })
  293. it('should prepend element of different tag type with updating children without keys', () => {
  294. const vnode1 = new VNode('div', {}, [
  295. new VNode('span', {}, undefined, 'world')
  296. ])
  297. const vnode2 = new VNode('div', {}, [
  298. new VNode('div', {}, undefined, 'hello'),
  299. new VNode('span', {}, undefined, 'world')
  300. ])
  301. let elm = patch(vnode0, vnode1)
  302. expect(map(inner, elm.children)).toEqual(['world'])
  303. elm = patch(vnode1, vnode2)
  304. expect(map(prop('tagName'), elm.children)).toEqual(['DIV', 'SPAN'])
  305. expect(map(inner, elm.children)).toEqual(['hello', 'world'])
  306. })
  307. it('should remove elements with updating children without keys', () => {
  308. const vnode1 = new VNode('div', {}, [
  309. new VNode('span', {}, undefined, 'one'),
  310. new VNode('span', {}, undefined, 'two'),
  311. new VNode('span', {}, undefined, 'three')
  312. ])
  313. const vnode2 = new VNode('div', {}, [
  314. new VNode('span', {}, undefined, 'one'),
  315. new VNode('span', {}, undefined, 'three')
  316. ])
  317. let elm = patch(vnode0, vnode1)
  318. expect(map(inner, elm.children)).toEqual(['one', 'two', 'three'])
  319. elm = patch(vnode1, vnode2)
  320. expect(map(inner, elm.children)).toEqual(['one', 'three'])
  321. })
  322. it('should remove a single text node with updating children without keys', () => {
  323. const vnode1 = new VNode('div', {}, undefined, 'one')
  324. const vnode2 = new VNode('div', {})
  325. let elm = patch(vnode0, vnode1)
  326. expect(elm.textContent).toBe('one')
  327. elm = patch(vnode1, vnode2)
  328. expect(elm.textContent).toBe('')
  329. })
  330. it('should remove a single text node when children are updated', () => {
  331. const vnode1 = new VNode('div', {}, undefined, 'one')
  332. const vnode2 = new VNode('div', {}, [
  333. new VNode('div', {}, undefined, 'two'),
  334. new VNode('span', {}, undefined, 'three')
  335. ])
  336. let elm = patch(vnode0, vnode1)
  337. expect(elm.textContent).toBe('one')
  338. elm = patch(vnode1, vnode2)
  339. expect(map(prop('textContent'), elm.childNodes)).toEqual(['two', 'three'])
  340. })
  341. it('should remove a text node among other elements', () => {
  342. const vnode1 = new VNode('div', {}, [
  343. createTextVNode('one'),
  344. new VNode('span', {}, undefined, 'two')
  345. ])
  346. const vnode2 = new VNode('div', {}, [
  347. new VNode('div', {}, undefined, 'three')
  348. ])
  349. let elm = patch(vnode0, vnode1)
  350. expect(map(prop('textContent'), elm.childNodes)).toEqual(['one', 'two'])
  351. elm = patch(vnode1, vnode2)
  352. expect(elm.childNodes.length).toBe(1)
  353. expect(elm.childNodes[0].tagName).toBe('DIV')
  354. expect(elm.childNodes[0].textContent).toBe('three')
  355. })
  356. it('should reorder elements', () => {
  357. const vnode1 = new VNode('div', {}, [
  358. new VNode('span', {}, undefined, 'one'),
  359. new VNode('div', {}, undefined, 'two'),
  360. new VNode('b', {}, undefined, 'three')
  361. ])
  362. const vnode2 = new VNode('div', {}, [
  363. new VNode('b', {}, undefined, 'three'),
  364. new VNode('span', {}, undefined, 'two'),
  365. new VNode('div', {}, undefined, 'one')
  366. ])
  367. let elm = patch(vnode0, vnode1)
  368. expect(map(inner, elm.children)).toEqual(['one', 'two', 'three'])
  369. elm = patch(vnode1, vnode2)
  370. expect(map(inner, elm.children)).toEqual(['three', 'two', 'one'])
  371. })
  372. it('should handle children with the same key but with different tag', () => {
  373. const vnode1 = new VNode('div', {}, [
  374. new VNode('div', { key: 1 }, undefined, 'one'),
  375. new VNode('div', { key: 2 }, undefined, 'two'),
  376. new VNode('div', { key: 3 }, undefined, 'three'),
  377. new VNode('div', { key: 4 }, undefined, 'four')
  378. ])
  379. const vnode2 = new VNode('div', {}, [
  380. new VNode('div', { key: 4 }, undefined, 'four'),
  381. new VNode('span', { key: 3 }, undefined, 'three'),
  382. new VNode('span', { key: 2 }, undefined, 'two'),
  383. new VNode('div', { key: 1 }, undefined, 'one')
  384. ])
  385. let elm = patch(vnode0, vnode1)
  386. expect(map(tag, elm.children)).toEqual(['DIV', 'DIV', 'DIV', 'DIV'])
  387. expect(map(inner, elm.children)).toEqual(['one', 'two', 'three', 'four'])
  388. elm = patch(vnode1, vnode2)
  389. expect(map(tag, elm.children)).toEqual(['DIV', 'SPAN', 'SPAN', 'DIV'])
  390. expect(map(inner, elm.children)).toEqual(['four', 'three', 'two', 'one'])
  391. })
  392. it('should handle children with the same tag, same key, but one with data and one without data', () => {
  393. const vnode1 = new VNode('div', {}, [
  394. new VNode('div', { class: 'hi' }, undefined, 'one')
  395. ])
  396. const vnode2 = new VNode('div', {}, [
  397. new VNode('div', undefined, undefined, 'four')
  398. ])
  399. let elm = patch(vnode0, vnode1)
  400. const child1 = elm.children[0]
  401. expect(child1.className).toBe('hi')
  402. elm = patch(vnode1, vnode2)
  403. const child2 = elm.children[0]
  404. expect(child1).not.toBe(child2)
  405. expect(child2.className).toBe('')
  406. })
  407. it('should handle static vnodes properly', function () {
  408. function makeNode (text) {
  409. return new VNode('div', undefined, [
  410. new VNode(undefined, undefined, undefined, text)
  411. ])
  412. }
  413. const b = makeNode('B')
  414. b.isStatic = true
  415. b.key = `__static__1`
  416. const vnode1 = new VNode('div', {}, [makeNode('A'), b, makeNode('C')])
  417. const vnode2 = new VNode('div', {}, [b])
  418. const vnode3 = new VNode('div', {}, [makeNode('A'), b, makeNode('C')])
  419. let elm = patch(vnode0, vnode1)
  420. expect(elm.textContent).toBe('ABC')
  421. elm = patch(vnode1, vnode2)
  422. expect(elm.textContent).toBe('B')
  423. elm = patch(vnode2, vnode3)
  424. expect(elm.textContent).toBe('ABC')
  425. })
  426. it('should handle static vnodes inside ', function () {
  427. function makeNode (text) {
  428. return new VNode('div', undefined, [
  429. new VNode(undefined, undefined, undefined, text)
  430. ])
  431. }
  432. const b = makeNode('B')
  433. b.isStatic = true
  434. b.key = `__static__1`
  435. const vnode1 = new VNode('div', {}, [makeNode('A'), b, makeNode('C')])
  436. const vnode2 = new VNode('div', {}, [b])
  437. const vnode3 = new VNode('div', {}, [makeNode('A'), b, makeNode('C')])
  438. let elm = patch(vnode0, vnode1)
  439. expect(elm.textContent).toBe('ABC')
  440. elm = patch(vnode1, vnode2)
  441. expect(elm.textContent).toBe('B')
  442. elm = patch(vnode2, vnode3)
  443. expect(elm.textContent).toBe('ABC')
  444. })
  445. })