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 array, units, and typestate tests

+172
+95
tests/array.test.ts
··· 1 + import { describe, it, expect } from "bun:test" 2 + import { arr, sortNum, sortBy, nonEmpty, map, filter, reduce, head, last, binarySearchNum, take, drop, pipe } from "../src/index" 3 + 4 + describe("Tracked Arrays", () => { 5 + describe("arr", () => { 6 + it("wraps array with phantom type", () => { 7 + const nums = arr([1, 2, 3]) 8 + expect([...nums]).toEqual([1, 2, 3]) 9 + }) 10 + }) 11 + 12 + describe("sort operations", () => { 13 + it("sortNum sorts numerically", () => { 14 + const result = sortNum(arr([3, 1, 4, 1, 5])) 15 + expect([...result]).toEqual([1, 1, 3, 4, 5]) 16 + }) 17 + 18 + it("sortBy sorts by key function", () => { 19 + const users = arr([{ name: "Bob", age: 30 }, { name: "Alice", age: 25 }]) 20 + const result = sortBy<typeof users[number], never>(u => u.age)(users) 21 + expect(result[0]!.name).toBe("Alice") 22 + }) 23 + }) 24 + 25 + describe("nonEmpty", () => { 26 + it("returns Some for non-empty arrays", () => { 27 + const result = nonEmpty(arr([1])) 28 + expect(result._tag).toBe("Some") 29 + }) 30 + 31 + it("returns None for empty arrays", () => { 32 + const result = nonEmpty(arr([])) 33 + expect(result._tag).toBe("None") 34 + }) 35 + }) 36 + 37 + describe("head and last", () => { 38 + it("head returns first element of NonEmpty array", () => { 39 + const nums = pipe(arr([1, 2, 3]), nonEmpty) 40 + if (nums._tag === "Some") { 41 + expect(head(nums.value)).toBe(1) 42 + } 43 + }) 44 + 45 + it("last returns last element of NonEmpty array", () => { 46 + const nums = pipe(arr([1, 2, 3]), nonEmpty) 47 + if (nums._tag === "Some") { 48 + expect(last(nums.value)).toBe(3) 49 + } 50 + }) 51 + }) 52 + 53 + describe("map, filter, reduce", () => { 54 + it("map transforms elements", () => { 55 + const result = pipe(arr([1, 2, 3]), map(x => x * 2)) 56 + expect([...result]).toEqual([2, 4, 6]) 57 + }) 58 + 59 + it("filter keeps matching elements", () => { 60 + const result = pipe(arr([1, 2, 3, 4]), filter(x => x % 2 === 0)) 61 + expect([...result]).toEqual([2, 4]) 62 + }) 63 + 64 + it("reduce folds array", () => { 65 + const result = pipe(arr([1, 2, 3, 4]), reduce((acc, x) => acc + x, 0)) 66 + expect(result).toBe(10) 67 + }) 68 + }) 69 + 70 + describe("take and drop", () => { 71 + it("take returns first n elements", () => { 72 + const result = pipe(arr([1, 2, 3, 4, 5]), take(3)) 73 + expect([...result]).toEqual([1, 2, 3]) 74 + }) 75 + 76 + it("drop skips first n elements", () => { 77 + const result = pipe(arr([1, 2, 3, 4, 5]), drop(2)) 78 + expect([...result]).toEqual([3, 4, 5]) 79 + }) 80 + }) 81 + 82 + describe("binarySearch", () => { 83 + it("finds element in sorted array", () => { 84 + const sorted = sortNum(arr([5, 2, 8, 1, 9])) 85 + const result = binarySearchNum(5)(sorted) 86 + expect(result._tag).toBe("Some") 87 + }) 88 + 89 + it("returns None when element not found", () => { 90 + const sorted = sortNum(arr([1, 2, 3])) 91 + const result = binarySearchNum(10)(sorted) 92 + expect(result._tag).toBe("None") 93 + }) 94 + }) 95 + })
+35
tests/typestate.test.ts
··· 1 + import { describe, it, expect } from "bun:test" 2 + import { entity, transition, pipe, type Entity } from "../src/index" 3 + 4 + describe("Typestate", () => { 5 + type Doc = { title: string; content: string } 6 + type Draft = Entity<Doc, "draft"> 7 + type Published = Entity<Doc, "published"> 8 + 9 + const createDraft = (title: string): Draft => 10 + entity<Doc, "draft">({ title, content: "" }) 11 + 12 + const edit = (content: string) => 13 + transition<Doc, "draft", "draft">(d => ({ ...d, content })) 14 + 15 + const publish: (d: Draft) => Published = 16 + transition<Doc, "draft", "published">() 17 + 18 + it("creates entities with initial state", () => { 19 + const doc = createDraft("My Post") 20 + expect(doc.title).toBe("My Post") 21 + expect(doc.content).toBe("") 22 + }) 23 + 24 + it("transitions between states", () => { 25 + const draft = createDraft("Post") 26 + const edited = edit("Hello world")(draft) 27 + expect(edited.content).toBe("Hello world") 28 + }) 29 + 30 + it("transitions to different state types", () => { 31 + const published = pipe(createDraft("Post"), edit("Content"), publish) 32 + expect(published.title).toBe("Post") 33 + expect(published.content).toBe("Content") 34 + }) 35 + })
+42
tests/units.test.ts
··· 1 + import { describe, it, expect } from "bun:test" 2 + import { meters, seconds, kilograms, velocity, addQ, subQ, scaleQ } from "../src/index" 3 + 4 + describe("Units", () => { 5 + describe("quantity constructors", () => { 6 + it("creates typed quantities", () => { 7 + const d = meters(100) 8 + const t = seconds(9.58) 9 + const m = kilograms(75) 10 + 11 + expect(d as number).toBe(100) 12 + expect(t as number).toBe(9.58) 13 + expect(m as number).toBe(75) 14 + }) 15 + }) 16 + 17 + describe("velocity", () => { 18 + it("computes velocity from distance and time", () => { 19 + const d = meters(100) 20 + const t = seconds(10) 21 + const v = velocity(d, t) 22 + expect(v as number).toBeCloseTo(10, 5) 23 + }) 24 + }) 25 + 26 + describe("arithmetic operations", () => { 27 + it("addQ adds same-unit quantities", () => { 28 + const result = addQ(meters(50), meters(30)) 29 + expect(result as number).toBe(80) 30 + }) 31 + 32 + it("subQ subtracts same-unit quantities", () => { 33 + const result = subQ(meters(100), meters(40)) 34 + expect(result as number).toBe(60) 35 + }) 36 + 37 + it("scaleQ multiplies by scalar", () => { 38 + const result = scaleQ(3)(meters(10)) 39 + expect(result as number).toBe(30) 40 + }) 41 + }) 42 + })