Skip to content

Commit 7e73089

Browse files
AG-16876 Fix date filter range validation persisting after type switch (ag-grid#13271)
1 parent 16fdff1 commit 7e73089

2 files changed

Lines changed: 191 additions & 4 deletions

File tree

packages/ag-grid-community/src/filter/provided/date/dateFilter.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { _warn } from '../../../validation/logging';
99
import type { FilterLocaleTextKey } from '../../filterLocaleText';
1010
import type { ICombinedSimpleModel, Tuple } from '../iSimpleFilter';
1111
import { SimpleFilter } from '../simpleFilter';
12-
import { removeItems } from '../simpleFilterUtils';
12+
import { getNumberOfInputs, removeItems } from '../simpleFilterUtils';
1313
import { DateCompWrapper } from './dateCompWrapper';
1414
import { DEFAULT_DATE_FILTER_OPTIONS } from './dateFilterConstants';
1515
import { mapValuesFromDateFilterModel } from './dateFilterUtils';
@@ -104,9 +104,11 @@ export class DateFilter extends SimpleFilter<DateFilterModel, Date, DateCompWrap
104104
const from = dateConditionFromComps[position];
105105
const to = dateConditionToComps[position];
106106

107+
const numberOfInputs = getNumberOfInputs(this.getConditionType(position), this.optionsFactory);
108+
107109
const fromDate = from.getDate();
108110
const toDate = to.getDate();
109-
const localeKey = getValidityMessageKey(fromDate, toDate, isFrom);
111+
const localeKey = numberOfInputs >= 2 ? getRangeValidityMessageKey(fromDate, toDate, isFrom) : null;
110112
const message = localeKey ? this.translate(localeKey, [String(isFrom ? toDate : fromDate)]) : '';
111113

112114
// FF seems to handle cursors/focus sufficiently well for the validation to be left as synchronous.
@@ -356,7 +358,7 @@ export class DateFilter extends SimpleFilter<DateFilterModel, Date, DateCompWrap
356358
}
357359
}
358360

