diff --git a/.circleci/settings.xml b/.circleci/settings.xml index 32380c42d4..af0f70f6ee 100644 --- a/.circleci/settings.xml +++ b/.circleci/settings.xml @@ -76,7 +76,7 @@ ossrh - ${env.SONATYPE_USER} + ${env.SONATYPE_USERNAME} ${env.SONATYPE_PASSWORD} diff --git a/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/form/FormStructureParserImpl.java b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/form/FormStructureParserImpl.java index 866451b898..45a8f4b742 100644 --- a/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/form/FormStructureParserImpl.java +++ b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/form/FormStructureParserImpl.java @@ -15,11 +15,9 @@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ package com.adobe.cq.forms.core.components.internal.form; -import java.io.IOException; import java.io.StringWriter; import java.io.Writer; -import org.apache.commons.lang3.StringEscapeUtils; import org.apache.sling.api.SlingHttpServletRequest; import org.apache.sling.api.resource.Resource; import org.apache.sling.models.annotations.Model; @@ -34,11 +32,10 @@ import com.adobe.cq.forms.core.components.models.form.FormStructureParser; import com.adobe.cq.forms.core.components.util.ComponentUtils; import com.adobe.cq.forms.core.components.views.Views; -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.core.SerializableString; +import com.fasterxml.jackson.core.io.CharacterEscapes; import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.SerializerProvider; -import com.fasterxml.jackson.databind.module.SimpleModule; +import com.fasterxml.jackson.databind.ObjectWriter; @Model( adaptables = { SlingHttpServletRequest.class, Resource.class }, @@ -122,25 +119,42 @@ public String getFormDefinition() { String result = null; FormContainer formContainer = resource.adaptTo(FormContainer.class); try { + HTMLCharacterEscapes htmlCharacterEscapes = new HTMLCharacterEscapes(); ObjectMapper mapper = new ObjectMapper(); - mapper.registerModule(new SimpleModule().addSerializer(String.class, new FormStructureParserImpl.EncodeHTMLSerializer())); Writer writer = new StringWriter(); + ObjectWriter objectWriter = mapper.writerWithView(Views.Publish.class); + objectWriter.getFactory().setCharacterEscapes(htmlCharacterEscapes); // return publish view specific properties only for runtime - mapper.writerWithView(Views.Publish.class).writeValue(writer, formContainer); + objectWriter.writeValue(writer, formContainer); result = writer.toString(); - } catch (IOException e) { - logger.error("Unable to generate json from resource"); + } catch (Exception e) { + logger.error("Unable to generate json from resource", e); } return result; } - private static class EncodeHTMLSerializer extends JsonSerializer { + private static final class HTMLCharacterEscapes extends CharacterEscapes { + private final int[] asciiEscapes; + + public HTMLCharacterEscapes() { + // start with set of characters known to require escaping (double-quote, backslash etc) + int[] esc = CharacterEscapes.standardAsciiEscapesForJSON(); + // and force escaping of a few others: + esc['<'] = CharacterEscapes.ESCAPE_STANDARD; + esc['>'] = CharacterEscapes.ESCAPE_STANDARD; + esc['&'] = CharacterEscapes.ESCAPE_STANDARD; + esc['\''] = CharacterEscapes.ESCAPE_STANDARD; + asciiEscapes = esc; + } + @Override - public void serialize(String value, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { - if (value != null) { - String escapedValue = StringEscapeUtils.escapeHtml4(value); - jsonGenerator.writeString(escapedValue); - } + public int[] getEscapeCodesForAscii() { + return asciiEscapes; + } + + @Override + public SerializableString getEscapeSequence(int ch) { + return null; } } } diff --git a/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v1/form/FormStructureParserImplTest.java b/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v1/form/FormStructureParserImplTest.java index 13abdd9545..4c2fc43a74 100644 --- a/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v1/form/FormStructureParserImplTest.java +++ b/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v1/form/FormStructureParserImplTest.java @@ -15,10 +15,7 @@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ package com.adobe.cq.forms.core.components.internal.models.v1.form; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; +import java.util.*; import java.util.stream.Collectors; import java.util.stream.StreamSupport; @@ -110,8 +107,17 @@ void testFormDefinitionWithHTMLEncoding() throws JsonProcessingException { new TypeReference>() {}); Assertions.assertNotNull(formStructureParser.getFormDefinition()); Map datepicker = (Map) ((Map) formJson.get(":items")).get("datepicker"); - Assertions.assertEquals(datepicker.get("description"), "<p>dummy</p>"); - Assertions.assertEquals(datepicker.get("tooltip"), "<p>test-short-description</p>"); + Assertions.assertEquals(datepicker.get("description"), "

dummy

"); + Assertions.assertEquals(datepicker.get("tooltip"), "

test-short-description

"); + Map textinput = (Map) ((Map) formJson.get(":items")).get("textinput"); + Assertions.assertEquals(textinput.get("description"), + "<ul> <li style="font-weight: bold;"><strong>abc</strong></li> <li style="font-weight: bold;"><strong>def</strong></li> <li style="font-weight: bold;"><strong>xyz</strong></li> </ul>"); + Assertions.assertEquals(textinput.get("pattern"), + "^(([^<>()[]\\.,;:s@"]+(.[^<>()[]\\.,;:s@"]+)*)|(".+"))@(([[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}])|(([a-zA-Z-0-9]+.)+[a-zA-Z]{2,}))$"); + Map events = (Map) textinput.get("events"); + String changeEvent = ((ArrayList) events.get("change")).get(0); + Assertions.assertEquals(changeEvent, + "[if(contains($event.payload.changes[].propertyName, 'value'), if(!(!($field.$value)), request(externalize('/content/forms/af/secur-bank-sfdc/secur-bank-credit-card-application-eds/jcr:content/guideContainer.af.dermis'),'POST', {operationName:'GET Person /Peoples',input:toString({UserName: $field.$value}),functionToExecute:'invokeFDMOperation',apiVersion:'2',formDataModelId:'/content/dam/formsanddocuments-fdm/wknd-vacations/wknd-vacations-triprwodata-di',runValidation:'false',guideNodePath:'/content/forms/af/secur-bank-sfdc/secur-bank-credit-card-application-eds/jcr:content/guideContainer/panelcontainer_877002065/verticaltabs/panel_copy/textinput'}, {"Content-Type" : 'application/x-www-form-urlencoded'}, 'custom:wsdlSuccess_1719466874036','custom:wsdlError_1719466874036'), {}), {})]"); } @Test diff --git a/bundles/af-core/src/test/resources/form/formstructparser/test-content.json b/bundles/af-core/src/test/resources/form/formstructparser/test-content.json index b66938e87f..cc387188f7 100644 --- a/bundles/af-core/src/test/resources/form/formstructparser/test-content.json +++ b/bundles/af-core/src/test/resources/form/formstructparser/test-content.json @@ -25,6 +25,24 @@ "visible": false, "fieldType": "datepicker" }, + "textinput": { + "jcr:primaryType": "nt:unstructured", + "sling:resourceType": "core/fd/components/form/textinput/v1/textinput", + "name": "abc", + "jcr:title": "def", + "hideTitle": false, + "description": "<ul> <li style="font-weight: bold;"><strong>abc</strong></li> <li style="font-weight: bold;"><strong>def</strong></li> <li style="font-weight: bold;"><strong>xyz</strong></li> </ul>", + "tooltip": "

test-short-description

", + "fieldType": "text-input", + "pattern": "^(([^<>()[]\\.,;:s@"]+(.[^<>()[]\\.,;:s@"]+)*)|(".+"))@(([[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}])|(([a-zA-Z-0-9]+.)+[a-zA-Z]{2,}))$", + "fd:rules" : { + "jcr:primaryType": "nt:unstructured" + }, + "fd:events" : { + "jcr:primaryType": "nt:unstructured", + "change": "[if(contains($event.payload.changes[].propertyName, 'value'), if(!(!($field.$value)), request(externalize('/content/forms/af/secur-bank-sfdc/secur-bank-credit-card-application-eds/jcr:content/guideContainer.af.dermis'),'POST', {operationName:'GET Person /Peoples',input:toString({UserName: $field.$value}),functionToExecute:'invokeFDMOperation',apiVersion:'2',formDataModelId:'/content/dam/formsanddocuments-fdm/wknd-vacations/wknd-vacations-triprwodata-di',runValidation:'false',guideNodePath:'/content/forms/af/secur-bank-sfdc/secur-bank-credit-card-application-eds/jcr:content/guideContainer/panelcontainer_877002065/verticaltabs/panel_copy/textinput'}, {"Content-Type" : 'application/x-www-form-urlencoded'}, 'custom:wsdlSuccess_1719466874036','custom:wsdlError_1719466874036'), {}), {})]" + } + }, "container1": { "jcr:primaryType": "nt:unstructured", "sling:resourceType": "wcm/foundation/components/responsivegrid", diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/samples/tabsontop/datepicker/.content.xml b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/samples/tabsontop/datepicker/.content.xml new file mode 100644 index 0000000000..ae838b23d1 --- /dev/null +++ b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/samples/tabsontop/datepicker/.content.xml @@ -0,0 +1,21 @@ + + + + + + diff --git a/it/content/src/main/content/jcr_root/content/forms/af/core-components-it/samples/tabsontop/datepicker/.content.xml b/it/content/src/main/content/jcr_root/content/forms/af/core-components-it/samples/tabsontop/datepicker/.content.xml new file mode 100755 index 0000000000..7587698031 --- /dev/null +++ b/it/content/src/main/content/jcr_root/content/forms/af/core-components-it/samples/tabsontop/datepicker/.content.xml @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/tabsontop/v1/tabsontop/clientlibs/site/js/tabs.js b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/tabsontop/v1/tabsontop/clientlibs/site/js/tabs.js index 441ac40392..13c323cb4f 100644 --- a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/tabsontop/v1/tabsontop/clientlibs/site/js/tabs.js +++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/tabsontop/v1/tabsontop/clientlibs/site/js/tabs.js @@ -64,7 +64,9 @@ setFocus(id) { super.setFocus(id); this.setActive(); - this.navigateAndFocusTab(id + '__tab'); + if (id) { + this.navigateAndFocusTab(id + '__tab'); + } } getWidget() { diff --git a/ui.frontend/src/view/FormTabs.js b/ui.frontend/src/view/FormTabs.js index 650133ea9e..67dacdc808 100644 --- a/ui.frontend/src/view/FormTabs.js +++ b/ui.frontend/src/view/FormTabs.js @@ -136,7 +136,11 @@ class FormTabs extends FormPanel { var _self = this; (function (index) { tabs[index].addEventListener("click", function (event) { - _self.navigateAndFocusTab(tabs[index].id); + // Check if the clicked element is the tab itself + if (event?.target?.id === tabs[index].id) { + _self.navigateAndFocusTab(tabs[index].id); + } + }); }(i)); tabs[i].addEventListener("keydown", function (event) { @@ -237,11 +241,16 @@ class FormTabs extends FormPanel { * @param {string} tabId - The ID of the tab to navigate to. */ navigateAndFocusTab(tabId) { - this.navigate(tabId); - this.focusWithoutScroll(this.#getTabNavElementById(tabId)); + this.setActive(); + // if current active tab and the new tabId is not same, only then set the new tab as active + if (this.#_active !== tabId) { + this.navigate(tabId); + this.focusWithoutScroll(this.#getTabNavElementById(tabId)); + } } + #getTabNavElementById(tabId) { var tabs = this.#getCachedTabs(); if (tabs) { @@ -461,7 +470,10 @@ class FormTabs extends FormPanel { var tabs = this.#getCachedTabs(); var index = this.#getTabIndexById(tabId); tabs[index].addEventListener("click", function (event) { - _self.navigateAndFocusTab(tabId); + // Check if the clicked element is the tab itself + if (event?.target?.id === tabId) { + _self.navigateAndFocusTab(tabId); + } }); tabs[index].addEventListener("keydown", function (event) { _self.#onKeyDown(event); diff --git a/ui.tests/test-module/specs/datepicker/datepicker.runtime.spec.js b/ui.tests/test-module/specs/datepicker/datepicker.runtime.spec.js index b7ab507b04..63223f1fbe 100644 --- a/ui.tests/test-module/specs/datepicker/datepicker.runtime.spec.js +++ b/ui.tests/test-module/specs/datepicker/datepicker.runtime.spec.js @@ -276,7 +276,7 @@ describe("Form Runtime with Date Picker", () => { it("Test custom error message when incorrect date format is entered", () => { const [datePicker7, datePicker7FieldView] = Object.entries(formContainer._fields)[6]; - const incorrectInputs = ["adfasdfa", "2023/11/08", "1-1-2023"]; + const incorrectInputs = ["adfasdfa", "2023/11/08", "1-1-2023", "10/2" ]; incorrectInputs.forEach(incorrectInput => { cy.get(`#${datePicker7}`).find("input").should('have.attr',"type", "text"); cy.get(`#${datePicker7}`).find("input").clear().wait(1000).type(incorrectInput).trigger('input').blur() diff --git a/ui.tests/test-module/specs/embedFormsInSiteOverrideConfig/embedFormsInSiteOverrideConfig.spec.js b/ui.tests/test-module/specs/embedFormsInSiteOverrideConfig/embedFormsInSiteOverrideConfig.spec.js index b24c69ecae..2a543ab310 100644 --- a/ui.tests/test-module/specs/embedFormsInSiteOverrideConfig/embedFormsInSiteOverrideConfig.spec.js +++ b/ui.tests/test-module/specs/embedFormsInSiteOverrideConfig/embedFormsInSiteOverrideConfig.spec.js @@ -26,31 +26,34 @@ describe("Form in site using embed container", () => { beforeEach(function () { cy.previewForm(pagePath, {multipleEmbedContainers: true}).then(p => { formContainer = p; - }) + }); }) it("should render two form", () => { expect(formContainer.length).to.equal(2); }); - it.skip("should submit on button click and thank you message of embed container must be overridden", () => { + if(cy.af.isLatestAddon) { + it("should submit on button click and thank you message of embed container must be overridden", () => { cy.intercept({ - method: 'POST', - url: '**/adobe/forms/af/submit/*', - }).as('afSubmission'); - cy.get(`.cmp-adaptiveform-button__widget`).eq(0).should('be.visible').click().then(() => { - cy.get('body').should('contain', "Thank You message from sites.\n") + method: 'POST', + url: '**/adobe/forms/af/submit/*', + }).as('afSubmission'); + cy.get(`.cmp-adaptiveform-button__widget`).eq(0).click().then(() => { + cy.get('body').should('contain', "Thank You message from sites.") + }); }); - }); - it.skip("should submit on button click and thank you message of embed container must be overridden", () => { - cy.intercept({ - method: 'POST', - url: '**/adobe/forms/af/submit/*', - }).as('afSubmission'); - cy.get(`.cmp-adaptiveform-button__widget`).eq(1).should('be.visible').click().then(() => { - cy.get('body').should('contain', "Thank You message from new form in sites.\n") + it("should submit on button click and thank you message of embed container must be overridden", () => { + cy.intercept({ + method: 'POST', + url: '**/adobe/forms/af/submit/*', + }).as('afSubmission'); + cy.get(`.cmp-adaptiveform-button__widget`).eq(1).click().then(() => { + cy.get('body').should('contain', "Thank You message from new form in sites.") + }); }); - }); + } }) }); + \ No newline at end of file diff --git a/ui.tests/test-module/specs/tabsontop/tabsontop.runtime.spec.js b/ui.tests/test-module/specs/tabsontop/tabsontop.runtime.spec.js index 94a45be6f5..bd3aa8175b 100644 --- a/ui.tests/test-module/specs/tabsontop/tabsontop.runtime.spec.js +++ b/ui.tests/test-module/specs/tabsontop/tabsontop.runtime.spec.js @@ -279,3 +279,31 @@ describe("setFocus on the first invalid field on submission", () => { }); }); }); + + + +describe("date picker calendar icon pop up inside tabs", () => { + + const pagePath = "content/forms/af/core-components-it/samples/tabsontop/datepicker.html"; + let formContainer = null; + + beforeEach(() => { + cy.previewForm(pagePath).then(p => { + formContainer = p; + }) + }); + + const tabSelector = 'ol li'; + const lastTab = () => { + return cy.get(tabSelector).last(); + } + + it(" should be visible on first click ", () => { + lastTab().click().then(() => { + // check if first click on date picker calendar opens the pope + cy.get('#datepicker-3a195924d8').find(".cmp-adaptiveform-datepicker__calendar-icon").should("be.visible").click().then(() => { + cy.get(".dp-clear").should("be.visible"); + }); + }); + }); +});