Skip to content

Commit

Permalink
feat(timer): fix impl, add tests
Browse files Browse the repository at this point in the history
  • Loading branch information
shigma committed Jan 5, 2024
1 parent ba08a0f commit cf098f2
Show file tree
Hide file tree
Showing 2 changed files with 102 additions and 26 deletions.
46 changes: 20 additions & 26 deletions packages/timer/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
import { Context, Service } from 'cordis'
import { defineProperty, remove } from 'cosmokit'
import Logger from 'reggol'

export { Logger }
import { remove } from 'cosmokit'

declare module 'cordis' {
interface Context {
Expand All @@ -13,42 +10,39 @@ declare module 'cordis' {
class TimerService extends Service {
constructor(ctx: Context) {
super(ctx, 'timer', true)
defineProperty(this, Context.current, ctx)
}

createTimerDispose(timer: number | NodeJS.Timeout) {
const dispose = () => {
clearTimeout(timer)
if (!this[Context.current].scope) return
return remove(this[Context.current].scope.disposables, dispose)
}
this[Context.current].scope.disposables.push(dispose)
return dispose
ctx.mixin('timer', ['setTimeout', 'setInterval', 'sleep', 'throttle', 'debounce'])
}

setTimeout(callback: (...args: any[]) => void, ms: number, ...args: any[]) {
const dispose = this.createTimerDispose(setTimeout(() => {
dispose()
callback()
}, ms, ...args))
setTimeout(callback: () => void, delay: number) {
const dispose = this[Context.current].effect(() => {
const timer = setTimeout(() => {
dispose()
callback()
}, delay)
return () => clearTimeout(timer)
})
return dispose
}

setInterval(callback: (...args: any[]) => void, ms: number, ...args: any[]) {
return this.createTimerDispose(setInterval(callback, ms, ...args))
setInterval(callback: () => void, delay: number) {
return this[Context.current].effect(() => {
const timer = setInterval(callback, delay)
return () => clearInterval(timer)
})
}

sleep(ms: number) {
sleep(delay: number) {
const caller = this[Context.current]
return new Promise<void>((resolve, reject) => {
const dispose1 = this.setTimeout(() => {
dispose1()
dispose2()
resolve()
}, ms)
const dispose2 = this[Context.current].on('dispose', () => {
}, delay)
const dispose2 = caller.on('dispose', () => {
dispose1()
dispose2()
reject(new Error('Context disposed'))
reject(new Error('Context has been disposed'))
})
})
}
Expand Down
82 changes: 82 additions & 0 deletions packages/timer/tests/index.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { afterEach, beforeEach, describe, mock, test } from 'node:test'
import { Context } from 'cordis'
import { expect } from 'chai'
import Timer from '../src'
import assert from 'node:assert'

function tick(delay = 0) {
mock.timers.tick(delay)
return new Promise<void>(resolve => process.nextTick(resolve))
}

beforeEach(() => {
mock.timers.enable()
})

afterEach(() => {
mock.timers.reset()
})

function withContext(callback: (ctx: Context) => Promise<void>) {
return () => new Promise<void>((resolve, reject) => {
const ctx = new Context()
ctx.plugin(Timer)
ctx.plugin(() => {
callback(ctx).then(resolve, reject)
})
})
}

describe('ctx.setTimeout()', () => {
test('basic support', withContext(async (ctx) => {
const callback = mock.fn()
ctx.setTimeout(callback, 1000)
expect(callback.mock.calls).to.have.length(0)
await tick(1000)
expect(callback.mock.calls).to.have.length(1)
await tick(1000)
expect(callback.mock.calls).to.have.length(1)
}))

test('dispose', withContext(async (ctx) => {
const callback = mock.fn()
const dispose = ctx.setTimeout(callback, 1000)
expect(callback.mock.calls).to.have.length(0)
dispose()
await tick(5000)
expect(callback.mock.calls).to.have.length(0)
}))
})

describe('ctx.setInterval()', () => {
test('basic support', withContext(async (ctx) => {
const callback = mock.fn()
const dispose = ctx.setInterval(callback, 1000)
expect(callback.mock.calls).to.have.length(0)
await tick(1000)
expect(callback.mock.calls).to.have.length(1)
await tick(1000)
expect(callback.mock.calls).to.have.length(2)
dispose()
await tick(5000)
expect(callback.mock.calls).to.have.length(2)
}))
})

describe('ctx.sleep()', () => {
test('basic support', withContext(async (ctx) => {
const resolve = mock.fn()
const reject = mock.fn()
ctx.sleep(1000).then(resolve, reject)
await tick(500)
assert.strictEqual(resolve.mock.calls.length, 0)
assert.strictEqual(reject.mock.calls.length, 0)
await tick(500)
assert.strictEqual(resolve.mock.calls.length, 1)
assert.strictEqual(reject.mock.calls.length, 0)
ctx.scope.dispose()
await tick(5000)
assert.strictEqual(resolve.mock.calls.length, 1)
assert.strictEqual(reject.mock.calls.length, 0)
}))
})

0 comments on commit cf098f2

Please sign in to comment.