Skip to content

Commit cd399bc

Browse files
sakshi-arora1Armaan Gupta
authored andcommitted
Fixing date picker min max constraints. (#1779)
* Fixing date picker min max constraints. * Adding the test collateral. * Formating date using DefaultValueSerializer, for sightly.
1 parent 3d47d9b commit cd399bc

File tree

5 files changed

+223
-22
lines changed

5 files changed

+223
-22
lines changed

bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/util/DefaultValueSerializer.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
*/
3232
public class DefaultValueSerializer extends StdSerializer<Object[]> {
3333

34-
private SimpleDateFormat formatter = new SimpleDateFormat(Base.DATE_FORMATTER);
34+
private static final SimpleDateFormat formatter = new SimpleDateFormat(Base.DATE_FORMATTER);
3535

3636
public DefaultValueSerializer() {
3737
this(null);
@@ -41,6 +41,10 @@ public DefaultValueSerializer(Class<Object[]> t) {
4141
super(t);
4242
}
4343

44+
public static synchronized String formatDate(Date date) {
45+
return formatter.format(date);
46+
}
47+
4448
private void serialize(Object value, JsonGenerator gen) throws IOException {
4549
if (value instanceof Boolean) {
4650
gen.writeBoolean((Boolean) value);
@@ -53,9 +57,9 @@ private void serialize(Object value, JsonGenerator gen) throws IOException {
5357
} else if (value instanceof String) {
5458
gen.writeString((String) value);
5559
} else if (value instanceof Date) {
56-
gen.writeString(formatter.format(value));
60+
gen.writeString(formatDate((Date) value));
5761
} else if (value instanceof Calendar) {
58-
gen.writeString(formatter.format(((Calendar) value).getTime()));
62+
gen.writeString(formatDate(((Calendar) value).getTime()));
5963
}
6064
}
6165

it/content/src/main/content/jcr_root/content/forms/af/core-components-it/samples/datepicker/basic/.content.xml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,24 @@
223223
textIsRich="[true,true,true]"
224224
unboundFormElement="{Boolean}false"
225225
visible="{Boolean}true"/>
226+
<datepicker_850803674
227+
jcr:created="{Date}2025-12-16T22:16:06.468+05:30"
228+
jcr:createdBy="admin"
229+
jcr:lastModified="{Date}2025-12-16T22:16:20.471+05:30"
230+
jcr:lastModifiedBy="admin"
231+
jcr:primaryType="nt:unstructured"
232+
jcr:title="Date Input"
233+
sling:resourceType="forms-components-examples/components/form/datepicker"
234+
enabled="{Boolean}true"
235+
fieldType="date-input"
236+
hideTitle="false"
237+
maximumDate="{Date}2025-12-16T00:00:00.000Z"
238+
minimumDate="{Date}2025-12-02T00:00:00.000Z"
239+
name="datepicker_8508036741765903566499"
240+
readOnly="{Boolean}false"
241+
textIsRich="[true,true,true]"
242+
unboundFormElement="{Boolean}false"
243+
visible="{Boolean}true"/>
226244
</guideContainer>
227245
</jcr:content>
228246
</jcr:root>
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*******************************************************************************
2+
* Copyright 2025 Adobe
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
******************************************************************************/
16+
use(function () {
17+
var DefaultValueSerializer = Packages.com.adobe.cq.forms.core.components.util.DefaultValueSerializer;
18+
date = this.date;
19+
20+
/**
21+
* Formats a Java Date object to yyyy-MM-dd string format for HTML5 date input.
22+
* Uses DefaultValueSerializer.formatDate() to ensure consistent date formatting.
23+
* @param {java.util.Date} date - The Java Date object to format
24+
* @return {string} Formatted date string or null if date is null
25+
*/
26+
var formatDate = function() {
27+
if (date != null) {
28+
return DefaultValueSerializer.formatDate(date);
29+
}
30+
return null;
31+
};
32+
33+
return {
34+
formatDate: formatDate
35+
}
36+
});

ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/datepicker/v1/datepicker/datepicker.html

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -34,22 +34,24 @@
3434
<div class="cmp-adaptiveform-datepicker__label-container">
3535
<div data-sly-call="${label.label @componentId=widgetId, labelValue=datePicker.label.value, labelVisible=datePicker.label.visible, labelRichText=datePicker.label.richText, bemBlock='cmp-adaptiveform-datepicker'}" data-sly-unwrap></div>
3636
<div data-sly-call="${questionMark.questionMark @componentId=datePicker.id, longDescription=datePicker.description, bemBlock='cmp-adaptiveform-datepicker'}" data-sly-unwrap></div>
37-
</div>
38-
<input type="date"
39-
name="${datePicker.name}"
40-
class="cmp-adaptiveform-datepicker__widget"
41-
disabled="${!datePicker.enabled}"
42-
readonly="${datePicker.readOnly}"
43-
required="${datePicker.required}"
44-
value="${datePicker.default? datePicker.default : ''}"
45-
data-cmp-data-layer="${datePicker.data.json}"
46-
title="${datePicker.tooltipVisible ? '' : datePicker.tooltipText}"
47-
id="${widgetId}"
48-
placeholder="${datePicker.placeHolder}"
49-
min="${datePicker.minimumDate}"
50-
max="${datePicker.maximumDate}"/>
51-
<div data-sly-call="${shortDescription.shortDescription @componentId=datePicker.id, shortDescriptionVisible=datePicker.tooltipVisible, shortDescription=datePicker.tooltip, bemBlock='cmp-adaptiveform-datepicker'}" data-sly-unwrap>
52-
</div>
53-
<div data-sly-call="${longDescription.longDescription @componentId=datePicker.id, longDescription=datePicker.description, bemBlock='cmp-adaptiveform-datepicker'}" data-sly-unwrap></div>
54-
<div data-sly-call="${errorMessage.errorMessage @componentId=datePicker.id, bemBlock='cmp-adaptiveform-datepicker'}" data-sly-unwrap></div>
55-
</div>
37+
</div>
38+
<sly data-sly-use.minDate="${'dateformatter.js' @date=datePicker.minimumDate}"
39+
data-sly-use.maxDate="${'dateformatter.js' @date=datePicker.maximumDate}"></sly>
40+
<input type="date"
41+
name="${datePicker.name}"
42+
class="cmp-adaptiveform-datepicker__widget"
43+
disabled="${!datePicker.enabled}"
44+
readonly="${datePicker.readOnly}"
45+
required="${datePicker.required}"
46+
value="${datePicker.default? datePicker.default : ''}"
47+
data-cmp-data-layer="${datePicker.data.json}"
48+
title="${datePicker.tooltipVisible ? '' : datePicker.tooltipText}"
49+
id="${widgetId}"
50+
placeholder="${datePicker.placeHolder}"
51+
min="${minDate.formatDate}"
52+
max="${maxDate.formatDate}"/>
53+
<div data-sly-call="${shortDescription.shortDescription @componentId=datePicker.id, shortDescriptionVisible=datePicker.tooltipVisible, shortDescription=datePicker.tooltip, bemBlock='cmp-adaptiveform-datepicker'}" data-sly-unwrap>
54+
</div>
55+
<div data-sly-call="${longDescription.longDescription @componentId=datePicker.id, longDescription=datePicker.description, bemBlock='cmp-adaptiveform-datepicker'}" data-sly-unwrap></div>
56+
<div data-sly-call="${errorMessage.errorMessage @componentId=datePicker.id, bemBlock='cmp-adaptiveform-datepicker'}" data-sly-unwrap></div>
57+
</div>
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
/*******************************************************************************
2+
* Copyright 2025 Adobe
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
******************************************************************************/
16+
describe("Form Runtime with Date Picker - Min/Max Constraints", () => {
17+
18+
const pagePath = "content/forms/af/core-components-it/samples/datepicker/basic.html"
19+
const bemBlock = 'cmp-adaptiveform-datepicker'
20+
21+
let formContainer = null
22+
const fmPropertiesUI = "/libs/fd/fm/gui/content/forms/formmetadataeditor.html/content/dam/formsanddocuments/core-components-it/samples/datepicker/basic"
23+
const themeRef = 'input[name="./jcr:content/metadata/themeRef"]'
24+
const propertiesSaveBtn = '#shell-propertiespage-doneactivator'
25+
26+
// enabling theme for this test case as without theme there is a bug in custom widget css
27+
before(() => {
28+
cy.openPage(fmPropertiesUI).then(() => {
29+
cy.get(themeRef).invoke('val', '').type('/libs/fd/af/themes/canvas', {force: true}).then(() => {
30+
cy.get(propertiesSaveBtn).click();
31+
})
32+
})
33+
})
34+
35+
beforeEach(() => {
36+
cy.previewForm(pagePath).then(p => {
37+
formContainer = p;
38+
})
39+
});
40+
41+
it("should have min and max attributes correctly set on input element", () => {
42+
const [dateInput12, dateInput12FieldView] = Object.entries(formContainer._fields)[12];
43+
44+
// Verify min attribute is set correctly (should be formatted as yyyy-MM-dd)
45+
cy.get(`#${dateInput12}`).find("input").should('have.attr', 'min', '2025-12-02');
46+
47+
// Verify max attribute is set correctly (should be formatted as yyyy-MM-dd)
48+
cy.get(`#${dateInput12}`).find("input").should('have.attr', 'max', '2025-12-16');
49+
});
50+
51+
it("should enforce min constraint when date is below minimum", () => {
52+
const [dateInput12, dateInput12FieldView] = Object.entries(formContainer._fields)[12];
53+
const dateBelowMin = "2025-07-09"; // One day before minimum
54+
55+
cy.get(`#${dateInput12}`).find("input").clear().type(dateBelowMin).blur().then(() => {
56+
// The browser should prevent dates below min, but if entered, validation should catch it
57+
cy.get(`#${dateInput12}`).find("input").invoke('val').then((val) => {
58+
// If browser enforces min constraint, the value might be empty or the min date
59+
// If validation catches it, error message should appear
60+
if (val === dateBelowMin) {
61+
cy.get(`#${dateInput12}`).find(".cmp-adaptiveform-datepicker__errormessage")
62+
.should('not.have.text', '');
63+
}
64+
});
65+
});
66+
});
67+
68+
it("should enforce max constraint when date is above maximum", () => {
69+
const [dateInput12, dateInput12FieldView] = Object.entries(formContainer._fields)[12];
70+
const dateAboveMax = "2025-12-18"; // One day after maximum
71+
72+
cy.get(`#${dateInput12}`).find("input").clear().type(dateAboveMax).blur().then(() => {
73+
// The browser should prevent dates above max, but if entered, validation should catch it
74+
cy.get(`#${dateInput12}`).find("input").invoke('val').then((val) => {
75+
// If browser enforces max constraint, the value might be empty or the max date
76+
// If validation catches it, error message should appear
77+
if (val === dateAboveMax) {
78+
cy.get(`#${dateInput12}`).find(".cmp-adaptiveform-datepicker__errormessage")
79+
.should('not.have.text', '');
80+
}
81+
});
82+
});
83+
});
84+
85+
it("should accept dates within min and max range", () => {
86+
const [dateInput12, dateInput12FieldView] = Object.entries(formContainer._fields)[12];
87+
const dateInRange = "2025-12-15"; // Within the range
88+
89+
cy.get(`#${dateInput12}`).find("input").clear().type(dateInRange).blur().then(() => {
90+
cy.get(`#${dateInput12}`).find("input").should('have.value', dateInRange);
91+
cy.get(`#${dateInput12}`).find(".cmp-adaptiveform-datepicker__errormessage")
92+
.should('have.text', '');
93+
});
94+
});
95+
96+
it("should accept minimum date value", () => {
97+
const [dateInput12, dateInput12FieldView] = Object.entries(formContainer._fields)[12];
98+
const minDate = "2025-12-02"; // Exactly the minimum date
99+
100+
cy.get(`#${dateInput12}`).find("input").clear().type(minDate).blur().then(() => {
101+
cy.get(`#${dateInput12}`).find("input").should('have.value', minDate);
102+
// Note: Since excludeMinimum is true, this date should show validation error
103+
// But the min attribute should still be set correctly
104+
});
105+
});
106+
107+
it("should accept maximum date value", () => {
108+
const [dateInput12, dateInput12FieldView] = Object.entries(formContainer._fields)[12];
109+
const maxDate = "2025-12-16"; // Exactly the maximum date
110+
111+
cy.get(`#${dateInput12}`).find("input").clear().type(maxDate).blur().then(() => {
112+
cy.get(`#${dateInput12}`).find("input").should('have.value', maxDate);
113+
// Note: Since excludeMaximum is true, this date should show validation error
114+
// But the max attribute should still be set correctly
115+
});
116+
});
117+
118+
it("should format min and max dates correctly as yyyy-MM-dd", () => {
119+
const [dateInput12, dateInput12FieldView] = Object.entries(formContainer._fields)[12];
120+
121+
// Verify the format matches HTML5 date input format (yyyy-MM-dd)
122+
cy.get(`#${dateInput12}`).find("input").invoke('attr', 'min').then((minValue) => {
123+
expect(minValue).to.match(/^\d{4}-\d{2}-\d{2}$/);
124+
expect(minValue).to.equal('2025-12-02');
125+
});
126+
127+
cy.get(`#${dateInput12}`).find("input").invoke('attr', 'max').then((maxValue) => {
128+
expect(maxValue).to.match(/^\d{4}-\d{2}-\d{2}$/);
129+
expect(maxValue).to.equal('2025-12-16');
130+
});
131+
});
132+
133+
it("should not have min or max attributes when constraints are not set", () => {
134+
// Find a datepicker without min/max constraints (first datepicker typically doesn't have them)
135+
const [datePicker1, datePicker1FieldView] = Object.entries(formContainer._fields)[0];
136+
137+
cy.get(`#${datePicker1}`).find("input").should('not.have.attr', 'min');
138+
cy.get(`#${datePicker1}`).find("input").should('not.have.attr', 'max');
139+
});
140+
});
141+

0 commit comments

Comments
 (0)