scheduler.spec.ts 20 KB

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