Skip to content

Commit

Permalink
Merge f106902 into b1eef83
Browse files Browse the repository at this point in the history
  • Loading branch information
stwa committed Jun 7, 2019
2 parents b1eef83 + f106902 commit 6508728
Show file tree
Hide file tree
Showing 3 changed files with 187 additions and 0 deletions.
90 changes: 90 additions & 0 deletions src/event.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { queue } from "./internal/queue";

/**
* The event module provides machanisms to trigger and handle the happening of
* events. Raw event handlers are based on reading from and writing to contexts.
* On top of the provided event contexts there is the concept of event handlers
* having causes and describing effects. Therefore, an event handler is a pure
* function that doesn't perform changes but describes them.
*
* @module event
*/

/** Holds the registered event handlers. */
let registry = new Map();

export const eventQueue = queue();

/**
* Registers an event handler identified by `eventId`.
*
* The event handler gets called whenever an event with the provided `eventId`
* gets triggered. It will receive a map of causes related to the event and must
* return a map which describes the resulting effects. The resulting effects are
* then performed by specific effect handlers and are not part of the event
*
* @param {string} eventId An event identifier.
* @param {function} handlerFn A function which gets passed causes of the event
* and must return a map of effects that should be applied.
*/
export function handling(eventId, handlerFn) {
return rawHandling(eventId, [], (context, eventId, ...args) => {
context.effects = handlerFn(context.causes, eventId, ...args);
return context;
});
}

/**
* Registers a raw event handler identified by `eventId`.
*
* The event handler gets called whenever an event with the provided `eventId`
* gets triggered. It will receive a context related to the event and must
* return a (modified) context. Interceptors can be added to actually perform
* actions based on the resulting context.
*
* @param {string} eventId An event identifier.
* @param {function} handlerFn A function which gets passed a context describing
* the causes of the event and modifies the context.
*/
export function rawHandling(eventId, interceptors, handlerFn) {
let handler = (eventId, ...args) => {
let context = {
causes: {
event: [eventId, args],
},
effects: {},
};
return handlerFn(context, eventId, ...args);
};
registry.set(eventId, handler);
return handler;
}

/**
* Enqueues an event for processing. Processing will not happen immediately, but
* on the next tick after all previously triggered events were handled.
*
* @param {string} eventId The event identifier.
* @param {...any} args Additional arguments describing the event.
*/
export function trigger(eventId, ...args) {
eventQueue.enqueue(() => handle(eventId, ...args));
}

/**
* Triggers an event immediately without queueing.
*
* @param {string} eventId The event identifier.
* @param {...any} args Additional arguments describing the event.
*/
export function triggerImmediately(eventId, ...args) {
handle(eventId, ...args);
}

function handle(eventId, ...args) {
let handlerFn = registry.get(eventId);
if (handlerFn) {
return handlerFn(eventId, ...args);
}
console.warn("no handler registered for:", eventId);
}
96 changes: 96 additions & 0 deletions src/event.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import {
eventQueue,
trigger,
triggerImmediately,
handling,
rawHandling,
} from "./event";

/* global global */

let ticker = (function() {
let fns = [];
return {
dispatch(fn) {
fns.push(fn);
},
advance() {
let fn = fns.pop();
if (fn) fn();
},
size() {
return fns.length;
},
};
})();

beforeEach(() => {
global.console.warn = jest.fn();
eventQueue.tickFn(ticker.dispatch);
});

describe("handling", () => {
it("passes causes and event information to the handler", () => {
let handler = handling("foo", (causes, eventId, ...args) => {
expect(causes.event).toStrictEqual([eventId, args]);
expect(eventId).toBe("foo");
expect(args).toStrictEqual(["bar", "baz"]);

return { succeed: true };
});
let context = handler("foo", "bar", "baz");
expect(context.causes.event).toStrictEqual(["foo", ["bar", "baz"]]);
expect(context.effects.succeed).toBeTruthy();
});
});

describe("rawHandling", () => {
it("passes context and event information to the handler", () => {
let handler = rawHandling("foo", [], (context, eventId, ...args) => {
expect(context.causes.event).toStrictEqual([eventId, args]);
expect(eventId).toBe("foo");
expect(args).toStrictEqual(["bar", "baz"]);

context.effects = { succeed: true };
return context;
});
let context = handler("foo", "bar", "baz");
expect(context.causes.event).toStrictEqual(["foo", ["bar", "baz"]]);
expect(context.effects.succeed).toBeTruthy();
});
});

describe("trigger", () => {
it("queues the call of the registered handler", () => {
let handled = 0;
handling("foo", () => handled++);
trigger("foo");
expect(handled).toBe(0);
ticker.advance();
expect(handled).toBe(1);
});
it("logs a warning for unknown events", () => {
trigger("bar");
ticker.advance();
expect(global.console.warn).toHaveBeenCalledWith(
"no handler registered for:",
"bar",
);
});
});

describe("triggerImmediately", () => {
it("calls the registered handler", () => {
let handled = 0;
handling("foo", () => handled++);
triggerImmediately("foo");
expect(handled).toBe(1);
});
it("logs a warning for unknown events", () => {
triggerImmediately("bar");
expect(global.console.warn).toHaveBeenCalledWith(
"no handler registered for:",
"bar",
);
});
});
1 change: 1 addition & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ export {
withInputSignals,
} from "./connector";
export { mount } from "./dom";
export { handling, rawHandling, trigger, triggerImmediately } from "./event";
export { signal, signalFn } from "./signal";

0 comments on commit 6508728

Please sign in to comment.