Skip to content

Commit b78e238

Browse files
authored
refactor(date-picker): Interaction behavior in readonly mode (#1692)
* refactor(date-picker): Interaction behavior in readonly mode
1 parent 42655bf commit b78e238

File tree

4 files changed

+633
-501
lines changed

4 files changed

+633
-501
lines changed

src/components/calendar/helpers.spec.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@ export function getDayViewDOM(element: IgcDaysViewComponent) {
5151
)
5252
);
5353
},
54+
get current() {
55+
return root.querySelector('span[part~="current"]')!;
56+
},
5457
},
5558
};
5659
}
Lines changed: 366 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,366 @@
1+
import { elementUpdated, expect, fixture, html } from '@open-wc/testing';
2+
import { CalendarDay, toCalendarDay } from '../calendar/model.js';
3+
import { type DateRangeDescriptor, DateRangeType } from '../calendar/types.js';
4+
import { defineComponents } from '../common/definitions/defineComponents.js';
5+
import { equal } from '../common/util.js';
6+
import {
7+
type ValidationContainerTestsParams,
8+
createFormAssociatedTestBed,
9+
runValidationContainerTests,
10+
simulatePointerDown,
11+
} from '../common/utils.spec.js';
12+
import IgcDateTimeInputComponent from '../date-time-input/date-time-input.js';
13+
import IgcDatePickerComponent from './date-picker.js';
14+
15+
describe('igc-datepicker form integration', () => {
16+
before(() => defineComponents(IgcDatePickerComponent));
17+
18+
function checkDatesEqual(a: CalendarDay | Date, b: CalendarDay | Date) {
19+
expect(equal(toCalendarDay(a), toCalendarDay(b))).to.be.true;
20+
}
21+
22+
describe('Initial validation', () => {
23+
it('should not enter in invalid state when clicking the calendar toggle part', async () => {
24+
const picker = await fixture<IgcDatePickerComponent>(
25+
html`<igc-date-picker required></igc-date-picker>`
26+
);
27+
const dateTimeInput = picker.renderRoot.querySelector(
28+
IgcDateTimeInputComponent.tagName
29+
)!;
30+
const icon = picker.renderRoot.querySelector('[name="today"]')!;
31+
32+
expect(picker.invalid).to.be.false;
33+
expect(dateTimeInput.invalid).to.be.false;
34+
35+
simulatePointerDown(icon);
36+
await elementUpdated(picker);
37+
38+
expect(picker.invalid).to.be.false;
39+
expect(dateTimeInput.invalid).to.be.false;
40+
});
41+
});
42+
43+
describe('Form integration', () => {
44+
const today = CalendarDay.today;
45+
const spec = createFormAssociatedTestBed<IgcDatePickerComponent>(
46+
html`<igc-date-picker name="datePicker"></igc-date-picker>`
47+
);
48+
49+
beforeEach(async () => {
50+
await spec.setup(IgcDatePickerComponent.tagName);
51+
});
52+
53+
it('should be form associated', () => {
54+
expect(spec.element.form).to.equal(spec.form);
55+
});
56+
57+
it('should not participate in form submission if the value is empty/invalid', () => {
58+
spec.assertSubmitHasValue(null);
59+
});
60+
61+
it('should participate in form submission if there is a value and the value adheres to the validation constraints', () => {
62+
spec.setProperties({ value: today.native });
63+
spec.assertSubmitHasValue(spec.element.value?.toISOString());
64+
});
65+
66+
it('should reset to its default value state on form reset', () => {
67+
spec.setProperties({ value: today.native });
68+
spec.reset();
69+
70+
expect(spec.element.value).to.be.null;
71+
});
72+
73+
it('should reset to the new default value after setAttribute() call', () => {
74+
spec.setAttributes({ value: today.native.toISOString() });
75+
spec.setProperties({ value: today.add('day', 180).native });
76+
spec.reset();
77+
78+
checkDatesEqual(spec.element.value!, today);
79+
spec.assertSubmitHasValue(today.native.toISOString());
80+
});
81+
82+
it('should reflect disabled ancestor state (fieldset/form)', () => {
83+
spec.setAncestorDisabledState(true);
84+
expect(spec.element.disabled).to.be.true;
85+
86+
spec.setAncestorDisabledState(false);
87+
expect(spec.element.disabled).to.be.false;
88+
});
89+
90+
it('should enforce required constraint', () => {
91+
spec.setProperties({ required: true });
92+
spec.assertSubmitFails();
93+
94+
spec.setProperties({ value: today.native });
95+
spec.assertSubmitPasses();
96+
});
97+
98+
it('should enforce min value constraint', () => {
99+
// No value - submit passes
100+
spec.setProperties({ min: new Date(2026, 0, 1) });
101+
spec.assertSubmitPasses();
102+
103+
// Invalid min constraint
104+
spec.setProperties({ value: new Date(2022, 0, 1) });
105+
spec.assertSubmitFails();
106+
107+
// Valid value
108+
spec.setProperties({ value: new Date(2026, 0, 2) });
109+
spec.assertSubmitPasses();
110+
});
111+
112+
it('should enforce max value constraint', () => {
113+
// No value - submit passes
114+
spec.setProperties({ max: new Date(2020, 0, 1) });
115+
spec.assertSubmitPasses();
116+
117+
// Invalid max constraint
118+
spec.setProperties({ value: today.native });
119+
spec.assertSubmitFails();
120+
121+
// Valid value
122+
spec.setProperties({ value: new Date(2020, 0, 1) });
123+
spec.assertSubmitPasses();
124+
});
125+
126+
it('should enforce min value constraint with string property', () => {
127+
// No value - submit passes
128+
spec.setProperties({ min: new Date(2026, 0, 1).toISOString() });
129+
spec.assertSubmitPasses();
130+
131+
// Invalid min constraint
132+
spec.setProperties({ value: new Date(2022, 0, 1).toISOString() });
133+
spec.assertSubmitFails();
134+
135+
// Valid value
136+
spec.setProperties({ value: new Date(2026, 0, 2).toISOString() });
137+
spec.assertSubmitPasses();
138+
});
139+
140+
it('should enforce max value constraint with string property', () => {
141+
// No value - submit passes
142+
spec.setProperties({ max: new Date(2020, 0, 1).toISOString() });
143+
spec.assertSubmitPasses();
144+
145+
// Invalid max constraint
146+
spec.setProperties({ value: today.native });
147+
spec.assertSubmitFails();
148+
149+
// Valid value
150+
spec.setProperties({ value: new Date(2020, 0, 1).toISOString() });
151+
spec.assertSubmitPasses();
152+
});
153+
154+
it('should invalidate the component if a disabled date is typed in the input', () => {
155+
const minDate = new Date(2024, 1, 1);
156+
const maxDate = new Date(2024, 1, 28);
157+
158+
const disabledDates: DateRangeDescriptor[] = [
159+
{
160+
type: DateRangeType.Between,
161+
dateRange: [minDate, maxDate],
162+
},
163+
];
164+
165+
spec.setProperties({ disabledDates, value: new Date(2024, 1, 26) });
166+
167+
expect(spec.element.invalid).to.be.true;
168+
spec.assertSubmitFails();
169+
});
170+
171+
it('should enforce custom constraint', () => {
172+
spec.element.setCustomValidity('invalid');
173+
spec.assertSubmitFails();
174+
175+
spec.element.setCustomValidity('');
176+
spec.assertSubmitPasses();
177+
});
178+
179+
it('synchronous form validation', () => {
180+
spec.setProperties({ required: true }, false);
181+
182+
expect(spec.form.checkValidity()).to.be.false;
183+
spec.assertSubmitFails();
184+
185+
spec.reset();
186+
187+
spec.setProperties({ value: today.native }, false);
188+
189+
expect(spec.form.checkValidity()).to.be.true;
190+
spec.assertSubmitPasses();
191+
});
192+
});
193+
194+
describe('defaultValue', () => {
195+
const today = CalendarDay.today;
196+
197+
describe('Form integration', () => {
198+
const spec = createFormAssociatedTestBed<IgcDatePickerComponent>(html`
199+
<igc-date-picker
200+
name="datePicker"
201+
.defaultValue=${today.native}
202+
></igc-date-picker>
203+
`);
204+
205+
beforeEach(async () => {
206+
await spec.setup(IgcDatePickerComponent.tagName);
207+
});
208+
209+
it('correct initial state', () => {
210+
spec.assertIsPristine();
211+
checkDatesEqual(spec.element.value!, today);
212+
});
213+
214+
it('is correctly submitted', () => {
215+
spec.assertSubmitHasValue(today.native.toISOString());
216+
});
217+
218+
it('is correctly reset', () => {
219+
spec.setProperties({ value: today.add('day', 1).native });
220+
spec.reset();
221+
222+
checkDatesEqual(spec.element.value!, today);
223+
});
224+
});
225+
226+
describe('Validation', () => {
227+
const spec = createFormAssociatedTestBed<IgcDatePickerComponent>(html`
228+
<igc-date-picker
229+
name="datePicker"
230+
.defaultValue=${null}
231+
></igc-date-picker>
232+
`);
233+
234+
beforeEach(async () => {
235+
await spec.setup(IgcDatePickerComponent.tagName);
236+
});
237+
238+
it('fails required validation', () => {
239+
spec.setProperties({ required: true });
240+
spec.assertIsPristine();
241+
spec.assertSubmitFails();
242+
});
243+
244+
it('passes required validation when updating defaultValue', () => {
245+
spec.setProperties({ required: true, defaultValue: today.native });
246+
spec.assertIsPristine();
247+
248+
spec.assertSubmitPasses();
249+
});
250+
251+
it('fails min validation', () => {
252+
spec.setProperties({
253+
min: today.native,
254+
defaultValue: today.add('day', -1).native,
255+
});
256+
257+
spec.assertIsPristine();
258+
spec.assertSubmitFails();
259+
});
260+
261+
it('passes min validation', () => {
262+
spec.setProperties({ min: today.native, defaultValue: today.native });
263+
264+
spec.assertIsPristine();
265+
spec.assertSubmitPasses();
266+
});
267+
268+
it('fails max validation', () => {
269+
spec.setProperties({
270+
max: today.native,
271+
defaultValue: today.add('day', 1).native,
272+
});
273+
274+
spec.assertIsPristine();
275+
spec.assertSubmitFails();
276+
});
277+
278+
it('passes max validation', () => {
279+
spec.setProperties({
280+
max: today.native,
281+
defaultValue: today.native,
282+
});
283+
284+
spec.assertIsPristine();
285+
spec.assertSubmitPasses();
286+
});
287+
288+
it('fails for range constraints', () => {
289+
const minDate = new Date(2024, 1, 1);
290+
const maxDate = new Date(2024, 1, 28);
291+
292+
const disabledDates: DateRangeDescriptor[] = [
293+
{
294+
type: DateRangeType.Between,
295+
dateRange: [minDate, maxDate],
296+
},
297+
];
298+
299+
spec.setProperties({
300+
disabledDates,
301+
defaultValue: new Date(2024, 1, 28),
302+
});
303+
304+
spec.assertIsPristine();
305+
spec.assertSubmitFails();
306+
});
307+
308+
it('passes for range constraints', () => {
309+
const minDate = new Date(2024, 1, 1);
310+
const maxDate = new Date(2024, 1, 28);
311+
312+
const disabledDates: DateRangeDescriptor[] = [
313+
{
314+
type: DateRangeType.Between,
315+
dateRange: [minDate, maxDate],
316+
},
317+
];
318+
319+
spec.setProperties({
320+
disabledDates,
321+
defaultValue: new Date(2024, 1, 29),
322+
});
323+
324+
spec.assertIsPristine();
325+
spec.assertSubmitPasses();
326+
});
327+
});
328+
});
329+
330+
describe('Validation message slots', () => {
331+
it('', () => {
332+
const now = CalendarDay.today;
333+
const tomorrow = now.add('day', 1);
334+
const yesterday = now.add('day', -1);
335+
336+
const testParameters: ValidationContainerTestsParams<IgcDatePickerComponent>[] =
337+
[
338+
{ slots: ['valueMissing'], props: { required: true } }, // value-missing slot
339+
{
340+
slots: ['rangeOverflow'],
341+
props: { value: now.native, max: yesterday.native }, // range-overflow slot
342+
},
343+
{
344+
slots: ['rangeUnderflow'],
345+
props: { value: now.native, min: tomorrow.native }, // range-underflow slot
346+
},
347+
{
348+
slots: ['badInput'],
349+
props: {
350+
value: now.native,
351+
disabledDates: [
352+
{
353+
type: DateRangeType.Between,
354+
dateRange: [yesterday.native, tomorrow.native], // bad-input slot
355+
},
356+
],
357+
},
358+
},
359+
{ slots: ['customError'] }, // custom-error slot
360+
{ slots: ['invalid'], props: { required: true } }, // invalid slot
361+
];
362+
363+
runValidationContainerTests(IgcDatePickerComponent, testParameters);
364+
});
365+
});
366+
});

0 commit comments

Comments
 (0)