diff --git a/.github/workflows/exporter-validate-pr.yml b/.github/workflows/exporter-validate-pr.yml index 0907dd8f64..87c035df5e 100644 --- a/.github/workflows/exporter-validate-pr.yml +++ b/.github/workflows/exporter-validate-pr.yml @@ -48,10 +48,10 @@ jobs: exit 1 fi - # Check for changed values - for key in $base_keys; do - base_value=$(echo "$base_file" | jq -r --arg key "$key" '.[$key]') - head_value=$(echo "$head_file" | jq -r --arg key "$key" '.[$key]') + # Check for changed values + for key in $base_keys; do + base_value=$(echo "$base_file" | jq -r ".$key") + head_value=$(echo "$head_file" | jq -r ".$key") if [ "$base_value" != "$head_value" ]; then echo "Backward incompatibility change detected in $file. The value of key '$key' was changed from '$base_value' to '$head_value'." @@ -64,4 +64,4 @@ jobs: done echo "All exporter JSON files have only additions. No backward incompatibility changes detected." - shell: bash + shell: bash \ No newline at end of file diff --git a/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/form/FormConstants.java b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/form/FormConstants.java index 2944ee3f6c..72689244d3 100644 --- a/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/form/FormConstants.java +++ b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/form/FormConstants.java @@ -142,4 +142,6 @@ private FormConstants() { /* The resource type for the pre-selected the linked panel */ public final static String RT_FD_FORM_REVIEW_DATASOURCE_V1 = RT_FD_FORM_PREFIX + "review/v1/datasource"; + + public static final String RT_FD_FORM_PASSWORD_V1 = RT_FD_FORM_PREFIX + "password/v1/password"; } diff --git a/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/models/v1/form/PasswordImpl.java b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/models/v1/form/PasswordImpl.java new file mode 100644 index 0000000000..cca55ae28c --- /dev/null +++ b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/models/v1/form/PasswordImpl.java @@ -0,0 +1,90 @@ +/**~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ~ Copyright 2024 Adobe + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +package com.adobe.cq.forms.core.components.internal.models.v1.form; + +import javax.annotation.Nullable; +import javax.annotation.PostConstruct; + +import org.apache.sling.api.SlingHttpServletRequest; +import org.apache.sling.api.resource.Resource; +import org.apache.sling.models.annotations.Exporter; +import org.apache.sling.models.annotations.Model; +import org.apache.sling.models.annotations.injectorspecific.InjectionStrategy; +import org.apache.sling.models.annotations.injectorspecific.ValueMapValue; + +import com.adobe.cq.export.json.ComponentExporter; +import com.adobe.cq.export.json.ExporterConstants; +import com.adobe.cq.forms.core.components.internal.form.FormConstants; +import com.adobe.cq.forms.core.components.models.form.FieldType; +import com.adobe.cq.forms.core.components.models.form.Password; +import com.adobe.cq.forms.core.components.util.AbstractFieldImpl; +import com.adobe.cq.forms.core.components.util.ComponentUtils; + +@Model( + adaptables = { SlingHttpServletRequest.class, Resource.class }, + adapters = { Password.class, ComponentExporter.class }, + resourceType = { FormConstants.RT_FD_FORM_PASSWORD_V1 }) +@Exporter( + name = ExporterConstants.SLING_MODEL_EXPORTER_NAME, + extensions = ExporterConstants.SLING_MODEL_EXTENSION) +public class PasswordImpl extends AbstractFieldImpl implements Password { + + private Object exclusiveMinimumVaue; + private Object exclusiveMaximumValue; + + @ValueMapValue(injectionStrategy = InjectionStrategy.OPTIONAL) + @Nullable + private String pattern; + + @Override + public String getFieldType() { + return getFieldType(FieldType.PASSWORD); + } + + @Override + public Integer getMinLength() { + return minLength; + } + + @Override + public Integer getMaxLength() { + return maxLength; + } + + @Override + public String getFormat() { + return displayFormat; + } + + @Override + public String getPattern() { + return pattern; + } + + @PostConstruct + private void initPassword() { + exclusiveMaximumValue = ComponentUtils.getExclusiveValue(exclusiveMaximum, maximum, null); + exclusiveMinimumVaue = ComponentUtils.getExclusiveValue(exclusiveMinimum, minimum, null); + // in json either, exclusiveMaximum or maximum should be present + if (exclusiveMaximumValue != null) { + maximum = null; + } + if (exclusiveMinimumVaue != null) { + minimum = null; + } + } +} diff --git a/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/models/form/Password.java b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/models/form/Password.java new file mode 100644 index 0000000000..f97bf94926 --- /dev/null +++ b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/models/form/Password.java @@ -0,0 +1,29 @@ +/**~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ~ Copyright 2024 Adobe + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +package com.adobe.cq.forms.core.components.models.form; + +import org.osgi.annotation.versioning.ConsumerType; + +/** + * Interface for {@code Password} Sling Model used for the {@code /apps/core/fd/components/form/password/v1/password} component. + * + * @since com.adobe.cq.forms.core.components.models.form 5.11.0 + */ +@ConsumerType +public interface Password extends Field, StringConstraint { + +} diff --git a/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v1/form/PasswordImplTest.java b/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v1/form/PasswordImplTest.java new file mode 100644 index 0000000000..c4b669e726 --- /dev/null +++ b/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v1/form/PasswordImplTest.java @@ -0,0 +1,176 @@ +/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ~ Copyright 2024 Adobe + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +package com.adobe.cq.forms.core.components.internal.models.v1.form; + +import org.apache.commons.lang3.reflect.FieldUtils; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mockito; + +import com.adobe.cq.forms.core.Utils; +import com.adobe.cq.forms.core.components.datalayer.FormComponentData; +import com.adobe.cq.forms.core.components.internal.form.FormConstants; +import com.adobe.cq.forms.core.components.models.form.FieldType; +import com.adobe.cq.forms.core.components.models.form.Password; +import com.adobe.cq.forms.core.context.FormsCoreComponentTestContext; +import io.wcm.testing.mock.aem.junit5.AemContext; +import io.wcm.testing.mock.aem.junit5.AemContextExtension; + +import static org.junit.Assert.assertEquals; + +@ExtendWith(AemContextExtension.class) +public class PasswordImplTest { + + private static final String BASE = "/form/password"; + private static final String CONTENT_ROOT = "/content"; + private static final String PATH_PASSWORD_DATALAYER = CONTENT_ROOT + "/password-datalayer"; + private static final String PATH_PASSWORD_CUSTOMIZED = CONTENT_ROOT + "/password-customized"; + + private static final String PATH_PASSWORD = CONTENT_ROOT + "/password"; + + private static final String PATH_PASSWORD_PATTERN = CONTENT_ROOT + "/password-pattern"; + + private final AemContext context = FormsCoreComponentTestContext.newAemContext(); + + @BeforeEach + void setUp() { + context.load().json(BASE + FormsCoreComponentTestContext.TEST_CONTENT_JSON, CONTENT_ROOT); + } + + @Test + void testExportedType() throws Exception { + Password password = Utils.getComponentUnderTest(PATH_PASSWORD_CUSTOMIZED, Password.class, context); + assertEquals(FormConstants.RT_FD_FORM_PASSWORD_V1, password.getExportedType()); + Password passwordMock = Mockito.mock(Password.class); + Mockito.when(passwordMock.getExportedType()).thenCallRealMethod(); + assertEquals("", passwordMock.getExportedType()); + } + + @Test + void testJSONExport() throws Exception { + Password password = Utils.getComponentUnderTest(PATH_PASSWORD_PATTERN, Password.class, context); + Utils.testJSONExport(password, Utils.getTestExporterJSONPath(BASE, PATH_PASSWORD)); + } + + @Test + void testFieldType() { + Password password = Utils.getComponentUnderTest(PATH_PASSWORD_CUSTOMIZED, Password.class, context); + assertEquals(FieldType.PASSWORD.getValue(), password.getFieldType()); + } + + @Test + void testGetLabel() { + Password password = Utils.getComponentUnderTest(PATH_PASSWORD_CUSTOMIZED, Password.class, context); + assertEquals("pwd", password.getLabel().getValue()); + } + + @Test + void testPlaceholder() { + Password password = Utils.getComponentUnderTest(PATH_PASSWORD_CUSTOMIZED, Password.class, context); + assertEquals("Enter valid password", password.getPlaceHolder()); + } + + @Test + void testGetName() { + Password password = Utils.getComponentUnderTest(PATH_PASSWORD_CUSTOMIZED, Password.class, context); + assertEquals("password", password.getName()); + + } + + @Test + void testDorProperties() { + Password password = Utils.getComponentUnderTest(PATH_PASSWORD_CUSTOMIZED, Password.class, context); + assertEquals(true, password.getDorProperties().get("dorExclusion")); + assertEquals("4", password.getDorProperties().get("dorColspan")); + assertEquals("Text1", password.getDorProperties().get("dorBindRef")); + + } + + @Test + void testGetDescription() { + Password password = Utils.getComponentUnderTest(PATH_PASSWORD_CUSTOMIZED, Password.class, context); + assertEquals("password field", password.getDescription()); + } + + @Test + void testGetRequired() { + Password password = Utils.getComponentUnderTest(PATH_PASSWORD_CUSTOMIZED, Password.class, context); + assertEquals(true, password.isRequired()); + } + + @Test + void testIsEnabled() { + Password password = Utils.getComponentUnderTest(PATH_PASSWORD_CUSTOMIZED, Password.class, context); + assertEquals(true, password.isEnabled()); + } + + @Test + void testIsEnabledForCustomized() { + Password password = Utils.getComponentUnderTest(PATH_PASSWORD_CUSTOMIZED, Password.class, context); + assertEquals(true, password.isEnabled()); + } + + @Test + void testIsReadOnly() { + Password password = Utils.getComponentUnderTest(PATH_PASSWORD_CUSTOMIZED, Password.class, context); + assertEquals(false, password.isReadOnly()); + } + + @Test + void testIsReadOnlyForCustomized() { + Password password = Utils.getComponentUnderTest(PATH_PASSWORD_CUSTOMIZED, Password.class, context); + assertEquals(false, password.isReadOnly()); + } + + @Test + void testMinLength() { + Password password = Utils.getComponentUnderTest(PATH_PASSWORD_CUSTOMIZED, Password.class, context); + assertEquals(5, password.getMinLength().intValue()); + } + + @Test + void testMaxLength() { + Password password = Utils.getComponentUnderTest(PATH_PASSWORD_CUSTOMIZED, Password.class, context); + assertEquals(10, password.getMaxLength().intValue()); + } + + @Test + void testGetDisplayFormat() throws Exception { + Password password = Utils.getComponentUnderTest(PATH_PASSWORD_CUSTOMIZED, Password.class, context); + assertEquals("password", password.getFormat()); + } + + @Test + void testGetPattern() throws Exception { + Password password = Utils.getComponentUnderTest(PATH_PASSWORD_PATTERN, Password.class, context); + assertEquals("^(?=.*\\d.*\\d)[A-Za-z\\d!@]+$", password.getPattern()); + } + + @Test + void testDataLayerProperties() throws IllegalAccessException { + Password password = Utils.getComponentUnderTest(PATH_PASSWORD_DATALAYER, Password.class, context); + FieldUtils.writeField(password, "dataLayerEnabled", true, true); + FormComponentData dataObject = (FormComponentData) password.getData(); + assert (dataObject != null); + assert (dataObject.getId()).equals("password-1c7bc238a6"); + assert (dataObject.getType()).equals("core/fd/components/form/password/v1/password"); + assert (dataObject.getTitle()).equals("Full Name"); + assert (dataObject.getFieldType()).equals("password"); + assert (dataObject.getDescription()).equals("Enter Full Name"); + } +} diff --git a/bundles/af-core/src/test/resources/form/password/exporter-password-customized.json b/bundles/af-core/src/test/resources/form/password/exporter-password-customized.json new file mode 100644 index 0000000000..ff9c746dfc --- /dev/null +++ b/bundles/af-core/src/test/resources/form/password/exporter-password-customized.json @@ -0,0 +1,43 @@ +{ + "id": "password-477c777f4e", + "fieldType": "password", + "name": "password1708629052628", + "visible": true, + "description": "

