Files

141 lines
4.0 KiB
TypeScript

/// <reference types="bun" />
import { describe, expect, test } from "bun:test";
import {
formatZonedDateTime,
addZonedCalendarInterval,
getDefaultScheduledSendAt,
isValidTimeZone,
toLocalDateTimeInputValue,
zonedDateTimeToInstant,
} from "../src/time-zone";
import {
EXPENSE_CATEGORIES,
formatElapsedSeconds,
getEffectiveInvoiceStatus,
getDaysPastDue,
parseReceiptText,
} from "../src";
describe("shared domain behavior", () => {
test("exposes the expense category vocabulary", () => {
expect(EXPENSE_CATEGORIES).toContain("Professional Services");
});
test("calculates overdue status at day precision", () => {
const yesterday = new Date();
yesterday.setDate(yesterday.getDate() - 1);
expect(getEffectiveInvoiceStatus("sent", yesterday)).toBe("overdue");
expect(getEffectiveInvoiceStatus("paid", yesterday)).toBe("paid");
});
test("counts calendar days rather than 24-hour blocks across fall DST", () => {
expect(
getDaysPastDue(
"sent",
"2026-11-01",
"America/New_York",
new Date("2026-11-02T17:00:00.000Z"),
),
).toBe(1);
});
test("formats elapsed time", () => {
expect(formatElapsedSeconds(3_661)).toBe("01:01:01");
});
test("extracts receipt totals and line items", () => {
const receipt = parseReceiptText(
"Corner Store\nCoffee 3.50\nSubtotal 3.50\nTax 0.31\nTotal 3.81",
);
expect(receipt.amount).toBe(3.81);
expect(receipt.tax).toBe(0.31);
expect(receipt.items[0]?.name).toBe("Coffee");
});
});
describe("time-zone helpers", () => {
test("formats the same instant in the selected IANA time zone", () => {
const instant = new Date("2026-08-17T16:30:00.000Z");
expect(formatZonedDateTime(instant, "America/New_York")).toContain(
"12:30 PM",
);
expect(formatZonedDateTime(instant, "America/Los_Angeles")).toContain(
"9:30 AM",
);
});
test("validates IANA time zones", () => {
expect(isValidTimeZone("America/New_York")).toBe(true);
expect(isValidTimeZone("not/a-zone")).toBe(false);
});
test("converts Eastern wall time to the correct absolute instant", () => {
expect(
zonedDateTimeToInstant(
"2026-08-17T09:00",
"America/New_York",
).toISOString(),
).toBe("2026-08-17T13:00:00.000Z");
});
test("rejects nonexistent spring-forward wall times", () => {
expect(() =>
zonedDateTimeToInstant("2026-03-08T02:30", "America/New_York"),
).toThrow("does not exist");
});
test("disambiguates both occurrences of a fall-back wall time", () => {
expect(
zonedDateTimeToInstant(
"2026-11-01T01:30",
"America/New_York",
"earlier",
).toISOString(),
).toBe("2026-11-01T05:30:00.000Z");
expect(
zonedDateTimeToInstant(
"2026-11-01T01:30",
"America/New_York",
"later",
).toISOString(),
).toBe("2026-11-01T06:30:00.000Z");
});
test("supports half-hour DST transitions", () => {
const earlier = zonedDateTimeToInstant(
"2026-04-05T01:45",
"Australia/Lord_Howe",
"earlier",
);
const later = zonedDateTimeToInstant(
"2026-04-05T01:45",
"Australia/Lord_Howe",
"later",
);
expect(later.getTime() - earlier.getTime()).toBe(30 * 60_000);
});
test("preserves Eastern wall time across DST and clamps month end", () => {
expect(
addZonedCalendarInterval(
new Date("2026-03-01T14:00:00.000Z"),
"weekly",
"America/New_York",
).toISOString(),
).toBe("2026-03-08T13:00:00.000Z");
expect(
addZonedCalendarInterval(
new Date("2026-01-31T14:00:00.000Z"),
"monthly",
"America/New_York",
).toISOString(),
).toBe("2026-02-28T14:00:00.000Z");
});
test("rounds the default schedule to the next local hour", () => {
const result = getDefaultScheduledSendAt(new Date(2026, 7, 17, 10, 42, 19));
expect(toLocalDateTimeInputValue(result)).toBe("2026-08-17T11:00");
});
});