scheduler.spec.ts 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836
  1. import {
  2. type SchedulerJob,
  3. SchedulerJobFlags,
  4. flushPostFlushCbs,
  5. flushPreFlushCbs,
  6. nextTick,
  7. queueJob,
  8. queuePostFlushCb,
  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 insert jobs in ascending order of job's id when flushing", async () => {
  44. const calls: string[] = []
  45. const job1 = () => {
  46. calls.push('job1')
  47. queueJob(job2, 10)
  48. queueJob(job3, 1)
  49. }
  50. const job2 = () => {
  51. calls.push('job2')
  52. queueJob(job4)
  53. queueJob(job5)
  54. }
  55. const job3 = () => {
  56. calls.push('job3')
  57. }
  58. const job4 = () => {
  59. calls.push('job4')
  60. }
  61. const job5 = () => {
  62. calls.push('job5')
  63. }
  64. queueJob(job1)
  65. expect(calls).toEqual([])
  66. await nextTick()
  67. expect(calls).toEqual(['job1', 'job3', 'job2', 'job4', 'job5'])
  68. })
  69. it('should dedupe queued jobs', async () => {
  70. const calls: string[] = []
  71. const job1 = () => {
  72. calls.push('job1')
  73. }
  74. const job2 = () => {
  75. calls.push('job2')
  76. }
  77. queueJob(job1)
  78. queueJob(job2)
  79. queueJob(job1)
  80. queueJob(job2)
  81. expect(calls).toEqual([])
  82. await nextTick()
  83. expect(calls).toEqual(['job1', 'job2'])
  84. })
  85. it('queueJob while flushing', async () => {
  86. const calls: string[] = []
  87. const job1 = () => {
  88. calls.push('job1')
  89. // job2 will be executed after job1 at the same tick
  90. queueJob(job2)
  91. }
  92. const job2 = () => {
  93. calls.push('job2')
  94. }
  95. queueJob(job1)
  96. await nextTick()
  97. expect(calls).toEqual(['job1', 'job2'])
  98. })
  99. it('keeps the backing queue length bounded when splicing jobs after flush', async () => {
  100. const splicedQueueLengths: number[] = []
  101. const originalSplice = Array.prototype.splice
  102. const spliceSpy = vi
  103. .spyOn(Array.prototype, 'splice')
  104. .mockImplementation(function (
  105. this: unknown[],
  106. ...args: [start: number, deleteCount?: number, ...items: unknown[]]
  107. ) {
  108. const job = args[2]
  109. if (
  110. args[1] === 0 &&
  111. typeof job === 'function' &&
  112. (job as SchedulerJob).order !== undefined
  113. ) {
  114. splicedQueueLengths.push(this.length)
  115. }
  116. return originalSplice.apply(this, args as any)
  117. })
  118. try {
  119. for (let i = 0; i < 5; i++) {
  120. queueJob(() => {}, i)
  121. }
  122. await nextTick()
  123. for (let i = 0; i < 3; i++) {
  124. queueJob(() => {}, 2)
  125. queueJob(() => {}, 1)
  126. await nextTick()
  127. }
  128. } finally {
  129. spliceSpy.mockRestore()
  130. }
  131. expect(splicedQueueLengths).toEqual([1, 1, 1])
  132. })
  133. })
  134. describe('pre flush jobs', () => {
  135. it('queueJob inside preFlushCb', async () => {
  136. const calls: string[] = []
  137. const job1 = () => {
  138. calls.push('job1')
  139. }
  140. const cb1: SchedulerJob = () => {
  141. // queueJob in postFlushCb
  142. calls.push('cb1')
  143. queueJob(job1)
  144. }
  145. queueJob(cb1, undefined, true)
  146. await nextTick()
  147. expect(calls).toEqual(['cb1', 'job1'])
  148. })
  149. it('queueJob & preFlushCb inside preFlushCb', async () => {
  150. const calls: string[] = []
  151. const job1 = () => {
  152. calls.push('job1')
  153. }
  154. const cb1: SchedulerJob = () => {
  155. calls.push('cb1')
  156. queueJob(job1, 1)
  157. // cb2 should execute before the job
  158. queueJob(cb2, 1, true)
  159. queueJob(cb3, 1, true)
  160. }
  161. const cb2: SchedulerJob = () => {
  162. calls.push('cb2')
  163. }
  164. const cb3: SchedulerJob = () => {
  165. calls.push('cb3')
  166. }
  167. queueJob(cb1, undefined, true)
  168. await nextTick()
  169. expect(calls).toEqual(['cb1', 'cb2', 'cb3', 'job1'])
  170. })
  171. it('should insert jobs after pre jobs with the same id', async () => {
  172. const calls: string[] = []
  173. const job1: SchedulerJob = () => {
  174. calls.push('job1')
  175. }
  176. const job2: SchedulerJob = () => {
  177. calls.push('job2')
  178. queueJob(job5, 2)
  179. queueJob(job6, 2, true)
  180. }
  181. const job3: SchedulerJob = () => {
  182. calls.push('job3')
  183. }
  184. const job4: SchedulerJob = () => {
  185. calls.push('job4')
  186. }
  187. const job5: SchedulerJob = () => {
  188. calls.push('job5')
  189. }
  190. const job6: SchedulerJob = () => {
  191. calls.push('job6')
  192. }
  193. // We need several jobs to test this properly, otherwise
  194. // findInsertionIndex can yield the correct index by chance
  195. queueJob(job4, 3, true)
  196. queueJob(job2, 2, true)
  197. queueJob(job3, 2, true)
  198. queueJob(job1, 1, true)
  199. await nextTick()
  200. expect(calls).toEqual(['job1', 'job2', 'job3', 'job6', 'job5', 'job4'])
  201. })
  202. it('preFlushCb inside queueJob', async () => {
  203. const calls: string[] = []
  204. const job1 = () => {
  205. // the only case where a pre-flush cb can be queued inside a job is
  206. // when updating the props of a child component. This is handled
  207. // directly inside `updateComponentPreRender` to avoid non atomic
  208. // cb triggers (#1763)
  209. queueJob(cb1, undefined, true)
  210. queueJob(cb2, undefined, true)
  211. flushPreFlushCbs()
  212. calls.push('job1')
  213. }
  214. const cb1: SchedulerJob = () => {
  215. calls.push('cb1')
  216. // a cb triggers its parent job, which should be skipped
  217. queueJob(job1)
  218. }
  219. const cb2: SchedulerJob = () => {
  220. calls.push('cb2')
  221. }
  222. queueJob(job1)
  223. await nextTick()
  224. expect(calls).toEqual(['cb1', 'cb2', 'job1'])
  225. })
  226. it('should insert pre jobs without ids first during flushing', async () => {
  227. const calls: string[] = []
  228. const job1: SchedulerJob = () => {
  229. calls.push('job1')
  230. queueJob(job3, undefined, true)
  231. queueJob(job4, undefined, true)
  232. }
  233. // job1 has no id
  234. const job2: SchedulerJob = () => {
  235. calls.push('job2')
  236. }
  237. const job3: SchedulerJob = () => {
  238. calls.push('job3')
  239. }
  240. // job3 has no id
  241. const job4: SchedulerJob = () => {
  242. calls.push('job4')
  243. }
  244. // job4 has no id
  245. queueJob(job1, undefined, true)
  246. queueJob(job2, 1, true)
  247. await nextTick()
  248. expect(calls).toEqual(['job1', 'job3', 'job4', 'job2'])
  249. })
  250. // #3806
  251. it('queue preFlushCb inside postFlushCb', async () => {
  252. const spy = vi.fn()
  253. const cb: SchedulerJob = () => spy()
  254. queuePostFlushCb(() => {
  255. queueJob(cb, undefined, true)
  256. })
  257. await nextTick()
  258. expect(spy).toHaveBeenCalled()
  259. })
  260. })
  261. describe('queuePostFlushCb', () => {
  262. it('basic usage', async () => {
  263. const calls: string[] = []
  264. const cb1 = () => {
  265. calls.push('cb1')
  266. }
  267. const cb2 = () => {
  268. calls.push('cb2')
  269. }
  270. const cb3 = () => {
  271. calls.push('cb3')
  272. }
  273. queuePostFlushCb([cb1, cb2])
  274. queuePostFlushCb(cb3)
  275. expect(calls).toEqual([])
  276. await nextTick()
  277. expect(calls).toEqual(['cb1', 'cb2', 'cb3'])
  278. })
  279. it('should dedupe queued postFlushCb', async () => {
  280. const calls: string[] = []
  281. const cb1 = () => {
  282. calls.push('cb1')
  283. }
  284. const cb2 = () => {
  285. calls.push('cb2')
  286. }
  287. const cb3 = () => {
  288. calls.push('cb3')
  289. }
  290. queuePostFlushCb([cb1, cb2])
  291. queuePostFlushCb(cb3)
  292. queuePostFlushCb([cb1, cb3])
  293. queuePostFlushCb(cb2)
  294. expect(calls).toEqual([])
  295. await nextTick()
  296. expect(calls).toEqual(['cb1', 'cb2', 'cb3'])
  297. })
  298. it('queuePostFlushCb while flushing', async () => {
  299. const calls: string[] = []
  300. const cb1 = () => {
  301. calls.push('cb1')
  302. // cb2 will be executed after cb1 at the same tick
  303. queuePostFlushCb(cb2)
  304. }
  305. const cb2 = () => {
  306. calls.push('cb2')
  307. }
  308. queuePostFlushCb(cb1)
  309. await nextTick()
  310. expect(calls).toEqual(['cb1', 'cb2'])
  311. })
  312. })
  313. describe('queueJob w/ queuePostFlushCb', () => {
  314. it('queueJob inside postFlushCb', async () => {
  315. const calls: string[] = []
  316. const job1 = () => {
  317. calls.push('job1')
  318. }
  319. const cb1 = () => {
  320. // queueJob in postFlushCb
  321. calls.push('cb1')
  322. queueJob(job1)
  323. }
  324. queuePostFlushCb(cb1)
  325. await nextTick()
  326. expect(calls).toEqual(['cb1', 'job1'])
  327. })
  328. it('queueJob & postFlushCb inside postFlushCb', async () => {
  329. const calls: string[] = []
  330. const job1 = () => {
  331. calls.push('job1')
  332. }
  333. const cb1 = () => {
  334. calls.push('cb1')
  335. queuePostFlushCb(cb2)
  336. // job1 will executed before cb2
  337. // Job has higher priority than postFlushCb
  338. queueJob(job1)
  339. }
  340. const cb2 = () => {
  341. calls.push('cb2')
  342. }
  343. queuePostFlushCb(cb1)
  344. await nextTick()
  345. expect(calls).toEqual(['cb1', 'job1', 'cb2'])
  346. })
  347. it('postFlushCb inside queueJob', async () => {
  348. const calls: string[] = []
  349. const job1 = () => {
  350. calls.push('job1')
  351. // postFlushCb in queueJob
  352. queuePostFlushCb(cb1)
  353. }
  354. const cb1 = () => {
  355. calls.push('cb1')
  356. }
  357. queueJob(job1)
  358. await nextTick()
  359. expect(calls).toEqual(['job1', 'cb1'])
  360. })
  361. it('queueJob & postFlushCb inside queueJob', async () => {
  362. const calls: string[] = []
  363. const job1 = () => {
  364. calls.push('job1')
  365. // cb1 will executed after job2
  366. // Job has higher priority than postFlushCb
  367. queuePostFlushCb(cb1)
  368. queueJob(job2)
  369. }
  370. const job2 = () => {
  371. calls.push('job2')
  372. }
  373. const cb1 = () => {
  374. calls.push('cb1')
  375. }
  376. queueJob(job1)
  377. await nextTick()
  378. expect(calls).toEqual(['job1', 'job2', 'cb1'])
  379. })
  380. it('nested queueJob w/ postFlushCb', async () => {
  381. const calls: string[] = []
  382. const job1 = () => {
  383. calls.push('job1')
  384. queuePostFlushCb(cb1)
  385. queueJob(job2)
  386. }
  387. const job2 = () => {
  388. calls.push('job2')
  389. queuePostFlushCb(cb2)
  390. }
  391. const cb1 = () => {
  392. calls.push('cb1')
  393. }
  394. const cb2 = () => {
  395. calls.push('cb2')
  396. }
  397. queueJob(job1)
  398. await nextTick()
  399. expect(calls).toEqual(['job1', 'job2', 'cb1', 'cb2'])
  400. })
  401. test('jobs added during post flush are ordered correctly', async () => {
  402. const calls: string[] = []
  403. const job1: SchedulerJob = () => {
  404. calls.push('job1')
  405. }
  406. const job2: SchedulerJob = () => {
  407. calls.push('job2')
  408. }
  409. queuePostFlushCb(() => {
  410. queueJob(job2, 2)
  411. queueJob(job1, 1)
  412. })
  413. await nextTick()
  414. expect(calls).toEqual(['job1', 'job2'])
  415. })
  416. })
  417. test('sort job based on id', async () => {
  418. const calls: string[] = []
  419. const job1 = () => calls.push('job1')
  420. // job1 has no id
  421. const job2 = () => calls.push('job2')
  422. const job3 = () => calls.push('job3')
  423. const job4: SchedulerJob = () => calls.push('job4')
  424. const job5: SchedulerJob = () => calls.push('job5')
  425. // job5 has no id
  426. queueJob(job1)
  427. queueJob(job2, 2)
  428. queueJob(job3, 1)
  429. queueJob(job4, 2, true)
  430. queueJob(job5, undefined, true)
  431. await nextTick()
  432. expect(calls).toEqual(['job5', 'job3', 'job4', 'job2', 'job1'])
  433. })
  434. test('sort SchedulerCbs based on id', async () => {
  435. const calls: string[] = []
  436. const cb1 = () => calls.push('cb1')
  437. // cb1 has no id
  438. const cb2 = () => calls.push('cb2')
  439. const cb3 = () => calls.push('cb3')
  440. queuePostFlushCb(cb1)
  441. queuePostFlushCb(cb2, 2)
  442. queuePostFlushCb(cb3, 1)
  443. await nextTick()
  444. expect(calls).toEqual(['cb3', 'cb2', 'cb1'])
  445. })
  446. // #1595
  447. test('avoid duplicate postFlushCb invocation', async () => {
  448. const calls: string[] = []
  449. const cb1 = () => {
  450. calls.push('cb1')
  451. queuePostFlushCb(cb2)
  452. }
  453. const cb2 = () => {
  454. calls.push('cb2')
  455. }
  456. queuePostFlushCb(cb1)
  457. queuePostFlushCb(cb2)
  458. await nextTick()
  459. expect(calls).toEqual(['cb1', 'cb2'])
  460. })
  461. test('nextTick should capture scheduler flush errors', async () => {
  462. const err = new Error('test')
  463. queueJob(() => {
  464. throw err
  465. })
  466. try {
  467. await nextTick()
  468. } catch (e: any) {
  469. expect(e).toBe(err)
  470. }
  471. expect(
  472. `Unhandled error during execution of scheduler flush`,
  473. ).toHaveBeenWarned()
  474. // this one should no longer error
  475. await nextTick()
  476. })
  477. test('jobs can be re-queued after an error', async () => {
  478. const err = new Error('test')
  479. let shouldThrow = true
  480. const job1: SchedulerJob = vi.fn(() => {
  481. if (shouldThrow) {
  482. shouldThrow = false
  483. throw err
  484. }
  485. })
  486. const job2: SchedulerJob = vi.fn()
  487. queueJob(job1, 1)
  488. queueJob(job2, 2)
  489. try {
  490. await nextTick()
  491. } catch (e: any) {
  492. expect(e).toBe(err)
  493. }
  494. expect(
  495. `Unhandled error during execution of scheduler flush`,
  496. ).toHaveBeenWarned()
  497. expect(job1).toHaveBeenCalledTimes(1)
  498. expect(job2).toHaveBeenCalledTimes(0)
  499. queueJob(job1, 1)
  500. queueJob(job2, 2)
  501. await nextTick()
  502. expect(job1).toHaveBeenCalledTimes(2)
  503. expect(job2).toHaveBeenCalledTimes(1)
  504. })
  505. test('should prevent self-triggering jobs by default', async () => {
  506. let count = 0
  507. const job = () => {
  508. if (count < 3) {
  509. count++
  510. queueJob(job)
  511. }
  512. }
  513. queueJob(job)
  514. await nextTick()
  515. // only runs once - a job cannot queue itself
  516. expect(count).toBe(1)
  517. })
  518. test('should allow explicitly marked jobs to trigger itself', async () => {
  519. // normal job
  520. let count = 0
  521. const job: SchedulerJob = () => {
  522. if (count < 3) {
  523. count++
  524. queueJob(job)
  525. }
  526. }
  527. job.flags! |= SchedulerJobFlags.ALLOW_RECURSE
  528. queueJob(job)
  529. await nextTick()
  530. expect(count).toBe(3)
  531. // post cb
  532. const cb: SchedulerJob = () => {
  533. if (count < 5) {
  534. count++
  535. queuePostFlushCb(cb)
  536. }
  537. }
  538. cb.flags! |= SchedulerJobFlags.ALLOW_RECURSE
  539. queuePostFlushCb(cb)
  540. await nextTick()
  541. expect(count).toBe(5)
  542. })
  543. test('recursive jobs can only be queued once non-recursively', async () => {
  544. const job: SchedulerJob = vi.fn()
  545. job.flags = SchedulerJobFlags.ALLOW_RECURSE
  546. queueJob(job, 1)
  547. queueJob(job, 1)
  548. await nextTick()
  549. expect(job).toHaveBeenCalledTimes(1)
  550. })
  551. test('recursive jobs can only be queued once recursively', async () => {
  552. let recurse = true
  553. const job: SchedulerJob = vi.fn(() => {
  554. if (recurse) {
  555. queueJob(job, 1)
  556. queueJob(job, 1)
  557. recurse = false
  558. }
  559. })
  560. job.flags = SchedulerJobFlags.ALLOW_RECURSE
  561. queueJob(job, 1)
  562. await nextTick()
  563. expect(job).toHaveBeenCalledTimes(2)
  564. })
  565. test(`recursive jobs can't be re-queued by other jobs`, async () => {
  566. let recurse = true
  567. const job1: SchedulerJob = () => {
  568. if (recurse) {
  569. // job2 is already queued, so this shouldn't do anything
  570. queueJob(job2, 2)
  571. recurse = false
  572. }
  573. }
  574. const job2: SchedulerJob = vi.fn(() => {
  575. if (recurse) {
  576. queueJob(job1, 1)
  577. queueJob(job2, 2)
  578. }
  579. })
  580. job2.flags = SchedulerJobFlags.ALLOW_RECURSE
  581. queueJob(job2, 2)
  582. await nextTick()
  583. expect(job2).toHaveBeenCalledTimes(2)
  584. })
  585. test('jobs are de-duplicated correctly when calling flushPreFlushCbs', async () => {
  586. let recurse = true
  587. const job1: SchedulerJob = vi.fn(() => {
  588. queueJob(job3, 3, true)
  589. queueJob(job3, 3, true)
  590. flushPreFlushCbs()
  591. })
  592. const job2: SchedulerJob = vi.fn(() => {
  593. if (recurse) {
  594. // job2 does not allow recurse, so this shouldn't do anything
  595. queueJob(job2, 2, true)
  596. // job3 is already queued, so this shouldn't do anything
  597. queueJob(job3, 3, true)
  598. recurse = false
  599. }
  600. })
  601. const job3: SchedulerJob = vi.fn(() => {
  602. if (recurse) {
  603. queueJob(job2, 2, true)
  604. queueJob(job3, 3, true)
  605. // The jobs are already queued, so these should have no effect
  606. queueJob(job2, 2, true)
  607. queueJob(job3, 3, true)
  608. }
  609. })
  610. job3.flags = SchedulerJobFlags.ALLOW_RECURSE
  611. queueJob(job1, 1, true)
  612. await nextTick()
  613. expect(job1).toHaveBeenCalledTimes(1)
  614. expect(job2).toHaveBeenCalledTimes(1)
  615. expect(job3).toHaveBeenCalledTimes(2)
  616. })
  617. // #1947 flushPostFlushCbs should handle nested calls
  618. // e.g. app.mount inside app.mount
  619. test('flushPostFlushCbs', async () => {
  620. let count = 0
  621. const queueAndFlush = (hook: Function) => {
  622. queuePostFlushCb(hook)
  623. flushPostFlushCbs()
  624. }
  625. queueAndFlush(() => {
  626. queueAndFlush(() => {
  627. count++
  628. })
  629. })
  630. await nextTick()
  631. expect(count).toBe(1)
  632. })
  633. // #910
  634. test('should not run stopped reactive effects', async () => {
  635. const spy = vi.fn()
  636. // simulate parent component that toggles child
  637. const job1 = () => {
  638. // @ts-expect-error
  639. job2.flags! |= SchedulerJobFlags.DISPOSED
  640. }
  641. // simulate child that's triggered by the same reactive change that
  642. // triggers its toggle
  643. const job2 = () => spy()
  644. expect(spy).toHaveBeenCalledTimes(0)
  645. queueJob(job1)
  646. queueJob(job2)
  647. await nextTick()
  648. // should not be called
  649. expect(spy).toHaveBeenCalledTimes(0)
  650. })
  651. it('flushPreFlushCbs inside a pre job', async () => {
  652. const spy = vi.fn()
  653. const job: SchedulerJob = () => {
  654. spy()
  655. flushPreFlushCbs()
  656. }
  657. queueJob(job, undefined, true)
  658. await nextTick()
  659. expect(spy).toHaveBeenCalledTimes(1)
  660. })
  661. test('flushPreFlushCbs inside a post job', async () => {
  662. const calls: string[] = []
  663. const callsAfterFlush: string[] = []
  664. const job1: SchedulerJob = () => {
  665. calls.push('job1')
  666. }
  667. const job2: SchedulerJob = () => {
  668. calls.push('job2')
  669. }
  670. queuePostFlushCb(() => {
  671. queueJob(job2, 2, true)
  672. queueJob(job1, 1, true)
  673. // e.g. nested app.mount() call
  674. flushPreFlushCbs()
  675. callsAfterFlush.push(...calls)
  676. })
  677. await nextTick()
  678. expect(callsAfterFlush).toEqual(['job1', 'job2'])
  679. expect(calls).toEqual(['job1', 'job2'])
  680. })
  681. it('nextTick should return promise', async () => {
  682. const fn = vi.fn(() => {
  683. return 1
  684. })
  685. const p = nextTick(fn)
  686. expect(p).toBeInstanceOf(Promise)
  687. expect(await p).toBe(1)
  688. expect(fn).toHaveBeenCalledTimes(1)
  689. })
  690. // #10003
  691. test('nested flushPostFlushCbs', async () => {
  692. const calls: string[] = []
  693. const cb1 = () => calls.push('cb1')
  694. // cb1 has no id
  695. const cb2 = () => calls.push('cb2')
  696. const queueAndFlush = (hook: Function) => {
  697. queuePostFlushCb(hook)
  698. flushPostFlushCbs()
  699. }
  700. queueAndFlush(() => {
  701. queuePostFlushCb(cb1)
  702. queuePostFlushCb(cb2, -1)
  703. flushPostFlushCbs()
  704. })
  705. await nextTick()
  706. expect(calls).toEqual(['cb2', 'cb1'])
  707. })
  708. test('error in postFlush cb should not cause nextTick to stuck in rejected state forever', async () => {
  709. queuePostFlushCb(() => {
  710. throw 'err'
  711. })
  712. await expect(nextTick).rejects.toThrow('err')
  713. await expect(nextTick()).resolves.toBeUndefined()
  714. })
  715. })