Pure function: getOrderAge(created, now?) returns {daysOld, bucket, label}
in calendar-day delta for Africa/Johannesburg. bucketToBadgeClass maps
to tailwind classes (fresh/recent/slipping/stale). No React, no DB.
Vitest unit tests cover all 5 bucket thresholds, future-date clamp, and
the JNB timezone edge case (same calendar day in JNB = daysOld 0).
Co-Authored-By: Claude Code <noreply@anthropic.com>
114 lines
No EOL
4.7 KiB
TypeScript
114 lines
No EOL
4.7 KiB
TypeScript
import { describe, it, expect } from "vitest";
|
|
import { getOrderAge, bucketToBadgeClass } from "./order-age";
|
|
|
|
describe("getOrderAge", () => {
|
|
// Anchor `now` so the tests don't drift with wall-clock time.
|
|
// 2026-09-01 12:00 in Africa/Johannesburg = 2026-09-01T10:00:00Z
|
|
const NOW = new Date("2026-09-01T10:00:00.000Z");
|
|
|
|
it("returns daysOld=0 with label 'today' when created today", () => {
|
|
const result = getOrderAge("2026-09-01T08:00:00.000Z", NOW);
|
|
expect(result).toEqual({ daysOld: 0, bucket: "fresh", label: "today" });
|
|
});
|
|
|
|
it("returns daysOld=1 with bucket fresh for 1-day-old orders", () => {
|
|
const result = getOrderAge("2026-08-31T08:00:00.000Z", NOW);
|
|
expect(result.daysOld).toBe(1);
|
|
expect(result.bucket).toBe("fresh");
|
|
expect(result.label).toBe("1d old");
|
|
});
|
|
|
|
it("returns daysOld=2 with bucket fresh at the upper edge of fresh", () => {
|
|
const result = getOrderAge("2026-08-30T08:00:00.000Z", NOW);
|
|
expect(result.daysOld).toBe(2);
|
|
expect(result.bucket).toBe("fresh");
|
|
expect(result.label).toBe("2d old");
|
|
});
|
|
|
|
it("returns daysOld=3 with bucket recent at the lower edge of recent", () => {
|
|
const result = getOrderAge("2026-08-29T08:00:00.000Z", NOW);
|
|
expect(result.daysOld).toBe(3);
|
|
expect(result.bucket).toBe("recent");
|
|
expect(result.label).toBe("3d old");
|
|
});
|
|
|
|
it("returns daysOld=7 with bucket recent at the upper edge of recent", () => {
|
|
const result = getOrderAge("2026-08-25T08:00:00.000Z", NOW);
|
|
expect(result.daysOld).toBe(7);
|
|
expect(result.bucket).toBe("recent");
|
|
expect(result.label).toBe("7d old");
|
|
});
|
|
|
|
it("returns daysOld=8 with bucket slipping at the lower edge of slipping", () => {
|
|
const result = getOrderAge("2026-08-24T08:00:00.000Z", NOW);
|
|
expect(result.daysOld).toBe(8);
|
|
expect(result.bucket).toBe("slipping");
|
|
expect(result.label).toBe("8d old");
|
|
});
|
|
|
|
it("returns daysOld=14 with bucket slipping at the upper edge of slipping", () => {
|
|
const result = getOrderAge("2026-08-18T08:00:00.000Z", NOW);
|
|
expect(result.daysOld).toBe(14);
|
|
expect(result.bucket).toBe("slipping");
|
|
expect(result.label).toBe("14d old");
|
|
});
|
|
|
|
it("returns daysOld=15 with bucket stale at the lower edge of stale", () => {
|
|
const result = getOrderAge("2026-08-17T08:00:00.000Z", NOW);
|
|
expect(result.daysOld).toBe(15);
|
|
expect(result.bucket).toBe("stale");
|
|
expect(result.label).toBe("15d old");
|
|
});
|
|
|
|
it("returns daysOld=365 with bucket stale for very old orders", () => {
|
|
const result = getOrderAge("2025-09-01T08:00:00.000Z", NOW);
|
|
expect(result.daysOld).toBe(365);
|
|
expect(result.bucket).toBe("stale");
|
|
expect(result.label).toBe("365d old");
|
|
});
|
|
|
|
it("clamps future-dated created to daysOld=0 (clock skew tolerance)", () => {
|
|
const result = getOrderAge("2026-09-02T08:00:00.000Z", NOW);
|
|
expect(result.daysOld).toBe(0);
|
|
expect(result.bucket).toBe("fresh");
|
|
expect(result.label).toBe("today");
|
|
});
|
|
|
|
it("computes calendar-day delta in Africa/Johannesburg, not millisecond delta", () => {
|
|
// 23:00 SAST on 2026-08-31 (UTC+2) → 21:00 UTC
|
|
// 01:00 SAST on 2026-09-01 → 23:00 UTC on 2026-08-31
|
|
// Both are in the SAME calendar day in JNB (Sep 1 vs Aug 31).
|
|
// Millisecond delta is 2 hours, but the day delta must be 1.
|
|
const created = new Date("2026-08-31T21:00:00.000Z"); // 23:00 SAST Aug 31
|
|
const now = new Date("2026-08-31T23:00:00.000Z"); // 01:00 SAST Sep 1
|
|
const result = getOrderAge(created, now);
|
|
expect(result.daysOld).toBe(1);
|
|
expect(result.label).toBe("1d old");
|
|
});
|
|
|
|
it("treats an order created at 23:00 yesterday JNB and read at the same JNB day as today", () => {
|
|
// 23:00 SAST Aug 31 = 21:00 UTC Aug 31
|
|
// 21:00 SAST Aug 31 = 19:00 UTC Aug 31 — same JNB calendar day
|
|
const created = new Date("2026-08-31T21:00:00.000Z");
|
|
const sameDayNow = new Date("2026-08-31T19:00:00.000Z");
|
|
const result = getOrderAge(created, sameDayNow);
|
|
expect(result.daysOld).toBe(0);
|
|
expect(result.label).toBe("today");
|
|
});
|
|
});
|
|
|
|
describe("bucketToBadgeClass", () => {
|
|
it("returns a non-empty string for every bucket", () => {
|
|
expect(bucketToBadgeClass("fresh").length).toBeGreaterThan(0);
|
|
expect(bucketToBadgeClass("recent").length).toBeGreaterThan(0);
|
|
expect(bucketToBadgeClass("slipping").length).toBeGreaterThan(0);
|
|
expect(bucketToBadgeClass("stale").length).toBeGreaterThan(0);
|
|
});
|
|
|
|
it("is stable — same input returns same output", () => {
|
|
expect(bucketToBadgeClass("fresh")).toBe(bucketToBadgeClass("fresh"));
|
|
expect(bucketToBadgeClass("recent")).toBe(bucketToBadgeClass("recent"));
|
|
expect(bucketToBadgeClass("slipping")).toBe(bucketToBadgeClass("slipping"));
|
|
expect(bucketToBadgeClass("stale")).toBe(bucketToBadgeClass("stale"));
|
|
});
|
|
}); |