This is an automated email from the ASF dual-hosted git repository.
shahar pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git
The following commit(s) were added to refs/heads/main by this push:
new 67748cc7676 Refactor DateRangeFilter tests to improve input validation
coverage (#60029)
67748cc7676 is described below
commit 67748cc7676db97a83b0b634b4df11ac1ec71740
Author: Arjav Patel <[email protected]>
AuthorDate: Sat Jan 3 15:09:53 2026 +0530
Refactor DateRangeFilter tests to improve input validation coverage (#60029)
---
.../FilterBar/filters/DateRangeFilter.test.tsx | 249 +++++++++++----------
1 file changed, 126 insertions(+), 123 deletions(-)
diff --git
a/airflow-core/src/airflow/ui/src/components/FilterBar/filters/DateRangeFilter.test.tsx
b/airflow-core/src/airflow/ui/src/components/FilterBar/filters/DateRangeFilter.test.tsx
index c03415ff8f3..51356dc9950 100644
---
a/airflow-core/src/airflow/ui/src/components/FilterBar/filters/DateRangeFilter.test.tsx
+++
b/airflow-core/src/airflow/ui/src/components/FilterBar/filters/DateRangeFilter.test.tsx
@@ -17,12 +17,12 @@
* under the License.
*/
import "@testing-library/jest-dom";
-import { render, screen, fireEvent, waitFor } from "@testing-library/react";
+import { render, screen, fireEvent, waitFor, cleanup } from
"@testing-library/react";
import dayjs from "dayjs";
import timezone from "dayjs/plugin/timezone";
import utc from "dayjs/plugin/utc";
import { useMemo } from "react";
-import { describe, it, expect, vi, beforeEach } from "vitest";
+import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import { TimezoneContext } from "src/context/timezone";
import { ChakraWrapper } from "src/utils/ChakraWrapper";
@@ -30,11 +30,9 @@ import { ChakraWrapper } from "src/utils/ChakraWrapper";
import type { FilterPluginProps } from "../types";
import { DateRangeFilter } from "./DateRangeFilter";
-// Initialize dayjs plugins
dayjs.extend(timezone);
dayjs.extend(utc);
-// Mock useTranslation
const mockTranslate = vi.fn((key: string) => {
const translations: Record<string, string> = {
"common:filters.endTime": "End Time",
@@ -75,7 +73,6 @@ const TestWrapper = ({ children }: { readonly children:
React.ReactNode }) => {
);
};
-// Mock data
const mockFilter = {
config: {
icon: undefined,
@@ -93,155 +90,161 @@ const defaultProps: FilterPluginProps = {
onRemove: vi.fn(),
};
-describe("DateRangeFilter - Input Validation", () => {
- beforeEach(() => {
- vi.clearAllMocks();
- });
+const getInputs = () => {
+ const dateInputs = screen.getAllByPlaceholderText("YYYY/MM/DD");
+ const timeInputs = screen.getAllByPlaceholderText("HH:mm");
- it("shows error for invalid date format in start date", async () => {
- render(
- <TestWrapper>
- <DateRangeFilter {...defaultProps} />
- </TestWrapper>,
- );
+ return {
+ endDateInput: dateInputs[1],
+ endTimeInput: timeInputs[1],
+ startDateInput: dateInputs[0],
+ startTimeInput: timeInputs[0],
+ };
+};
- const [startDateInput] = screen.getAllByPlaceholderText("YYYY/MM/DD");
+const changeDateInput = (input: HTMLElement | undefined, value: string) => {
+ if (input) {
+ fireEvent.change(input, { target: { value } });
+ }
+};
- if (startDateInput) {
- fireEvent.change(startDateInput, { target: { value: "invalid-date" } });
- }
+const changeTimeInput = (input: HTMLElement | undefined, value: string) => {
+ if (input) {
+ fireEvent.change(input, { target: { value } });
+ }
+};
- await waitFor(() => {
- expect(screen.getByText("Invalid date format.")).toBeInTheDocument();
- });
+const waitForError = async (errorText: string) => {
+ await waitFor(() => {
+ expect(screen.getByText(errorText)).toBeInTheDocument();
});
+};
- it("shows error for invalid date format in end date", async () => {
- render(
- <TestWrapper>
- <DateRangeFilter {...defaultProps} />
- </TestWrapper>,
- );
-
- const [, endDateInput] = screen.getAllByPlaceholderText("YYYY/MM/DD");
+const waitForNoError = async (errorText: string) => {
+ await waitFor(() => {
+ expect(screen.queryByText(errorText)).not.toBeInTheDocument();
+ });
+};
- if (endDateInput) {
- fireEvent.change(endDateInput, { target: { value: "invalid-date" } });
+const waitForNoErrors = async (errorTexts: Array<string>) => {
+ await waitFor(() => {
+ for (const errorText of errorTexts) {
+ expect(screen.queryByText(errorText)).not.toBeInTheDocument();
}
-
- await waitFor(() => {
- expect(screen.getByText("Invalid date format.")).toBeInTheDocument();
- });
});
+};
- it("shows error for invalid time format in start time", async () => {
- render(
- <TestWrapper>
- <DateRangeFilter {...defaultProps} />
- </TestWrapper>,
- );
+const renderFilter = (props: FilterPluginProps = defaultProps) =>
+ render(
+ <TestWrapper>
+ <DateRangeFilter {...props} />
+ </TestWrapper>,
+ );
- const [startTimeInput] = screen.getAllByPlaceholderText("HH:mm");
+describe("DateRangeFilter", () => {
+ beforeEach(() => {
+ vi.clearAllMocks();
+ });
- if (startTimeInput) {
- fireEvent.change(startTimeInput, { target: { value: "invalid-time" } });
- }
+ afterEach(() => {
+ cleanup();
+ });
- await waitFor(() => {
- expect(screen.getByText("Invalid time format.")).toBeInTheDocument();
+ describe("Input Validation", () => {
+ it("validates date and time formats", async () => {
+ renderFilter();
+ const { startDateInput, startTimeInput } = getInputs();
+
+ changeDateInput(startDateInput, "invalid-date");
+ await waitForError("Invalid date format.");
+ changeDateInput(startDateInput, "2024/13/01");
+ await waitForError("Invalid date format.");
+ changeTimeInput(startTimeInput, "25:00");
+ await waitForError("Invalid time format.");
});
- });
- it("shows error for invalid time format in end time", async () => {
- render(
- <TestWrapper>
- <DateRangeFilter {...defaultProps} />
- </TestWrapper>,
- );
+ it("validates date range", async () => {
+ renderFilter();
+ const { endDateInput, endTimeInput, startDateInput, startTimeInput } =
getInputs();
- const [, endTimeInput] = screen.getAllByPlaceholderText("HH:mm");
+ changeDateInput(startDateInput, "2024/01/15");
+ changeTimeInput(startTimeInput, "10:00");
+ changeDateInput(endDateInput, "2024/01/14");
+ changeTimeInput(endTimeInput, "09:00");
+ await waitForError("Start date/time must be before end date/time");
+ });
- if (endTimeInput) {
- fireEvent.change(endTimeInput, { target: { value: "invalid-time" } });
- }
+ it("accepts valid inputs", async () => {
+ renderFilter();
+ const { endDateInput, endTimeInput, startDateInput, startTimeInput } =
getInputs();
- await waitFor(() => {
- expect(screen.getByText("Invalid time format.")).toBeInTheDocument();
+ changeDateInput(startDateInput, "2024/01/15");
+ changeTimeInput(startTimeInput, "09:00");
+ changeDateInput(endDateInput, "2024/01/20");
+ changeTimeInput(endTimeInput, "17:00");
+ await waitForNoErrors(["Invalid date format.", "Start date/time must be
before end date/time"]);
});
});
- it("shows error when start date/time is after end date/time", async () => {
- render(
- <TestWrapper>
- <DateRangeFilter {...defaultProps} />
- </TestWrapper>,
- );
-
- const [startDateInput, endDateInput] =
screen.getAllByPlaceholderText("YYYY/MM/DD");
- const [startTimeInput, endTimeInput] =
screen.getAllByPlaceholderText("HH:mm");
-
- // Set start date/time to be after end date/time
- if (startDateInput && startTimeInput && endDateInput && endTimeInput) {
- fireEvent.change(startDateInput, { target: { value: "2024/01/15" } });
- fireEvent.change(startTimeInput, { target: { value: "10:00" } });
- fireEvent.change(endDateInput, { target: { value: "2024/01/14" } });
- fireEvent.change(endTimeInput, { target: { value: "09:00" } });
- }
+ describe("Display Value Formatting", () => {
+ it("displays placeholder when no value is set", () => {
+ renderFilter();
+ expect(screen.getByText("Select Date Range")).toBeInTheDocument();
+ });
- await waitFor(() => {
- expect(screen.getByText("Start date/time must be before end
date/time")).toBeInTheDocument();
+ it("displays formatted date range", () => {
+ const props = {
+ ...defaultProps,
+ filter: {
+ ...mockFilter,
+ value: { endDate: "2024-01-20T17:00:00Z", startDate:
"2024-01-15T09:00:00Z" },
+ },
+ };
+
+ renderFilter(props);
+ expect(screen.getByText(/Jan 15, 2024/u)).toBeInTheDocument();
});
- });
- it("accepts valid date and time inputs", async () => {
- render(
- <TestWrapper>
- <DateRangeFilter {...defaultProps} />
- </TestWrapper>,
- );
-
- const [startDateInput, endDateInput] =
screen.getAllByPlaceholderText("YYYY/MM/DD");
- const [startTimeInput, endTimeInput] =
screen.getAllByPlaceholderText("HH:mm");
-
- // Set valid inputs
- if (startDateInput && startTimeInput && endDateInput && endTimeInput) {
- fireEvent.change(startDateInput, { target: { value: "2024/01/15" } });
- fireEvent.change(startTimeInput, { target: { value: "09:00" } });
- fireEvent.change(endDateInput, { target: { value: "2024/01/20" } });
- fireEvent.change(endTimeInput, { target: { value: "17:00" } });
- }
+ it("displays 'From' when only start date is set", () => {
+ const props = {
+ ...defaultProps,
+ filter: { ...mockFilter, value: { endDate: undefined, startDate:
"2024-01-15T09:00:00Z" } },
+ };
- await waitFor(() => {
- // Should not show any validation errors
- expect(screen.queryByText("Invalid date
format.")).not.toBeInTheDocument();
- expect(screen.queryByText("Invalid time
format.")).not.toBeInTheDocument();
- expect(screen.queryByText("Start date/time must be before end
date/time")).not.toBeInTheDocument();
+ renderFilter(props);
+ expect(screen.getAllByText(/From/u).length).toBeGreaterThan(0);
});
- });
-
- it("clears validation errors when invalid input is corrected", async () => {
- render(
- <TestWrapper>
- <DateRangeFilter {...defaultProps} />
- </TestWrapper>,
- );
- const [startDateInput] = screen.getAllByPlaceholderText("YYYY/MM/DD");
+ it("displays 'To' when only end date is set", () => {
+ const props = {
+ ...defaultProps,
+ filter: { ...mockFilter, value: { endDate: "2024-01-20T17:00:00Z",
startDate: undefined } },
+ };
- if (startDateInput) {
- // First, set an invalid date
- fireEvent.change(startDateInput, { target: { value: "invalid-date" } });
+ renderFilter(props);
+ expect(screen.getAllByText(/To/u).length).toBeGreaterThan(0);
+ });
+ });
- await waitFor(() => {
- expect(screen.getByText("Invalid date format.")).toBeInTheDocument();
- });
+ describe("Edge Cases", () => {
+ it("handles leap years and boundary dates", async () => {
+ renderFilter();
+ const { endDateInput, startDateInput } = getInputs();
+
+ changeDateInput(startDateInput, "2024/02/29");
+ await waitForNoError("Invalid date format.");
+ changeDateInput(startDateInput, "2023/02/29");
+ await waitForError("Invalid date format.");
+ changeDateInput(startDateInput, "2024/01/31");
+ changeDateInput(endDateInput, "2024/02/01");
+ await waitForNoError("Invalid date format.");
+ });
- // Then, correct it to a valid date
- fireEvent.change(startDateInput, { target: { value: "2024/01/15" } });
- }
+ it("handles undefined filter value", () => {
+ const props = { ...defaultProps, filter: { ...mockFilter, value:
undefined } };
- await waitFor(() => {
- expect(screen.queryByText("Invalid date
format.")).not.toBeInTheDocument();
+ // Should not throw when filter value is undefined
+ renderFilter(props);
});
});
});