rafa
Version:
Rafa.js is a Javascript framework for building concurrent applications.
164 lines (140 loc) • 5.9 kB
JavaScript
module.exports = (assert, Rafa) => {
suite("flatMap", () => {
// basic test: flatMap should push values from the returned inner stream
// to the outer.flatMap's children. Done messages from the inner should
// be converted to normal messages for the outer.
test("pushes values from inner stream", done => {
var outer = Rafa.stream();
var values = [];
// create flatMap handler
outer.flatMap(value => {
var inner = Rafa.stream();
var vals = value.split(",");
// defer writing value and done messages to inner.
// messages are 'a' and 'b'
setTimeout(() => {
Rafa.message(vals[0]).push(inner);
Rafa.doneMessage(vals[1]).push(inner);
});
return inner;
})
.each(v => values.push(v)) // should catch both inner messages
.done(v => values.push('x'+v)); // should never be called
// init flatMap by pushing message to outer stream
Rafa.message("a,b").push(outer);
// defer to wait for inner messages.
// values should only have two items, 'a' and 'b'; they should not
// have been added by the done handler otherwise they would start with
// 'x'
setTimeout(() => {
assert.equal(values.length, 2);
assert.equal(values[0], "a");
assert.equal(values[1], "b");
done();
});
});
// error messages from the outer stream should pass through
test("errors from parent pass through", () => {
var stream = Rafa.stream();
var value;
stream.flatMap(Rafa.noop).error(err => value = err);
stream.write(new Error("foo"));
assert.equal(value.message, "foo");
});
// Parent steam must wait for the inner stream to complete before
// sending more messages through. Push a message, then check that the
// parent context is in a pending state until the inner stream completes.
test("parent push incomplete until child stream is done", done => {
var outer = Rafa.stream();
outer.flatMap(message => {
var inner = Rafa.stream();
// defer to allow testing the context.pending value
// send a done message to clear the context
setTimeout(() => inner.write(message.value + 1, true));
return inner;
});
// create a context to track, push a message to the outer stream which
// will create the inner stream and defer a write. The context should be
// pending until the inner stream send the done message.
var context = Rafa.context();
outer.push(context, Rafa.message(1));
assert.equal(context.pending, 1);
setTimeout(() => {
assert.equal(context.pending, 0);
done();
});
});
// If the inner stream returns a promise, then the outer stream's
// context should wait until the promise is fulfilled.
test("inner returns a promise", done => {
var outer = Rafa.stream();
var inner = Rafa.stream();
var future = Rafa.future();
var context = Rafa.context();
var values = [];
outer.flatMap(() => inner.map(v => future.promise))
.each(v => values.push(v));
// push a message to the outer stream.
// context should be pending the inner stream
outer.push(context, Rafa.message(1));
assert.equal(context.pending, 1);
// push a done message to the inner stream; inner creates a promise.
inner.write(2, true);
setTimeout(() => {
// resolving the promise should push the promise's value to the
// outer stream and into the values array. outer stream's context
// should no longer be pending.
future.resolve(3);
setTimeout(() => {
assert.equal(values[0], 3);
assert.equal(context.pending, 0);
done();
});
});
});
// downstream promise should force the inner and outer stream to wait
test("downstream promise", done => {
var outer = Rafa.stream();
var inner = Rafa.stream();
var outercontext = Rafa.context();
var innercontext = Rafa.context();
var future = Rafa.future();
outer.flatMap(v => inner.map(iv => v + iv))
.map(() => future.promise).each(Rafa.noop);
// push from outer creates inner, outercontext should be pending
// inner's done message
outer.push(outercontext, Rafa.message(1));
assert.equal(outercontext.pending, 1);
// push from inner goes to flatMap's child which then maps to a promise.
// inner's context should be pending the promise. outer's context
// is now pending the inner and promise.
inner.push(innercontext, Rafa.message(2));
assert.equal(innercontext.pending, 1);
assert.equal(outercontext.pending, 2);
// resolve the promise should reduce contexts: inner is done, outer
// is still pending inner. (need to recreate a promise to continue test)
future.resolve(3);
setTimeout(() => {
assert.equal(outercontext.pending, 1);
assert.equal(innercontext.pending, 0);
// reset
future = Rafa.future();
innercontext = Rafa.context();
// push done from inner goes to flatMap's child which then maps to a
// promise. inner and outer's context should be pending the promise.
// outer is no longer pending the inner because of the done message.
inner.push(innercontext, Rafa.doneMessage(2));
assert.equal(innercontext.pending, 1);
assert.equal(outercontext.pending, 1);
// resolve the promise should cause both the inner and outer context
// to be cleared.
future.resolve(3);
setTimeout(() => {
assert.equal(outercontext.pending, 0);
assert.equal(innercontext.pending, 0);
done();
});
});
});
});
};