Skip to content

Commit c1e8a5f

Browse files
committed
Refactor DateRangeFilter tests to improve input validation coverage
1 parent 54741f8 commit c1e8a5f

1 file changed

Lines changed: 126 additions & 123 deletions

File tree

airflow-core/src/airflow/ui/src/components/FilterBar/filters/DateRangeFilter.test.tsx

Lines changed: 126 additions & 123 deletions
Original file line numberDiff line numberDiff line change
@@ -17,24 +17,22 @@
1717
* under the License.
1818
*/
1919
import "@testing-library/jest-dom";
20-
import { render, screen, fireEvent, waitFor } from "@testing-library/react";
20+
import { render, screen, fireEvent, waitFor, cleanup } from "@testing-library/react";
2121
import dayjs from "dayjs";
2222
import timezone from "dayjs/plugin/timezone";
2323
import utc from "dayjs/plugin/utc";
2424
import { useMemo } from "react";
25-
import { describe, it, expect, vi, beforeEach } from "vitest";
25+
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
2626

2727
import { TimezoneContext } from "src/context/timezone";
2828
import { ChakraWrapper } from "src/utils/ChakraWrapper";
2929

3030
import type { FilterPluginProps } from "../types";
3131
import { DateRangeFilter } from "./DateRangeFilter";
3232

33-
// Initialize dayjs plugins
3433
dayjs.extend(timezone);
3534
dayjs.extend(utc);
3635

