Fix map calendar relative date labels
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
* @vitest-environment jsdom
|
||||
*/
|
||||
|
||||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
||||
import { render, screen, fireEvent } from "@testing-library/react";
|
||||
import { FlightsMapFilter } from "./FlightsMapFilter.js";
|
||||
import type { IFlightsMapFilterState } from "../types.js";
|
||||
@@ -40,7 +40,12 @@ vi.mock("@modern-js/runtime/router", () => ({
|
||||
|
||||
vi.mock("@/i18n/provider.js", () => ({
|
||||
useTranslation: () => ({
|
||||
t: (key: string) => key,
|
||||
t: (key: string) =>
|
||||
key === "SHARED.TODAY"
|
||||
? "Сегодня"
|
||||
: key === "SHARED.TOMORROW"
|
||||
? "Завтра"
|
||||
: key,
|
||||
i18n: { language: "ru" },
|
||||
}),
|
||||
}));
|
||||
@@ -201,6 +206,83 @@ describe("FlightsMapFilter — Calendar wiring", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("FlightsMapFilter — TIRREDESIGN-22: calendar today/tomorrow labels", () => {
|
||||
beforeEach(() => {
|
||||
vi.useFakeTimers();
|
||||
vi.setSystemTime(new Date(2026, 4, 6, 12, 0, 0));
|
||||
lastCalendarProps = null;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
it("formats today's selected date as a word", () => {
|
||||
render(
|
||||
<FlightsMapFilter
|
||||
value={filter({ departure: "MOW", date: "20260506" })}
|
||||
onChange={vi.fn()}
|
||||
/>,
|
||||
);
|
||||
|
||||
const formatDateTime = lastCalendarProps!["formatDateTime"] as (
|
||||
date: Date,
|
||||
) => string;
|
||||
|
||||
expect(formatDateTime(new Date(2026, 4, 6))).toBe("Сегодня");
|
||||
});
|
||||
|
||||
it("formats tomorrow's selected date as a word", () => {
|
||||
render(
|
||||
<FlightsMapFilter
|
||||
value={filter({ departure: "MOW", date: "20260507" })}
|
||||
onChange={vi.fn()}
|
||||
/>,
|
||||
);
|
||||
|
||||
const formatDateTime = lastCalendarProps!["formatDateTime"] as (
|
||||
date: Date,
|
||||
) => string;
|
||||
|
||||
expect(formatDateTime(new Date(2026, 4, 7))).toBe("Завтра");
|
||||
});
|
||||
|
||||
it("keeps normal dates in dd.MM.yyyy format", () => {
|
||||
render(
|
||||
<FlightsMapFilter
|
||||
value={filter({ departure: "MOW", date: "20260508" })}
|
||||
onChange={vi.fn()}
|
||||
/>,
|
||||
);
|
||||
|
||||
const formatDateTime = lastCalendarProps!["formatDateTime"] as (
|
||||
date: Date,
|
||||
) => string;
|
||||
|
||||
expect(formatDateTime(new Date(2026, 4, 8))).toBe("08.05.2026");
|
||||
});
|
||||
|
||||
it("parses typed today/tomorrow words back to dates", () => {
|
||||
render(
|
||||
<FlightsMapFilter
|
||||
value={filter({ departure: "MOW", date: "20260506" })}
|
||||
onChange={vi.fn()}
|
||||
/>,
|
||||
);
|
||||
|
||||
const parseDateTime = lastCalendarProps!["parseDateTime"] as (
|
||||
text: string,
|
||||
) => Date;
|
||||
|
||||
expect(parseDateTime("Сегодня").toISOString()).toBe(
|
||||
new Date(2026, 4, 6).toISOString(),
|
||||
);
|
||||
expect(parseDateTime("Завтра").toISOString()).toBe(
|
||||
new Date(2026, 4, 7).toISOString(),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// TZ §4.1.24.2 R16: Calendar disabled when Город вылета is empty
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@@ -44,6 +44,64 @@ function dateToYyyymmdd(value: Date): string {
|
||||
return `${y}${m}${d}`;
|
||||
}
|
||||
|
||||
function addDays(base: Date, days: number): Date {
|
||||
const date = new Date(base);
|
||||
date.setDate(date.getDate() + days);
|
||||
date.setHours(0, 0, 0, 0);
|
||||
return date;
|
||||
}
|
||||
|
||||
function isSameDay(a: Date, b: Date): boolean {
|
||||
return (
|
||||
a.getFullYear() === b.getFullYear() &&
|
||||
a.getMonth() === b.getMonth() &&
|
||||
a.getDate() === b.getDate()
|
||||
);
|
||||
}
|
||||
|
||||
function formatDateInputValue(value: Date, t: (key: string) => string): string {
|
||||
const today = new Date();
|
||||
today.setHours(0, 0, 0, 0);
|
||||
|
||||
if (isSameDay(value, today)) return t("SHARED.TODAY");
|
||||
if (isSameDay(value, addDays(today, 1))) return t("SHARED.TOMORROW");
|
||||
|
||||
const d = value.getDate().toString().padStart(2, "0");
|
||||
const m = (value.getMonth() + 1).toString().padStart(2, "0");
|
||||
return `${d}.${m}.${value.getFullYear()}`;
|
||||
}
|
||||
|
||||
function parseDateInputValue(value: string, t: (key: string) => string): Date | null {
|
||||
const text = value.trim();
|
||||
const today = new Date();
|
||||
today.setHours(0, 0, 0, 0);
|
||||
|
||||
if (text.localeCompare(t("SHARED.TODAY"), undefined, { sensitivity: "accent" }) === 0) {
|
||||
return today;
|
||||
}
|
||||
|
||||
if (
|
||||
text.localeCompare(t("SHARED.TOMORROW"), undefined, { sensitivity: "accent" }) === 0
|
||||
) {
|
||||
return addDays(today, 1);
|
||||
}
|
||||
|
||||
const match = /^(\d{1,2})\.(\d{1,2})\.(\d{4})$/.exec(text);
|
||||
if (!match) return null;
|
||||
|
||||
const day = Number(match[1]);
|
||||
const month = Number(match[2]) - 1;
|
||||
const year = Number(match[3]);
|
||||
const date = new Date(year, month, day);
|
||||
date.setHours(0, 0, 0, 0);
|
||||
|
||||
return date.getFullYear() === year &&
|
||||
date.getMonth() === month &&
|
||||
date.getDate() === day
|
||||
? date
|
||||
: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter component for the flights map. Controls departure, arrival,
|
||||
* connections, domestic/international toggles, and date selection.
|
||||
@@ -153,6 +211,16 @@ export const FlightsMapFilter: FC<FlightsMapFilterProps> = ({
|
||||
[value, onChange],
|
||||
);
|
||||
|
||||
const formatDateTime = useCallback(
|
||||
(date: Date) => formatDateInputValue(date, t),
|
||||
[t],
|
||||
);
|
||||
|
||||
const parseDateTime = useCallback(
|
||||
(text: string) => parseDateInputValue(text, t) ?? new Date(Number.NaN),
|
||||
[t],
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="flights-map-filter" data-testid="flights-map-filter">
|
||||
<div className="flights-map-filter-header">
|
||||
@@ -294,6 +362,8 @@ export const FlightsMapFilter: FC<FlightsMapFilterProps> = ({
|
||||
maxDate={maxDate}
|
||||
disabledDates={disabledDates}
|
||||
dateFormat="dd.mm.yy"
|
||||
formatDateTime={formatDateTime}
|
||||
parseDateTime={parseDateTime}
|
||||
placeholder={t("SHARED.DATE_FORMAT")}
|
||||
showIcon
|
||||
disabled={!value.departure}
|
||||
|
||||
Reference in New Issue
Block a user