Просмотр исходного кода

RHL-025 refactor(date-range): add utility functions and tests for SearchDateRangePicker

Code_Uwe 1 неделя назад
Родитель
Сommit
bdc0e9977b

+ 67 - 0
lib/frontend/search/dateRangePickerUtils.js

@@ -0,0 +1,67 @@
+/**
+ * Pure helpers for the SearchDateRangePicker hook.
+ *
+ * Goals:
+ * - Keep the hook smaller and focused on React state/effects.
+ * - Keep complex logic testable without rendering.
+ */
+
+export function normalizeDayClickArgs(args) {
+	// react-day-picker handler signatures have differed across versions.
+	// We normalize both of these variants:
+	// - onDayClick(day, modifiers, event)
+	// - onDayClick(event, day, modifiers)
+	//
+	// Background:
+	// - There has been historical ambiguity around onDayClick argument order. :contentReference[oaicite:2]{index=2}
+	const a0 = args?.[0];
+	const a1 = args?.[1];
+	const a2 = args?.[2];
+
+	if (a0 instanceof Date) return { day: a0, modifiers: a1 || null };
+	if (a1 instanceof Date) return { day: a1, modifiers: a2 || null };
+
+	return { day: null, modifiers: null };
+}
+
+export function buildCalendarState({ fromDate, toDate, isRangeInvalid }) {
+	let calendarSelected = undefined;
+	let invalidInterval = null;
+
+	if (fromDate && toDate) {
+		if (isRangeInvalid) {
+			const min = fromDate < toDate ? fromDate : toDate;
+			const max = fromDate < toDate ? toDate : fromDate;
+
+			calendarSelected = { from: min, to: max };
+			invalidInterval = { from: min, to: max };
+		} else {
+			calendarSelected = { from: fromDate, to: toDate };
+		}
+	} else if (fromDate) {
+		calendarSelected = { from: fromDate, to: undefined };
+	} else if (toDate) {
+		// "to only" -> visually represent as a single-day range
+		calendarSelected = { from: toDate, to: toDate };
+	}
+
+	const calendarModifiers =
+		isRangeInvalid && invalidInterval
+			? {
+					invalid_range: invalidInterval,
+					invalid_range_edge: [fromDate, toDate].filter(Boolean),
+				}
+			: undefined;
+
+	const calendarModifiersClassNames =
+		isRangeInvalid && invalidInterval
+			? {
+					invalid_range:
+						"bg-destructive/10 text-destructive dark:bg-destructive/20 dark:text-destructive",
+					invalid_range_edge:
+						"!bg-destructive !text-white hover:!bg-destructive/90",
+				}
+			: undefined;
+
+	return { calendarSelected, calendarModifiers, calendarModifiersClassNames };
+}

+ 85 - 0
lib/frontend/search/dateRangePickerUtils.test.js

@@ -0,0 +1,85 @@
+/* @vitest-environment node */
+
+import { describe, it, expect } from "vitest";
+import {
+	normalizeDayClickArgs,
+	buildCalendarState,
+} from "./dateRangePickerUtils.js";
+
+describe("lib/frontend/search/dateRangePickerUtils", () => {
+	describe("normalizeDayClickArgs", () => {
+		it("supports (day, modifiers) signature", () => {
+			const day = new Date(2026, 0, 10);
+			const modifiers = { disabled: false };
+
+			const out = normalizeDayClickArgs([day, modifiers]);
+			expect(out.day).toBe(day);
+			expect(out.modifiers).toBe(modifiers);
+		});
+
+		it("supports (event, day, modifiers) signature", () => {
+			const day = new Date(2026, 0, 10);
+			const modifiers = { disabled: true };
+
+			const out = normalizeDayClickArgs([{}, day, modifiers]);
+			expect(out.day).toBe(day);
+			expect(out.modifiers).toBe(modifiers);
+		});
+
+		it("returns nulls for unknown inputs", () => {
+			const out = normalizeDayClickArgs([{}, {}, {}]);
+			expect(out).toEqual({ day: null, modifiers: null });
+		});
+	});
+
+	describe("buildCalendarState", () => {
+		it("builds a valid selected range", () => {
+			const from = new Date(2026, 0, 1);
+			const to = new Date(2026, 0, 5);
+
+			const out = buildCalendarState({
+				fromDate: from,
+				toDate: to,
+				isRangeInvalid: false,
+			});
+			expect(out.calendarSelected).toEqual({ from, to });
+			expect(out.calendarModifiers).toBe(undefined);
+		});
+
+		it("builds an invalid interval and modifiers when range is invalid", () => {
+			const from = new Date(2026, 0, 10);
+			const to = new Date(2026, 0, 5);
+
+			const out = buildCalendarState({
+				fromDate: from,
+				toDate: to,
+				isRangeInvalid: true,
+			});
+			expect(out.calendarSelected.from <= out.calendarSelected.to).toBe(true);
+			expect(out.calendarModifiers).toBeTruthy();
+			expect(out.calendarModifiersClassNames).toBeTruthy();
+		});
+
+		it("supports open range (from only)", () => {
+			const from = new Date(2026, 0, 2);
+
+			const out = buildCalendarState({
+				fromDate: from,
+				toDate: null,
+				isRangeInvalid: false,
+			});
+			expect(out.calendarSelected).toEqual({ from, to: undefined });
+		});
+
+		it("supports to-only (single-day range)", () => {
+			const to = new Date(2026, 0, 2);
+
+			const out = buildCalendarState({
+				fromDate: null,
+				toDate: to,
+				isRangeInvalid: false,
+			});
+			expect(out.calendarSelected).toEqual({ from: to, to });
+		});
+	});
+});