children.spec.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431
  1. import { basePatch as 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. describe('children', () => {
  38. let vnode0
  39. beforeEach(() => {
  40. vnode0 = new VNode('p', { attrs: { id: '1' }}, [createTextVNode('hello world')])
  41. patch(null, vnode0)
  42. })
  43. it('should appends elements', () => {
  44. const vnode1 = new VNode('p', {}, [1].map(spanNum))
  45. const vnode2 = new VNode('p', {}, [1, 2, 3].map(spanNum))
  46. let elm = patch(vnode0, vnode1)
  47. expect(elm.children.length).toBe(1)
  48. elm = patch(vnode1, vnode2)
  49. expect(elm.children.length).toBe(3)
  50. expect(elm.children[1].innerHTML).toBe('2')
  51. expect(elm.children[2].innerHTML).toBe('3')
  52. })
  53. it('should prepends elements', () => {
  54. const vnode1 = new VNode('p', {}, [4, 5].map(spanNum))
  55. const vnode2 = new VNode('p', {}, [1, 2, 3, 4, 5].map(spanNum))
  56. let elm = patch(vnode0, vnode1)
  57. expect(elm.children.length).toBe(2)
  58. elm = patch(vnode1, vnode2)
  59. expect(map(inner, elm.children)).toEqual(['1', '2', '3', '4', '5'])
  60. })
  61. it('should add elements in the middle', () => {
  62. const vnode1 = new VNode('p', {}, [1, 2, 4, 5].map(spanNum))
  63. const vnode2 = new VNode('p', {}, [1, 2, 3, 4, 5].map(spanNum))
  64. let elm = patch(vnode0, vnode1)
  65. expect(elm.children.length).toBe(4)
  66. elm = patch(vnode1, vnode2)
  67. expect(map(inner, elm.children)).toEqual(['1', '2', '3', '4', '5'])
  68. })
  69. it('should add elements at begin and end', () => {
  70. const vnode1 = new VNode('p', {}, [2, 3, 4].map(spanNum))
  71. const vnode2 = new VNode('p', {}, [1, 2, 3, 4, 5].map(spanNum))
  72. let elm = patch(vnode0, vnode1)
  73. expect(elm.children.length).toBe(3)
  74. elm = patch(vnode1, vnode2)
  75. expect(map(inner, elm.children)).toEqual(['1', '2', '3', '4', '5'])
  76. })
  77. it('should add children to parent with no children', () => {
  78. const vnode1 = new VNode('p', { key: 'p' })
  79. const vnode2 = new VNode('p', { key: 'p' }, [1, 2, 3].map(spanNum))
  80. let elm = patch(vnode0, vnode1)
  81. expect(elm.children.length).toBe(0)
  82. elm = patch(vnode1, vnode2)
  83. expect(map(inner, elm.children)).toEqual(['1', '2', '3'])
  84. })
  85. it('should remove all children from parent', () => {
  86. const vnode1 = new VNode('p', { key: 'p' }, [1, 2, 3].map(spanNum))
  87. const vnode2 = new VNode('p', { key: 'p' })
  88. let elm = patch(vnode0, vnode1)
  89. expect(map(inner, elm.children)).toEqual(['1', '2', '3'])
  90. elm = patch(vnode1, vnode2)
  91. expect(elm.children.length).toBe(0)
  92. })
  93. it('should remove elements from the beginning', () => {
  94. const vnode1 = new VNode('p', {}, [1, 2, 3, 4, 5].map(spanNum))
  95. const vnode2 = new VNode('p', {}, [3, 4, 5].map(spanNum))
  96. let elm = patch(vnode0, vnode1)
  97. expect(elm.children.length).toBe(5)
  98. elm = patch(vnode1, vnode2)
  99. expect(map(inner, elm.children)).toEqual(['3', '4', '5'])
  100. })
  101. it('should removes elements from end', () => {
  102. const vnode1 = new VNode('p', {}, [1, 2, 3, 4, 5].map(spanNum))
  103. const vnode2 = new VNode('p', {}, [1, 2, 3].map(spanNum))
  104. let elm = patch(vnode0, vnode1)
  105. expect(elm.children.length).toBe(5)
  106. elm = patch(vnode1, vnode2)
  107. expect(elm.children.length).toBe(3)
  108. expect(elm.children[0].innerHTML).toBe('1')
  109. expect(elm.children[1].innerHTML).toBe('2')
  110. expect(elm.children[2].innerHTML).toBe('3')
  111. })
  112. it('should remove elements from the middle', () => {
  113. const vnode1 = new VNode('p', {}, [1, 2, 3, 4, 5].map(spanNum))
  114. const vnode2 = new VNode('p', {}, [1, 2, 4, 5].map(spanNum))
  115. let elm = patch(vnode0, vnode1)
  116. expect(elm.children.length).toBe(5)
  117. elm = patch(vnode1, vnode2)
  118. expect(elm.children.length).toBe(4)
  119. expect(elm.children[0].innerHTML).toBe('1')
  120. expect(elm.children[1].innerHTML).toBe('2')
  121. expect(elm.children[2].innerHTML).toBe('4')
  122. expect(elm.children[3].innerHTML).toBe('5')
  123. })
  124. it('should moves element forward', () => {
  125. const vnode1 = new VNode('p', {}, [1, 2, 3, 4].map(spanNum))
  126. const vnode2 = new VNode('p', {}, [2, 3, 1, 4].map(spanNum))
  127. let elm = patch(vnode0, vnode1)
  128. expect(elm.children.length).toBe(4)
  129. elm = patch(vnode1, vnode2)
  130. expect(elm.children.length).toBe(4)
  131. expect(elm.children[0].innerHTML).toBe('2')
  132. expect(elm.children[1].innerHTML).toBe('3')
  133. expect(elm.children[2].innerHTML).toBe('1')
  134. expect(elm.children[3].innerHTML).toBe('4')
  135. })
  136. it('should move elements to end', () => {
  137. const vnode1 = new VNode('p', {}, [1, 2, 3].map(spanNum))
  138. const vnode2 = new VNode('p', {}, [2, 3, 1].map(spanNum))
  139. let elm = patch(vnode0, vnode1)
  140. expect(elm.children.length).toBe(3)
  141. elm = patch(vnode1, vnode2)
  142. expect(elm.children.length).toBe(3)
  143. expect(elm.children[0].innerHTML).toBe('2')
  144. expect(elm.children[1].innerHTML).toBe('3')
  145. expect(elm.children[2].innerHTML).toBe('1')
  146. })
  147. it('should move element backwards', () => {
  148. const vnode1 = new VNode('p', {}, [1, 2, 3, 4].map(spanNum))
  149. const vnode2 = new VNode('p', {}, [1, 4, 2, 3].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(elm.children[0].innerHTML).toBe('1')
  155. expect(elm.children[1].innerHTML).toBe('4')
  156. expect(elm.children[2].innerHTML).toBe('2')
  157. expect(elm.children[3].innerHTML).toBe('3')
  158. })
  159. it('should swap first and last', () => {
  160. const vnode1 = new VNode('p', {}, [1, 2, 3, 4].map(spanNum))
  161. const vnode2 = new VNode('p', {}, [4, 2, 3, 1].map(spanNum))
  162. let elm = patch(vnode0, vnode1)
  163. expect(elm.children.length).toBe(4)
  164. elm = patch(vnode1, vnode2)
  165. expect(elm.children.length).toBe(4)
  166. expect(elm.children[0].innerHTML).toBe('4')
  167. expect(elm.children[1].innerHTML).toBe('2')
  168. expect(elm.children[2].innerHTML).toBe('3')
  169. expect(elm.children[3].innerHTML).toBe('1')
  170. })
  171. it('should move to left and replace', () => {
  172. const vnode1 = new VNode('p', {}, [1, 2, 3, 4, 5].map(spanNum))
  173. const vnode2 = new VNode('p', {}, [4, 1, 2, 3, 6].map(spanNum))
  174. let elm = patch(vnode0, vnode1)
  175. expect(elm.children.length).toBe(5)
  176. elm = patch(vnode1, vnode2)
  177. expect(elm.children.length).toBe(5)
  178. expect(elm.children[0].innerHTML).toBe('4')
  179. expect(elm.children[1].innerHTML).toBe('1')
  180. expect(elm.children[2].innerHTML).toBe('2')
  181. expect(elm.children[3].innerHTML).toBe('3')
  182. expect(elm.children[4].innerHTML).toBe('6')
  183. })
  184. it('should move to left and leaves hold', () => {
  185. const vnode1 = new VNode('p', {}, [1, 4, 5].map(spanNum))
  186. const vnode2 = new VNode('p', {}, [4, 6].map(spanNum))
  187. let elm = patch(vnode0, vnode1)
  188. expect(elm.children.length).toBe(3)
  189. elm = patch(vnode1, vnode2)
  190. expect(map(inner, elm.children)).toEqual(['4', '6'])
  191. })
  192. it('should handle moved and set to undefined element ending at the end', () => {
  193. const vnode1 = new VNode('p', {}, [2, 4, 5].map(spanNum))
  194. const vnode2 = new VNode('p', {}, [4, 5, 3].map(spanNum))
  195. let elm = patch(vnode0, vnode1)
  196. expect(elm.children.length).toBe(3)
  197. elm = patch(vnode1, vnode2)
  198. expect(elm.children.length).toBe(3)
  199. expect(elm.children[0].innerHTML).toBe('4')
  200. expect(elm.children[1].innerHTML).toBe('5')
  201. expect(elm.children[2].innerHTML).toBe('3')
  202. })
  203. it('should move a key in non-keyed nodes with a size up', () => {
  204. const vnode1 = new VNode('p', {}, [1, 'a', 'b', 'c'].map(spanNum))
  205. const vnode2 = new VNode('p', {}, ['d', 'a', 'b', 'c', 1, 'e'].map(spanNum))
  206. let elm = patch(vnode0, vnode1)
  207. expect(elm.children.length).toBe(4)
  208. expect(elm.textContent, '1abc')
  209. elm = patch(vnode1, vnode2)
  210. expect(elm.children.length).toBe(6)
  211. expect(elm.textContent, 'dabc1e')
  212. })
  213. it('should reverse element', () => {
  214. const vnode1 = new VNode('p', {}, [1, 2, 3, 4, 5, 6, 7, 8].map(spanNum))
  215. const vnode2 = new VNode('p', {}, [8, 7, 6, 5, 4, 3, 2, 1].map(spanNum))
  216. let elm = patch(vnode0, vnode1)
  217. expect(elm.children.length).toBe(8)
  218. elm = patch(vnode1, vnode2)
  219. expect(map(inner, elm.children)).toEqual(['8', '7', '6', '5', '4', '3', '2', '1'])
  220. })
  221. it('something', () => {
  222. const vnode1 = new VNode('p', {}, [0, 1, 2, 3, 4, 5].map(spanNum))
  223. const vnode2 = new VNode('p', {}, [4, 3, 2, 1, 5, 0].map(spanNum))
  224. let elm = patch(vnode0, vnode1)
  225. expect(elm.children.length).toBe(6)
  226. elm = patch(vnode1, vnode2)
  227. expect(map(inner, elm.children)).toEqual(['4', '3', '2', '1', '5', '0'])
  228. })
  229. it('should handle random shuffle', () => {
  230. let n
  231. let i
  232. const arr = []
  233. const opacities = []
  234. const elms = 14
  235. const samples = 5
  236. function spanNumWithOpacity (n, o) {
  237. return new VNode('span', { key: n, style: { opacity: o }}, undefined, n.toString())
  238. }
  239. for (n = 0; n < elms; ++n) { arr[n] = n }
  240. for (n = 0; n < samples; ++n) {
  241. const vnode1 = new VNode('span', {}, arr.map(n => {
  242. return spanNumWithOpacity(n, '1')
  243. }))
  244. const shufArr = shuffle(arr.slice(0))
  245. let elm = patch(vnode0, vnode1)
  246. for (i = 0; i < elms; ++i) {
  247. expect(elm.children[i].innerHTML).toBe(i.toString())
  248. opacities[i] = Math.random().toFixed(5).toString()
  249. }
  250. const vnode2 = new VNode('span', {}, arr.map(n => {
  251. return spanNumWithOpacity(shufArr[n], opacities[n])
  252. }))
  253. elm = patch(vnode1, vnode2)
  254. for (i = 0; i < elms; ++i) {
  255. expect(elm.children[i].innerHTML).toBe(shufArr[i].toString())
  256. expect(opacities[i].indexOf(elm.children[i].style.opacity)).toBe(0)
  257. }
  258. }
  259. })
  260. it('should append elements with updating children without keys', () => {
  261. const vnode1 = new VNode('div', {}, [
  262. new VNode('span', {}, undefined, 'hello')
  263. ])
  264. const vnode2 = new VNode('div', {}, [
  265. new VNode('span', {}, undefined, 'hello'),
  266. new VNode('span', {}, undefined, 'world')
  267. ])
  268. let elm = patch(vnode0, vnode1)
  269. expect(map(inner, elm.children)).toEqual(['hello'])
  270. elm = patch(vnode1, vnode2)
  271. expect(map(inner, elm.children)).toEqual(['hello', 'world'])
  272. })
  273. it('should handle unmoved text nodes with updating children without keys', () => {
  274. const vnode1 = new VNode('div', {}, [
  275. createTextVNode('text'),
  276. new VNode('span', {}, undefined, 'hello')
  277. ])
  278. const vnode2 = new VNode('div', {}, [
  279. createTextVNode('text'),
  280. new VNode('span', {}, undefined, 'hello')
  281. ])
  282. let elm = patch(vnode0, vnode1)
  283. expect(elm.childNodes[0].textContent).toBe('text')
  284. elm = patch(vnode1, vnode2)
  285. expect(elm.childNodes[0].textContent).toBe('text')
  286. })
  287. it('should handle changing text children with updating children without keys', () => {
  288. const vnode1 = new VNode('div', {}, [
  289. createTextVNode('text'),
  290. new VNode('span', {}, undefined, 'hello')
  291. ])
  292. const vnode2 = new VNode('div', {}, [
  293. createTextVNode('text2'),
  294. new VNode('span', {}, undefined, 'hello')
  295. ])
  296. let elm = patch(vnode0, vnode1)
  297. expect(elm.childNodes[0].textContent).toBe('text')
  298. elm = patch(vnode1, vnode2)
  299. expect(elm.childNodes[0].textContent).toBe('text2')
  300. })
  301. it('should prepend element with updating children without keys', () => {
  302. const vnode1 = new VNode('div', {}, [
  303. new VNode('span', {}, undefined, 'world')
  304. ])
  305. const vnode2 = new VNode('div', {}, [
  306. new VNode('span', {}, undefined, 'hello'),
  307. new VNode('span', {}, undefined, 'world')
  308. ])
  309. let elm = patch(vnode0, vnode1)
  310. expect(map(inner, elm.children)).toEqual(['world'])
  311. elm = patch(vnode1, vnode2)
  312. expect(map(inner, elm.children)).toEqual(['hello', 'world'])
  313. })
  314. it('should prepend element of different tag type with updating children without keys', () => {
  315. const vnode1 = new VNode('div', {}, [
  316. new VNode('span', {}, undefined, 'world')
  317. ])
  318. const vnode2 = new VNode('div', {}, [
  319. new VNode('div', {}, undefined, 'hello'),
  320. new VNode('span', {}, undefined, 'world')
  321. ])
  322. let elm = patch(vnode0, vnode1)
  323. expect(map(inner, elm.children)).toEqual(['world'])
  324. elm = patch(vnode1, vnode2)
  325. expect(map(prop('tagName'), elm.children)).toEqual(['DIV', 'SPAN'])
  326. expect(map(inner, elm.children)).toEqual(['hello', 'world'])
  327. })
  328. it('should remove elements with updating children without keys', () => {
  329. const vnode1 = new VNode('div', {}, [
  330. new VNode('span', {}, undefined, 'one'),
  331. new VNode('span', {}, undefined, 'two'),
  332. new VNode('span', {}, undefined, 'three')
  333. ])
  334. const vnode2 = new VNode('div', {}, [
  335. new VNode('span', {}, undefined, 'one'),
  336. new VNode('span', {}, undefined, 'three')
  337. ])
  338. let elm = patch(vnode0, vnode1)
  339. expect(map(inner, elm.children)).toEqual(['one', 'two', 'three'])
  340. elm = patch(vnode1, vnode2)
  341. expect(map(inner, elm.children)).toEqual(['one', 'three'])
  342. })
  343. it('should remove a single text node with updating children without keys', () => {
  344. const vnode1 = new VNode('div', {}, undefined, 'one')
  345. const vnode2 = new VNode('div', {})
  346. let elm = patch(vnode0, vnode1)
  347. expect(elm.textContent).toBe('one')
  348. elm = patch(vnode1, vnode2)
  349. expect(elm.textContent).toBe('')
  350. })
  351. it('should remove a single text node when children are updated', () => {
  352. const vnode1 = new VNode('div', {}, undefined, 'one')
  353. const vnode2 = new VNode('div', {}, [
  354. new VNode('div', {}, undefined, 'two'),
  355. new VNode('span', {}, undefined, 'three')
  356. ])
  357. let elm = patch(vnode0, vnode1)
  358. expect(elm.textContent).toBe('one')
  359. elm = patch(vnode1, vnode2)
  360. expect(map(prop('textContent'), elm.childNodes)).toEqual(['two', 'three'])
  361. })
  362. it('should remove a text node among other elements', () => {
  363. const vnode1 = new VNode('div', {}, [
  364. createTextVNode('one'),
  365. new VNode('span', {}, undefined, 'two')
  366. ])
  367. const vnode2 = new VNode('div', {}, [
  368. new VNode('div', {}, undefined, 'three')
  369. ])
  370. let elm = patch(vnode0, vnode1)
  371. expect(map(prop('textContent'), elm.childNodes)).toEqual(['one', 'two'])
  372. elm = patch(vnode1, vnode2)
  373. expect(elm.childNodes.length).toBe(1)
  374. expect(elm.childNodes[0].tagName).toBe('DIV')
  375. expect(elm.childNodes[0].textContent).toBe('three')
  376. })
  377. it('should reorder elements', () => {
  378. const vnode1 = new VNode('div', {}, [
  379. new VNode('span', {}, undefined, 'one'),
  380. new VNode('div', {}, undefined, 'two'),
  381. new VNode('b', {}, undefined, 'three')
  382. ])
  383. const vnode2 = new VNode('div', {}, [
  384. new VNode('b', {}, undefined, 'three'),
  385. new VNode('span', {}, undefined, 'two'),
  386. new VNode('div', {}, undefined, 'one')
  387. ])
  388. let elm = patch(vnode0, vnode1)
  389. expect(map(inner, elm.children)).toEqual(['one', 'two', 'three'])
  390. elm = patch(vnode1, vnode2)
  391. expect(map(inner, elm.children)).toEqual(['three', 'two', 'one'])
  392. })
  393. })