flyd
Version:
The less is more, modular, functional reactive programming library
1,078 lines (1,027 loc) • 31.7 kB
JavaScript
var assert = require('assert');
var Promise = require('bluebird');
var R = require('ramda');
var t = require('transducers.js');
var flyd = require('../lib');
var stream = flyd.stream;
var combine = flyd.combine;
var map = flyd.map;
var ap = flyd.ap;
var chain = flyd.chain;
// Some combinators
function doubleFn(x) { return x() * 2; }
function sumFn(x, y) { return x() + y(); }
function identityLift(x) { return x(); }
describe('stream', function() {
it('can be set with initial value', function() {
var s = stream(12);
assert.equal(s(), 12);
});
it('can be set', function() {
var s = stream();
s(23);
assert.equal(s(), 23);
s(3);
assert.equal(s(), 3);
});
it('setting a stream returns the stream', function() {
var s = stream();
assert.equal(s, s(23));
});
it('can works with JSON.stringify', function() {
var obj = {
num: stream(23),
str: stream('string'),
obj: stream({ is_object: true })
};
var expected_outcome = {
num: 23,
str: 'string',
obj: {
is_object: true
}
};
var jsonObject = JSON.parse(JSON.stringify(obj));
assert.deepEqual(jsonObject, expected_outcome);
});
it("let's explicit `undefined` flow down streams", function() {
var result = [];
var s1 = stream(undefined);
flyd.map(function(v) { result.push(v); }, s1);
s1(2)(undefined);
assert.deepEqual(result, [undefined, 2, undefined]);
});
it('handles a null floating down the stream', function() {
stream()(null);
});
it('can typecheck', function() {
var s1 = stream();
var s2 = stream(null);
var s3 = stream();
var f = function() { };
assert(flyd.isStream(s1));
assert(flyd.isStream(s2));
assert(flyd.isStream(s3));
assert(!flyd.isStream(f));
});
it('has pretty string representation', function() {
var ns = stream(1);
var ss = stream('hello');
var os = stream({});
assert.deepEqual('' + ns, 'stream(1)');
assert.deepEqual('' + ss, 'stream(hello)');
assert.deepEqual('' + os, 'stream([object Object])');
});
describe('dependent streams', function() {
it('updates dependencies', function() {
var x = stream(3);
var x2 = combine(doubleFn, [x]);
assert.equal(x2(), x() * 2);
});
it('can set result by returning value', function() {
var x = stream(3);
var y = stream(4);
var sum = combine(sumFn, [x, y]);
assert.equal(sum(), x() + y());
});
it('is updated when dependencies change', function() {
var x = stream(3);
var y = stream(4);
var sum = combine(sumFn, [x, y]);
assert.equal(sum(), x() + y()); // 7
x(12);
assert.equal(sum(), x() + y()); // 16
y(8);
assert.equal(sum(), x() + y()); // 20
});
it('can set result by calling callback', function() {
var x = stream(3);
var y = stream(4);
var times = 0;
var sum = combine(sumFn, [x, y]);
combine(function() {
times++;
}, [sum]);
assert.equal(sum(), x() + y()); // 7
x(12);
assert.equal(sum(), x() + y()); // 16
y(8);
assert.equal(sum(), x() + y()); // 20
assert.equal(times, 3);
});
it('is not called until dependencies have value', function() {
var x = stream();
var y = stream();
var called = 0;
combine(function(x, y) {
called++;
return x() + y();
}, [x, y]);
x(2); x(1); y(2); y(4); x(2);
assert.equal(called, 3);
});
it('streams can lead into other streams', function() {
var x = stream(3);
var y = stream(4);
var sum = combine(sumFn, [x, y]);
var twiceSum = combine(doubleFn, [sum]);
var sumPlusDoubleSum = combine(sumFn, [twiceSum, sum]);
x(12);
assert.equal(sumPlusDoubleSum(), sum() * 3);
y(3);
assert.equal(sumPlusDoubleSum(), sum() * 3);
x(2);
assert.equal(sumPlusDoubleSum(), sum() * 3);
assert.equal(sumPlusDoubleSum(), (2 + 3) * 3);
});
it('can get its own value', function() {
var num = stream(0);
var sum = combine(function(num, self) {
return (self() || 0) + num();
}, [num]);
num(2)(3)(8)(7);
assert.equal(sum(), 20);
});
it('is called with changed streams', function() {
var s1 = stream(0);
var s2 = stream(0);
var result = [];
combine(function(s1, s2, self, changed) {
if (changed[0] === s1) result.push(1);
if (changed[0] === s2) result.push(2);
}, [s1, s2]);
s1(1);
s2(1);
s2(1);
s1(1);
s2(1);
s1(1);
assert.deepEqual(result, [1, 2, 2, 1, 2, 1]);
});
it('handles dependencies when streams are triggered in streams', function() {
var x = stream(4);
var y = stream(3);
var z = stream(1);
var doubleX = combine(doubleFn, [x]);
var setAndSum = combine(function(y, z) {
x(3);
return z() + y();
}, [y, z]);
z(4);
assert.equal(setAndSum(), 7);
assert.equal(doubleX(), 6);
});
it('executes to the end before handlers are triggered', function() {
var order = [];
var x = stream(4);
var y = stream(3);
combine(function dx(x) {
if (x() === 3) order.push(2);
return x() * 2;
}, [x]);
combine(function sy(y) {
x(3);
order.push(1);
return y();
}, [y]);
assert.deepEqual(order, [1, 2]);
});
it('with static deps executes to the end', function() {
var order = [];
var x = stream(4);
var y = stream(3);
combine(function(x) {
if (x() === 3) order.push(2);
return x() * 2;
}, [x]);
combine(function(y) {
x(3);
order.push(1);
return y();
}, [y]);
assert.equal(order[0], 1);
assert.equal(order[1], 2);
});
it('can filter values', function() {
var result = [];
var n = stream(0);
var lrg5 = combine(function(n) {
if (n() > 5) return n();
}, [n]);
flyd.map(function(v) { result.push(v); }, lrg5);
n(4)(6)(2)(8)(3)(4);
assert.deepEqual(result, [6, 8]);
});
it('can set another stream\'s value multiple times from inside a stream', function() {
var result = [];
var a = stream();
var b = stream();
combine(function(b) {
a(b());
a();
a(b() + 1);
assert.equal(a(), 2);
}, [b]);
combine(function(a) {
result.push(a());
}, [a]);
b(1);
assert.deepEqual(result, [1, 2]);
});
it('can combine streams and project deps as args', function() {
var a = flyd.stream();
var b = flyd.stream(0);
var collect = function(x, y, self) { return (self() || []).concat([x(), y()]); };
var history = flyd.combine(collect, [a, b]);
a(1)(2); // [1, 0, 2, 0]
b(3); // [1, 0, 2, 0, 2, 3]
a(4); // [1, 0, 2, 0, 2, 3, 4, 3]
assert.deepEqual(history(), [
1, 0, 2, 0, 2, 3, 4, 3
]);
});
});
describe('streams created within dependent stream bodies', function() {
it('if dependencies are met it is updated eventually', function() {
var result;
stream(1).pipe(map(function() {
var n = flyd.stream(1);
n.pipe(map(function(v) { result = v + 100; }));
}));
assert.equal(result, 101);
});
it('if dependencies are not met at creation it is updated after their dependencies are met', function() {
var result;
stream(1).pipe(map(function() {
var n = stream();
n.pipe(map(function(v) { result = v + 100; }));
n(1);
}));
assert.equal(result, 101);
});
it('if a streams end stream is called it takes effect immediately', function() {
var result = undefined;
stream(1).pipe(map(function() {
var n = stream();
n.pipe(map(function(v) { result = v + 100; }));
n.end(true);
n(1);
n(2);
}));
assert.equal(result, undefined);
});
it('can create multi-level dependent streams inside a stream body', function() {
var result = 0;
var externalStream = stream(0);
function mapper(val) {
++result;
return val + 1;
}
stream(1).map(function() {
externalStream
.map(mapper)
.map(mapper);
return;
});
assert.equal(result, 2);
});
it('can create multi-level dependent streams inside a stream body part 2', function() {
var result = '';
var externalStream = stream(0);
var theStream = stream(1);
function mapper(val) {
result += '' + val;
return val + 1;
}
theStream.map(function() {
externalStream
.map(mapper)
.map(mapper);
return;
});
theStream(1);
assert.equal(result, '0101');
});
});
describe('ending a stream', function() {
it('works for streams without dependencies', function() {
var s = stream(1);
s.end(true);
assert(s.end());
assert(s.end());
});
it('detaches it from dependencies', function() {
var x = stream(3);
var y = stream(2);
var sum = combine(function(x, y) {
return y() * x();
}, [x, y]);
assert.equal(y.listeners.length, 1);
assert.equal(x.listeners.length, 1);
sum.end(true);
assert.equal(y.listeners.length, 0);
assert.equal(x.listeners.length, 0);
assert(sum.end());
});
it('ends its dependents', function() {
var x = stream(3);
var y = combine(doubleFn, [x]);
var z = combine(doubleFn, [y]);
assert.equal(z(), x() * 2 * 2);
x.end(true);
assert(x.end());
assert.equal(x.listeners.length, 0);
assert(y.end());
assert.equal(y.listeners.length, 0);
assert(z.end());
});
it('updates children if stream ends after recieving value', function() {
var x = stream(3);
var whenX2 = combine(function(x) {
if (x() === 0) {
return true;
}
}, [x]);
var y = combine(identityLift, [x]);
flyd.endsOn(whenX2, y);
var z = combine(identityLift, [y]);
assert.equal(y(), z());
x(2);
assert.equal(y(), z());
assert(!y.end());
assert(!z.end());
x(0);
assert.equal(x.listeners.length, 1);
assert(y.end());
assert.equal(y.listeners.length, 0);
assert(z.end());
assert.equal(2, y());
assert.equal(2, z());
});
it('works if end stream has initial value', function() {
var killer = stream(true);
var x = stream(1);
var y = flyd.endsOn(killer, combine(doubleFn, [x]));
x(2);
assert.equal(undefined, y.end());
assert.equal(2 * x(), y());
});
it('end stream does not have value even if base stream has initial value', function() {
var killer = stream(true);
var x = stream(1);
var y = flyd.endsOn(killer, combine(doubleFn, [x]));
assert.equal(false, y.end.hasVal);
});
it('ends stream can be changed without affecting listeners', function() {
var killer1 = stream();
var killer2 = stream();
var ended = false;
var x = stream(1);
var y = flyd.endsOn(killer1, combine(doubleFn, [x]));
flyd.map(function() {
ended = true;
}, y.end);
flyd.endsOn(killer2, y);
killer2(true);
assert(ended);
});
it('end stream can be set on top level stream', function() {
var killer = stream();
var s = flyd.endsOn(killer, stream(1));
assert.notEqual(s.end(), true);
killer(true);
assert.equal(s.end(), true);
});
});
describe('Promises', function() {
describe('fromPromise', function() {
it('pushes result of promise down the stream', function(done) {
var s = flyd.fromPromise(Promise.resolve(12));
combine(function(s) {
assert.equal(s(), 12);
done();
}, [s]);
});
it('recursively unpacks promise', function(done) {
var s = flyd.fromPromise(new Promise(function(res) {
setTimeout(function() {
res(new Promise(function(res) {
setTimeout(res.bind(null, 12));
}));
}, 20);
}));
combine(function(s) {
assert.equal(s(), 12);
done();
}, [s]);
});
it('does not process out of order promises', function(done) {
var promises = [];
var delay = function(ms, val) {
var p = new Promise(function(res) {
setTimeout(function() {
res(val);
}, ms)
});
promises.push(p);
return p;
};
var s = stream();
var res = s.chain(function(val) {
return flyd.fromPromise(delay(val, val));
})
.pipe(flyd.scan(function(acc, v) {
return acc + v;
}, 0));
s(100)(50)(70)(200);
Promise.all(promises).then(function() {
assert.equal(res(), 200);
done();
});
});
});
describe('flattenPromise', function() {
it('processes out of order promises', function(done) {
var promises = [];
var delay = function(ms, val) {
var p = new Promise(function(res) {
setTimeout(function() {
res(val);
}, ms)
});
promises.push(p);
return p;
};
var s = stream();
var res = s.map(function(val) {
return delay(val, val);
})
.pipe(flyd.flattenPromise)
.pipe(flyd.scan(function(acc, v) {
return acc + v;
}, 0));
s(100)(50)(70)(200);
Promise.all(promises).then(function() {
assert.equal(res(), 420);
done();
});
});
});
});
describe('on', function() {
it('is invoked when stream changes', function() {
var s = flyd.stream();
var result = [];
var f = function(val) { result.push(val); };
flyd.on(f, s);
s(1)(2);
assert.deepEqual(result, [1, 2]);
});
});
describe('map', function() {
it('maps a function', function() {
var x = stream(3);
var doubleX = x.pipe(map(function(x) { return 2 * x; }));
assert.equal(doubleX(), 6);
x(1);
assert.equal(doubleX(), 2);
});
it('maps a function', function() {
var x = stream(3);
var doubleX = flyd.map(function(x) { return 2 * x; }, x);
assert.equal(doubleX(), 6);
x(1);
assert.equal(doubleX(), 2);
});
it('handles function returning undefined', function() {
var x = stream(1);
var maybeDoubleX = flyd.map(function(x) {
return x > 3 ? 2 * x : undefined;
}, x);
assert.equal(undefined, maybeDoubleX());
assert.equal(true, maybeDoubleX.hasVal);
x(4);
assert.equal(8, maybeDoubleX());
});
it('is curried', function() {
var x = stream(3);
var doubler = flyd.map(function(x) { return 2 * x; });
var quadroX = doubler(doubler(x));
assert.equal(quadroX(), 12);
x(2);
assert.equal(quadroX(), 8);
});
it('returns equivalent stream when mapping identity', function() {
var x = stream(3);
var x2 = x.pipe(map(function(a) { return a; }));
assert.equal(x2(), x());
x('foo');
assert.equal(x2(), x());
});
it('is compositive', function() {
function f(x) { return x * 2; }
function g(x) { return x + 4; }
var x = stream(3);
var s1 = x.pipe(map(g)).pipe(map(f));
var s2 = x.pipe(map(function(x) { return f(g(x)); }));
assert.equal(s1(), s2());
x(12);
assert.equal(s1(), s2());
});
});
describe('chain', function() {
it('applies function to values in stream', function() {
var result = [];
function f(v) {
result.push(v);
return stream();
}
var s = stream();
flyd.chain(f, s);
s(1)(2)(3)(4)(5);
assert.deepEqual(result, [1, 2, 3, 4, 5]);
});
it('returns stream with result from all streams created by function', function() {
var result = [];
function f(v) {
var s = stream();
setImmediate(function() {
s(v + 1)(v + 2)(v + 3);
});
return s;
}
var s = stream();
flyd.map(function(v) {
result.push(v);
}, flyd.chain(f, s));
s(1)(3)(5);
setImmediate(function() {
assert.deepEqual(result, [2, 3, 4,
4, 5, 6,
6, 7, 8]);
});
});
it('passed bug outlined in https://github.com/paldepind/flyd/issues/31', function(done) {
function delay(val, ms) {
var outStream = flyd.stream();
setTimeout(function() {
outStream(val);
outStream.end(true);
}, ms);
return outStream;
}
var main = delay(1, 500);
var merged = flyd.chain(function(v) {
return delay(v, 1000)
}, main);
flyd.on(function() {
assert(main() === 1);
assert(merged() === 1);
done();
}, merged.end);
});
it('preserves ordering', function() {
function delay(val, ms) {
var outStream = flyd.stream();
setTimeout(function() {
outStream(val);
outStream.end(true);
}, ms);
return outStream;
}
var s = stream();
var s2 = s
.pipe(chain(function(val) {
return delay(val, 100);
}));
s(1)(2)(3)(4);
flyd.on(function(val) {
assert.equal(val, 4);
}, s2)
});
});
describe('scan', function() {
it('has initial acc as value when stream is undefined', function() {
var numbers = stream();
var sum = flyd.scan(function(sum, n) {
return sum + n;
}, 0, numbers);
assert.equal(sum(), 0);
});
it('can sum streams of integers', function() {
var numbers = stream();
var sum = flyd.scan(function(sum, n) {
return sum + n;
}, 0, numbers);
numbers(3)(2)(4)(10);
assert.equal(sum(), 19);
});
it('is curried', function() {
var numbers = stream();
var sumStream = flyd.scan(function(sum, n) {
return sum + n;
}, 0);
var sum = sumStream(numbers);
numbers(3)(2)(4)(10);
assert.equal(sum(), 19);
});
it('passes undefined', function() {
var x = stream();
var scan = flyd.scan(function(acc, x) {
return acc.concat([x]);
}, [], x);
x(1)(2)(undefined)(3)(4);
assert.deepEqual(scan(), [1, 2, undefined, 3, 4]);
});
});
describe('merge', function() {
it('can sum streams of integers', function() {
var result = [];
var s1 = stream();
var s2 = stream();
var merged = flyd.merge(s1, s2);
combine(function(merged) {
result.push(merged());
}, [merged]);
s1(12)(2); s2(4)(44); s1(1); s2(12)(2);
assert.deepEqual(result, [12, 2, 4, 44, 1, 12, 2]);
});
it('is curried', function() {
var result = [];
var s1 = stream();
var mergeWithS1 = flyd.merge(s1);
var s2 = stream();
var s1and2 = mergeWithS1(s2);
flyd.map(function(v) { result.push(v); }, s1and2);
s1(12)(2); s2(4)(44); s1(1); s2(12)(2);
assert.deepEqual(result, [12, 2, 4, 44, 1, 12, 2]);
});
it('should pass defined undefined along', function() {
var s1 = stream();
var s2 = stream(undefined);
var merged = flyd.merge(s1, s2);
assert.equal(merged(), undefined);
s1(25);
assert.equal(merged(), 25);
s1(undefined);
assert.equal(merged(), undefined);
s2(15);
assert.equal(merged(), 15);
});
it('should work for s1 being defined first', function() {
var s1 = stream(undefined);
var s2 = stream();
var merged = flyd.merge(s1, s2);
assert.equal(merged(), undefined);
s1(25);
assert.equal(merged(), 25);
});
it('ends only when both merged streams have ended', function() {
var result = [];
var s1 = stream();
var s2 = stream();
var s1and2 = flyd.merge(s1, s2);
flyd.map(function(v) { result.push(v); }, s1and2);
s1(12)(2); s2(4)(44); s1(1);
s1.end(true);
assert(!s1and2.end());
s2(12)(2);
s2.end(true);
assert(s1and2.end());
assert.deepEqual(result, [12, 2, 4, 44, 1, 12, 2]);
});
});
describe('ap', function() {
it('applies functions in stream', function() {
var a = stream(function(x) { return 2 * x; });
var v = stream(3);
var s = a.pipe(ap(v));
assert.equal(s(), 6);
a(function(x) { return x / 3; });
assert.equal(s(), 1);
v(9);
assert.equal(s(), 3);
});
it('is compositive', function() {
var a = stream(function(x) { return x * 2; });
var u = stream(function(x) { return x + 5; });
var v = stream(8);
var s1 = a
.pipe(map(function(f) {
return function(g) {
return function(x) {
return f(g(x));
};
};
}))
.pipe(ap(u))
.pipe(ap(v));
var s2 = a.pipe(ap(u.pipe(ap(v))));
assert.equal(s1(), 26);
assert.equal(s2(), 26);
a(function(x) { return x * 4; });
assert.equal(s1(), 52);
assert.equal(s2(), 52);
u(function(x) { return x / 8; });
assert.equal(s1(), 4);
assert.equal(s2(), 4);
v(24);
assert.equal(s1(), 12);
assert.equal(s2(), 12);
});
it('supports neat ap pattern', function() {
var result = [];
var sumThree = flyd.curryN(3, function(x, y, z) {
return x + y + z;
});
var s1 = stream(0);
var s2 = stream(0);
var s3 = stream(0);
var sum = flyd.map(sumThree, s1).pipe(ap(s2)).pipe(ap(s3));
flyd.map(function(v) { result.push(v); }, sum);
s1(3); s2(2); s3(5);
assert.deepEqual(result, [0, 3, 5, 10]);
});
it('applies functions if streams have no initial value', function() {
var result = [];
var add = flyd.curryN(2, function(x, y) { return x + y; });
var numbers1 = stream();
var numbers2 = stream();
var addToNumbers1 = flyd.map(add, numbers1);
var added = addToNumbers1.pipe(ap(numbers2));
flyd.map(function(n) { result.push(n); }, added);
numbers1(3); numbers2(2); numbers1(4);
assert.deepEqual(result, [5, 6]);
});
});
describe('of', function() {
it('can be accessed through the constructor property', function() {
var s1 = stream(2);
var s2 = s1.constructor.of(3);
var s3 = s2.constructor['fantasy-land/of'](3);
assert.equal(flyd.isStream(s2), true);
assert.equal(s2(), 3);
assert.equal(s3(), 3);
});
it('returns a stream with the passed value', function() {
var s1 = stream(2);
var s2 = s1.of(3);
assert.equal(s2(), 3);
});
it('has identity', function() {
var a = stream();
var id = function(a) { return a; };
var v = stream(12);
assert.equal(a.of(id).pipe(ap(v))(), v());
});
it('is homomorphic', function() {
var a = stream(0);
var f = function(x) { return 2 * x; };
var x = 12;
assert.equal(a.of(f).pipe(ap(a.of(x)))(), a.of(f(x))());
});
it('is interchangeable', function() {
var y = 7;
var a = stream();
var u = stream()(function(x) { return 3 * x; });
assert.equal(u.pipe(ap(a.of(y)))(),
a.of(function(f) { return f(y); }).pipe(ap(u))());
});
it('can create dependent stream inside stream', function() {
var one = flyd.stream();
combine(function(one, self) {
self(flyd.combine(function() { }, []));
}, [one]);
one(1);
});
it('can create immediate dependent stream inside stream', function() {
var one = flyd.stream();
combine(function(one, self) {
self(flyd.immediate(flyd.combine(function() { }, [])));
}, [one]);
one(1);
});
it('creating a stream inside a stream all dependencies are updated', function() {
var result = [];
var str = flyd.stream();
flyd.map(function(x) {
result.push(x);
}, str);
flyd.map(function() {
// create a stream, the first dependant on `str` should still be updated
flyd.combine(function() { }, []);
}, str);
str(1);
str(2);
str(3);
assert.deepEqual(result, [1, 2, 3]);
});
});
describe('transducer.js transducer support', function() {
it('creates new stream with map applied', function() {
var result = [];
var s1 = stream();
var tx = t.map(function(x) { return x * 3; });
var s2 = flyd.transduce(tx, s1);
combine(function(s2) { result.push(s2()); }, [s2]);
s1(1)(2)(4)(6);
assert.deepEqual(result, [3, 6, 12, 18]);
});
it('creates new stream with filter applied', function() {
var result = [];
var s1 = stream();
var tx = t.compose(
t.map(function(x) { return x * 3; }),
t.filter(function(x) { return x % 2 === 0; })
);
var s2 = flyd.transduce(tx, s1);
combine(function(s2) { result.push(s2()); }, [s2]);
s1(1)(2)(3)(4);
assert.deepEqual(result, [6, 12]);
});
it('supports dedupe', function() {
var result = [];
var s1 = stream();
var tx = t.compose(
t.map(function(x) { return x * 2; }),
t.dedupe()
);
var s2 = flyd.transduce(tx, s1);
combine(function(s2) { result.push(s2()); }, [s2]);
s1(1)(1)(2)(3)(3)(3)(4);
assert.deepEqual(result, [2, 4, 6, 8]);
});
it('handles reduced stream and ends', function() {
var result = [];
var s1 = stream();
var tx = t.compose(
t.map(function(x) { return x * 2; }),
t.take(3)
);
var s2 = flyd.transduce(tx, s1);
combine(function(s2) { result.push(s2()); }, [s2]);
s1(1)(2);
assert.notEqual(true, s2.end());
s1(3);
assert.equal(true, s2.end());
s1(4);
assert.deepEqual(result, [2, 4, 6]);
});
});
describe('Ramda transducer support', function() {
it('creates new stream with map applied', function() {
var result = [];
var s1 = stream();
var tx = R.map(function(x) { return x * 3; });
var s2 = flyd.transduce(tx, s1);
combine(function(s2) { result.push(s2()); }, [s2]);
s1(1)(2)(4)(6);
assert.deepEqual(result, [3, 6, 12, 18]);
});
it('creates new stream with filter applied', function() {
var result = [];
var s1 = stream();
var tx = R.pipe(
R.map(function(x) { return x * 3; }),
R.filter(function(x) { return x % 2 === 0; })
);
var s2 = flyd.transduce(tx, s1);
combine(function(s2) { result.push(s2()); }, [s2]);
s1(1)(2)(3)(4);
assert.deepEqual(result, [6, 12]);
});
it('filters empty elements', function() {
var result = [];
var s1 = stream();
var s2 = flyd.transduce(R.reject(R.isEmpty), s1);
flyd.map(function(v) { result.push(v); }, s2);
s1('foo')('')('bar')('')('')('!');
assert.deepEqual(result, ['foo', 'bar', '!']);
});
it('supports dedupe', function() {
var result = [];
var s1 = stream();
var tx = R.compose(
R.map(R.multiply(2)),
R.dropRepeats()
);
var s2 = flyd.transduce(tx, s1);
combine(function(s2) { result.push(s2()); }, [s2]);
s1(1)(1)(2)(3)(3)(3)(4);
assert.deepEqual(result, [2, 4, 6, 8]);
});
// @todo: Better transducer tests!!
});
describe('atomic updates', function() {
it('does atomic updates', function() {
var result = [];
var a = stream(1);
var b = combine(doubleFn, [a]);
var c = combine(function(a) { return a() + 4; }, [a]);
combine(function(b, c) {
result.push(b() + c());
}, [b, c]);
a(2);
assert.deepEqual(result, [7, 10]);
});
it('does not glitch', function() {
var result = [];
var s1 = stream(1);
var s1x2 = flyd.map(function(x) { return x * 2; }, s1);
var s2 = combine(sumFn, [s1, s1x2]);
var s1x4 = combine(sumFn, [s1, s2]);
flyd.map(function(n) { result.push(n); }, s1x4);
s1(2)(3)(4);
assert.deepEqual(result, [4, 8, 12, 16]);
});
it('handles complex dependency graph', function() {
var result = [];
var a = flyd.stream();
var b = flyd.combine(function(a) { return a() + 1; }, [a]);
var c = flyd.combine(function(a) { return a() + 2; }, [a]);
var d = flyd.combine(function(c) { return c() + 3; }, [c]);
var e = flyd.combine(function(b, d) {
return b() + d();
}, [b, d]);
flyd.map(function(v) { result.push(v); }, e);
a(1)(5)(11);
assert.deepEqual(result, [8, 16, 28]);
});
it('handles another complex dependency graph', function() {
var result = [];
var a = flyd.stream();
var b = flyd.combine(function(a) { return a() + 1; }, [a]);
var c = flyd.combine(function(a) { return a() + 2; }, [a]);
var d = flyd.combine(function(a) { return a() + 4; }, [a]);
var e = flyd.combine(function(b, c, d) { return b() + c() + d(); }, [b, c, d]);
flyd.map(function(v) { result.push(v); }, e);
a(1)(2)(3);
assert.deepEqual(result, [10, 13, 16]);
});
it('is called with all changed dependencies', function() {
var result = [];
var a = flyd.stream(0);
var b = flyd.combine(function(a) { return a() + 1; }, [a]);
var c = flyd.combine(function(a) { return a() + 2; }, [a]);
var d = flyd.stream(0);
var e = flyd.combine(function(d) { return d() + 4; }, [d]);
var f = flyd.combine(function(d) { return d() + 5; }, [d]);
var g = flyd.combine(function(d) { return d() + 6; }, [d]);
flyd.combine(function(a, b, c, d, e, f, g, self, changed) {
var vals = changed.map(function(s) { return s(); });
result.push(vals);
return 1;
}, [a, b, c, d, e, f, g]);
a(1); d(2); a(3);
assert.deepEqual(result, [
[], [1, 3, 2], [2, 8, 7, 6], [3, 5, 4]
]);
});
it('nested streams atomic update', function() {
var invocationCount = 0;
var mapper = function(val) {
invocationCount += 1;
return val + 1;
};
stream(1).map(function() {
stream(0)
.map(mapper)
.map(mapper);
});
assert.equal(invocationCount, 2);
});
});
describe('fantasy-land', function() {
it('map', function() {
var s = stream(1);
var mapped = R.map(R.add(3), s);
assert.equal(mapped(), 4);
assert.equal(s(), 1);
});
it('chain', function() {
var s = stream(1);
var chained = R.chain(R.compose(stream, R.add(3)), s);
assert.equal(chained(), 4);
assert.equal(s(), 1);
});
it('ap', function() {
var s = stream(R.add(3));
var val = stream(3);
var applied = R.ap(s, val);
assert.equal(applied(), 6);
});
it('old ap', function() {
var s = stream(R.add(3))
.ap(stream(3));
assert.equal(s(), 6);
});
it('of', function() {
var s = flyd.stream(3);
var s2 = s['fantasy-land/of'](5);
assert(flyd.isStream(s));
assert.equal(s(), 3);
assert(flyd.isStream(s2));
assert.equal(s2(), 5);
})
});
});