Skip to content
Merged
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
105 changes: 105 additions & 0 deletions lib/bills/urgency.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
import { daysDiff, computeUrgency, computeDaysInfo } from "./urgency";

describe("Bills Urgency", () => {
beforeEach(() => {
vi.useFakeTimers();
});

afterEach(() => {
vi.useRealTimers();
});

describe("daysDiff", () => {
it("returns 0 for today", () => {
vi.setSystemTime(new Date(2024, 0, 15, 12, 0, 0));
expect(daysDiff("2024-01-15T00:00:00")).toBe(0);
expect(daysDiff("2024-01-15T23:59:59")).toBe(0);
});

it("returns negative for past dates (overdue)", () => {
vi.setSystemTime(new Date(2024, 0, 15, 12, 0, 0));
expect(daysDiff("2024-01-14T10:00:00")).toBe(-1);
expect(daysDiff("2024-01-01T00:00:00")).toBe(-14);
});

it("returns positive for future dates (upcoming)", () => {
vi.setSystemTime(new Date(2024, 0, 15, 12, 0, 0));
expect(daysDiff("2024-01-16T10:00:00")).toBe(1);
expect(daysDiff("2024-01-20T00:00:00")).toBe(5);
});

it("handles end-of-month rollover", () => {
vi.setSystemTime(new Date(2024, 0, 31, 12, 0, 0));
expect(daysDiff("2024-02-01T00:00:00")).toBe(1);
});

it("handles leap year boundaries", () => {
vi.setSystemTime(new Date(2024, 1, 28, 12, 0, 0));
expect(daysDiff("2024-02-29T00:00:00")).toBe(1);
expect(daysDiff("2024-03-01T00:00:00")).toBe(2);

// non-leap year
vi.setSystemTime(new Date(2023, 1, 28, 12, 0, 0));
expect(daysDiff("2023-03-01T00:00:00")).toBe(1);
});

it("ignores timezone offset differences by standardizing calendar days", () => {
vi.setSystemTime(new Date(2024, 0, 15, 23, 0, 0));
expect(daysDiff("2024-01-15T01:00:00")).toBe(0);
expect(daysDiff("2024-01-16T01:00:00")).toBe(1);
});
});

describe("computeUrgency", () => {
beforeEach(() => {
vi.setSystemTime(new Date(2024, 4, 15, 12, 0, 0));
});

it("returns overdue for past dates", () => {
expect(computeUrgency("2024-05-14T00:00:00")).toBe("overdue");
expect(computeUrgency("2024-05-01T00:00:00")).toBe("overdue");
});

it("returns urgent for today and next 3 days", () => {
expect(computeUrgency("2024-05-15T00:00:00")).toBe("urgent"); // 0
expect(computeUrgency("2024-05-16T00:00:00")).toBe("urgent"); // 1
expect(computeUrgency("2024-05-17T00:00:00")).toBe("urgent"); // 2
expect(computeUrgency("2024-05-18T00:00:00")).toBe("urgent"); // 3
});

it("returns upcoming for 4 days and beyond", () => {
expect(computeUrgency("2024-05-19T00:00:00")).toBe("upcoming"); // 4
expect(computeUrgency("2024-06-01T00:00:00")).toBe("upcoming"); // far future
});

it("asserts the urgent-upcoming boundary", () => {
expect(computeUrgency("2024-05-18T00:00:00")).toBe("urgent");
expect(computeUrgency("2024-05-19T00:00:00")).toBe("upcoming");
});
});

describe("computeDaysInfo", () => {
beforeEach(() => {
vi.setSystemTime(new Date(2024, 4, 15, 12, 0, 0));
});

it("formats overdue dates correctly", () => {
expect(computeDaysInfo("2024-05-14T00:00:00")).toBe("1d overdue");
expect(computeDaysInfo("2024-05-05T00:00:00")).toBe("10d overdue");
});

it("formats due today correctly", () => {
expect(computeDaysInfo("2024-05-15T00:00:00")).toBe("Due today");
});

it("formats due tomorrow correctly", () => {
expect(computeDaysInfo("2024-05-16T00:00:00")).toBe("Due tomorrow");
});

it("formats upcoming dates correctly", () => {
expect(computeDaysInfo("2024-05-17T00:00:00")).toBe("2d left");
expect(computeDaysInfo("2024-05-25T00:00:00")).toBe("10d left");
});
});
});
12 changes: 6 additions & 6 deletions lib/bills/urgency.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* Keeps mock data and API responses in sync with the same logic.
*/

export type BillUrgency = 'overdue' | 'urgent' | 'upcoming' | 'paid';
export type BillUrgency = "overdue" | "urgent" | "upcoming" | "paid";

/** Returns days difference: negative = overdue, 0 = today, positive = future */
export function daysDiff(dueDateStr: string): number {
Expand All @@ -17,15 +17,15 @@ export function daysDiff(dueDateStr: string): number {

export function computeUrgency(dueDateStr: string): BillUrgency {
const diff = daysDiff(dueDateStr);
if (diff < 0) return 'overdue';
if (diff <= 3) return 'urgent';
return 'upcoming';
if (diff < 0) return "overdue";
if (diff <= 3) return "urgent";
return "upcoming";
}

export function computeDaysInfo(dueDateStr: string): string {
const diff = daysDiff(dueDateStr);
if (diff < 0) return `${Math.abs(diff)}d overdue`;
if (diff === 0) return 'Due today';
if (diff === 1) return 'Due tomorrow';
if (diff === 0) return "Due today";
if (diff === 1) return "Due tomorrow";
return `${diff}d left`;
}
2 changes: 2 additions & 0 deletions vitest.config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export default defineConfig({
test: {
include: [
'lib/contracts/**/*.test.ts',
'lib/**/*.test.ts',
'tests/unit/**/*.test.ts',
'tests/unit/**/*.test.cjs',
'tests/integration/**/*.test.ts',
Expand All @@ -22,6 +23,7 @@ export default defineConfig({
include: ['lib/contracts/**/*.ts', 'app/**/*.ts', 'lib/**/*.ts'],
exclude: [
'lib/contracts/**/*.test.ts',
'lib/**/*.test.ts',
'tests/**',
],
},
Expand Down