Commit f970ad34 authored by Paul Slaughter's avatar Paul Slaughter

Create mitt like event hub for event_hub_factory

- This way we can avoid creating a whole Vue
  instance and all the coupling there
- In a follow up we will probably replace with mitt
  once our coupling to certain quirks is fixed
parent a6eb8c15
import Vue from 'vue'; /**
* An event hub with a Vue instance like API
*
* NOTE: There's an [issue open][4] to eventually remove this when some
* coupling in our codebase has been fixed.
*
* NOTE: This is a derivative work from [mitt][1] v1.2.0 which is licensed by
* [MIT License][2] © [Jason Miller][3]
*
* [1]: https://github.com/developit/mitt
* [2]: https://opensource.org/licenses/MIT
* [3]: https://jasonformat.com/
* [4]: https://gitlab.com/gitlab-org/gitlab/-/issues/223864
*/
class EventHub {
constructor() {
this.$_all = new Map();
}
dispose() {
this.$_all.clear();
}
/**
* Register an event handler for the given type.
*
* @param {string|symbol} type Type of event to listen for
* @param {Function} handler Function to call in response to given event
*/
$on(type, handler) {
const handlers = this.$_all.get(type);
const added = handlers && handlers.push(handler);
if (!added) {
this.$_all.set(type, [handler]);
}
}
/**
* Remove an event handler or all handlers for the given type.
*
* @param {string|symbol} type Type of event to unregister `handler`
* @param {Function} handler Handler function to remove
*/
$off(type, handler) {
const handlers = this.$_all.get(type) || [];
const newHandlers = handler ? handlers.filter(x => x !== handler) : [];
if (newHandlers.length) {
this.$_all.set(type, newHandlers);
} else {
this.$_all.delete(type);
}
}
/**
* Add an event listener to type but only trigger it once
*
* @param {string|symbol} type Type of event to listen for
* @param {Function} handler Handler function to call in response to event
*/
$once(type, handler) {
const wrapHandler = (...args) => {
this.$off(type, wrapHandler);
handler(...args);
};
this.$on(type, wrapHandler);
}
/**
* Invoke all handlers for the given type.
*
* @param {string|symbol} type The event type to invoke
* @param {Any} [evt] Any value passed to each handler
*/
$emit(type, ...args) {
const handlers = this.$_all.get(type) || [];
handlers.forEach(handler => {
handler(...args);
});
}
}
/** /**
* Return a Vue like event hub * Return a Vue like event hub
...@@ -14,5 +97,5 @@ import Vue from 'vue'; ...@@ -14,5 +97,5 @@ import Vue from 'vue';
* We'd like to shy away from using a full fledged Vue instance from this in the future. * We'd like to shy away from using a full fledged Vue instance from this in the future.
*/ */
export default () => { export default () => {
return new Vue(); return new EventHub();
}; };
import createEventHub from '~/helpers/event_hub_factory'; import createEventHub from '~/helpers/event_hub_factory';
const TEST_EVENT = 'foobar'; const TEST_EVENT = 'foobar';
const TEST_EVENT_2 = 'testevent';
describe('event bus factory', () => { describe('event bus factory', () => {
let eventBus; let eventBus;
let handler; let handler;
let otherHandlers;
beforeEach(() => { beforeEach(() => {
eventBus = createEventHub(); eventBus = createEventHub();
handler = jest.fn(); handler = jest.fn();
otherHandlers = [jest.fn(), jest.fn()];
}); });
afterEach(() => { afterEach(() => {
eventBus.dispose();
eventBus = null; eventBus = null;
}); });
...@@ -48,22 +52,6 @@ describe('event bus factory', () => { ...@@ -48,22 +52,6 @@ describe('event bus factory', () => {
expect(handler).toHaveBeenCalledTimes(2); expect(handler).toHaveBeenCalledTimes(2);
}); });
it('does not call handler after $off with handler', () => {
eventBus.$off(TEST_EVENT, handler);
eventBus.$emit(TEST_EVENT);
expect(handler).not.toHaveBeenCalled();
});
it('does not call handler after $off', () => {
eventBus.$off(TEST_EVENT);
eventBus.$emit(TEST_EVENT);
expect(handler).not.toHaveBeenCalled();
});
}); });
describe('$once', () => { describe('$once', () => {
...@@ -102,4 +90,55 @@ describe('event bus factory', () => { ...@@ -102,4 +90,55 @@ describe('event bus factory', () => {
}); });
}); });
}); });
describe('$off', () => {
beforeEach(() => {
otherHandlers.forEach(x => eventBus.$on(TEST_EVENT, x));
eventBus.$on(TEST_EVENT, handler);
});
it('can be called on event with no handlers', () => {
expect(() => {
eventBus.$off(TEST_EVENT_2);
}).not.toThrow();
});
it('can be called on event with no handlers, with a handler', () => {
expect(() => {
eventBus.$off(TEST_EVENT_2, handler);
}).not.toThrow();
});
it('with a handler, will no longer call that handler', () => {
eventBus.$off(TEST_EVENT, handler);
eventBus.$emit(TEST_EVENT);
expect(handler).not.toHaveBeenCalled();
expect(otherHandlers.map(x => x.mock.calls.length)).toEqual(otherHandlers.map(() => 1));
});
it('without a handler, will no longer call any handlers', () => {
eventBus.$off(TEST_EVENT);
eventBus.$emit(TEST_EVENT);
expect(handler).not.toHaveBeenCalled();
expect(otherHandlers.map(x => x.mock.calls.length)).toEqual(otherHandlers.map(() => 0));
});
});
describe('$emit', () => {
beforeEach(() => {
otherHandlers.forEach(x => eventBus.$on(TEST_EVENT_2, x));
eventBus.$on(TEST_EVENT, handler);
});
it('only calls handlers for given type', () => {
eventBus.$emit(TEST_EVENT, 'arg1');
expect(handler).toHaveBeenCalledWith('arg1');
expect(otherHandlers.map(x => x.mock.calls.length)).toEqual(otherHandlers.map(() => 0));
});
});
}); });
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment