Skip to content

Commit

Permalink
fix: make emitter errors more helpful
Browse files Browse the repository at this point in the history
  • Loading branch information
noomorph committed Feb 17, 2024
1 parent 11dd432 commit 5d494d0
Show file tree
Hide file tree
Showing 5 changed files with 92 additions and 0 deletions.
23 changes: 23 additions & 0 deletions src/emitters/SemiAsyncEmitter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,27 @@ describe('SemiAsyncEmitter', () => {
expect(listener).toHaveBeenCalledTimes(2);
expect(listener).toHaveBeenCalledWith(84);
});

describe('unhappy paths', () => {
describe('invalid type', () => {
test.each([['on'], ['once'], ['emit'], ['off']])('in emitter.%s(...)', (method) =>
// @ts-expect-error TS7053
expect(() => emitter[method]()).toThrow(/type must be a string/),
);
});

describe('invalid listener', () => {
test.each([['on'], ['once'], ['off']])('in emitter.%s(...)', (method) =>
// @ts-expect-error TS7053
expect(() => emitter[method]('*')).toThrow(/listener must be a function/),
);
});

describe('invalid order', () => {
test.each([['on'], ['once']])('in emitter.%s(...)', (method) =>
// @ts-expect-error TS7053
expect(() => emitter[method]('*', () => {}, '')).toThrow(/order must be a number/),
);
});
});
});
14 changes: 14 additions & 0 deletions src/emitters/SemiAsyncEmitter.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { assertFunction, assertNumber, assertString } from '../utils';
import type { ReadonlyAsyncEmitter } from './AsyncEmitter';
import type { ReadonlyEmitter } from './Emitter';
import { SerialAsyncEmitter } from './SerialAsyncEmitter';
Expand Down Expand Up @@ -34,6 +35,10 @@ export class SemiAsyncEmitter<AsyncMap, SyncMap>
listener: (event: any) => unknown,
order?: number,
): this {
assertString(type, 'type');
assertFunction(listener, 'listener');
order !== undefined && assertNumber(order, 'order');

return this.#invoke('on', type, listener, order);
}

Expand All @@ -52,6 +57,10 @@ export class SemiAsyncEmitter<AsyncMap, SyncMap>
listener: (event: any) => unknown,
order?: number,
): this {
assertString(type, 'type');
assertFunction(listener, 'listener');
order !== undefined && assertNumber(order, 'order');

return this.#invoke('once', type, listener, order);
}

Expand All @@ -61,12 +70,17 @@ export class SemiAsyncEmitter<AsyncMap, SyncMap>
type: K | '*',
listener: (event: any) => unknown,
): this {
assertString(type, 'type');
assertFunction(listener, 'listener');

return this.#invoke('off', type, listener);
}

emit<K extends keyof SyncMap>(type: K, event: SyncMap[K]): void;
emit<K extends keyof AsyncMap>(type: K, event: AsyncMap[K]): Promise<void>;
emit<K extends keyof (AsyncMap & SyncMap)>(type: K, event: any): void | Promise<void> {
assertString(type, 'type');

return this.#syncEvents.has(type as keyof SyncMap)
? this.#syncEmitter.emit(type as keyof SyncMap, event)
: this.#asyncEmitter.emit(type as keyof AsyncMap, event);
Expand Down
33 changes: 33 additions & 0 deletions src/utils/assertions.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { assertString, assertFunction, assertNumber } from './assertions';

describe('Tests assert functions', () => {
test('successfully validates a string', () => {
const testValue = 'test';
expect(() => assertString(testValue, 'testValue')).not.toThrow();
});

test('throws error for non-string', () => {
const testValue = 123;
expect(() => assertString(testValue, 'testValue')).toThrow(TypeError);
});

test('successfully validates a function', () => {
const testValue = () => {}; // dummy function
expect(() => assertFunction(testValue, 'testValue')).not.toThrow();
});

test('throws error for non-function', () => {
const testValue: any = 123;
expect(() => assertFunction(testValue, 'testValue')).toThrow(TypeError);
});

test('successfully validates a number', () => {
const testValue = 123;
expect(() => assertNumber(testValue, 'testValue')).not.toThrow();
});

test('throws error for non-number', () => {
const testValue: any = 'test';
expect(() => assertNumber(testValue, 'testValue')).toThrow(TypeError);
});
});
21 changes: 21 additions & 0 deletions src/utils/assertions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
export function assertString(
value: string | number | symbol,
name: string,
): asserts value is string {
assertType('string', value, name);
}

// eslint-disable-next-line @typescript-eslint/ban-types
export function assertFunction(value: Function, name: string): asserts value is Function {
assertType('function', value, name);
}

export function assertNumber(value: number, name: string): asserts value is number {
assertType('number', value, name);
}

function assertType(type: 'string' | 'function' | 'number', value: unknown, name: string) {
if (typeof value !== type) {
throw new TypeError(`${name} must be a ${type}`);
}
}
1 change: 1 addition & 0 deletions src/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './assertions';
export * from './getHierarchy';
export * from './iterateSorted';
export { makeDeferred, Deferred } from './makeDeferred';
Expand Down

0 comments on commit 5d494d0

Please sign in to comment.