From 142c916991ca8d6aedcc2d41255bdafe96d76e98 Mon Sep 17 00:00:00 2001 From: Ami Pham Date: Wed, 27 May 2026 14:21:45 +0700 Subject: [PATCH] Add coverage tests for tiny utils --- src/arrays.test.ts | 34 ++++++++++++++++++++++++++++++++++ src/dates.test.ts | 39 +++++++++++++++++++++++++++++++++++++++ src/money.test.ts | 41 +++++++++++++++++++++++++++++++++++++++++ src/strings.test.ts | 38 +++++++++++++++++++++++++++++++++++++- 4 files changed, 151 insertions(+), 1 deletion(-) create mode 100644 src/arrays.test.ts create mode 100644 src/dates.test.ts create mode 100644 src/money.test.ts diff --git a/src/arrays.test.ts b/src/arrays.test.ts new file mode 100644 index 0000000..d067273 --- /dev/null +++ b/src/arrays.test.ts @@ -0,0 +1,34 @@ +import { describe, expect, it } from "vitest"; +import { chunk, dedupe, groupBy } from "./arrays"; + +describe("chunk", () => { + it("splits arrays into fixed-size groups", () => { + expect(chunk([1, 2, 3, 4, 5], 2)).toEqual([[1, 2], [3, 4], [5]]); + }); + + it("returns an empty array for invalid 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 items by the selected key", () => { + const rows = [ + { kind: "fruit", name: "apple" }, + { kind: "veg", name: "carrot" }, + { kind: "fruit", name: "pear" }, + ]; + + expect(groupBy(rows, (row) => row.kind)).toEqual({ + fruit: [rows[0], rows[2]], + veg: [rows[1]], + }); + }); +}); diff --git a/src/dates.test.ts b/src/dates.test.ts new file mode 100644 index 0000000..c87e427 --- /dev/null +++ b/src/dates.test.ts @@ -0,0 +1,39 @@ +import { describe, expect, it } from "vitest"; +import { addDays, daysBetween, isWeekend, startOfDayUTC } from "./dates"; + +describe("daysBetween", () => { + it("returns rounded absolute day difference", () => { + const a = new Date("2026-05-27T00:00:00Z"); + const b = new Date("2026-05-29T13:00:00Z"); + + expect(daysBetween(a, b)).toBe(3); + expect(daysBetween(b, a)).toBe(3); + }); +}); + +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("returns a new date offset by the requested day count", () => { + const original = new Date("2026-05-27T08:30:00Z"); + const result = addDays(original, 5); + + expect(result.toISOString()).toBe("2026-06-01T08:30:00.000Z"); + expect(original.toISOString()).toBe("2026-05-27T08:30:00.000Z"); + }); +}); + +describe("startOfDayUTC", () => { + it("normalizes to midnight UTC", () => { + expect(startOfDayUTC(new Date("2026-05-27T23:59:30Z")).toISOString()).toBe("2026-05-27T00:00:00.000Z"); + }); +}); diff --git a/src/money.test.ts b/src/money.test.ts new file mode 100644 index 0000000..3ceb2cb --- /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 unknown currency codes", () => { + expect(formatCents(500, "GBP")).toBe("GBP 5.00"); + }); +}); + +describe("platformFeeCents", () => { + it("rounds basis point fees", () => { + expect(platformFeeCents(999, 250)).toBe(25); + }); + + it("returns zero for non-positive gross or bps", () => { + expect(platformFeeCents(0, 250)).toBe(0); + expect(platformFeeCents(1000, 0)).toBe(0); + }); +}); + +describe("netPayoutCents", () => { + it("subtracts platform fee from the gross amount", () => { + expect(netPayoutCents(1000, 2000)).toBe(800); + }); +}); + +describe("splitEvenly", () => { + it("splits remainder cents across the earliest recipients", () => { + 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, -2)).toEqual([]); + }); +}); diff --git a/src/strings.test.ts b/src/strings.test.ts index daeb1cc..ee9dcf0 100644 --- a/src/strings.test.ts +++ b/src/strings.test.ts @@ -1,7 +1,43 @@ import { describe, it, expect } from "vitest"; -import { slugify } from "./strings"; +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 repeated dashes", () => { + expect(slugify(" Hello, Tiny---Utils! ")).toBe("hello-tiny-utils"); + }); +}); + +describe("truncate", () => { + it("returns an empty string for non-positive max length", () => { + expect(truncate("hello", 0)).toBe(""); + expect(truncate("hello", -3)).toBe(""); + }); + + it("keeps short input unchanged", () => { + expect(truncate("short", 10)).toBe("short"); + }); + + it("uses the suffix within the max length", () => { + expect(truncate("abcdef", 5, "...")).toBe("ab..."); + }); +}); + +describe("titleCase", () => { + it("capitalizes each whitespace-separated word", () => { + expect(titleCase("hello WORLD tiny")).toBe("Hello World Tiny"); + }); + + it("preserves empty segments when input has leading whitespace", () => { + expect(titleCase(" mixed")).toBe(" Mixed"); + }); +}); + +describe("escapeHtml", () => { + it("escapes common HTML-sensitive characters", () => { + expect(escapeHtml(`Tom & "Sue" '`)).toBe("Tom & "Sue" <tag>'"); + }); });