Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add fast failing to all remaining curried functions #69

Merged
merged 1 commit into from
Feb 13, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
349 changes: 222 additions & 127 deletions fluture.js

Large diffs are not rendered by default.

53 changes: 39 additions & 14 deletions test/1.future.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -281,19 +281,31 @@ describe('Future', () => {
expect(Future.fork(U.noop, U.noop)).to.be.a('function');
});

it('throws when not given a Future', () => {
const f = () => Future.fork(U.noop)(U.noop)(1);
expect(f).to.throw(TypeError, /Future/);
it('throws when not given a Function as first argument', () => {
const f = () => Future.fork(1);
expect(f).to.throw(TypeError, /Future.*first/);
});

it('throws when not given a Function as second argument', () => {
const f = () => Future.fork(U.add(1), 1);
expect(f).to.throw(TypeError, /Future.*second/);
});

it('dispatches to #fork()', done => {
it('throws when not given a Future as third argument', () => {
const f = () => Future.fork(U.add(1), U.add(1), 1);
expect(f).to.throw(TypeError, /Future.*third/);
});

it('dispatches to #_f()', done => {
const a = () => {};
const b = () => {};
Future.fork(a, b, {fork: (x, y) => {
const mock = Object.create(F.mock);
mock._f = (x, y) => {
expect(x).to.equal(a);
expect(y).to.equal(b);
done();
}});
};
Future.fork(a, b, mock);
});

});
Expand All @@ -306,17 +318,24 @@ describe('Future', () => {
expect(Future.value(U.noop)).to.be.a('function');
});

it('throws when not given a Future', () => {
const f = () => Future.value(U.noop)(1);
expect(f).to.throw(TypeError, /Future/);
it('throws when not given a Function as first argument', () => {
const f = () => Future.value(1);
expect(f).to.throw(TypeError, /Future.*first/);
});

it('throws when not given a Future as second argument', () => {
const f = () => Future.value(U.add(1), 1);
expect(f).to.throw(TypeError, /Future.*second/);
});

it('dispatches to #value()', done => {
const a = () => {};
Future.value(a, {value: x => {
const mock = Object.create(F.mock);
mock.value = x => {
expect(x).to.equal(a);
done();
}});
};
Future.value(a, mock);
});

});
Expand All @@ -329,7 +348,9 @@ describe('Future', () => {
});

it('dispatches to #promise', done => {
Future.promise({promise: done});
const mock = Object.create(F.mock);
mock.promise = done;
Future.promise(mock);
});

});
Expand All @@ -342,7 +363,9 @@ describe('Future', () => {
});

it('dispatches to #extractLeft', done => {
Future.extractLeft({extractLeft: done});
const mock = Object.create(F.mock);
mock.extractLeft = done;
Future.extractLeft(mock);
});

});
Expand All @@ -355,7 +378,9 @@ describe('Future', () => {
});

it('dispatches to #extractRight', done => {
Future.extractRight({extractRight: done});
const mock = Object.create(F.mock);
mock.extractRight = done;
Future.extractRight(mock);
});

});
Expand Down
110 changes: 51 additions & 59 deletions test/5.future-and.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,113 +2,65 @@

const expect = require('chai').expect;
const Future = require('../fluture.js');
const FutureAnd = Future.classes.FutureAnd;
const U = require('./util');
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer this new way of testing, because it tests behavior rather than implementation.

const F = require('./futures');

describe('Future.and()', () => {

it('is a curried binary function', () => {
expect(Future.and).to.be.a('function');
expect(Future.and.length).to.equal(2);
expect(Future.and(F.resolved)).to.be.a('function');
});

it('throws when not given a Future as first argument', () => {
const f = () => Future.and(1);
expect(f).to.throw(TypeError, /Future.*first/);
});

it('throws when not given a Future as second argument', () => {
const f = () => Future.and(Future.of(1), 1);
expect(f).to.throw(TypeError, /Future.*second/);
});

it('returns an instance of FutureAnd', () => {
expect(Future.and(F.resolved, F.resolved)).to.be.an.instanceof(FutureAnd);
});

});

describe('Future#and()', () => {

it('throws when invoked out of context', () => {
const f = () => Future.of(1).and.call(null, Future.of(1));
expect(f).to.throw(TypeError, /Future/);
});

it('throw TypeError when not given a Future', () => {
const xs = [NaN, {}, [], 1, 'a', new Date, undefined, null, x => x];
const fs = xs.map(x => () => Future.of(1).and(x));
fs.forEach(f => expect(f).to.throw(TypeError, /Future/));
});

it('returns an instance of FutureAnd', () => {
expect(F.resolved.and(F.resolved)).to.be.an.instanceof(FutureAnd);
});

});

