From b839caa17ec483b81a8a29fc1cd93bc226fbbf09 Mon Sep 17 00:00:00 2001 From: Shivam Agarwal <47965724+im-shiv@users.noreply.github.com> Date: Tue, 23 Sep 2025 12:42:13 +0530 Subject: [PATCH 01/35] FORMS-21799 fixing aria-label to display plain text (#1715) * FORMS-21799 fixing aria-label to display plain text * FORMS-21799 correcting naming convention * FORMS-21799 adding tests * FORMS-21799 increasing test coverage * FORMS-21799 increasing test coverage --------- Co-authored-by: Shivam Agarwal --- .../models/form/OptionsConstraint.java | 10 ++++++ .../components/models/form/package-info.java | 2 +- .../util/AbstractOptionsFieldImpl.java | 25 ++++++++++++++ .../models/v1/form/CheckBoxGroupImplTest.java | 33 ++++++++++++++++--- .../models/v1/form/RadioButtonImplTest.java | 9 +++++ .../models/v1/form/SwitchImplTest.java | 9 +++++ .../form/checkboxgroup/test-content.json | 19 +++++++++++ .../form/radiobutton/test-content.json | 18 ++++++++++ .../resources/form/switch/test-content.json | 20 +++++++++++ .../v1/checkboxgroup/checkboxgroup.html | 1 - .../clientlibs/site/js/checkboxgroupview.js | 5 +-- .../v1/checkboxgroup/widget.html | 3 +- .../clientlibs/site/js/radiobuttonview.js | 6 ++-- .../v1/radiobutton/radiobutton.html | 3 +- .../form/switch/v1/switch/switch.html | 5 +-- ui.frontend/src/view/FormOptionFieldBase.js | 10 +++--- .../test-module/specs/dynamicOptions.cy.js | 18 +++++----- 17 files changed, 166 insertions(+), 30 deletions(-) diff --git a/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/models/form/OptionsConstraint.java b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/models/form/OptionsConstraint.java index 2b949352d7..58bced92bc 100644 --- a/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/models/form/OptionsConstraint.java +++ b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/models/form/OptionsConstraint.java @@ -17,6 +17,7 @@ import org.osgi.annotation.versioning.ProviderType; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; /** @@ -58,4 +59,13 @@ default boolean isEnforceEnum() { * @since com.adobe.cq.forms.core.components.models.form 0.0.1 */ String[] getEnumNames(); + + /** + * Returns screen reader friendly aria labels for the options. + * + * @return the list of aria labels for the options + * @since com.adobe.cq.forms.core.components.models.form 5.12.3 + */ + @JsonIgnore + String[] getOptionScreenReaderLabels(); } diff --git a/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/models/form/package-info.java b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/models/form/package-info.java index 05bc65c576..28bc21d5c0 100644 --- a/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/models/form/package-info.java +++ b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/models/form/package-info.java @@ -35,7 +35,7 @@ *

*/ -@Version("5.12.2") +@Version("5.12.3") package com.adobe.cq.forms.core.components.models.form; import org.osgi.annotation.versioning.Version; diff --git a/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/util/AbstractOptionsFieldImpl.java b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/util/AbstractOptionsFieldImpl.java index 61947e8b80..ce1ac5c3d3 100644 --- a/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/util/AbstractOptionsFieldImpl.java +++ b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/util/AbstractOptionsFieldImpl.java @@ -30,6 +30,7 @@ import com.adobe.cq.forms.core.components.internal.form.ReservedProperties; import com.adobe.cq.forms.core.components.models.form.Field; +import com.adobe.cq.forms.core.components.models.form.Label; import com.adobe.cq.forms.core.components.models.form.OptionsConstraint; /** @@ -136,4 +137,28 @@ public Object[] getDefault() { } return typedDefaultValue; } + + @Override + public String[] getOptionScreenReaderLabels() { + String[] enumNames = getEnumNames(); + if (enumNames == null) { + return null; + } + + Label label = getLabel(); + String labelValue = (label != null && label.getValue() != null) ? label.getValue() : ""; + boolean hasRichTextLabel = label != null && label.isRichText() != null && label.isRichText(); + + // Strip HTML from label once if needed + String cleanLabel = hasRichTextLabel ? labelValue.replaceAll("<[^>]*>", "") : labelValue; + + String[] ariaLabels = new String[enumNames.length]; + for (int i = 0; i < enumNames.length; i++) { + // Strip HTML from enum name for screen readers + String cleanEnumName = enumNames[i].replaceAll("<[^>]*>", ""); + ariaLabels[i] = cleanLabel + ": " + cleanEnumName; + } + + return ariaLabels; + } } diff --git a/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v1/form/CheckBoxGroupImplTest.java b/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v1/form/CheckBoxGroupImplTest.java index 5f6f20e903..2132af9625 100644 --- a/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v1/form/CheckBoxGroupImplTest.java +++ b/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v1/form/CheckBoxGroupImplTest.java @@ -39,10 +39,7 @@ import io.wcm.testing.mock.aem.junit5.AemContext; import io.wcm.testing.mock.aem.junit5.AemContextExtension; -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.*; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; @@ -57,6 +54,7 @@ public class CheckBoxGroupImplTest { private static final String PATH_CHECKBOX_GROUP_FOR_INSERTION_ORDER = CONTENT_ROOT + "/checkboxgroup-insertion-order"; private static final String PATH_CHECKBOX_GROUP_FOR_BOOLEAN = CONTENT_ROOT + "/checkboxgroup-boolean"; private static final String PATH_CHECKBOX_GROUP_NO_FIELDTYPE = CONTENT_ROOT + "/checkboxgroup-without-fieldtype"; + private static final String PATH_CHECKBOX_GROUP_OPTION_SCREEN_READER_LABEL = CONTENT_ROOT + "/checkboxgroup-option-screenreader-label"; private static final String PATH_CHECKBOX_GROUP_WITH_NULL_VALUES = CONTENT_ROOT + "/checkboxgroup-with-null-values"; private final AemContext context = FormsCoreComponentTestContext.newAemContext(); @@ -401,6 +399,33 @@ void testInsertionOrderForEnumNames() { assertArrayEquals(set.toArray(new String[0]), checkboxGroup.getEnumNames()); } + @Test + void testGetOptionScreenReaderLabels() { + CheckBoxGroup checkboxGroup = getCheckBoxGroupUnderTest(PATH_CHECKBOX_GROUP_OPTION_SCREEN_READER_LABEL); + String[] screenReaderLabels = checkboxGroup.getOptionScreenReaderLabels(); + assertEquals("Gender: Male", screenReaderLabels[0]); + assertEquals("Gender: Female", screenReaderLabels[1]); + + CheckBoxGroup spyCheckboxGroup1 = Mockito.spy(checkboxGroup); + Mockito.when(spyCheckboxGroup1.getEnumNames()).thenReturn(null); + assertNull(spyCheckboxGroup1.getOptionScreenReaderLabels()); + + CheckBoxGroup spyCheckboxGroup2 = Mockito.spy(checkboxGroup); + Mockito.when(spyCheckboxGroup2.getLabel()).thenReturn(null); + screenReaderLabels = spyCheckboxGroup2.getOptionScreenReaderLabels(); + assertEquals(": Male", screenReaderLabels[0]); + assertEquals(": Female", screenReaderLabels[1]); + + Label label = Mockito.mock(Label.class); + Mockito.when(label.getValue()).thenReturn(null); + Mockito.when(label.isRichText()).thenReturn(null); + CheckBoxGroup spyCheckboxGroup3 = Mockito.spy(checkboxGroup); + Mockito.when(spyCheckboxGroup3.getLabel()).thenReturn(label); + screenReaderLabels = spyCheckboxGroup3.getOptionScreenReaderLabels(); + assertEquals(": Male", screenReaderLabels[0]); + assertEquals(": Female", screenReaderLabels[1]); + } + @Test void testNoFieldType() { CheckBoxGroup checkboxGroup = getCheckBoxGroupUnderTest(PATH_CHECKBOX_GROUP_NO_FIELDTYPE); diff --git a/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v1/form/RadioButtonImplTest.java b/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v1/form/RadioButtonImplTest.java index cdbbf4f584..db907d4705 100644 --- a/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v1/form/RadioButtonImplTest.java +++ b/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v1/form/RadioButtonImplTest.java @@ -54,6 +54,7 @@ public class RadioButtonImplTest { private static final String PATH_RADIOBUTTON_WITH_DUPLICATE_ENUMS = CONTENT_ROOT + "/radiobutton-duplicate-enum"; private static final String PATH_RADIOBUTTON_FOR_INSERTION_ORDER = CONTENT_ROOT + "/radiobutton-insertion-order"; private static final String PATH_RADIOBUTTON_WITHOUT_FIELDTYPE = CONTENT_ROOT + "/radiobutton-without-fieldtype"; + private static final String PATH_RADIOBUTTON_OPTION_SCREEN_READER_LABEL = CONTENT_ROOT + "/radiobutton-option-screenreader-label"; private final AemContext context = FormsCoreComponentTestContext.newAemContext(); @@ -395,6 +396,14 @@ void testInsertionOrderForEnumNames() { assertArrayEquals(set.toArray(new String[0]), radioButton.getEnumNames()); } + @Test + void testGetOptionScreenReaderLabels() { + RadioButton radioButton = getRadioButtonUnderTest(PATH_RADIOBUTTON_OPTION_SCREEN_READER_LABEL); + String[] screenReaderLabels = radioButton.getOptionScreenReaderLabels(); + assertEquals("Gender: Male", screenReaderLabels[0]); + assertEquals("Gender: Female", screenReaderLabels[1]); + } + @Test void testGetScreenReaderTextWithLabel() { RadioButton radioButton = getRadioButtonUnderTest(PATH_RADIOBUTTON_CUSTOMIZED_WITH_LABEL); diff --git a/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v1/form/SwitchImplTest.java b/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v1/form/SwitchImplTest.java index dbcd75ecb0..8f089b55f2 100644 --- a/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v1/form/SwitchImplTest.java +++ b/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v1/form/SwitchImplTest.java @@ -49,6 +49,7 @@ public class SwitchImplTest { private static final String PATH_SWITCH_ENABLEUNCHECKED_BOOLEAN = CONTENT_ROOT + "/switch-boolean"; private static final String PATH_SWITCH_ENABLEUNCHECKEDOFF_BOOLEAN = CONTENT_ROOT + "/switch-enableUncheckedValueFalse-boolean"; private static final String PATH_SWITCH_WITHOUT_FIELDTYPE = CONTENT_ROOT + "/switch-without-fieldtype"; + private static final String PATH_SWITCH_OPTION_SCREEN_READER_LABEL = CONTENT_ROOT + "/switch-option-screenreader-label"; private final AemContext context = FormsCoreComponentTestContext.newAemContext(); @BeforeEach @@ -346,6 +347,14 @@ private Switch getSwitchUnderTest(String resourcePath) { return request.adaptTo(Switch.class); } + @Test + void testGetOptionScreenReaderLabels() { + Switch switchObject = getSwitchUnderTest(PATH_SWITCH_OPTION_SCREEN_READER_LABEL); + String[] screenReaderLabels = switchObject.getOptionScreenReaderLabels(); + assertEquals("SWITCH: OFF", screenReaderLabels[0]); + assertEquals("SWITCH: ON", screenReaderLabels[1]); + } + @Test void testNoFieldType() { Switch switchComp = getSwitchUnderTest(PATH_SWITCH_WITHOUT_FIELDTYPE); diff --git a/bundles/af-core/src/test/resources/form/checkboxgroup/test-content.json b/bundles/af-core/src/test/resources/form/checkboxgroup/test-content.json index ffb9ea7b03..cc60a97c14 100644 --- a/bundles/af-core/src/test/resources/form/checkboxgroup/test-content.json +++ b/bundles/af-core/src/test/resources/form/checkboxgroup/test-content.json @@ -289,5 +289,24 @@ "", "value3" ] + }, + "checkboxgroup-option-screenreader-label": { + "id": "checkboxgroup-1076f3bd737", + "jcr:primaryType": "nt:unstructured", + "sling:resourceType": "core/fd/components/form/checkboxgroup/v1/checkboxgroup", + "name": "checkboxgroup", + "fieldType": "checkbox-group", + "jcr:title": "Gender", + "orientation": "vertical", + "type": "number[]", + "enum": [ + "0", + "1" + ], + "enforceEnum": true, + "enumNames": [ + "

Male

", + "

Female

" + ] } } diff --git a/bundles/af-core/src/test/resources/form/radiobutton/test-content.json b/bundles/af-core/src/test/resources/form/radiobutton/test-content.json index f4b6205a12..cc008b590e 100644 --- a/bundles/af-core/src/test/resources/form/radiobutton/test-content.json +++ b/bundles/af-core/src/test/resources/form/radiobutton/test-content.json @@ -350,5 +350,23 @@ "

Item 2

" ], "fieldType": "radio-group" + }, + "radiobutton-option-screenreader-label": { + "id": "radiobutton-1076f3bd737", + "jcr:primaryType": "nt:unstructured", + "sling:resourceType": "core/fd/components/form/radiobutton/v1/radiobutton", + "name": "radiobutton", + "isTitleRichText": "true", + "jcr:title": "Gender", + "orientation": "vertical", + "enum": [ + "0", + "1" + ], + "enumNames": [ + "

Male

", + "

Female

" + ], + "fieldType": "radio-group" } } \ No newline at end of file diff --git a/bundles/af-core/src/test/resources/form/switch/test-content.json b/bundles/af-core/src/test/resources/form/switch/test-content.json index d62334d044..8524651529 100644 --- a/bundles/af-core/src/test/resources/form/switch/test-content.json +++ b/bundles/af-core/src/test/resources/form/switch/test-content.json @@ -198,5 +198,25 @@ "jcr:primaryType": "nt:unstructured" }, "customProp": "customPropValue" + }, + "switch-option-screenreader-label": { + "id": "switch-1076f3bd737", + "jcr:primaryType": "nt:unstructured", + "sling:resourceType": "core/fd/components/form/switch/v1/switch", + "name": "switch-option-screenreader-label", + "fieldType": "checkbox", + "isTitleRichText": "true", + "jcr:title": "SWITCH", + "tooltip": "test-short-description", + "type" : "string", + "enum": [ + "0", + "1" + ], + "enumNames": [ + "

OFF

", + "

ON

" + ], + "enableUncheckedValue": true } } diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/checkboxgroup/v1/checkboxgroup/checkboxgroup.html b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/checkboxgroup/v1/checkboxgroup/checkboxgroup.html index 96c0ceafe7..3c524e065c 100644 --- a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/checkboxgroup/v1/checkboxgroup/checkboxgroup.html +++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/checkboxgroup/v1/checkboxgroup/checkboxgroup.html @@ -13,7 +13,6 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/--> -