359-
function getValidityMessageKey(
361+
function getRangeValidityMessageKey(
360362
fromDate: Date | null,
361363
toDate: Date | null,
362364
isFrom: boolean

testing/behavioural/src/filters/date-filter-range-validation.test.ts

Lines changed: 186 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
import { getByTestId, waitFor } from '@testing-library/dom';
1+
import { getAllByTestId, getByTestId, waitFor } from '@testing-library/dom';
22
import { userEvent } from '@testing-library/user-event';
33

4+
import type { DateFilterModel } from 'ag-grid-community';
45
import {
56
ClientSideRowModelModule,
67
DateFilterModule,
@@ -101,3 +102,187 @@ describe('Number Range Filter', () => {
101102
expect(toNumberInput).toHaveAttribute('aria-invalid', 'false');
102103
});
103104
});
105+
106+
async function selectFilterOption(gridDiv: HTMLElement, userSession: any, optionText: string): Promise<void> {
107+
const pickerDisplay = getAllByTestId(
108+
gridDiv,
109+
agTestIdFor.filterInstancePickerDisplay({ source: 'column-filter' })
110+
)[0];
111+
await userSession.click(pickerDisplay);
112+
113+
await asyncSetTimeout(0);
114+
115+
const listItems = document.querySelectorAll('.ag-list-item');
116+
let targetItem: Element | null = null;
117+
listItems.forEach((item) => {
118+
if (item.textContent?.trim() === optionText) {
119+
targetItem = item;
120+
}
121+
});
122+
expect(targetItem).not.toBeNull();
123+
await userSession.click(targetItem!);
124+
125+
await asyncSetTimeout(0);
126+
}
127+
128+
describe('Date Range Filter', () => {
129+
const gridsManager = new TestGridsManager({
130+
modules: [DateFilterModule, ClientSideRowModelModule, TextFilterModule],
131+
});
132+
133+
beforeAll(() => setupAgTestIds());
134+
afterEach(() => gridsManager.reset());
135+
136+
test('Switching from inRange to equals clears range validation on the from input', async () => {
137+
const userSession = userEvent.setup();
138+
139+
const api = await gridsManager.createGridAndWait('grid1', {
140+
columnDefs: [
141+
{
142+
field: 'date',
143+
filter: 'agDateColumnFilter',
144+
filterParams: {
145+
filterOptions: ['inRange', 'equals'],
146+
},
147+
},
148+
],
149+
rowData: [{ date: '2024-01-15' }, { date: '2024-06-15' }, { date: '2024-12-15' }],
150+
});
151+
152+
const gridDiv = getGridElement(api)! as HTMLElement;
153+
154+
await asyncSetTimeout(0);
155+
156+
// Open the filter popup
157+
const filterBtn = getByTestId(gridDiv, agTestIdFor.headerFilterButton('date'));
158+
await userSession.click(filterBtn);
159+
160+
await asyncSetTimeout(0);
161+
162+
// Enter dates into the inRange inputs: from=2024-01-15, to=2024-06-15
163+
const fromDateInput = getByTestId<HTMLInputElement>(
164+
gridDiv,
165+
agTestIdFor.dateFilterInstanceInput({ source: 'column-filter', index: 0 })
166+
);
167+
const toDateInput = getByTestId<HTMLInputElement>(
168+
gridDiv,
169+
agTestIdFor.dateFilterInstanceInput({ source: 'column-filter', index: 1 })
170+
);
171+
172+
// Use fireEvent to set date values (userEvent.type doesn't work well with date inputs)
173+
fromDateInput.valueAsDate = new Date('2024-01-15');
174+
fromDateInput.dispatchEvent(new Event('input', { bubbles: true }));
175+
fromDateInput.dispatchEvent(new Event('change', { bubbles: true }));
176+
177+
toDateInput.valueAsDate = new Date('2024-06-15');
178+
toDateInput.dispatchEvent(new Event('input', { bubbles: true }));
179+
toDateInput.dispatchEvent(new Event('change', { bubbles: true }));
180+
181+
await asyncSetTimeout(0);
182+
183+
// Both inputs should be valid (from < to)
184+
expect(fromDateInput.validity.valid).toBe(true);
185+
expect(toDateInput.validity.valid).toBe(true);
186+
187+
await waitFor(() => {
188+
const model = api.getFilterModel()?.date as DateFilterModel;
189+
expect(model).toBeTruthy();
190+
expect(model.type).toBe('inRange');
191+
});
192+
193+
// Switch to "equals" via the filter type picker
194+
await selectFilterOption(gridDiv, userSession, 'Equals');
195+
196+
// Now the filter is "equals" - the to input is hidden but still has its value.
197+
// Change the from date to match what was in the to date.
198+
const fromDateInputEquals = getByTestId<HTMLInputElement>(
199+
gridDiv,
200+
agTestIdFor.dateFilterInstanceInput({ source: 'column-filter' })
201+
);
202+
203+
fromDateInputEquals.valueAsDate = new Date('2024-06-15');
204+
fromDateInputEquals.dispatchEvent(new Event('input', { bubbles: true }));
205+
fromDateInputEquals.dispatchEvent(new Event('change', { bubbles: true }));
206+
207+
await asyncSetTimeout(0);
208+
209+
// The from input should be valid - no range validation should apply for "equals"
210+
expect(fromDateInputEquals.validity.valid).toBe(true);
211+
});
212+
213+
test('Switching from equals back to inRange re-enables range validation', async () => {
214+
const userSession = userEvent.setup();
215+
216+
const api = await gridsManager.createGridAndWait('grid1', {
217+
columnDefs: [
218+
{
219+
field: 'date',
220+
filter: 'agDateColumnFilter',
221+
filterParams: {
222+
filterOptions: ['inRange', 'equals'],
223+
},
224+
},
225+
],
226+
rowData: [{ date: '2024-01-15' }, { date: '2024-06-15' }, { date: '2024-12-15' }],
227+
});
228+
229+
const gridDiv = getGridElement(api)! as HTMLElement;
230+
231+
await asyncSetTimeout(0);
232+
233+
// Open the filter popup
234+
const filterBtn = getByTestId(gridDiv, agTestIdFor.headerFilterButton('date'));
235+
await userSession.click(filterBtn);
236+
237+
await asyncSetTimeout(0);
238+
239+
// Enter valid inRange dates: from=2024-01-15, to=2024-06-15
240+
const fromDateInput = getByTestId<HTMLInputElement>(
241+
gridDiv,
242+
agTestIdFor.dateFilterInstanceInput({ source: 'column-filter', index: 0 })
243+
);
244+
const toDateInput = getByTestId<HTMLInputElement>(
245+
gridDiv,
246+
agTestIdFor.dateFilterInstanceInput({ source: 'column-filter', index: 1 })
247+
);
248+
249+
fromDateInput.valueAsDate = new Date('2024-01-15');
250+
fromDateInput.dispatchEvent(new Event('input', { bubbles: true }));
251+
toDateInput.valueAsDate = new Date('2024-06-15');
252+
toDateInput.dispatchEvent(new Event('input', { bubbles: true }));
253+
254+
await asyncSetTimeout(0);
255+
256+
// Switch to "equals"
257+
await selectFilterOption(gridDiv, userSession, 'Equals');
258+
259+
// Change the from date to be after what was in the to date
260+
const fromDateInputEquals = getByTestId<HTMLInputElement>(
261+
gridDiv,
262+
agTestIdFor.dateFilterInstanceInput({ source: 'column-filter' })
263+
);
264+
265+
fromDateInputEquals.valueAsDate = new Date('2024-12-15');
266+
fromDateInputEquals.dispatchEvent(new Event('input', { bubbles: true }));
267+
268+
await asyncSetTimeout(0);
269+
270+
// Valid in "equals" mode - no range validation
271+
expect(fromDateInputEquals.validity.valid).toBe(true);
272+
273+
// Switch back to "inRange" - the from date (2024-12-15) is now after the to date (2024-06-15)
274+
await selectFilterOption(gridDiv, userSession, 'Between');
275+
276+
// Trigger validation by interacting with the from input
277+
const fromDateInputRange = getByTestId<HTMLInputElement>(
278+
gridDiv,
279+
agTestIdFor.dateFilterInstanceInput({ source: 'column-filter', index: 0 })
280+
);
281+
fromDateInputRange.dispatchEvent(new Event('focusin', { bubbles: true }));
282+
283+
await asyncSetTimeout(0);
284+
285+
// Range validation should now be active again - from > to is invalid
286+
expect(fromDateInputRange.validity.valid).toBe(false);
287+
});
288+
});

0 commit comments

Comments
 (0)