Skip to content

Commit

Permalink
Merge f65ea1e into 4730926
Browse files Browse the repository at this point in the history
  • Loading branch information
stwa committed Jun 18, 2019
2 parents 4730926 + f65ea1e commit ef3d42f
Show file tree
Hide file tree
Showing 8 changed files with 203 additions and 2 deletions.
27 changes: 27 additions & 0 deletions src/batteries.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,32 @@
import { causing } from "./cause";
import { effector } from "./effect";
import { trigger } from "./event";
import { db } from "./db";

/**
* Cause that returs the state of the database signal.
*
* @see {@link db}
*/
causing("db", () => db.value());

/**
* Cause that returns the current time.
*/
causing("now", () => Date.now());

/**
* Effect that resets the state of the database signal to a new value.
*
* @see {@link db}
*
* @example
*
* {
* db: { ...db, some: "value" }
* }
*/
effector("db", updatedDb => db.reset(updatedDb));

/**
* Effect that triggers an event with args.
Expand Down
25 changes: 25 additions & 0 deletions src/batteries.test.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import "./batteries";
import { cause } from "./cause";
import { db } from "./db";
import { effect } from "./effect";
import { trigger } from "./event";

Expand All @@ -21,6 +23,29 @@ window.XMLHttpRequest = function() {
return xhr;
};

describe("db cause", () => {
it("should return the state of db", () => {
db.reset({ foo: "bar" });
expect(cause("db")).toStrictEqual({ foo: "bar" });
});
});

describe("now cause", () => {
it("should return the current time", () => {
let now = Date.now();
let causedNow = cause("now");
let delta = (causedNow - now) / 1000;
expect(delta).toBeCloseTo(0);
});
});

describe("db effect", () => {
it("should reset the state of db", () => {
effect("db", { foo: "baz" });
expect(db.value()).toStrictEqual({ foo: "baz" });
});
});

describe("trigger effect", () => {
it("should trigger the described event", () => {
effect("trigger", ["foo", "bar", "baz"]);
Expand Down
50 changes: 50 additions & 0 deletions src/cause.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/**
* Unlike an effect, a cause does not mutate but extracts state from the world.
* By providing a cause identifier the registered cause handler gets called and
* must return the requested state. A cause can be anything, e.g. the current
* state of a `signal`, the current time or a browsers window dimensions, just
* to give some examples.
*
* @module cause
*/

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

/**
* Registers a cause handler identified by `causeId`.
*
* @param {string} causeId A cause identifier.
* @param {function} handlerFn A function which gets passed the arguments from
* the call to `cause`.
*/
export function causing(causeId, handlerFn) {
if (registry.has(causeId)) {
console.warn("overwriting causing for", causeId);
}
registry.set(causeId, handlerFn);
}

/**
* Calls the cause handler identified by `causeId` with the provided `args`.
*
* Note: for any given call to `cause` there must be a previous call to
* `causing`, registering a handler function for `causeId`.
*
* @param {string} causeId The cause identifier.
* @param {...any} args Arguments passed to the cause handler.
*/
export function cause(causeId, ...args) {
let handlerFn = registry.get(causeId);
if (handlerFn) {
return handlerFn(...args);
}
console.warn("no causing registered for:", causeId);
}

/**
* Clears all registered causings.
*/
export function clearCausings() {
registry.clear();
}
34 changes: 34 additions & 0 deletions src/cause.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { cause, causing, clearCausings } from "./cause";

/* global global */

beforeEach(() => {
global.console.warn = jest.fn();
clearCausings();
});

describe("cause", () => {
it("handles causings", () => {
causing("foo", value => value + "baz");
let result = cause("foo", "bar");
expect(result).toEqual("barbaz");
});
it("logs a warning for unknown causings", () => {
cause("foo", "bar");
expect(global.console.warn).toHaveBeenCalledWith(
"no causing registered for:",
"foo",
);
});
});

describe("causing", () => {
it("logs a warning when overwriting an existing causing", () => {
causing("foo", () => {});
causing("foo", () => {});
expect(global.console.warn).toHaveBeenCalledWith(
"overwriting causing for",
"foo",
);
});
});
6 changes: 6 additions & 0 deletions src/db.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { signal } from "./signal";

/**
* A signal storing the state of a database.
*/
export const db = signal({});
16 changes: 15 additions & 1 deletion src/event.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { queue } from "./internal/queue";
import { cause } from "./cause";
import { effect } from "./effect";

/**
Expand Down Expand Up @@ -40,7 +41,7 @@ export function handler(eventId, handlerFn, interceptors = []) {
context.effects = handlerFn(context.causes, eventId, ...args);
return context;
},
[effectsInterceptor, ...interceptors],
[injectCause("db"), effectsInterceptor, ...interceptors],
);
}

Expand Down Expand Up @@ -108,6 +109,19 @@ export function clearHandlers() {
registry.clear();
}

/**
* Creates an interceptor which injects the cause identified by `causeId` into
* the contexts causes.
*/
export function injectCause(causeId, ...args) {
return nextFn => {
return context => {
context.causes[causeId] = cause(causeId, ...args);
return nextFn(context);
};
};
}

/**
* An interceptor which calls the corresponding effect handler for each
* described effect in `context.effects`.
Expand Down
37 changes: 37 additions & 0 deletions src/event.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ import {
triggerImmediately,
handler,
rawHandler,
injectCause,
effectsInterceptor,
} from "./event";
import { clearCausings, causing } from "./cause";
import { clearEffectors, effector } from "./effect";

/* global global */
Expand Down Expand Up @@ -37,6 +40,7 @@ beforeEach(() => {
global.console.warn = jest.fn();
eventQueue.tickFn(ticker.dispatch);
clearHandlers();
clearCausings();
clearEffectors();
});

Expand All @@ -53,6 +57,16 @@ describe("handler", () => {
expect(context.causes.event).toStrictEqual(["foo", ["bar", "baz"]]);
expect(context.effects.succeed).toBeTruthy();
});
it("injects the db cause into the context", () => {
let called = false;
let handlerFn = handler("foo", causes => {
expect(causes.db).toBe("value");
called = true;
});
causing("db", () => "value");
handlerFn("foo");
expect(called).toBe(true);
});
it("handles effects from context", () => {
let succeed = false;
let handlerFn = handler("foo", () => ({ bar: "baz" }));
Expand Down Expand Up @@ -133,3 +147,26 @@ describe("triggerImmediately", () => {
);
});
});

describe("injectCause", () => {
it("injects the causes into the context", () => {
causing("foo", () => "bar");
let interceptor = injectCause("foo");
let handlerFn = interceptor(context => context);
let context = handlerFn({ causes: {} });
expect(context.causes.foo).toBe("bar");
});
});

describe("effectsInterceptor", () => {
it("runs the effects registered in the context", () => {
let called = false;
effector("foo", arg => {
expect(arg).toBe("bar");
called = true;
});
let handlerFn = effectsInterceptor(context => context);
handlerFn({ effects: { foo: "bar" } });
expect(called).toBeTruthy();
});
});
10 changes: 9 additions & 1 deletion src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,17 @@ export {
rawConnector,
withInputSignals,
} from "./connector";
export { cause, causing } from "./cause";
export { mount } from "./dom";
export { effect, effector } from "./effect";
export { handler, rawHandler, trigger, triggerImmediately } from "./event";
export {
handler,
rawHandler,
trigger,
triggerImmediately,
injectCause,
effectsInterceptor,
} from "./event";
export { signal, signalFn } from "./signal";

import "./batteries";

0 comments on commit ef3d42f

Please sign in to comment.