long desc

\r\n", + "tooltip": "

short desc

\r\n", + "type": "string", + "required": true, + "enabled": true, + "constraintMessages": { + "required": "Password is required", + "minLength": "min chars", + "maxLength": "max chars", + "pattern": "Invalid pwd" + }, + "readOnly": false, + "minLength": 5, + "maxLength": 10, + "exclusiveMinimum" : 8, + "exclusiveMaximum" : 16, + "pattern": "/^(?=.*\\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[a-zA-Z]).{8,}$/", + "label": { + "value": "Password", + "visible": true + }, + "properties": { + "afs:layout": { + "tooltipVisible": false + }, + "fd:dor": { + "dorExclusion": false + }, + "fd:path": "/content/password-customized" + }, + "events": { + "custom:setProperty": [ + "$event.payload" + ] + }, + "placeholder": "Enter valid password", + ":type": "core/fd/components/form/password/v1/password" +} \ No newline at end of file diff --git a/bundles/af-core/src/test/resources/form/password/exporter-password-datalayer.json b/bundles/af-core/src/test/resources/form/password/exporter-password-datalayer.json new file mode 100644 index 0000000000..6dafe57656 --- /dev/null +++ b/bundles/af-core/src/test/resources/form/password/exporter-password-datalayer.json @@ -0,0 +1,38 @@ +{ + "id": "password-1c7bc238a6", + "fieldType": "password", + "name": "password1732094911597", + "visible": true, + "description": "Enter Full Name", + "tooltip": "Full Name", + "type": "string", + "required": true, + "enabled": true, + "readOnly": false, + "label": { + "value": "Full Name" + }, + "events": { + "custom:setProperty": [ + "$event.payload" + ] + }, + "properties": { + "afs:layout": { + "tooltipVisible": false + }, + "fd:dor": { + "dorExclusion": false + }, + "fd:path": "/content/password-datalayer" + }, + ":type": "core/fd/components/form/password/v1/password", + "dataLayer": { + "password-1c7bc238a6": { + "dc:title": "Full Name", + "dc:description": "Enter Full Name", + "@type": "core/fd/components/form/password/v1/password", + "fieldType": "password" + } + } +} \ No newline at end of file diff --git a/bundles/af-core/src/test/resources/form/password/exporter-password.json b/bundles/af-core/src/test/resources/form/password/exporter-password.json new file mode 100644 index 0000000000..148a0b951e --- /dev/null +++ b/bundles/af-core/src/test/resources/form/password/exporter-password.json @@ -0,0 +1,26 @@ +{ + "id": "password-91417957c6", + "fieldType": "password", + "name": "password1732174214265", + "visible": true, + "type": "string", + "enabled": true, + "readOnly": false, + "pattern": "^(?=.*\\d.*\\d)[A-Za-z\\d!@]+$", + "label": { + "value": "" + }, + "events": { + "custom:setProperty": [ + "$event.payload" + ] + }, + "properties": { + ":type": "forms-components-examples/components/form/password", + "fd:dor": { + "dorExclusion": false + }, + "fd:path": "/content/password-pattern" + }, + ":type": "nt:unstructured" +} \ No newline at end of file diff --git a/bundles/af-core/src/test/resources/form/password/test-content.json b/bundles/af-core/src/test/resources/form/password/test-content.json new file mode 100644 index 0000000000..8b85cd1773 --- /dev/null +++ b/bundles/af-core/src/test/resources/form/password/test-content.json @@ -0,0 +1,146 @@ +{ + "password" : { + "jcr:primaryType": "nt:unstructured", + "sling:resourceType" : "core/fd/components/form/password/v1/password", + "name" : "abc", + "jcr:title" : "def", + "fieldType": "password" + }, + "password-customized" : { + "jcr:primaryType": "nt:unstructured", + "sling:resourceType" : "core/fd/components/form/password/v1/password", + "name" : "password", + "jcr:title" : "pwd", + "hideTitle" : false, + "dorExclusion": true, + "dorColspan": "4", + "dorBindRef": "Text1", + "description" : "password field", + "visible" : false, + "readOnly": false, + "enabled": true, + "required": true, + "minLength": 5, + "maxLength": 10, + "displayFormat" : "password", + "assistPriority" : "custom", + "dataRef" : "a.b", + "custom" : "Custom screen reader text", + "typeMessage" : "incorrect type", + "tooltip": "test-short-description", + "placeholder": "Enter valid password", + "pattern": "/^(?=.*\\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[a-zA-Z]).{8,}$/", + "default" : "password", + "fieldType": "password", + "label": { + "value": "Password", + "visible": true + } + }, + "password-pattern" : { + "id": "password-91417957c6", + "fieldType": "password", + "name": "password1732174214265", + "visible": true, + "type": "string", + "enabled": true, + "constraintMessages": { + "pattern": "Enter valid password" + }, + "readOnly": false, + "pattern": "^(?=.*\\d.*\\d)[A-Za-z\\d!@]+$", + "label": { + "visible": true, + "value": "Password" + }, + "events": { + "custom:setProperty": [ + "$event.payload" + ] + }, + "properties": { + "fd:dor": { + "dorExclusion": false + }, + "fd:path": "/content/forms/af/testcheckboaf2/jcr:content/guideContainer/password" + }, + ":type": "forms-components-examples/components/form/password" + }, + + "password-format" : { + "jcr:primaryType": "nt:unstructured", + "sling:resourceType" : "core/fd/components/form/password/v1/password", + "name" : "abc", + "jcr:title" : "def", + "hideTitle" : false, + "description" : "dummy", + "visible" : false, + "assistPriority" : "custom", + "dataRef" : "a.b", + "custom" : "Custom screen reader text", + "typeMessage" : "incorrect type", + "tooltip": "test-short-description", + "default" : "abc", + "fieldType": "password", + "readOnly": false, + "enabled": true, + "displayFormat" : "password" + }, + "password-datalayer" : { + "sling:resourceType" : "core/fd/components/form/password/v1/password", + "id": "password-1c7bc238a6", + "fieldType": "password", + "name": "Full Name", + "jcr:title" : "Full Name", + "visible": true, + "description": "Enter Full Name", + "tooltip": "Full Name", + "type": "string", + "required": true, + "enabled": true, + "readOnly": false, + "label": { + "value": "Full Name" + }, + "events": { + "custom:setProperty": [ + "$event.payload" + ] + }, + "properties": { + "afs:layout": { + "tooltipVisible": false + }, + "fd:dor": { + "dorExclusion": false + }, + "fd:path": "/content/forms/af/af2/jcr:content/guideContainer/password" + }, + ":type": "forms-components-examples/components/form/password" + }, + "password_unboundFormElement" : { + "jcr:primaryType": "nt:unstructured", + "sling:resourceType" : "core/fd/components/form/password/v1/password", + "name" : "abc", + "jcr:title" : "def", + "fieldType": "password", + "unboundFormElement": true, + "dataRef": "$.h" + }, + "password-blank-dataref" : { + "jcr:primaryType": "nt:unstructured", + "sling:resourceType" : "core/fd/components/form/password/v1/password", + "name" : "abc", + "jcr:title" : "def", + "fieldType": "password", + "dataRef": "" + }, + "password-blank-validationExpression" : { + "jcr:primaryType": "nt:unstructured", + "sling:resourceType" : "core/fd/components/form/password/v1/password", + "name" : "abc", + "jcr:title" : "def", + "fieldType": "password", + "validationExpression": "" + } +} diff --git a/examples/ui.apps/src/main/content/jcr_root/apps/forms-components-examples/clientlibs/forms-clientlib-site/styles/components/adaptive-form/password.less b/examples/ui.apps/src/main/content/jcr_root/apps/forms-components-examples/clientlibs/forms-clientlib-site/styles/components/adaptive-form/password.less new file mode 100644 index 0000000000..070e26a5da --- /dev/null +++ b/examples/ui.apps/src/main/content/jcr_root/apps/forms-components-examples/clientlibs/forms-clientlib-site/styles/components/adaptive-form/password.less @@ -0,0 +1,3 @@ +.cmp-adaptiveform-password { + .inputMixin(); + } \ No newline at end of file diff --git a/examples/ui.apps/src/main/content/jcr_root/apps/forms-components-examples/components/form/password/.content.xml b/examples/ui.apps/src/main/content/jcr_root/apps/forms-components-examples/components/form/password/.content.xml new file mode 100644 index 0000000000..acb1cd0182 --- /dev/null +++ b/examples/ui.apps/src/main/content/jcr_root/apps/forms-components-examples/components/form/password/.content.xml @@ -0,0 +1,7 @@ + + \ No newline at end of file diff --git a/examples/ui.apps/src/main/content/jcr_root/apps/forms-components-examples/components/form/password/_cq_template.xml b/examples/ui.apps/src/main/content/jcr_root/apps/forms-components-examples/components/form/password/_cq_template.xml new file mode 100644 index 0000000000..359c182ba3 --- /dev/null +++ b/examples/ui.apps/src/main/content/jcr_root/apps/forms-components-examples/components/form/password/_cq_template.xml @@ -0,0 +1,4 @@ + + \ No newline at end of file diff --git a/examples/ui.content/src/main/content/jcr_root/content/core-components-examples/library/adaptive-form/password/.content.xml b/examples/ui.content/src/main/content/jcr_root/content/core-components-examples/library/adaptive-form/password/.content.xml new file mode 100644 index 0000000000..e97222c1be --- /dev/null +++ b/examples/ui.content/src/main/content/jcr_root/content/core-components-examples/library/adaptive-form/password/.content.xml @@ -0,0 +1,151 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/samples/password/.content.xml b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/samples/password/.content.xml new file mode 100644 index 0000000000..d738d26849 --- /dev/null +++ b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/samples/password/.content.xml @@ -0,0 +1,6 @@ + + diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/samples/password/basic/.content.xml b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/samples/password/basic/.content.xml new file mode 100644 index 0000000000..ef1113d23c --- /dev/null +++ b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/samples/password/basic/.content.xml @@ -0,0 +1,23 @@ + + + + + + diff --git a/it/content/src/main/content/jcr_root/content/forms/af/core-components-it/samples/password/.content.xml b/it/content/src/main/content/jcr_root/content/forms/af/core-components-it/samples/password/.content.xml new file mode 100644 index 0000000000..a0ac99e384 --- /dev/null +++ b/it/content/src/main/content/jcr_root/content/forms/af/core-components-it/samples/password/.content.xml @@ -0,0 +1,3 @@ + + diff --git a/it/content/src/main/content/jcr_root/content/forms/af/core-components-it/samples/password/basic/.content.xml b/it/content/src/main/content/jcr_root/content/forms/af/core-components-it/samples/password/basic/.content.xml new file mode 100644 index 0000000000..292ec6da0d --- /dev/null +++ b/it/content/src/main/content/jcr_root/content/forms/af/core-components-it/samples/password/basic/.content.xml @@ -0,0 +1,251 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ui.af.apps/pom.xml b/ui.af.apps/pom.xml index 38362a2211..209df589d0 100644 --- a/ui.af.apps/pom.xml +++ b/ui.af.apps/pom.xml @@ -324,6 +324,7 @@ + diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/af-clientlibs/core-forms-components-runtime-all/.content.xml b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/af-clientlibs/core-forms-components-runtime-all/.content.xml index 0ef50cf14f..4c45390ac7 100644 --- a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/af-clientlibs/core-forms-components-runtime-all/.content.xml +++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/af-clientlibs/core-forms-components-runtime-all/.content.xml @@ -5,4 +5,4 @@ cssProcessor="[default:none,min:none]" jsProcessor="[default:none,min:none]" categories="[core.forms.components.runtime.all]" - embed="[core.forms.components.runtime.base,core.forms.components.container.v2.runtime,core.forms.components.datePicker.v1.runtime,core.forms.components.textinput.v1.runtime,core.forms.components.numberinput.v1.runtime,core.forms.components.panelcontainer.v1.runtime,core.forms.components.radiobutton.v1.runtime,core.forms.components.text.v1.runtime,core.forms.components.checkboxgroup.v1.runtime,core.forms.components.button.v1.runtime,core.forms.components.image.v1.runtime,core.forms.components.dropdown.v1.runtime,core.forms.components.fileinput.v3.runtime,core.forms.components.accordion.v1.runtime,core.forms.components.tabs.v1.runtime,core.forms.components.wizard.v1.runtime,core.forms.components.verticaltabs.v1.runtime,core.forms.components.recaptcha.v1.runtime,core.forms.components.checkbox.v1.runtime,core.forms.components.fragment.v1.runtime,core.forms.components.switch.v1.runtime,core.forms.components.termsandconditions.v1.runtime, core.forms.components.hcaptcha.v1.runtime,core.forms.components.review.v1.runtime, core.forms.components.turnstile.v1.runtime]"/> + embed="[core.forms.components.runtime.base,core.forms.components.container.v2.runtime,core.forms.components.datePicker.v1.runtime,core.forms.components.textinput.v1.runtime,core.forms.components.numberinput.v1.runtime,core.forms.components.panelcontainer.v1.runtime,core.forms.components.radiobutton.v1.runtime,core.forms.components.text.v1.runtime,core.forms.components.checkboxgroup.v1.runtime,core.forms.components.button.v1.runtime,core.forms.components.image.v1.runtime,core.forms.components.dropdown.v1.runtime,core.forms.components.fileinput.v3.runtime,core.forms.components.accordion.v1.runtime,core.forms.components.tabs.v1.runtime,core.forms.components.wizard.v1.runtime,core.forms.components.verticaltabs.v1.runtime,core.forms.components.recaptcha.v1.runtime,core.forms.components.checkbox.v1.runtime,core.forms.components.fragment.v1.runtime,core.forms.components.switch.v1.runtime,core.forms.components.termsandconditions.v1.runtime, core.forms.components.hcaptcha.v1.runtime,core.forms.components.review.v1.runtime, core.forms.components.turnstile.v1.runtime, core.forms.components.password.v1.runtime]"/> diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/container/v2/container/clientlibs/editorhook/js/replacehook.js b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/container/v2/container/clientlibs/editorhook/js/replacehook.js index 3e3ae98578..aa31facd66 100644 --- a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/container/v2/container/clientlibs/editorhook/js/replacehook.js +++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/container/v2/container/clientlibs/editorhook/js/replacehook.js @@ -40,7 +40,8 @@ 'plain-text': fieldTypes.NON_INPUT, 'title': fieldTypes.NON_INPUT, 'image': fieldTypes.NON_INPUT, - 'panel': fieldTypes.CONTAINER + 'panel': fieldTypes.CONTAINER, + 'password' : fieldTypes.TEXT } const preservedProperties = ['id', 'description', 'enabled', 'jcr:created', 'jcr:title', 'name', diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/password/.content.xml b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/password/.content.xml new file mode 100644 index 0000000000..491392d539 --- /dev/null +++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/password/.content.xml @@ -0,0 +1,3 @@ + + diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/password/v1/.content.xml b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/password/v1/.content.xml new file mode 100644 index 0000000000..12d179732b --- /dev/null +++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/password/v1/.content.xml @@ -0,0 +1,4 @@ + + + \ No newline at end of file diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/password/v1/password/.content.xml b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/password/v1/password/.content.xml new file mode 100644 index 0000000000..a2530d42b7 --- /dev/null +++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/password/v1/password/.content.xml @@ -0,0 +1,8 @@ + + \ No newline at end of file diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/password/v1/password/README.md b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/password/v1/password/README.md new file mode 100644 index 0000000000..499db3745d --- /dev/null +++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/password/v1/password/README.md @@ -0,0 +1,98 @@ + +Adaptive Form Password (v1) +==== +Adaptive Form Password field component written in HTL. + +## Features + +* Provides the following type of input: + * password + +* Custom constraint messages for the above types +* Styles +* Allows replacing this component with other component (as mentioned below). + +### Use Object +The Form Password component uses the `com.adobe.cq.forms.core.components.models.form.Password` Sling Model for its Use-object. + +### Edit Dialog Properties +The following properties are written to JCR for this Form Password component and are expected to be available as `Resource` properties: + +1. `./jcr:title` - defines the label to use for this field +2. `./hideTitle` - if set to `true`, the label of this field will be hidden +3. `./name` - defines the name of the field, which will be submitted with the form data +4. `./description` - defines a help message that can be rendered in the field as a hint for the user +5. `./dataRef` - defines bind reference with model +6. `./placeholder` - defines place holder for the field +7. `./unboundFormElement` - defines if the field is unbound +8. `./required` - if set to `true`, this field will be marked as required, not allowing the form to be submitted until the field has a value +9. `./requiredMessage` - defines the message displayed as tooltip when submitting the form if the value is left empty +10. `./readOnly` - if set to `true`, the filed will be read only +11. `./maxLength` - define maximum characters permitted +12. `./minLength` - define minimum characters permitted +13. `./maxLengthMessage` - define maximum characters of error message permitted +14. `./pattern` - define regular expression permitted +15. `./minLengthMessage` - define minimum characters of error message permitted +16. `./validatePictureClauseMessage` - define error message if wrong pattern is entered. + +## Client Libraries +The component provides a `core.forms.components.password.v1.runtime` client library category that contains the Javascript runtime for the component. +It should be added to a relevant site client library using the `embed` property. + +It also provides a `core.forms.components.password.v1.editor` editor client library category that includes +JavaScript handling for dialog interaction. It is already included by its edit dialog. + +## BEM Description +``` +BLOCK cmp-adaptiveform-password + ELEMENT cmp-adaptiveform-password__label + ELEMENT cmp-adaptiveform-password__label-container + ELEMENT cmp-adaptiveform-password__widget + ELEMENT cmp-adaptiveform-password__questionmark + ELEMENT cmp-adaptiveform-password__shortdescription + ELEMENT cmp-adaptiveform-password__longdescription + ELEMENT cmp-adaptiveform-password__errormessage + ELEMENT cmp-adaptiveform-password__eyeicon + ELEMENT cmp-adaptiveform-password__input-wrapper +``` + +### Note +By placing the class names `cmp-adaptiveform-password__label` and `cmp-adaptiveform-password__questionmark` within the `cmp-adaptiveform-password__label-container` class, you create a logical grouping of the label and question mark elements. This approach simplifies the process of maintaining a consistent styling for both elements. + +## Replace feature: +We support replace feature that allows replacing Password component to any of the below components: + +* Email Input +* Number Input +* Telephone Input +* Text Box +* Date Picker + +## JavaScript Data Attribute Bindings + +The following attributes must be added for the initialization of the password component in the form view: + 1. `data-cmp-is="adaptiveFormPassword"` + 2. `data-cmp-adaptiveformcontainer-path="${formstructparser.formContainerPath}"` + + +The following are optional attributes that can be added to the component in the form view: +1. `data-cmp-valid` having a boolean value to indicate whether the field is currently valid or not +2. `data-cmp-required` having a boolean value to indicate whether the field is currently required or not +3. `data-cmp-readonly` having a boolean value to indicate whether the field is currently readonly or not +4. `data-cmp-active` having a boolean value to indicate whether the field is currently active or not +5. `data-cmp-visible` having a boolean value to indicate whether the field is currently visible or not +6. `data-cmp-enabled` having a boolean value to indicate whether the field is currently enabled or not \ No newline at end of file diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/password/v1/password/_cq_design_dialog/.content.xml b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/password/v1/password/_cq_design_dialog/.content.xml new file mode 100644 index 0000000000..630ec0ff2e --- /dev/null +++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/password/v1/password/_cq_design_dialog/.content.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/password/v1/password/_cq_dialog/.content.xml b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/password/v1/password/_cq_dialog/.content.xml new file mode 100644 index 0000000000..409d2d1d93 --- /dev/null +++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/password/v1/password/_cq_dialog/.content.xml @@ -0,0 +1,131 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/password/v1/password/_cq_template.xml b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/password/v1/password/_cq_template.xml new file mode 100644 index 0000000000..f5fbc5ecf6 --- /dev/null +++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/password/v1/password/_cq_template.xml @@ -0,0 +1,4 @@ + + diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/password/v1/password/clientlibs/.content.xml b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/password/v1/password/clientlibs/.content.xml new file mode 100644 index 0000000000..491392d539 --- /dev/null +++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/password/v1/password/clientlibs/.content.xml @@ -0,0 +1,3 @@ + + diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/password/v1/password/clientlibs/editor/.content.xml b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/password/v1/password/clientlibs/editor/.content.xml new file mode 100644 index 0000000000..9db59eb5c7 --- /dev/null +++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/password/v1/password/clientlibs/editor/.content.xml @@ -0,0 +1,5 @@ + + diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/password/v1/password/clientlibs/editor/js.txt b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/password/v1/password/clientlibs/editor/js.txt new file mode 100644 index 0000000000..8a845d07a4 --- /dev/null +++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/password/v1/password/clientlibs/editor/js.txt @@ -0,0 +1,18 @@ +############################################################################### +# Copyright 2024 Adobe +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +############################################################################### + +#base=js +editDialog.js \ No newline at end of file diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/password/v1/password/clientlibs/editor/js/editDialog.js b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/password/v1/password/clientlibs/editor/js/editDialog.js new file mode 100644 index 0000000000..66e54543a5 --- /dev/null +++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/password/v1/password/clientlibs/editor/js/editDialog.js @@ -0,0 +1,39 @@ +/******************************************************************************* + * Copyright 2024 Adobe + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ******************************************************************************/ +(function($) { + "use strict"; + + var EDIT_DIALOG = ".cmp-adaptiveform-password__editdialog", + PASSWORD_MAXLENGTH = EDIT_DIALOG + " .cmp-adaptiveform-password__maxlength", + PASSWORD_MINLENGTH = EDIT_DIALOG + " .cmp-adaptiveform-password__minlength", + BASE_PLACEHOLDER = EDIT_DIALOG + " .cmp-adaptiveform-base__placeholder", + PASSWORD_VALUE = EDIT_DIALOG + " .cmp-adaptiveform-password__value", + PASSWORD_RICHTEXTVALUE = EDIT_DIALOG + " .cmp-adaptiveform-password__richtextvalue", + PASSWORD_VALIDATIONPATTERN = EDIT_DIALOG + " .cmp-adaptiveform-password__validationpattern", + PASSWORD_VALIDATIONFORMAT = EDIT_DIALOG + " .cmp-adaptiveform-password__validationformat", + Utils = window.CQ.FormsCoreComponents.Utils.v1; + + function handleValidationPatternDropDown(dialog) { + Utils.handlePatternDropDown(dialog,PASSWORD_VALIDATIONPATTERN,PASSWORD_VALIDATIONFORMAT); + } + + function handleValidationFormat(dialog){ + Utils.handlePatternFormat(dialog,PASSWORD_VALIDATIONPATTERN,PASSWORD_VALIDATIONFORMAT); + } + + Utils.initializeEditDialog(EDIT_DIALOG)(handleValidationPatternDropDown,handleValidationFormat); + +})(jQuery); diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/password/v1/password/clientlibs/site/.content.xml b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/password/v1/password/clientlibs/site/.content.xml new file mode 100644 index 0000000000..50764aff9f --- /dev/null +++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/password/v1/password/clientlibs/site/.content.xml @@ -0,0 +1,6 @@ + + diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/password/v1/password/clientlibs/site/css.txt b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/password/v1/password/clientlibs/site/css.txt new file mode 100644 index 0000000000..1e33d67d6c --- /dev/null +++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/password/v1/password/clientlibs/site/css.txt @@ -0,0 +1,18 @@ +############################################################################### +# Copyright 2024 Adobe +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +############################################################################### + +#base=css +passwordview.css \ No newline at end of file diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/password/v1/password/clientlibs/site/css/passwordview.css b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/password/v1/password/clientlibs/site/css/passwordview.css new file mode 100644 index 0000000000..5e3f0f91b1 --- /dev/null +++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/password/v1/password/clientlibs/site/css/passwordview.css @@ -0,0 +1,44 @@ +/******************************************************************************* + * Copyright 2024 Adobe + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ******************************************************************************/ + + .cmp-adaptiveform-password { + + } + + .cmp-adaptiveform-password__widget { + + } + .cmp-adaptiveform-password__widget{ + + } + + .cmp-adaptiveform-password__label { + + } + .cmp-adaptiveform-password__label-container{ + + } + + .cmp-adaptiveform-password__longdescription { + } + + .cmp-adaptiveform-password__shortdescription { + } + + .cmp-adaptiveform-password__questionmark { + + } + diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/password/v1/password/clientlibs/site/js.txt b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/password/v1/password/clientlibs/site/js.txt new file mode 100644 index 0000000000..12d384898e --- /dev/null +++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/password/v1/password/clientlibs/site/js.txt @@ -0,0 +1,18 @@ +############################################################################### +# Copyright 2024 Adobe +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +############################################################################### + +#base=js +passwordview.js diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/password/v1/password/clientlibs/site/js/passwordview.js b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/password/v1/password/clientlibs/site/js/passwordview.js new file mode 100644 index 0000000000..d4230f9ca6 --- /dev/null +++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/password/v1/password/clientlibs/site/js/passwordview.js @@ -0,0 +1,109 @@ +/******************************************************************************* + * Copyright 2024 Adobe + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ******************************************************************************/ +(function() { + + "use strict"; + class Password extends FormView.FormFieldBase { + + static NS = FormView.Constants.NS; + /** + * Each FormField has a data attribute class that is prefixed along with the global namespace to + * distinguish between them. If a component wants to put a data-attribute X, the attribute in HTML would be + * data-{NS}-{IS}-x="" + * @type {string} + */ + static IS = "adaptiveFormPassword"; + static bemBlock = 'cmp-adaptiveform-password' + static selectors = { + self: "[data-" + this.NS + '-is="' + this.IS + '"]', + widget: `.${Password.bemBlock}__widget`, + label: `.${Password.bemBlock}__label`, + description: `.${Password.bemBlock}__longdescription`, + qm: `.${Password.bemBlock}__questionmark`, + errorDiv: `.${Password.bemBlock}__errormessage`, + tooltipDiv: `.${Password.bemBlock}__shortdescription`, + eyeIcon: `.${Password.bemBlock}__eyeicon` + }; + + constructor(params) { + super(params); + } + + getWidget() { + return this.element.querySelector(Password.selectors.widget); + } + + getDescription() { + return this.element.querySelector(Password.selectors.description); + } + + getLabel() { + return this.element.querySelector(Password.selectors.label); + } + + getErrorDiv() { + return this.element.querySelector(Password.selectors.errorDiv); + } + + getTooltipDiv() { + return this.element.querySelector(Password.selectors.tooltipDiv); + } + + getQuestionMarkDiv() { + return this.element.querySelector(Password.selectors.qm); + } + + getEyeIcon(){ + return this.element.querySelector(Password.selectors.eyeIcon); + } + + setModel(model) { + super.setModel(model); + if (this.widget.value !== '') { + this._model.value = this.widget.value; + } + this.widget.addEventListener('blur', (e) => { + this._model.value = e.target.value; + this.setInactive(); + }); + this.widget.addEventListener('focus', (e) => { + this.setActive(); + }); + this.getEyeIcon().addEventListener('click', (e) => { + this.#togglePasswordType(); + }); + } + + #togglePasswordType() { + const widget = this.getWidget(); + if (widget.value) { + const widget = this.getWidget(); + if (widget.type === "password") { + widget.type = "text"; + this.getEyeIcon().classList.add('open'); + } else { + widget.type = "password"; + this.getEyeIcon().classList.remove('open'); + } + } + } + } + + FormView.Utils.setupField(({element, formContainer}) => { + return new Password({element, formContainer}) + }, Password.selectors.self); + +})(); diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/password/v1/password/password.html b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/password/v1/password/password.html new file mode 100644 index 0000000000..35776817fb --- /dev/null +++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/password/v1/password/password.html @@ -0,0 +1,57 @@ + + +
+
+
+
+
+
+ + +
+
+
+
+
+
diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/password/v1/password/password.js b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/password/v1/password/password.js new file mode 100644 index 0000000000..dc33ea8b81 --- /dev/null +++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/password/v1/password/password.js @@ -0,0 +1,34 @@ +/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ~ Copyright 2024 Adobe + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +use(function () { + + var clientlibsArr = ['core.forms.components.base.v1.editor']; + var labelPath = 'core/fd/components/af-commons/v1/fieldTemplates/label.html'; + var shortDescriptionPath = "core/fd/components/af-commons/v1/fieldTemplates/shortDescription.html"; + var longDescriptionPath = "core/fd/components/af-commons/v1/fieldTemplates/longDescription.html"; + var questionMarkPath = "core/fd/components/af-commons/v1/fieldTemplates/questionMark.html" + var errorMessagePath = "core/fd/components/af-commons/v1/fieldTemplates/errorMessage.html"; + + return { + labelPath: labelPath, + shortDescriptionPath: shortDescriptionPath, + longDescriptionPath: longDescriptionPath, + questionMarkPath: questionMarkPath, + errorMessagePath: errorMessagePath, + clientlibs: clientlibsArr + } +}); \ No newline at end of file diff --git a/ui.frontend/src/constants.js b/ui.frontend/src/constants.js index 04d6f8078e..a74dc91e20 100644 --- a/ui.frontend/src/constants.js +++ b/ui.frontend/src/constants.js @@ -374,6 +374,18 @@ export const Constants = { * @memberof module:FormView~Constants * @namespace FIELD_TYPE */ - FIELD_TYPE: FIELD_TYPE + FIELD_TYPE: FIELD_TYPE, + + /** + * Html input type text + * @type {string} + */ + HTML_INPUT_TYPE_TEXT : "text", + + /** + * Html input type password + * @type {string} + */ + HTML_INPUT_TYPE_PASSWORD : "password" }; diff --git a/ui.tests/test-module/libs/commons/formsConstants.js b/ui.tests/test-module/libs/commons/formsConstants.js index c511260f7a..cf2e1992fa 100644 --- a/ui.tests/test-module/libs/commons/formsConstants.js +++ b/ui.tests/test-module/libs/commons/formsConstants.js @@ -50,6 +50,7 @@ var formsConstants = { "termsandconditions": "/apps/forms-components-examples/components/form/termsandconditions", "submitButton": "/apps/forms-components-examples/components/form/actions/submit", "review": "/apps/forms-components-examples/components/form/review", + "password": "/apps/forms-components-examples/components/form/password" } }, resourceType : { diff --git a/ui.tests/test-module/specs/password/password.authoring.cy.js b/ui.tests/test-module/specs/password/password.authoring.cy.js new file mode 100644 index 0000000000..a6e4552e99 --- /dev/null +++ b/ui.tests/test-module/specs/password/password.authoring.cy.js @@ -0,0 +1,177 @@ +/* + * Copyright 2024 Adobe Systems Incorporated + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const sitesSelectors = require('../../libs/commons/sitesSelectors'), + afConstants = require('../../libs/commons/formsConstants'); + +/** + * Testing Password with Sites Editor + */ +describe('Page - Authoring', function () { + // we can use these values to log in + + const dropPasswordInContainer = function () { + const dataPath = "/content/forms/af/core-components-it/blank/jcr:content/guideContainer/*", + responsiveGridDropZoneSelector = sitesSelectors.overlays.overlay.component + "[data-path='" + dataPath + "']"; + cy.selectLayer("Edit"); + cy.insertComponent(responsiveGridDropZoneSelector, "Adaptive Form Password Box", afConstants.components.forms.resourceType.password); + cy.get('body').click(0, 0); + } + + const dropPasswordInSites = function () { + const dataPath = "/content/core-components-examples/library/adaptive-form/password/jcr:content/root/responsivegrid/demo/component/guideContainer/*", + responsiveGridDropZoneSelector = sitesSelectors.overlays.overlay.component + "[data-path='" + dataPath + "']"; + cy.selectLayer("Edit"); + cy.insertComponent(responsiveGridDropZoneSelector, "Adaptive Form Password Box", afConstants.components.forms.resourceType.password); + cy.get('body').click(0, 0); + } + + const testPasswordBehaviour = function (passwordEditPathSelector, passwordDrop, isSites) { + if (isSites) { + dropPasswordInSites(); + } else { + dropPasswordInContainer(); + } + cy.openEditableToolbar(sitesSelectors.overlays.overlay.component + passwordEditPathSelector); + cy.invokeEditableAction("[data-action='CONFIGURE']"); // this line is causing frame busting which is causing cypress to fail + + // Checking some dynamic behaviours + cy.get(".cmp-adaptiveform-password__maxlength").invoke('css', 'display').should('equal', 'block'); + cy.get(".cmp-adaptiveform-password__minlength").invoke('css', 'display').should('equal', 'block'); + cy.get(".cmp-adaptiveform-base__placeholder").parent('div').invoke('css', 'display').should('equal', 'block'); + cy.get('.cmp-adaptiveform-password__editdialog').contains('Validation').click({force: true}).then(() => { + cy.get("[name='./pattern']").should('have.length', 1); + cy.get("[name='./validatePictureClauseMessage']").should('have.length', 1); + cy.get('.cq-dialog-cancel').click(); + cy.deleteComponentByPath(passwordDrop); + }) + } + + const testCopyPasteComponent = function (passwordEditPathSelector, passwordEditPathSelectorCopy, passwordDrop) { + dropPasswordInContainer(); + cy.openEditableToolbar(sitesSelectors.overlays.overlay.component + passwordEditPathSelector); + cy.invokeEditableAction("[data-action='CONFIGURE']"); // this line is causing frame busting which is causing cypress to fail + // Check If Dialog Options Are Visible + cy.get(".cmp-adaptiveform-base__editdialogbasic input[name='./name']") + .should("exist") + .should("be.visible"); + cy.get(".cmp-adaptiveform-base__editdialogbasic input[name='./name']").focus().clear(); + cy.get(".cmp-adaptiveform-base__editdialogbasic input[name='./name']").invoke('val', 'Password'); + cy.get(".cmp-adaptiveform-base__editdialogbasic input[name='./name']").focus().type("{enter}"); + cy.openEditableToolbar(sitesSelectors.overlays.overlay.component + passwordEditPathSelector); + cy.invokeEditableAction("[data-action='COPY']"); + const dataPath = "/content/forms/af/core-components-it/blank/jcr:content/guideContainer/*", + responsiveGridDropZoneSelector = sitesSelectors.overlays.overlay.component + "[data-path='" + dataPath + "']"; + cy.openEditableToolbar(responsiveGridDropZoneSelector); + cy.invokeEditableAction("[data-action='PASTE']"); + cy.openEditableToolbar(sitesSelectors.overlays.overlay.component + passwordEditPathSelectorCopy); + cy.invokeEditableAction("[data-action='CONFIGURE']"); // this line is causing frame busting which is causing cypress to fail + // Check If Dialog Options Are Visible + cy.get(".cmp-adaptiveform-base__editdialogbasic [name='./name']") + .should("exist") + .should("have.value", "Password_copy_1"); + cy.get("coral-dialog.is-open coral-dialog-footer button[variant='default']").click(); + cy.deleteComponentByPath(passwordDrop); + cy.deleteComponentByPath(passwordDrop + "_copy"); + } + + const getRuleEditorIframe = () => { + // get the iframe > document > body + // and retry until the body element is not empty + return cy + .get('iframe#af-rule-editor') + .its('0.contentDocument.body').should('not.be.empty') + .then(cy.wrap) + } + + context('Open Forms Editor', function () { + const pagePath = "/content/forms/af/core-components-it/blank", + passwordEditPath = pagePath + afConstants.FORM_EDITOR_FORM_CONTAINER_SUFFIX + "/password", + passwordEditPathSelector = "[data-path='" + passwordEditPath + "']", + passwordEditPathSelectorCopy = "[data-path='" + passwordEditPath + "_copy']", + passwordDrop = pagePath + afConstants.FORM_EDITOR_FORM_CONTAINER_SUFFIX + "/" + afConstants.components.forms.resourceType.password.split("/").pop(); + beforeEach(function () { + // this is done since cypress session results in 403 sometimes + cy.openAuthoring(pagePath); + }); + + it('insert Password in form container', function () { + dropPasswordInContainer(); + cy.deleteComponentByPath(passwordDrop); + }); + + it('open edit dialog of Password', function () { + testPasswordBehaviour(passwordEditPathSelector, passwordDrop); + }) + + it('pasted component should have unique name', function () { + testCopyPasteComponent(passwordEditPathSelector, passwordEditPathSelectorCopy, passwordDrop); + }) + }) + + context('Open Sites Editor', function () { + const pagePath = "/content/core-components-examples/library/adaptive-form/password", + passwordEditPath = pagePath + afConstants.RESPONSIVE_GRID_DEMO_SUFFIX + "/guideContainer/password", + passwordInsideSitesContainerEditPath = pagePath + afConstants.RESPONSIVE_GRID_DEMO_SUFFIX + "/guideContainer/container/password_demo", + passwordEditPathSelector = "[data-path='" + passwordEditPath + "']", + passwordInsideSitesContainerEditPathSelector = "[data-path='" + passwordInsideSitesContainerEditPath + "']", + passwordDrop = pagePath + afConstants.RESPONSIVE_GRID_DEMO_SUFFIX + '/guideContainer/' + afConstants.components.forms.resourceType.password.split("/").pop(); + + beforeEach(function () { + cy.openAuthoring(pagePath); + }); + + it('insert aem forms Password', function () { + dropPasswordInSites(); + cy.deleteComponentByPath(passwordDrop); + }); + + it('open edit dialog of aem forms Password', function() { + testPasswordBehaviour(passwordEditPathSelector, passwordDrop, true); + }); + + it('Test z-index of Rule editor iframe', function () { + dropPasswordInSites(); + cy.openSidePanelTab("Content Tree"); + cy.openEditableToolbar(sitesSelectors.overlays.overlay.component + passwordEditPathSelector); + cy.invokeEditableAction("[data-action='editexpression']"); + cy.get("#af-rule-editor").should("be.visible"); + cy.get("#af-rule-editor") + .invoke("css", "z-index") + .should("equal", '10'); + getRuleEditorIframe().find("#objectNavigationTree").should("be.visible"); + getRuleEditorIframe().find("#create-rule-button").should("be.visible"); + cy.wait(1000) + getRuleEditorIframe().find(".exp-Close-Button").should("be.visible").click(); + cy.deleteComponentByPath(passwordDrop); + }); + + it('Test z-index of Rule editor iframe for components inside site container', function () { + cy.openSidePanelTab("Content Tree"); + cy.openEditableToolbar(sitesSelectors.overlays.overlay.component + passwordInsideSitesContainerEditPathSelector); + cy.invokeEditableAction("[data-action='editexpression']"); + cy.get("#af-rule-editor").should("be.visible"); + cy.get("#af-rule-editor") + .invoke("css", "z-index") + .should("equal", '10'); + getRuleEditorIframe().find("#objectNavigationTree").should("be.visible"); + getRuleEditorIframe().find("#objectNavigationTree " + passwordInsideSitesContainerEditPathSelector).should("be.visible"); + getRuleEditorIframe().find("#create-rule-button").should("be.visible"); + cy.wait(1000); + getRuleEditorIframe().find(".exp-Close-Button").should("be.visible").click(); + }); + }); +}); diff --git a/ui.tests/test-module/specs/password/password.runtime.cy.js b/ui.tests/test-module/specs/password/password.runtime.cy.js new file mode 100644 index 0000000000..7163223cf1 --- /dev/null +++ b/ui.tests/test-module/specs/password/password.runtime.cy.js @@ -0,0 +1,169 @@ +/******************************************************************************* + * Copyright 2024 Adobe + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ******************************************************************************/ +describe("Form Runtime with Password", () => { + + before(() => { + cy.attachConsoleErrorSpy(); + }); + + const pagePath = "content/forms/af/core-components-it/samples/password/basic.html" + const bemBlock = 'cmp-adaptiveform-password' + const IS = "adaptiveFormPassword" + const selectors = { + password : `[data-cmp-is="${IS}"]` + } + + let formContainer = null; + + /** + * initialization of form container before every test + * */ + beforeEach(() => { + cy.previewForm(pagePath).then(p => { + formContainer = p; + }); + }); + const checkHTML = (id, state) => { + const visible = state.visible; + const passVisibleCheck = `${visible === true ? "" : "not."}be.visible`; + const passDisabledAttributeCheck = `${state.enabled === false ? "" : "not."}have.attr`; + const value = state.value == null ? '' : state.value; + cy.get(`#${id}`) + .should(passVisibleCheck) + .invoke('attr', 'data-cmp-visible') + .should('eq', visible.toString()); + cy.get(`#${id}`) + .invoke('attr', 'data-cmp-enabled') + .should('eq', state.enabled.toString()); + return cy.get(`#${id}`).within((root) => { + cy.get('*').should(passVisibleCheck) + cy.get('input') + .should(passDisabledAttributeCheck, 'disabled'); + cy.get('input').should('have.value', value) + }); + } + + it(" should get model and view initialized properly ", () => { + expect(formContainer, "formcontainer is initialized").to.not.be.null; + expect(formContainer._model.items.length, "model and view elements match").to.equal(Object.keys(formContainer._fields).length); + Object.entries(formContainer._fields).forEach(([id, field]) => { + if(field.options.visible === "true") { + expect(field.getId()).to.equal(id) + expect(formContainer._model.getElement(id), `model and view are in sync`).to.equal(field.getModel()) + checkHTML(id, field.getModel().getState()) + } + }); + cy.expectNoConsoleErrors(); + }) + + it(" model's changes are reflected in the html ", () => { + const [id, passwordfieldView] = Object.entries(formContainer._fields)[0] + const model = formContainer._model.getElement(id) + model.value = "some other value" + checkHTML(model.id, model.getState()).then(() => { + model.visible = false + return checkHTML(model.id, model.getState()) + }).then(() => { + model.enabled = false + return checkHTML(model.id, model.getState()) + }) + cy.expectNoConsoleErrors(); + }); + + it(" html changes are reflected in model ", () => { + const [id, password1fieldView] = Object.entries(formContainer._fields)[0] + const model = formContainer._model.getElement(id) + const input = "value" + cy.get(`#${id}`).find("input").clear().type(input).blur().then(x => { + expect(model.getState().value).to.equal(input) + }) + cy.expectNoConsoleErrors(); + }); + + it("should toggle description and tooltip", () => { + cy.toggleDescriptionTooltip(bemBlock, 'tooltip_scenario_test'); + }) + + it("should show and hide other fields on a certain input", () => { + // Rule on passwordbox1: When passwordbox1 has input "Adobe@123" => Show passwordbox3 and Hide passwordbox2 + + const [passwordbox1, passwordbox1FieldView] = Object.entries(formContainer._fields)[0]; + const [passwordbox2, passwordbox2FieldView] = Object.entries(formContainer._fields)[1]; + const [passwordbox3, passwordbox3FieldView] = Object.entries(formContainer._fields)[2]; + const input = "Adobe@123"; + + cy.get(`#${passwordbox1}`).find("input").clear().type(input).blur().then(x => { + cy.get(`#${passwordbox3}`).should('be.visible') + cy.get(`#${passwordbox2}`).should('not.be.visible') + }) + cy.expectNoConsoleErrors(); + }) + + it("should enable and disable other textfields on a certain string input", () => { + // Rule on passwordbox1: When passwordbox1 has input "Aem@123" => Enable passwordbox2 and Disable passwordbox4 + + const [passwordbox1, passwordbox1FieldView] = Object.entries(formContainer._fields)[0]; + const [passwordbox2, passwordbox2FieldView] = Object.entries(formContainer._fields)[1]; + const [passwordbox4, passwordbox4FieldView] = Object.entries(formContainer._fields)[3]; + const input = "Aem@123"; + + cy.get(`#${passwordbox1}`).find("input").clear().type(input).blur().then(x => { + cy.get(`#${passwordbox2}`).find("input").should('be.enabled') + cy.get(`#${passwordbox4}`).find("input").should('not.be.enabled') + }) + cy.expectNoConsoleErrors(); + }) + + it("should set and clear value based on rules", () => { + // Rule on passwordbox5: When input of passwordbox5 is "Aemforms@123", set value of passwordbox1 to "new password" and clear value of passwordbox4 + + const [passwordbox1, passwordbox1FieldView] = Object.entries(formContainer._fields)[0]; + const [passwordbox4, passwordbox4FieldView] = Object.entries(formContainer._fields)[3]; + const [passwordbox5, passwordbox5FieldView] = Object.entries(formContainer._fields)[4]; + + const input = "Aemforms@123"; + // cy.get(`#${passwordbox4}`).find("input").clear().type("this must be cleared") + cy.get(`#${passwordbox5}`).find("input").clear().type(input).blur().then(x => { + cy.get(`#${passwordbox4}`).find("input").should('have.value',"") + cy.get(`#${passwordbox1}`).find("input").should('have.value', "new password") + }) + cy.expectNoConsoleErrors(); + }) + + it("should show required error message on blur when field is empty", () =>{ + const [passwordbox6, passwordbox6FieldView] = Object.entries(formContainer._fields)[5]; + cy.get(`#${passwordbox6}`).find("input").clear().focus().blur().then(x => { + cy.get(`#${passwordbox6}`).find(".cmp-adaptiveform-password__errormessage").should('have.text',"Please fill in this field.") + }) + }) + + it("decoration element should not have same class name", () => { + expect(formContainer, "formcontainer is initialized").to.not.be.null; + cy.wrap().then(() => { + const id = formContainer._model._children[0].id; + cy.get(`#${id}`).parent().should("not.have.class", "cmp-adaptiveform-password"); + }) + + }) + + it("should show required error message on entering wrong validation pattern of password", () =>{ + const [passwordbox11, passwordbox11FieldView] = Object.entries(formContainer._fields)[10]; + cy.get(`#${passwordbox11}`).find("input").clear().type(1).blur().then(x => { + cy.get(`#${passwordbox11}`).find(".cmp-adaptiveform-password__errormessage").should('have.text',"Please enter valid password") + }) + }) +}) +