Skip to content

Commit

Permalink
Add new methods on Flux: addAction, addActions, addStore, addStores
Browse files Browse the repository at this point in the history
Add new method on Dispatcher: addStore to support Flux#addStore
Add tests for new api functionality
  • Loading branch information
randallsquared committed Oct 2, 2014
1 parent e4fbd80 commit 316e46e
Show file tree
Hide file tree
Showing 3 changed files with 179 additions and 21 deletions.
9 changes: 7 additions & 2 deletions lib/dispatcher.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,22 @@ var _clone = require("lodash-node/modern/objects/clone"),
_uniq = require("lodash-node/modern/arrays/uniq");

var Dispatcher = function(stores) {
this.stores = stores;
this.stores = {};
this.currentDispatch = null;
this.waitingToDispatch = [];

for (var key in stores) {
if (stores.hasOwnProperty(key)) {
stores[key].dispatcher = this;
this.addStore(key, stores[key]);
}
}
};

Dispatcher.prototype.addStore = function(name, store) {
store.dispatcher = this;
this.stores[name] = store;
};

Dispatcher.prototype.dispatch = function(action) {
if (this.currentDispatch) {
throw new Error("Cannot dispatch an action while another action is being dispatched");
Expand Down
92 changes: 77 additions & 15 deletions lib/flux.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,39 +4,101 @@ function bindActions(target, actions, dispatchBinder) {
for (var key in actions) {
if (actions.hasOwnProperty(key)) {
if (typeof actions[key] === "function") {
target[key] = actions[key].bind(dispatchBinder);
if (target[key] === undefined) {
target[key] = actions[key].bind(dispatchBinder);
} else {
// TODO: pass in the sequence of keys by which we arrived here to provide in the error
throw new Error("An action by the name of '" + key + "' already exists here");
}
} else if (typeof actions[key] === "object") {
target[key] = {};
if (typeof target[key] === "function") {
throw new Error("A namespace by the name of '" + key + "' already exists here");
} else if (target[key] === undefined) {
target[key] = {};
}
bindActions(target[key], actions[key], dispatchBinder);
}
}
}
}
}
}

var Flux = function(stores, actions) {
var dispatcher = new Dispatcher(stores),
dispatchBinder = {
this.dispatcher = new Dispatcher(stores);
this.actions = {};
this.stores = {};

this.addActions(actions);
this.addStores(stores);

};

Flux.prototype.addActions = function(actions) {
var dispatcher = this.dispatcher,
dispatchBinder = {
flux: this,
dispatch: function(type, payload) {
dispatcher.dispatch({type: type, payload: payload});
}
};

this.dispatcher = dispatcher;
this.actions = {};
this.stores = stores;

};

bindActions(this.actions, actions, dispatchBinder);
};

