UNPKG

pragmatic-fp-ts

Version:

Opinionated functional programming library with easy use in mind

752 lines (633 loc) 22 kB
import * as l from "../main.ts"; const even = (n: number) => n % 2 === 0; describe("fns", () => { describe("add()", () => { it("adds numbers", () => { expect(l.add(1)(2)).toBe(3); }); }); describe("all()", () => { it("checks all predicates", () => { expect(l.all(l.gte(0))([1, 2, 3])).toBeTruthy(); expect(l.all(l.gte(0))([1, 2, 3, -1])).toBeFalsy(); }); }); describe("allPass()", () => { expect(l.allPass([l.gte(0), l.lte(3)])(2)).toBeTruthy(); expect(l.allPass([l.gte(0), l.lte(3)])(6)).toBeFalsy(); }); describe("and()", () => { it("tests if both params are truthy", () => { expect(l.and(true)(true)).toBeTruthy(); expect(l.and(true)(false)).toBeFalsy(); expect(l.and(false)(true)).toBeFalsy(); expect(l.and(false)(false)).toBeFalsy(); }); }); describe("append()", () => { it("appends arrays", () => { expect(l.append(3)([1, 2])).toEqual([1, 2, 3]); expect(l.append(3)([])).toEqual([3]); }); it("appends strings", () => { expect(l.append("bar")("foo")).toEqual("foobar"); }); }); describe("apply()", () => { expect(l.apply(l.add)([1, 1])).toEqual(2); }); describe("applyTo()", () => { it("applies 2nd param to 1st", () => { expect(l.applyTo(1)(l.inc)).toEqual(2); expect(l.applyTo(1)(String)).toEqual("1"); expect(l.applyTo(1)(null as any)).toBe(null); }); }); describe("both()", () => { it("tests if value satisfies both preds", () => { expect(l.both(l.gt(0))(l.lt(2))(1)).toBeTruthy(); expect(l.both(l.gt(0))(l.lt(1))(1)).toBeFalsy(); expect(l.both(l.gt(1))(l.lt(2))(1)).toBeFalsy(); expect(l.both(l.gt(1))(l.lt(1))(1)).toBeFalsy(); }); }); describe("clamp()", () => { it("Clamps numbers between [min,max]", () => { expect(l.clamp(0)(2)(1)).toBe(1); expect(l.clamp(0)(2)(3)).toBe(2); expect(l.clamp(0)(2)(-1)).toBe(0); expect(l.clamp(0, 2, 1)).toBe(1); }); it("Clamps strings between [min,max]", () => { expect(l.clamp<string>("aaa")("bbb")("aab")).toBe("aab"); expect(l.clamp<string>("aaa")("bbb")("cab")).toBe("bbb"); expect(l.clamp<string>("aab")("bbb")("aaa")).toBe("aab"); expect(l.clamp<string>("aaa")("bbb")("aab")).toBe("aab"); }); }); describe("comparator()", () => { it("creates a comparator from an ordering function", () => { const bySize = (a: number, b: number) => a < b; const compare = l.comparator(bySize); expect(compare(1, 2)).toBe(1); expect(compare(2, 1)).toBe(-1); expect(compare(0, 0)).toBe(0); }); }); describe("complement()", () => { it("inverts predicates", () => { const p = (a: number) => a > 0; expect(p(1)).toBeTruthy(); expect(l.complement(p)(1)).toBeFalsy(); }); }); describe("cond()", () => { it("matches patterns", () => { const findBoundary = l.cond([ [l.gt(3), () => 3], [l.gt(0), () => 0], ]); expect(findBoundary(4)).toBe(3); expect(findBoundary(1)).toBe(0); expect(findBoundary(-1)).toBe(null); }); }); describe("constantly()", () => { it("always returns the value", () => { const c = l.constantly(7); expect(c()).toBe(7); expect(c("foo", "bar")).toBe(7); }); }); describe("converge()", () => { it("transforms stuff", () => { expect(l.converge(l.join(""))([l.toUpper, l.toLower])("foo")).toEqual("FOOfoo"); expect(l.converge(l.sum)([l.max, l.min, l.mean])([1, 2, 3, 4, 5])).toBe(9); }); }); describe("countBy()", () => { it("counts by mappable", () => { expect(l.countBy(l.gt(2))(l.range(0, 6))).toEqual({ true: 3, false: 3 }); const grade = l.prop("grade"); const students = [ { name: "fred", grade: "A" }, { name: "adrian", grade: "D" }, { name: "john", grade: "A" }, { name: "peter", grade: "C" }, ]; expect(l.countBy(grade)(students)).toEqual({ A: 2, C: 1, D: 1 }); }); }); describe("dissoc()", () => { it("removes a key from an object", () => { const dict = { foo: 1, bar: 2 }; expect(l.dissoc("foo")(dict)).toEqual({ bar: 2 }); }); it("should remove items from arrays", () => { expect(l.dissoc(1)(["foo", "bar", "baz"])).toEqual(["foo", "baz"]); expect(l.dissoc(0)([1])).toEqual([]); expect(l.dissoc(19, [])).toEqual([]); expect(l.dissoc(19, [1])).toEqual([1]); }); }); describe("dissocIn()", () => { it("removes deep elements from objects", () => { const input = { foo: { bar: 1, baz: 2 } }; expect(l.dissocIn(["foo", "baz"])(input)).toEqual({ foo: { bar: 1 } }); }); }); describe("drop()", () => { it("drops first n elements", () => { let rng = l.range(0, 9); expect(l.drop(4)(rng)).toEqual([4, 5, 6, 7, 8]); expect(l.drop(0, rng)).toEqual(rng); expect(l.drop(100, rng)).toEqual([]); expect(l.drop(-1, rng)).toEqual(rng); expect(l.drop(-17, rng)).toEqual(rng); }); }); describe("dropRepeats()", () => { it("removes successive repeats in a list, leaving only one copy", () => { const withRepeats = [0, 1, 1, 1, 2, 3, 4, 4, 3, 2, 2, 1]; const clean = [0, 1, 2, 3, 4, 3, 2, 1]; expect(l.dropRepeats(withRepeats)).toEqual(clean); }); }); describe("dropRepeatsWith()", () => { it("removes successive repeats in a list, identify them with mappable, leaving only one copy", () => { const withRepeats = [0, 1, 1, 1, 2, 3, 4, 4, 3, 2, 2, 1]; const clean = [0, 1, 2, 3, 4, 3, 2, 1]; expect(l.dropRepeatsWith(l.eq)(withRepeats)).toEqual(clean); }); }); describe("dropRight()", () => { it("drops n elements from the end of a list", () => { expect(l.dropRight(3)(l.range(0, 6))).toEqual(l.range(0, 3)); }); }); describe("dropWhile()", () => { it("drops while pred", () => { expect(l.dropWhile(l.lt(3))(l.range(0, 5))).toEqual([3, 4]); }); }); describe("dropRightWhile()", () => { it("drops while pred", () => { const rng = l.range(0, 5); expect(l.dropRightWhile(l.gt(3))(rng)).toEqual([0, 1, 2, 3]); expect(l.dropRightWhile(l.gte(0))(rng)).toEqual(rng); }); }); describe("eitherOr", () => { it("returns first result if trutyh, else second", () => { expect(l.eitherOr(l.identity)(l.T)(false)).toBeTruthy(); expect(l.eitherOr(l.T)(l.identity)(false)).toBeTruthy(); expect(l.eitherOr(l.identity)(l.identity)(false)).toBeFalsy(); }); }); describe("eqProps()", () => { it("tests if two objects have equal props", () => { const objA = { foo: 1, bar: 2 }; const objB = { foo: 1, bar: 3 }; expect(l.eqProps("foo")(objA)(objB)).toBeTruthy(); expect(l.eqProps("bar", objA, objB)).toBeFalsy(); }); }); describe("filter()", () => { const even = (x: number) => x % 2 === 0; it("filters lists", () => { expect(l.filter(even)(l.range(0, 5))).toEqual([0, 2, 4]); }); it("filters object values", () => { const obj = { 0: 0, 1: 1, 2: 2, 3: 3, 4: 4, 5: 5 }; expect(l.filter(even)(obj)).toEqual({ 0: 0, 2: 2, 4: 4 }); }); }); describe("filterKeys()", () => { it("filters objects by testing predicates on their property names", () => { const obj = { foo: 1, bar: 2, foobar: 3 }; expect(l.filterKeys((str) => str.startsWith("foo"))(obj)).toEqual({ foo: 1, foobar: 3, }); }); }); describe("find()", () => { it("finds elements within an array", () => { expect(l.find(l.eq(2))([1, 2, 3])).toEqual(2); expect(l.find(l.eq(4))([1, 2, 3])).toEqual(null); }); }); describe("findIndex()", () => { it("finds elements within an array", () => { expect(l.findIndex(l.eq(2))([1, 2, 3])).toEqual(1); expect(l.findIndex(l.eq(4))([1, 2, 3])).toEqual(-1); }); it("finds elements within an object", () => { expect(l.findIndex(l.eq(2))({ foo: 1, bar: 2, yordle: 3 })).toEqual("bar"); }); }); describe("findLast()", () => { it("finds elements within an array", () => { expect(l.findLast(l.eq(2))([1, 2, 3, 2, 1])).toEqual(2); expect(l.findLast(l.eq(4))([1, 2, 3, 2, 1])).toEqual(null); }); }); describe("findLastIndex()", () => { it("finds elements within an array", () => { expect(l.findLastIndex(l.eq(2))([1, 2, 3, 2, 1])).toEqual(3); expect(l.findLastIndex(l.eq(4))([1, 2, 3, 2, 1])).toEqual(-1); }); it("finds elements within an object", () => { expect(l.findLastIndex(l.eq(2))({ foo: 1, bar: 2, yordle: 2 })).toEqual("yordle"); }); }); describe("first()", () => { it("finds first letter of string", () => { expect(l.first("Hello")).toEqual("H"); }); it("finds first element of list", () => { expect(l.first([1, 2, 3])).toEqual(1); }); }); describe("flatMap()", () => { describe("flatMap()", () => { it("maps over a list and flattens one level of results", () => { expect(l.flatMap(l.inc)([1, 2, [3, 4], 5])).toEqual([2, 3, 4, 5, 6]); }); }); }); describe("forEach()", () => { it("performs side effects on lists", () => { const result: any[] = []; const rng = l.range(0, 5); l.forEach((x) => result.push(x))(rng); expect(result).toEqual(rng); }); it("performs side effects on object values", () => { const result: any[] = []; l.forEach((x) => result.push(x))({ foo: 1, bar: 2, baz: 3 }); expect(result).toEqual([1, 2, 3]); }); }); describe("fromPairs()", () => { it("creates dictionaries from lists", () => { expect( l.fromPairs([ ["foo", 1], ["bar", 2], ]) ).toEqual({ foo: 1, bar: 2 }); }); }); describe("getIn()", () => { it("gets elements deep", () => { const obj = { foo: { bar: [1, 2, 3] } }; expect(l.getIn(["foo", "bar", 2])(obj)).toEqual(3); }); }); describe("getOr()", () => { it("gets elements deep, with default", () => { const obj = { foo: { bar: [1, 2, 3] } }; expect(l.getOr(0)(["foo", "bar", 2])(obj)).toEqual(3); expect(l.getOr(0)(["foo", "bar", 17])(obj)).toEqual(0); }); it("gets elements flat", () => { expect(l.getOr(0)("foo")({ foo: 1 })).toEqual(1); expect(l.getOr(0)("bar")({ foo: 1 })).toEqual(0); expect(l.getOr(0)(1)([1, 2, 3])).toEqual(2); expect(l.getOr(0)(9)([1, 2, 3])).toEqual(0); }); }); describe("groupBy()", () => { it("groups stuff by mappable", () => { const fred = { name: "fred", grade: "A" }; const adrian = { name: "adrian", grade: "D" }; const john = { name: "john", grade: "A" }; const peter = { name: "peter", grade: "C" }; const students = [fred, adrian, john, peter]; expect(l.groupBy(l.prop("grade"))(students)).toEqual({ A: [fred, john], C: [peter], D: [adrian], }); }); }); describe("has()", () => { it("tests if property exists", () => { expect(l.has("foo")({ foo: "bar" })).toBeTruthy(); expect(l.has("bar")({ foo: "bar" })).toBeFalsy(); expect(l.has("foo")({ foo: undefined })).toBeTruthy(); }); }); describe("identical()", () => { it("test for referential identity, aka why is mutation bad", () => { const foo = { foo: 1 }; const bar = { foo: 1 }; expect(l.identical(foo, foo)).toBe(true); expect(l.identical(foo, bar)).toBe(false); const isFoo = l.identical(foo); foo.foo = 18; expect(isFoo(foo)).toBe(true); }); }); describe("lenses", () => { describe("lensProp()", () => { const obj = { foo: 1 }; const barLens = l.lensProp<number>("bar"); expect(l.view(barLens)(obj)).toBe(undefined); expect(l.set(barLens)(2)(obj)).toEqual({ foo: 1, bar: 2 }); expect(l.over(barLens)(l.inc)({ bar: 1 })).toEqual({ bar: 2 }); }); describe("lensIndex()", () => { const arr = [1, 2, 3]; const headLens = l.lensIndex<number>(0); expect(l.view(headLens, arr)).toBe(1); expect(l.set(headLens, 2, arr)).toEqual([2, 2, 3]); expect(l.over(headLens, l.inc, arr)).toEqual([2, 2, 3]); }); describe("lensPath()", () => { const person = { name: "John Doe", tags: { positive: ["anonymous hero"], negative: ["most often in criminal records"], }, }; const posTagLens = l.lensPath(["tags", "positive", 0]); expect(l.view(posTagLens, person)).toEqual("anonymous hero"); expect(l.set(posTagLens, "nice guy", person)).toEqual({ name: "John Doe", tags: { positive: ["nice guy"], negative: ["most often in criminal records"], }, }); expect(l.over(posTagLens, l.toUpper, person)).toEqual({ name: "John Doe", tags: { positive: ["ANONYMOUS HERO"], negative: ["most often in criminal records"], }, }); }); }); describe("ifElse()", () => { expect(l.ifElse(l.eq(1))(l.dec)(l.inc)(1)).toBe(0); expect(l.ifElse(l.eq(1), l.dec, l.inc, -1)).toBe(0); }); describe("includes()", () => { expect(l.includes("foo")("foobar")).toBe(true); expect(l.includes("foo")("fobar")).toBe(false); expect(l.includes(1)([1, 2, 3])).toBe(true); expect(l.includes(5)([1, 2, 3])).toBe(false); }); describe("indexBy()", () => { const list = [ { id: "xyz", title: "A" }, { id: "abc", title: "B" }, ]; const indexed = { abc: { id: "abc", title: "B" }, xyz: { id: "xyz", title: "A" }, }; expect(l.indexBy(l.prop("id"))(list)).toEqual(indexed); }); describe("init()", () => { expect(l.init(l.range(0, 5))).toEqual(l.range(0, 4)); }); describe("invert()", () => { expect(l.invert({ foo: 1, bar: 2 })).toEqual({ 1: ["foo"], 2: ["bar"] }); }); describe("invertObj()", () => { expect(l.invertObj({ foo: 1, bar: 2 })).toEqual({ 1: "foo", 2: "bar" }); }); describe("isIn()", () => { expect(l.isIn([1, 2, 3])(1)).toBe(true); expect(l.isIn([1, 2, 3])(4)).toBe(false); expect(l.isIn("foo")("o")).toBe(true); expect(l.isIn("foo")("x")).toBe(false); }); describe("predicates", () => { expect(l.isPos(1)).toBe(true); expect(l.isPos(0)).toBe(false); expect(l.isPos(-1)).toBe(false); expect(l.isNeg(-1)).toBe(true); expect(l.isNeg(0)).toBe(false); expect(l.isNeg(1)).toBe(false); expect(l.isNumber(1)).toBe(true); expect(l.isNumber(NaN)).toBe(false); expect(l.isNumber("x")).toBe(false); expect(l.isZero(0)).toBe(true); expect(l.isZero(1)).toBe(false); }); describe("keys()", () => { expect(l.keys({ foo: 1, bar: 2 })).toEqual(["foo", "bar"]); expect(l.keys({})).toEqual([]); }); describe("values()", () => { expect(l.values({ foo: 1, bar: 2 })).toEqual([1, 2]); expect(l.values({})).toEqual([]); }); describe("map()", () => { it("maps over arrays", () => { expect(l.map(l.add(2))([1, 2, 3])).toEqual([3, 4, 5]); }); it("maps over object values", () => { expect(l.map(l.inc)({ foo: 1, bar: 2 })).toEqual({ foo: 2, bar: 3 }); }); }); describe("last()", () => { expect(l.last([1, 2, 3])).toBe(3); expect(l.last([])).toBe(null); }); describe("mapKeys()", () => { const obj = { foo: 1, bar: 2 }; const expected = { FOO: 1, BAR: 2 }; expect(l.mapKeys(l.toUpper)(obj)).toEqual(expected); expect(l.mapKeys(l.toUpper)({})).toEqual({}); }); describe("memoizeWith()", () => { let counter = 0; const updateCounter = (n: Number) => { counter += 1; return n; }; const memoized = l.memoizeWith((_: number) => "foo")(updateCounter); const rng = l.range(0, 10); const result = rng.map(memoized); expect(result.length).toEqual(rng.length); result.forEach((r) => expect(r).toBe(0)); expect(counter).toBe(1); }); describe("negate()", () => { expect(l.negate(1)).toBe(-1); expect(l.negate(-1)).toBe(1); }); describe("not()", () => { expect(l.not(true)).toBe(false); expect(l.not(false)).toBe(true); }); describe("partial()", () => { const fn = (a: number, b: number) => a + b; const add5 = l.partial(fn, 5); expect(add5(1)).toBe(6); }); describe("partition()", () => { const rng = l.range(0, 6); const expected = { t: [3, 4, 5], f: [0, 1, 2] }; expect(l.partition(l.gt(2))(rng)).toEqual(expected); }); describe("none()", () => { expect(l.none(l.gt(3))(l.range(0, 4))).toBe(true); expect(l.none(l.gt(3))(l.range(0, 5))).toBe(false); }); describe("nonePass()", () => { expect(l.nonePass([l.gt(3), l.gt(4), l.gt(5)])(3)).toBe(true); expect(l.nonePass([l.isPos, l.gt(3), l.gt(4)])(3)).toBe(false); }); describe("prepend()", () => { expect(l.prepend("foo")("bar")).toEqual("foobar"); expect(l.prepend(0)([1, 2, 3])).toEqual([0, 1, 2, 3]); }); describe("product()", () => { expect(l.product([1, 2, 3, 4])).toEqual(24); }); describe("propEq()", () => { const obj = { foo: 1, bar: { baz: 2, sth: [1, 2, 3] } }; expect(l.propEq("foo")(1)(obj)).toBe(true); expect(l.propEq("baz", 1, obj)).toBe(false); expect(l.propEq(["bar", "baz"], 2, obj)).toBe(true); expect(l.propEq(["bar", "sth", 1], 2, obj)).toBe(true); expect(l.propEq(["bar", "sth", 1], 1, obj)).toBe(false); }); describe("propSatisfies()", () => { const obj = { foo: 1, bar: { baz: 2, sth: [1, 2, 3] } }; expect(l.propSatisfies("foo")(l.gt(1))(obj)).toBe(false); expect(l.propSatisfies("foo")(l.isPos)(obj)).toBe(true); expect(l.propSatisfies(["bar", "baz"], l.gt(1), obj)).toBe(true); expect("prop"); }); describe("props()", () => { const obj = { foo: 1, bar: 2, baz: 3 }; expect(l.props(["foo", "bar"])(obj)).toEqual([1, 2]); }); describe("reduceKV()", () => { const obj = { foo: 1, bar: 2, baz: 3, }; const result = l.reduceKV((accum: string, n: number, k: string) => [...(accum ? [accum] : []), k + ":" + String(n)].join(",") )("")(obj); expect(result).toEqual("foo:1,bar:2,baz:3"); }); describe("reject()", () => { expect(l.reject(even)(l.range(0, 6))).toEqual([1, 3, 5]); expect(l.reject(even)([])).toEqual([]); expect(l.reject(even)(null as any)).toEqual([]); }); describe("some()", () => { expect(l.some(even)(l.range(0, 4))).toBe(true); expect(l.some(l.isNeg)(l.range(0, 4))).toBe(false); expect(l.some(even)(null as any)).toBe(false); }); describe("somePass()", () => { expect(l.somePass([l.isPos, l.isNeg])(1)).toBe(true); expect(l.somePass([even, l.isNeg])(1)).toBe(false); expect(l.somePass(null as any, 1)).toBe(false); }); describe("sort()", () => { const unsorted = ["this", "is", "a", "text"]; const sorted = ["a", "is", "text", "this"]; expect(l.sort(unsorted)).toEqual(sorted); }); describe("sortBy()", () => { const unsorted = [4, 3, 5, 1, 2]; const sorted = [1, 2, 3, 4, 5]; const numeric = (a: number, b: number) => a - b; expect(l.sortBy(numeric)(unsorted)).toEqual(sorted); }); describe("split()", () => { const text = "this is a text"; const words = ["this", "is", "a", "text"]; expect(l.split(" ")(text)).toEqual(words); }); describe("test()", () => { const re = /\d{4}-\d{2}-\d{2}/; expect(l.test(re)("2020-01-01")).toBe(true); expect(l.test(re, "not-a-date")).toBe(false); }); describe("toPairs()", () => { expect(l.toPairs({ foo: 1, bar: 2 })).toEqual([ ["foo", 1], ["bar", 2], ]); }); describe("trim()", () => { expect(l.trim(" text ")).toEqual("text"); }); describe("tryCatch()", () => { const bomb = (_: number): number => { throw "boom"; return 0; }; const catcher = (_: Error, n: number) => { return n + 1; }; expect(l.tryCatch(bomb)(catcher)(1)).toEqual(2); expect(l.tryCatch(l.dec, catcher, 1)).toEqual(0); }); describe("unless()", () => { expect(l.unless(l.isPos)(l.negate)(-1)).toBe(1); expect(l.unless(l.isPos)(l.negate)(1)).toBe(1); }); describe("when()", () => { expect(l.when(l.isNeg)(l.negate)(-1)).toBe(1); expect(l.when(l.isNeg)(l.negate)(1)).toBe(1); }); describe("xor()", () => { expect(l.xor(true)(true)).toBe(false); expect(l.xor(true)(false)).toBe(true); expect(l.xor(false)(true)).toBe(true); expect(l.xor(false)(false)).toBe(false); }); describe("where()", () => { const arr = [ { foo: 1, id: 1 }, { foo: -1, id: 1 }, ]; const noFoo = l.where({ foo: l.isNeg }); expect(l.find<typeof arr[0]>(noFoo, arr)?.id).toBe(1); expect(l.find<typeof arr[0]>(noFoo, [])?.id).toBe(undefined); }); describe("whereEq()", () => { const arr = [ { foo: 1, id: 1 }, { foo: -1, id: 1 }, ]; const noFoo = l.whereEq({ foo: -1 }); expect(l.find<typeof arr[0]>(noFoo)(arr)?.id).toBe(1); expect(l.find<typeof arr[0]>(noFoo)([])?.id).toBe(undefined); }); describe("zip()", () => { const a1 = ["a", "b"]; const a2 = [1, 2, 3, 4, 5]; expect(l.zip(a1)(a2)).toEqual([ ["a", 1], ["b", 2], ]); expect(l.zip(a1, null as any)).toEqual([]); expect(l.zip(null as any, a2)).toEqual([]); expect(l.zip(null as any, null as any)).toEqual([]); }); describe("zipMap()", () => { const a1 = ["a", "b"]; const a2 = [1, 2, 3, 4, 5]; const join = ([txt, n]: [string, number]) => txt + ":" + String(n); expect(l.zipMap(join)(a1)(a2)).toEqual(["a:1", "b:2"]); expect(l.zipMap(join, a1, null as any)).toEqual([]); expect(l.zipMap(join, null as any, a2)).toEqual([]); expect(l.zipMap(join, null as any, null as any)).toEqual([]); }); });