diff --git a/src/arrays.test.ts b/src/arrays.test.ts new file mode 100644 index 0000000..960c093 --- /dev/null +++ b/src/arrays.test.ts @@ -0,0 +1,40 @@ +import { describe, expect, it } from "vitest"; +import { chunk, dedupe, groupBy } from "./arrays"; + +describe("chunk", () => { + it("splits arrays 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 chunk sizes", () => { + expect(chunk([1, 2, 3], 0)).toEqual([]); + expect(chunk([1, 2, 3], -1)).toEqual([]); + }); +}); + +describe("dedupe", () => { + it("keeps first occurrences in insertion order", () => { + expect(dedupe(["a", "b", "a", "c", "b"])).toEqual(["a", "b", "c"]); + }); +}); + +describe("groupBy", () => { + it("groups values by the key returned from the callback", () => { + const grouped = groupBy( + [ + { type: "fruit", name: "apple" }, + { type: "veg", name: "carrot" }, + { type: "fruit", name: "banana" }, + ], + (item) => item.type, + ); + + expect(grouped).toEqual({ + fruit: [ + { type: "fruit", name: "apple" }, + { type: "fruit", name: "banana" }, + ], + veg: [{ type: "veg", name: "carrot" }], + }); + }); +}); diff --git a/src/dates.test.ts b/src/dates.test.ts new file mode 100644 index 0000000..732eb06 --- /dev/null +++ b/src/dates.test.ts @@ -0,0 +1,41 @@ +import { describe, expect, it } from "vitest"; +import { addDays, daysBetween, isWeekend, startOfDayUTC } from "./dates"; + +describe("daysBetween", () => { + it("returns rounded absolute day differences", () => { + const start = new Date("2026-01-01T00:00:00.000Z"); + const end = new Date("2026-01-03T12:00:00.000Z"); + + expect(daysBetween(start, end)).toBe(3); + expect(daysBetween(end, start)).toBe(3); + }); +}); + +describe("isWeekend", () => { + it("detects Saturday and Sunday as weekends", () => { + expect(isWeekend(new Date("2026-05-30T12:00:00.000Z"))).toBe(true); + expect(isWeekend(new Date("2026-05-31T12:00:00.000Z"))).toBe(true); + }); + + it("detects weekdays as non-weekends", () => { + expect(isWeekend(new Date("2026-05-29T12:00:00.000Z"))).toBe(false); + }); +}); + +describe("addDays", () => { + it("returns a new date advanced by the requested number of days", () => { + const original = new Date("2026-01-10T00:00:00.000Z"); + const result = addDays(original, 5); + + expect(result.toISOString()).toBe("2026-01-15T00:00:00.000Z"); + expect(original.toISOString()).toBe("2026-01-10T00:00:00.000Z"); + }); +}); + +describe("startOfDayUTC", () => { + it("returns midnight UTC for the given date", () => { + const result = startOfDayUTC(new Date("2026-05-31T19:45:30.000Z")); + + expect(result.toISOString()).toBe("2026-05-31T00:00:00.000Z"); + }); +}); diff --git a/src/money.test.ts b/src/money.test.ts new file mode 100644 index 0000000..a40f2c2 --- /dev/null +++ b/src/money.test.ts @@ -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 currency symbols", () => { + expect(formatCents(1234)).toBe("$12.34"); + expect(formatCents(1234, "EUR")).toBe("€12.34"); + }); + + it("prefixes unsupported currencies with the currency code", () => { + expect(formatCents(1234, "GBP")).toBe("GBP 12.34"); + }); +}); + +describe("platformFeeCents", () => { + it("returns zero when gross amount or basis points are non-positive", () => { + expect(platformFeeCents(0, 250)).toBe(0); + expect(platformFeeCents(1000, 0)).toBe(0); + }); + + it("rounds percentage fees to the nearest cent", () => { + expect(platformFeeCents(999, 250)).toBe(25); + }); +}); + +describe("netPayoutCents", () => { + it("subtracts the calculated platform fee", () => { + expect(netPayoutCents(1000, 250)).toBe(975); + }); +}); + +describe("splitEvenly", () => { + it("splits cents evenly and distributes the remainder first", () => { + expect(splitEvenly(10, 3)).toEqual([4, 3, 3]); + }); + + it("returns an empty list for non-positive recipient counts", () => { + expect(splitEvenly(10, 0)).toEqual([]); + expect(splitEvenly(10, -1)).toEqual([]); + }); +}); diff --git a/src/strings.test.ts b/src/strings.test.ts index daeb1cc..6148c10 100644 --- a/src/strings.test.ts +++ b/src/strings.test.ts @@ -1,7 +1,49 @@ -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("removes punctuation and collapses duplicate dashes", () => { + expect(slugify(" Hello, --- Tiny Utils!!! ")).toBe("hello-tiny-utils"); + }); +}); + +describe("truncate", () => { + it("returns an empty string for non-positive limits", () => { + expect(truncate("hello", 0)).toBe(""); + expect(truncate("hello", -1)).toBe(""); + }); + + it("returns short input unchanged", () => { + expect(truncate("hello", 5)).toBe("hello"); + }); + + it("adds a suffix when input exceeds the limit", () => { + expect(truncate("hello world", 8, "...")).toBe("hello..."); + }); + + it("handles suffixes longer than the limit", () => { + expect(truncate("hello", 2, "...")).toBe("..."); + }); +}); + +describe("titleCase", () => { + it("capitalizes each word and lowercases the rest", () => { + expect(titleCase("hELLO tiny UTILS")).toBe("Hello Tiny Utils"); + }); + + it("preserves spacing behavior from split and join", () => { + expect(titleCase("hello world")).toBe("Hello World"); + }); +}); + +describe("escapeHtml", () => { + it("escapes HTML-sensitive characters", () => { + expect(escapeHtml(`Tom & "Jerry" 's>`)).toBe( + "Tom & "Jerry" <cat>'s>", + ); + }); });