An educational pure functional programming library in TypeScript
2
fork

Configure Feed

Select the types of activity you want to include in your feed.

Add pattern matching, composition, and guards tests

+149
+47
tests/composition.test.ts
··· 1 + import { describe, it, expect } from "bun:test" 2 + import { pipe, flow, id, constant, flip, tap } from "../src/index" 3 + 4 + describe("Composition", () => { 5 + describe("pipe", () => { 6 + it("pipes single value", () => { 7 + expect(pipe(5)).toBe(5) 8 + }) 9 + 10 + it("pipes through functions", () => { 11 + const result = pipe(5, x => x + 1, x => x * 2, x => `result: ${x}`) 12 + expect(result).toBe("result: 12") 13 + }) 14 + }) 15 + 16 + describe("flow", () => { 17 + it("composes functions left-to-right", () => { 18 + const process = flow((x: number) => x + 1, x => x * 2, x => `result: ${x}`) 19 + expect(process(5)).toBe("result: 12") 20 + }) 21 + }) 22 + 23 + describe("utilities", () => { 24 + it("id returns input unchanged", () => { 25 + expect(id(42)).toBe(42) 26 + }) 27 + 28 + it("constant returns a function that always returns the value", () => { 29 + const always5 = constant(5) 30 + expect(always5()).toBe(5) 31 + }) 32 + 33 + it("flip swaps curried function arguments", () => { 34 + const sub = (a: number) => (b: number) => a - b 35 + const flipped = flip(sub) 36 + expect(sub(10)(3)).toBe(7) 37 + expect(flipped(3)(10)).toBe(7) 38 + }) 39 + 40 + it("tap performs side effect and returns value", () => { 41 + let sideEffect = 0 42 + const result = tap<number>(x => { sideEffect = x })(42) 43 + expect(result).toBe(42) 44 + expect(sideEffect).toBe(42) 45 + }) 46 + }) 47 + })
+37
tests/guards.test.ts
··· 1 + import { describe, it, expect } from "bun:test" 2 + import { isString, isNumber, isBoolean, and, or, isPositive, isInteger } from "../src/index" 3 + 4 + describe("Guards", () => { 5 + describe("primitive guards", () => { 6 + it("isString checks for strings", () => { 7 + expect(isString("hello")).toBe(true) 8 + expect(isString(123)).toBe(false) 9 + }) 10 + 11 + it("isNumber checks for numbers", () => { 12 + expect(isNumber(42)).toBe(true) 13 + expect(isNumber("42")).toBe(false) 14 + }) 15 + 16 + it("isBoolean checks for booleans", () => { 17 + expect(isBoolean(true)).toBe(true) 18 + expect(isBoolean(0)).toBe(false) 19 + }) 20 + }) 21 + 22 + describe("compound guards", () => { 23 + it("and combines guards with intersection", () => { 24 + const isPositiveNumber = and(isNumber, isPositive) 25 + expect(isPositiveNumber(5)).toBe(true) 26 + expect(isPositiveNumber(-5)).toBe(false) 27 + expect(isPositiveNumber("5")).toBe(false) 28 + }) 29 + 30 + it("or combines guards with union", () => { 31 + const isStringOrNumber = or(isString, isNumber) 32 + expect(isStringOrNumber("hello")).toBe(true) 33 + expect(isStringOrNumber(42)).toBe(true) 34 + expect(isStringOrNumber(true)).toBe(false) 35 + }) 36 + }) 37 + })
+65
tests/pattern-matching.test.ts
··· 1 + import { describe, it, expect } from "bun:test" 2 + import { match, matchOr, when, matchResult, matchOption, ok, err, some, none } from "../src/index" 3 + 4 + describe("Pattern Matching", () => { 5 + type Shape = 6 + | { _tag: "Circle"; radius: number } 7 + | { _tag: "Rectangle"; width: number; height: number } 8 + 9 + describe("match", () => { 10 + it("matches all variants exhaustively", () => { 11 + const area = (shape: Shape) => 12 + match(shape)({ 13 + Circle: ({ radius }) => Math.PI * radius * radius, 14 + Rectangle: ({ width, height }) => width * height, 15 + }) 16 + 17 + expect(area({ _tag: "Circle", radius: 2 })).toBeCloseTo(12.566, 2) 18 + expect(area({ _tag: "Rectangle", width: 3, height: 4 })).toBe(12) 19 + }) 20 + }) 21 + 22 + describe("matchOr", () => { 23 + it("uses default for unhandled variants", () => { 24 + const describe = matchOr<Shape, string>("unknown") 25 + const result = describe({ _tag: "Rectangle", width: 1, height: 1 })({ Circle: () => "a circle" }) 26 + expect(result).toBe("unknown") 27 + }) 28 + }) 29 + 30 + describe("when", () => { 31 + it("matches predicates in order", () => { 32 + const grade = (score: number) => 33 + when(score)( 34 + [s => s >= 90, () => "A"], 35 + [s => s >= 80, () => "B"], 36 + )(() => "F") 37 + 38 + expect(grade(95)).toBe("A") 39 + expect(grade(85)).toBe("B") 40 + expect(grade(65)).toBe("F") 41 + }) 42 + }) 43 + 44 + describe("matchResult", () => { 45 + it("matches Ok and Err", () => { 46 + const format = matchResult<number, string, string>( 47 + v => `value: ${v}`, 48 + e => `error: ${e}` 49 + ) 50 + expect(format(ok(42))).toBe("value: 42") 51 + expect(format(err("fail"))).toBe("error: fail") 52 + }) 53 + }) 54 + 55 + describe("matchOption", () => { 56 + it("matches Some and None", () => { 57 + const format = matchOption<number, string>( 58 + v => `found: ${v}`, 59 + () => "empty" 60 + ) 61 + expect(format(some(42))).toBe("found: 42") 62 + expect(format(none)).toBe("empty") 63 + }) 64 + }) 65 + })