diff --git a/src/shared/__tests__/helpers.test.ts b/src/shared/__tests__/helpers.test.ts index ab1c7c2a2..8693fb7c0 100644 --- a/src/shared/__tests__/helpers.test.ts +++ b/src/shared/__tests__/helpers.test.ts @@ -7,6 +7,7 @@ import { getNextOccurrence, toWalletAddressUrl, setDifference, + Timeout, } from '../helpers'; describe('objectEquals', () => { @@ -165,3 +166,54 @@ describe('toWalletAddressUrl', () => { ); }); }); + +describe('Timeout', () => { + jest.useFakeTimers(); + + let callback: jest.Mock; + let timeout: Timeout; + beforeEach(() => { + callback = jest.fn(); + timeout = new Timeout(1000, callback); + }); + + afterEach(() => { + jest.clearAllTimers(); + test; + }); + + it('should call the callback after the specified time', () => { + jest.advanceTimersByTime(1000); + expect(callback).toHaveBeenCalledTimes(1); + }); + + it('should reset the timeout', () => { + timeout.reset(2000); + // @ts-expect-error for testing it's ok to access private properties + expect(timeout.ms).toBe(2000); + jest.advanceTimersByTime(2000); + expect(callback).toHaveBeenCalledTimes(1); + }); + + it('should pause the timeout', () => { + timeout.pause(); + jest.advanceTimersByTime(1000); + expect(callback).not.toHaveBeenCalled(); + }); + + it('should resume the timeout', () => { + timeout.pause(); + jest.advanceTimersByTime(500); + timeout.resume(); + jest.advanceTimersByTime(500); + expect(callback).not.toHaveBeenCalled(); + jest.advanceTimersByTime(500); + expect(callback).toHaveBeenCalledTimes(1); + }); + + it('should clear the timeout', () => { + timeout.clear(); + jest.advanceTimersByTime(1000); + expect(callback).not.toHaveBeenCalled(); + }); +}); diff --git a/src/shared/helpers/time.ts b/src/shared/helpers/time.ts index 5de86b1be..a9e34f2ff 100644 --- a/src/shared/helpers/time.ts +++ b/src/shared/helpers/time.ts @@ -116,20 +116,46 @@ export function debounceSync( export class Timeout { private timeout: ReturnType | null = null; + #isPaused = false; + #remaining = 0; + #startTime = 0; + constructor( - ms: number, + private ms: number, private callback: () => void, ) { - this.reset(ms); + if (ms > 0) this.reset(ms); } reset(ms: number) { this.clear(); + this.ms = ms; + this.#isPaused = false; + this.#startTime = Date.now(); this.timeout = setTimeout(this.callback, ms); } + pause() { + if (this.#isPaused) return; + this.clear(); + this.#isPaused = true; + this.#remaining = this.ms - (Date.now() - this.#startTime); + } + + resume() { + if (!this.#isPaused) return; + if (this.#remaining > 0) { + this.timeout = setTimeout(() => { + this.callback(); + this.reset(this.ms); + }, this.#remaining); + } else { + this.reset(this.ms); + } + } + clear() { - if (this.timeout) { + if (this.timeout !== null) { clearTimeout(this.timeout); this.timeout = null; }