lazy-either
Version:
A lazy implementation of the Fantasy Land Either type
355 lines (298 loc) • 10.4 kB
JavaScript
const expect = require('chai').expect
const LazyEither = require('../index')
const FL = require('fantasy-land')
, R = require('ramda')
, S = require('sanctuary')
describe('Constructors', function() {
it('should construct a Lazy Left value', function(done) {
LazyEither.Left('bad').value(res => {
expect(res).to.deep.equal(S.Left('bad'))
done()
})
})
it('should construct a Lazy Right value', function(done) {
LazyEither.Right('good').value(res => {
expect(res).to.deep.equal(S.Right('good'))
done()
})
})
it('should construct using LazyEither["fantasy-land/of"]', function(done) {
S.of(LazyEither, 'good').value(res => {
expect(res).to.deep.equal(S.Right('good'))
done()
})
})
it('should handle async delayed values', function(done) {
let startTime = Date.now()
LazyEither(resolve => {
setTimeout(() => {
return resolve(S.Right('hello'))
}, 1000)
}).value(res => {
let endTime = Date.now()
expect(res).to.deep.equal(S.Right('hello'))
expect(endTime - startTime).to.be.closeTo(1000, 100)
done()
})
})
})
describe('Chain', function() {
before(function() {
this.delayed = t => LazyEither(resolve => {
if (t > 1000)
return resolve(S.Left('Delay too long'))
setTimeout(() => {
return resolve(S.Right('hello'))
}, t)
})
})
it('should not execute the rest of the chain if it fails and should propagate the Left value', function(done) {
let startTime = Date.now()
LazyEither.Left('bad')[FL.chain](this.delayed).value(res => {
let endTime = Date.now()
expect(res).to.deep.equal(S.Left('bad'))
expect(endTime - startTime).to.be.closeTo(0, 100)
done()
})
})
it('should pass the result to the next item in the chain (1)', function(done) {
let startTime = Date.now()
LazyEither.Right(1500)[FL.chain](this.delayed).value(res => {
let endTime = Date.now()
expect(res).to.deep.equal(S.Left('Delay too long'))
expect(endTime - startTime).to.be.closeTo(0, 100)
done()
})
})
it('should pass the result to the next item in the chain (2)', function(done) {
let startTime = Date.now()
LazyEither.Right(800)[FL.chain](this.delayed).value(res => {
let endTime = Date.now()
expect(res).to.deep.equal(S.Right('hello'))
expect(endTime - startTime).to.be.closeTo(800, 100)
done()
})
})
it('should pass the result to the next item in the chain (3)', function(done) {
let startTime = Date.now()
LazyEither.Right(1001)[FL.chain](this.delayed)[FL.chain](LazyEither.lift(R.identity)).value(res => {
let endTime = Date.now()
expect(res).to.deep.equal(S.Left('Delay too long'))
expect(endTime - startTime).to.be.closeTo(0, 100)
done()
})
})
it('should pass the result to the next item in the chain (4)', function(done) {
let startTime = Date.now()
LazyEither.Right(1001)[FL.chain](LazyEither.lift(a => a - 1))[FL.chain](this.delayed).value(res => {
let endTime = Date.now()
expect(res).to.deep.equal(S.Right('hello'))
expect(endTime - startTime).to.be.closeTo(1000, 100)
done()
})
})
it('should pass the result to the next item in the chain (5)', function(done) {
let startTime = Date.now()
LazyEither.Right(200)[FL.chain](this.delayed)[FL.chain](LazyEither.lift(hi => `${hi} world`)).value(res => {
let endTime = Date.now()
expect(res).to.deep.equal(S.Right('hello world'))
expect(endTime - startTime).to.be.closeTo(200, 100)
done()
})
})
it('should satisfy associativity', function(done) {
let val1, val2
let add3 = LazyEither.lift(R.add(3))
, mult2 = LazyEither.lift(R.multiply(2))
let resolveIfDone = _ => { if (val1 && val2) done() }
LazyEither.Right(5)[FL.chain](add3)[FL.chain](mult2).value(res => {
val1 = res
expect(res).to.deep.equal(S.Right(16))
resolveIfDone()
})
LazyEither.Right(5)[FL.chain](x => add3(x)[FL.chain](mult2)).value(res => {
val2 = res
expect(res).to.deep.equal(S.Right(16))
resolveIfDone()
})
})
})
describe('Functor', function() {
it('should apply the map to a Right value (1)', function(done) {
LazyEither.Right(1)[FL.map](R.add(7)).value(res => {
expect(res).to.deep.equal(S.Right(8))
done()
})
})
it('should apply the map to a Right value (2)', function(done) {
LazyEither.Right('foo')[FL.map](a => `${a} bar`)[FL.map](a => `${a} baz`).value(res => {
expect(res).to.deep.equal(S.Right('foo bar baz'))
done()
})
})
it('should not apply the map function to a Left value (1)', function(done) {
LazyEither.Left('bad')[FL.map](a => `${a} bar`)[FL.map](a => `${a} baz`).value(res => {
expect(res).to.deep.equal(S.Left('bad'))
done()
})
})
it('should satisfy identity (1)', function(done) {
LazyEither.Right('good')[FL.map](R.identity).value(res => {
expect(res).to.deep.equal(S.Right('good'))
done()
})
})
it('should satisfy identity (2)', function(done) {
LazyEither.Left('bad')[FL.map](R.identity).value(res => {
expect(res).to.deep.equal(S.Left('bad'))
done()
})
})
it('should satisfy composition', function(done) {
let val1, val2
let resolveIfDone = _ => { if (val1 && val2) done() }
LazyEither.Right(5)[FL.map](R.add(3))[FL.map](R.multiply(2)).value(res => {
val1 = res
expect(res).to.deep.equal(S.Right(16))
resolveIfDone()
})
LazyEither.Right(5)[FL.map](x => R.multiply(2)(R.add(3, x))).value(res => {
val2 = res
expect(res).to.deep.equal(S.Right(16))
resolveIfDone()
})
})
})
describe('Applicative', function() {
it('should be apply the ap function', function(done) {
LazyEither.Right(4)['fantasy-land/ap'](LazyEither.Right(R.multiply(3))).value(res => {
expect(res).to.deep.equal(S.Right(12))
done()
})
})
it('should propagate a Left value (1)', function(done) {
LazyEither.Right(2)[FL.ap](LazyEither.Left('bad')).value(res => {
expect(res).to.deep.equal(S.Left('bad'))
done()
})
})
it('should propagate a Left value (2)', function(done) {
LazyEither.Left('bad')[FL.ap](LazyEither.Right(R.add(5))).value(res => {
expect(res).to.deep.equal(S.Left('bad'))
done()
})
})
it('should be able to compose multiple ap functions', function(done) {
LazyEither.Right(5)[FL.ap](LazyEither.Right(R.add(3)))[FL.ap](LazyEither.Right(R.multiply(2))).value(res => {
expect(res).to.deep.equal(S.Right(16))
done()
})
})
it('should satisfy identity', function(done) {
LazyEither.Right('good')[FL.ap](LazyEither.Right(R.identity)).value(res => {
expect(res).to.deep.equal(S.Right('good'))
done()
})
})
it('should satisfy homomorphism', function(done) {
LazyEither.Right(5)[FL.ap](LazyEither.Right(R.add(3))).value(res => {
LazyEither.Right(R.add(3, 5)).value(res2 => {
expect(res).to.deep.equal(res2)
done()
})
})
})
it('should satisfy interchange', function(done) {
LazyEither.Right(5)[FL.ap](LazyEither.Right(R.add(3))).value(res => {
LazyEither.Right(R.add(3))[FL.ap](LazyEither.Right(f => f(5))).value(res2 => {
expect(res).to.deep.equal(S.Right(8))
expect(res).to.deep.equal(res2)
done()
})
})
})
})
describe('Setoid (contitional)', function() {
it('should return true if a and b are equal (reflexivity) (1)', function(done) {
LazyEither.Right('good')[FL.equals](LazyEither.Right('good'), res => {
expect(res).to.be.true
done()
})
})
it('should return true if a and b are equal (reflexivity) (2)', function(done) {
LazyEither.Left('bad')[FL.equals](LazyEither.Left('bad'), res => {
expect(res).to.be.true
done()
})
})
it('should return false if a and b are not equal (1)', function(done) {
LazyEither.Right(1)[FL.equals](LazyEither.Right(2), res => {
expect(res).to.be.false
done()
})
})
it('should return false if a and b are not equal (2)', function(done) {
LazyEither.Left(1)[FL.equals](LazyEither.Left(2), res => {
expect(res).to.be.false
done()
})
})
it('should return false if a and b are equal but Left and Right', function(done) {
LazyEither.Left(1)[FL.equals](LazyEither.Right(1), res => {
expect(res).to.be.false
done()
})
})
it('should satisfy symmetry', function(done) {
LazyEither.Right(7 + 5)[FL.equals](LazyEither.Right(3 * 4), res => {
LazyEither.Right(3 * 4)[FL.equals](LazyEither.Right(7 + 5), res2 => {
expect(res).to.deep.equal(res2)
done()
})
})
})
it('should satisfy transitivity', function(done) {
LazyEither.Right(7 + 5)[FL.equals](LazyEither.Right(3 * 4), res => {
LazyEither.Right(3 * 4)[FL.equals](LazyEither.Right(72 / 6), res2 => {
LazyEither.Right(7 + 5)[FL.equals](LazyEither.Right(72 / 6), res2 => {
expect(res).to.deep.equal(res2)
done()
})
})
})
})
})
describe('Lift', function() {
it('should lift a function of arity 1', function(done) {
let lifted = LazyEither.lift(R.multiply(3))
LazyEither.Right(3)[FL.chain](lifted).value(res => {
expect(res).to.deep.equal(S.Right(9))
done()
})
})
it('should lift a function of arity n', function(done) {
let add = R.curry((a, b, c, d, e) => a + b + c + d + e)
, lifted = LazyEither.liftN(5, add)
LazyEither.Right(3)[FL.chain](lifted(4, 5, 6, 7)).value(res => {
expect(res).to.deep.equal(S.Right(25))
done()
})
})
})
describe('Promote', function() {
it('should promote an Either type to a LazyEither type (Left)', function(done) {
LazyEither.promote(S.Left('bad')).value(either => {
expect(either.isLeft).to.be.true
expect(either.value).to.equal('bad')
done()
})
})
it('should promote an Either type to a LazyEither type (Right)', function(done) {
LazyEither.promote(S.Right('good')).value(either => {
expect(either.isRight).to.be.true
expect(either.value).to.equal('good')
done()
})
})
})