mirror of
https://github.com/amitwh/markdown-converter.git
synced 2026-08-02 10:00:17 +05:30
feat(plugins): add EventBus with typed events and crash-safe handlers
Amit Haridas
This commit is contained in:
@@ -0,0 +1,43 @@
|
|||||||
|
class EventBus {
|
||||||
|
constructor() {
|
||||||
|
this.listeners = new Map();
|
||||||
|
}
|
||||||
|
|
||||||
|
on(event, handler) {
|
||||||
|
if (!this.listeners.has(event)) {
|
||||||
|
this.listeners.set(event, []);
|
||||||
|
}
|
||||||
|
this.listeners.get(event).push(handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
off(event, handler) {
|
||||||
|
if (!handler) {
|
||||||
|
this.listeners.delete(event);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const handlers = this.listeners.get(event);
|
||||||
|
if (handlers) {
|
||||||
|
const idx = handlers.indexOf(handler);
|
||||||
|
if (idx !== -1) handlers.splice(idx, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
emit(event, payload) {
|
||||||
|
const handlers = this.listeners.get(event);
|
||||||
|
if (!handlers) return;
|
||||||
|
for (const handler of handlers) {
|
||||||
|
try {
|
||||||
|
handler(payload);
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`[EventBus] Error in handler for "${event}":`, err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hasHandler(event) {
|
||||||
|
const handlers = this.listeners.get(event);
|
||||||
|
return handlers != null && handlers.length > 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { EventBus };
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
const { EventBus } = require('../src/plugins/event-bus');
|
||||||
|
|
||||||
|
describe('EventBus', () => {
|
||||||
|
let bus;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
bus = new EventBus();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('on/emit — listener receives payload', () => {
|
||||||
|
const received = [];
|
||||||
|
bus.on('document:saved', (payload) => received.push(payload));
|
||||||
|
bus.emit('document:saved', { filePath: '/test.md', tabId: 'tab1' });
|
||||||
|
expect(received).toEqual([{ filePath: '/test.md', tabId: 'tab1' }]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('on — ignores events with no listeners', () => {
|
||||||
|
expect(() => bus.emit('unknown:event', {})).not.toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('off — removes specific listener', () => {
|
||||||
|
const handler = jest.fn();
|
||||||
|
bus.on('test:event', handler);
|
||||||
|
bus.off('test:event', handler);
|
||||||
|
bus.emit('test:event', {});
|
||||||
|
expect(handler).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('off — removes all listeners for event when no handler given', () => {
|
||||||
|
const h1 = jest.fn();
|
||||||
|
const h2 = jest.fn();
|
||||||
|
bus.on('test:event', h1);
|
||||||
|
bus.on('test:event', h2);
|
||||||
|
bus.off('test:event');
|
||||||
|
bus.emit('test:event', {});
|
||||||
|
expect(h1).not.toHaveBeenCalled();
|
||||||
|
expect(h2).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('hasHandler — returns true when listener exists', () => {
|
||||||
|
bus.on('ai:analyze', () => {});
|
||||||
|
expect(bus.hasHandler('ai:analyze')).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('hasHandler — returns false when no listener exists', () => {
|
||||||
|
expect(bus.hasHandler('ai:analyze')).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('handler errors are caught and logged, not thrown', () => {
|
||||||
|
const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
||||||
|
bus.on('bad:event', () => { throw new Error('boom'); });
|
||||||
|
expect(() => bus.emit('bad:event', {})).not.toThrow();
|
||||||
|
expect(errorSpy).toHaveBeenCalled();
|
||||||
|
errorSpy.mockRestore();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('multiple listeners all receive the event', () => {
|
||||||
|
const results = [];
|
||||||
|
bus.on('multi:event', () => results.push('a'));
|
||||||
|
bus.on('multi:event', () => results.push('b'));
|
||||||
|
bus.emit('multi:event', {});
|
||||||
|
expect(results).toEqual(['a', 'b']);
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user