hooks.spec.ts 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378
  1. import { patch } from 'web/runtime/patch'
  2. import { createPatchFunction } from 'core/vdom/patch'
  3. import baseModules from 'core/vdom/modules/index'
  4. import * as nodeOps from 'web/runtime/node-ops'
  5. import platformModules from 'web/runtime/modules/index'
  6. import VNode from 'core/vdom/vnode'
  7. const modules = baseModules.concat(platformModules) as any[]
  8. describe('vdom patch: hooks', () => {
  9. let vnode0
  10. beforeEach(() => {
  11. vnode0 = new VNode('p', { attrs: { id: '1' } }, [
  12. createTextVNode('hello world')
  13. ])
  14. patch(null, vnode0)
  15. })
  16. it('should call `insert` listener after both parents, siblings and children have been inserted', () => {
  17. const result: any[] = []
  18. function insert(vnode) {
  19. expect(vnode.elm.children.length).toBe(2)
  20. expect(vnode.elm.parentNode.children.length).toBe(3)
  21. result.push(vnode)
  22. }
  23. const vnode1 = new VNode('div', {}, [
  24. new VNode('span', {}, undefined, 'first sibling'),
  25. new VNode('div', { hook: { insert } }, [
  26. new VNode('span', {}, undefined, 'child 1'),
  27. new VNode('span', {}, undefined, 'child 2')
  28. ]),
  29. new VNode('span', {}, undefined, 'can touch me')
  30. ])
  31. patch(vnode0, vnode1)
  32. expect(result.length).toBe(1)
  33. })
  34. it('should call `prepatch` listener', () => {
  35. const result: any[] = []
  36. function prepatch(oldVnode, newVnode) {
  37. expect(oldVnode).toEqual(vnode1.children[1])
  38. expect(newVnode).toEqual(vnode2.children[1])
  39. result.push(newVnode)
  40. }
  41. const vnode1 = new VNode('div', {}, [
  42. new VNode('span', {}, undefined, 'first sibling'),
  43. new VNode('div', { hook: { prepatch } }, [
  44. new VNode('span', {}, undefined, 'child 1'),
  45. new VNode('span', {}, undefined, 'child 2')
  46. ])
  47. ])
  48. const vnode2 = new VNode('div', {}, [
  49. new VNode('span', {}, undefined, 'first sibling'),
  50. new VNode('div', { hook: { prepatch } }, [
  51. new VNode('span', {}, undefined, 'child 1'),
  52. new VNode('span', {}, undefined, 'child 2')
  53. ])
  54. ])
  55. patch(vnode0, vnode1)
  56. patch(vnode1, vnode2)
  57. expect(result.length).toBe(1)
  58. })
  59. it('should call `postpatch` after `prepatch` listener', () => {
  60. const pre: any[] = []
  61. const post: any[] = []
  62. function prepatch(oldVnode, newVnode) {
  63. pre.push(pre)
  64. }
  65. function postpatch(oldVnode, newVnode) {
  66. expect(pre.length).toBe(post.length + 1)
  67. post.push(post)
  68. }
  69. const vnode1 = new VNode('div', {}, [
  70. new VNode('span', {}, undefined, 'first sibling'),
  71. new VNode('div', { hook: { prepatch, postpatch } }, [
  72. new VNode('span', {}, undefined, 'child 1'),
  73. new VNode('span', {}, undefined, 'child 2')
  74. ])
  75. ])
  76. const vnode2 = new VNode('div', {}, [
  77. new VNode('span', {}, undefined, 'first sibling'),
  78. new VNode('div', { hook: { prepatch, postpatch } }, [
  79. new VNode('span', {}, undefined, 'child 1'),
  80. new VNode('span', {}, undefined, 'child 2')
  81. ])
  82. ])
  83. patch(vnode0, vnode1)
  84. patch(vnode1, vnode2)
  85. expect(pre.length).toBe(1)
  86. expect(post.length).toBe(1)
  87. })
  88. it('should call `update` listener', () => {
  89. const result1: any[] = []
  90. const result2: any[] = []
  91. function cb(result, oldVnode, newVnode) {
  92. if (result.length > 1) {
  93. expect(result[result.length - 1]).toEqual(oldVnode)
  94. }
  95. result.push(newVnode)
  96. }
  97. const vnode1 = new VNode('div', {}, [
  98. new VNode('span', {}, undefined, 'first sibling'),
  99. new VNode('div', { hook: { update: cb.bind(null, result1) } }, [
  100. new VNode('span', {}, undefined, 'child 1'),
  101. new VNode(
  102. 'span',
  103. { hook: { update: cb.bind(null, result2) } },
  104. undefined,
  105. 'child 2'
  106. )
  107. ])
  108. ])
  109. const vnode2 = new VNode('div', {}, [
  110. new VNode('span', {}, undefined, 'first sibling'),
  111. new VNode('div', { hook: { update: cb.bind(null, result1) } }, [
  112. new VNode('span', {}, undefined, 'child 1'),
  113. new VNode(
  114. 'span',
  115. { hook: { update: cb.bind(null, result2) } },
  116. undefined,
  117. 'child 2'
  118. )
  119. ])
  120. ])
  121. patch(vnode0, vnode1)
  122. patch(vnode1, vnode2)
  123. expect(result1.length).toBe(1)
  124. expect(result2.length).toBe(1)
  125. })
  126. it('should call `remove` listener', () => {
  127. const result: any[] = []
  128. function remove(vnode, rm) {
  129. const parent = vnode.elm.parentNode
  130. expect(vnode.elm.children.length).toBe(2)
  131. expect(vnode.elm.children.length).toBe(2)
  132. result.push(vnode)
  133. rm()
  134. expect(parent.children.length).toBe(1)
  135. }
  136. const vnode1 = new VNode('div', {}, [
  137. new VNode('span', {}, undefined, 'first sibling'),
  138. new VNode('div', { hook: { remove } }, [
  139. new VNode('span', {}, undefined, 'child 1'),
  140. new VNode('span', {}, undefined, 'child 2')
  141. ])
  142. ])
  143. const vnode2 = new VNode('div', {}, [
  144. new VNode('span', {}, undefined, 'first sibling')
  145. ])
  146. patch(vnode0, vnode1)
  147. patch(vnode1, vnode2)
  148. expect(result.length).toBe(1)
  149. })
  150. it('should call `init` and `prepatch` listeners on root', () => {
  151. let count = 0
  152. function init(vnode) {
  153. count++
  154. }
  155. function prepatch(oldVnode, newVnode) {
  156. count++
  157. }
  158. const vnode1 = new VNode('div', { hook: { init, prepatch } })
  159. patch(vnode0, vnode1)
  160. expect(count).toBe(1)
  161. const vnode2 = new VNode('span', { hook: { init, prepatch } })
  162. patch(vnode1, vnode2)
  163. expect(count).toBe(2)
  164. })
  165. it('should remove element when all remove listeners are done', () => {
  166. let rm1, rm2, rm3
  167. const patch1 = createPatchFunction({
  168. nodeOps,
  169. // @ts-ignore - TODO dtw
  170. modules: modules.concat([
  171. {
  172. remove(_, rm) {
  173. rm1 = rm
  174. }
  175. },
  176. {
  177. remove(_, rm) {
  178. rm2 = rm
  179. }
  180. }
  181. ])
  182. })
  183. const vnode1 = new VNode('div', {}, [
  184. new VNode('a', {
  185. hook: {
  186. remove(_, rm) {
  187. rm3 = rm
  188. }
  189. }
  190. })
  191. ])
  192. const vnode2 = new VNode('div', {}, [])
  193. let elm = patch1(vnode0, vnode1)
  194. expect(elm.children.length).toBe(1)
  195. elm = patch1(vnode1, vnode2)
  196. expect(elm.children.length).toBe(1)
  197. rm1()
  198. expect(elm.children.length).toBe(1)
  199. rm3()
  200. expect(elm.children.length).toBe(1)
  201. rm2()
  202. expect(elm.children.length).toBe(0)
  203. })
  204. it('should invoke the remove hook on replaced root', () => {
  205. const result: any[] = []
  206. const parent = nodeOps.createElement('div')
  207. vnode0 = nodeOps.createElement('div')
  208. parent.appendChild(vnode0)
  209. function remove(vnode, rm) {
  210. result.push(vnode)
  211. rm()
  212. }
  213. const vnode1 = new VNode('div', { hook: { remove } }, [
  214. new VNode('b', {}, undefined, 'child 1'),
  215. new VNode('i', {}, undefined, 'child 2')
  216. ])
  217. const vnode2 = new VNode('span', {}, [
  218. new VNode('b', {}, undefined, 'child 1'),
  219. new VNode('i', {}, undefined, 'child 2')
  220. ])
  221. patch(vnode0, vnode1)
  222. patch(vnode1, vnode2)
  223. expect(result.length).toBe(1)
  224. })
  225. it('should invoke global `destroy` hook for all removed children', () => {
  226. const result: any[] = []
  227. function destroy(vnode) {
  228. result.push(vnode)
  229. }
  230. const vnode1 = new VNode('div', {}, [
  231. new VNode('span', {}, undefined, 'first sibling'),
  232. new VNode('div', {}, [
  233. new VNode('span', { hook: { destroy } }, undefined, 'child 1'),
  234. new VNode('span', {}, undefined, 'child 2')
  235. ])
  236. ])
  237. const vnode2 = new VNode('div')
  238. patch(vnode0, vnode1)
  239. patch(vnode1, vnode2)
  240. expect(result.length).toBe(1)
  241. })
  242. it('should handle text vnodes with `undefined` `data` property', () => {
  243. const vnode1 = new VNode('div', {}, [createTextVNode(' ')])
  244. const vnode2 = new VNode('div', {}, [])
  245. patch(vnode0, vnode1)
  246. patch(vnode1, vnode2)
  247. })
  248. it('should invoke `destroy` module hook for all removed children', () => {
  249. let created = 0
  250. let destroyed = 0
  251. const patch1 = createPatchFunction({
  252. nodeOps,
  253. modules: modules.concat([
  254. {
  255. create() {
  256. created++
  257. }
  258. },
  259. {
  260. destroy() {
  261. destroyed++
  262. }
  263. }
  264. ])
  265. })
  266. const vnode1 = new VNode('div', {}, [
  267. new VNode('span', {}, undefined, 'first sibling'),
  268. new VNode('div', {}, [
  269. new VNode('span', {}, undefined, 'child 1'),
  270. new VNode('span', {}, undefined, 'child 2')
  271. ])
  272. ])
  273. const vnode2 = new VNode('div', {})
  274. patch1(vnode0, vnode1)
  275. expect(destroyed).toBe(1) // should invoke for replaced root nodes too
  276. patch1(vnode1, vnode2)
  277. expect(created).toBe(5)
  278. expect(destroyed).toBe(5)
  279. })
  280. it('should not invoke `create` and `remove` module hook for text nodes', () => {
  281. let created = 0
  282. let removed = 0
  283. const patch1 = createPatchFunction({
  284. nodeOps,
  285. modules: modules.concat([
  286. {
  287. create() {
  288. created++
  289. }
  290. },
  291. {
  292. remove() {
  293. removed++
  294. }
  295. }
  296. ])
  297. })
  298. const vnode1 = new VNode('div', {}, [
  299. new VNode('span', {}, undefined, 'first child'),
  300. createTextVNode(''),
  301. new VNode('span', {}, undefined, 'third child')
  302. ])
  303. const vnode2 = new VNode('div', {})
  304. patch1(vnode0, vnode1)
  305. patch1(vnode1, vnode2)
  306. expect(created).toBe(3)
  307. expect(removed).toBe(2)
  308. })
  309. it('should not invoke `destroy` module hook for text nodes', () => {
  310. let created = 0
  311. let destroyed = 0
  312. const patch1 = createPatchFunction({
  313. nodeOps,
  314. modules: modules.concat([
  315. {
  316. create() {
  317. created++
  318. }
  319. },
  320. {
  321. destroy() {
  322. destroyed++
  323. }
  324. }
  325. ])
  326. })
  327. const vnode1 = new VNode('div', {}, [
  328. new VNode('span', {}, undefined, 'first sibling'),
  329. new VNode('div', {}, [
  330. new VNode('span', {}, undefined, 'child 1'),
  331. new VNode('span', {}, [
  332. createTextVNode('text1'),
  333. createTextVNode('text2')
  334. ])
  335. ])
  336. ])
  337. const vnode2 = new VNode('div', {})
  338. patch1(vnode0, vnode1)
  339. expect(destroyed).toBe(1) // should invoke for replaced root nodes too
  340. patch1(vnode1, vnode2)
  341. expect(created).toBe(5)
  342. expect(destroyed).toBe(5)
  343. })
  344. it('should call `create` listener before inserted into parent but after children', () => {
  345. const result: any[] = []
  346. function create(empty, vnode) {
  347. expect(vnode.elm.children.length).toBe(2)
  348. expect(vnode.elm.parentNode).toBe(null)
  349. result.push(vnode)
  350. }
  351. const vnode1 = new VNode('div', {}, [
  352. new VNode('span', {}, undefined, 'first sibling'),
  353. new VNode('div', { hook: { create } }, [
  354. new VNode('span', {}, undefined, 'child 1'),
  355. new VNode('span', {}, undefined, 'child 2')
  356. ]),
  357. new VNode('span', {}, undefined, "can't touch me")
  358. ])
  359. patch(vnode0, vnode1)
  360. expect(result.length).toBe(1)
  361. })
  362. })