diff --git a/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/models/v1/form/DropDownImpl.java b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/models/v1/form/DropDownImpl.java index ee02949480..212adf4ee9 100644 --- a/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/models/v1/form/DropDownImpl.java +++ b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/models/v1/form/DropDownImpl.java @@ -22,6 +22,7 @@ import org.apache.sling.models.annotations.Model; import org.apache.sling.models.annotations.injectorspecific.InjectionStrategy; import org.apache.sling.models.annotations.injectorspecific.ValueMapValue; +import org.jetbrains.annotations.Nullable; import com.adobe.cq.export.json.ComponentExporter; import com.adobe.cq.export.json.ExporterConstants; @@ -44,6 +45,10 @@ public class DropDownImpl extends AbstractOptionsFieldImpl implements DropDown { @Default(booleanValues = false) protected boolean multiSelect; + @ValueMapValue(injectionStrategy = InjectionStrategy.OPTIONAL, name = ReservedProperties.PN_PATTERN) + @Nullable + protected String pattern; + @Override public Integer getMinItems() { return minItems; @@ -87,4 +92,34 @@ public Type getType() { public String getFieldType() { return super.getFieldType(FieldType.DROP_DOWN); } + + /** + * String length/pattern constraints are only meaningful for a single-select (string-typed) drop-down, + * e.g. a searchable drop-down where {@code enforceEnum} is disabled and free text is allowed. For a + * multi-select drop-down the value type is an array, so these constraints are not applicable and are + * omitted from the model (mirroring the runtime, which strips them for non-string types). + */ + @Override + public Integer getMinLength() { + return isMultiSelect() ? null : minLength; + } + + @Override + public Integer getMaxLength() { + return isMultiSelect() ? null : maxLength; + } + + @Override + public String getPattern() { + return isMultiSelect() ? null : pattern; + } + + /** + * Drop-down does not support the {@code format} string constraint (there is no authorable format for an + * enumerated field), so this always returns {@code null} and is omitted from the exported model. + */ + @Override + public String getFormat() { + return null; + } } diff --git a/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/models/form/DropDown.java b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/models/form/DropDown.java index 3d753cb699..d2410e7195 100644 --- a/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/models/form/DropDown.java +++ b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/models/form/DropDown.java @@ -25,7 +25,7 @@ * @since com.adobe.cq.forms.core.components.models.form 2.0.0 */ @ConsumerType -public interface DropDown extends Field, OptionsConstraint, ContainerConstraint { +public interface DropDown extends Field, OptionsConstraint, ContainerConstraint, StringConstraint { /** * Returns {@code true} if multiple items can be selected in dropdown, {@code false} otherwise diff --git a/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v1/form/DropDownImplTest.java b/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v1/form/DropDownImplTest.java index 726e1980e8..f53ffc0df2 100644 --- a/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v1/form/DropDownImplTest.java +++ b/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v1/form/DropDownImplTest.java @@ -56,6 +56,7 @@ public class DropDownImplTest { private static final String PATH_MULTISELECT_DROPDOWN = CONTENT_ROOT + "/multiselect-dropdown"; private static final String PATH_MULTISELECT_DROPDOWN_WITH_VARIANT_PROPERTY = CONTENT_ROOT + "/multiselect-dropdown-2"; private static final String PATH_DROPDOWN2 = CONTENT_ROOT + "/dropdown2"; + private static final String PATH_SEARCHABLE_DROPDOWN = CONTENT_ROOT + "/searchable-dropdown"; private static final String PATH_DROPDOWN = CONTENT_ROOT + "/dropdown"; private static final String PATH_DROPDOWN_DATALAYER = CONTENT_ROOT + "/dropdown-datalayer"; @@ -287,6 +288,38 @@ void testMultiSelectJSONExport() throws Exception { Utils.testJSONExport(dropdown, Utils.getTestExporterJSONPath(BASE, PATH_MULTISELECT_DROPDOWN)); } + @Test + void testStringConstraintsForSingleSelect() { + DropDown dropdown = Utils.getComponentUnderTest(PATH_SEARCHABLE_DROPDOWN, DropDown.class, context); + assertFalse(dropdown.isMultiSelect()); + assertEquals(Integer.valueOf(2), dropdown.getMinLength()); + assertEquals(Integer.valueOf(10), dropdown.getMaxLength()); + assertEquals("[a-z]+", dropdown.getPattern()); + } + + @Test + void testStringConstraintsNullForMultiSelect() { + // authored minLength/maxLength/pattern must be dropped for a multi-select (array-typed) drop-down + DropDown dropdown = Utils.getComponentUnderTest(PATH_MULTISELECT_DROPDOWN, DropDown.class, context); + assertTrue(dropdown.isMultiSelect()); + assertNull(dropdown.getMinLength()); + assertNull(dropdown.getMaxLength()); + assertNull(dropdown.getPattern()); + } + + @Test + void testGetPatternDefault() { + DropDown dropdownMock = Mockito.mock(DropDown.class); + Mockito.when(dropdownMock.getPattern()).thenCallRealMethod(); + assertNull(dropdownMock.getPattern()); + } + + @Test + void testSearchableDropdownJSONExport() throws Exception { + DropDown dropdown = Utils.getComponentUnderTest(PATH_SEARCHABLE_DROPDOWN, DropDown.class, context); + Utils.testJSONExport(dropdown, Utils.getTestExporterJSONPath(BASE, PATH_SEARCHABLE_DROPDOWN)); + } + @Test void testGetProperties() throws Exception { DropDown dropdown = Utils.getComponentUnderTest(PATH_DROPDOWN_1, DropDown.class, context); diff --git a/bundles/af-core/src/test/resources/form/dropdown/exporter-searchable-dropdown.json b/bundles/af-core/src/test/resources/form/dropdown/exporter-searchable-dropdown.json new file mode 100644 index 0000000000..51f2dadcd1 --- /dev/null +++ b/bundles/af-core/src/test/resources/form/dropdown/exporter-searchable-dropdown.json @@ -0,0 +1,32 @@ +{ + "id": "dropdown-6bc8619e69", + "fieldType": "drop-down", + "name": "abc", + "type": "string", + "minLength": 2, + "maxLength": 10, + "enforceEnum": false, + "enumNames": [ + "m", + "f", + "o" + ], + "pattern": "[a-z]+", + "label": { + "value": "def" + }, + "properties": { + "fd:path": "/content/searchable-dropdown" + }, + "events": { + "custom:setProperty": [ + "$event.payload" + ] + }, + "enum": [ + "a", + "b", + "c" + ], + ":type": "core/fd/components/form/dropdown/v1/dropdown" +} diff --git a/bundles/af-core/src/test/resources/form/dropdown/test-content.json b/bundles/af-core/src/test/resources/form/dropdown/test-content.json index c5b040b64a..c99f92bf55 100644 --- a/bundles/af-core/src/test/resources/form/dropdown/test-content.json +++ b/bundles/af-core/src/test/resources/form/dropdown/test-content.json @@ -51,7 +51,24 @@ "enumNames" : ["m", "f", "o"], "minItems" : 1, "maxItems" : 2, - "required": true + "required": true, + "minLength" : 2, + "maxLength" : 10, + "pattern" : "[a-z]+" + }, + "searchable-dropdown" : { + "jcr:primaryType": "nt:unstructured", + "sling:resourceType" : "core/fd/components/form/dropdown/v1/dropdown", + "name" : "abc", + "jcr:title" : "def", + "fieldType": "drop-down", + "type" : "string", + "enforceEnum" : false, + "enum" : ["a", "b", "c"], + "enumNames" : ["m", "f", "o"], + "minLength" : 2, + "maxLength" : 10, + "pattern" : "[a-z]+" }, "dropdown2" : { "jcr:primaryType": "nt:unstructured", diff --git a/bundles/af-core/src/test/resources/schema/0.15.2/adaptive-form.schema.json b/bundles/af-core/src/test/resources/schema/0.15.2/adaptive-form.schema.json index ed6cb8dd94..12501b1205 100644 --- a/bundles/af-core/src/test/resources/schema/0.15.2/adaptive-form.schema.json +++ b/bundles/af-core/src/test/resources/schema/0.15.2/adaptive-form.schema.json @@ -578,7 +578,6 @@ "properties": { "fieldType": { "enum": [ - "drop-down", "checkbox-group" ] } @@ -592,7 +591,6 @@ "properties": { "fieldType": { "enum": [ - "drop-down", "checkbox-group" ] }, @@ -668,7 +666,109 @@ "enum": "Options are missing from selection. Add an empty enum array" }, "properties": { - "fieldType": "${0/fieldType} is not a valid SelectionField field type. It should be either drop-down or checkbox-group" + "fieldType": "${0/fieldType} is not a valid SelectionField field type. It should be checkbox-group" + } + } + } + }, + "DropDownField": { + "if": { + "type": "object", + "properties": { + "fieldType": { + "enum": [ + "drop-down" + ] + } + }, + "required": [ + "fieldType" + ] + }, + "then": { + "type": "object", + "properties": { + "fieldType": { + "enum": [ + "drop-down" + ] + }, + "type": { + "enum": [ + "string", + "number", + "boolean", + "string[]", + "number[]", + "boolean[]" + ] + }, + "rules": { + "type": "object", + "propertyNames": { + "enum": [ + "description", + "enabled", + "enum", + "enumNames", + "label", + "properties", + "readOnly", + "required", + "value", + "visible" + ] + } + } + }, + "propertyNames": { + "enum": [ + ":type", + "appliedCssClassNames", + "constraintMessages", + "dataLayer", + "dataRef", + "default", + "description", + "enabled", + "enforceEnum", + "enum", + "enumNames", + "events", + "fieldType", + "id", + "label", + "maxItems", + "maxLength", + "minItems", + "minLength", + "name", + "pattern", + "placeholder", + "properties", + "readOnly", + "required", + "rules", + "screenReaderText", + "tooltip", + "type", + "uniqueItems", + "validationExpression", + "visible" + ], + "errorMessage": { + "enum": "${0} property is not allowed in DropDownField" + } + }, + "required": [ + "enum" + ], + "errorMessage": { + "required": { + "enum": "Options are missing from selection. Add an empty enum array" + }, + "properties": { + "fieldType": "${0/fieldType} is not a valid DropDownField field type. It should be drop-down" } } } @@ -1696,6 +1796,9 @@ { "$ref": "#/$defs/SelectionField" }, + { + "$ref": "#/$defs/DropDownField" + }, { "$ref": "#/$defs/RadioGroupField" },