| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847 |
- import {
- type SchedulerJob,
- SchedulerJobFlags,
- flushPostFlushCbs,
- flushPreFlushCbs,
- nextTick,
- queueJob,
- queuePostFlushCb,
- } from '../src/scheduler'
- describe('scheduler', () => {
- it('nextTick', async () => {
- const calls: string[] = []
- const dummyThen = Promise.resolve().then()
- const job1 = () => {
- calls.push('job1')
- }
- const job2 = () => {
- calls.push('job2')
- }
- nextTick(job1)
- job2()
- expect(calls.length).toBe(1)
- await dummyThen
- // job1 will be pushed in nextTick
- expect(calls.length).toBe(2)
- expect(calls).toMatchObject(['job2', 'job1'])
- })
- describe('queueJob', () => {
- it('basic usage', async () => {
- const calls: string[] = []
- const job1 = () => {
- calls.push('job1')
- }
- const job2 = () => {
- calls.push('job2')
- }
- queueJob(job1)
- queueJob(job2)
- expect(calls).toEqual([])
- await nextTick()
- expect(calls).toEqual(['job1', 'job2'])
- })
- it("should insert jobs in ascending order of job's id when flushing", async () => {
- const calls: string[] = []
- const job1 = () => {
- calls.push('job1')
- queueJob(job2)
- queueJob(job3)
- }
- const job2 = () => {
- calls.push('job2')
- queueJob(job4)
- queueJob(job5)
- }
- job2.id = 10
- const job3 = () => {
- calls.push('job3')
- }
- job3.id = 1
- const job4 = () => {
- calls.push('job4')
- }
- const job5 = () => {
- calls.push('job5')
- }
- queueJob(job1)
- expect(calls).toEqual([])
- await nextTick()
- expect(calls).toEqual(['job1', 'job3', 'job2', 'job4', 'job5'])
- })
- it('should dedupe queued jobs', async () => {
- const calls: string[] = []
- const job1 = () => {
- calls.push('job1')
- }
- const job2 = () => {
- calls.push('job2')
- }
- queueJob(job1)
- queueJob(job2)
- queueJob(job1)
- queueJob(job2)
- expect(calls).toEqual([])
- await nextTick()
- expect(calls).toEqual(['job1', 'job2'])
- })
- it('queueJob while flushing', async () => {
- const calls: string[] = []
- const job1 = () => {
- calls.push('job1')
- // job2 will be executed after job1 at the same tick
- queueJob(job2)
- }
- const job2 = () => {
- calls.push('job2')
- }
- queueJob(job1)
- await nextTick()
- expect(calls).toEqual(['job1', 'job2'])
- })
- })
- describe('pre flush jobs', () => {
- it('queueJob inside preFlushCb', async () => {
- const calls: string[] = []
- const job1 = () => {
- calls.push('job1')
- }
- const cb1: SchedulerJob = () => {
- // queueJob in postFlushCb
- calls.push('cb1')
- queueJob(job1)
- }
- cb1.flags! |= SchedulerJobFlags.PRE
- queueJob(cb1)
- await nextTick()
- expect(calls).toEqual(['cb1', 'job1'])
- })
- it('queueJob & preFlushCb inside preFlushCb', async () => {
- const calls: string[] = []
- const job1 = () => {
- calls.push('job1')
- }
- job1.id = 1
- const cb1: SchedulerJob = () => {
- calls.push('cb1')
- queueJob(job1)
- // cb2 should execute before the job
- queueJob(cb2)
- queueJob(cb3)
- }
- cb1.flags! |= SchedulerJobFlags.PRE
- const cb2: SchedulerJob = () => {
- calls.push('cb2')
- }
- cb2.flags! |= SchedulerJobFlags.PRE
- cb2.id = 1
- const cb3: SchedulerJob = () => {
- calls.push('cb3')
- }
- cb3.flags! |= SchedulerJobFlags.PRE
- cb3.id = 1
- queueJob(cb1)
- await nextTick()
- expect(calls).toEqual(['cb1', 'cb2', 'cb3', 'job1'])
- })
- it('should insert jobs after pre jobs with the same id', async () => {
- const calls: string[] = []
- const job1: SchedulerJob = () => {
- calls.push('job1')
- }
- job1.id = 1
- job1.flags! |= SchedulerJobFlags.PRE
- const job2: SchedulerJob = () => {
- calls.push('job2')
- queueJob(job5)
- queueJob(job6)
- }
- job2.id = 2
- job2.flags! |= SchedulerJobFlags.PRE
- const job3: SchedulerJob = () => {
- calls.push('job3')
- }
- job3.id = 2
- job3.flags! |= SchedulerJobFlags.PRE
- const job4: SchedulerJob = () => {
- calls.push('job4')
- }
- job4.id = 3
- job4.flags! |= SchedulerJobFlags.PRE
- const job5: SchedulerJob = () => {
- calls.push('job5')
- }
- job5.id = 2
- const job6: SchedulerJob = () => {
- calls.push('job6')
- }
- job6.id = 2
- job6.flags! |= SchedulerJobFlags.PRE
- // We need several jobs to test this properly, otherwise
- // findInsertionIndex can yield the correct index by chance
- queueJob(job4)
- queueJob(job2)
- queueJob(job3)
- queueJob(job1)
- await nextTick()
- expect(calls).toEqual(['job1', 'job2', 'job3', 'job6', 'job5', 'job4'])
- })
- it('preFlushCb inside queueJob', async () => {
- const calls: string[] = []
- const job1 = () => {
- // the only case where a pre-flush cb can be queued inside a job is
- // when updating the props of a child component. This is handled
- // directly inside `updateComponentPreRender` to avoid non atomic
- // cb triggers (#1763)
- queueJob(cb1)
- queueJob(cb2)
- flushPreFlushCbs()
- calls.push('job1')
- }
- const cb1: SchedulerJob = () => {
- calls.push('cb1')
- // a cb triggers its parent job, which should be skipped
- queueJob(job1)
- }
- cb1.flags! |= SchedulerJobFlags.PRE
- const cb2: SchedulerJob = () => {
- calls.push('cb2')
- }
- cb2.flags! |= SchedulerJobFlags.PRE
- queueJob(job1)
- await nextTick()
- expect(calls).toEqual(['cb1', 'cb2', 'job1'])
- })
- it('should insert pre jobs without ids first during flushing', async () => {
- const calls: string[] = []
- const job1: SchedulerJob = () => {
- calls.push('job1')
- queueJob(job3)
- queueJob(job4)
- }
- // job1 has no id
- job1.flags! |= SchedulerJobFlags.PRE
- const job2: SchedulerJob = () => {
- calls.push('job2')
- }
- job2.id = 1
- job2.flags! |= SchedulerJobFlags.PRE
- const job3: SchedulerJob = () => {
- calls.push('job3')
- }
- // job3 has no id
- job3.flags! |= SchedulerJobFlags.PRE
- const job4: SchedulerJob = () => {
- calls.push('job4')
- }
- // job4 has no id
- job4.flags! |= SchedulerJobFlags.PRE
- queueJob(job1)
- queueJob(job2)
- await nextTick()
- expect(calls).toEqual(['job1', 'job3', 'job4', 'job2'])
- })
- // #3806
- it('queue preFlushCb inside postFlushCb', async () => {
- const spy = vi.fn()
- const cb: SchedulerJob = () => spy()
- cb.flags! |= SchedulerJobFlags.PRE
- queuePostFlushCb(() => {
- queueJob(cb)
- })
- await nextTick()
- expect(spy).toHaveBeenCalled()
- })
- })
- describe('queuePostFlushCb', () => {
- it('basic usage', async () => {
- const calls: string[] = []
- const cb1 = () => {
- calls.push('cb1')
- }
- const cb2 = () => {
- calls.push('cb2')
- }
- const cb3 = () => {
- calls.push('cb3')
- }
- queuePostFlushCb([cb1, cb2])
- queuePostFlushCb(cb3)
- expect(calls).toEqual([])
- await nextTick()
- expect(calls).toEqual(['cb1', 'cb2', 'cb3'])
- })
- it('should dedupe queued postFlushCb', async () => {
- const calls: string[] = []
- const cb1 = () => {
- calls.push('cb1')
- }
- const cb2 = () => {
- calls.push('cb2')
- }
- const cb3 = () => {
- calls.push('cb3')
- }
- queuePostFlushCb([cb1, cb2])
- queuePostFlushCb(cb3)
- queuePostFlushCb([cb1, cb3])
- queuePostFlushCb(cb2)
- expect(calls).toEqual([])
- await nextTick()
- expect(calls).toEqual(['cb1', 'cb2', 'cb3'])
- })
- it('queuePostFlushCb while flushing', async () => {
- const calls: string[] = []
- const cb1 = () => {
- calls.push('cb1')
- // cb2 will be executed after cb1 at the same tick
- queuePostFlushCb(cb2)
- }
- const cb2 = () => {
- calls.push('cb2')
- }
- queuePostFlushCb(cb1)
- await nextTick()
- expect(calls).toEqual(['cb1', 'cb2'])
- })
- })
- describe('queueJob w/ queuePostFlushCb', () => {
- it('queueJob inside postFlushCb', async () => {
- const calls: string[] = []
- const job1 = () => {
- calls.push('job1')
- }
- const cb1 = () => {
- // queueJob in postFlushCb
- calls.push('cb1')
- queueJob(job1)
- }
- queuePostFlushCb(cb1)
- await nextTick()
- expect(calls).toEqual(['cb1', 'job1'])
- })
- it('queueJob & postFlushCb inside postFlushCb', async () => {
- const calls: string[] = []
- const job1 = () => {
- calls.push('job1')
- }
- const cb1 = () => {
- calls.push('cb1')
- queuePostFlushCb(cb2)
- // job1 will executed before cb2
- // Job has higher priority than postFlushCb
- queueJob(job1)
- }
- const cb2 = () => {
- calls.push('cb2')
- }
- queuePostFlushCb(cb1)
- await nextTick()
- expect(calls).toEqual(['cb1', 'job1', 'cb2'])
- })
- it('postFlushCb inside queueJob', async () => {
- const calls: string[] = []
- const job1 = () => {
- calls.push('job1')
- // postFlushCb in queueJob
- queuePostFlushCb(cb1)
- }
- const cb1 = () => {
- calls.push('cb1')
- }
- queueJob(job1)
- await nextTick()
- expect(calls).toEqual(['job1', 'cb1'])
- })
- it('queueJob & postFlushCb inside queueJob', async () => {
- const calls: string[] = []
- const job1 = () => {
- calls.push('job1')
- // cb1 will executed after job2
- // Job has higher priority than postFlushCb
- queuePostFlushCb(cb1)
- queueJob(job2)
- }
- const job2 = () => {
- calls.push('job2')
- }
- const cb1 = () => {
- calls.push('cb1')
- }
- queueJob(job1)
- await nextTick()
- expect(calls).toEqual(['job1', 'job2', 'cb1'])
- })
- it('nested queueJob w/ postFlushCb', async () => {
- const calls: string[] = []
- const job1 = () => {
- calls.push('job1')
- queuePostFlushCb(cb1)
- queueJob(job2)
- }
- const job2 = () => {
- calls.push('job2')
- queuePostFlushCb(cb2)
- }
- const cb1 = () => {
- calls.push('cb1')
- }
- const cb2 = () => {
- calls.push('cb2')
- }
- queueJob(job1)
- await nextTick()
- expect(calls).toEqual(['job1', 'job2', 'cb1', 'cb2'])
- })
- test('jobs added during post flush are ordered correctly', async () => {
- const calls: string[] = []
- const job1: SchedulerJob = () => {
- calls.push('job1')
- }
- job1.id = 1
- const job2: SchedulerJob = () => {
- calls.push('job2')
- }
- job2.id = 2
- queuePostFlushCb(() => {
- queueJob(job2)
- queueJob(job1)
- })
- await nextTick()
- expect(calls).toEqual(['job1', 'job2'])
- })
- })
- test('sort job based on id', async () => {
- const calls: string[] = []
- const job1 = () => calls.push('job1')
- // job1 has no id
- const job2 = () => calls.push('job2')
- job2.id = 2
- const job3 = () => calls.push('job3')
- job3.id = 1
- const job4: SchedulerJob = () => calls.push('job4')
- job4.id = 2
- job4.flags! |= SchedulerJobFlags.PRE
- const job5: SchedulerJob = () => calls.push('job5')
- // job5 has no id
- job5.flags! |= SchedulerJobFlags.PRE
- queueJob(job1)
- queueJob(job2)
- queueJob(job3)
- queueJob(job4)
- queueJob(job5)
- await nextTick()
- expect(calls).toEqual(['job5', 'job3', 'job4', 'job2', 'job1'])
- })
- test('sort SchedulerCbs based on id', async () => {
- const calls: string[] = []
- const cb1 = () => calls.push('cb1')
- // cb1 has no id
- const cb2 = () => calls.push('cb2')
- cb2.id = 2
- const cb3 = () => calls.push('cb3')
- cb3.id = 1
- queuePostFlushCb(cb1)
- queuePostFlushCb(cb2)
- queuePostFlushCb(cb3)
- await nextTick()
- expect(calls).toEqual(['cb3', 'cb2', 'cb1'])
- })
- // #1595
- test('avoid duplicate postFlushCb invocation', async () => {
- const calls: string[] = []
- const cb1 = () => {
- calls.push('cb1')
- queuePostFlushCb(cb2)
- }
- const cb2 = () => {
- calls.push('cb2')
- }
- queuePostFlushCb(cb1)
- queuePostFlushCb(cb2)
- await nextTick()
- expect(calls).toEqual(['cb1', 'cb2'])
- })
- test('nextTick should capture scheduler flush errors', async () => {
- const err = new Error('test')
- queueJob(() => {
- throw err
- })
- try {
- await nextTick()
- } catch (e: any) {
- expect(e).toBe(err)
- }
- expect(
- `Unhandled error during execution of scheduler flush`,
- ).toHaveBeenWarned()
- // this one should no longer error
- await nextTick()
- })
- test('jobs can be re-queued after an error', async () => {
- const err = new Error('test')
- let shouldThrow = true
- const job1: SchedulerJob = vi.fn(() => {
- if (shouldThrow) {
- shouldThrow = false
- throw err
- }
- })
- job1.id = 1
- const job2: SchedulerJob = vi.fn()
- job2.id = 2
- queueJob(job1)
- queueJob(job2)
- try {
- await nextTick()
- } catch (e: any) {
- expect(e).toBe(err)
- }
- expect(
- `Unhandled error during execution of scheduler flush`,
- ).toHaveBeenWarned()
- expect(job1).toHaveBeenCalledTimes(1)
- expect(job2).toHaveBeenCalledTimes(0)
- queueJob(job1)
- queueJob(job2)
- await nextTick()
- expect(job1).toHaveBeenCalledTimes(2)
- expect(job2).toHaveBeenCalledTimes(1)
- })
- test('should prevent self-triggering jobs by default', async () => {
- let count = 0
- const job = () => {
- if (count < 3) {
- count++
- queueJob(job)
- }
- }
- queueJob(job)
- await nextTick()
- // only runs once - a job cannot queue itself
- expect(count).toBe(1)
- })
- test('should allow explicitly marked jobs to trigger itself', async () => {
- // normal job
- let count = 0
- const job: SchedulerJob = () => {
- if (count < 3) {
- count++
- queueJob(job)
- }
- }
- job.flags! |= SchedulerJobFlags.ALLOW_RECURSE
- queueJob(job)
- await nextTick()
- expect(count).toBe(3)
- // post cb
- const cb: SchedulerJob = () => {
- if (count < 5) {
- count++
- queuePostFlushCb(cb)
- }
- }
- cb.flags! |= SchedulerJobFlags.ALLOW_RECURSE
- queuePostFlushCb(cb)
- await nextTick()
- expect(count).toBe(5)
- })
- test('recursive jobs can only be queued once non-recursively', async () => {
- const job: SchedulerJob = vi.fn()
- job.id = 1
- job.flags = SchedulerJobFlags.ALLOW_RECURSE
- queueJob(job)
- queueJob(job)
- await nextTick()
- expect(job).toHaveBeenCalledTimes(1)
- })
- test('recursive jobs can only be queued once recursively', async () => {
- let recurse = true
- const job: SchedulerJob = vi.fn(() => {
- if (recurse) {
- queueJob(job)
- queueJob(job)
- recurse = false
- }
- })
- job.id = 1
- job.flags = SchedulerJobFlags.ALLOW_RECURSE
- queueJob(job)
- await nextTick()
- expect(job).toHaveBeenCalledTimes(2)
- })
- test(`recursive jobs can't be re-queued by other jobs`, async () => {
- let recurse = true
- const job1: SchedulerJob = () => {
- if (recurse) {
- // job2 is already queued, so this shouldn't do anything
- queueJob(job2)
- recurse = false
- }
- }
- job1.id = 1
- const job2: SchedulerJob = vi.fn(() => {
- if (recurse) {
- queueJob(job1)
- queueJob(job2)
- }
- })
- job2.id = 2
- job2.flags = SchedulerJobFlags.ALLOW_RECURSE
- queueJob(job2)
- await nextTick()
- expect(job2).toHaveBeenCalledTimes(2)
- })
- test('jobs are de-duplicated correctly when calling flushPreFlushCbs', async () => {
- let recurse = true
- const job1: SchedulerJob = vi.fn(() => {
- queueJob(job3)
- queueJob(job3)
- flushPreFlushCbs()
- })
- job1.id = 1
- job1.flags = SchedulerJobFlags.PRE
- const job2: SchedulerJob = vi.fn(() => {
- if (recurse) {
- // job2 does not allow recurse, so this shouldn't do anything
- queueJob(job2)
- // job3 is already queued, so this shouldn't do anything
- queueJob(job3)
- recurse = false
- }
- })
- job2.id = 2
- job2.flags = SchedulerJobFlags.PRE
- const job3: SchedulerJob = vi.fn(() => {
- if (recurse) {
- queueJob(job2)
- queueJob(job3)
- // The jobs are already queued, so these should have no effect
- queueJob(job2)
- queueJob(job3)
- }
- })
- job3.id = 3
- job3.flags = SchedulerJobFlags.ALLOW_RECURSE | SchedulerJobFlags.PRE
- queueJob(job1)
- await nextTick()
- expect(job1).toHaveBeenCalledTimes(1)
- expect(job2).toHaveBeenCalledTimes(1)
- expect(job3).toHaveBeenCalledTimes(2)
- })
- // #1947 flushPostFlushCbs should handle nested calls
- // e.g. app.mount inside app.mount
- test('flushPostFlushCbs', async () => {
- let count = 0
- const queueAndFlush = (hook: Function) => {
- queuePostFlushCb(hook)
- flushPostFlushCbs()
- }
- queueAndFlush(() => {
- queueAndFlush(() => {
- count++
- })
- })
- await nextTick()
- expect(count).toBe(1)
- })
- // #910
- test('should not run stopped reactive effects', async () => {
- const spy = vi.fn()
- // simulate parent component that toggles child
- const job1 = () => {
- // @ts-expect-error
- job2.flags! |= SchedulerJobFlags.DISPOSED
- }
- // simulate child that's triggered by the same reactive change that
- // triggers its toggle
- const job2 = () => spy()
- expect(spy).toHaveBeenCalledTimes(0)
- queueJob(job1)
- queueJob(job2)
- await nextTick()
- // should not be called
- expect(spy).toHaveBeenCalledTimes(0)
- })
- it('flushPreFlushCbs inside a pre job', async () => {
- const spy = vi.fn()
- const job: SchedulerJob = () => {
- spy()
- flushPreFlushCbs()
- }
- job.flags! |= SchedulerJobFlags.PRE
- queueJob(job)
- await nextTick()
- expect(spy).toHaveBeenCalledTimes(1)
- })
- test('flushPreFlushCbs inside a post job', async () => {
- const calls: string[] = []
- const callsAfterFlush: string[] = []
- const job1: SchedulerJob = () => {
- calls.push('job1')
- }
- job1.id = 1
- job1.flags! |= SchedulerJobFlags.PRE
- const job2: SchedulerJob = () => {
- calls.push('job2')
- }
- job2.id = 2
- job2.flags! |= SchedulerJobFlags.PRE
- queuePostFlushCb(() => {
- queueJob(job2)
- queueJob(job1)
- // e.g. nested app.mount() call
- flushPreFlushCbs()
- callsAfterFlush.push(...calls)
- })
- await nextTick()
- expect(callsAfterFlush).toEqual(['job1', 'job2'])
- expect(calls).toEqual(['job1', 'job2'])
- })
- it('nextTick should return promise', async () => {
- const fn = vi.fn(() => {
- return 1
- })
- const p = nextTick(fn)
- expect(p).toBeInstanceOf(Promise)
- expect(await p).toBe(1)
- expect(fn).toHaveBeenCalledTimes(1)
- })
- // #10003
- test('nested flushPostFlushCbs', async () => {
- const calls: string[] = []
- const cb1 = () => calls.push('cb1')
- // cb1 has no id
- const cb2 = () => calls.push('cb2')
- cb2.id = -1
- const queueAndFlush = (hook: Function) => {
- queuePostFlushCb(hook)
- flushPostFlushCbs()
- }
- queueAndFlush(() => {
- queuePostFlushCb([cb1, cb2])
- flushPostFlushCbs()
- })
- await nextTick()
- expect(calls).toEqual(['cb2', 'cb1'])
- })
- })
|