1 | var R = require('ramda');
|
2 | var assert = require('assert');
|
3 | var equalsInvoker = require('./utils').equalsInvoker;
|
4 | var types = require('./types')(equalsInvoker);
|
5 | var Future = require('../src/Future');
|
6 |
|
7 | Future.prototype.equals = function(b) {
|
8 | this.fork(function(e1) {
|
9 | b.fork(function(e2) {
|
10 | assert.equal(e1, e2);
|
11 | }, function() {
|
12 | assert.fail(null, e1, 'Futures not equal: f1 failed, f2 did not', '===');
|
13 | });
|
14 | }, function(v1) {
|
15 | b.fork(function() {
|
16 | assert.fail(null, v1, 'Futures not equal: f1 succeeded, f2 did not', '===');
|
17 | }, function(v2) {
|
18 | assert.equal(v1, v2);
|
19 | });
|
20 | });
|
21 | return true;
|
22 | };
|
23 |
|
24 | describe('Future', function() {
|
25 |
|
26 | it('should equal another future', function() {
|
27 | var f1 = Future.of(2);
|
28 | var f2 = Future.of(2);
|
29 | assert.equal(true, f1.equals(f2));
|
30 | });
|
31 |
|
32 | it('is a Functor', function() {
|
33 | var fTest = types.functor;
|
34 | var f = Future.of(2);
|
35 | assert.equal(true, fTest.iface(f));
|
36 | assert.equal(true, fTest.id(f));
|
37 | assert.equal(true, fTest.compose(f, R.multiply(2), R.add(3)));
|
38 | });
|
39 |
|
40 | it('is an Apply', function() {
|
41 | var aTest = types.apply;
|
42 | var appA = Future.of(R.multiply(10));
|
43 | var appU = Future.of(R.add(5));
|
44 | var appV = Future.of(10);
|
45 | assert.equal(true, aTest.iface(appA));
|
46 | assert.equal(true, aTest.compose(appA, appU, appV));
|
47 | });
|
48 |
|
49 | it('is an Applicative', function() {
|
50 | var aTest = types.applicative;
|
51 | var app1 = Future.of(101);
|
52 | var app2 = Future.of(-123);
|
53 | var appF = Future.of(R.multiply(3));
|
54 |
|
55 | assert.equal(true, aTest.iface(app1));
|
56 | assert.equal(true, aTest.id(app1, app2));
|
57 | assert.equal(true, aTest.homomorphic(app1, R.add(3), 46));
|
58 | assert.equal(true, aTest.interchange(app1, appF, 17));
|
59 | });
|
60 |
|
61 | it('is a Chain', function() {
|
62 | var cTest = types.chain;
|
63 | var f = Future.of(2);
|
64 | var f1 = function(x) {return Future.of((3 * x));};
|
65 | var f2 = function(x) {return Future.of((5 + x));};
|
66 |
|
67 | assert.equal(true, cTest.iface(f));
|
68 | assert.equal(true, cTest.associative(f, f1, f2));
|
69 | });
|
70 |
|
71 | it('is a Monad', function() {
|
72 | var mTest = types.monad;
|
73 | var f = Future.of(null);
|
74 | assert.equal(true, mTest.iface(f));
|
75 | });
|
76 |
|
77 | it('.map should work according to the functor specification', function() {
|
78 | var result = Future.of(1).map(R.inc);
|
79 | assert.equal(true, Future.of(2).equals(result));
|
80 | });
|
81 |
|
82 | it('.chain should work according to the chainable specification', function() {
|
83 | var incInTheFuture = function(val) {
|
84 | return Future.of(R.inc(val));
|
85 | };
|
86 | var result = Future.of(1).chain(incInTheFuture);
|
87 | assert.equal(true, Future.of(2).equals(result));
|
88 | });
|
89 |
|
90 | describe('chainReject', function() {
|
91 | it('.chainReject should work like chain but off reject case', function() {
|
92 | var f1 = Future.reject(2);
|
93 | var f2 = function(val){ return Future.of(val + 3);};
|
94 | assert.equal(true, Future.of(5).equals(f1.chainReject(f2)));
|
95 | });
|
96 | });
|
97 |
|
98 | describe('#ap', function() {
|
99 |
|
100 | var add = R.add;
|
101 | function delayError(delay, err) {
|
102 |
|
103 | return new Future(function(reject, resolve) {
|
104 | setTimeout(reject, delay, err);
|
105 | });
|
106 | }
|
107 |
|
108 | function delayValue(delay, value) {
|
109 | return new Future(function(reject, resolve) {
|
110 | setTimeout(resolve, delay, value);
|
111 | });
|
112 | }
|
113 |
|
114 | function assertCbVal(done, expectedVal) {
|
115 | return function(val) {
|
116 | assert.equal(expectedVal, val);
|
117 | done();
|
118 | };
|
119 | }
|
120 |
|
121 | it('applies its function to the passed in future', function() {
|
122 | var f1 = Future.of(add(1));
|
123 | var result = f1.ap(Future.of(2));
|
124 | assert.equal(true, Future.of(3).equals(result));
|
125 | });
|
126 |
|
127 | it('does the apply in parallel', function(done) {
|
128 | this.timeout(25);
|
129 | var f1 = delayValue(15, 1);
|
130 | var f2 = delayValue(15, 2);
|
131 | f1.map(add).ap(f2).fork(assert.fail, assertCbVal(done, 3));
|
132 | });
|
133 |
|
134 | it('can handle itself being resolved first', function(done) {
|
135 | var f1 = delayValue(1, 1);
|
136 | var f2 = delayValue(15, 2);
|
137 | f1.map(add).ap(f2).fork(assert.fail, assertCbVal(done, 3));
|
138 | });
|
139 |
|
140 | it('can handle the input future being resolved first', function(done) {
|
141 | var f1 = delayValue(15, 1);
|
142 | var f2 = delayValue(1, 2);
|
143 | f1.map(add).ap(f2).fork(assert.fail, assertCbVal(done, 3));
|
144 | });
|
145 |
|
146 | it('is rejected with the first error to occur - case 1', function(done) {
|
147 | var f1 = delayError(10, 'firstError');
|
148 | var f2 = delayError(20, 'secondError');
|
149 | f1.map(add).ap(f2).fork(assertCbVal(done, 'firstError'));
|
150 | });
|
151 |
|
152 | it('is rejected with the first error to occur - case 2', function(done) {
|
153 | var f1 = delayError(20, 'firstError');
|
154 | var f2 = delayError(10, 'secondError');
|
155 | f1.map(add).ap(f2).fork(assertCbVal(done, 'secondError'));
|
156 | });
|
157 |
|
158 | });
|
159 |
|
160 | describe('reject', function() {
|
161 |
|
162 | it('creates a rejected future with the given value', function() {
|
163 | var f = Future.reject('foo');
|
164 | var forked;
|
165 | f.fork(function(err) {
|
166 | forked = true;
|
167 | assert.equal('foo', err);
|
168 | });
|
169 | assert.equal(true, forked);
|
170 | });
|
171 |
|
172 | });
|
173 |
|
174 | describe('#bimap', function() {
|
175 |
|
176 | it('maps the first function over the rejected value', function() {
|
177 | var f = Future.reject('err');
|
178 | var result = f.bimap(R.concat('map over '));
|
179 | result.fork(function(e) {
|
180 | assert.equal(e, 'map over err');
|
181 | });
|
182 | });
|
183 |
|
184 | it('maps the second function over the resolved value', function() {
|
185 | var f = Future.of(1);
|
186 | var result = f.bimap(null, R.add(1));
|
187 | result.fork(null, function(v) {
|
188 | assert.equal(v, 2);
|
189 | });
|
190 | });
|
191 |
|
192 | });
|
193 |
|
194 | describe('#toString', function() {
|
195 |
|
196 | it('returns the string representation of a Future', function() {
|
197 | assert.strictEqual(
|
198 | Future(function(reject, resolve) { void resolve; }).toString(),
|
199 | 'Future(function (reject, resolve) { void resolve; })'
|
200 | );
|
201 | });
|
202 |
|
203 | });
|
204 |
|
205 | describe('#fork', function() {
|
206 |
|
207 | var result;
|
208 | var futureOne = Future.of(1);
|
209 | var throwError = function() {
|
210 | throw new Error('Some error message');
|
211 | };
|
212 | var setErrorResult = function(e) {
|
213 | result = e.message;
|
214 | };
|
215 | var delayValue = function(delay, value) {
|
216 | return new Future(function(reject, resolve) {
|
217 | setTimeout(resolve, delay, value);
|
218 | });
|
219 | };
|
220 |
|
221 | beforeEach(function() {
|
222 | result = null;
|
223 | });
|
224 |
|
225 | it('creates a rejected future if the resolve function throws an error', function() {
|
226 | futureOne.fork(setErrorResult, throwError);
|
227 | assert.equal('Some error message', result);
|
228 | });
|
229 |
|
230 | it('rejects the future if an error is thrown in a map function', function() {
|
231 | futureOne.map(throwError).fork(setErrorResult);
|
232 | assert.equal('Some error message', result);
|
233 | });
|
234 |
|
235 | it('rejects the future if an error is thrown in a chain function', function() {
|
236 | futureOne.chain(throwError).fork(setErrorResult);
|
237 | assert.equal('Some error message', result);
|
238 | });
|
239 |
|
240 | it('rejects the future if an error is thrown in a ap function', function() {
|
241 | Future.of(throwError).ap(futureOne).fork(setErrorResult);
|
242 | assert.equal('Some error message', result);
|
243 | });
|
244 |
|
245 | it('eventually rejects the future if an error is thrown in a chain function', function(done){
|
246 | var throwEscapeError = function(){
|
247 | throw new Error('This error should be caught');
|
248 | };
|
249 | delayValue(15, 1).chain(throwEscapeError).fork(
|
250 | function(err){
|
251 | assert.equal('This error should be caught', err.message);
|
252 | done();
|
253 | },
|
254 | function(){
|
255 | done(new Error('The future resolved'));
|
256 | }
|
257 | );
|
258 | });
|
259 |
|
260 | });
|
261 |
|
262 | describe('#cache', function() {
|
263 | var cached;
|
264 | var throwIfCalledTwice;
|
265 |
|
266 | beforeEach(function() {
|
267 | throwIfCalledTwice = (function() {
|
268 | var count = 0;
|
269 | return function(val) {
|
270 | if (++count > 1) {
|
271 | throw new Error('Was called twice');
|
272 | }
|
273 | return val;
|
274 | };
|
275 | }());
|
276 | });
|
277 |
|
278 | describe('resolve cases', function() {
|
279 |
|
280 | beforeEach(function() {
|
281 | cached = Future.cache(Future.of(1).map(throwIfCalledTwice));
|
282 | });
|
283 |
|
284 | it('can be forked with a resolved value', function(done) {
|
285 | cached.fork(done, function(v) {
|
286 | assert.equal(1, v);
|
287 | done();
|
288 | });
|
289 | });
|
290 |
|
291 | it('passes on the same value to the cached future', function(done) {
|
292 | cached.fork(done, function() {
|
293 | cached.fork(done, function(v) {
|
294 | assert.equal(1, v);
|
295 | done();
|
296 | });
|
297 | });
|
298 | });
|
299 |
|
300 | });
|
301 |
|
302 | describe('reject cases', function() {
|
303 |
|
304 | var throwError = function() {
|
305 | throw new Error('SomeError');
|
306 | };
|
307 |
|
308 | beforeEach(function() {
|
309 | cached = Future.cache(Future.of(1).map(throwIfCalledTwice).map(throwError));
|
310 | });
|
311 |
|
312 | it('can be forked with a rejected value', function() {
|
313 | var result;
|
314 | cached.fork(function(err) {
|
315 | result = err.message;
|
316 | });
|
317 | assert.equal('SomeError', result);
|
318 | });
|
319 |
|
320 | it('does not call the underlying fork twice', function() {
|
321 | var result;
|
322 | cached.fork(function() {
|
323 | cached.fork(function(err) {
|
324 | result = err.message;
|
325 | });
|
326 | });
|
327 | assert.equal('SomeError', result);
|
328 | });
|
329 |
|
330 | });
|
331 |
|
332 | describe('pending cases', function() {
|
333 |
|
334 | it('calls all fork resolve functions when the cached future is resolved', function(done) {
|
335 | var delayed = new Future(function(reject, resolve) {
|
336 | setTimeout(resolve, 5, 'resolvedValue');
|
337 | });
|
338 | var cached = Future.cache(delayed.map(throwIfCalledTwice));
|
339 | var result1;
|
340 | var result2;
|
341 | function assertBoth() {
|
342 | if (result1 !== undefined && result2 !== undefined) {
|
343 | assert.equal('resolvedValue', result1);
|
344 | assert.equal('resolvedValue', result2);
|
345 | done();
|
346 | }
|
347 | }
|
348 | cached.fork(done, function(v) {
|
349 | result1 = v;
|
350 | assertBoth();
|
351 | });
|
352 | cached.fork(done, function(v) {
|
353 | result2 = v;
|
354 | assertBoth();
|
355 | });
|
356 | });
|
357 |
|
358 | it('calls all fork reject fnctions when the cached future is rejected', function(done) {
|
359 | var delayed = new Future(function(reject) {
|
360 | setTimeout(reject, 5, 'rejectedValue');
|
361 | });
|
362 | var cached = Future.cache(delayed.bimap(throwIfCalledTwice, R.identity));
|
363 | var result1;
|
364 | var result2;
|
365 | function assertBoth() {
|
366 | if (result1 !== undefined && result2 !== undefined) {
|
367 | assert.equal('rejectedValue', result1);
|
368 | assert.equal('rejectedValue', result2);
|
369 | done();
|
370 | }
|
371 | }
|
372 | cached.fork(function(e) {
|
373 | result1 = e;
|
374 | assertBoth();
|
375 | });
|
376 | cached.fork(function(e) {
|
377 | result2 = e;
|
378 | assertBoth();
|
379 | });
|
380 |
|
381 | });
|
382 |
|
383 | });
|
384 |
|
385 | });
|
386 |
|
387 | });
|
388 |
|