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
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"type": "module",
"scripts": {
"test": "vitest run",
"test:coverage": "vitest run --coverage --coverage.reporter=text-summary --coverage.include='src/**/*.ts' --coverage.exclude='src/**/*.test.ts'"
"test:coverage": "vitest run --coverage --coverage.reporter=text-summary --coverage.include=\"src/**/*.ts\" --coverage.exclude=\"src/**/*.test.ts\""
},
"devDependencies": {
"@vitest/coverage-v8": "^2.0.0",
Expand Down
36 changes: 36 additions & 0 deletions src/arrays.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { describe, expect, it } from "vitest";
import { chunk, dedupe, groupBy } from "./arrays";

describe("array utilities", () => {
it("splits arrays into fixed-size chunks and keeps a short final chunk", () => {
expect(chunk([1, 2, 3, 4, 5], 2)).toEqual([[1, 2], [3, 4], [5]]);
});

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

it("deduplicates values while preserving first-seen order", () => {
expect(dedupe(["api", "web", "api", "cli"])).toEqual(["api", "web", "cli"]);
});

it("groups items by the provided key function", () => {
const grouped = groupBy(
[
{ owner: "alice", task: "docs" },
{ owner: "bob", task: "tests" },
{ owner: "alice", task: "review" },
],
(item) => item.owner,
);

expect(grouped).toEqual({
alice: [
{ owner: "alice", task: "docs" },
{ owner: "alice", task: "review" },
],
bob: [{ owner: "bob", task: "tests" }],
});
});
});
31 changes: 31 additions & 0 deletions src/dates.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { describe, expect, it } from "vitest";
import { addDays, daysBetween, isWeekend, startOfDayUTC } from "./dates";

describe("date utilities", () => {
it("returns the absolute rounded day distance between dates", () => {
const start = new Date("2026-05-01T12:00:00Z");
const end = new Date("2026-05-04T11:00:00Z");

expect(daysBetween(start, end)).toBe(3);
expect(daysBetween(end, start)).toBe(3);
});

it("detects weekend and weekday dates", () => {
expect(isWeekend(new Date("2026-05-30T12:00:00Z"))).toBe(true);
expect(isWeekend(new Date("2026-06-01T12:00:00Z"))).toBe(false);
});

it("adds days without mutating the original date", () => {
const original = new Date("2026-05-31T10:00:00Z");
const result = addDays(original, 2);

expect(result.toISOString()).toBe("2026-06-02T10:00:00.000Z");
expect(original.toISOString()).toBe("2026-05-31T10:00:00.000Z");
});

it("normalizes a date to the start of its UTC day", () => {
expect(startOfDayUTC(new Date("2026-05-31T23:59:59Z")).toISOString()).toBe(
"2026-05-31T00:00:00.000Z",
);
});
});
35 changes: 35 additions & 0 deletions src/money.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { describe, expect, it } from "vitest";
import { formatCents, netPayoutCents, platformFeeCents, splitEvenly } from "./money";

describe("money utilities", () => {
it("formats cents as USD by default", () => {
expect(formatCents(1234)).toBe("$12.34");
});

it("formats non-USD currencies with the amount preserved", () => {
expect(formatCents(1234, "EUR")).toContain("12.34");
expect(formatCents(1234, "ZEC")).toBe("ZEC 12.34");
});

it("calculates platform fees from basis points", () => {
expect(platformFeeCents(10_00, 250)).toBe(25);
expect(platformFeeCents(999, 333)).toBe(33);
});

it("does not charge fees for non-positive gross amounts or fee rates", () => {
expect(platformFeeCents(0, 250)).toBe(0);
expect(platformFeeCents(10_00, 0)).toBe(0);
});

it("subtracts the platform fee from the gross payout", () => {
expect(netPayoutCents(10_00, 2000)).toBe(800);
});

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

it("returns no payouts when recipient count is not positive", () => {
expect(splitEvenly(10, 0)).toEqual([]);
});
});
52 changes: 46 additions & 6 deletions src/strings.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,47 @@
import { describe, it, expect } from "vitest";
import { slugify } from "./strings";
describe("slugify", () => {
it("lowercases and dashifies basic input", () => {
expect(slugify("Hello World")).toBe("hello-world");
import { describe, expect, it } from "vitest";
import { escapeHtml, slugify, titleCase, truncate } from "./strings";

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

it("removes punctuation and trims repeated dashes", () => {
expect(slugify(" TaskBounty: Coverage ++ Uplift!!! ")).toBe("taskbounty-coverage-uplift");
});
});
});

describe("truncate", () => {
it("returns an empty string for non-positive maximum lengths", () => {
expect(truncate("coverage", 0)).toBe("");
expect(truncate("coverage", -1)).toBe("");
});

it("leaves strings within the maximum length unchanged", () => {
expect(truncate("short", 10)).toBe("short");
});

it("shortens long strings and appends the configured suffix", () => {
expect(truncate("sandbox verified", 10, "...")).toBe("sandbox...");
});
});

describe("titleCase", () => {
it("capitalizes words and lowercases the remaining letters", () => {
expect(titleCase("mIXed CASE words")).toBe("Mixed Case Words");
});

it("collapses repeated whitespace while title-casing words", () => {
expect(titleCase("Agent Memory")).toBe("Agent Memory");
});
});

describe("escapeHtml", () => {
it("escapes special HTML characters", () => {
expect(escapeHtml("<span title='\"bounty\"'>&</span>")).toBe(
"&lt;span title=&#39;&quot;bounty&quot;&#39;&gt;&amp;&lt;/span&gt;",
);
});
});
});