From cf098f27add58114050c490e2749423cc85aed08 Mon Sep 17 00:00:00 2001 From: Shigma Date: Sat, 6 Jan 2024 02:11:38 +0800 Subject: [PATCH] feat(timer): fix impl, add tests --- packages/timer/src/index.ts | 46 ++++++++--------- packages/timer/tests/index.spec.ts | 82 ++++++++++++++++++++++++++++++ 2 files changed, 102 insertions(+), 26 deletions(-) create mode 100644 packages/timer/tests/index.spec.ts diff --git a/packages/timer/src/index.ts b/packages/timer/src/index.ts index c366f7b..cd7135c 100644 --- a/packages/timer/src/index.ts +++ b/packages/timer/src/index.ts @@ -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 { @@ -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((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')) }) }) } diff --git a/packages/timer/tests/index.spec.ts b/packages/timer/tests/index.spec.ts new file mode 100644 index 0000000..340cb23 --- /dev/null +++ b/packages/timer/tests/index.spec.ts @@ -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(resolve => process.nextTick(resolve)) +} + +beforeEach(() => { + mock.timers.enable() +}) + +afterEach(() => { + mock.timers.reset() +}) + +function withContext(callback: (ctx: Context) => Promise) { + return () => new Promise((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) + })) +})