Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions src/arrays.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { describe, expect, it } from "vitest";
import { chunk, dedupe, groupBy } from "./arrays";

describe("chunk", () => {
it("splits an array into fixed-size chunks", () => {
expect(chunk([1, 2, 3, 4, 5], 2)).toEqual([[1, 2], [3, 4], [5]]);
});

it("returns an empty array for non-positive sizes", () => {
expect(chunk([1, 2, 3], 0)).toEqual([]);
expect(chunk([1, 2, 3], -1)).toEqual([]);
});

it("returns no chunks for an empty input", () => {
expect(chunk([], 3)).toEqual([]);
});
});

describe("dedupe", () => {
it("keeps the first occurrence order", () => {
expect(dedupe(["a", "b", "a", "c", "b"])).toEqual(["a", "b", "c"]);
});

it("uses Set identity semantics for objects", () => {
const shared = { id: 1 };
const other = { id: 1 };
expect(dedupe([shared, other, shared])).toEqual([shared, other]);
});
});

describe("groupBy", () => {
it("groups values by string keys", () => {
const result = groupBy(["apple", "apricot", "banana"], (x) => x[0]);
expect(result).toEqual({ a: ["apple", "apricot"], b: ["banana"] });
});

it("groups values by numeric keys", () => {
const result = groupBy([1, 2, 3, 4], (x) => x % 2);
expect(result).toEqual({ 0: [2, 4], 1: [1, 3] });
});
});
44 changes: 44 additions & 0 deletions src/dates.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { describe, expect, it } from "vitest";
import { addDays, daysBetween, isWeekend, startOfDayUTC } from "./dates";

describe("daysBetween", () => {
it("returns the absolute day distance", () => {
const a = new Date("2026-05-01T00:00:00Z");
const b = new Date("2026-05-04T00:00:00Z");
expect(daysBetween(a, b)).toBe(3);
expect(daysBetween(b, a)).toBe(3);
});

it("rounds partial days to the nearest whole day", () => {
const a = new Date("2026-05-01T00:00:00Z");
const b = new Date("2026-05-02T13:00:00Z");
expect(daysBetween(a, b)).toBe(2);
});
});

describe("isWeekend", () => {
it("detects Saturday and Sunday", () => {
expect(isWeekend(new Date("2026-05-30T12:00:00Z"))).toBe(true);
expect(isWeekend(new Date("2026-05-31T12:00:00Z"))).toBe(true);
});

it("returns false for weekdays", () => {
expect(isWeekend(new Date("2026-05-27T12:00:00Z"))).toBe(false);
});
});

describe("addDays", () => {
it("adds positive and negative day offsets without mutating input", () => {
const input = new Date("2026-05-27T10:30:00Z");
expect(addDays(input, 2).toISOString()).toBe("2026-05-29T10:30:00.000Z");
expect(addDays(input, -3).toISOString()).toBe("2026-05-24T10:30:00.000Z");
expect(input.toISOString()).toBe("2026-05-27T10:30:00.000Z");
});
});

describe("startOfDayUTC", () => {
it("returns midnight UTC for the supplied date", () => {
const result = startOfDayUTC(new Date("2026-05-27T23:59:59Z"));
expect(result.toISOString()).toBe("2026-05-27T00:00:00.000Z");
});
});
41 changes: 41 additions & 0 deletions src/money.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { describe, expect, it } from "vitest";
import { formatCents, netPayoutCents, platformFeeCents, splitEvenly } from "./money";

describe("formatCents", () => {
it("formats USD and EUR with symbols", () => {
expect(formatCents(12345)).toBe("$123.45");
expect(formatCents(12345, "EUR")).toBe("€123.45");
});

it("falls back to the currency code for other currencies", () => {
expect(formatCents(987, "GBP")).toBe("GBP 9.87");
});
});

describe("platformFeeCents", () => {
it("rounds basis-point fees to whole cents", () => {
expect(platformFeeCents(999, 250)).toBe(25);
});

it("returns zero for non-positive gross amounts or rates", () => {
expect(platformFeeCents(0, 250)).toBe(0);
expect(platformFeeCents(1000, 0)).toBe(0);
});
});

describe("netPayoutCents", () => {
it("subtracts the platform fee from the gross amount", () => {
expect(netPayoutCents(10_000, 250)).toBe(9_750);
});
});

describe("splitEvenly", () => {
it("splits cents evenly and distributes the remainder first", () => {
expect(splitEvenly(10, 3)).toEqual([4, 3, 3]);
});

it("returns an empty split for non-positive participant counts", () => {
expect(splitEvenly(10, 0)).toEqual([]);
expect(splitEvenly(10, -2)).toEqual([]);
});
});
49 changes: 45 additions & 4 deletions src/strings.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,48 @@
import { describe, it, expect } from "vitest";
import { slugify } from "./strings";
import { describe, expect, it } from "vitest";
import { escapeHtml, slugify, titleCase, truncate } from "./strings";

describe("slugify", () => {
it("lowercases and dashifies basic input", () => {
expect(slugify("Hello World")).toBe("hello-world");
it("lowercases, trims, and dashifies basic input", () => {
expect(slugify(" Hello World ")).toBe("hello-world");
});

it("removes punctuation and collapses repeated separators", () => {
expect(slugify("Ship fast!!! With---tests")).toBe("ship-fast-with-tests");
});
});

describe("truncate", () => {
it("returns short strings unchanged", () => {
expect(truncate("short", 10)).toBe("short");
});

it("adds a suffix when the input exceeds max length", () => {
expect(truncate("abcdefgh", 6, "...")).toBe("abc...");
});

it("handles non-positive max lengths", () => {
expect(truncate("abcdefgh", 0)).toBe("");
});

it("does not slice below zero when suffix is longer than max", () => {
expect(truncate("abcdefgh", 2, "...")).toBe("...");
});
});

describe("titleCase", () => {
it("capitalizes words and normalizes casing", () => {
expect(titleCase("hello WORLD from TESTS")).toBe("Hello World From Tests");
});

it("preserves spacing behavior from split and join", () => {
expect(titleCase(" hello world ")).toBe(" Hello World ");
});
});

describe("escapeHtml", () => {
it("escapes all supported HTML-sensitive characters", () => {
expect(escapeHtml(`Tom & "Jerry" <cat>'s tail>`)).toBe(
"Tom &amp; &quot;Jerry&quot; &lt;cat&gt;&#39;s tail&gt;",
);
});
});