specs/fantasy-land.spec.js
import * as chai from 'chai';
import { assert, expect } from 'chai';
import { Functor, Apply, Applicative, Monad, LazyFunctor } from '../src/Fantasy-Land';
import { Maybe } from '../src/Fantasy-Land/maybe';
import { Either } from '../src/Fantasy-Land/either';
import { toSetoid, isSetoid } from '../src/Fantasy-Land/setoid';
import { identity } from '../src/Function-Combinators';
/** @test {Fantasy-Land} */
describe('Fantasy Land Library Tests', () => {
/** @test {Fantasy-Land#Functor} */
it('test: Functor', () => {
let sideEffectVar = 0
, functor = Functor(' 64 ')
.map(s => s.trim())
.map(r => parseInt(r))
.tap(v => sideEffectVar = 3)
.map(i => i + 1)
.map(i => String.fromCharCode(i))
assert.equal(functor.fold(identity), 'A');
assert.equal(functor.get(), 'A');
assert.equal(sideEffectVar, 3);
assert.equal(functor.toString(), 'Functor(A)');
assert.equal(functor.inspect(), 'Functor(A)');
});
/** @test {Fantasy-Land#Apply} */
it('test: Apply', () => {
let sideEffectVar = 0
, curry2Add = x => y => x + y
, apply = Apply(curry2Add)
.tap(() => sideEffectVar = 3)
assert.equal(sideEffectVar, 3);
assert.isFunction(apply.get());
assert.isFunction(apply.fold(identity));
assert.isFunction(apply.ap(2).get());
assert.equal(apply.ap(2).ap(3).get(), 5);
assert.equal(apply.ap(2).apGet(3), 5);
assert.equal(apply.ap(2).ap(3).toString(), 'Apply(5)');
assert.equal(apply.ap(2).ap(3).inspect(), 'Apply(5)');
assert.equal(
apply
.ap(2)
.ap(3)
.map(n => `Number is ${n}`)
.get()
, 'Number is 5');
});
/** @test {Fantasy-Land#Applicative */
it('test: Applicative', () => {
let sideEffectVar = 0
, curry2Add = x => y => x + y
, applicative = Applicative.of(curry2Add)
.tap(() => sideEffectVar = 3)
assert.equal(sideEffectVar, 3);
assert.isFunction(applicative.get());
assert.isFunction(applicative.fold(identity));
assert.isFunction(applicative.ap(2).get());
assert.equal(applicative.ap(2).ap(3).get(), 5);
assert.equal(applicative.ap(2).apGet(3), 5);
assert.equal(applicative.ap(2).ap(3).toString(), 'Applicative(5)');
assert.equal(applicative.ap(2).ap(3).inspect(), 'Applicative(5)');
assert.equal(
applicative
.ap(2)
.ap(3)
.map(n => `Number is ${n}`)
.get()
, 'Number is 5');
});
/** @test {Fantasy-Land#Monad} */
it('test: Monad', () => {
let sideEffectVar = 0
, curry2Add = x => y => x + y
, monad = Monad.of(curry2Add)
.tap(() => sideEffectVar = 3)
assert.equal(sideEffectVar, 3);
assert.isFunction(monad.get());
assert.isFunction(monad.fold(identity));
assert.isFunction(monad.ap(2).get());
assert.equal(monad.ap(2).ap(3).get(), 5);
assert.equal(monad.ap(2).apGet(3), 5);
assert.equal(monad.ap(2).ap(3).toString(), 'Monad(5)');
assert.equal(monad.ap(2).ap(3).inspect(), 'Monad(5)');
assert.equal(
monad
.ap(2)
.ap(3)
.map(n => `Number is ${n}`)
.get()
, 'Number is 5');
assert.equal(
monad
.ap(2)
.ap(3)
.chain(v => Monad.of(v * v))
.get()
, 25);
assert.equal(
monad
.ap(2)
.ap(3)
.map(v => Monad.of(v * v))
.join()
.get()
, 25);
assert.equal(
monad
.ap(3)
.apChain(Monad.of(2))
.get()
, 5);
});
/** @test {Fantasy-Land#LazyFunctor} */
it('test: LazyFunctor', () => {
let wasInvoked = false;
let multiplyWithInvokeUpdate = x => {
wasInvoked = true;
return x * x;
}
assert.isNotNumber(
LazyFunctor(() => 7)
.lazyMap(multiplyWithInvokeUpdate)
.lazyMap(x => x * 100)
);
assert.isFalse(wasInvoked);
assert.isNumber(
LazyFunctor(() => 7)
.lazyMap(multiplyWithInvokeUpdate)
.lazyMap(x => x * 100)
.lazyFold(identity)
);
assert.isTrue(wasInvoked);
assert.equal(
LazyFunctor(() => 7)
.lazyMap(multiplyWithInvokeUpdate)
.lazyMap(x => x * 10)
.lazyFold(identity),
490
);
});
/** @test {Fantasy-Land#Maybe} */
describe('test: Maybe', () => {
/** @test {Fantasy-Land#Maybe#fromNullable} */
it('test: Maybe.fromNullable', () => {
let positiveMaybe = Maybe.fromNullable('hello')
, negativeMaybe = Maybe.fromNullable(undefined)
assert.isTrue(positiveMaybe.isJust);
assert.isFalse(positiveMaybe.isNothing);
assert.isFalse(negativeMaybe.isJust);
assert.isTrue(negativeMaybe.isNothing);
});
/** @test {Fantasy-Land#Maybe#of} */
it('test: Maybe.of', () => {
let positiveMaybe = Maybe.of('hello');
assert.isTrue(positiveMaybe.isJust);
assert.isFalse(positiveMaybe.isNothing);
});
/** @test {Fantasy-Land#Maybe#just} */
it('test: Maybe.just', () => {
let positiveMaybe = Maybe.just('hello');
assert.isTrue(positiveMaybe.isJust);
assert.isFalse(positiveMaybe.isNothing);
});
/** @test {Fantasy-Land#Maybe#nothing} */
it('test: Maybe.nothing', () => {
let positiveMaybe = Maybe.nothing('hello');
assert.isFalse(positiveMaybe.isJust);
assert.isTrue(positiveMaybe.isNothing);
});
/** @test {Fantasy-Land#Maybe#map} */
it('test: Maybe.Just/Maybe.Nothing#map(f)', () => {
let positiveMaybe = Maybe.fromNullable('hello')
, negativeMaybe = Maybe.fromNullable(undefined);
assert.equal(
positiveMaybe
.map(s => `${s} world`)
.map(s => s.toUpperCase())
.toString()
, 'Maybe.Just(HELLO WORLD)');
assert.equal(
negativeMaybe
.map(s => `${s} world`)
.map(s => s.toUpperCase())
.toString()
, 'Maybe.Nothing()');
});
/** @test {Fantasy-Land#Maybe#getValue} */
it('test: Maybe.Just/Maybe.Nothing#getValue()', () => {
let positiveMaybe = Maybe.fromNullable('hello')
, negativeMaybe = Maybe.fromNullable(undefined);
assert.equal(
positiveMaybe
.map(s => `${s} world`)
.map(s => s.toUpperCase())
.getValue()
, 'HELLO WORLD');
assert.isUndefined(
negativeMaybe
.map(s => `${s} world`)
.map(s => s.toUpperCase())
.getValue());
});
/** @test {Fantasy-Land#Maybe#get} */
it('test: Maybe.Just/Maybe.Nothing#get()', () => {
let positiveMaybe = Maybe.fromNullable('hello')
, negativeMaybe = Maybe.fromNullable(undefined);
assert.equal(
positiveMaybe
.map(s => `${s} world`)
.map(s => s.toUpperCase())
.get()
, 'HELLO WORLD');
expect(
negativeMaybe
.map(s => `${s} world`)
.map(s => s.toUpperCase())
.get
).to.throw(chai.TypeError, 'Can\'t extract the value of a Nothing.');
});
/** @test {Fantasy-Land#Maybe#getOrElse} */
it('test: Maybe.Just/Maybe.Nothing#getOrElse()', () => {
let positiveMaybe = Maybe.fromNullable('hello')
, negativeMaybe = Maybe.fromNullable(undefined);
assert.equal(
positiveMaybe
.map(s => `${s} world`)
.map(s => s.toUpperCase())
.getOrElse('Other')
, 'HELLO WORLD');
assert.equal(
negativeMaybe
.map(s => `${s} world`)
.map(s => s.toUpperCase())
.getOrElse('Other')
, 'Other');
});
/** @test {Fantasy-Land#Maybe#chain} */
it('test: Maybe.Just/Maybe.Nothing#chain()', () => {
assert.equal(
Maybe.just('hello')
.chain(s => Maybe.of(s.toUpperCase()))
.getOrElse('Other')
, 'HELLO');
assert.equal(
Maybe.nothing()
.chain(s => Maybe.of(s.toUpperCase()))
.getOrElse('Other')
, 'Other');
});
/** @test {Fantasy-Land#Maybe#filter} */
it('test: Maybe.Just/Maybe.Nothing#filter()', () => {
assert.equal(
Maybe.just('hello')
.map(s => s.toUpperCase())
.filter(s => s === 'HELLO')
.getOrElse('Other')
, 'HELLO');
assert.equal(
Maybe.nothing()
.map(s => s.toUpperCase())
.filter(s => s === 'HELLO')
.getOrElse('Other')
, 'Other');
});
});
/** @test {Fantasy-Land#Either} */
describe('test: Either', () => {
/** @test {Fantasy-Land#Maybe#fromNullable} */
it('test: Either.fromNullable', () => {
let positiveEither = Either.fromNullable('hello')
, negativeEither = Either.fromNullable(undefined)
assert.isTrue(positiveEither.isRight);
assert.isFalse(positiveEither.isLeft);
assert.isFalse(negativeEither.isRight);
assert.isTrue(negativeEither.isLeft);
});
/** @test {Fantasy-Land#Either#of} */
it('test: Either.of', () => {
let positiveEither = Either.of('hello');
assert.isTrue(positiveEither.isRight);
assert.isFalse(positiveEither.isLeft);
});
/** @test {Fantasy-Land#Either#tryCatch} */
it('test: Either.tryCatch with falsy value', () => {
const expected = 3000;
assert.equal(
Either.tryCatch(() => JSON.parse('incorrect json'))
.map(c => c.port)
.getOrElse(expected)
, expected);
});
/** @test {Fantasy-Land#Either#tryCatch} */
it('test: Either.tryCatch with truthy value', () => {
assert.equal(
Either.tryCatch(() => JSON.parse('{"port": 8888}'))
.map(c => c.port)
.getOrElse(3000)
, 8888);
});
/** @test {Fantasy-Land#Either#tryCatch} */
it('test: double Either.tryCatch', () => {
const expected = 3000;
assert.equal(
Either.tryCatch(() => JSON.parse('incorrect json'))
.map(c => Either.tryCatch(JSON.parse('another incorrect json')))
.getOrElse(expected)
, expected);
});
/** @test {Fantasy-Land#Either#right} */
it('test: Either.right', () => {
let positiveEither = Either.right('hello');
assert.isTrue(positiveEither.isRight);
assert.isFalse(positiveEither.isLeft);
});
/** @test {Fantasy-Land#Either#left} */
it('test: Either.left', () => {
let positiveEither = Either.left('hello');
assert.isFalse(positiveEither.isRight);
assert.isTrue(positiveEither.isLeft);
});
/** @test {Fantasy-Land#Either#map} */
it('test: Either.Right/Either.Left#map(f)', () => {
let positiveEither = Either.fromNullable('hello')
, negativeEither = Either.fromNullable(undefined);
assert.equal(
positiveEither
.map(s => `${s} world`)
.map(s => s.toUpperCase())
.toString()
, 'Either.Right(HELLO WORLD)');
assert.equal(
negativeEither
.map(s => `${s} world`)
.map(s => s.toUpperCase())
.toString()
, 'Either.Left(undefined)');
});
/** @test {Fantasy-Land#Either#getValue} */
it('test: Either.Right/Either.Left#getValue()', () => {
let positiveEither = Either.fromNullable('hello')
, negativeEither = Either.fromNullable(undefined);
assert.equal(
positiveEither
.map(s => `${s} world`)
.map(s => s.toUpperCase())
.getValue()
, 'HELLO WORLD');
assert.isUndefined(
negativeEither
.map(s => `${s} world`)
.map(s => s.toUpperCase())
.getValue());
});
/** @test {Fantasy-Land#Either#get} */
it('test: Either.Right/Either.Left#get()', () => {
let positiveEither = Either.fromNullable('hello')
, negativeEither = Either.fromNullable(undefined);
assert.equal(
positiveEither
.map(s => `${s} world`)
.map(s => s.toUpperCase())
.get()
, 'HELLO WORLD');
expect(
negativeEither
.map(s => `${s} world`)
.map(s => s.toUpperCase())
.get
).to.throw(chai.TypeError, 'Can\'t extract the value of a Left.');
});
/** @test {Fantasy-Land#Either#getOrElse} */
it('test: Either.Right/Either.Left#getOrElse()', () => {
let positiveEither = Either.fromNullable('hello')
, negativeEither = Either.fromNullable(undefined);
assert.equal(
positiveEither
.map(s => `${s} world`)
.map(s => s.toUpperCase())
.getOrElse('Other')
, 'HELLO WORLD');
assert.equal(
negativeEither
.map(s => `${s} world`)
.map(s => s.toUpperCase())
.getOrElse('Other')
, 'Other');
});
/** @test {Fantasy-Land#Either#chain} */
it('test: Either.Right/Either.Left#chain()', () => {
assert.equal(
Either.right('hello')
.chain(s => Either.of(s.toUpperCase()))
.getOrElse('Other')
, 'HELLO');
assert.equal(
Either.left()
.chain(s => Either.of(s.toUpperCase()))
.getOrElse('Other')
, 'Other');
});
/** @test {Fantasy-Land#Either#filter} */
it('test: Either.Right/Either.Left#filter()', () => {
assert.equal(
Either.right('hello')
.map(s => s.toUpperCase())
.filter(s => s === 'HELLO')
.getOrElse('Other')
, 'HELLO');
assert.equal(
Either.left()
.map(s => s.toUpperCase())
.filter(s => s === 'HELLO')
.getOrElse('Other')
, 'Other');
});
/** @test {Fantasy-Land#Either#getOrElseThrow} */
it('test: Either.Rigth/Either.Left#getOrElseThrow(err)', () => {
let positiveEither = Either.fromNullable('hello')
, negativeEither = Either.fromNullable(undefined);
assert.equal(
positiveEither
.map(s => `${s} world`)
.map(s => s.toUpperCase())
.getOrElseThrow('some error message')
, 'HELLO WORLD');
expect(() =>
negativeEither
.map(s => `${s} world`)
.map(s => s.toUpperCase())
.getOrElseThrow('some error message')
).to.throw(chai.Error, 'some error message');
});
/** @test {Fantasy-Land#Either#orElse} */
it('test: Either.Rigth/Either.Left#orElse(f)', () => {
let positiveEither = Either.fromNullable('hello')
, negativeEither = Either.fromNullable(undefined);
assert.equal(
positiveEither
.map(s => `${s} world`)
.map(s => s.toUpperCase())
.orElse(() => Either.right(3))
.get()
, 'HELLO WORLD');
assert.equal(
negativeEither
.map(s => `${s} world`)
.map(s => s.toUpperCase())
.orElse(() => Either.right(3))
.get()
, 3);
});
});
describe('test: Setoid', () => {
it('test: .toSetoid(eqFn) with positive case', () => {
const comparePeopleFn = (p, p2) => p.id === p2.id;
const PersonSetoid = toSetoid(comparePeopleFn);
const person1 = { id: 1, name: 'Oleh' };
const person2 = { id: 1, name: 'Oleh' };
assert.isTrue(
PersonSetoid(person1)
.equals(
PersonSetoid(person2))
);
assert.isTrue(
PersonSetoid(person2)
.equals(
PersonSetoid(person1))
);
});
it('test: .toSetoid(eqFn) with negative case', () => {
const comparePeopleFn = (p, p2) => p.id === p2.id;
const PersonSetoid = toSetoid(comparePeopleFn);
const person1 = { id: 1, name: 'Oleh' }
const person2 = { id: 2, name: 'Oleh' }
assert.isFalse(
PersonSetoid(person1)
.equals(
PersonSetoid(person2))
);
assert.isFalse(
PersonSetoid(person2)
.equals(
PersonSetoid(person1))
);
});
it('test: .isSetoid(obj)', () => {
const comparePeopleFn = (p, p2) => p.id === p2.id;
const PersonSetoid = toSetoid(comparePeopleFn);
const person1 = { id: 1, name: 'Oleh' }
const person2 = {
id: 2,
name: 'Oleh',
equals: p => true
};
assert.isTrue(isSetoid(PersonSetoid(person1)));
assert.isFalse(isSetoid(person1));
assert.isFalse(isSetoid(person2));
});
});
});