pragmatic-fp-ts
Version:
Opinionated functional programming library with easy use in mind
246 lines (211 loc) • 8.87 kB
text/typescript
import { futureMaybe } from "../Future.ts";
import * as l from "../main.ts";
const even = (n: number) => n % 2 === 0;
const odd = (n: number) => n % 2 === 1;
const asyncEven = (n: number) => Promise.resolve(n % 2 === 0);
const asyncOdd = (n: number) => Promise.resolve(n % 2 === 1);
describe("Future", () => {
describe("eitherFuture", () => {
it("should lift values", async () => {
const num = l.futureEither(1);
expect(await num.getValue()).toBe(1);
});
it("should provide default values", async () => {
expect(await l.futureEither<number>(null as any).getValueOr(1)).toBe(1);
await expect(l.futureEither(null as any).getValue()).rejects.toBeTruthy();
});
it("should lift promises", async () => {
const num = l.futureEither(Promise.resolve(1));
expect(await num.getValue()).toBe(1);
});
it("should chain bind functions", async () => {
const num = l.futureEither(1)._(l.add(1));
expect(await num.getValue()).toBe(2);
});
it("should chain bind promise generating functions", async () => {
const incAsync = (n: number) => Promise.resolve(n + 1);
const num = l.futureEither(1)._(incAsync);
expect(await num.getValue()).toBe(2);
});
it("should filter values with predicates", async () => {
const num = l.futureEither(1);
expect(await num.filter(odd).getMonad()).toBeInstanceOf(l.Right);
expect(await num.filter(even).getMonad()).toBeInstanceOf(l.Left);
expect(await num.filter(even).getMonad()).toBeInstanceOf(l.Left);
});
it("should filter values with predicate promises", async () => {
const num = l.futureEither(1);
expect(await num.filter(asyncOdd).getMonad()).toBeInstanceOf(l.Right);
expect(await num.filter(asyncEven).getMonad()).toBeInstanceOf(l.Left);
expect(await num.filter(asyncEven)._(l.add(1)).getMonad()).toBeInstanceOf(l.Left);
});
it("should bind effect", async () => {
let value = 1;
const num = l.futureEither("foo");
const updateValue = () => (value += 1);
expect(await num.effect(updateValue).getValue()).toEqual("foo");
expect(value).toBe(2);
});
it("should bind async effects", async () => {
let value = 1;
const num = l.futureEither("foo");
let p: Promise<any> = null as any;
const updateAsync = () => {
p = new Promise<void>((resolve) => {
setTimeout(() => {
value += 1;
resolve();
}, 500);
});
return p;
};
expect(await num.effect(updateAsync).getValue()).toEqual("foo");
expect(value).toBe(2);
});
it("should execute and await effects in order", async () => {
const results: any[] = [];
await futureMaybe(1)
.effect(async () => {
await new Promise((resolve) => setTimeout(resolve, 500));
results.push(1);
})
.effect(async () => {
results.push(2);
})
.effect(async () => {
await new Promise((resolve) => setTimeout(resolve, 100));
results.push(3);
})
.effect(async () => {
results.push(4);
})
.getValue();
expect(results).toEqual([1, 2, 3, 4]);
});
it("should catch errors", async () => {
const num = l.futureEither(1);
const boom1 = () => Promise.reject("boom") as any;
const boom2 = () => {
throw "boom";
};
expect(await num._(boom1).getMonad()).toBeInstanceOf(l.Left);
expect(await num.filter(boom1).getMonad()).toBeInstanceOf(l.Left);
expect(await num._(boom2).getMonad()).toBeInstanceOf(l.Left);
expect(await num.filter(boom2).getMonad()).toBeInstanceOf(l.Left);
const error1 = await num._(boom1, "bad stuff!").getMonad();
const error2 = await num._(boom2, "bad stuff!").getMonad();
const error3 = await num._(boom1).getMonad();
const error4 = await num._(boom2).getMonad();
expect(error1.getReason()).toEqual("bad stuff!");
expect(error2.getReason()).toEqual("bad stuff!");
expect(error3.getReason()).toEqual("boom");
expect(error4.getReason()).toEqual("boom");
});
const syncMatcher = { right: l.add(1), left: () => 0 };
const asyncMatcher = { right: (n: number) => Promise.resolve(n + 1), left: () => 0 };
it("should match success types", async () => {
const num = l.futureEither(1).match(syncMatcher);
expect(await num.getValue()).toEqual(2);
});
it("should match success types with promises", async () => {
const num = l.futureEither(1).match(asyncMatcher);
expect(await num.getValue()).toEqual(2);
});
it("should rethrow errors", async () => {
await expect(
l.futureEither(l.left<number>(new Error())).match(l.throwLeftAsError).getValue()
).rejects.toBeInstanceOf(Error);
});
});
describe("maybeFuture", () => {
it("should lift values", async () => {
const num = l.futureMaybe(1);
expect(await num.getValue()).toBe(1);
});
it("should lift promises", async () => {
const num = l.futureMaybe(Promise.resolve(1));
expect(await num.getValue()).toBe(1);
});
it("should provide default values", async () => {
expect(await l.futureMaybe<number>(null as any).getValueOr(1)).toBe(1);
await expect(l.futureMaybe(null as any).getValue()).rejects.toBeTruthy();
});
it("should chain bind functions", async () => {
const num = l.futureMaybe(1)._(l.add(1));
expect(await num.getValue()).toBe(2);
});
it("should chain bind promise generating functions", async () => {
const incAsync = (n: number) => Promise.resolve(n + 1);
const num = l.futureMaybe(1)._(incAsync);
expect(await num.getValue()).toBe(2);
});
it("should filter values with predicates", async () => {
const num = l.futureMaybe(1);
expect(await num.filter(odd).getMonad()).toBeInstanceOf(l.Just);
expect(await num.filter(even).getMonad()).toBeInstanceOf(l.Nothing);
expect(await num.filter(even)._(l.add(1)).getMonad()).toBeInstanceOf(l.Nothing);
});
it("should filter values with predicate promises", async () => {
const num = l.futureMaybe(1);
expect(await num.filter(asyncOdd).getMonad()).toBeInstanceOf(l.Just);
expect(await num.filter(asyncEven).getMonad()).toBeInstanceOf(l.Nothing);
expect(await num.filter(asyncEven)._(l.add(1)).getMonad()).toBeInstanceOf(l.Nothing);
});
it("should bind effect", async () => {
let value = 1;
const num = l.futureMaybe("foo");
const updateValue = () => (value += 1);
expect(await num.effect(updateValue).getValue()).toEqual("foo");
expect(value).toBe(2);
});
it("should bind async effects", async () => {
let value = 1;
const num = l.futureMaybe("foo");
let p: Promise<any> = null as any;
const updateAsync = () => {
p = new Promise<void>((resolve) => {
setTimeout(() => {
value += 1;
resolve();
}, 500);
});
return p;
};
expect(await num.effect(updateAsync).getValue()).toEqual("foo");
expect(value).toBe(2);
});
it("should become Nothing if effects throw", async () => {
const causeError = () => Promise.reject("error");
await expect(l.futureMaybe("test").effect(causeError).getValue()).rejects.toBeDefined();
});
it("should catch errors", async () => {
const num = l.futureMaybe(1);
const boom1 = () => Promise.reject("boom") as any;
const boom2 = () => {
throw new Error("boom");
};
expect(await num._(boom1).getMonad()).toBeInstanceOf(l.Nothing);
expect(await num.filter(boom1).getMonad()).toBeInstanceOf(l.Nothing);
expect(await num._(boom2).getMonad()).toBeInstanceOf(l.Nothing);
expect(await num.filter(boom2).getMonad()).toBeInstanceOf(l.Nothing);
});
const syncMatcher = { just: l.add(1), nothing: () => 0 };
const asyncMatcher = { just: (n: number) => Promise.resolve(n + 1), nothing: () => 0 };
it("should match success types", async () => {
const num = l.futureMaybe(1).match(syncMatcher);
expect(await num.getValue()).toEqual(2);
});
it("should match success types with promises", async () => {
const num = l.futureMaybe(1).match(asyncMatcher);
expect(await num.getValue()).toEqual(2);
});
it("should match error types", async () => {
const num = l.futureMaybe(l.nothing<number>()).match(syncMatcher);
expect(await num.getValue()).toEqual(0);
});
it("should match error types with promises", async () => {
const num = l.futureMaybe(l.nothing<number>()).match(asyncMatcher);
expect(await num.getValue()).toEqual(0);
});
});
});