UNPKG

bacon.circuit

Version:

Bacon.js plugin for easily constructing stateful software components as a set of interconnected observables

573 lines (391 loc) 13.1 kB
describe('Bacon.Circuit.Field', function () { it("instantiates field that delivers observable of specified type or `Bacon.Property` if not provided", function () { var field = new Bacon.Circuit.Field(_.noop, Bacon.EventStream); expect(field.observable()).to.be.instanceof(Bacon.EventStream); var field = new Bacon.Circuit.Field(_.noop, Bacon.Property); expect(field.observable()).to.be.instanceof(Bacon.Property); var field = new Bacon.Circuit.Field(_.noop); expect(field.observable()).to.be.instanceof(Bacon.Property); }); it("delivers observable that never automatically ends", function (done) { var field = new Bacon.Circuit.Field(function (sink) { sink(1); _.delay(sink, 100, 3); return Bacon.constant(2); }); field.observable().subscribe(_.after(3, function (event) { assert(!event.isEnd() && event.value() === 3); done(); })); field.start(); }); it("can be started as soon as its observable has been subscribed to", function () { var field = new Bacon.Circuit.Field(); expect(field).not.to.have.property('start'); field.observable().subscribe(_.noop); expect(field).to.have.property('start').that.is.a('function'); }); it("executes setup on start and no sooner than that", function () { var onSetup = sinon.spy(); var field = new Bacon.Circuit.Field(onSetup); expect(onSetup).to.have.not.been.called; field.observable().subscribe(_.noop); expect(onSetup).to.have.not.been.called; field.start(); expect(onSetup).to.have.been.calledOnce; }); it("calls setup with sink callback, observable and the parameters that were provided upon start", function (done) { var a = {}, b = 'b', c = true; var field = new Bacon.Circuit.Field(function (sink, me, name, circuit) { expect(this).to.equal(a); expect(sink).to.be.a('function'); expect(me).to.equal(field.observable()); expect(name).to.equal(b); expect(circuit).to.equal(c); done(); }); field.observable().subscribe(_.noop); field.start(a, b, c); }); it("emits sunk values as events in generated observable", function (done) { var field = new Bacon.Circuit.Field(function (sink) { sink(1); }); field.observable().onValue(function (value) { expect(value).to.equal(1); done(); }); field.start(); }); it("emits sunk values and returned stream values in order of appearance", function (done) { var field = new Bacon.Circuit.Field(function (sink) { sink(1); return Bacon.once(2); }); field.observable().onValue(_.after(2, function (value) { expect(value).to.equal(2); done(); })); field.start(); }); it("dismisses setup return value if not an observable of any sort", function (done) { var never = new Bacon.Circuit.Field(function () { return "ignore me"; }); Bacon.mergeAll( never.observable(), Bacon.once('first') ).onValue(function (value) { expect(value).to.equal('first'); done(); }); never.start(); }); }); describe("Bacon.Circuit.Field.stream.expose", function () { it("assigns stream observable to circuit upon setup", function (done) { var field = Bacon.Circuit.Field.stream.expose(function (sink, me, name) { expect(circuit.set). to.have.been.calledOnce. to.have.been.calledWithExactly(name, field.observable()); done(); }); var circuit = { set: sinon.spy() }; field.observable().subscribe(_.noop); field.start({}, 'propName', circuit); }); }); describe("Bacon.Circuit.Field.stream.method", function () { it("assigns function to circuit upon setup", function (done) { var field = Bacon.Circuit.Field.stream.method(); field.observable().subscribe(_.noop); field.start({}, 'propName', { set: function (name, fn) { expect(fn).to.be.a.function; expect(fn()).to.be.undefined; done(); } }); }); it("returns a promise with event value from a function call if a promise constructor is provided", function (done) { var field = Bacon.Circuit.Field.stream.method(); field.observable().subscribe(_.noop); field.start({}, 'propName', { set: function (name, fn) { assert(Q.isPromise(fn())); done(); }, promiseConstructor: Q.Promise }); }); it("captures every invocation of the function as a stream event", function (done) { var invoke; var field = Bacon.Circuit.Field.stream.method(); field.observable().onValue(function (value) { expect(value).to.be.arguments; expect(_.toArray(value)).to.deep.equal([1, 2, 3]); done(); }); expect(invoke).to.be.undefined; field.start({}, 'propName', { set: function (name, fn) { invoke = fn; } }); expect(invoke).to.be.a.function; invoke(1, 2, 3); }); it("can amend the result of the invocation using a `flatMapLatest` operation", function (done) { var onValue = sinon.spy(); var field = Bacon.Circuit.Field.stream.method(function () { var args = _.toArray(arguments); return Bacon.once(args).startWith(args.map(function (n) { return n * n; })).map(function (numbers) { return numbers.reduce(function (sum, plus) { return sum + plus; }, 0); }); }); field.observable().onValue(function () { onValue.apply(this, arguments); if (onValue.calledTwice) { expect(onValue.firstCall).to.have.been.calledWithExactly(1*1 + 2*2 + 3*3); expect(onValue.secondCall).to.have.been.calledWithExactly(1 + 2 + 3); done(); } }); field.start({}, 'propName', { set: function (name, fn) { fn(1, 2, 3); } }); }); it("resolves promise with first event after invocation", function (done) { var field = Bacon.Circuit.Field.stream.method(function (arg) { return Bacon.fromArray([arg, !arg]); }); field.observable().subscribe(_.noop); field.start({}, 'propName', { set: function (name, fn) { fn(true).done(function (value) { expect(value).to.be.true; done(); }); }, promiseConstructor: Q.Promise }); }); it("drops pending events as soon as new (synchronous) invocation event comes in", function (done) { var onValue = sinon.spy(); var field = Bacon.Circuit.Field.stream.method(function (value) { if (value === 1) return Bacon.later(50, 'drop me').startWith(value); return Bacon.once(value); }); field.observable().onValue(onValue); field.start({}, 'propName', { set: function (name, fn) { fn(1); fn(2); _.delay(fn, 100, 3); } }); _.delay(function () { expect(onValue.firstCall).to.have.been.calledWithExactly(1); expect(onValue.secondCall).to.have.been.calledWithExactly(2); expect(onValue.thirdCall).to.have.been.calledWithExactly(3); done(); }, 200); }); it("drops pending events as soon as new (asynchronous) invocation event comes in", function (done) { var onValue = sinon.spy(); var field = Bacon.Circuit.Field.stream.method(function (value) { if (value === 1) return Bacon.later(50, 'drop me').startWith(value); return Bacon.once(value); }); field.observable().onValue(onValue); field.start({}, 'propName', { set: function (name, fn) { fn(1); _.delay(fn, 0, 2); _.delay(fn, 100, 3); } }); _.delay(function () { expect(onValue.firstCall).to.have.been.calledWithExactly(1); expect(onValue.secondCall).to.have.been.calledWithExactly(2); expect(onValue.thirdCall).to.have.been.calledWithExactly(3); done(); }, 200); }); it("drops pending events as soon as the actual invocation takes place, rather than the resulting event (if any)", function (done) { var onValue = sinon.spy(); var field = Bacon.Circuit.Field.stream.method(function (value) { if (value === 1) return Bacon.later(200, 'drop me').startWith(value); if (value === 2) return Bacon.never(); return Bacon.once(value); }); field.observable().onValue(onValue); field.start({}, 'propName', { set: function (name, fn) { fn(1); _.delay(fn, 50, 2); _.delay(fn, 400, 3); } }); _.delay(function () { expect(onValue.firstCall).to.have.been.calledWithExactly(1); expect(onValue.secondCall).to.have.been.calledWithExactly(3); done(); }, 500); }); }); describe("Bacon.Circuit.Field.property.digest", function () { it("assigns every value of property observable to circuit", function (done) { var onSet = sinon.spy(); var field = Bacon.Circuit.Field.property.digest(function () { return Bacon.once(2).startWith(1); }); field.observable().onValue(function (value) { if (value < 2) return; expect(onSet.firstCall).to.have.been.calledWithExactly('propName', 1); expect(onSet.secondCall).to.have.been.calledWithExactly('propName', 2); done(); }); field.start({}, 'propName', { set: onSet }); }); it("digests sunk values just as values from returned streams", function (done) { var field = Bacon.Circuit.Field.property.digest(function (sink) { sink(1); }); field.observable().onValue(_.noop); field.start({}, 'propName', { set: function (key, value) { expect(value).to.equal(1); done(); } }); }); }); describe("Bacon.Circuit.Field.property.watch", function () { it("watches and reports changes of value on circuit", function (done) { var field = Bacon.Circuit.Field.property.watch(); field.observable().onValue(function (value) { expect(value).to.equal(1); done(); }); field.start({}, 'propName', { watch: function (name, cb) { cb(1); }, set: function () {} }); }); it("will merge the provided observable with (and before) the watch stream", function (done) { var onSet = sinon.spy(); var field = Bacon.Circuit.Field.property.watch(function () { return Bacon.once(1); }); field.observable().onValue(function (value) { if (value < 2) return; expect(onSet.firstCall).to.have.been.calledWithExactly('propName', 1); expect(onSet.secondCall).to.have.been.calledWithExactly('propName', 2); done(); }); field.start({}, 'propName', { watch: function (name, cb) { cb(2); }, set: onSet }); }); it("calls merge with sink callback, observable and the parameters that were provided upon start", function (done) { var a = {}, b = 'b', c = { watch: _.noop, set: _.noop }; var field = Bacon.Circuit.Field.property.watch(function (sink, me, name, circuit) { expect(this).to.equal(a); expect(sink).to.be.a('function'); expect(me).to.equal(field.observable()); expect(name).to.equal(b); expect(circuit).to.equal(c); done(); }); field.observable().subscribe(_.noop); field.start(a, b, c); }); it("will merge sunk values with (and before) the watch stream", function (done) { var onSet = sinon.spy(); var field = Bacon.Circuit.Field.property.watch(function (sink) { sink(1); }); field.observable().onValue(function (value) { if (value < 2) return; expect(onSet.firstCall).to.have.been.calledWithExactly('propName', 1); expect(onSet.secondCall).to.have.been.calledWithExactly('propName', 2); done(); }); field.start({}, 'propName', { watch: function (name, cb) { cb(2); }, set: onSet }); }); it("assigns every value of property observable to circuit", function (done) { var onSet = sinon.spy(); var field = Bacon.Circuit.Field.property.watch(function () { return Bacon.once(2).startWith(1); }); field.observable().onValue(function (value) { if (value < 3) return; expect(onSet.firstCall).to.have.been.calledWithExactly('propName', 1); expect(onSet.secondCall).to.have.been.calledWithExactly('propName', 2); expect(onSet.thirdCall).to.have.been.calledWithExactly('propName', 3); done(); }); field.start({}, 'propName', { watch: function (name, cb) { cb(3); }, set: onSet }); }); it("will only issue events for actual value changes", function (done) { var onValue = sinon.spy(); var o1 = {}, o2 = {}; var field = Bacon.Circuit.Field.property.watch(function (sink) { sink(undefined); return Bacon.once(1).startWith(undefined); }); field.observable().onValue(function () { onValue.apply(this, arguments); if (onValue.callCount === 6) { expect(onValue.getCall(0)).to.have.been.calledWithExactly(undefined); expect(onValue.getCall(1)).to.have.been.calledWithExactly(1); expect(onValue.getCall(2)).to.have.been.calledWithExactly(2); expect(onValue.getCall(3)).to.have.been.calledWithExactly(undefined); expect(onValue.getCall(4)).to.have.been.calledWithExactly(o1); expect(onValue.getCall(5)).to.have.been.calledWithExactly(o2); done(); } }); field.start({}, 'propName', { watch: function (name, cb) { cb(1); cb(1); cb(2); cb(undefined); cb(o1); cb(o2); }, set: function () {} }); }); });