mirror of
https://github.com/acamarata/luxon-hijri.git
synced 2026-06-30 18:54:28 +00:00
Convert all LOCAL-noon Date anchors (new Date(y, m, d, 12)) to UTC-explicit
anchors (new Date(Date.UTC(y, m-1, d))) in test.mjs and test-cjs.cjs.
Add UAQ default-engine round-trip regression suite (5 cases).
Extend FCNA round-trips; update vitest header comment.
README: add "Day boundaries and time zones" section explaining the UTC-day
contract, the correct pattern for zone-aware Luxon DateTimes, and ISO-string
parsing behaviour. Quick Start examples updated to use Date.UTC.
CHANGELOG: document inherited UTC-day fix under [Unreleased].
Lock-step dependency: requires hijri-core fix (commit 3419378,
branch fix/utc-day-boundary). Both packages release together per ADR-013.
Verified: TZ={UTC,America/New_York,Pacific/Auckland} × {test.mjs,
test-cjs.cjs, test-crossval.mjs, vitest} — all pass (88+26+120+15 tests).
103 lines
3.1 KiB
TypeScript
103 lines
3.1 KiB
TypeScript
/**
|
||
* Purpose: Vitest suite for luxon-hijri — conversion, formatting, and validation.
|
||
* Inputs: Pure functions from src/index.ts. Requires luxon + hijri-core as peer deps.
|
||
* Outputs: Vitest pass/fail assertions.
|
||
* Constraints: UAQ range 1318–1500 AH. toGregorian throws (not null) on invalid input.
|
||
* toHijri reads the Date's UTC calendar day; pass UTC midnight or use
|
||
* Date.UTC(year, month-1, day) for exact results on all hosts.
|
||
* Usage: pnpm vitest run
|
||
* SOT: packages.md — luxon-hijri row
|
||
*/
|
||
import { describe, it, expect } from "vitest";
|
||
import {
|
||
toHijri,
|
||
toGregorian,
|
||
isValidHijriDate,
|
||
formatHijriDate,
|
||
hmLong,
|
||
hmMedium,
|
||
hmShort,
|
||
} from "./src/index";
|
||
|
||
// Anchor: toGregorian(1446, 9, 1) = 2025-03-01 midnight UTC
|
||
// toHijri on noon 2025-03-01 reliably returns { hm: 9, hd: 1 }
|
||
const RAMADAN_1446_NOON = new Date("2025-03-01T12:00:00Z");
|
||
|
||
describe("toHijri", () => {
|
||
it("converts noon 2025-03-01 UTC to 1 Ramadan 1446", () => {
|
||
const result = toHijri(RAMADAN_1446_NOON);
|
||
expect(result).not.toBeNull();
|
||
expect(result!.hy).toBe(1446);
|
||
expect(result!.hm).toBe(9);
|
||
expect(result!.hd).toBe(1);
|
||
});
|
||
|
||
it("returns null for dates outside UAQ range", () => {
|
||
expect(toHijri(new Date("2100-01-01"))).toBeNull();
|
||
});
|
||
|
||
it("throws on an invalid Date", () => {
|
||
expect(() => toHijri(new Date("not-a-date"))).toThrow();
|
||
});
|
||
});
|
||
|
||
describe("toGregorian", () => {
|
||
it("converts 1 Ramadan 1446 to 2025-03-01 UTC midnight", () => {
|
||
const result = toGregorian(1446, 9, 1);
|
||
expect(result.toISOString()).toBe("2025-03-01T00:00:00.000Z");
|
||
});
|
||
|
||
it("throws on invalid Hijri date (out of range)", () => {
|
||
expect(() => toGregorian(1501, 1, 1)).toThrow("Invalid Hijri date");
|
||
});
|
||
});
|
||
|
||
describe("isValidHijriDate", () => {
|
||
it("returns true for 1 Ramadan 1446", () => {
|
||
expect(isValidHijriDate(1446, 9, 1)).toBe(true);
|
||
});
|
||
|
||
it("returns false for month 0", () => {
|
||
expect(isValidHijriDate(1446, 0, 1)).toBe(false);
|
||
});
|
||
|
||
it("returns false for day 31", () => {
|
||
expect(isValidHijriDate(1446, 1, 31)).toBe(false);
|
||
});
|
||
});
|
||
|
||
describe("formatHijriDate", () => {
|
||
const hijriDate = { hy: 1446, hm: 9, hd: 1 };
|
||
|
||
it("formats iYYYY-iMM-iDD correctly", () => {
|
||
expect(formatHijriDate(hijriDate, "iYYYY-iMM-iDD")).toBe("1446-09-01");
|
||
});
|
||
|
||
it("formats iMMMM as full month name Ramadan", () => {
|
||
expect(formatHijriDate(hijriDate, "iMMMM")).toBe("Ramadan");
|
||
});
|
||
|
||
it("formats iMMM as a non-empty medium month name", () => {
|
||
const result = formatHijriDate(hijriDate, "iMMM");
|
||
expect(typeof result).toBe("string");
|
||
expect(result.length).toBeGreaterThan(0);
|
||
});
|
||
|
||
it("throws RangeError on invalid month 0", () => {
|
||
expect(() => formatHijriDate({ hy: 1446, hm: 0, hd: 1 }, "iMMMM")).toThrow(RangeError);
|
||
});
|
||
});
|
||
|
||
describe("month name tables", () => {
|
||
it("hmLong index 8 is Ramadan", () => {
|
||
expect(hmLong[8]).toBe("Ramadan");
|
||
});
|
||
|
||
it("hmMedium has 12 entries", () => {
|
||
expect(hmMedium).toHaveLength(12);
|
||
});
|
||
|
||
it("hmShort has 12 entries", () => {
|
||
expect(hmShort).toHaveLength(12);
|
||
});
|
||
});
|