spica
Version:
Supervisor, Coroutine, Channel, select, AtomicPromise, Cancellation, Cache, List, Queue, Stack, and some utils.
174 lines (148 loc) • 4.77 kB
text/typescript
import { Either, Left, Right } from './either';
describe('Unit: lib/either', () => {
const Return = Either.Return;
function throwError(msg: string): never {
throw new Error(msg);
}
describe('Either', () => {
it('Left', () => {
const result = Return(0)
.bind(n => Right(n + 1))
.bind(n => Right(n + 1).bind(n => Left(`Left ${n}`)))
.bind(n => Right(`Right ${n}`))
.extract(l => `Left ${l}`);
assert(result === 'Left Left 2');
try {
Left(0).extract();
throw 1;
}
catch (reason) {
assert(reason === 0);
}
});
it('Left nest', () => {
const result = Return(Return(''))
.bind(m => m.bind(() => Left(NaN)).bind(throwError))
.bind(throwError)
.extract(() => 'Nothing');
assert(result === 'Nothing');
});
it('Right', () => {
const result = Return(0)
.bind(n => Right(n + 1))
.bind(n => Right(n + 1).bind(n => Right(`Right ${n}`)))
.extract(l => `Left ${l}`);
assert(result === 'Right 2');
});
it('Right nest', () => {
const result = Return(Return(0))
.bind(m => Right(m))
.bind(m => m.bind(n => Right(n + 1)).bind(n => Right(`Right ${n}`)))
.extract(() => 'Nothing');
assert(result === 'Right 1');
});
it('Either', () => {
const result = Return(0)
.bind<number, number>(n => <Right<number> | Left<number> | Either<number, number>>Right(n).bind(n => <Right<number> | Left<number>>Right(n) || Left(0)))
.bind(n => <Right<number> | Left<number> | Either<number, number>>Right(n).bind(n => <Right<number> | Left<number>>Right(n) || Left(0)))
.extract(n => n + '');
assert(result === 0);
});
it('either', () => {
assert(Right(0).extract(() => -1, n => n + 1) === 1);
assert(Left(0).extract(n => n -1, () => 0 + 1) === -1);
});
});
describe('Functor', () => {
it('fmap', () => {
assert(Either.fmap(n => n + 1, Return(0)).extract() === 1);
});
it('Functor law 1', () => {
const f = <T>(n: T) => n;
const x = 0;
const fa = Return(x).fmap(f);
const fb = f(Return(x));
assert(fa.extract() === fb.extract());
});
it('Functor law 2', () => {
const f = (n: number) => n + 2;
const g = (n: number) => n * 3;
const x = 1;
const fa = Return(x).fmap(n => g(f(n)));
const fb = Return(x).fmap(f).fmap(g);
assert(fa.extract() === fb.extract());
});
});
describe('Applicative', () => {
it('ap 1', () => {
assert.strictEqual(
Either.pure((a: number) => a)
.ap(Either.pure(1))
.extract(),
1);
assert.strictEqual(
Either.pure<number, typeof throwError>(throwError)
.ap(Left(0))
.extract(n => n),
0);
});
it('ap 2', () => {
assert.strictEqual(
Either.pure((a: number, b: number) => a + b)
.ap(Either.pure(1))
.ap(Either.pure(2))
.extract(),
3);
});
it('ap 3', () => {
assert.strictEqual(
Either.pure((a: number, b: number, c: number) => a + b + c)
.ap(Either.pure(1))
.ap(Either.pure(2))
.ap(Either.pure(3))
.extract(),
6);
});
});
describe('Monad', () => {
it('bind', () => {
assert(Either.bind(n => Return(n + 1), Return(0)).extract() === 1);
});
it('join', () => {
assert(Return(Return(0)).join().extract() === 0);
});
it('sequence', async () => {
assert.deepStrictEqual(Either.sequence([Right(0), Right(1)]).extract(), [0, 1]);
assert.deepStrictEqual(Either.sequence([Left([]), Right(1)]).extract(a => a), []);
assert(await Either.sequence(Right(Promise.resolve(0))).then(m => m.extract()) === 0);
assert(await Either.sequence(Right(Promise.reject(1))).catch(a => a) === 1);
assert(await Either.sequence(Left(1)).then(m => m.extract(a => a)) === 1);
});
it('Monad law 1', () => {
const f = (n: number) => Return(n + 1);
const x = 0;
const ma = Return(x).bind(f);
const mb = f(x);
assert(ma.extract() === mb.extract());
});
it('Monad law 2', () => {
const x = 0;
const ma = Return(x);
const mb = ma.bind(Return);
assert(ma.extract() === mb.extract());
});
it('Monad law 3', () => {
const f = (n: number) => Return(n + 2);
const g = (n: number) => Return(n * 3);
const x = 1;
const ma = Return(x)
.bind(f)
.bind(g);
const mb = Return(x)
.bind(x =>
f(x)
.bind(g));
assert(ma.extract() === mb.extract());
});
});
});