for (var key in stores) {
if (stores.hasOwnProperty(key)) {
stores[key].flux = this;
// addAction has two signatures:
// 1: string[, string, string, string...], actionFunction
// 2: arrayOfStrings, actionFunction
Flux.prototype.addAction = function() {
if (arguments.length < 2) {
throw new Error("addAction requires at least two arguments, a string (or array of strings), and a function");
}
if (typeof arguments[0] === "string") {
// convert signature 2 into 1
var args = [];
for (var i = 0; i<arguments.length; i++) {
if (typeof arguments[i] === "string") {
args.push(arguments[i]);
} else if (typeof arguments[i] === "function" && i===arguments.length-1) {
return this.addAction(args, arguments[i]);
} else {
throw new Error("didn't understand argument " + i+1 + " to addAction");
}
}
throw new Error("didn't find action function in " + arguments.length + " arguments to addAction");
} else {
// handle signature 1
var keys = arguments[0];
var actionFunc = arguments[1];
var actions = {};
actions[keys.pop()] = actionFunc;
while (keys.length) {
var key = keys.pop();
var outer = {};
outer[key] = actions;
actions = outer;
}
this.addActions(actions);
}
};

Flux.prototype.store = function(name) {
return this.stores[name];
};

Flux.prototype.addStore = function(name, store) {
if (name in this.stores) {
throw new Error("A store keyed by '" + name + "' already exists");
}
store.flux = this;
this.stores[name] = store;
this.dispatcher.addStore(name, store);
};

Flux.prototype.addStores = function(stores) {
for (var key in stores) {
if (stores.hasOwnProperty(key)) {
this.addStore(key, stores[key]);
}
}
};

module.exports = Flux;
99 changes: 95 additions & 4 deletions test/unit/test_flux.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,42 @@ var chai = require("chai"),
chai.use(sinonChai);

describe("Flux", function() {
it("allows retrieval of stores", function() {
var store1 = {};
var store2 = {};
it("allows retrieval of stores added by constructor", function() {
var store1 = {};
var store2 = {};
var stores = { Store1: store1, Store2: store2 };
var flux = new Fluxxor.Flux(stores, {});
expect(flux.store("Store1")).to.equal(store1);
expect(flux.store("Store2")).to.equal(store2);
});
});

it("allows retrieval of stores added by addStores", function() {
var store1 = {};
var store2 = {};
var stores = { Store1: store1, Store2: store2 };
var flux = new Fluxxor.Flux({}, {});
flux.addStores(stores);
expect(flux.store("Store1")).to.equal(store1);
expect(flux.store("Store2")).to.equal(store2);
});

it("allows retrieval of stores added by addStore", function() {
var store1 = {};
var store2 = {};
var flux = new Fluxxor.Flux({}, {});
flux.addStore("Store1", store1);
flux.addStore("Store2", store2);
expect(flux.store("Store1")).to.equal(store1);
expect(flux.store("Store2")).to.equal(store2);
});

it("does not allow duplicate stores", function() {
var store1 = {};
var flux = new Fluxxor.Flux({}, {});
flux.addStore("Store1", store1);
expect(function() { flux.addStore("Store1", {}); }).to.throw("already exists");
expect(flux.store("Store1")).to.equal(store1);
});

it("sets a 'flux' property on stores", function() {
var store1 = {};
Expand Down Expand Up @@ -69,4 +97,67 @@ describe("Flux", function() {
flux.actions.a.b.c();
expect(flux.dispatcher.dispatch).to.have.been.calledWith({type: "action", payload: {name: "a.b.c"}});
});

it("allows adding actions after Flux creation via addActions", function() {
var actions = {
a: {
b: {
c: function() { this.dispatch("action", {name: "a.b.c"}); }
},
d: function() { this.dispatch("action", {name: "a.d"}); }
},
e: function() { this.dispatch("action", {name: "e"}); }
};
var flux = new Fluxxor.Flux({}, {});
flux.addActions(actions);
flux.dispatcher.dispatch = sinon.spy();
flux.actions.e();
expect(flux.dispatcher.dispatch).to.have.been.calledWith({type: "action", payload: {name: "e"}});
flux.actions.a.d();
expect(flux.dispatcher.dispatch).to.have.been.calledWith({type: "action", payload: {name: "a.d"}});
flux.actions.a.b.c();
expect(flux.dispatcher.dispatch).to.have.been.calledWith({type: "action", payload: {name: "a.b.c"}});
});

it("allows adding actions after Flux creation via addAction", function() {
var actions = {
a: {
b: {
c: function() { this.dispatch("action", {name: "a.b.c"}); }
},
d: function() { this.dispatch("action", {name: "a.d"}); }
},
e: function() { this.dispatch("action", {name: "e"}); }
};
var flux = new Fluxxor.Flux({}, actions);
flux.addAction("f", function() { this.dispatch("action", {name: "f"}); });
flux.addAction("a", "b", "g", function() { this.dispatch("action", {name: "a.b.g"}); });
flux.addAction("h", "i", "j", function() { this.dispatch("action", {name: "h.i.j"}); });
flux.addAction(["k", "l", "m"], function() { this.dispatch("action", {name: "k.l.m"}); });
flux.dispatcher.dispatch = sinon.spy();

flux.actions.f();
expect(flux.dispatcher.dispatch).to.have.been.calledWith({type: "action", payload: {name: "f"}});
flux.actions.a.b.g();
expect(flux.dispatcher.dispatch).to.have.been.calledWith({type: "action", payload: {name: "a.b.g"}});
flux.actions.h.i.j();
expect(flux.dispatcher.dispatch).to.have.been.calledWith({type: "action", payload: {name: "h.i.j"}});
flux.actions.k.l.m();
expect(flux.dispatcher.dispatch).to.have.been.calledWith({type: "action", payload: {name: "k.l.m"}});
});

it("does not allow duplicate actions", function() {
var actions = {
a: {
b: function() { this.dispatch("action", {name: "a.b"}); }
}
};
var flux = new Fluxxor.Flux({}, actions);
expect(function() { flux.addAction("a", "b", function() { this.dispatch("action", {name: "a.z"}); }); }).to.throw("already exists");

flux.dispatcher.dispatch = sinon.spy();
flux.actions.a.b();
expect(flux.dispatcher.dispatch).to.have.been.calledWith({type: "action", payload: {name: "a.b"}});

});
});

0 comments on commit 316e46e

Please sign in to comment.