import { test } from "node:test"; import assert from "node:assert/strict"; import { formatBaseValue, formatRate, isDurationUnit, perDayRate, splitDuration, toBaseUnit, unitLabel, type DurationUnit, } from "./duration"; // Run with: pnpm --filter yt-dlp-transcript-common test // (or `./node_modules/.bin/tsx --test lib/duration.test.ts` from common/) const MHD: DurationUnit[] = ["minutes", "hours", "days"]; test("splitDuration picks the largest unit that divides evenly", () => { assert.deepEqual(splitDuration(1440, MHD), { amount: 1, unit: "days" }); assert.deepEqual(splitDuration(60, MHD), { amount: 1, unit: "hours" }); assert.deepEqual(splitDuration(120, MHD), { amount: 2, unit: "hours" }); assert.deepEqual(splitDuration(10080, MHD), { amount: 7, unit: "days" }); assert.deepEqual(splitDuration(10080, ["minutes", "hours", "days", "weeks"]), { amount: 1, unit: "weeks", }); }); test("splitDuration never rounds a hand-set value", () => { // 137 divides by nothing above minutes, so it stays 137 minutes. assert.deepEqual(splitDuration(137, MHD), { amount: 137, unit: "minutes" }); // 90 is 1.5h — hours would have to round, so minutes wins. assert.deepEqual(splitDuration(90, MHD), { amount: 90, unit: "minutes" }); assert.deepEqual(splitDuration(1441, MHD), { amount: 1441, unit: "minutes" }); }); test("splitDuration falls back to baseUnit even when it isn't offered", () => { // The full-sweep field offers only hours and days, but a hand-edited 137 must // still round-trip unchanged rather than snapping to 2 hours. assert.deepEqual(splitDuration(137, ["hours", "days"], "minutes"), { amount: 137, unit: "minutes", }); assert.deepEqual(splitDuration(1440, ["hours", "days"], "minutes"), { amount: 1, unit: "days", }); }); test("splitDuration handles a seconds base unit", () => { const SM: DurationUnit[] = ["seconds", "minutes"]; assert.deepEqual(splitDuration(300, SM, "seconds"), { amount: 5, unit: "minutes", }); assert.deepEqual(splitDuration(45, SM, "seconds"), { amount: 45, unit: "seconds", }); assert.deepEqual(splitDuration(14400, ["seconds", "minutes", "hours"], "seconds"), { amount: 4, unit: "hours", }); }); test("splitDuration passes sentinels through in the base unit", () => { assert.deepEqual(splitDuration(0, MHD), { amount: 0, unit: "minutes" }); assert.deepEqual(splitDuration(0, ["hours", "days"], "minutes"), { amount: 0, unit: "minutes", }); assert.deepEqual(splitDuration(Number.NaN, MHD), { amount: 0, unit: "minutes", }); // Units smaller than the base unit are never chosen. assert.deepEqual(splitDuration(5, ["seconds", "minutes"], "minutes"), { amount: 5, unit: "minutes", }); }); test("toBaseUnit converts both directions across the base", () => { assert.equal(toBaseUnit(1, "days", "minutes"), 1440); assert.equal(toBaseUnit(36, "hours", "minutes"), 2160); assert.equal(toBaseUnit(137, "minutes", "minutes"), 137); assert.equal(toBaseUnit(5, "minutes", "seconds"), 300); assert.equal(toBaseUnit(1, "weeks", "minutes"), 10080); assert.equal(toBaseUnit(Number.NaN, "hours", "minutes"), 0); }); test("round-trip: toBaseUnit(splitDuration(n)) === n", () => { for (const base of ["minutes", "seconds"] as DurationUnit[]) { const units: DurationUnit[] = base === "seconds" ? ["seconds", "minutes", "hours"] : ["minutes", "hours", "days", "weeks"]; for (const n of [ 1, 2, 7, 15, 30, 45, 60, 90, 120, 137, 300, 360, 720, 1439, 1440, 2160, 4320, 10080, 14400, 43200, 44640, ]) { const { amount, unit } = splitDuration(n, units, base); assert.equal( toBaseUnit(amount, unit, base), n, `round-trip failed for ${n} ${base} (${amount} ${unit})`, ); } } }); test("unitLabel singularises at exactly 1", () => { assert.equal(unitLabel("days", 1), "day"); assert.equal(unitLabel("days", 2), "days"); assert.equal(unitLabel("days", 0), "days"); assert.equal(unitLabel("minutes"), "minutes"); assert.equal(unitLabel("hours", 1), "hour"); }); test("formatBaseValue keeps the raw stored number visible", () => { assert.equal(formatBaseValue(1440, "minutes"), "1,440 minutes"); assert.equal(formatBaseValue(1, "minutes"), "1 minute"); assert.equal(formatBaseValue(0, "minutes"), "0 minutes"); assert.equal(formatBaseValue(90, "seconds"), "90 seconds"); }); test("perDayRate / formatRate describe the cadence as events per day", () => { assert.equal(perDayRate(1440, "minutes"), 1); assert.equal(perDayRate(60, "minutes"), 24); assert.equal(formatRate(perDayRate(2160, "minutes")), "0.7"); assert.equal(formatRate(perDayRate(10080, "minutes")), "0.1"); assert.equal(formatRate(perDayRate(44640, "minutes")), "<0.1"); assert.equal(formatRate(perDayRate(0, "minutes")), "0"); assert.equal(formatRate(perDayRate(10, "minutes")), "144"); }); test("isDurationUnit guards the unit