scheduler.spec.ts 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529
  1. import {
  2. queueJob,
  3. nextTick,
  4. queuePostFlushCb,
  5. invalidateJob,
  6. queuePreFlushCb,
  7. flushPreFlushCbs,
  8. flushPostFlushCbs
  9. } from '../src/scheduler'
  10. describe('scheduler', () => {
  11. it('nextTick', async () => {
  12. const calls: string[] = []
  13. const dummyThen = Promise.resolve().then()
  14. const job1 = () => {
  15. calls.push('job1')
  16. }
  17. const job2 = () => {
  18. calls.push('job2')
  19. }
  20. nextTick(job1)
  21. job2()
  22. expect(calls.length).toBe(1)
  23. await dummyThen
  24. // job1 will be pushed in nextTick
  25. expect(calls.length).toBe(2)
  26. expect(calls).toMatchObject(['job2', 'job1'])
  27. })
  28. describe('queueJob', () => {
  29. it('basic usage', async () => {
  30. const calls: string[] = []
  31. const job1 = () => {
  32. calls.push('job1')
  33. }
  34. const job2 = () => {
  35. calls.push('job2')
  36. }
  37. queueJob(job1)
  38. queueJob(job2)
  39. expect(calls).toEqual([])
  40. await nextTick()
  41. expect(calls).toEqual(['job1', 'job2'])
  42. })
  43. it('should dedupe queued jobs', async () => {
  44. const calls: string[] = []
  45. const job1 = () => {
  46. calls.push('job1')
  47. }
  48. const job2 = () => {
  49. calls.push('job2')
  50. }
  51. queueJob(job1)
  52. queueJob(job2)
  53. queueJob(job1)
  54. queueJob(job2)
  55. expect(calls).toEqual([])
  56. await nextTick()
  57. expect(calls).toEqual(['job1', 'job2'])
  58. })
  59. it('queueJob while flushing', async () => {
  60. const calls: string[] = []
  61. const job1 = () => {
  62. calls.push('job1')
  63. // job2 will be executed after job1 at the same tick
  64. queueJob(job2)
  65. }
  66. const job2 = () => {
  67. calls.push('job2')
  68. }
  69. queueJob(job1)
  70. await nextTick()
  71. expect(calls).toEqual(['job1', 'job2'])
  72. })
  73. })
  74. describe('queuePreFlushCb', () => {
  75. it('basic usage', async () => {
  76. const calls: string[] = []
  77. const cb1 = () => {
  78. calls.push('cb1')
  79. }
  80. const cb2 = () => {
  81. calls.push('cb2')
  82. }
  83. queuePreFlushCb(cb1)
  84. queuePreFlushCb(cb2)
  85. expect(calls).toEqual([])
  86. await nextTick()
  87. expect(calls).toEqual(['cb1', 'cb2'])
  88. })
  89. it('should dedupe queued preFlushCb', async () => {
  90. const calls: string[] = []
  91. const cb1 = () => {
  92. calls.push('cb1')
  93. }
  94. const cb2 = () => {
  95. calls.push('cb2')
  96. }
  97. const cb3 = () => {
  98. calls.push('cb3')
  99. }
  100. queuePreFlushCb(cb1)
  101. queuePreFlushCb(cb2)
  102. queuePreFlushCb(cb1)
  103. queuePreFlushCb(cb2)
  104. queuePreFlushCb(cb3)
  105. expect(calls).toEqual([])
  106. await nextTick()
  107. expect(calls).toEqual(['cb1', 'cb2', 'cb3'])
  108. })
  109. it('chained queuePreFlushCb', async () => {
  110. const calls: string[] = []
  111. const cb1 = () => {
  112. calls.push('cb1')
  113. // cb2 will be executed after cb1 at the same tick
  114. queuePreFlushCb(cb2)
  115. }
  116. const cb2 = () => {
  117. calls.push('cb2')
  118. }
  119. queuePreFlushCb(cb1)
  120. await nextTick()
  121. expect(calls).toEqual(['cb1', 'cb2'])
  122. })
  123. })
  124. describe('queueJob w/ queuePreFlushCb', () => {
  125. it('queueJob inside preFlushCb', async () => {
  126. const calls: string[] = []
  127. const job1 = () => {
  128. calls.push('job1')
  129. }
  130. const cb1 = () => {
  131. // queueJob in postFlushCb
  132. calls.push('cb1')
  133. queueJob(job1)
  134. }
  135. queuePreFlushCb(cb1)
  136. await nextTick()
  137. expect(calls).toEqual(['cb1', 'job1'])
  138. })
  139. it('queueJob & preFlushCb inside preFlushCb', async () => {
  140. const calls: string[] = []
  141. const job1 = () => {
  142. calls.push('job1')
  143. }
  144. const cb1 = () => {
  145. calls.push('cb1')
  146. queueJob(job1)
  147. // cb2 should execute before the job
  148. queuePreFlushCb(cb2)
  149. }
  150. const cb2 = () => {
  151. calls.push('cb2')
  152. }
  153. queuePreFlushCb(cb1)
  154. await nextTick()
  155. expect(calls).toEqual(['cb1', 'cb2', 'job1'])
  156. })
  157. it('preFlushCb inside queueJob', async () => {
  158. const calls: string[] = []
  159. const job1 = () => {
  160. // the only case where a pre-flush cb can be queued inside a job is
  161. // when updating the props of a child component. This is handled
  162. // directly inside `updateComponentPreRender` to avoid non atomic
  163. // cb triggers (#1763)
  164. queuePreFlushCb(cb1)
  165. queuePreFlushCb(cb2)
  166. flushPreFlushCbs(undefined, job1)
  167. calls.push('job1')
  168. }
  169. const cb1 = () => {
  170. calls.push('cb1')
  171. // a cb triggers its parent job, which should be skipped
  172. queueJob(job1)
  173. }
  174. const cb2 = () => {
  175. calls.push('cb2')
  176. }
  177. queueJob(job1)
  178. await nextTick()
  179. expect(calls).toEqual(['cb1', 'cb2', 'job1'])
  180. })
  181. })
  182. describe('queuePostFlushCb', () => {
  183. it('basic usage', async () => {
  184. const calls: string[] = []
  185. const cb1 = () => {
  186. calls.push('cb1')
  187. }
  188. const cb2 = () => {
  189. calls.push('cb2')
  190. }
  191. const cb3 = () => {
  192. calls.push('cb3')
  193. }
  194. queuePostFlushCb([cb1, cb2])
  195. queuePostFlushCb(cb3)
  196. expect(calls).toEqual([])
  197. await nextTick()
  198. expect(calls).toEqual(['cb1', 'cb2', 'cb3'])
  199. })
  200. it('should dedupe queued postFlushCb', async () => {
  201. const calls: string[] = []
  202. const cb1 = () => {
  203. calls.push('cb1')
  204. }
  205. const cb2 = () => {
  206. calls.push('cb2')
  207. }
  208. const cb3 = () => {
  209. calls.push('cb3')
  210. }
  211. queuePostFlushCb([cb1, cb2])
  212. queuePostFlushCb(cb3)
  213. queuePostFlushCb([cb1, cb3])
  214. queuePostFlushCb(cb2)
  215. expect(calls).toEqual([])
  216. await nextTick()
  217. expect(calls).toEqual(['cb1', 'cb2', 'cb3'])
  218. })
  219. it('queuePostFlushCb while flushing', async () => {
  220. const calls: string[] = []
  221. const cb1 = () => {
  222. calls.push('cb1')
  223. // cb2 will be executed after cb1 at the same tick
  224. queuePostFlushCb(cb2)
  225. }
  226. const cb2 = () => {
  227. calls.push('cb2')
  228. }
  229. queuePostFlushCb(cb1)
  230. await nextTick()
  231. expect(calls).toEqual(['cb1', 'cb2'])
  232. })
  233. })
  234. describe('queueJob w/ queuePostFlushCb', () => {
  235. it('queueJob inside postFlushCb', async () => {
  236. const calls: string[] = []
  237. const job1 = () => {
  238. calls.push('job1')
  239. }
  240. const cb1 = () => {
  241. // queueJob in postFlushCb
  242. calls.push('cb1')
  243. queueJob(job1)
  244. }
  245. queuePostFlushCb(cb1)
  246. await nextTick()
  247. expect(calls).toEqual(['cb1', 'job1'])
  248. })
  249. it('queueJob & postFlushCb inside postFlushCb', async () => {
  250. const calls: string[] = []
  251. const job1 = () => {
  252. calls.push('job1')
  253. }
  254. const cb1 = () => {
  255. calls.push('cb1')
  256. queuePostFlushCb(cb2)
  257. // job1 will executed before cb2
  258. // Job has higher priority than postFlushCb
  259. queueJob(job1)
  260. }
  261. const cb2 = () => {
  262. calls.push('cb2')
  263. }
  264. queuePostFlushCb(cb1)
  265. await nextTick()
  266. expect(calls).toEqual(['cb1', 'job1', 'cb2'])
  267. })
  268. it('postFlushCb inside queueJob', async () => {
  269. const calls: string[] = []
  270. const job1 = () => {
  271. calls.push('job1')
  272. // postFlushCb in queueJob
  273. queuePostFlushCb(cb1)
  274. }
  275. const cb1 = () => {
  276. calls.push('cb1')
  277. }
  278. queueJob(job1)
  279. await nextTick()
  280. expect(calls).toEqual(['job1', 'cb1'])
  281. })
  282. it('queueJob & postFlushCb inside queueJob', async () => {
  283. const calls: string[] = []
  284. const job1 = () => {
  285. calls.push('job1')
  286. // cb1 will executed after job2
  287. // Job has higher priority than postFlushCb
  288. queuePostFlushCb(cb1)
  289. queueJob(job2)
  290. }
  291. const job2 = () => {
  292. calls.push('job2')
  293. }
  294. const cb1 = () => {
  295. calls.push('cb1')
  296. }
  297. queueJob(job1)
  298. await nextTick()
  299. expect(calls).toEqual(['job1', 'job2', 'cb1'])
  300. })
  301. it('nested queueJob w/ postFlushCb', async () => {
  302. const calls: string[] = []
  303. const job1 = () => {
  304. calls.push('job1')
  305. queuePostFlushCb(cb1)
  306. queueJob(job2)
  307. }
  308. const job2 = () => {
  309. calls.push('job2')
  310. queuePostFlushCb(cb2)
  311. }
  312. const cb1 = () => {
  313. calls.push('cb1')
  314. }
  315. const cb2 = () => {
  316. calls.push('cb2')
  317. }
  318. queueJob(job1)
  319. await nextTick()
  320. expect(calls).toEqual(['job1', 'job2', 'cb1', 'cb2'])
  321. })
  322. })
  323. test('invalidateJob', async () => {
  324. const calls: string[] = []
  325. const job1 = () => {
  326. calls.push('job1')
  327. invalidateJob(job2)
  328. job2()
  329. }
  330. const job2 = () => {
  331. calls.push('job2')
  332. }
  333. const job3 = () => {
  334. calls.push('job3')
  335. }
  336. const job4 = () => {
  337. calls.push('job4')
  338. }
  339. // queue all jobs
  340. queueJob(job1)
  341. queueJob(job2)
  342. queueJob(job3)
  343. queuePostFlushCb(job4)
  344. expect(calls).toEqual([])
  345. await nextTick()
  346. // job2 should be called only once
  347. expect(calls).toEqual(['job1', 'job2', 'job3', 'job4'])
  348. })
  349. test('sort job based on id', async () => {
  350. const calls: string[] = []
  351. const job1 = () => calls.push('job1')
  352. // job1 has no id
  353. const job2 = () => calls.push('job2')
  354. job2.id = 2
  355. const job3 = () => calls.push('job3')
  356. job3.id = 1
  357. queueJob(job1)
  358. queueJob(job2)
  359. queueJob(job3)
  360. await nextTick()
  361. expect(calls).toEqual(['job3', 'job2', 'job1'])
  362. })
  363. test('sort SchedulerCbs based on id', async () => {
  364. const calls: string[] = []
  365. const cb1 = () => calls.push('cb1')
  366. // cb1 has no id
  367. const cb2 = () => calls.push('cb2')
  368. cb2.id = 2
  369. const cb3 = () => calls.push('cb3')
  370. cb3.id = 1
  371. queuePostFlushCb(cb1)
  372. queuePostFlushCb(cb2)
  373. queuePostFlushCb(cb3)
  374. await nextTick()
  375. expect(calls).toEqual(['cb3', 'cb2', 'cb1'])
  376. })
  377. // #1595
  378. test('avoid duplicate postFlushCb invocation', async () => {
  379. const calls: string[] = []
  380. const cb1 = () => {
  381. calls.push('cb1')
  382. queuePostFlushCb(cb2)
  383. }
  384. const cb2 = () => {
  385. calls.push('cb2')
  386. }
  387. queuePostFlushCb(cb1)
  388. queuePostFlushCb(cb2)
  389. await nextTick()
  390. expect(calls).toEqual(['cb1', 'cb2'])
  391. })
  392. test('nextTick should capture scheduler flush errors', async () => {
  393. const err = new Error('test')
  394. queueJob(() => {
  395. throw err
  396. })
  397. try {
  398. await nextTick()
  399. } catch (e) {
  400. expect(e).toBe(err)
  401. }
  402. expect(
  403. `Unhandled error during execution of scheduler flush`
  404. ).toHaveBeenWarned()
  405. // this one should no longer error
  406. await nextTick()
  407. })
  408. test('should prevent self-triggering jobs by default', async () => {
  409. let count = 0
  410. const job = () => {
  411. if (count < 3) {
  412. count++
  413. queueJob(job)
  414. }
  415. }
  416. queueJob(job)
  417. await nextTick()
  418. // only runs once - a job cannot queue itself
  419. expect(count).toBe(1)
  420. })
  421. test('should allow explicitly marked jobs to trigger itself', async () => {
  422. // normal job
  423. let count = 0
  424. const job = () => {
  425. if (count < 3) {
  426. count++
  427. queueJob(job)
  428. }
  429. }
  430. job.allowRecurse = true
  431. queueJob(job)
  432. await nextTick()
  433. expect(count).toBe(3)
  434. // post cb
  435. const cb = () => {
  436. if (count < 5) {
  437. count++
  438. queuePostFlushCb(cb)
  439. }
  440. }
  441. cb.allowRecurse = true
  442. queuePostFlushCb(cb)
  443. await nextTick()
  444. expect(count).toBe(5)
  445. })
  446. test('should prevent duplicate queue', async () => {
  447. let count = 0
  448. const job = () => {
  449. count++
  450. }
  451. job.cb = true
  452. queueJob(job)
  453. queueJob(job)
  454. await nextTick()
  455. expect(count).toBe(1)
  456. })
  457. // #1947 flushPostFlushCbs should handle nested calls
  458. // e.g. app.mount inside app.mount
  459. test('flushPostFlushCbs', async () => {
  460. let count = 0
  461. const queueAndFlush = (hook: Function) => {
  462. queuePostFlushCb(hook)
  463. flushPostFlushCbs()
  464. }
  465. queueAndFlush(() => {
  466. queueAndFlush(() => {
  467. count++
  468. })
  469. })
  470. await nextTick()
  471. expect(count).toBe(1)
  472. })
  473. })