diff --git a/src/helpers/__tests__/helpers.test.ts b/src/helpers/__tests__/helpers.test.ts index c543308..44e4100 100644 --- a/src/helpers/__tests__/helpers.test.ts +++ b/src/helpers/__tests__/helpers.test.ts @@ -50,10 +50,63 @@ describe('Helper tests', () => { expect(countListeners()).toBe(0); }); - test('waiter is finished', async () => { + test('waiter is resolved', async () => { + const myWaiter = waiter(); + myWaiter.resolve(); + await myWaiter; + expect(myWaiter.isFinished).toBe(true); + expect(myWaiter.isRejected).toBe(false); + expect(myWaiter.isResolved).toBe(true); + }); + + test('waiter is resolved with value', async () => { + const myWaiter = waiter(); + const value = 'my resolve result'; + myWaiter.resolve(value); + const result = await myWaiter; + expect(result).toBe(value); + expect(myWaiter.isFinished).toBe(true); + expect(myWaiter.isRejected).toBe(false); + expect(myWaiter.isResolved).toBe(true); + }); + + test('waiter is finished (ensure finish alias works)', async () => { const myWaiter = waiter(); myWaiter.finish(); await myWaiter; expect(myWaiter.isFinished).toBe(true); + expect(myWaiter.isRejected).toBe(false); + expect(myWaiter.isResolved).toBe(true); + }); + + test('waiter is rejected', async () => { + const myWaiter = waiter(); + const error = new Error('Waiter was rejected'); + myWaiter.reject(error); + await expect(myWaiter).rejects.toThrow(error); + expect(myWaiter.isFinished).toBe(true); + expect(myWaiter.isRejected).toBe(true); + expect(myWaiter.isResolved).toBe(false); + }); + + test('waiter is rejected with error type', async () => { + class MyError extends Error { + readonly name = 'MyError'; + } + const myWaiter = waiter(); + const error = new MyError('MyError test instance'); + myWaiter.reject(error); + await expect(myWaiter).rejects.toThrow(error); + expect(myWaiter.isFinished).toBe(true); + expect(myWaiter.isRejected).toBe(true); + expect(myWaiter.isResolved).toBe(false); + + // Expect other error types to cause a typescript error + class OtherError extends Error { + readonly name = 'OtherError'; + } + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-expect-error + myWaiter.reject(new OtherError('OtherError test instance')); }); }); diff --git a/src/helpers/time.ts b/src/helpers/time.ts index 61f40a5..bfe6b4e 100644 --- a/src/helpers/time.ts +++ b/src/helpers/time.ts @@ -105,26 +105,43 @@ export function stopwatch(): Stopwatch { return result; } -export type Waiter = Promise & { +export type Waiter = Promise & { + /** Alias for `resolve` */ finish: (result: T) => void; + resolve: (result: T) => void; + reject: (error: E) => void; + /** True if the promise is resolved or rejected */ isFinished: boolean; + /** True only if the promise is resolved */ + isResolved: boolean; + /** True only if the promise is rejected */ + isRejected: boolean; }; /** - * Creates a `Waiter` promise that can be resolved at a later time with a return value. + * Creates a `Waiter` promise that can be resolved or rejected at a later time. * @returns Waiter */ -export function waiter(): Waiter { +export function waiter(): Waiter { let resolveFn: (result: T) => void; - const promise = new Promise(resolve => { + let rejectFn: (error: E) => void; + const promise = new Promise((resolve, reject) => { resolveFn = resolve; + rejectFn = reject; }); const completer = { - finish: (result: T) => { - void Object.assign(promise, { isFinished: true }); + finish: (result: T) => completer.resolve(result), + resolve: (result: T) => { + void Object.assign(promise, { isFinished: true, isResolved: true }); resolveFn(result); }, + reject: (error: E) => { + void Object.assign(promise, { isFinished: true, isRejected: true }); + rejectFn(error); + }, isFinished: false, + isResolved: false, + isRejected: false, }; return Object.assign(promise, completer); }