37-
// Mock useTranslation
3836
const mockTranslate = vi.fn((key: string) => {
3937
const translations: Record<string, string> = {
4038
"common:filters.endTime": "End Time",
@@ -75,7 +73,6 @@ const TestWrapper = ({ children }: { readonly children: React.ReactNode }) => {
7573
);
7674
};
7775

78-
// Mock data
7976
const mockFilter = {
8077
config: {
8178
icon: undefined,
@@ -93,155 +90,161 @@ const defaultProps: FilterPluginProps = {
9390
onRemove: vi.fn(),
9491
};
9592

96-
describe("DateRangeFilter - Input Validation", () => {
97-
beforeEach(() => {
98-
vi.clearAllMocks();
99-
});
93+
const getInputs = () => {
94+
const dateInputs = screen.getAllByPlaceholderText("YYYY/MM/DD");
95+
const timeInputs = screen.getAllByPlaceholderText("HH:mm");
10096

101-
it("shows error for invalid date format in start date", async () => {
102-
render(
103-
<TestWrapper>
104-
<DateRangeFilter {...defaultProps} />
105-
</TestWrapper>,
106-
);
97+
return {
98+
endDateInput: dateInputs[1],
99+
endTimeInput: timeInputs[1],
100+
startDateInput: dateInputs[0],
101+
startTimeInput: timeInputs[0],
102+
};
103+
};
107104

108-
const [startDateInput] = screen.getAllByPlaceholderText("YYYY/MM/DD");
105+
const changeDateInput = (input: HTMLElement | undefined, value: string) => {
106+
if (input) {
107+
fireEvent.change(input, { target: { value } });
108+
}
109+
};
109110

110-
if (startDateInput) {
111-
fireEvent.change(startDateInput, { target: { value: "invalid-date" } });
112-
}
111+
const changeTimeInput = (input: HTMLElement | undefined, value: string) => {
112+
if (input) {
113+
fireEvent.change(input, { target: { value } });
114+
}
115+
};
113116

114-
await waitFor(() => {
115-
expect(screen.getByText("Invalid date format.")).toBeInTheDocument();
116-
});
117+
const waitForError = async (errorText: string) => {
118+
await waitFor(() => {
119+
expect(screen.getByText(errorText)).toBeInTheDocument();
117120
});
121+
};
118122

119-
it("shows error for invalid date format in end date", async () => {
120-
render(
121-
<TestWrapper>
122-
<DateRangeFilter {...defaultProps} />
123-
</TestWrapper>,
124-
);
125-
126-
const [, endDateInput] = screen.getAllByPlaceholderText("YYYY/MM/DD");
123+
const waitForNoError = async (errorText: string) => {
124+
await waitFor(() => {
125+
expect(screen.queryByText(errorText)).not.toBeInTheDocument();
126+
});
127+
};
127128

128-
if (endDateInput) {
129-
fireEvent.change(endDateInput, { target: { value: "invalid-date" } });
129+
const waitForNoErrors = async (errorTexts: Array<string>) => {
130+
await waitFor(() => {
131+
for (const errorText of errorTexts) {
132+
expect(screen.queryByText(errorText)).not.toBeInTheDocument();
130133
}
131-
132-
await waitFor(() => {
133-
expect(screen.getByText("Invalid date format.")).toBeInTheDocument();
134-
});
135134
});
135+
};
136136

137-
it("shows error for invalid time format in start time", async () => {
138-
render(
139-
<TestWrapper>
140-
<DateRangeFilter {...defaultProps} />
141-
</TestWrapper>,
142-
);
137+
const renderFilter = (props: FilterPluginProps = defaultProps) =>
138+
render(
139+
<TestWrapper>
140+
<DateRangeFilter {...props} />
141+
</TestWrapper>,
142+
);
143143

144-
const [startTimeInput] = screen.getAllByPlaceholderText("HH:mm");
144+
describe("DateRangeFilter", () => {
145+
beforeEach(() => {
146+
vi.clearAllMocks();
147+
});
145148

146-
if (startTimeInput) {
147-
fireEvent.change(startTimeInput, { target: { value: "invalid-time" } });
148-
}
149+
afterEach(() => {
150+
cleanup();
151+
});
149152

150-
await waitFor(() => {
151-
expect(screen.getByText("Invalid time format.")).toBeInTheDocument();
153+
describe("Input Validation", () => {
154+
it("validates date and time formats", async () => {
155+
renderFilter();
156+
const { startDateInput, startTimeInput } = getInputs();
157+
158+
changeDateInput(startDateInput, "invalid-date");
159+
await waitForError("Invalid date format.");
160+
changeDateInput(startDateInput, "2024/13/01");
161+
await waitForError("Invalid date format.");
162+
changeTimeInput(startTimeInput, "25:00");
163+
await waitForError("Invalid time format.");
152164
});
153-
});
154165

155-
it("shows error for invalid time format in end time", async () => {
156-
render(
157-
<TestWrapper>
158-
<DateRangeFilter {...defaultProps} />
159-
</TestWrapper>,
160-
);
166+
it("validates date range", async () => {
167+
renderFilter();
168+
const { endDateInput, endTimeInput, startDateInput, startTimeInput } = getInputs();
161169

162-
const [, endTimeInput] = screen.getAllByPlaceholderText("HH:mm");
170+
changeDateInput(startDateInput, "2024/01/15");
171+
changeTimeInput(startTimeInput, "10:00");
172+
changeDateInput(endDateInput, "2024/01/14");
173+
changeTimeInput(endTimeInput, "09:00");
174+
await waitForError("Start date/time must be before end date/time");
175+
});
163176

164-
if (endTimeInput) {
165-
fireEvent.change(endTimeInput, { target: { value: "invalid-time" } });
166-
}
177+
it("accepts valid inputs", async () => {
178+
renderFilter();
179+
const { endDateInput, endTimeInput, startDateInput, startTimeInput } = getInputs();
167180

168-
await waitFor(() => {
169-
expect(screen.getByText("Invalid time format.")).toBeInTheDocument();
181+
changeDateInput(startDateInput, "2024/01/15");
182+
changeTimeInput(startTimeInput, "09:00");
183+
changeDateInput(endDateInput, "2024/01/20");
184+
changeTimeInput(endTimeInput, "17:00");
185+
await waitForNoErrors(["Invalid date format.", "Start date/time must be before end date/time"]);
170186
});
171187
});
172188

173-
it("shows error when start date/time is after end date/time", async () => {
174-
render(
175-
<TestWrapper>
176-
<DateRangeFilter {...defaultProps} />
177-
</TestWrapper>,
178-
);
179-
180-
const [startDateInput, endDateInput] = screen.getAllByPlaceholderText("YYYY/MM/DD");
181-
const [startTimeInput, endTimeInput] = screen.getAllByPlaceholderText("HH:mm");
182-
183-
// Set start date/time to be after end date/time
184-
if (startDateInput && startTimeInput && endDateInput && endTimeInput) {
185-
fireEvent.change(startDateInput, { target: { value: "2024/01/15" } });
186-
fireEvent.change(startTimeInput, { target: { value: "10:00" } });
187-
fireEvent.change(endDateInput, { target: { value: "2024/01/14" } });
188-
fireEvent.change(endTimeInput, { target: { value: "09:00" } });
189-
}
189+
describe("Display Value Formatting", () => {
190+
it("displays placeholder when no value is set", () => {
191+
renderFilter();
192+
expect(screen.getByText("Select Date Range")).toBeInTheDocument();
193+
});
190194

191-
await waitFor(() => {
192-
expect(screen.getByText("Start date/time must be before end date/time")).toBeInTheDocument();
195+
it("displays formatted date range", () => {
196+
const props = {
197+
...defaultProps,
198+
filter: {
199+
...mockFilter,
200+
value: { endDate: "2024-01-20T17:00:00Z", startDate: "2024-01-15T09:00:00Z" },
201+
},
202+
};
203+
204+
renderFilter(props);
205+
expect(screen.getByText(/Jan 15, 2024/u)).toBeInTheDocument();
193206
});
194-
});
195207

196-
it("accepts valid date and time inputs", async () => {
197-
render(
198-
<TestWrapper>
199-
<DateRangeFilter {...defaultProps} />
200-
</TestWrapper>,
201-
);
202-
203-
const [startDateInput, endDateInput] = screen.getAllByPlaceholderText("YYYY/MM/DD");
204-
const [startTimeInput, endTimeInput] = screen.getAllByPlaceholderText("HH:mm");
205-
206-
// Set valid inputs
207-
if (startDateInput && startTimeInput && endDateInput && endTimeInput) {
208-
fireEvent.change(startDateInput, { target: { value: "2024/01/15" } });
209-
fireEvent.change(startTimeInput, { target: { value: "09:00" } });
210-
fireEvent.change(endDateInput, { target: { value: "2024/01/20" } });
211-
fireEvent.change(endTimeInput, { target: { value: "17:00" } });
212-
}
208+
it("displays 'From' when only start date is set", () => {
209+
const props = {
210+
...defaultProps,
211+
filter: { ...mockFilter, value: { endDate: undefined, startDate: "2024-01-15T09:00:00Z" } },
212+
};
213213

214-
await waitFor(() => {
215-
// Should not show any validation errors
216-
expect(screen.queryByText("Invalid date format.")).not.toBeInTheDocument();
217-
expect(screen.queryByText("Invalid time format.")).not.toBeInTheDocument();
218-
expect(screen.queryByText("Start date/time must be before end date/time")).not.toBeInTheDocument();
214+
renderFilter(props);
215+
expect(screen.getAllByText(/From/u).length).toBeGreaterThan(0);
219216
});
220-
});
221-
222-
it("clears validation errors when invalid input is corrected", async () => {
223-
render(
224-
<TestWrapper>
225-
<DateRangeFilter {...defaultProps} />
226-
</TestWrapper>,
227-
);
228217

229-
const [startDateInput] = screen.getAllByPlaceholderText("YYYY/MM/DD");
218+
it("displays 'To' when only end date is set", () => {
219+
const props = {
220+
...defaultProps,
221+
filter: { ...mockFilter, value: { endDate: "2024-01-20T17:00:00Z", startDate: undefined } },
222+
};
230223

231-
if (startDateInput) {
232-
// First, set an invalid date
233-
fireEvent.change(startDateInput, { target: { value: "invalid-date" } });
224+
renderFilter(props);
225+
expect(screen.getAllByText(/To/u).length).toBeGreaterThan(0);
226+
});
227+
});
234228

235-
await waitFor(() => {
236-
expect(screen.getByText("Invalid date format.")).toBeInTheDocument();
237-
});
229+
describe("Edge Cases", () => {
230+
it("handles leap years and boundary dates", async () => {
231+
renderFilter();
232+
const { endDateInput, startDateInput } = getInputs();
233+
234+
changeDateInput(startDateInput, "2024/02/29");
235+
await waitForNoError("Invalid date format.");
236+
changeDateInput(startDateInput, "2023/02/29");
237+
await waitForError("Invalid date format.");
238+
changeDateInput(startDateInput, "2024/01/31");
239+
changeDateInput(endDateInput, "2024/02/01");
240+
await waitForNoError("Invalid date format.");
241+
});
238242

239-
// Then, correct it to a valid date
240-
fireEvent.change(startDateInput, { target: { value: "2024/01/15" } });
241-
}
243+
it("handles undefined filter value", () => {
244+
const props = { ...defaultProps, filter: { ...mockFilter, value: undefined } };
242245

243-
await waitFor(() => {
244-
expect(screen.queryByText("Invalid date format.")).not.toBeInTheDocument();
246+
// Should not throw when filter value is undefined
247+
renderFilter(props);
245248
});
246249
});
247250
});

0 commit comments

Comments
 (0)