src / __tests__ / dateParser.test.ts
import { describe, expect, it } from "vitest";
import { parseEmailSearchDate, validateDateRange } from "../dateParser";
function localParts(date: Date): [number, number, number] {
return [date.getFullYear(), date.getMonth() + 1, date.getDate()];
}
const NOW = new Date(2026, 7, 5, 18, 44, 0);
describe("parseEmailSearchDate", () => {
it("parses YYYY-MM-DD as local midnight", () => {
const date = parseEmailSearchDate("2026-08-01", "since", NOW);
expect(localParts(date)).toEqual([2026, 8, 1]);
expect(date.getHours()).toBe(0);
});
it("parses today, yesterday, and tomorrow", () => {
expect(localParts(parseEmailSearchDate("today", "since", NOW))).toEqual([2026, 8, 5]);
expect(localParts(parseEmailSearchDate("yesterday", "since", NOW))).toEqual([2026, 8, 4]);
expect(localParts(parseEmailSearchDate("tomorrow", "before", NOW))).toEqual([2026, 8, 6]);
});
it("parses numeric relative dates", () => {
expect(localParts(parseEmailSearchDate("2 weeks ago", "since", NOW))).toEqual([
2026,
7,
22,
]);
expect(localParts(parseEmailSearchDate("last 3 months", "since", NOW))).toEqual([
2026,
5,
5,
]);
});
it("parses article and word-number forms", () => {
expect(localParts(parseEmailSearchDate("a month ago", "since", NOW))).toEqual([
2026,
7,
5,
]);
expect(localParts(parseEmailSearchDate("one year ago", "since", NOW))).toEqual([
2025,
8,
5,
]);
});
it("clamps end-of-month calendar shifts", () => {
const march31 = new Date(2026, 2, 31, 12, 0, 0);
expect(localParts(parseEmailSearchDate("a month ago", "since", march31))).toEqual([
2026,
2,
28,
]);
});
it("rejects impossible and unsupported dates", () => {
expect(() => parseEmailSearchDate("2026-02-31", "since", NOW)).toThrow("Invalid since date");
expect(() => parseEmailSearchDate("recently", "since", NOW)).toThrow("Unsupported since date");
});
});
describe("validateDateRange", () => {
it("accepts an increasing range", () => {
expect(() =>
validateDateRange(
parseEmailSearchDate("2026-08-01", "since", NOW),
parseEmailSearchDate("2026-08-02", "before", NOW),
),
).not.toThrow();
});
it("rejects equal or reversed ranges", () => {
const date = parseEmailSearchDate("2026-08-01", "since", NOW);
expect(() => validateDateRange(date, new Date(date))).toThrow("Invalid date range");
expect(() =>
validateDateRange(
parseEmailSearchDate("2026-08-03", "since", NOW),
parseEmailSearchDate("2026-08-02", "before", NOW),
),
).toThrow("Invalid date range");
});
});
src / __tests__ / dateParser.test.ts
import { describe, expect, it } from "vitest";
import { parseEmailSearchDate, validateDateRange } from "../dateParser";
function localParts(date: Date): [number, number, number] {
return [date.getFullYear(), date.getMonth() + 1, date.getDate()];
}
const NOW = new Date(2026, 7, 5, 18, 44, 0);
describe("parseEmailSearchDate", () => {
it("parses YYYY-MM-DD as local midnight", () => {
const date = parseEmailSearchDate("2026-08-01", "since", NOW);
expect(localParts(date)).toEqual([2026, 8, 1]);
expect(date.getHours()).toBe(0);
});
it("parses today, yesterday, and tomorrow", () => {
expect(localParts(parseEmailSearchDate("today", "since", NOW))).toEqual([2026, 8, 5]);
expect(localParts(parseEmailSearchDate("yesterday", "since", NOW))).toEqual([2026, 8, 4]);
expect(localParts(parseEmailSearchDate("tomorrow", "before", NOW))).toEqual([2026, 8, 6]);
});
it("parses numeric relative dates", () => {
expect(localParts(parseEmailSearchDate("2 weeks ago", "since", NOW))).toEqual([
2026,
7,
22,
]);
expect(localParts(parseEmailSearchDate("last 3 months", "since", NOW))).toEqual([
2026,
5,
5,
]);
});
it("parses article and word-number forms", () => {
expect(localParts(parseEmailSearchDate("a month ago", "since", NOW))).toEqual([
2026,
7,
5,
]);
expect(localParts(parseEmailSearchDate("one year ago", "since", NOW))).toEqual([
2025,
8,
5,
]);
});
it("clamps end-of-month calendar shifts", () => {
const march31 = new Date(2026, 2, 31, 12, 0, 0);
expect(localParts(parseEmailSearchDate("a month ago", "since", march31))).toEqual([
2026,
2,
28,
]);
});
it("rejects impossible and unsupported dates", () => {
expect(() => parseEmailSearchDate("2026-02-31", "since", NOW)).toThrow("Invalid since date");
expect(() => parseEmailSearchDate("recently", "since", NOW)).toThrow("Unsupported since date");
});
});
describe("validateDateRange", () => {
it("accepts an increasing range", () => {
expect(() =>
validateDateRange(
parseEmailSearchDate("2026-08-01", "since", NOW),
parseEmailSearchDate("2026-08-02", "before", NOW),
),
).not.toThrow();
});
it("rejects equal or reversed ranges", () => {
const date = parseEmailSearchDate("2026-08-01", "since", NOW);
expect(() => validateDateRange(date, new Date(date))).toThrow("Invalid date range");
expect(() =>
validateDateRange(
parseEmailSearchDate("2026-08-03", "since", NOW),
parseEmailSearchDate("2026-08-02", "before", NOW),
),
).toThrow("Invalid date range");
});
});