describe('FutureAnd', () => {

it('extends Future', () => {
expect(new FutureAnd).to.be.an.instanceof(Future);
});
const testInstance = and => {

describe('#fork()', () => {

describe('(res, res)', () => {

it('resolves with right if left resolves first', () => {
return U.assertResolved(F.resolved.and(F.resolvedSlow), 'resolvedSlow');
return U.assertResolved(and(F.resolved, F.resolvedSlow), 'resolvedSlow');
});

it('resolves with right if left resolves last', () => {
return U.assertResolved(F.resolvedSlow.and(F.resolved), 'resolved');
return U.assertResolved(and(F.resolvedSlow, F.resolved), 'resolved');
});

});

describe('(rej, rej)', () => {

it('rejects with left if right rejects first', () => {
return U.assertRejected(F.rejectedSlow.and(F.rejected), 'rejectedSlow');
return U.assertRejected(and(F.rejectedSlow, F.rejected), 'rejectedSlow');
});

it('rejects with left if right rejects last', () => {
return U.assertRejected(F.rejected.and(F.rejectedSlow), 'rejected');
return U.assertRejected(and(F.rejected, F.rejectedSlow), 'rejected');
});

});

describe('(rej, res)', () => {

it('rejects with left if right resolves first', () => {
return U.assertRejected(F.rejectedSlow.and(F.resolved), 'rejectedSlow');
return U.assertRejected(and(F.rejectedSlow, F.resolved), 'rejectedSlow');
});

it('rejects with left if right resolves last', () => {
return U.assertRejected(F.rejected.and(F.resolvedSlow), 'rejected');
return U.assertRejected(and(F.rejected, F.resolvedSlow), 'rejected');
});

});

describe('(res, rej)', () => {

it('rejects with right if left resolves first', () => {
return U.assertRejected(F.resolved.and(F.rejectedSlow), 'rejectedSlow');
return U.assertRejected(and(F.resolved, F.rejectedSlow), 'rejectedSlow');
});

it('rejects with right if left resolves last', () => {
return U.assertRejected(F.resolvedSlow.and(F.rejected), 'rejected');
return U.assertRejected(and(F.resolvedSlow, F.rejected), 'rejected');
});

});

it('creates a cancel function which cancels both Futures', done => {
let cancelled = false;
const m = Future(() => () => (cancelled ? done() : (cancelled = true)));
const cancel = m.and(m).fork(U.noop, U.noop);
const cancel = and(m, m).fork(U.noop, U.noop);
cancel();
});

Expand All @@ -117,11 +69,51 @@ describe('FutureAnd', () => {
describe('#toString()', () => {

it('returns the code to create the FutureAnd', () => {
const m = Future.of(1).and(Future.of(2));
const m = and(Future.of(1), Future.of(2));
const s = 'Future.of(1).and(Future.of(2))';
expect(m.toString()).to.equal(s);
});

});

};

describe('Future.and()', () => {

it('is a curried binary function', () => {
expect(Future.and).to.be.a('function');
expect(Future.and.length).to.equal(2);
expect(Future.and(F.resolved)).to.be.a('function');
});

it('throws when not given a Future as first argument', () => {
const f = () => Future.and(1);
expect(f).to.throw(TypeError, /Future.*first/);
});

it('throws when not given a Future as second argument', () => {
const f = () => Future.and(Future.of(1), 1);
expect(f).to.throw(TypeError, /Future.*second/);
});

//TODO: Argument order.
testInstance((a, b) => Future.and(a, b));
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Future.and was one of the functions I implemented without a createDispatcher from the start, and I made the same mistake there that caused all this mess now. Essentially the argument order of Future.and is the wrong way around (static functions should be flipped). If I'd fix that now, it would be a breaking change, hence the #TODO.


});

describe('Future#and()', () => {

it('throws when invoked out of context', () => {
const f = () => Future.of(1).and.call(null, Future.of(1));
expect(f).to.throw(TypeError, /Future/);
});

it('throw TypeError when not given a Future', () => {
const xs = [NaN, {}, [], 1, 'a', new Date, undefined, null, x => x];
const fs = xs.map(x => () => Future.of(1).and(x));
fs.forEach(f => expect(f).to.throw(TypeError, /Future/));
});

testInstance((a, b) => a.and(b));

});
Loading