-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
3 changed files
with
187 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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", | ||
); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters