Skip to content

Commit

Permalink
refactor: restructure events library
Browse files Browse the repository at this point in the history
  • Loading branch information
FlorianWoelki committed Jul 6, 2024
1 parent 195162f commit 5eb2f5e
Show file tree
Hide file tree
Showing 4 changed files with 55 additions and 44 deletions.
31 changes: 16 additions & 15 deletions src/lib/event/event.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { describe, it, vi, expect, beforeEach } from 'vitest';
import { EventEmitter } from './event';
import { AllIconsLoadedEvent } from './events';

describe('EventEmitter', () => {
let emitter: EventEmitter;
Expand All @@ -11,32 +10,34 @@ describe('EventEmitter', () => {

it('should register and emit event listeners', () => {
const listener = vi.fn();
emitter.on<AllIconsLoadedEvent>('allIconsLoaded', listener);
emitter.on('allIconsLoaded', listener);

emitter.emit({ type: 'allIconsLoaded' });
emitter.emit('allIconsLoaded');

expect(listener).toHaveBeenCalledWith({ type: 'allIconsLoaded' });
expect(listener).toHaveBeenCalledWith({ payload: undefined });
expect(listener).toHaveBeenCalledTimes(1);
});

it('should unregister event listeners', () => {
const listener = vi.fn();
emitter.on<AllIconsLoadedEvent>('allIconsLoaded', listener);
emitter.off<AllIconsLoadedEvent>('allIconsLoaded', listener);
emitter.on('allIconsLoaded', listener);
emitter.off('allIconsLoaded', listener);

emitter.emit({ type: 'allIconsLoaded' });
emitter.emit('allIconsLoaded');

expect(listener).not.toHaveBeenCalled();
});

it('should support once-only event listeners', () => {
const listener = vi.fn();
emitter.once<AllIconsLoadedEvent>('allIconsLoaded', listener);
emitter.once('allIconsLoaded', listener);

emitter.emit({ type: 'allIconsLoaded' });
emitter.emit({ type: 'allIconsLoaded' });
emitter.emit('allIconsLoaded');
emitter.emit('allIconsLoaded');

expect(listener).toHaveBeenCalledWith({ type: 'allIconsLoaded' });
expect(listener).toHaveBeenCalledWith({
payload: undefined,
});
expect(listener).toHaveBeenCalledTimes(1);
});

Expand All @@ -45,11 +46,11 @@ describe('EventEmitter', () => {
const listener2 = vi.fn();
const listener3 = vi.fn();

emitter.on<AllIconsLoadedEvent>('allIconsLoaded', listener1, 1);
emitter.on<AllIconsLoadedEvent>('allIconsLoaded', listener2, 2);
emitter.on<AllIconsLoadedEvent>('allIconsLoaded', listener3, 3);
emitter.on('allIconsLoaded', listener1, 1);
emitter.on('allIconsLoaded', listener2, 2);
emitter.on('allIconsLoaded', listener3, 3);

emitter.emit({ type: 'allIconsLoaded' });
emitter.emit('allIconsLoaded');

expect(listener3).toHaveBeenCalled();
expect(listener2).toHaveBeenCalled();
Expand Down
55 changes: 30 additions & 25 deletions src/lib/event/event.ts
Original file line number Diff line number Diff line change
@@ -1,66 +1,71 @@
type Events = 'allIconsLoaded';
import type { EventType, EventMap, AnyEvent } from './events';

export interface Event {
type: Events;
export interface Event<P = unknown> {
payload?: P;
}

type EventListener<T extends Event> = (event: T) => void;
type EventListener<E extends AnyEvent> = (event: E) => void;

interface ListenerEntry<T extends Event> {
listener: EventListener<T>;
interface ListenerEntry<E extends AnyEvent> {
listener: EventListener<E>;
once: boolean;
priority: number;
}

export class EventEmitter {
private listeners: { [key: string]: ListenerEntry<Event>[] } = {};
private listeners: { [K in EventType]?: ListenerEntry<AnyEvent>[] } = {};

on<T extends Event>(
type: T['type'],
listener: EventListener<T>,
priority: number = 0,
on<K extends EventType>(
type: K,
listener: EventListener<EventMap[K]>,
priority = 0,
): void {
this.listeners[type] ??= [];
this.listeners[type].push({ listener, once: false, priority });
this.listeners[type]?.push({ listener, once: false, priority });
this.sortListeners(type);
}

once<T extends Event>(
type: T['type'],
listener: EventListener<T>,
priority: number = 0,
once<K extends EventType>(
type: K,
listener: EventListener<EventMap[K]>,
priority = 0,
): void {
this.listeners[type] ??= [];
this.listeners[type].push({ listener, once: true, priority });
this.listeners[type]?.push({ listener, once: true, priority });
this.sortListeners(type);
}

off<T extends Event>(type: T['type'], listener: EventListener<T>): void {
off<K extends EventType>(
type: K,
listener: EventListener<EventMap[K]>,
): void {
if (!this.listeners[type]) {
return;
}

this.listeners[type] = this.listeners[type].filter(
this.listeners[type] = this.listeners[type]?.filter(
(entry) => entry.listener !== listener,
);
}

emit<T extends Event>(event: T): void {
if (!this.listeners[event.type]) {
emit<K extends EventType>(type: K, payload?: EventMap[K]['payload']): void {
const listeners = this.listeners[type];
if (!listeners) {
return;
}

this.listeners[event.type].forEach((entry) => {
const event = { payload } as EventMap[K];
listeners.slice().forEach((entry) => {
entry.listener(event);
if (entry.once) {
this.off(event.type, entry.listener);
this.off(type, entry.listener);
}
});
}

private sortListeners(type: string): void {
private sortListeners(type: EventType): void {
if (this.listeners[type]) {
this.listeners[type].sort((a, b) => b.priority - a.priority);
this.listeners[type]?.sort((a, b) => b.priority - a.priority);
}
}
}
11 changes: 8 additions & 3 deletions src/lib/event/events.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import { Event } from './event';

export interface AllIconsLoadedEvent extends Event {
type: 'allIconsLoaded';
}
export type AllIconsLoadedEvent = Event<undefined>;

export type EventMap = {
allIconsLoaded: AllIconsLoadedEvent;
};

export type EventType = keyof EventMap;
export type AnyEvent = EventMap[EventType];
2 changes: 1 addition & 1 deletion src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -394,7 +394,7 @@ export default class IconFolderPlugin extends Plugin {
resetPreloadedIcons();
}

this.eventEmitter.emit({ type: 'allIconsLoaded' });
this.eventEmitter.emit('allIconsLoaded');
});

if (this.getSettings().iconInFrontmatterEnabled) {
Expand Down

0 comments on commit 5eb2f5e

Please sign in to comment.