diff --git a/.circleci/ci/it-tests.js b/.circleci/ci/it-tests.js
index c2fd7df59e..22d0b4bf69 100644
--- a/.circleci/ci/it-tests.js
+++ b/.circleci/ci/it-tests.js
@@ -39,7 +39,7 @@ try {
//todo: remove this later, once aem image is released, since sites rotary aem base image has "2.25.4"
//let wcmVersion = ci.sh('mvn help:evaluate -Dexpression=core.wcm.components.version -q -DforceStdout', true);
- let wcmVersion = "2.26.0";
+ let wcmVersion = "2.27.0";
ci.stage("Integration Tests");
ci.dir(qpPath, () => {
// Connect to QP
diff --git a/.circleci/docker-compose.yml b/.circleci/docker-compose.yml
index c09afbe139..2416954443 100644
--- a/.circleci/docker-compose.yml
+++ b/.circleci/docker-compose.yml
@@ -6,7 +6,7 @@ services:
entrypoint: tail -f /dev/null # Keeps the container running
circleci-aem-cloudready:
- image: docker-adobe-cif-release.dr-uw2.adobeitc.com/circleci-aem-cloudready:17689-rel-openjdk11
+ image: docker-adobe-cif-release.dr-uw2.adobeitc.com/circleci-aem-cloudready:9aa621f43a-openjdk11
depends_on:
- circleci-qp
# Add any additional configurations or environment variables if needed
diff --git a/.github/workflows/exporter-validate-pr.yml b/.github/workflows/exporter-validate-pr.yml
index 8f3ac2c5b8..0907dd8f64 100644
--- a/.github/workflows/exporter-validate-pr.yml
+++ b/.github/workflows/exporter-validate-pr.yml
@@ -29,34 +29,39 @@ jobs:
# Loop through each changed file
for file in $changed_files; do
- # Fetch the base and head versions of the file
- base_file=$(git show ${{ github.base_ref }}:$file)
- head_file=$(git show ${{ github.head_ref }}:$file)
+ # Check if the file exists in both branches
+ if git cat-file -e origin/${{ github.base_ref }}:$file 2>/dev/null && git cat-file -e origin/${{ github.head_ref }}:$file 2>/dev/null; then
+ # Fetch the base and head versions of the file
+ base_file=$(git show origin/${{ github.base_ref }}:$file)
+ head_file=$(git show origin/${{ github.head_ref }}:$file)
- # Compare the JSON keys
- base_keys=$(echo "$base_file" | jq -r 'paths | map(tostring) | join(".")' | sed 's/\./\\./g')
- head_keys=$(echo "$head_file" | jq -r 'paths | map(tostring) | join(".")' | sed 's/\./\\./g')
+ # Compare the JSON keys
+ base_keys=$(echo "$base_file" | jq -r 'paths | map(tostring) | join(".")' | sed 's/\./\\./g')
+ head_keys=$(echo "$head_file" | jq -r 'paths | map(tostring) | join(".")' | sed 's/\./\\./g')
- # Check for removed keys
- removed_keys=$(comm -23 <(echo "$base_keys" | sort) <(echo "$head_keys" | sort))
+ # Check for removed keys
+ removed_keys=$(comm -23 <(echo "$base_keys" | sort) <(echo "$head_keys" | sort))
- if [ -n "$removed_keys" ]; then
- echo "Backward incompatibility change detected in $file. The following keys were removed:"
- echo "$removed_keys"
- exit 1
- fi
+ if [ -n "$removed_keys" ]; then
+ echo "Backward incompatibility change detected in $file. The following keys were removed:"
+ echo "$removed_keys"
+ 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]')
- 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'."
- exit 1
- fi
- done
+ 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'."
+ exit 1
+ fi
+ done
+ else
+ echo "Skipping file $file as it exists in one branch but not the other."
+ fi
done
echo "All exporter JSON files have only additions. No backward incompatibility changes detected."
- shell: bash
\ No newline at end of file
+ shell: bash
diff --git a/.github/workflows/spec-validate-pr.yml b/.github/workflows/spec-validate-pr.yml
index e9a9b3fea5..12d5dab8ef 100644
--- a/.github/workflows/spec-validate-pr.yml
+++ b/.github/workflows/spec-validate-pr.yml
@@ -13,19 +13,34 @@ jobs:
- name: Check out code
uses: actions/checkout@v2
+ - name: Validate branch names
+ id: validate
+ run: |
+ echo "Validating branch names..."
+ if ! [[ "${{ github.event.pull_request.head.ref }}" =~ ^[a-zA-Z0-9._/-]+$ ]]; then
+ echo "Invalid characters in head ref"
+ exit 1
+ fi
+ if ! [[ "${{ github.event.pull_request.base.ref }}" =~ ^[a-zA-Z0-9._/-]+$ ]]; then
+ echo "Invalid characters in base ref"
+ exit 1
+ fi
+ echo "::set-output name=head_ref::${{ github.event.pull_request.head.ref }}"
+ echo "::set-output name=base_ref::${{ github.event.pull_request.base.ref }}"
+
- name: Fetch Base and Head References
run: |
- git fetch origin ${{ github.base_ref }}:${{ github.base_ref }}
- git fetch origin ${{ github.head_ref }}:${{ github.head_ref }}
+ git fetch origin ${{ steps.validate.outputs.head_ref }}:${{ steps.validate.outputs.head_ref }}
+ git fetch origin ${{ steps.validate.outputs.base_ref }}:${{ steps.validate.outputs.base_ref }}
if: github.event_name == 'pull_request'
- name: Validate Changes and Commit Message
run: |
# Check for changes in specification files inside the resources folder
- changed_files=$(git diff --name-only ${{ github.base_ref }} ${{ github.head_ref }})
+ changed_files=$(git diff --name-only ${{ steps.validate.outputs.base_ref }} ${{ steps.validate.outputs.head_ref }})
# Fetch the commit messages from the PR
- commit_messages=$(git log --pretty=oneline ${{ github.base_ref }}..${{ github.head_ref }})
+ commit_messages=$(git log --pretty=oneline ${{ steps.validate.outputs.base_ref }}..${{ steps.validate.outputs.head_ref }})
# Check if any commit message contains a specific keyword or pattern (e.g., "RTC")
if echo "$commit_messages" | grep -q 'RTC' && echo "$changed_files" | grep -E 'resources/schema/.*\.json$'; then
@@ -34,4 +49,4 @@ jobs:
echo "Either commit message doesn't contain 'RTC' keyword or specification files haven't changed. Build failed. Please check if your changes are working in visual rule editor"
exit 1
fi
- 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 9e9af9feab..2944ee3f6c 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
@@ -74,7 +74,10 @@ private FormConstants() {
/** The resource type for check box group v1 */
public static final String RT_FD_FORM_CHECKBOX_GROUP_V1 = RT_FD_FORM_PREFIX + "checkboxgroup/v1/checkboxgroup";
- /** The resource type for reCaptcha v1 */
+ /** The resource type for turnstile v1 */
+ public static final String RT_FD_FORM_TURNSTILE_V1 = RT_FD_FORM_PREFIX + "turnstile/v1/turnstile";
+
+ /** The resource type for hCaptcha v1 */
public static final String RT_FD_FORM_HCAPTCHA_V1 = RT_FD_FORM_PREFIX + "hcaptcha/v1/hcaptcha";
/** The resource type for reCaptcha v1 */
@@ -133,4 +136,10 @@ private FormConstants() {
public static final String REQ_ATTR_REFERENCED_PATH = "referencedPage";
public static final String PROP_FRAGMENT_PATH = "fragmentPath";
+
+ /** The resource type for review v1 */
+ public static final String RT_FD_FORM_REVIEW_V1 = RT_FD_FORM_PREFIX + "review/v1/review";
+
+ /* 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";
}
diff --git a/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/models/v1/form/HCaptchaImpl.java b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/models/v1/form/HCaptchaImpl.java
index 82bc77d889..ad27b1d95f 100644
--- a/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/models/v1/form/HCaptchaImpl.java
+++ b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/models/v1/form/HCaptchaImpl.java
@@ -42,7 +42,6 @@
import com.adobe.cq.forms.core.components.internal.form.ReservedProperties;
import com.adobe.cq.forms.core.components.models.form.HCaptcha;
import com.adobe.cq.forms.core.components.util.AbstractCaptchaImpl;
-import com.fasterxml.jackson.annotation.JsonIgnore;
@Model(
adaptables = { SlingHttpServletRequest.class, Resource.class },
@@ -68,26 +67,23 @@ public class HCaptchaImpl extends AbstractCaptchaImpl implements HCaptcha {
private CloudConfigurationProvider cloudConfigurationProvider;
@ValueMapValue(injectionStrategy = InjectionStrategy.OPTIONAL)
- @JsonIgnore
@Named(ReservedProperties.PN_CLOUD_SERVICE_PATH)
protected String cloudServicePath;
@ValueMapValue(injectionStrategy = InjectionStrategy.OPTIONAL)
- @JsonIgnore
@Named(ReservedProperties.PN_SIZE)
protected String size;
- private static final String SITE_KEY = "siteKey";
- private static final String URI = "uri";
- private static final String SIZE = "size";
- private static final String THEME = "theme";
- private static final String TYPE = "type";
-
@Override
public String getCloudServicePath() {
return cloudServicePath;
}
+ @Override
+ public String getSize() {
+ return size;
+ }
+
@Override
public String getProvider() {
return "hcaptcha";
@@ -113,11 +109,11 @@ public Map getCaptchaProperties() throws GuideException {
} catch (GuideException e) {
LOGGER.error("[AF] [Captcha] [HCAPTCHA] Error while fetching cloud configuration, upgrade to latest release to use hCaptcha.");
}
- customCaptchaProperties.put(SITE_KEY, siteKey);
- customCaptchaProperties.put(URI, uri);
- customCaptchaProperties.put(SIZE, this.size);
- customCaptchaProperties.put(THEME, "light");
- customCaptchaProperties.put(TYPE, "image");
+ customCaptchaProperties.put(CAPTCHA_SITE_KEY, siteKey);
+ customCaptchaProperties.put(CAPTCHA_URI, uri);
+ customCaptchaProperties.put(CAPTCHA_SIZE, getSize());
+ customCaptchaProperties.put(CAPTCHA_THEME, "light");
+ customCaptchaProperties.put(CAPTCHA_TYPE, "image");
return customCaptchaProperties;
diff --git a/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/models/v1/form/RecaptchaImpl.java b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/models/v1/form/RecaptchaImpl.java
index 1127021c1d..3f93b6f050 100644
--- a/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/models/v1/form/RecaptchaImpl.java
+++ b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/models/v1/form/RecaptchaImpl.java
@@ -41,7 +41,6 @@
import com.adobe.cq.forms.core.components.internal.form.ReservedProperties;
import com.adobe.cq.forms.core.components.models.form.Captcha;
import com.adobe.cq.forms.core.components.util.AbstractCaptchaImpl;
-import com.fasterxml.jackson.annotation.JsonIgnore;
@Model(
adaptables = { SlingHttpServletRequest.class, Resource.class },
@@ -65,33 +64,25 @@ public class RecaptchaImpl extends AbstractCaptchaImpl implements Captcha {
private CloudConfigurationProvider cloudConfigurationProvider;
@ValueMapValue(injectionStrategy = InjectionStrategy.OPTIONAL)
- @JsonIgnore
@Named(ReservedProperties.PN_RECAPTCHA_CLOUD_SERVICE_PATH)
protected String cloudServicePath;
@ValueMapValue(injectionStrategy = InjectionStrategy.OPTIONAL)
- @JsonIgnore
@Named(ReservedProperties.PN_RECAPTCHA_SIZE)
protected String size;
public static final String RECAPTCHA_DEFAULT_DOMAIN = "https://www.recaptcha.net/";
public static final String RECAPTCHA_DEFAULT_URL = RECAPTCHA_DEFAULT_DOMAIN + "recaptcha/api.js";
public static final String RECAPTCHA_ENTERPRISE_DEFAULT_URL = RECAPTCHA_DEFAULT_DOMAIN + "recaptcha/enterprise.js";
- private static final String RECAPTCHA_SITE_KEY = "siteKey";
- private static final String RECAPTCHA_URI = "uri";
- private static final String RECAPTCHA_SIZE = "size";
- private static final String RECAPTCHA_THEME = "theme";
- private static final String RECAPTCHA_TYPE = "type";
- private static final String RECAPTCHA_VERSION = "version";
- private static final String RECAPTCHA_KEYTYPE = "keyType";
+ public static final String RECAPTCHA_VERSION = "version";
+ public static final String RECAPTCHA_KEYTYPE = "keyType";
@Override
- @JsonIgnore
public String getCloudServicePath() {
return cloudServicePath;
}
- @JsonIgnore
+ @Override
public String getSize() {
return size;
}
@@ -101,7 +92,6 @@ public String getProvider() {
return "recaptcha";
}
- @JsonIgnore
@Override
public Map getCaptchaProperties() throws GuideException {
@@ -118,20 +108,19 @@ public Map getCaptchaProperties() throws GuideException {
keyType = reCaptchaConfiguration.keyType();
}
}
- customCaptchaProperties.put(RECAPTCHA_SITE_KEY, siteKey);
+ customCaptchaProperties.put(CAPTCHA_SITE_KEY, siteKey);
if (StringUtils.isNotEmpty(version) && version.equals("enterprise")) {
- customCaptchaProperties.put(RECAPTCHA_URI, RECAPTCHA_ENTERPRISE_DEFAULT_URL);
+ customCaptchaProperties.put(CAPTCHA_URI, RECAPTCHA_ENTERPRISE_DEFAULT_URL);
} else {
- customCaptchaProperties.put(RECAPTCHA_URI, RECAPTCHA_DEFAULT_URL);
+ customCaptchaProperties.put(CAPTCHA_URI, RECAPTCHA_DEFAULT_URL);
}
- customCaptchaProperties.put(RECAPTCHA_SIZE, getSize());
- customCaptchaProperties.put(RECAPTCHA_THEME, "light");
- customCaptchaProperties.put(RECAPTCHA_TYPE, "image");
+ customCaptchaProperties.put(CAPTCHA_SIZE, getSize());
+ customCaptchaProperties.put(CAPTCHA_THEME, "light");
+ customCaptchaProperties.put(CAPTCHA_TYPE, "image");
customCaptchaProperties.put(RECAPTCHA_VERSION, version);
customCaptchaProperties.put(RECAPTCHA_KEYTYPE, keyType);
return customCaptchaProperties;
}
-
}
diff --git a/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/models/v1/form/ReviewImpl.java b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/models/v1/form/ReviewImpl.java
new file mode 100644
index 0000000000..362447b4f2
--- /dev/null
+++ b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/models/v1/form/ReviewImpl.java
@@ -0,0 +1,76 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ Copyright 2023 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 java.util.Arrays;
+import java.util.Map;
+
+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 org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+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.Review;
+import com.adobe.cq.forms.core.components.util.AbstractBaseImpl;
+
+@Model(
+ adaptables = { SlingHttpServletRequest.class, Resource.class },
+ adapters = { Review.class,
+ ComponentExporter.class },
+ resourceType = { FormConstants.RT_FD_FORM_REVIEW_V1 })
+@Exporter(name = ExporterConstants.SLING_MODEL_EXPORTER_NAME, extensions = ExporterConstants.SLING_MODEL_EXTENSION)
+public class ReviewImpl extends AbstractBaseImpl implements Review {
+
+ private static final String LINKED_PANEL_PROPERTY = "fd:linkedPanels";
+ private static final String EDIT_ACTION_PROPERTY = "fd:editModeAction";
+
+ @ValueMapValue(injectionStrategy = InjectionStrategy.OPTIONAL, name = "fd:linkedPanels")
+ @Nullable
+ private String[] linkedPanels;
+
+ @ValueMapValue(injectionStrategy = InjectionStrategy.OPTIONAL, name = "fd:editModeAction")
+ @Nullable
+ private String editModeAction;
+
+ public String[] getLinkedPanels() {
+ return linkedPanels != null ? Arrays.copyOf(linkedPanels, linkedPanels.length) : new String[] {};
+ }
+
+ public String getEditModeAction() {
+ return editModeAction;
+ }
+
+ @Override
+ public String getFieldType() {
+ return super.getFieldType(FieldType.PLAIN_TEXT);
+ }
+
+ @Override
+ public @NotNull Map getProperties() {
+ Map customProperties = super.getProperties();
+ customProperties.put(LINKED_PANEL_PROPERTY, getLinkedPanels());
+ customProperties.put(EDIT_ACTION_PROPERTY, getEditModeAction());
+ return customProperties;
+ }
+}
diff --git a/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/models/v1/form/TurnstileImpl.java b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/models/v1/form/TurnstileImpl.java
new file mode 100644
index 0000000000..d1f3991b9e
--- /dev/null
+++ b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/models/v1/form/TurnstileImpl.java
@@ -0,0 +1,156 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ 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 java.util.LinkedHashMap;
+import java.util.Map;
+
+import javax.annotation.PostConstruct;
+import javax.inject.Inject;
+import javax.inject.Named;
+
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+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.OSGiService;
+import org.apache.sling.models.annotations.injectorspecific.ValueMapValue;
+import org.osgi.service.component.annotations.Reference;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.adobe.aemds.guide.model.TurnstileConfiguration;
+import com.adobe.aemds.guide.service.CloudConfigurationProvider;
+import com.adobe.aemds.guide.service.GuideException;
+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.Turnstile;
+import com.adobe.cq.forms.core.components.util.AbstractCaptchaImplV2;
+
+@Model(
+ adaptables = { SlingHttpServletRequest.class, Resource.class },
+ adapters = { Turnstile.class,
+ ComponentExporter.class },
+ resourceType = { FormConstants.RT_FD_FORM_TURNSTILE_V1 })
+@Exporter(name = ExporterConstants.SLING_MODEL_EXPORTER_NAME, extensions = ExporterConstants.SLING_MODEL_EXTENSION)
+public class TurnstileImpl extends AbstractCaptchaImplV2 implements Turnstile {
+ private static final Logger LOGGER = LoggerFactory.getLogger(TurnstileImpl.class);
+
+ @Inject
+ private ResourceResolver resourceResolver;
+
+ private Resource resource;
+ private String captchaSiteKey;
+
+ @Reference
+ @ValueMapValue(injectionStrategy = InjectionStrategy.OPTIONAL)
+ private TurnstileConfiguration turnstileConfiguration;
+
+ @OSGiService
+ @ValueMapValue(injectionStrategy = InjectionStrategy.OPTIONAL)
+ private CloudConfigurationProvider cloudConfigurationProvider;
+
+ @ValueMapValue(injectionStrategy = InjectionStrategy.OPTIONAL)
+ @Named("cloudServicePath")
+ protected String cloudServicePath;
+
+ @ValueMapValue(injectionStrategy = InjectionStrategy.OPTIONAL)
+ @Named("size")
+ protected String size;
+
+ @Override
+ public String getCloudServicePath() {
+ return cloudServicePath;
+ }
+
+ @Override
+ public String getProvider() {
+ return "turnstile";
+ }
+
+ @Override
+ public String getSize() {
+ return size;
+ }
+
+ /**
+ * Set the turnstileConfiguration, by fetching it from the cloud configurations.
+ * Also sets the captchaSiteKey.
+ */
+ private void setTurnstileConfiguration() {
+ LOGGER.debug("[AF] [Captcha] [TURNSTILE] Fetching cloud configuration for turnstile.");
+ if (cloudConfigurationProvider != null) {
+ try {
+ resource = resourceResolver.getResource(this.getPath());
+ turnstileConfiguration = cloudConfigurationProvider.getTurnstileCloudConfiguration(resource);
+ if (turnstileConfiguration != null) {
+ captchaSiteKey = turnstileConfiguration.getSiteKey();
+ } else {
+ LOGGER.debug("[AF] [Captcha] [TURNSTILE] Cloud configuration for turnstile is not available for " + this.getPath());
+ }
+ } catch (GuideException e) {
+ LOGGER.error(
+ "[AF] [Captcha] [TURNSTILE] Error while fetching cloud configuration, upgrade to latest release to use turnstile.", e);
+ }
+ } else {
+ LOGGER.error(
+ "[AF] [Captcha] [TURNSTILE] Error while fetching cloud configuration, upgrade to latest release to use turnstile.");
+ }
+ }
+
+ @PostConstruct
+ @Override
+ public Map getCaptchaProperties() {
+ Map customCaptchaProperties = new LinkedHashMap<>();
+ String siteKey = null, uri = null, widgetType = null;
+ if (turnstileConfiguration == null) {
+ setTurnstileConfiguration();
+ }
+ if (turnstileConfiguration != null) {
+ customCaptchaProperties.put(CAPTCHA_URI, turnstileConfiguration.getClientSideJsUrl());
+ customCaptchaProperties.put(CAPTCHA_WIDGET_TYPE, turnstileConfiguration.getWidgetType());
+ }
+ customCaptchaProperties.put(CAPTCHA_SIZE, getSize());
+ customCaptchaProperties.put(CAPTCHA_THEME, CAPTCHA_THEME_LIGHT);
+ return customCaptchaProperties;
+ }
+
+ @PostConstruct
+ @Override
+ public String getCaptchaDisplayMode() {
+ CaptchaDisplayMode captchaDisplayMode = CaptchaDisplayMode.VISIBLE;
+ if (turnstileConfiguration == null) {
+ setTurnstileConfiguration();
+ }
+ if (turnstileConfiguration != null && CaptchaDisplayMode.INVISIBLE.getValue().equals(turnstileConfiguration.getWidgetType())) {
+ captchaDisplayMode = CaptchaDisplayMode.INVISIBLE;
+ }
+ return captchaDisplayMode.getValue();
+ }
+
+ @PostConstruct
+ @Override
+ public String getCaptchaSiteKey() {
+ if (turnstileConfiguration == null) {
+ setTurnstileConfiguration();
+ }
+ return this.captchaSiteKey;
+ }
+}
diff --git a/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/models/v2/form/FormContainerImpl.java b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/models/v2/form/FormContainerImpl.java
index bf7eeee41b..26c2ac816a 100644
--- a/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/models/v2/form/FormContainerImpl.java
+++ b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/models/v2/form/FormContainerImpl.java
@@ -77,6 +77,8 @@ public class FormContainerImpl extends AbstractContainerImpl implements FormCont
private static final String FD_IS_HAMBURGER_MENU_ENABLED = "fd:isHamburgerMenuEnabled";
public static final String FD_FORM_DATA_ENABLED = "fd:formDataEnabled";
public static final String FD_ROLE_ATTRIBUTE = "fd:roleAttribute";
+ private static final String FD_CUSTOM_FUNCTIONS_URL = "fd:customFunctionsUrl";
+ private static final String FD_DATA_URL = "fd:dataUrl";
@OSGiService(injectionStrategy = InjectionStrategy.OPTIONAL)
private CoreComponentCustomPropertiesProvider coreComponentCustomPropertiesProvider;
@@ -343,6 +345,9 @@ public String getLanguageDirection() {
properties.put(FD_ROLE_ATTRIBUTE, getRoleAttribute());
properties.put(FD_FORM_DATA_ENABLED, formDataEnabled);
properties.put(ReservedProperties.FD_AUTO_SAVE_PROPERTY_WRAPPER, this.autoSaveConfig);
+ properties.put(FD_CUSTOM_FUNCTIONS_URL, getCustomFunctionUrl());
+ properties.put(FD_DATA_URL, getDataUrl());
+
return properties;
}
@@ -396,4 +401,9 @@ public String getName() {
return FormContainer.super.getName();
}
+ @Override
+ public String getCustomFunctionUrl() {
+ return getContextPath() + ADOBE_GLOBAL_API_ROOT + FORMS_RUNTIME_API_GLOBAL_ROOT + "/customfunctions/" + getId();
+ }
+
}
diff --git a/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/servlets/ReviewDataSourceServlet.java b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/servlets/ReviewDataSourceServlet.java
new file mode 100644
index 0000000000..bd73c36b6d
--- /dev/null
+++ b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/servlets/ReviewDataSourceServlet.java
@@ -0,0 +1,135 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ 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.servlets;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import javax.servlet.Servlet;
+
+import org.apache.jackrabbit.JcrConstants;
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.api.SlingHttpServletResponse;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.resource.SyntheticResource;
+import org.apache.sling.api.resource.ValueMap;
+import org.apache.sling.api.wrappers.ValueMapDecorator;
+import org.jetbrains.annotations.NotNull;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+
+import com.adobe.cq.export.json.ComponentExporter;
+import com.adobe.cq.forms.core.components.internal.form.FormConstants;
+import com.adobe.cq.forms.core.components.models.form.Base;
+import com.adobe.cq.forms.core.components.models.form.Container;
+import com.adobe.cq.forms.core.components.models.form.FormComponent;
+import com.adobe.cq.forms.core.components.models.form.FormContainer;
+import com.adobe.cq.forms.core.components.models.form.Label;
+import com.adobe.cq.forms.core.components.util.ComponentUtils;
+import com.adobe.granite.ui.components.ExpressionResolver;
+import com.adobe.granite.ui.components.ds.DataSource;
+import com.adobe.granite.ui.components.ds.SimpleDataSource;
+import com.adobe.granite.ui.components.ds.ValueMapResource;
+
+@Component(
+ service = { Servlet.class },
+ property = {
+ "sling.servlet.resourceTypes=" + FormConstants.RT_FD_FORM_REVIEW_DATASOURCE_V1,
+ "sling.servlet.methods=GET",
+ "sling.servlet.extensions=html"
+ })
+public class ReviewDataSourceServlet extends AbstractDataSourceServlet {
+
+ /**
+ * Defines the form meta data type. Possible values: {@code submitAction},
+ * {@code prefillServiceProvider}
+ *
+ * @todo: Add other metadata types here like fragment, actions etc
+ */
+ @Reference
+ private transient ExpressionResolver expressionResolver;
+
+ @NotNull
+ @Override
+ protected ExpressionResolver getExpressionResolver() {
+ return expressionResolver;
+ }
+
+ @Override
+ protected void doGet(@NotNull SlingHttpServletRequest request, @NotNull SlingHttpServletResponse response) {
+
+ ResourceResolver resourceResolver = request.getResourceResolver();
+ String componentInstancePath = request.getRequestPathInfo().getSuffix();
+ List resources = new ArrayList<>();
+ if (resourceResolver != null) {
+ Resource componentInstance = resourceResolver.getResource(componentInstancePath);
+ Resource formInstance = ComponentUtils.getFormContainer(componentInstance);
+ if (formInstance != null) {
+ FormContainer formContainer = formInstance.adaptTo(FormContainer.class);
+ List panels = ((List) getMultipleChildPanels(formContainer))
+ .stream().filter(x -> "panel".equals(x.getFieldType())).collect(Collectors.toList());
+ for (Base panel : panels) {
+ String name = panel != null ? panel.getName() : "";
+ String title = "";
+ if (panel != null) {
+ Label label = panel.getLabel();
+ if (label != null) {
+ String value = label.getValue();
+ if (value != null) {
+ title = value;
+ }
+ }
+ }
+ if (name != null && title != null) {
+ resources.add(getResourceForDropdownDisplay(resourceResolver, title, name));
+ }
+ }
+ }
+ }
+ SimpleDataSource actionTypeDataSource = new SimpleDataSource(resources.iterator());
+ request.setAttribute(DataSource.class.getName(), actionTypeDataSource);
+ }
+
+ /**
+ * Retrieves a list of child panels that have at least two siblings.
+ * If a panel has fewer than two siblings, it will not be included in the returned list.
+ *
+ * @param formContainer the top-level form container
+ * @return a list of panels with at least two siblings
+ */
+ private List extends ComponentExporter> getMultipleChildPanels(FormComponent formContainer) {
+ while (formContainer instanceof Container && ((Container) formContainer).getItems().size() == 1) {
+ formContainer = (FormComponent) ((Container) formContainer).getItems().get(0);
+ }
+ if (formContainer instanceof Container) {
+ return ((Container) formContainer).getItems();
+ }
+ return new ArrayList<>();
+ }
+
+ private SyntheticResource getResourceForDropdownDisplay(ResourceResolver resourceResolver, String displayValue,
+ String dataValue) {
+ Map dropdownMap = new HashMap<>();
+ dropdownMap.put("text", displayValue);
+ dropdownMap.put("value", dataValue);
+ ValueMap dropdownEntryVm = new ValueMapDecorator(dropdownMap);
+ return new ValueMapResource(resourceResolver, "", JcrConstants.NT_UNSTRUCTURED, dropdownEntryVm);
+ }
+}
diff --git a/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/models/form/Captcha.java b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/models/form/Captcha.java
index 3fcc130f5a..baf2e33847 100644
--- a/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/models/form/Captcha.java
+++ b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/models/form/Captcha.java
@@ -30,15 +30,63 @@
@ConsumerType
public interface Captcha extends Field {
+ /**
+ * Defines the display mode for captcha.
+ * Possible values: {@code visible}, {@code invisible}
+ *
+ * @since com.adobe.cq.forms.core.components.models.form 5.10.0
+ */
+ enum CaptchaDisplayMode {
+ VISIBLE("visible"),
+ INVISIBLE("invisible");
+
+ private String displayMode;
+
+ CaptchaDisplayMode(String displayMode) {
+ this.displayMode = displayMode;
+ }
+
+ /**
+ * Returns the string value of this enum constant.
+ *
+ * @return the string value of this enum constant
+ * @since com.adobe.cq.forms.core.components.models.form 5.10.0
+ */
+ public String getValue() {
+ return displayMode;
+ }
+ }
+
@JsonIgnore
default String getCloudServicePath() {
return null;
}
@JsonIgnore
+ String getSize();
+
String getProvider();
@JsonIgnore
Map getCaptchaProperties() throws GuideException;
+ /**
+ * Returns the display mode of the captcha component.
+ *
+ * @return the string value of the one of the {@link CaptchaDisplayMode} enum
+ * @since com.adobe.cq.forms.core.components.models.form 5.10.0
+ */
+ default String getCaptchaDisplayMode() {
+ return null;
+ }
+
+ /**
+ * Returns the site key of the captcha component.
+ *
+ * @return the site key
+ * @since com.adobe.cq.forms.core.components.models.form 5.10.0
+ */
+ default String getCaptchaSiteKey() {
+ return null;
+ }
}
diff --git a/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/models/form/FormContainer.java b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/models/form/FormContainer.java
index 3244d42a1e..e74933fec9 100644
--- a/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/models/form/FormContainer.java
+++ b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/models/form/FormContainer.java
@@ -373,4 +373,16 @@ default void visit(Consumer callback) throws Exception {}
default String getParentPagePath() {
return null;
}
+
+ /**
+ * Returns the url from where the custom functions should be registered
+ *
+ * @return custom function registration url
+ * @since com.adobe.cq.forms.core.components.models.form 5.9.5
+ */
+ @JsonIgnore
+ default String getCustomFunctionUrl() {
+ return null;
+ }
+
}
diff --git a/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/models/form/Review.java b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/models/form/Review.java
new file mode 100644
index 0000000000..066a376a7d
--- /dev/null
+++ b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/models/form/Review.java
@@ -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.
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
+package com.adobe.cq.forms.core.components.models.form;
+
+import org.osgi.annotation.versioning.ConsumerType;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+
+/**
+ * Defines the {@code Review} Sling Model used for the {@code /apps/core/fd/components/form/review} component.
+ *
+ * @since com.adobe.cq.forms.core.components.models 5.9.6
+ */
+@ConsumerType
+public interface Review extends Base {
+
+ /**
+ * @return an array of linked panels to be reviewed on the review page. Each linked panel is the name of a panel that is linked to the
+ * review page.
+ * @since com.adobe.cq.forms.core.components.models.form 5.9.6
+ */
+ @JsonIgnore
+ String[] getLinkedPanels();
+
+ /**
+ * @return the edit mode action, which indicates whether edit button is visible on the review page at field, panel, both, or none
+ * @since com.adobe.cq.forms.core.components.models.form 5.9.6
+ */
+ @JsonIgnore
+ String getEditModeAction();
+}
diff --git a/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/models/form/Turnstile.java b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/models/form/Turnstile.java
new file mode 100644
index 0000000000..10d55eeb51
--- /dev/null
+++ b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/models/form/Turnstile.java
@@ -0,0 +1,28 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ 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;
+
+/**
+ * Defines the form {@code Turnstile} Sling Model used for the {@code /apps/core/fd/components/form/turnstile/v1/turnstile}
+ * component.
+ *
+ * @since com.adobe.cq.forms.core.components.models.form 5.10.0
+ */
+@ConsumerType
+public interface Turnstile extends Captcha {}
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 5151c0b7ea..a659eb1155 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.7.5")
+@Version("5.10.0")
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/AbstractCaptchaImpl.java b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/util/AbstractCaptchaImpl.java
index 0a34746ea6..4b82de8c63 100644
--- a/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/util/AbstractCaptchaImpl.java
+++ b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/util/AbstractCaptchaImpl.java
@@ -28,7 +28,17 @@
*/
public abstract class AbstractCaptchaImpl extends AbstractFieldImpl implements Captcha {
public static final String CUSTOM_RECAPTCHA_PROPERTY_WRAPPER = "fd:captcha";
+ protected static final String CAPTCHA_CONFIG = "config";
+ protected static final String CAPTCHA_SITE_KEY = "siteKey";
+ protected static final String CAPTCHA_URI = "uri";
+ protected static final String CAPTCHA_SIZE = "size";
+ protected static final String CAPTCHA_THEME = "theme";
+ protected static final String CAPTCHA_THEME_LIGHT = "light";
+ protected static final String CAPTCHA_TYPE = "type";
+ protected static final String CAPTCHA_TYPE_IMAGE = "image";
+ protected static final String CAPTCHA_WIDGET_TYPE = "widgetType";
+ @Override
@JsonIgnore
public abstract String getProvider();
@@ -37,6 +47,7 @@ public String getFieldType() {
return super.getFieldType(FieldType.CAPTCHA);
}
+ @JsonIgnore
public abstract Map getCaptchaProperties();
public Map getProperties() {
diff --git a/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/util/AbstractCaptchaImplV2.java b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/util/AbstractCaptchaImplV2.java
new file mode 100644
index 0000000000..cc1e17c282
--- /dev/null
+++ b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/util/AbstractCaptchaImplV2.java
@@ -0,0 +1,73 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ 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.util;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.annotation.PostConstruct;
+
+import com.adobe.cq.forms.core.components.models.form.Captcha;
+import com.adobe.cq.forms.core.components.models.form.FieldType;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ * AbstractCaptchaImplV2 is an updated implementation for handling captcha field types.
+ *
+ * This class represents an evolution in the captcha JSON structure where captchaProvider
+ * is promoted to a top-level property, improving JSON clarity and eliminating redundancy.
+ *
+ * Background:
+ * Previous Implementation (AbstractCaptchaImpl):
+ * - Captcha provider information was embedded within the fd:captcha custom property
+ * - This led to redundant data and a less clean JSON structure with the updated forms spec
+ *
+ * Current Implementation (AbstractCaptchaImplV2):
+ * - CaptchaProvider is now a first-class citizen at the root level of the JSON
+ * - This change results in a cleaner and more efficient JSON structure
+ *
+ * Note: AbstractCaptchaImpl is not deprecated yet, as it is still used by
+ * recaptcha/hcaptcha v1 implementations in core components. Once these are migrated
+ * to AbstractCaptchaImplV2, the V1 implementation will be deprecated.
+ */
+public abstract class AbstractCaptchaImplV2 extends AbstractCaptchaImpl implements Captcha {
+
+ @Override
+ @JsonProperty("captchaProvider")
+ @JsonIgnore(false)
+ public abstract String getProvider();
+
+ @Override
+ public String getFieldType() {
+ return super.getFieldType(FieldType.CAPTCHA);
+ }
+
+ public abstract Map getCaptchaProperties();
+
+ @PostConstruct
+ public Map getProperties() {
+ Map properties = super.getProperties();
+ Map captchaConfig = new HashMap<>();
+ Map captchaProperties = getCaptchaProperties();
+ if (captchaProperties != null && captchaProperties.size() > 0) {
+ captchaConfig.put(CAPTCHA_CONFIG, captchaProperties);
+ }
+ properties.put(CUSTOM_RECAPTCHA_PROPERTY_WRAPPER, captchaConfig);
+ return properties;
+ }
+
+}
diff --git a/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v1/form/ReviewImplTest.java b/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v1/form/ReviewImplTest.java
new file mode 100644
index 0000000000..4b8e902de1
--- /dev/null
+++ b/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v1/form/ReviewImplTest.java
@@ -0,0 +1,87 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ 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 java.io.IOException;
+import java.util.Map;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+import com.adobe.cq.forms.core.Utils;
+import com.adobe.cq.forms.core.components.models.form.Review;
+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.jupiter.api.Assertions.*;
+
+@ExtendWith(AemContextExtension.class)
+public class ReviewImplTest {
+
+ private final AemContext context = FormsCoreComponentTestContext.newAemContext();
+ private static final String TEST_BASE = "/form/review";
+ private static final String APPS_ROOT = "/apps";
+ private static final String PATH_REVIEW = "/apps/formcontainer/wizard/panel2/review";
+
+ @BeforeEach
+ void setUp() throws Exception {
+ context.load().json(TEST_BASE + FormsCoreComponentTestContext.TEST_CONTENT_JSON, APPS_ROOT);
+ }
+
+ @Test
+ void testGetEditAction() {
+ Review review = Utils.getComponentUnderTest(PATH_REVIEW, ReviewImpl.class, context);
+ assertEquals("field", review.getEditModeAction());
+ }
+
+ @Test
+ public void testGetLinkedPanelsWithNonNullArray() throws IOException {
+ Review review = Utils.getComponentUnderTest(PATH_REVIEW, ReviewImpl.class, context);
+ String[] linkedPanels = review.getLinkedPanels();
+ String[] expectedLinkedPanels = context.resourceResolver().getResource(PATH_REVIEW).getValueMap().get("fd:linkedPanels",
+ String[].class);
+ assertNotNull(expectedLinkedPanels);
+ assertArrayEquals(expectedLinkedPanels, linkedPanels);
+ }
+
+ @Test
+ public void testGetLinkedPanelsWithNullArray() {
+ Review review = new ReviewImpl();
+ String[] linkedPanels = review.getLinkedPanels();
+ assertNotNull(linkedPanels);
+ assertEquals(0, linkedPanels.length);
+ }
+
+ @Test
+ void testGetProperties() {
+ Review review = Utils.getComponentUnderTest(PATH_REVIEW, ReviewImpl.class, context);
+ Map properties = review.getProperties();
+ assertEquals("/apps/formcontainer/wizard/panel2/review", properties.get("fd:path"));
+ assertEquals("field", properties.get("fd:editModeAction"));
+
+ }
+
+ @Test
+ public void testGetFieldType() {
+ Review review = new ReviewImpl();
+ String fieldType = review.getFieldType();
+ assertEquals("plain-text", fieldType);
+ }
+
+}
diff --git a/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v1/form/TurnstileImplTest.java b/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v1/form/TurnstileImplTest.java
new file mode 100644
index 0000000000..d54786385b
--- /dev/null
+++ b/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v1/form/TurnstileImplTest.java
@@ -0,0 +1,155 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ 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.sling.api.resource.Resource;
+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.aemds.guide.model.HCaptchaConfiguration;
+import com.adobe.aemds.guide.model.ReCaptchaConfigurationModel;
+import com.adobe.aemds.guide.model.TurnstileConfiguration;
+import com.adobe.aemds.guide.service.CloudConfigurationProvider;
+import com.adobe.aemds.guide.service.GuideException;
+import com.adobe.cq.forms.core.Utils;
+import com.adobe.cq.forms.core.components.internal.form.FormConstants;
+import com.adobe.cq.forms.core.components.models.form.Captcha;
+import com.adobe.cq.forms.core.components.models.form.FieldType;
+import com.adobe.cq.forms.core.components.models.form.Turnstile;
+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;
+import static org.junit.Assert.assertNotNull;
+
+@ExtendWith(AemContextExtension.class)
+public class TurnstileImplTest {
+ private static final String BASE = "/form/turnstile";
+ private static final String CONTENT_ROOT = "/content";
+ private static final String PATH_TURNSTILE = CONTENT_ROOT + "/turnstile";
+
+ private final AemContext context = FormsCoreComponentTestContext.newAemContext();
+
+ TurnstileConfiguration turnstileConfig = Mockito.mock(TurnstileConfiguration.class);
+
+ CloudConfigurationProvider cloudConfigurationProvider = new CloudConfigurationProvider() {
+ @Override
+ public ReCaptchaConfigurationModel getRecaptchaCloudConfiguration(Resource resource) throws GuideException {
+ return null;
+ }
+
+ @Override
+ public String getCustomFunctionUrl(Resource resource) {
+ return null;
+ }
+
+ @Override
+ public HCaptchaConfiguration getHCaptchaCloudConfiguration(Resource resource) throws GuideException {
+ return null;
+ }
+
+ @Override
+ public TurnstileConfiguration getTurnstileCloudConfiguration(Resource resource) throws GuideException {
+ return turnstileConfig;
+ }
+
+ };
+
+ @BeforeEach
+ void setUp() throws GuideException {
+ context.load().json(BASE + FormsCoreComponentTestContext.TEST_CONTENT_JSON, CONTENT_ROOT);
+ context.registerService(CloudConfigurationProvider.class, cloudConfigurationProvider);
+ }
+
+ @Test
+ void testExportedType() {
+ Captcha turnstile = Utils.getComponentUnderTest(PATH_TURNSTILE, Turnstile.class, context);
+ assertEquals(FormConstants.RT_FD_FORM_TURNSTILE_V1, turnstile.getExportedType());
+ Turnstile turnstileMock = Mockito.mock(Turnstile.class);
+ Mockito.when(turnstileMock.getExportedType()).thenCallRealMethod();
+ assertEquals("", turnstileMock.getExportedType());
+ }
+
+ @Test
+ void testFieldType() {
+ Turnstile turnstile = Utils.getComponentUnderTest(PATH_TURNSTILE, Turnstile.class, context);
+ assertEquals(FieldType.CAPTCHA.getValue(), turnstile.getFieldType());
+ }
+
+ @Test
+ void testGetName() {
+ Turnstile turnstile = Utils.getComponentUnderTest(PATH_TURNSTILE, Turnstile.class, context);
+ assertEquals("turnstile1715230058257", turnstile.getName());
+ Turnstile turnstileMock = Mockito.mock(Turnstile.class);
+ Mockito.when(turnstileMock.getName()).thenCallRealMethod();
+ assertEquals(null, turnstileMock.getName());
+ }
+
+ @Test
+ void testGetTurnstileProvider() {
+ Turnstile turnstile = Utils.getComponentUnderTest(PATH_TURNSTILE, Turnstile.class, context);
+ assertEquals("turnstile", turnstile.getProvider());
+ Turnstile turnstileMock = Mockito.mock(Turnstile.class);
+ Mockito.when(turnstileMock.getName()).thenCallRealMethod();
+ assertEquals(null, turnstileMock.getName());
+ }
+
+ @Test
+ void testGetConfigurationPath() {
+ Turnstile turnstile = Utils.getComponentUnderTest(PATH_TURNSTILE, Turnstile.class, context);
+ assertEquals("managed", turnstile.getCloudServicePath());
+ Turnstile turnstileMock = Mockito.mock(Turnstile.class);
+ Mockito.when(turnstileMock.getName()).thenCallRealMethod();
+ assertEquals(null, turnstileMock.getName());
+ }
+
+ @Test
+ void testIsVisible() {
+ Turnstile turnstile = Utils.getComponentUnderTest(PATH_TURNSTILE, Turnstile.class, context);
+ assertEquals(true, turnstile.isVisible());
+ Turnstile turnstileMock = Mockito.mock(Turnstile.class);
+ Mockito.when(turnstileMock.isVisible()).thenCallRealMethod();
+ assertEquals(null, turnstileMock.isVisible());
+ }
+
+ @Test
+ void testIsEnabled() {
+ Turnstile turnstile = Utils.getComponentUnderTest(PATH_TURNSTILE, Turnstile.class, context);
+ assertEquals(true, turnstile.isEnabled());
+ Turnstile turnstileMock = Mockito.mock(Turnstile.class);
+ Mockito.when(turnstileMock.isEnabled()).thenCallRealMethod();
+ assertEquals(null, turnstileMock.isEnabled());
+ }
+
+ @Test
+ void testJSONExport() throws Exception {
+ Mockito.when(turnstileConfig.getSiteKey()).thenReturn("siteKey");
+ Mockito.when(turnstileConfig.getWidgetType()).thenReturn("invisible");
+ Mockito.when(turnstileConfig.getClientSideJsUrl()).thenReturn("https://challenges.cloudflare.com/turnstile/v0/api.js");
+ Turnstile turnstile = Utils.getComponentUnderTest(PATH_TURNSTILE, Turnstile.class, context);
+ Utils.testJSONExport(turnstile, Utils.getTestExporterJSONPath(BASE, PATH_TURNSTILE));
+ }
+
+ @Test
+ void turnstileConfigExceptionTest() throws GuideException {
+ Mockito.when(turnstileConfig.getSiteKey()).thenThrow(new GuideException("Error while fetching site key"));
+ Turnstile turnstile = Utils.getComponentUnderTest(PATH_TURNSTILE, Turnstile.class, context);
+ assertNotNull(turnstile.getCaptchaProperties());
+ }
+}
diff --git a/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v2/form/FormContainerImplTest.java b/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v2/form/FormContainerImplTest.java
index 35699fca0f..1b97c1327e 100644
--- a/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v2/form/FormContainerImplTest.java
+++ b/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v2/form/FormContainerImplTest.java
@@ -524,4 +524,10 @@ void testGetPropertiesForCoreComponentCustomPropertiesProviderForNull() throws E
Mockito.when(coreComponentCustomPropertiesProvider.getProperties()).thenReturn(null);
assertEquals("customPropValue", formContainer.getProperties().get("customProp"));
}
+
+ @Test
+ void testCustomFunctionUrl() throws Exception {
+ FormContainer formContainer = Utils.getComponentUnderTest(PATH_FORM_1, FormContainer.class, context);
+ assertEquals("/adobe/forms/af/customfunctions/L2NvbnRlbnQvZm9ybXMvYWYvZGVtbw==", formContainer.getCustomFunctionUrl());
+ }
}
diff --git a/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/servlets/ReviewDataSourceServletTest.java b/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/servlets/ReviewDataSourceServletTest.java
new file mode 100644
index 0000000000..2bcc46b70d
--- /dev/null
+++ b/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/servlets/ReviewDataSourceServletTest.java
@@ -0,0 +1,132 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ 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.servlets;
+
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.StreamSupport;
+
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.testing.mock.sling.servlet.MockRequestPathInfo;
+import org.apache.sling.testing.mock.sling.servlet.MockSlingHttpServletRequest;
+import org.apache.sling.testing.mock.sling.servlet.MockSlingHttpServletResponse;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import com.adobe.cq.export.json.SlingModelFilter;
+import com.adobe.cq.forms.core.context.FormsCoreComponentTestContext;
+import com.adobe.granite.ui.components.ExpressionResolver;
+import com.adobe.granite.ui.components.ds.DataSource;
+import com.day.cq.wcm.api.NameConstants;
+import com.day.cq.wcm.msm.api.MSMNameConstants;
+import io.wcm.testing.mock.aem.junit5.AemContext;
+import io.wcm.testing.mock.aem.junit5.AemContextExtension;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+@ExtendWith({ AemContextExtension.class, MockitoExtension.class })
+public class ReviewDataSourceServletTest {
+
+ public static final String RT_FD_FORM_REVIEW_DATASOURCE_V1 = "core/fd/components/form/review/v1/datasource";
+ private static final String TEST_BASE = "/form/review/datasource";
+ private static final String APPS_ROOT = "/apps";
+ private String componentInstancePath = "/apps/formcontainer/wizard/panel2/review";
+ private String componentInstancePath2 = "/apps/formcontainer2/panel2/review";
+
+ public final AemContext context = FormsCoreComponentTestContext.newAemContext();
+
+ @BeforeEach
+ public void setUp() {
+ context.load().json(TEST_BASE + FormsCoreComponentTestContext.TEST_CONTENT_JSON, APPS_ROOT);
+ context.registerService(SlingModelFilter.class, new SlingModelFilter() {
+
+ private final Set IGNORED_NODE_NAMES = new HashSet() {
+ {
+ add(NameConstants.NN_RESPONSIVE_CONFIG);
+ add(MSMNameConstants.NT_LIVE_SYNC_CONFIG);
+ add("cq:annotations");
+ }
+ };
+
+ @Override
+ public Map filterProperties(Map map) {
+ return map;
+ }
+
+ @Override
+ public Iterable filterChildResources(Iterable childResources) {
+ return StreamSupport
+ .stream(childResources.spliterator(), false)
+ .filter(r -> !IGNORED_NODE_NAMES.contains(r.getName()))
+ .collect(Collectors.toList());
+ }
+ });
+ }
+
+ @Test
+ public void testDoGet() {
+
+ context.currentResource("/apps");
+ ReviewDataSourceServlet reviewDataSourceServlet = new ReviewDataSourceServlet();
+ MockSlingHttpServletRequest request = context.request();
+ MockSlingHttpServletResponse response = context.response();
+ MockRequestPathInfo mockRequestPathInfo = (MockRequestPathInfo) request.getRequestPathInfo();
+ mockRequestPathInfo.setSuffix(componentInstancePath);
+ reviewDataSourceServlet.doGet(request, response);
+ DataSource dataSource = (com.adobe.granite.ui.components.ds.DataSource) request.getAttribute(DataSource.class.getName());
+ assertNotNull(dataSource);
+ Resource resource = dataSource.iterator().next();
+ assertEquals("Item 1", resource.getValueMap().get("text", String.class));
+ assertEquals("item_1", resource.getValueMap().get("value", String.class));
+
+ }
+
+ @Test
+ public void testFormContainerNull() {
+
+ context.currentResource("/apps");
+ ReviewDataSourceServlet reviewDataSourceServlet = new ReviewDataSourceServlet();
+ MockSlingHttpServletRequest request = context.request();
+ MockSlingHttpServletResponse response = context.response();
+ MockRequestPathInfo mockRequestPathInfo = (MockRequestPathInfo) request.getRequestPathInfo();
+ mockRequestPathInfo.setSuffix(componentInstancePath2);
+ reviewDataSourceServlet.doGet(request, response);
+ DataSource dataSource = (com.adobe.granite.ui.components.ds.DataSource) request.getAttribute(DataSource.class.getName());
+ assertNotNull(dataSource);
+ assertFalse(dataSource.iterator().hasNext());
+
+ }
+
+ @Mock
+ ExpressionResolver expressionResolver;
+
+ @InjectMocks
+ private ReviewDataSourceServlet reviewDataSourceServlet;
+
+ @Test
+ public void testGetExpressionResolver() {
+ context.currentResource("/apps");
+ ExpressionResolver expressionResolver = reviewDataSourceServlet.getExpressionResolver();
+ assertNotNull(expressionResolver);
+ }
+
+}
diff --git a/bundles/af-core/src/test/resources/form/formcontainer/exporter-formcontainerv2.json b/bundles/af-core/src/test/resources/form/formcontainer/exporter-formcontainerv2.json
index 1469dd47e3..3cd882cce8 100644
--- a/bundles/af-core/src/test/resources/form/formcontainer/exporter-formcontainerv2.json
+++ b/bundles/af-core/src/test/resources/form/formcontainer/exporter-formcontainerv2.json
@@ -16,7 +16,9 @@
"fd:schemaType": "BASIC",
"fd:formDataEnabled": true,
"customProp": "customPropValue",
- "fd:roleAttribute":"test-role-attribute"
+ "fd:roleAttribute":"test-role-attribute",
+ "fd:customFunctionsUrl": "/adobe/forms/af/customfunctions/L2NvbnRlbnQvZm9ybXMvYWYvZGVtbw==",
+ "fd:dataUrl": "/adobe/forms/af/data/L2NvbnRlbnQvZm9ybXMvYWYvZGVtbw=="
},
"events": {
"custom:setProperty": [
diff --git a/bundles/af-core/src/test/resources/form/review/datasource/test-content.json b/bundles/af-core/src/test/resources/form/review/datasource/test-content.json
new file mode 100644
index 0000000000..54c50522e2
--- /dev/null
+++ b/bundles/af-core/src/test/resources/form/review/datasource/test-content.json
@@ -0,0 +1,82 @@
+{
+ "formcontainer": {
+ "jcr:primaryType": "nt:unstructured",
+ "sling:resourceType": "core/fd/components/form/container/v2/container",
+ "dorType": "none",
+ "fd:version": "2.1",
+ "fieldType": "form",
+ "thankYouOption": "page",
+ "themeRef": "/libs/fd/af/themes/canvas",
+ "title": "test-review",
+ "wizard": {
+ "jcr:primaryType": "nt:unstructured",
+ "jcr:createdBy": "admin",
+ "jcr:title": "Wizard",
+ "jcr:lastModifiedBy": "admin",
+ "jcr:created": "Wed Aug 16 2023 11:21:29 GMT+0530",
+ "name": "wizard1692165089931",
+ "jcr:lastModified": "Wed Aug 16 2023 11:21:29 GMT+0530",
+ "sling:resourceType": "core/fd/components/form/wizard/v1/wizard",
+ "fieldType": "panel",
+ "panel1": {
+ "fieldType": "panel",
+ "jcr:primaryType": "nt:unstructured",
+ "jcr:title": "Item 1",
+ "layout": "responsiveGrid",
+ "name": "item_1",
+ "sling:resourceType": "core/fd/components/form/panelcontainer/v1/panelcontainer",
+ "textinput": {
+ "fieldType": "text-input",
+ "jcr:primaryType": "nt:unstructured",
+ "jcr:title": "Text Input",
+ "name": "textinput1692165103863",
+ "sling:resourceType": "core/fd/components/form/textinput/v1/textinput"
+ }
+ },
+ "panel2": {
+ "fieldType": "panel",
+ "jcr:primaryType": "nt:unstructured",
+ "jcr:title": "Item 2",
+ "layout": "responsiveGrid",
+ "name": "item_2",
+ "sling:resourceType": "core/fd/components/form/panelcontainer/v1/panelcontainer",
+ "review": {
+ "fd:editModeAction": "field",
+ "fieldType": "plain-text",
+ "hideTitle": "false",
+ "jcr:primaryType": "nt:unstructured",
+ "jcr:title": "Review",
+ "name": "textinput1692165103863",
+ "sling:resourceType": "core/fd/components/form/review/v1/review"
+ }
+ }
+ }
+ },
+ "formcontainer2": {
+ "jcr:primaryType": "nt:unstructured",
+ "sling:resourceType": "core/fd/components/form/container/v2/container",
+ "dorType": "none",
+ "fd:version": "2.1",
+ "fieldType": "form",
+ "thankYouOption": "page",
+ "themeRef": "/libs/fd/af/themes/canvas",
+ "title": "test-review",
+ "panel2": {
+ "fieldType": "panel",
+ "jcr:primaryType": "nt:unstructured",
+ "jcr:title": "Item 2",
+ "layout": "responsiveGrid",
+ "name": "item_2",
+ "sling:resourceType": "core/fd/components/form/panelcontainer/v1/panelcontainer",
+ "review": {
+ "fd:editModeAction": "field",
+ "fieldType": "plain-text",
+ "hideTitle": "false",
+ "jcr:primaryType": "nt:unstructured",
+ "jcr:title": "Review",
+ "name": "textinput1692165103863",
+ "sling:resourceType": "core/fd/components/form/review/v1/review"
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/bundles/af-core/src/test/resources/form/review/test-content.json b/bundles/af-core/src/test/resources/form/review/test-content.json
new file mode 100644
index 0000000000..3dc1d85acb
--- /dev/null
+++ b/bundles/af-core/src/test/resources/form/review/test-content.json
@@ -0,0 +1,68 @@
+{
+ "formcontainer": {
+ "jcr:primaryType": "nt:unstructured",
+ "sling:resourceType": "core/fd/components/form/container/v2/container",
+ "dorType": "none",
+ "fd:version": "2.1",
+ "fieldType": "form",
+ "thankYouOption": "page",
+ "themeRef": "/libs/fd/af/themes/canvas",
+ "title": "test-review",
+ "wizard": {
+ "jcr:primaryType": "nt:unstructured",
+ "jcr:createdBy": "admin",
+ "jcr:title": "Wizard",
+ "jcr:lastModifiedBy": "admin",
+ "jcr:created": "Wed Aug 16 2023 11:21:29 GMT+0530",
+ "name": "wizard1692165089931",
+ "jcr:lastModified": "Wed Aug 16 2023 11:21:29 GMT+0530",
+ "sling:resourceType": "core/fd/components/form/wizard/v1/wizard",
+ "fieldType": "panel",
+ "panel1": {
+ "fieldType": "panel",
+ "id": "panel1",
+ "jcr:primaryType": "nt:unstructured",
+ "jcr:title": "Item 1",
+ "layout": "responsiveGrid",
+ "name": "item_1",
+ "sling:resourceType": "core/fd/components/form/panelcontainer/v1/panelcontainer",
+ "textinput": {
+ "fieldType": "text-input",
+ "jcr:primaryType": "nt:unstructured",
+ "jcr:title": "Text Input",
+ "name": "textinput1692165103863",
+ "sling:resourceType": "core/fd/components/form/textinput/v1/textinput"
+ }
+ },
+ "panel2": {
+ "fieldType": "panel",
+ "id": "panel2",
+ "jcr:primaryType": "nt:unstructured",
+ "jcr:title": "Item 2",
+ "layout": "responsiveGrid",
+ "name": "item_2",
+ "sling:resourceType": "core/fd/components/form/panelcontainer/v1/panelcontainer",
+ "review" : {
+ "id": "review-c47310b241",
+ "jcr:primaryType": "nt:unstructured",
+ "sling:resourceType" : "core/fd/components/form/review/v1/review",
+ "name" : "review1691474151104",
+ "jcr:title" : "Review",
+ "description" : "test-description",
+ "fieldType" : "text",
+ "properties": {
+ "fd:path": "/apps/formcontainer/wizard/panel2/review",
+ "fd:linkedPanels": [
+ "item_1"
+ ],
+ "fd:editModeAction": "field"
+ },
+ "fd:linkedPanels": [
+ "item_1"
+ ],
+ "fd:editModeAction": "field"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/bundles/af-core/src/test/resources/form/turnstile/exporter-turnstile.json b/bundles/af-core/src/test/resources/form/turnstile/exporter-turnstile.json
new file mode 100644
index 0000000000..1afda00aa6
--- /dev/null
+++ b/bundles/af-core/src/test/resources/form/turnstile/exporter-turnstile.json
@@ -0,0 +1,37 @@
+{
+ "id": "turnstile-b4c0808e68",
+ "fieldType": "captcha",
+ "name": "turnstile1715230058257",
+ "visible": true,
+ "type": "string",
+ "required": true,
+ "enabled": true,
+ "readOnly": false,
+ "properties": {
+ "fd:dor": {
+ "dorExclusion": false
+ },
+ "fd:path": "/content/turnstile",
+ "fd:captcha": {
+ "config": {
+ "uri": "https://challenges.cloudflare.com/turnstile/v0/api.js",
+ "widgetType": "invisible",
+ "size": "normal",
+ "theme": "light"
+ }
+ }
+ },
+ "captchaProvider": "turnstile",
+ "captchaDisplayMode": "invisible",
+ "captchaSiteKey": "siteKey",
+ "label": {
+ "visible": true,
+ "value": "TURNSTILE"
+ },
+ "events": {
+ "custom:setProperty": [
+ "$event.payload"
+ ]
+ },
+ ":type": "core/fd/components/form/turnstile/v1/turnstile"
+}
diff --git a/bundles/af-core/src/test/resources/form/turnstile/test-content.json b/bundles/af-core/src/test/resources/form/turnstile/test-content.json
new file mode 100644
index 0000000000..17aa78a94e
--- /dev/null
+++ b/bundles/af-core/src/test/resources/form/turnstile/test-content.json
@@ -0,0 +1,22 @@
+{
+ "turnstile": {
+ "jcr:primaryType": "nt:unstructured",
+ "jcr:createdBy": "admin",
+ "jcr:title": "TURNSTILE",
+ "enabled": true,
+ "jcr:lastModifiedBy": "admin",
+ "readOnly": false,
+ "required": true,
+ "jcr:created": "Thu May 09 2024 10:17:37 GMT+0530",
+ "name": "turnstile1715230058257",
+ "size": "normal",
+ "cloudServicePath": "managed",
+ "visible": true,
+ "hideTitle": "false",
+ "jcr:lastModified": "Thu May 09 2024 10:18:00 GMT+0530",
+ "sling:resourceType": "core/fd/components/form/turnstile/v1/turnstile",
+ "fieldType": "captcha",
+ "textIsRich": "true",
+ "unboundFormElement": false
+ }
+}
diff --git a/bundles/core/src/main/java/com/adobe/cq/forms/core/components/internal/models/v1/formsportal/LinkImpl.java b/bundles/core/src/main/java/com/adobe/cq/forms/core/components/internal/models/v1/formsportal/LinkImpl.java
index 777c0021c6..825784ccfc 100644
--- a/bundles/core/src/main/java/com/adobe/cq/forms/core/components/internal/models/v1/formsportal/LinkImpl.java
+++ b/bundles/core/src/main/java/com/adobe/cq/forms/core/components/internal/models/v1/formsportal/LinkImpl.java
@@ -102,8 +102,7 @@ public String getAssetPathWithQueryParams() {
return "#";
}
try {
- URIBuilder uriBuilder = null;
- uriBuilder = new URIBuilder(url);
+ URIBuilder uriBuilder = new URIBuilder(url);
Map queryParams = getQueryParams();
if (queryParams != null && !uriBuilder.isPathEmpty()) {
for (String key : queryParams.keySet()) {
@@ -118,7 +117,7 @@ public String getAssetPathWithQueryParams() {
}
url = uriBuilder.build().toString();
} catch (URISyntaxException e) {
- logger.error("[FORMS] Link Component Failed to parse assetPath {}", url, e);
+ logger.warn("The [Forms] link component failed to process the asset path {}. Parameters will not be added to the URL.", url, e);
}
return url;
}
@@ -199,16 +198,28 @@ public Boolean accepts(LinkImpl link) {
@Override
public String processLink(LinkImpl link, SlingHttpServletRequest request) {
String givenPath = link.getAssetPath();
- String builtPath = givenPath + "/" + JcrConstants.JCR_CONTENT;
+ String encodedPath = encodePath(givenPath);
+ String builtPath = encodedPath + "/" + JcrConstants.JCR_CONTENT;
ResourceResolver resourceResolver = request.getResourceResolver();
- if (resourceResolver.getResource(builtPath) != null) {
+ if (resourceResolver.getResource(givenPath + "/" + JcrConstants.JCR_CONTENT) != null) {
Map params = link.getQueryParams();
if (AssetType.ADAPTIVE_FORM.equals(link.getAssetType()) && !params.containsKey(QP_AF_DEFAULT_MODE_KEY)) {
builtPath += "?" + QP_AF_DEFAULT_MODE_KEY + "=" + QP_AF_DEFAULT_MODE_VALUE;
}
- givenPath = builtPath;
+ encodedPath = builtPath;
+ }
+ return encodedPath;
+ }
+
+ private String encodePath(String path) {
+ try {
+ // Encode path to allow space in asset names using URIBuilder#setPath method
+ URIBuilder uriBuilder = new URIBuilder().setPath(path);
+ return uriBuilder.build().toString();
+ } catch (URISyntaxException e) {
+ logger.warn("The [Forms] link component failed to process the asset path {} due to invalid path.", path, e);
+ return path;
}
- return givenPath;
}
@Override
diff --git a/bundles/core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v1/formsportal/link/LinkImplTest.java b/bundles/core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v1/formsportal/link/LinkImplTest.java
index 74623ff9c5..113b0500df 100644
--- a/bundles/core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v1/formsportal/link/LinkImplTest.java
+++ b/bundles/core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v1/formsportal/link/LinkImplTest.java
@@ -40,6 +40,8 @@ public class LinkImplTest {
private static final String LINK1_PATH = ROOT_PAGE + "/linkcomponent-v1";
private static final String LINK1_PATH_WITH_INVALID_LINK = ROOT_PAGE + "/linkcomponent-v1-invalidref";
+ private static final String LINK2_PATH_WITH_SPACE_IN_ASSET_NAME = ROOT_PAGE + "/linkcomponent-v2-with-space-in-asset-name";
+
@BeforeEach
public void setUp() {
context.load().json(TEST_BASE + FormsCoreComponentTestContext.TEST_CONTENT_JSON, CONTENT_ROOT);
@@ -82,12 +84,22 @@ public void testLinkComponent() {
public void testLinkComponentWithInvalidPath() {
Link link = getLinkUnderTest(LINK1_PATH_WITH_INVALID_LINK);
Assertions.assertEquals("https://www.adobe.com/", link.getAssetPath());
- Assertions.assertEquals("https://www.adobe.com/?hello", link.getAssetPathWithQueryParams());
+ Assertions.assertEquals("/https://www.adobe.com/?hello", link.getAssetPathWithQueryParams());
Assertions.assertEquals("Link Component", link.getTitle());
Assertions.assertEquals("Some Hover Tooltip Text", link.getTooltip());
Assertions.assertEquals(Link.AssetType.ADAPTIVE_FORM, link.getAssetType());
}
+ @Test
+ public void testLinkComponentWithSpaceInAssetPath() {
+ Link link = getLinkUnderTest(LINK2_PATH_WITH_SPACE_IN_ASSET_NAME);
+ Assertions.assertEquals("/content/dam/formsanddocuments/sample form", link.getAssetPath());
+ Assertions.assertEquals("/content/dam/formsanddocuments/sample%20form?hello=world", link.getAssetPathWithQueryParams());
+ Assertions.assertEquals("Link Component", link.getTitle());
+ Assertions.assertEquals("Some Hover Tooltip Text", link.getTooltip());
+ Assertions.assertEquals(Link.AssetType.PDF, link.getAssetType());
+ }
+
@Test
public void testMainInterface() {
Link linkMock = Mockito.mock(Link.class);
diff --git a/bundles/core/src/test/resources/link/test-content.json b/bundles/core/src/test/resources/link/test-content.json
index 06ae876ddf..830053e7e2 100644
--- a/bundles/core/src/test/resources/link/test-content.json
+++ b/bundles/core/src/test/resources/link/test-content.json
@@ -111,6 +111,21 @@
"title" : "Link Component",
"sling:resourceType" : "core/fd/components/formsportal/link/v2/link",
"assetType" : "Others"
+ },
+ "linkcomponent-v2-with-space-in-asset-name":{
+ "tooltip" : "Some Hover Tooltip Text",
+ "pdfPath" : "/content/dam/formsanddocuments/sample form",
+ "title" : "Link Component",
+ "sling:resourceType" : "core/fd/components/formsportal/link/v2/link",
+ "assetType" : "PDF",
+ "queryParams":{
+ "jcr:primaryType" : "nt:unstructured",
+ "item0":{
+ "jcr:primaryType" : "nt:unstructured",
+ "key" : "hello",
+ "value" : "world"
+ }
+ }
}
},
"dam" : {
@@ -128,4 +143,4 @@
}
}
}
-}
\ No newline at end of file
+}
diff --git a/examples/ui.apps/src/main/content/jcr_root/apps/forms-components-examples/components/form/review/.content.xml b/examples/ui.apps/src/main/content/jcr_root/apps/forms-components-examples/components/form/review/.content.xml
new file mode 100644
index 0000000000..a8d3af2ba6
--- /dev/null
+++ b/examples/ui.apps/src/main/content/jcr_root/apps/forms-components-examples/components/form/review/.content.xml
@@ -0,0 +1,7 @@
+
+
diff --git a/examples/ui.apps/src/main/content/jcr_root/apps/forms-components-examples/components/form/review/_cq_template.xml b/examples/ui.apps/src/main/content/jcr_root/apps/forms-components-examples/components/form/review/_cq_template.xml
new file mode 100644
index 0000000000..1eed759959
--- /dev/null
+++ b/examples/ui.apps/src/main/content/jcr_root/apps/forms-components-examples/components/form/review/_cq_template.xml
@@ -0,0 +1,7 @@
+
+
diff --git a/examples/ui.content/src/main/content/jcr_root/content/core-components-examples/library/adaptive-form/turnstile/.content.xml b/examples/ui.content/src/main/content/jcr_root/content/core-components-examples/library/adaptive-form/turnstile/.content.xml
new file mode 100644
index 0000000000..333978ce8e
--- /dev/null
+++ b/examples/ui.content/src/main/content/jcr_root/content/core-components-examples/library/adaptive-form/turnstile/.content.xml
@@ -0,0 +1,156 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/clientlibs/custom-forms-components-runtime-all/.content.xml b/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/clientlibs/custom-forms-components-runtime-all/.content.xml
index c862537b53..1b446738e2 100644
--- a/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/clientlibs/custom-forms-components-runtime-all/.content.xml
+++ b/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/clientlibs/custom-forms-components-runtime-all/.content.xml
@@ -5,4 +5,4 @@
cssProcessor="[default:none,min:none]"
jsProcessor="[default:none,min:none]"
categories="[core.forms.components.it.runtime.all]"
- embed="[core.forms.components.runtime.base,core.forms.components.it.container.v1.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.v2.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.it.textinput.v1.runtime, core.forms.components.hcaptcha.v1.runtime]"/>
+ embed="[core.forms.components.runtime.base,core.forms.components.it.container.v1.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.v2.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.it.textinput.v1.runtime, core.forms.components.hcaptcha.v1.runtime, core.forms.components.turnstile.v1.runtime]"/>
diff --git a/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/clientlibs/svg-selector/.content.xml b/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/clientlibs/svg-selector/.content.xml
new file mode 100755
index 0000000000..f9297f711f
--- /dev/null
+++ b/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/clientlibs/svg-selector/.content.xml
@@ -0,0 +1,5 @@
+
+
diff --git a/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/clientlibs/svg-selector/css.txt b/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/clientlibs/svg-selector/css.txt
new file mode 100755
index 0000000000..d59b7cc13b
--- /dev/null
+++ b/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/clientlibs/svg-selector/css.txt
@@ -0,0 +1,19 @@
+###############################################################################
+# 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=styles
+
+site.css
diff --git a/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/clientlibs/svg-selector/js.txt b/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/clientlibs/svg-selector/js.txt
new file mode 100755
index 0000000000..f2d5bc8b2e
--- /dev/null
+++ b/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/clientlibs/svg-selector/js.txt
@@ -0,0 +1,17 @@
+###############################################################################
+# 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
+functions.js
diff --git a/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/clientlibs/svg-selector/js/functions.js b/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/clientlibs/svg-selector/js/functions.js
new file mode 100755
index 0000000000..ae571c5d16
--- /dev/null
+++ b/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/clientlibs/svg-selector/js/functions.js
@@ -0,0 +1,57 @@
+/*******************************************************************************
+ * 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 () {
+
+ let toggleColour = function (element, checked) {
+ element.setAttribute("fill", checked ? "#bee8f6" : "#ffffff");
+ };
+
+ let handleFormInitialization = function(event) {
+ let formContainerView = event.detail;
+ let formElement = formContainerView.getFormElement();
+ let svgImageComponents = formElement.querySelectorAll('[data-cmp-is="adaptiveFormSvg"]');
+ let handleSvgImage = function(svgImage) {
+ let svgContainerModelId = svgImage.closest('[data-cmp-is="adaptiveFormPanel"]').getAttribute("id");
+ let svgContainerModel = formContainerView.getModel(svgContainerModelId);
+ let svgSelectorModel = svgContainerModel.parent;
+ let accessibleContainerModel = svgSelectorModel.items[1];
+ accessibleContainerModel.items.forEach((fieldModel) => {
+ if (fieldModel.properties['svg-linked-checkbox']) {
+ let fieldName = fieldModel.name;
+ svgImage.querySelectorAll('[data-svg-af-field="' + fieldName + '"]').forEach((svgPathField) => {
+ svgPathField.addEventListener("click", (clickEvent) => {
+ fieldModel.checked = !fieldModel.checked;
+ });
+ fieldModel.subscribe((action) => {
+ let changes = action.payload.changes;
+ changes.forEach(change => {
+ if (change.propertyName === 'checked') {
+ toggleColour(svgPathField, fieldModel.checked);
+ }
+ });
+ });
+ });
+ }
+ });
+ };
+ svgImageComponents.forEach(handleSvgImage);
+ };
+
+ document.addEventListener("AF_FormContainerInitialised", handleFormInitialization);
+
+})();
diff --git a/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/clientlibs/svg-selector/styles/site.css b/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/clientlibs/svg-selector/styles/site.css
new file mode 100755
index 0000000000..44bda83bb6
--- /dev/null
+++ b/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/clientlibs/svg-selector/styles/site.css
@@ -0,0 +1,19 @@
+/*******************************************************************************
+ * 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.
+ ******************************************************************************/
+
+[data-svg-af-field] {
+ cursor: pointer;
+}
\ No newline at end of file
diff --git a/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/componentDef/svg/.content.xml b/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/componentDef/svg/.content.xml
new file mode 100755
index 0000000000..a0ac99e384
--- /dev/null
+++ b/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/componentDef/svg/.content.xml
@@ -0,0 +1,3 @@
+
+
diff --git a/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/componentDef/svg/v1/.content.xml b/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/componentDef/svg/v1/.content.xml
new file mode 100755
index 0000000000..44256e1862
--- /dev/null
+++ b/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/componentDef/svg/v1/.content.xml
@@ -0,0 +1,5 @@
+
+
+
+
diff --git a/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/componentDef/svg/v1/svg/.content.xml b/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/componentDef/svg/v1/svg/.content.xml
new file mode 100755
index 0000000000..3a6b88ae06
--- /dev/null
+++ b/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/componentDef/svg/v1/svg/.content.xml
@@ -0,0 +1,7 @@
+
+
diff --git a/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/componentDef/svg/v1/svg/README.md b/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/componentDef/svg/v1/svg/README.md
new file mode 100755
index 0000000000..da016b1ebd
--- /dev/null
+++ b/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/componentDef/svg/v1/svg/README.md
@@ -0,0 +1,57 @@
+
+Adaptive Form Svg (v1)
+====
+Image component written in HTL that renders an adaptive image.
+
+## Features
+
+### Use Object
+
+
+### Component Policy Configuration Properties
+The following configuration properties are used:
+
+1.`./value` svg paths definition
+2.`./height` height of svg
+3.`./width` width of svg
+4.`./jcr:title` title of svg
+
+
+## BEM Description
+```
+BLOCK cmp-svg-image-container
+ ELEMENT cmp-svg-image
+```
+
+## Client Libraries
+
+
+## JavaScript Data Attribute Bindings
+
+The following attributes must be added for the initialization of the image component in the form view:
+ 1. `data-cmp-is="adaptiveFormSvg"`
+
+
+
+## Replace feature:
+
+
+## Information
+* **Vendor**: Adobe
+* **Version**: v1
+* **Compatibility**: Cloud
+* **Status**: production-ready
\ No newline at end of file
diff --git a/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/componentDef/svg/v1/svg/_cq_dialog/.content.xml b/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/componentDef/svg/v1/svg/_cq_dialog/.content.xml
new file mode 100755
index 0000000000..2d02db5e5b
--- /dev/null
+++ b/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/componentDef/svg/v1/svg/_cq_dialog/.content.xml
@@ -0,0 +1,70 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/componentDef/svg/v1/svg/_cq_editConfig.xml b/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/componentDef/svg/v1/svg/_cq_editConfig.xml
new file mode 100755
index 0000000000..7db8f452dc
--- /dev/null
+++ b/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/componentDef/svg/v1/svg/_cq_editConfig.xml
@@ -0,0 +1,7 @@
+
+
diff --git a/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/componentDef/svg/v1/svg/_cq_template/.content.xml b/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/componentDef/svg/v1/svg/_cq_template/.content.xml
new file mode 100755
index 0000000000..b8dc61d4fd
--- /dev/null
+++ b/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/componentDef/svg/v1/svg/_cq_template/.content.xml
@@ -0,0 +1,4 @@
+
+
diff --git a/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/componentDef/svg/v1/svg/icon.png b/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/componentDef/svg/v1/svg/icon.png
new file mode 100755
index 0000000000..436b271a35
Binary files /dev/null and b/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/componentDef/svg/v1/svg/icon.png differ
diff --git a/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/componentDef/svg/v1/svg/svg.html b/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/componentDef/svg/v1/svg/svg.html
new file mode 100755
index 0000000000..94f5edb258
--- /dev/null
+++ b/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/componentDef/svg/v1/svg/svg.html
@@ -0,0 +1,21 @@
+
+
+
+
+
diff --git a/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/componentDef/svgSelector/.content.xml b/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/componentDef/svgSelector/.content.xml
new file mode 100755
index 0000000000..a0ac99e384
--- /dev/null
+++ b/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/componentDef/svgSelector/.content.xml
@@ -0,0 +1,3 @@
+
+
diff --git a/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/componentDef/svgSelector/v1/.content.xml b/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/componentDef/svgSelector/v1/.content.xml
new file mode 100755
index 0000000000..792c7a7e42
--- /dev/null
+++ b/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/componentDef/svgSelector/v1/.content.xml
@@ -0,0 +1,5 @@
+
+
+
+
diff --git a/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/componentDef/svgSelector/v1/svgSelector/.content.xml b/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/componentDef/svgSelector/v1/svgSelector/.content.xml
new file mode 100755
index 0000000000..ace19bb85b
--- /dev/null
+++ b/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/componentDef/svgSelector/v1/svgSelector/.content.xml
@@ -0,0 +1,9 @@
+
+
diff --git a/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/componentDef/svgSelector/v1/svgSelector/_cq_template/.content.xml b/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/componentDef/svgSelector/v1/svgSelector/_cq_template/.content.xml
new file mode 100755
index 0000000000..c0c7063414
--- /dev/null
+++ b/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/componentDef/svgSelector/v1/svgSelector/_cq_template/.content.xml
@@ -0,0 +1,5 @@
+
+
diff --git a/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/components/svg/.content.xml b/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/components/svg/.content.xml
new file mode 100755
index 0000000000..bc48808e6e
--- /dev/null
+++ b/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/components/svg/.content.xml
@@ -0,0 +1,9 @@
+
+
diff --git a/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/components/svgSelector/.content.xml b/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/components/svgSelector/.content.xml
new file mode 100755
index 0000000000..5a30fe1e8e
--- /dev/null
+++ b/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/components/svgSelector/.content.xml
@@ -0,0 +1,9 @@
+
+
diff --git a/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/components/svgSelector/_cq_template/.content.xml b/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/components/svgSelector/_cq_template/.content.xml
new file mode 100755
index 0000000000..fe8387b3a5
--- /dev/null
+++ b/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/components/svgSelector/_cq_template/.content.xml
@@ -0,0 +1,68 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/form/turnstile/.content.xml b/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/form/turnstile/.content.xml
new file mode 100644
index 0000000000..3431ffc5e5
--- /dev/null
+++ b/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/form/turnstile/.content.xml
@@ -0,0 +1,7 @@
+
+
diff --git a/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/form/turnstile/_cq_template.xml b/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/form/turnstile/_cq_template.xml
new file mode 100644
index 0000000000..c55ac31fef
--- /dev/null
+++ b/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/form/turnstile/_cq_template.xml
@@ -0,0 +1,7 @@
+
+
diff --git a/it/config/src/main/content/META-INF/vault/filter.xml b/it/config/src/main/content/META-INF/vault/filter.xml
index c1342e11ad..db6833e6df 100644
--- a/it/config/src/main/content/META-INF/vault/filter.xml
+++ b/it/config/src/main/content/META-INF/vault/filter.xml
@@ -5,4 +5,5 @@
+
diff --git a/it/config/src/main/content/jcr_root/apps/system/config/com.adobe.granite.toggle.impl.dev.DynamicToggleProviderImpl.cfg.json b/it/config/src/main/content/jcr_root/apps/system/config/com.adobe.granite.toggle.impl.dev.DynamicToggleProviderImpl.cfg.json
index 682f52b610..8842ce8f0c 100644
--- a/it/config/src/main/content/jcr_root/apps/system/config/com.adobe.granite.toggle.impl.dev.DynamicToggleProviderImpl.cfg.json
+++ b/it/config/src/main/content/jcr_root/apps/system/config/com.adobe.granite.toggle.impl.dev.DynamicToggleProviderImpl.cfg.json
@@ -20,6 +20,7 @@
"FT_FORMS-14545",
"FT_SITES-19631",
"FT_FORMS-14255",
- "FT_FORMS-14068"
+ "FT_FORMS-14068",
+ "FT_FORMS-16351"
]
}
diff --git a/it/config/src/main/content/jcr_root/apps/system/config/org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.amended~headlessreplication.cfg.json b/it/config/src/main/content/jcr_root/apps/system/config/org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.amended~headlessreplication.cfg.json
new file mode 100644
index 0000000000..e3585f27f6
--- /dev/null
+++ b/it/config/src/main/content/jcr_root/apps/system/config/org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.amended~headlessreplication.cfg.json
@@ -0,0 +1,5 @@
+{
+ "user.mapping": [
+ "com.adobe.aem.core-forms-components-it-tests-core:core-components-it-replication-sub-service=[replication-service]"
+ ]
+}
\ No newline at end of file
diff --git a/it/content/src/main/content/META-INF/vault/filter.xml b/it/content/src/main/content/META-INF/vault/filter.xml
index 3c0ff06e21..c6dfb60772 100644
--- a/it/content/src/main/content/META-INF/vault/filter.xml
+++ b/it/content/src/main/content/META-INF/vault/filter.xml
@@ -12,4 +12,5 @@
+
diff --git a/it/content/src/main/content/jcr_root/conf/core-components-it/samples/recaptcha/basic/settings/cloudconfigs/recaptcha/entcheckbox/.content.xml b/it/content/src/main/content/jcr_root/conf/core-components-it/samples/recaptcha/basic/settings/cloudconfigs/recaptcha/entcheckbox/.content.xml
index 3e2b39c5c2..05aa120ee5 100644
--- a/it/content/src/main/content/jcr_root/conf/core-components-it/samples/recaptcha/basic/settings/cloudconfigs/recaptcha/entcheckbox/.content.xml
+++ b/it/content/src/main/content/jcr_root/conf/core-components-it/samples/recaptcha/basic/settings/cloudconfigs/recaptcha/entcheckbox/.content.xml
@@ -12,7 +12,7 @@
sling:resourceType="fd/af/cloudservices/recaptcha/page"
enterpriseVerifyUrl="https://recaptchaenterprise.googleapis.com/"
keyType="checkbox"
- name="entCheckbox"
+ name="entcheckbox"
projectId="aem-forms-internal"
secretKey="\{ee8e90ea55c4fbc7675fb8e9ffd7cea858e5536f0789198aabc40a3295f278f59f30e4325aa5ad93124380c962198c4fbf6fe3f86ac979fa64da7d99f47b24fb}"
siteKey="6LfaMOkpAAAAAK9ooBLOcnDO84EE7UTTgBKA6e3d"
diff --git a/it/content/src/main/content/jcr_root/conf/core-components-it/samples/recaptcha/basic/settings/cloudconfigs/recaptcha/entscore/.content.xml b/it/content/src/main/content/jcr_root/conf/core-components-it/samples/recaptcha/basic/settings/cloudconfigs/recaptcha/entscore/.content.xml
index cdc833c4cf..f7a36e04b0 100644
--- a/it/content/src/main/content/jcr_root/conf/core-components-it/samples/recaptcha/basic/settings/cloudconfigs/recaptcha/entscore/.content.xml
+++ b/it/content/src/main/content/jcr_root/conf/core-components-it/samples/recaptcha/basic/settings/cloudconfigs/recaptcha/entscore/.content.xml
@@ -12,7 +12,7 @@
sling:resourceType="fd/af/cloudservices/recaptcha/page"
enterpriseVerifyUrl="https://recaptchaenterprise.googleapis.com/"
keyType="score"
- name="entScore"
+ name="entscore"
projectId="aem-forms-internal"
secretKey="\{ee8e90ea55c4fbc7675fb8e9ffd7cea858e5536f0789198aabc40a3295f278f59f30e4325aa5ad93124380c962198c4fbf6fe3f86ac979fa64da7d99f47b24fb}"
siteKey="6LfKIukpAAAAAFabx1W7ve1hWDKXBD92oHtGb9j6"
diff --git a/it/content/src/main/content/jcr_root/conf/core-components-it/settings/cloudconfigs/turnstile/.content.xml b/it/content/src/main/content/jcr_root/conf/core-components-it/settings/cloudconfigs/turnstile/.content.xml
new file mode 100644
index 0000000000..a0ac99e384
--- /dev/null
+++ b/it/content/src/main/content/jcr_root/conf/core-components-it/settings/cloudconfigs/turnstile/.content.xml
@@ -0,0 +1,3 @@
+
+
diff --git a/it/content/src/main/content/jcr_root/conf/core-components-it/settings/cloudconfigs/turnstile/invisible/.content.xml b/it/content/src/main/content/jcr_root/conf/core-components-it/settings/cloudconfigs/turnstile/invisible/.content.xml
new file mode 100644
index 0000000000..53c61ce32a
--- /dev/null
+++ b/it/content/src/main/content/jcr_root/conf/core-components-it/settings/cloudconfigs/turnstile/invisible/.content.xml
@@ -0,0 +1,18 @@
+
+
+
+
diff --git a/it/content/src/main/content/jcr_root/conf/core-components-it/settings/cloudconfigs/turnstile/managed/.content.xml b/it/content/src/main/content/jcr_root/conf/core-components-it/settings/cloudconfigs/turnstile/managed/.content.xml
new file mode 100644
index 0000000000..14e72741af
--- /dev/null
+++ b/it/content/src/main/content/jcr_root/conf/core-components-it/settings/cloudconfigs/turnstile/managed/.content.xml
@@ -0,0 +1,18 @@
+
+
+
+
diff --git a/it/content/src/main/content/jcr_root/conf/core-components-it/settings/wcm/policies/.content.xml b/it/content/src/main/content/jcr_root/conf/core-components-it/settings/wcm/policies/.content.xml
index 952cdfc6ef..4f8efcff26 100755
--- a/it/content/src/main/content/jcr_root/conf/core-components-it/settings/wcm/policies/.content.xml
+++ b/it/content/src/main/content/jcr_root/conf/core-components-it/settings/wcm/policies/.content.xml
@@ -1,5 +1,5 @@
-
@@ -465,7 +465,7 @@
sling:resourceType="wcm/core/components/policy/policy"
ampMode="pairedAmp"
appResourcesClientlib="cmp-examples.site"
- clientlibs="[forms-it.base,forms-it.custom-locale]"
+ clientlibs="[forms-it.base,forms-it.custom-locale,svg-selector]"
clientlibsAsync="true">
@@ -543,6 +543,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/it/content/src/main/content/jcr_root/conf/core-components-it/settings/wcm/templates/blank/.content.xml b/it/content/src/main/content/jcr_root/conf/core-components-it/settings/wcm/templates/blank/.content.xml
new file mode 100755
index 0000000000..c8b3462873
--- /dev/null
+++ b/it/content/src/main/content/jcr_root/conf/core-components-it/settings/wcm/templates/blank/.content.xml
@@ -0,0 +1,11 @@
+
+
+
+
diff --git a/it/content/src/main/content/jcr_root/conf/core-components-it/settings/wcm/templates/blank/initial/.content.xml b/it/content/src/main/content/jcr_root/conf/core-components-it/settings/wcm/templates/blank/initial/.content.xml
new file mode 100755
index 0000000000..71b92695f2
--- /dev/null
+++ b/it/content/src/main/content/jcr_root/conf/core-components-it/settings/wcm/templates/blank/initial/.content.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
diff --git a/it/content/src/main/content/jcr_root/conf/core-components-it/settings/wcm/templates/blank/policies/.content.xml b/it/content/src/main/content/jcr_root/conf/core-components-it/settings/wcm/templates/blank/policies/.content.xml
new file mode 100755
index 0000000000..149237c63a
--- /dev/null
+++ b/it/content/src/main/content/jcr_root/conf/core-components-it/settings/wcm/templates/blank/policies/.content.xml
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/it/content/src/main/content/jcr_root/conf/core-components-it/settings/wcm/templates/blank/structure/.content.xml b/it/content/src/main/content/jcr_root/conf/core-components-it/settings/wcm/templates/blank/structure/.content.xml
new file mode 100755
index 0000000000..9e7bad1001
--- /dev/null
+++ b/it/content/src/main/content/jcr_root/conf/core-components-it/settings/wcm/templates/blank/structure/.content.xml
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/samples/embed/customactiontesting/.content.xml b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/samples/embed/customactiontesting/.content.xml
new file mode 100644
index 0000000000..d7cdfee32c
--- /dev/null
+++ b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/samples/embed/customactiontesting/.content.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/samples/ruleeditor/navigate-in-panel/.content.xml b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/samples/ruleeditor/navigate-in-panel/.content.xml
new file mode 100644
index 0000000000..290e53b989
--- /dev/null
+++ b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/samples/ruleeditor/navigate-in-panel/.content.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/samples/ruleeditor/navigate-in-panel/basic/.content.xml b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/samples/ruleeditor/navigate-in-panel/basic/.content.xml
new file mode 100644
index 0000000000..73f2fabd0b
--- /dev/null
+++ b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/samples/ruleeditor/navigate-in-panel/basic/.content.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/samples/ruleeditor/navigate-in-panel/blank/.content.xml b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/samples/ruleeditor/navigate-in-panel/blank/.content.xml
new file mode 100644
index 0000000000..fb45b800fa
--- /dev/null
+++ b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/samples/ruleeditor/navigate-in-panel/blank/.content.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/samples/testsvg/.content.xml b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/samples/testsvg/.content.xml
new file mode 100755
index 0000000000..83d6e32776
--- /dev/null
+++ b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/samples/testsvg/.content.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/samples/turnstile/.content.xml b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/samples/turnstile/.content.xml
new file mode 100644
index 0000000000..ea2e55bfa7
--- /dev/null
+++ b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/samples/turnstile/.content.xml
@@ -0,0 +1,9 @@
+
+
+
+
diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/samples/turnstile/basic/.content.xml b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/samples/turnstile/basic/.content.xml
new file mode 100644
index 0000000000..f87b064c12
--- /dev/null
+++ b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/samples/turnstile/basic/.content.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/samples/turnstile/invisible/.content.xml b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/samples/turnstile/invisible/.content.xml
new file mode 100644
index 0000000000..8af952bdaf
--- /dev/null
+++ b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/samples/turnstile/invisible/.content.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/samples/turnstile/managed/.content.xml b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/samples/turnstile/managed/.content.xml
new file mode 100644
index 0000000000..5b6b4741b8
--- /dev/null
+++ b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/samples/turnstile/managed/.content.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
diff --git a/it/content/src/main/content/jcr_root/content/forms/af/core-components-it/samples/button/buttonv1/basic/.content.xml b/it/content/src/main/content/jcr_root/content/forms/af/core-components-it/samples/button/buttonv1/basic/.content.xml
index c5784e7477..8af2ebd4f2 100644
--- a/it/content/src/main/content/jcr_root/content/forms/af/core-components-it/samples/button/buttonv1/basic/.content.xml
+++ b/it/content/src/main/content/jcr_root/content/forms/af/core-components-it/samples/button/buttonv1/basic/.content.xml
@@ -76,6 +76,25 @@
jcr:primaryType="nt:unstructured"
click="[navigateTo('https://www.google.com'\, '_newwindow')]"/>
+
+
+
+
diff --git a/it/content/src/main/content/jcr_root/content/forms/af/core-components-it/samples/checkboxgroup/basic/.content.xml b/it/content/src/main/content/jcr_root/content/forms/af/core-components-it/samples/checkboxgroup/basic/.content.xml
index 9dc2b02920..2b87557fc7 100755
--- a/it/content/src/main/content/jcr_root/content/forms/af/core-components-it/samples/checkboxgroup/basic/.content.xml
+++ b/it/content/src/main/content/jcr_root/content/forms/af/core-components-it/samples/checkboxgroup/basic/.content.xml
@@ -175,6 +175,22 @@
type="number[]"
unboundFormElement="{Boolean}false"
visible="{Boolean}true"/>
+
+
+
diff --git a/it/content/src/main/content/jcr_root/content/forms/af/core-components-it/samples/embed/customactiontesting/.content.xml b/it/content/src/main/content/jcr_root/content/forms/af/core-components-it/samples/embed/customactiontesting/.content.xml
new file mode 100644
index 0000000000..772727aa42
--- /dev/null
+++ b/it/content/src/main/content/jcr_root/content/forms/af/core-components-it/samples/embed/customactiontesting/.content.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
diff --git a/it/content/src/main/content/jcr_root/content/forms/af/core-components-it/samples/radiobutton/basic/.content.xml b/it/content/src/main/content/jcr_root/content/forms/af/core-components-it/samples/radiobutton/basic/.content.xml
index 3908394709..62063519b1 100755
--- a/it/content/src/main/content/jcr_root/content/forms/af/core-components-it/samples/radiobutton/basic/.content.xml
+++ b/it/content/src/main/content/jcr_root/content/forms/af/core-components-it/samples/radiobutton/basic/.content.xml
@@ -213,6 +213,22 @@
type="number[]"
unboundFormElement="{Boolean}false"
visible="{Boolean}true"/>
+
+
+
\ No newline at end of file
diff --git a/it/content/src/main/content/jcr_root/content/forms/af/core-components-it/samples/review/.content.xml b/it/content/src/main/content/jcr_root/content/forms/af/core-components-it/samples/review/.content.xml
new file mode 100644
index 0000000000..bbf62ed22c
--- /dev/null
+++ b/it/content/src/main/content/jcr_root/content/forms/af/core-components-it/samples/review/.content.xml
@@ -0,0 +1,5 @@
+
+
diff --git a/it/content/src/main/content/jcr_root/content/forms/af/core-components-it/samples/review/basic/.content.xml b/it/content/src/main/content/jcr_root/content/forms/af/core-components-it/samples/review/basic/.content.xml
new file mode 100644
index 0000000000..529f03f020
--- /dev/null
+++ b/it/content/src/main/content/jcr_root/content/forms/af/core-components-it/samples/review/basic/.content.xml
@@ -0,0 +1,402 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/it/content/src/main/content/jcr_root/content/forms/af/core-components-it/samples/review/repeatability/.content.xml b/it/content/src/main/content/jcr_root/content/forms/af/core-components-it/samples/review/repeatability/.content.xml
new file mode 100644
index 0000000000..800fad8ec4
--- /dev/null
+++ b/it/content/src/main/content/jcr_root/content/forms/af/core-components-it/samples/review/repeatability/.content.xml
@@ -0,0 +1,390 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/it/content/src/main/content/jcr_root/content/forms/af/core-components-it/samples/ruleeditor/basic/.content.xml b/it/content/src/main/content/jcr_root/content/forms/af/core-components-it/samples/ruleeditor/basic/.content.xml
index c4fcf4fd80..94f0869c82 100644
--- a/it/content/src/main/content/jcr_root/content/forms/af/core-components-it/samples/ruleeditor/basic/.content.xml
+++ b/it/content/src/main/content/jcr_root/content/forms/af/core-components-it/samples/ruleeditor/basic/.content.xml
@@ -1,60 +1,106 @@
+ jcr:primaryType="cq:Page">
+ cq:deviceGroups="[/etc/mobile/groups/responsive]"
+ cq:lastModified="{Date}2024-10-23T06:16:03.852Z"
+ cq:lastModifiedBy="admin"
+ cq:template="/conf/core-components-examples/settings/wcm/templates/af-blank-v2"
+ jcr:language="en"
+ jcr:primaryType="cq:PageContent"
+ jcr:title="basic"
+ sling:configRef="/conf/forms/core-components-it/samples/ruleeditor/basic/"
+ sling:resourceType="forms-components-examples/components/page">
-
+ sling:resourceType="forms-components-examples/components/form/container"
+ clientLibRef="corecomponent.it.customfunction,corecomponent.it.customfunction2"
+ dorType="none"
+ fieldType="form"
+ schemaType="none"
+ textIsRich="true"
+ thankYouMessage="Thank you for submitting the form."
+ thankYouOption="page"
+ themeRef="/libs/fd/af/themes/canvas"
+ title="basic">
+
+
+
+
+
+
+
+
+
-
+
\ No newline at end of file
diff --git a/it/content/src/main/content/jcr_root/content/forms/af/core-components-it/samples/ruleeditor/navigate-in-panel/.content.xml b/it/content/src/main/content/jcr_root/content/forms/af/core-components-it/samples/ruleeditor/navigate-in-panel/.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/ruleeditor/navigate-in-panel/.content.xml
@@ -0,0 +1,3 @@
+
+
diff --git a/it/content/src/main/content/jcr_root/content/forms/af/core-components-it/samples/ruleeditor/navigate-in-panel/basic/.content.xml b/it/content/src/main/content/jcr_root/content/forms/af/core-components-it/samples/ruleeditor/navigate-in-panel/basic/.content.xml
new file mode 100644
index 0000000000..b360bf824e
--- /dev/null
+++ b/it/content/src/main/content/jcr_root/content/forms/af/core-components-it/samples/ruleeditor/navigate-in-panel/basic/.content.xml
@@ -0,0 +1,611 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/it/content/src/main/content/jcr_root/content/forms/af/core-components-it/samples/ruleeditor/navigate-in-panel/blank/.content.xml b/it/content/src/main/content/jcr_root/content/forms/af/core-components-it/samples/ruleeditor/navigate-in-panel/blank/.content.xml
new file mode 100644
index 0000000000..79292d6e30
--- /dev/null
+++ b/it/content/src/main/content/jcr_root/content/forms/af/core-components-it/samples/ruleeditor/navigate-in-panel/blank/.content.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
diff --git a/it/content/src/main/content/jcr_root/content/forms/af/core-components-it/samples/testsvg/.content.xml b/it/content/src/main/content/jcr_root/content/forms/af/core-components-it/samples/testsvg/.content.xml
new file mode 100755
index 0000000000..8b9b24a8ec
--- /dev/null
+++ b/it/content/src/main/content/jcr_root/content/forms/af/core-components-it/samples/testsvg/.content.xml
@@ -0,0 +1,489 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/it/content/src/main/content/jcr_root/content/forms/af/core-components-it/samples/turnstile/.content.xml b/it/content/src/main/content/jcr_root/content/forms/af/core-components-it/samples/turnstile/.content.xml
new file mode 100644
index 0000000000..3edadd6c63
--- /dev/null
+++ b/it/content/src/main/content/jcr_root/content/forms/af/core-components-it/samples/turnstile/.content.xml
@@ -0,0 +1,5 @@
+
+
diff --git a/it/content/src/main/content/jcr_root/content/forms/af/core-components-it/samples/turnstile/basic/.content.xml b/it/content/src/main/content/jcr_root/content/forms/af/core-components-it/samples/turnstile/basic/.content.xml
new file mode 100644
index 0000000000..8999bca137
--- /dev/null
+++ b/it/content/src/main/content/jcr_root/content/forms/af/core-components-it/samples/turnstile/basic/.content.xml
@@ -0,0 +1,71 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/it/content/src/main/content/jcr_root/content/forms/af/core-components-it/samples/turnstile/invisible/.content.xml b/it/content/src/main/content/jcr_root/content/forms/af/core-components-it/samples/turnstile/invisible/.content.xml
new file mode 100644
index 0000000000..6318dc1f19
--- /dev/null
+++ b/it/content/src/main/content/jcr_root/content/forms/af/core-components-it/samples/turnstile/invisible/.content.xml
@@ -0,0 +1,71 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/it/content/src/main/content/jcr_root/content/forms/af/core-components-it/samples/turnstile/managed/.content.xml b/it/content/src/main/content/jcr_root/content/forms/af/core-components-it/samples/turnstile/managed/.content.xml
new file mode 100644
index 0000000000..1196a59e27
--- /dev/null
+++ b/it/content/src/main/content/jcr_root/content/forms/af/core-components-it/samples/turnstile/managed/.content.xml
@@ -0,0 +1,71 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/it/content/src/main/content/jcr_root/content/forms/sites/core-components-it/site-with-captcha-inline-form/.content.xml b/it/content/src/main/content/jcr_root/content/forms/sites/core-components-it/site-with-captcha-inline-form/.content.xml
new file mode 100644
index 0000000000..a225e5b6d8
--- /dev/null
+++ b/it/content/src/main/content/jcr_root/content/forms/sites/core-components-it/site-with-captcha-inline-form/.content.xml
@@ -0,0 +1,86 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/it/content/src/main/content/jcr_root/content/forms/sites/core-components-it/site-with-turnstile-afv2-form/.content.xml b/it/content/src/main/content/jcr_root/content/forms/sites/core-components-it/site-with-turnstile-afv2-form/.content.xml
new file mode 100644
index 0000000000..b4205fa962
--- /dev/null
+++ b/it/content/src/main/content/jcr_root/content/forms/sites/core-components-it/site-with-turnstile-afv2-form/.content.xml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/it/content/src/main/content/jcr_root/etc/.content.xml b/it/content/src/main/content/jcr_root/etc/.content.xml
new file mode 100755
index 0000000000..1581aa3428
--- /dev/null
+++ b/it/content/src/main/content/jcr_root/etc/.content.xml
@@ -0,0 +1,4 @@
+
+
diff --git a/it/content/src/main/content/jcr_root/etc/replication/.content.xml b/it/content/src/main/content/jcr_root/etc/replication/.content.xml
new file mode 100755
index 0000000000..54909b7586
--- /dev/null
+++ b/it/content/src/main/content/jcr_root/etc/replication/.content.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
diff --git a/it/content/src/main/content/jcr_root/etc/replication/agents.author/.content.xml b/it/content/src/main/content/jcr_root/etc/replication/agents.author/.content.xml
new file mode 100755
index 0000000000..5580192f8c
--- /dev/null
+++ b/it/content/src/main/content/jcr_root/etc/replication/agents.author/.content.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
diff --git a/it/content/src/main/content/jcr_root/etc/replication/agents.author/corecomponentsit/.content.xml b/it/content/src/main/content/jcr_root/etc/replication/agents.author/corecomponentsit/.content.xml
new file mode 100755
index 0000000000..e55cdbc22e
--- /dev/null
+++ b/it/content/src/main/content/jcr_root/etc/replication/agents.author/corecomponentsit/.content.xml
@@ -0,0 +1,19 @@
+
+
+
+
diff --git a/it/core/pom.xml b/it/core/pom.xml
index 462a280e46..3ef2cf1334 100644
--- a/it/core/pom.xml
+++ b/it/core/pom.xml
@@ -85,6 +85,8 @@
<_metatypeannotations>*
javax.annotation;version=0.0.0,
+ com.adobe.cq.forms.core.components.models.form;version="[1.0.0,10.0.0)",
+ io.jsonwebtoken;resolution:=optional;version="[0.0.0,1.0.0)",
*
@@ -156,6 +158,24 @@ Import-Package: javax.annotation;version=0.0.0,*
com.adobe.aemaem-forms-sdk-api
+
+ com.adobe.aem
+ core-forms-components-af-core
+ 3.0.70
+
+
+
+ io.jsonwebtoken
+ jjwt-api
+ 0.11.2
+ provided
+
+
+ io.jsonwebtoken
+ jjwt-impl
+ 0.11.2
+ provided
+
diff --git a/it/core/src/main/java/com/adobe/cq/forms/core/components/it/service/HeadlessTransportHandler.java b/it/core/src/main/java/com/adobe/cq/forms/core/components/it/service/HeadlessTransportHandler.java
new file mode 100644
index 0000000000..33acd89eb1
--- /dev/null
+++ b/it/core/src/main/java/com/adobe/cq/forms/core/components/it/service/HeadlessTransportHandler.java
@@ -0,0 +1,236 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ 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.it.service;
+
+import com.adobe.cq.forms.core.components.models.form.FormStructureParser;
+import com.adobe.cq.forms.core.components.util.ComponentUtils;
+import com.day.cq.replication.AgentConfig;
+import com.day.cq.replication.ReplicationAction;
+import com.day.cq.replication.ReplicationException;
+import com.day.cq.replication.ReplicationResult;
+import com.day.cq.replication.ReplicationTransaction;
+import com.day.cq.replication.TransportContext;
+import com.day.cq.replication.TransportHandler;
+import com.day.cq.wcm.api.NameConstants;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.http.client.config.RequestConfig;
+import org.apache.http.client.methods.HttpDelete;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.client.methods.HttpRequestBase;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
+import org.apache.http.osgi.services.HttpClientBuilderFactory;
+import org.apache.sling.api.resource.LoginException;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.resource.ResourceResolverFactory;
+import org.apache.sling.serviceusermapping.ServiceUserMapped;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Deactivate;
+import org.osgi.service.component.annotations.Reference;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.Function;
+
+/**
+ * Agent needs to be configured as per this, https://medium.com/@toimrank/aem-transporthandler-e761accaec51
+ * https://blog.developer.adobe.com/reimagining-replication-agents-on-aem-as-a-cloud-service-a4437b7eeb60
+ */
+
+@Component(
+ service = TransportHandler.class,
+ property = {
+ "service.ranking:Integer=1000"
+ }
+)
+public class HeadlessTransportHandler implements TransportHandler {
+
+ // todo: embedding credentials in source code risks unauthorized access
+ private static final String CLIENT_ID = "your_client_id";
+ private static final String CLIENT_SECRET = "your_client_secret";
+
+ private static final Logger LOG = LoggerFactory.getLogger(HeadlessTransportHandler.class);
+ private static final Map AUTH;
+ private final static String URI = "corecomponentsitheadless";
+ /**
+ * The Sling ServiceUserMapper service allows for mapping Service IDs comprised of the Service
+ * Names defined by the providing bundles and optional Subservice Name to ResourceResolver and/or
+ * JCR Repository user IDs. This mapping is configurable such that system administrators are in
+ * full control of assigning users to services. cf. http://sling.apache.org/documentation/the-sling-engine/service-authentication.html#implementation
+ */
+ private final static String USER_MAPPED_SUB_SERVICE_NAME = "core-components-it-replication-sub-service";
+
+ static {
+ AUTH = new HashMap<>();
+ // name of subservice, this is part of ui.config
+ AUTH.put(ResourceResolverFactory.SUBSERVICE, USER_MAPPED_SUB_SERVICE_NAME);
+ }
+
+ @Reference
+ private HttpClientBuilderFactory clientBuilderFactory;
+ @Reference
+ private ResourceResolverFactory resourceResolverFactory;
+
+ private CloseableHttpClient httpClient;
+
+ @Activate
+ protected void activate() {
+ PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager();
+ connectionManager.setDefaultMaxPerRoute(100);
+ connectionManager.setMaxTotal(100);
+
+ httpClient = clientBuilderFactory.newBuilder()
+ .setConnectionManager(connectionManager)
+ .setDefaultRequestConfig(RequestConfig.custom()
+ .setConnectTimeout(30000)
+ .setSocketTimeout(30000)
+ .setConnectionRequestTimeout(30000)
+ .build())
+ .build();
+ }
+
+ @Deactivate
+ protected void deactivate() {
+ try {
+ httpClient.close();
+ } catch (IOException ex) {
+ LOG.warn("[HeadlessTransportHandler] Failed to release http client: {}", ex.getMessage(), ex);
+ }
+ }
+
+ @Override
+ public boolean canHandle(AgentConfig agentConfig) {
+ return StringUtils.equals(agentConfig.getTransportURI(), URI);
+ // for oauth 2, hence commenting this
+ //&& StringUtils.isNotEmpty(agentConfig.getTransportUser())
+ // && StringUtils.isNotEmpty(agentConfig.getTransportPassword());
+
+ }
+
+ @Override
+ public ReplicationResult deliver(TransportContext transportContext, ReplicationTransaction replicationTransaction)
+ throws ReplicationException {
+ ReplicationAction action = replicationTransaction.getAction();
+ Function requestSupplier;
+
+ switch (action.getType()) {
+ case ACTIVATE:
+ requestSupplier = HttpPost::new;
+ break;
+ case DEACTIVATE:
+ case DELETE:
+ requestSupplier = HttpDelete::new;
+ break;
+ default:
+ LOG.debug("[HeadlessTransportHandler] Unsupported replication action type: {}", action);
+ return new ReplicationResult(true, 405, "Method Not Allowed");
+ }
+
+ AgentConfig agentConfig = transportContext.getConfig();
+ /*
+ String transportUri = agentConfig.getTransportURI();
+ String transportAuth = agentConfig.getTransportUser() + ':' + agentConfig.getTransportPassword();
+ byte[] encodedAuth = Base64.encodeBase64(transportAuth.getBytes(StandardCharsets.ISO_8859_1));
+ Header authHeader = new BasicHeader(HttpHeaders.AUTHORIZATION, "Basic " + new String(encodedAuth));
+ Function authenticatedRequestSupplier = uri -> {
+ HttpRequestBase request = requestSupplier.apply(uri);
+ request.addHeader(authHeader);
+ return request;
+ }; */
+
+ try (ResourceResolver resourceResolver = resourceResolverFactory.getServiceResourceResolver(AUTH)) {
+ for (String path : action.getPaths()) {
+ Resource resource = resourceResolver.getResource(path);
+ if (resource == null || (!resource.isResourceType(NameConstants.NT_PAGE))) {
+ LOG.info("[HeadlessTransportHandler] Resource not found or not a cq:Page {}. Skipping", path);
+ continue;
+ }
+ // get the model json from the resource
+ FormStructureParser parser = getFormStructureParserFromPage(resource);
+ if (parser != null) {
+ String formModelJson = parser.getFormDefinition();
+ // todo: publish this form model json to the external system
+ LOG.info("[HeadlessTransportHandler] Form Model JSON: {}", formModelJson);
+ /**
+ PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager();
+ connectionManager.setDefaultMaxPerRoute(100);
+ connectionManager.setMaxTotal(100);
+
+ CloseableHttpClient httpClient = HttpClients.custom()
+ .setConnectionManager(connectionManager)
+ .setDefaultRequestConfig(RequestConfig.custom()
+ .setConnectTimeout(30000)
+ .setSocketTimeout(30000)
+ .setConnectionRequestTimeout(30000)
+ .build())
+ .build();
+
+ OAuth2Client oauth2Client = new OAuth2Client(
+ "https://example.com/oauth2/token",
+ "your_client_id",
+ "your_private_key",
+ "your_certificate_thumbprint",
+ "your_resource_uri",
+ httpClient
+ );
+ oauth2Client.publishOrDeleteFormModelJson(formModelJson, "https://example.com/api/publish", HttpPost::new);
+ **/
+ } else {
+ LOG.info("[HeadlessTransportHandler] No adaptive form container found for resource {}. Skipping", resource.getPath());
+ }
+ }
+ return ReplicationResult.OK;
+ } catch (LoginException /*| IOException */ ex) {
+ throw new ReplicationException("Failed to get delivery url for: " + action, ex);
+ }
+ }
+
+ private static FormStructureParser getFormStructureParserFromPage(Resource resource) {
+ if (resource == null) {
+ LOG.info("[HeadlessTransportHandler] Resource is null. Skipping");
+ return null;
+ }
+
+ if (ComponentUtils.isAFContainer(resource)) {
+ FormStructureParser parser = resource.adaptTo(FormStructureParser.class);
+ if (parser != null) {
+ return parser;
+ } else {
+ LOG.info("[HeadlessTransportHandler] Form structure parser not found for form container resource {}. Skipping", resource.getPath());
+ return null;
+ }
+ }
+
+ for (Resource child : resource.getChildren()) {
+ FormStructureParser parser = getFormStructureParserFromPage(child);
+ if (parser != null) {
+ return parser;
+ }
+ }
+ return null;
+ }
+
+ private static class NotOk extends IOException {
+ NotOk(int status) {
+ super("status code = " + status);
+ }
+ }
+}
diff --git a/it/core/src/main/java/com/adobe/cq/forms/core/components/it/service/OAuth2Client.java b/it/core/src/main/java/com/adobe/cq/forms/core/components/it/service/OAuth2Client.java
new file mode 100644
index 0000000000..78ff020fe2
--- /dev/null
+++ b/it/core/src/main/java/com/adobe/cq/forms/core/components/it/service/OAuth2Client.java
@@ -0,0 +1,194 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ 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.it.service;
+
+import java.io.IOException;
+import java.security.KeyFactory;
+import java.security.PrivateKey;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.util.Base64;
+import java.util.Date;
+import java.util.concurrent.locks.ReentrantLock;
+
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.client.methods.HttpRequestBase;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.util.EntityUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.json.Json;
+import javax.json.JsonObject;
+import javax.json.JsonReader;
+import java.io.StringReader;
+import java.util.function.Function;
+
+// these bundles are not present on local cloud ready sdk
+// to make this work, you have to download/install 0.11.2 from here, https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-api/0.11.2
+// making the import optional
+import io.jsonwebtoken.Jwts;
+import io.jsonwebtoken.SignatureAlgorithm;
+
+/**
+ * Uses the OAuth2 Client Credentials flow with a signed JWT (client assertion) for certificate-based authentication.
+ * The token request payload includes grant_type, client_id, client_assertion_type, client_assertion, and resource.
+ * The signed JWT includes claims such as iss, sub, aud, jti, iat, and exp.
+ * The JWT is signed using a private key and includes a certificate thumbprint in the header.
+ *
+ * Uses a signed JWT (client assertion) with a private key and certificate thumbprint.
+ * Provides enhanced security by using certificate-based authentication and a signed JWT.
+ */
+public class OAuth2Client {
+ private static final Logger LOG = LoggerFactory.getLogger(OAuth2Client.class);
+
+ private final String tokenEndpoint;
+ private final String clientId;
+ private final String privateKey;
+ private final String certificateThumbprint;
+ private final String resource;
+ private final CloseableHttpClient httpClient;
+
+ private String accessToken;
+ private long tokenExpirationTime;
+ private final ReentrantLock lock = new ReentrantLock();
+
+ public OAuth2Client(String tokenEndpoint, String clientId, String privateKey, String certificateThumbprint, String resource, CloseableHttpClient httpClient) {
+ this.tokenEndpoint = tokenEndpoint;
+ this.clientId = clientId;
+ this.privateKey = privateKey;
+ this.certificateThumbprint = certificateThumbprint;
+ this.resource = resource;
+ this.httpClient = httpClient;
+ }
+
+ public void publishOrDeleteFormModelJson(String formModelJson, String apiEndpoint, Function requestSupplier) throws IOException {
+ String token = getValidToken();
+ HttpRequestBase request = requestSupplier.apply(apiEndpoint);
+ request.setHeader("Authorization", "Bearer " + token);
+ request.setHeader("Content-Type", "application/json");
+ if (request instanceof HttpPost) {
+ ((HttpPost) request).setEntity(new StringEntity(formModelJson));
+ }
+
+ try (CloseableHttpResponse response = httpClient.execute(request)) {
+ if (response.getStatusLine().getStatusCode() == 401) {
+ // Token expired, refresh and retry
+ token = refreshOAuth2Token();
+ request.setHeader("Authorization", "Bearer " + token);
+ try (CloseableHttpResponse retryResponse = httpClient.execute(request)) {
+ if (retryResponse.getStatusLine().getStatusCode() != 200) {
+ throw new NotOk(retryResponse.getStatusLine().getStatusCode());
+ }
+ }
+ } else if (response.getStatusLine().getStatusCode() != 200) {
+ throw new NotOk(response.getStatusLine().getStatusCode());
+ }
+ }
+ }
+
+ private String getValidToken() throws IOException {
+ lock.lock();
+ try {
+ if (accessToken == null || System.currentTimeMillis() >= tokenExpirationTime) {
+ accessToken = fetchOAuth2Token();
+ }
+ return accessToken;
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ private String fetchOAuth2Token() throws IOException {
+ HttpPost post = new HttpPost(tokenEndpoint);
+ post.setHeader("Content-Type", "application/x-www-form-urlencoded");
+
+ String clientAssertion = generateClientAssertion();
+
+ String payload = "grant_type=client_credentials&client_id=" + clientId + "&client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer&client_assertion=" + clientAssertion + "&resource=" + resource;
+
+ post.setEntity(new StringEntity(payload));
+
+ try (CloseableHttpResponse response = httpClient.execute(post)) {
+ if (response.getStatusLine().getStatusCode() == 200) {
+ String responseBody = EntityUtils.toString(response.getEntity());
+ return parseToken(responseBody);
+ } else {
+ throw new NotOk(response.getStatusLine().getStatusCode());
+ }
+ }
+ }
+
+ private String refreshOAuth2Token() throws IOException {
+ return fetchOAuth2Token(); // Assuming the same flow for refresh token
+ }
+
+ private String parseToken(String responseBody) {
+ try (JsonReader jsonReader = Json.createReader(new StringReader(responseBody))) {
+ JsonObject jsonObject = jsonReader.readObject();
+ long expiresIn = jsonObject.getJsonNumber("expires_in").longValue();
+ tokenExpirationTime = System.currentTimeMillis() + (expiresIn * 1000) - 60000; // 1 minute buffer
+ return jsonObject.getString("access_token");
+ }
+ }
+
+ private String generateClientAssertion() {
+ long nowMillis = System.currentTimeMillis();
+ Date now = new Date(nowMillis);
+
+ // Create the JWT claims
+ JsonObject claims = Json.createObjectBuilder()
+ .add("iss", clientId)
+ .add("sub", clientId)
+ .add("aud", tokenEndpoint)
+ .add("jti", java.util.UUID.randomUUID().toString())
+ .add("iat", nowMillis / 1000)
+ .add("exp", (nowMillis / 1000) + 300) // 5 minutes expiration
+ .build();
+
+ // Create the JWT header
+ JsonObject header = Json.createObjectBuilder()
+ .add("alg", "RS256")
+ .add("x5t", certificateThumbprint)
+ .build();
+
+ // Sign the JWT
+ return Jwts.builder()
+ .setHeaderParam("x5t", certificateThumbprint)
+ .setClaims(claims)
+ .setHeaderParam("typ", "JWT")
+ .signWith(SignatureAlgorithm.RS256, getPrivateKey())
+ .compact();
+ }
+
+ private PrivateKey getPrivateKey() {
+ try {
+ byte[] keyBytes = Base64.getDecoder().decode(privateKey);
+ PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes);
+ KeyFactory kf = KeyFactory.getInstance("RSA");
+ return kf.generatePrivate(spec);
+ } catch (Exception e) {
+ throw new RuntimeException("Failed to load private key", e);
+ }
+ }
+
+ private static class NotOk extends IOException {
+ NotOk(int status) {
+ super("status code = " + status);
+ }
+ }
+}
\ No newline at end of file
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 99d071211e..0ef50cf14f 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]"/>
+ 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]"/>
diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/accordion/v1/accordion/clientlibs/commons/js/common.js b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/accordion/v1/accordion/clientlibs/commons/js/common.js
index ff44304bcf..977f8b7faa 100644
--- a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/accordion/v1/accordion/clientlibs/commons/js/common.js
+++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/accordion/v1/accordion/clientlibs/commons/js/common.js
@@ -42,11 +42,13 @@
static cssClasses = {
button: {
disabled: "cmp-accordion__button--disabled",
- expanded: "cmp-accordion__button--expanded"
+ expanded: "cmp-accordion__button--expanded",
+ stepped: "cmp-accordion__button--stepped"
},
panel: {
hidden: "cmp-accordion__panel--hidden",
- expanded: "cmp-accordion__panel--expanded"
+ expanded: "cmp-accordion__panel--expanded",
+ stepped: "cmp-accordion__panel--stepped"
}
};
@@ -239,6 +241,11 @@
item.removeAttribute(this.constructor.dataAttributes.item.expanded);
var button = this.getCachedButtons()[index];
var panel = this.getCachedPanels()[index];
+ if (button.classList.contains(this.constructor.cssClasses.button.expanded)) {
+ // Only apply `stepped` class if the tab was previously expanded
+ button.classList.add(this.constructor.cssClasses.button.stepped);
+ panel.classList.add(this.constructor.cssClasses.panel.stepped);
+ }
button.classList.remove(this.constructor.cssClasses.button.expanded);
// used to fix some known screen readers issues in reading the correct state of the 'aria-expanded' attribute
// e.g. https://bugs.webkit.org/show_bug.cgi?id=210934
@@ -250,8 +257,9 @@
panel.setAttribute("aria-hidden", true);
}
}
- }
+ };
}
+
window.Forms = window.Forms || {};
window.Forms.CoreComponentsCommons = window.Forms.CoreComponentsCommons || {};
diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/accordion/v1/accordion/clientlibs/site/js/accordionview.js b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/accordion/v1/accordion/clientlibs/site/js/accordionview.js
index a73d45d652..4ae139799e 100644
--- a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/accordion/v1/accordion/clientlibs/site/js/accordionview.js
+++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/accordion/v1/accordion/clientlibs/site/js/accordionview.js
@@ -258,6 +258,12 @@
}
this.expandItem(itemDivToExpand);
this.collapseAllOtherItems(itemDivToExpand.id);
+
+ const cachedItems = this.getCachedItems();
+ const newIndex = cachedItems.indexOf(itemDivToExpand);
+ this.getCachedButtons()[newIndex].classList.remove(this.constructor.cssClasses.button.stepped);
+ this.getCachedPanels()[newIndex].classList.remove(this.constructor.cssClasses.panel.stepped);
+
this.#showHideRepeatableButtons(childView.getInstanceManager());
}
diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/button/v1/button/clientlibs/site/js/buttonview.js b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/button/v1/button/clientlibs/site/js/buttonview.js
index 5f274ceaca..dc7512fc63 100644
--- a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/button/v1/button/clientlibs/site/js/buttonview.js
+++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/button/v1/button/clientlibs/site/js/buttonview.js
@@ -30,6 +30,7 @@
static selectors = {
self: "[data-" + this.NS + '-is="' + this.IS + '"]',
widget: `.${Button.bemBlock}__widget`,
+ label: `.${Button.bemBlock}__text`,
description: `.${Button.bemBlock}__longdescription`,
qm: `.${Button.bemBlock}__questionmark`,
tooltipDiv: `.${Button.bemBlock}__shortdescription`
@@ -40,7 +41,7 @@
}
getLabel() {
- return null;
+ return this.element.querySelector(Button.selectors.label);
}
getWidget() {
diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/checkboxgroup/v1/checkboxgroup/clientlibs/site/js/checkboxgroupview.js b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/checkboxgroup/v1/checkboxgroup/clientlibs/site/js/checkboxgroupview.js
index 39c6ce71e2..ee342d89b8 100644
--- a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/checkboxgroup/v1/checkboxgroup/clientlibs/site/js/checkboxgroupview.js
+++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/checkboxgroup/v1/checkboxgroup/clientlibs/site/js/checkboxgroupview.js
@@ -112,10 +112,13 @@
}
updateValidity(validity) {
- const valid = validity.valid ? validity.valid : false;
- let widgets = this.widget;
- this.element.setAttribute(FormView.Constants.DATA_ATTRIBUTE_VALID, valid);
- widgets.forEach(widget => widget.setAttribute(FormView.Constants.ARIA_INVALID, !valid));
+ if(validity.valid === undefined) {
+ this.element.removeAttribute(FormView.Constants.DATA_ATTRIBUTE_VALID);
+ this.widget.forEach(widget => widget.removeAttribute(FormView.Constants.ARIA_INVALID));
+ } else {
+ this.element.setAttribute(FormView.Constants.DATA_ATTRIBUTE_VALID, validity.valid);
+ this.widget.forEach(widget => widget.setAttribute(FormView.Constants.ARIA_INVALID, !validity.valid));
+ }
}
updateValue(modelValue) {
diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/radiobutton/v1/radiobutton/clientlibs/site/js/radiobuttonview.js b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/radiobutton/v1/radiobutton/clientlibs/site/js/radiobuttonview.js
index e58e0c1563..99f2f04f9f 100644
--- a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/radiobutton/v1/radiobutton/clientlibs/site/js/radiobuttonview.js
+++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/radiobutton/v1/radiobutton/clientlibs/site/js/radiobuttonview.js
@@ -120,10 +120,13 @@
}
updateValidity(validity) {
- const valid = validity.valid ? validity.valid : false;
- let widgets = this.widget;
- this.element.setAttribute(FormView.Constants.DATA_ATTRIBUTE_VALID, valid);
- widgets.forEach(widget => widget.setAttribute(FormView.Constants.ARIA_INVALID, !valid));
+ if(validity.valid === undefined) {
+ this.element.removeAttribute(FormView.Constants.DATA_ATTRIBUTE_VALID);
+ this.widget.forEach(widget => widget.removeAttribute(FormView.Constants.ARIA_INVALID));
+ } else {
+ this.element.setAttribute(FormView.Constants.DATA_ATTRIBUTE_VALID, validity.valid);
+ this.widget.forEach(widget => widget.setAttribute(FormView.Constants.ARIA_INVALID, !validity.valid));
+ }
}
updateValue(modelValue) {
diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/review/.content.xml b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/review/.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/review/.content.xml
@@ -0,0 +1,3 @@
+
+
diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/review/v1/.content.xml b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/review/v1/.content.xml
new file mode 100644
index 0000000000..4551dc306a
--- /dev/null
+++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/review/v1/.content.xml
@@ -0,0 +1,5 @@
+
+
+
+
diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/review/v1/review/.content.xml b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/review/v1/review/.content.xml
new file mode 100644
index 0000000000..40bea36bd4
--- /dev/null
+++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/review/v1/review/.content.xml
@@ -0,0 +1,7 @@
+
+
diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/review/v1/review/README.md b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/review/v1/review/README.md
new file mode 100644
index 0000000000..e848248ad5
--- /dev/null
+++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/review/v1/review/README.md
@@ -0,0 +1,59 @@
+
+Adaptive Form Review (v1)
+====
+Adaptive Form Review field component written in HTL.
+
+## Features
+
+* Provides the review all Components/Panel:
+* Styles
+
+### Use Object
+The Form Review component uses the `com.adobe.cq.forms.core.components.models.form.Review` Sling Model for its Use-object.
+
+### Edit Dialog Properties
+The following properties are written to JCR for this Form Review 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
+3. `./fd:linkedPanels` - defines linked panels for reviewing the panel. If no panel is linked, the component will review the entire form.
+3. `./fd:editModeAction` - defines the edit action for editing the fields, panels, field & panel, or none
+## Client Libraries
+The component provides a `core.forms.components.review.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.review.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-review
+ ELEMENT cmp-adaptiveform-review__container
+ ELEMENT cmp-adaptiveform-review__panel
+ MODIFIER cmp-adaptiveform-review__panel--repeatable
+ ELEMENT cmp-adaptiveform-review__label-container
+ ELEMENT cmp-adaptiveform-review__label
+ ELEMENT cmp-adaptiveform-review__edit-button
+ ELEMENT cmp-adaptiveform-review__value
+ ELEMENT cmp-adaptiveform-review__field
+ ELEMENT cmp-adaptiveform-review__label
+ ELEMENT cmp-adaptiveform-review__value
+ ELEMENT cmp-adaptiveform-review__edit-button
+ ELEMENT cmp-adaptiveform-review__text
+ ELEMENT cmp-adaptiveform-review__label
+```
\ No newline at end of file
diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/review/v1/review/_cq_dialog/.content.xml b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/review/v1/review/_cq_dialog/.content.xml
new file mode 100644
index 0000000000..b2de838672
--- /dev/null
+++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/review/v1/review/_cq_dialog/.content.xml
@@ -0,0 +1,124 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/review/v1/review/_cq_template.xml b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/review/v1/review/_cq_template.xml
new file mode 100644
index 0000000000..5c1a29ae2e
--- /dev/null
+++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/review/v1/review/_cq_template.xml
@@ -0,0 +1,5 @@
+
+
diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/review/v1/review/clientlibs/.content.xml b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/review/v1/review/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/review/v1/review/clientlibs/.content.xml
@@ -0,0 +1,3 @@
+
+
diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/review/v1/review/clientlibs/site/.content.xml b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/review/v1/review/clientlibs/site/.content.xml
new file mode 100644
index 0000000000..5279ed95aa
--- /dev/null
+++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/review/v1/review/clientlibs/site/.content.xml
@@ -0,0 +1,5 @@
+
+
diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/review/v1/review/clientlibs/site/css.txt b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/review/v1/review/clientlibs/site/css.txt
new file mode 100644
index 0000000000..1b9eacb50e
--- /dev/null
+++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/review/v1/review/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
+reviewview.css
diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/review/v1/review/clientlibs/site/css/reviewview.css b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/review/v1/review/clientlibs/site/css/reviewview.css
new file mode 100644
index 0000000000..cbb445de86
--- /dev/null
+++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/review/v1/review/clientlibs/site/css/reviewview.css
@@ -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.
+ ******************************************************************************/
+
+ .cmp-adaptiveform-review {
+
+ }
+ .cmp-adaptiveform-review__container {
+ }
+ .cmp-adaptiveform-review__panel{
+ }
+ .cmp-adaptiveform-review__label-container{
+ }
+
+ .cmp-adaptiveform-review__field{
+ }
+ .cmp-adaptiveform-review__label{
+ }
+ .cmp-adaptiveform-review__value{
+ }
+ .cmp-adaptiveform-review__edit-button{
+ }
\ No newline at end of file
diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/review/v1/review/clientlibs/site/js.txt b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/review/v1/review/clientlibs/site/js.txt
new file mode 100644
index 0000000000..9cdd050115
--- /dev/null
+++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/review/v1/review/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
+reviewview.js
diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/review/v1/review/clientlibs/site/js/reviewview.js b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/review/v1/review/clientlibs/site/js/reviewview.js
new file mode 100644
index 0000000000..2e12c323bb
--- /dev/null
+++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/review/v1/review/clientlibs/site/js/reviewview.js
@@ -0,0 +1,333 @@
+/*******************************************************************************
+ * 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 Review 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 = "adaptiveFormReview";
+ static bemBlock = 'cmp-adaptiveform-review';
+ static templateAttribute = 'data-cmp-review';
+ static DATA_ATTRIBUTE_VISIBLE = 'data-cmp-visible';
+ static FIELD_TYPE = FormView.Constants.FIELD_TYPE;
+ static HIDE_FIELD_FROM_REVIEW = [Review.FIELD_TYPE.BUTTON, Review.FIELD_TYPE.PLAIN_TEXT, Review.FIELD_TYPE.CAPTCHA, Review.FIELD_TYPE.IMAGE];
+ static panelModifier = '__panel--repeatable';
+ static fieldModifier = '__field--';
+ static selectors = {
+ self: "[data-" + this.NS + '-is="' + this.IS + '"]',
+ container: `.${Review.bemBlock}__container`,
+ templates: "template[data-cmp-review-fieldType]",
+ label: '[' + Review.templateAttribute + '-label]',
+ value: '[' + Review.templateAttribute + '-value]',
+ editButton:'[' + Review.templateAttribute + '-editButton]',
+ fieldId: Review.templateAttribute + '-fieldId',
+ fieldType: Review.templateAttribute + '-fieldType',
+ panelContent:'[' + Review.templateAttribute + '-content]',
+ labelContainer: `.${Review.bemBlock}__label-container`,
+ intersection: Review.templateAttribute + "-intersection="
+ };
+ static intersectionOptions = {
+ root: null,
+ rootMargin: "0px",
+ threshold: Review.buildThresholdList(),
+ };
+ static buildThresholdList() {
+ let thresholds = [];
+ let numSteps = 20;
+ for (let i = 1.0; i <= numSteps; i++) {
+ thresholds.push(i / numSteps);
+ }
+ thresholds.push(0);
+ return thresholds;
+ }
+
+ static sanitizedValue(value, options) {
+ if(options){
+ return window.DOMPurify ? window.DOMPurify.sanitize(value, options) : value;
+ }
+ return window.DOMPurify ? window.DOMPurify.sanitize(value) : value;
+ }
+
+ static hasChild(element) {
+ return element && element.children && element.children.length;
+ }
+
+ static addClassModifier(element, item) {
+ if(item.fieldType !== Review.FIELD_TYPE.PANEL){
+ element.querySelector('div').classList.add(Review.bemBlock + Review.fieldModifier + item.fieldType);
+ }
+ if(item.repeatable){
+ element.querySelector('div').classList.add(Review.bemBlock + Review.panelModifier);
+ }
+ if(item.name){
+ element.querySelector('div').classList.add(item.name);
+ }
+ }
+
+ static renderLabel(element, item) {
+ const label = element.querySelector(Review.selectors.label);
+ label.innerHTML = item.fieldType === Review.FIELD_TYPE.PLAIN_TEXT ? Review.sanitizedValue(item.value) : Review.sanitizedValue(item?.label?.value, {ALLOWED_TAGS: [] });
+ if (item.required) {
+ label.setAttribute('data-cmp-required', item.required);
+ }
+ }
+
+ static renderValue(element, item) {
+ const value = element.querySelector(Review.selectors.value);
+ if (value) {
+ const plainText = Review.getValue(item, item.value) || '';
+ value.innerHTML = Review.sanitizedValue(plainText);
+ }
+ }
+
+ static addAccessibilityAttributes(element, item) {
+ const container = element.querySelector('div');
+ const label = element.querySelector(Review.selectors.label);
+ const value = element.querySelector(Review.selectors.value);
+ const editButton = element.querySelector(Review.selectors.editButton);
+ const id = `${item.id}-review-label`;
+ if(label){
+ label.setAttribute('id', id);
+ container.setAttribute('role', 'region');
+ container.setAttribute('aria-labelledby', id);
+ }
+ if(value){
+ value.setAttribute('aria-labelledby', id);
+ }
+ if(editButton && editButton.getAttribute(Review.DATA_ATTRIBUTE_VISIBLE) === 'true'){
+ editButton.setAttribute('aria-describedby', id);
+ }
+ }
+
+ static isRepeatable(item) {
+ return item.fieldType === Review.FIELD_TYPE.PANEL && item.type === 'array'
+ }
+
+ /* return value of the field, if value is array then return comma separated string and
+ if value is file then return file name
+ */
+ static getValue(item, value) {
+ if (value === undefined || value === null) return '';
+ if (item.fieldType === Review.FIELD_TYPE.FILE_INPUT) {
+ return Array.isArray(value) ? value.filter(file => file && file.name).map(file => file.name).join(', ') : (value.name || '');
+ }
+ if (Array.isArray(item.enumNames)) {
+ const values = Array.isArray(value) ? value : [value];
+ return values.reduce((names, val) => {
+ const index = item.enum.indexOf(val);
+ if (index >= 0) {
+ names.push(item.enumNames[index]);
+ }
+ return names;
+ }, []).join(', ');
+ }
+ if (Array.isArray(value)) {
+ return value.join(', ');
+ }
+ return value;
+ }
+
+ constructor(params) {
+ super(params);
+ this.#attachIntersectionObserver();
+ this.#attachClickEventOnEdit();
+ this.#setFieldTemplateMapping();
+ }
+ getContainer() {
+ return this.element.querySelector(Review.selectors.container);
+ }
+ getTemplates() {
+ return this.element.querySelectorAll(Review.selectors.templates);
+ }
+ getIntersectionElement() {
+ return this.element.querySelector("[" + Review.selectors.intersection + this.id + "]");
+ }
+ getWidget() { return null }
+ getDescription() { return null; }
+ getLabel() { return null; }
+ getErrorDiv() { return null; }
+ getTooltipDiv() { return null; }
+ getQuestionMarkDiv() { return null; }
+ setModel(model) {
+ super.setModel(model);
+ }
+
+ // attach intersection observer to the review container for lazy loading
+ #attachIntersectionObserver() {
+ let observer = new IntersectionObserver(this.#intersectionObserverHandler.bind(this), Review.intersectionOptions);
+ const ele = this.getIntersectionElement();
+ if(ele){
+ observer.observe(ele);
+ }
+ }
+
+ // attach click event on edit button to set focus on the field
+ #attachClickEventOnEdit() {
+ this.getContainer().addEventListener('click', this.#clickHandler.bind(this))
+ }
+
+ // create template mapping based on field type and use it to render review fields
+ #setFieldTemplateMapping() {
+ const templates = this.getTemplates();
+ let mappings = {};
+ templates.forEach(template => {
+ const type = template.getAttribute(Review.selectors.fieldType);
+ mappings[type || 'default'] = template;
+ });
+ this.templateMappings = mappings;
+ }
+
+ // click handler to set focus on the field when use click on edit button
+ #clickHandler(event) {
+ if (event?.target?.nodeName === 'BUTTON') {
+ const id = event.target.getAttribute(Review.selectors.fieldId);
+ const form = this.formContainer.getModel();;
+ const field = this.formContainer.getField(id);
+ if (form && field) {
+ form.setFocus(field._model);
+ }
+ }
+ }
+
+ // if review container is in view port then render review fields
+ #intersectionObserverHandler(entries) {
+ entries.forEach(entry => {
+ if (entry.isIntersecting && this._model) {
+ const panels = this.#getPanels();
+ const reviewContainer = this.getContainer();
+ const children = this.#renderReviewFields(panels);
+ reviewContainer.innerHTML = '';
+ reviewContainer.appendChild(children);
+ }
+ })
+ }
+
+ // iterate over all the panels or linked panels and render child of each panel
+ #renderReviewFields(items) {
+ if (!items) return;
+ const currentFragment = document.createDocumentFragment();
+ items.filter(item => item.visible !== false && item.fieldType && item[':type']).forEach(item => {
+ if (Review.isRepeatable(item)) {
+ const repeatableItems = this.getModel().form.getElement(item.id).getState();
+ const repeatablePanel = this.#renderReviewFields(repeatableItems.items);
+ if (Review.hasChild(repeatablePanel)) {
+ currentFragment.appendChild(repeatablePanel);
+ }
+ } else {
+ let template = this.templateMappings[item.fieldType] || this.templateMappings['default'];
+ const cloneNode = template.content.cloneNode(true);
+ Review.addClassModifier(cloneNode, item);
+ Review.renderLabel(cloneNode, item);
+ this.#renderEditButton(cloneNode, item);
+ if (item.fieldType === Review.FIELD_TYPE.PANEL) {
+ const fields = this.#renderReviewFields(item.items);
+ if (Review.hasChild(fields)) {
+ const labelContainer = cloneNode.querySelector(Review.selectors.labelContainer);
+ if(item?.label?.visible === false && labelContainer){
+ labelContainer.remove();
+ }
+ Review.addAccessibilityAttributes(cloneNode, item);
+ const contentElement = cloneNode.querySelector(Review.selectors.panelContent);
+ if(contentElement){
+ contentElement.appendChild(fields);
+ }
+ currentFragment.appendChild(cloneNode);
+ }
+ } else if (!Review.HIDE_FIELD_FROM_REVIEW.includes(item.fieldType) && !item[':type'].endsWith('review')) {
+ Review.renderValue(cloneNode, item);
+ Review.addAccessibilityAttributes(cloneNode, item);
+ currentFragment.appendChild(cloneNode);
+ }
+ }
+ });
+ return currentFragment;
+ }
+
+ #getPanels() {
+ const linkedPanels = this.getModel().properties["fd:linkedPanels"] || [];
+ if (linkedPanels.length) {
+ return this.#getLinkedPanels([...linkedPanels]);
+ }
+ return this.#getAllPanels();
+ }
+
+ // return all top lavel panels if author has not linked any panel
+ #getAllPanels() {
+ let state = this.getModel().form.getState();
+ while (state?.items?.length === 1) {
+ state = state.items[0];
+ }
+ return state.items || [];
+ }
+
+ // return linked panels if author has linked any panel
+ #getLinkedPanels(linkedPanels) {
+ let queue = [], result = [];
+ let state = this.getModel().form.getState();
+ queue.push(state);
+ while (queue.length && linkedPanels.length) {
+ const items = Array.isArray(queue[0]) ? queue[0] : [queue[0]];
+ items.forEach(item => {
+ const index = linkedPanels.indexOf(item.name);
+ if (index > -1) {
+ result.push(item);
+ linkedPanels.splice(index, 1);
+ }
+ if (item.items) {
+ queue.push(item.items);
+ }
+ })
+ queue.shift();
+ }
+ return result;
+ }
+
+ // render edit button of the field
+ #renderEditButton(cloneNode, item) {
+ const editButton = cloneNode.querySelector(Review.selectors.editButton);
+ if (editButton) {
+ editButton.setAttribute(Review.selectors.fieldId, item.id);
+ editButton.setAttribute('aria-label', item?.label?.value);
+ if (item.enabled === false) {
+ editButton.setAttribute('disabled', true);
+ }
+ editButton.setAttribute(Review.DATA_ATTRIBUTE_VISIBLE, this.#isVisibleEditButton(item.fieldType));
+ }
+ }
+ #isVisibleEditButton(fieldType) {
+ let editModeAction = this.getModel().properties?.['fd:editModeAction'];
+ if (editModeAction === 'both' ||
+ (editModeAction === Review.FIELD_TYPE.PANEL && fieldType === Review.FIELD_TYPE.PANEL) ||
+ (editModeAction === 'field' && fieldType !== Review.FIELD_TYPE.PANEL)) {
+ return true;
+ }
+ return false;
+ }
+ }
+
+ FormView.Utils.setupField(({ element, formContainer }) => {
+ return new Review({ element, formContainer })
+ }, Review.selectors.self);
+
+})();
diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/review/v1/review/review.html b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/review/v1/review/review.html
new file mode 100644
index 0000000000..0c23c5762b
--- /dev/null
+++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/review/v1/review/review.html
@@ -0,0 +1,50 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/review/v1/review/review.js b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/review/v1/review/review.js
new file mode 100644
index 0000000000..e14853be36
--- /dev/null
+++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/review/v1/review/review.js
@@ -0,0 +1,40 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ 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";
+ var fieldTemplatePath = "core/fd/components/form/review/v1/review/template/fieldTemplate.html";
+ var panelTemplatePath = "core/fd/components/form/review/v1/review/template/panelTemplate.html";
+ var plainTextTemplatePath = "core/fd/components/form/review/v1/review/template/plainTextTemplate.html";
+
+ return {
+ labelPath: labelPath,
+ shortDescriptionPath: shortDescriptionPath,
+ longDescriptionPath: longDescriptionPath,
+ questionMarkPath: questionMarkPath,
+ errorMessagePath: errorMessagePath,
+ clientlibs: clientlibsArr,
+ fieldTemplatePath: fieldTemplatePath,
+ panelTemplatePath: panelTemplatePath,
+ plainTextTemplatePath: plainTextTemplatePath
+ }
+});
\ No newline at end of file
diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/review/v1/review/template/fieldTemplate.html b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/review/v1/review/template/fieldTemplate.html
new file mode 100644
index 0000000000..35ce3308e2
--- /dev/null
+++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/review/v1/review/template/fieldTemplate.html
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/review/v1/review/template/panelTemplate.html b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/review/v1/review/template/panelTemplate.html
new file mode 100644
index 0000000000..c8ec2e4e33
--- /dev/null
+++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/review/v1/review/template/panelTemplate.html
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/review/v1/review/template/plainTextTemplate.html b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/review/v1/review/template/plainTextTemplate.html
new file mode 100644
index 0000000000..6b9fb16fa7
--- /dev/null
+++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/review/v1/review/template/plainTextTemplate.html
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/tabsontop/v1/tabsontop/README.md b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/tabsontop/v1/tabsontop/README.md
index 34f4f2afc0..95c3bffba8 100644
--- a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/tabsontop/v1/tabsontop/README.md
+++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/tabsontop/v1/tabsontop/README.md
@@ -50,6 +50,7 @@ BLOCK cmp-tabs
ELEMENT cmp-tabs__tablist
ELEMENT cmp-tabs__tab
MOD cmp-tabs__tab--active
+ MOD cmp-tabs__tab--stepped
ELEMENT cmp-tabs__title
ELEMENT cmp-tabs__icon
ELEMENT cmp-tabs__label
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 4dd738e70b..1489af4264 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
@@ -27,6 +27,10 @@
tab: "cmp-tabs__tab--active",
tabpanel: "cmp-tabs__tabpanel--active"
},
+ stepped: {
+ tab: "cmp-tabs__tab--stepped",
+ tabpanel: "cmp-tabs__tabpanel--stepped"
+ },
label: `.${Tabs.bemBlock}__label`,
description: `.${Tabs.bemBlock}__longdescription`,
qm: `.${Tabs.bemBlock}__questionmark`,
diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/turnstile/.content.xml b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/turnstile/.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/turnstile/.content.xml
@@ -0,0 +1,3 @@
+
+
diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/turnstile/v1/.content.xml b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/turnstile/v1/.content.xml
new file mode 100644
index 0000000000..5e25fbe65e
--- /dev/null
+++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/turnstile/v1/.content.xml
@@ -0,0 +1,4 @@
+
+
+
diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/turnstile/v1/turnstile/.content.xml b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/turnstile/v1/turnstile/.content.xml
new file mode 100644
index 0000000000..9af5bb2a03
--- /dev/null
+++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/turnstile/v1/turnstile/.content.xml
@@ -0,0 +1,8 @@
+
+
diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/turnstile/v1/turnstile/README.md b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/turnstile/v1/turnstile/README.md
new file mode 100644
index 0000000000..e5fe2ab00f
--- /dev/null
+++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/turnstile/v1/turnstile/README.md
@@ -0,0 +1,71 @@
+
+Adaptive Form Turnstile (v1)
+====
+Adaptive Form Cloudflare Turnstile field component written in HTL.
+
+## Features
+
+* Provides the following type of input:
+ * Turnstile
+* The following widgets are supported for Turnstile:
+ * Managed
+ * Non-Interactive
+ * Invisible
+* Styles
+* Custom constraint messages for the above types
+
+### Use Object
+The Form Text component uses the `com.adobe.cq.forms.core.components.models.form.Turnstile` Sling Model for its Use-object.
+
+### Edit Dialog Properties
+The following properties are written to JCR for this Form Recaptcha 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. `./mandatoryMessage` - defines the message displayed as tooltip when submitting the form if the value is left empty
+5. `./cloudServicePath` - defines the path of cloud configuration resource for Turnstile
+6. `./size` - defines the size attribute of Turnstile
+
+## Client Libraries
+The component provides a `core.forms.components.turnstile.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.
+
+
+## BEM Description
+```
+BLOCK cmp-adaptiveform-turnstile
+ ELEMENT cmp-adaptiveform-turnstile__label
+ ELEMENT cmp-adaptiveform-turnstile__widget
+ ELEMENT cmp-adaptiveform-turnstile__errormessage
+```
+
+## JavaScript Data Attribute Bindings
+
+The following attributes must be added for the initialization of the hCaptcha component in the form view:
+1. `data-cmp-is="adaptiveFormTurnstile"`
+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
diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/turnstile/v1/turnstile/_cq_dialog/.content.xml b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/turnstile/v1/turnstile/_cq_dialog/.content.xml
new file mode 100644
index 0000000000..5f51f816fa
--- /dev/null
+++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/turnstile/v1/turnstile/_cq_dialog/.content.xml
@@ -0,0 +1,172 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/turnstile/v1/turnstile/_cq_template.xml b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/turnstile/v1/turnstile/_cq_template.xml
new file mode 100644
index 0000000000..6e03b7d2d3
--- /dev/null
+++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/turnstile/v1/turnstile/_cq_template.xml
@@ -0,0 +1,6 @@
+
+
diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/turnstile/v1/turnstile/clientlibs/.content.xml b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/turnstile/v1/turnstile/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/turnstile/v1/turnstile/clientlibs/.content.xml
@@ -0,0 +1,3 @@
+
+
diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/turnstile/v1/turnstile/clientlibs/editor/.content.xml b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/turnstile/v1/turnstile/clientlibs/editor/.content.xml
new file mode 100644
index 0000000000..9370b2d52c
--- /dev/null
+++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/turnstile/v1/turnstile/clientlibs/editor/.content.xml
@@ -0,0 +1,5 @@
+
+
diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/turnstile/v1/turnstile/clientlibs/editor/js.txt b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/turnstile/v1/turnstile/clientlibs/editor/js.txt
new file mode 100644
index 0000000000..dd642eaed7
--- /dev/null
+++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/turnstile/v1/turnstile/clientlibs/editor/js.txt
@@ -0,0 +1,18 @@
+###############################################################################
+# Copyright 2022 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/turnstile/v1/turnstile/clientlibs/editor/js/editDialog.js b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/turnstile/v1/turnstile/clientlibs/editor/js/editDialog.js
new file mode 100644
index 0000000000..8c2817de3d
--- /dev/null
+++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/turnstile/v1/turnstile/clientlibs/editor/js/editDialog.js
@@ -0,0 +1,47 @@
+/*******************************************************************************
+ * 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-turnstile__editdialog",
+ TURNSTILE_CONFIG = EDIT_DIALOG + " .cmp-adaptiveform-turnstile__configuration",
+ TURNSTILE_SIZE = EDIT_DIALOG + " .cmp-adaptiveform-turnstile__size",
+ Utils = window.CQ.FormsCoreComponents.Utils.v1;
+
+
+ function addListenerForCaptchaConfigChange(dialog) {
+ const turnstileConfigElement = dialog.find(TURNSTILE_CONFIG)[0];
+ turnstileConfigElement.addEventListener("change", function() {
+ handleCaptchaConfigChange(turnstileConfigElement, dialog);
+ });
+ handleCaptchaConfigChange(turnstileConfigElement, dialog);
+ }
+
+ function handleCaptchaConfigChange(turnstileConfigElement, dialog) {
+ const turnstileSizeElement = dialog.find(TURNSTILE_SIZE)[0];
+ const selectedConfig = turnstileConfigElement.querySelector("coral-select-item[selected]");
+ const selectedWidgetType = selectedConfig.getAttribute("data-widget.type");
+ const inputs = turnstileSizeElement.querySelectorAll('input');
+ if (selectedWidgetType === "invisible") {
+ inputs.forEach(input => input.setAttribute("disabled", true));
+ } else {
+ inputs.forEach(input => input.removeAttribute("disabled"));
+ }
+ }
+
+ Utils.initializeEditDialog(EDIT_DIALOG)(addListenerForCaptchaConfigChange);
+
+})(jQuery);
diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/turnstile/v1/turnstile/clientlibs/site/.content.xml b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/turnstile/v1/turnstile/clientlibs/site/.content.xml
new file mode 100644
index 0000000000..0ace1e4531
--- /dev/null
+++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/turnstile/v1/turnstile/clientlibs/site/.content.xml
@@ -0,0 +1,6 @@
+
+
diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/turnstile/v1/turnstile/clientlibs/site/css.txt b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/turnstile/v1/turnstile/clientlibs/site/css.txt
new file mode 100644
index 0000000000..b2a208dac5
--- /dev/null
+++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/turnstile/v1/turnstile/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
+turnstileview.css
diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/turnstile/v1/turnstile/clientlibs/site/css/turnstileview.css b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/turnstile/v1/turnstile/clientlibs/site/css/turnstileview.css
new file mode 100644
index 0000000000..941298eba2
--- /dev/null
+++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/turnstile/v1/turnstile/clientlibs/site/css/turnstileview.css
@@ -0,0 +1,19 @@
+/**
+## BEM Description
+BLOCK cmp-adaptiveform-turnstile
+ ELEMENT cmp-adaptiveform-turnstile__label
+ ELEMENT cmp-adaptiveform-turnstile__widget
+ ELEMENT cmp-adaptiveform-turnstile__errormessage
+*/
+
+.cmp-adaptiveform-turnstile {
+
+}
+
+.cmp-adaptiveform-turnstile__widget {
+
+}
+
+.cmp-adaptiveform-turnstile__label {
+
+}
diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/turnstile/v1/turnstile/clientlibs/site/js.txt b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/turnstile/v1/turnstile/clientlibs/site/js.txt
new file mode 100644
index 0000000000..fff80d07dd
--- /dev/null
+++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/turnstile/v1/turnstile/clientlibs/site/js.txt
@@ -0,0 +1,19 @@
+###############################################################################
+# 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
+turnstileview.js
+turnstilewidget.js
diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/turnstile/v1/turnstile/clientlibs/site/js/turnstileview.js b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/turnstile/v1/turnstile/clientlibs/site/js/turnstileview.js
new file mode 100644
index 0000000000..87d65a0d1c
--- /dev/null
+++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/turnstile/v1/turnstile/clientlibs/site/js/turnstileview.js
@@ -0,0 +1,85 @@
+/*******************************************************************************
+ * 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 Turnstile extends FormView.FormFieldBase {
+
+ static NS = FormView.Constants.NS;
+ static IS = "adaptiveFormTurnstile";
+ static bemBlock = 'cmp-adaptiveform-turnstile';
+ static selectors = {
+ self: "[data-" + this.NS + '-is="' + this.IS + '"]',
+ widget: `.${Turnstile.bemBlock}__widget`,
+ label: `.${Turnstile.bemBlock}__label`,
+ errorDiv: `.${Turnstile.bemBlock}__errormessage`
+ };
+
+ constructor(params) {
+ super(params);
+ }
+
+ getWidget() {
+ return this.element.querySelector(Turnstile.selectors.widget);
+ }
+
+ getDescription() {
+ return null;
+ }
+
+ getLabel() {
+ return this.element.querySelector(Turnstile.selectors.label);
+ }
+
+ getTooltipDiv() {
+ return null;
+ }
+
+ getErrorDiv() {
+ return this.element.querySelector(Turnstile.selectors.errorDiv);
+ }
+
+ getQuestionMarkDiv() {
+ return null;
+ }
+
+ initializeWidget() {
+ this.widgetObject = new TurnstileWidget(this, this._model, this.getWidget());
+ this.getWidget().addEventListener('blur', (e) => {
+ if(this.element) {
+ this.setInactive();
+ }
+ });
+
+ }
+
+ setModel(model) {
+ super.setModel(model);
+ if (this.widgetObject == null) {
+ this.initializeWidget();
+ } else {
+ if (this.widget.value !== '') {
+ this._model.dispatch(new FormView.Actions.UIChange({'value': this.widget.value}));
+ }
+ }
+ }
+ }
+
+ FormView.Utils.setupField(({element, formContainer}) => {
+ return new Turnstile({element, formContainer})
+ }, Turnstile.selectors.self);
+
+})();
diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/turnstile/v1/turnstile/clientlibs/site/js/turnstilewidget.js b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/turnstile/v1/turnstile/clientlibs/site/js/turnstilewidget.js
new file mode 100644
index 0000000000..d9ccf2ce0e
--- /dev/null
+++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/turnstile/v1/turnstile/clientlibs/site/js/turnstilewidget.js
@@ -0,0 +1,91 @@
+/*******************************************************************************
+ * 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.
+ ******************************************************************************/
+
+/**
+ * This class is responsible for interacting with the turnstile widget. It displays turnstile challenge.
+ */
+
+if (typeof window.TurnstileWidget === 'undefined') {
+ window.TurnstileWidget = class {
+ #widget = null
+ #model = null // passed by reference
+ #options = null
+ #lang = 'en'
+ static FD_CAPTCHA = "fd:captcha";
+
+ constructor(view, model, widget) {
+ // initialize the widget and model
+ this.#widget = widget;
+ this.#model = model;
+ this.#widget = document.createElement("div");
+ this.#widget.classList.add("cmp-adaptiveform-turnstile__widget");
+ this.#lang = view.formContainer.getModel().lang;
+
+ //Always inserting it in body
+ document.body.appendChild(this.#widget);
+ this.#options = Object.assign({}, this.#model._jsonModel);
+
+ this.#renderTurnstile(widget);
+ }
+
+ #renderTurnstile(turnstileContainer) {
+
+ const self = this;
+ const turnstileConfigData = this.#options;
+ let widgetId;
+ const url = turnstileConfigData.properties[TurnstileWidget.FD_CAPTCHA].config.uri;
+
+ const successCallback = function(response) {
+ self.setCaptchaModel(response);
+ };
+
+ const expiredCallback = function() {
+ turnstile.reset(widgetId);
+ self.setCaptchaModel("");
+ };
+
+ const onloadCallbackInternal = function() {
+ widgetId = turnstile.render(
+ turnstileContainer,
+ turnstileParameters
+ );
+ return widgetId;
+ };
+
+ const turnstileParameters = {
+ 'sitekey': this.#model.captchaSiteKey,
+ 'size': turnstileConfigData.properties[TurnstileWidget.FD_CAPTCHA].config.size,
+ 'theme': turnstileConfigData.properties[TurnstileWidget.FD_CAPTCHA].config.theme || 'light',
+ 'callback': successCallback,
+ 'expired-callback': expiredCallback,
+ 'language': this.#lang
+ };
+
+ window.onloadTurnstileCallback = onloadCallbackInternal;
+ const scr = document.createElement('script');
+ const queryParams = (this.#model.captchaDisplayMode === 'invisible')
+ ? "?render=explicit"
+ : "?onload=onloadTurnstileCallback&render=explicit";
+ scr.src = url + queryParams;
+ scr.async = true;
+ turnstileContainer.appendChild(scr);
+ }
+
+ setCaptchaModel(response) {
+ this.#model.dispatch(new FormView.Actions.UIChange({'value': response}));
+ }
+ }
+}
diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/turnstile/v1/turnstile/turnstile.html b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/turnstile/v1/turnstile/turnstile.html
new file mode 100644
index 0000000000..af12e42f7e
--- /dev/null
+++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/turnstile/v1/turnstile/turnstile.html
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/verticaltabs/v1/verticaltabs/README.md b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/verticaltabs/v1/verticaltabs/README.md
index d616724e0c..8a5f4638b9 100644
--- a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/verticaltabs/v1/verticaltabs/README.md
+++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/verticaltabs/v1/verticaltabs/README.md
@@ -48,6 +48,7 @@ BLOCK cmp-verticaltabs
ELEMENT cmp-verticaltabs__label-container
ELEMENT cmp-verticaltabs__tab
MOD cmp-verticaltabs__tab--active
+ MOD cmp-verticaltabs__tab--stepped
ELEMENT cmp-verticaltabs__title
ELEMENT cmp-verticaltabs__icon
ELEMENT cmp-verticaltabs__label
diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/verticaltabs/v1/verticaltabs/clientlibs/contentframe/css.txt b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/verticaltabs/v1/verticaltabs/clientlibs/contentframe/css.txt
new file mode 100644
index 0000000000..b04581e480
--- /dev/null
+++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/verticaltabs/v1/verticaltabs/clientlibs/contentframe/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
+tabsview.css
diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/verticaltabs/v1/verticaltabs/clientlibs/contentframe/css/tabsview.css b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/verticaltabs/v1/verticaltabs/clientlibs/contentframe/css/tabsview.css
new file mode 100644
index 0000000000..ee36a0ef2b
--- /dev/null
+++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/verticaltabs/v1/verticaltabs/clientlibs/contentframe/css/tabsview.css
@@ -0,0 +1,23 @@
+
+
+.cmp-verticaltabs__tabpanel{
+ display: none;
+}
+
+.cmp-verticaltabs__tabpanel.cmp-verticaltabs__tabpanel--active{
+ display: block;
+}
\ No newline at end of file
diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/verticaltabs/v1/verticaltabs/clientlibs/site/js/verticaltabs.js b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/verticaltabs/v1/verticaltabs/clientlibs/site/js/verticaltabs.js
index 69667ae682..5e0e654d42 100644
--- a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/verticaltabs/v1/verticaltabs/clientlibs/site/js/verticaltabs.js
+++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/verticaltabs/v1/verticaltabs/clientlibs/site/js/verticaltabs.js
@@ -27,6 +27,10 @@
tab: "cmp-verticaltabs__tab--active",
tabpanel: "cmp-verticaltabs__tabpanel--active"
},
+ stepped: {
+ tab: "cmp-verticaltabs__tab--stepped",
+ tabpanel: "cmp-verticaltabs__tabpanel--stepped"
+ },
label: `.${VerticalTabs.bemBlock}__label`,
description: `.${VerticalTabs.bemBlock}__longdescription`,
qm: `.${VerticalTabs.bemBlock}__questionmark`,
diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/verticaltabs/v1/verticaltabs/verticaltabs.html b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/verticaltabs/v1/verticaltabs/verticaltabs.html
index ea042f2561..ebcdcfaa42 100644
--- a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/verticaltabs/v1/verticaltabs/verticaltabs.html
+++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/verticaltabs/v1/verticaltabs/verticaltabs.html
@@ -64,6 +64,6 @@
data-sly-test="${(wcmmode.edit || wcmmode.preview)}">
-
+
diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/wizard/v1/wizard/README.md b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/wizard/v1/wizard/README.md
index 19aa9653be..ee369fc407 100644
--- a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/wizard/v1/wizard/README.md
+++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/wizard/v1/wizard/README.md
@@ -46,11 +46,15 @@ It is already included by its edit and policy dialogs.
```
BLOCK cmp-adaptiveform-wizard
ELEMENT cmp-adaptiveform-wizard__label
- ELEMENT cmp-adaptiveform-wizard__label-container
+ ELEMENT cmp-adaptiveform-wizard__label-container
ELEMENT cmp-adaptiveform-wizard__tab
+ MOD cmp-adaptiveform-wizard__tab--active
+ MOD cmp-adaptiveform-wizard__tab--stepped
ELEMENT cmp-adaptiveform-wizard__wizardpanel
ELEMENT cmp-adaptiveform-wizard__previousNav
+ MOD cmp-adaptiveform-wizard__previousNav--hidden
ELEMENT cmp-adaptiveform-wizard__nextNav
+ MOD cmp-adaptiveform-wizard__nextNav--hidden
```
diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/wizard/v1/wizard/clientlibs/commons/js/common.js b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/wizard/v1/wizard/clientlibs/commons/js/common.js
index 419285efb7..b2dc6ea8ab 100644
--- a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/wizard/v1/wizard/clientlibs/commons/js/common.js
+++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/wizard/v1/wizard/clientlibs/commons/js/common.js
@@ -29,7 +29,11 @@
active: {
tab: "cmp-adaptiveform-wizard__tab--active",
wizardpanel: "cmp-adaptiveform-wizard__wizardpanel--active"
- }
+ },
+ stepped: {
+ tab: "cmp-adaptiveform-wizard__tab--stepped",
+ wizardpanel: "cmp-adaptiveform-wizard__wizardpanel--stepped",
+ }
};
_active;
@@ -72,6 +76,48 @@
}
}
+ addSteppedClass() {
+ const tabs = this.getCachedTabs();
+ const wizardPanels = this.getCachedWizardPanels();
+ const activeChildId = this.getActiveWizardTabId(tabs)
+ const activeTab = this.getTabNavElementById(activeChildId);
+ if (activeTab.classList.contains(this.constructor.selectors.active.tab)) {
+ activeTab.classList.add(this.constructor.selectors.stepped.tab);
+
+ const correspondingPanel = Array.from(wizardPanels).find(panel =>
+ panel.getAttribute("aria-labelledby") === activeTab.id
+ );
+
+ if (correspondingPanel) {
+ correspondingPanel.classList.add(this.constructor.selectors.stepped.wizardpanel);
+ }
+ }
+ }
+
+ getTabNavElementById(tabId) {
+ let tabs = this.getCachedTabs();
+ if (tabs) {
+ for (let i = 0; i < tabs.length; i++) {
+ if (tabs[i].id === tabId) {
+ return tabs[i];
+ }
+ }
+ }
+ }
+
+ getActiveWizardTabId(tabs) {
+ if (tabs) {
+ var result = tabs[0].id;
+ for (var i = 0; i < tabs.length; i++) {
+ if (tabs[i].classList.contains(this.constructor.selectors.active.tab)) {
+ result = tabs[i].id;
+ break;
+ }
+ }
+ return result;
+ }
+ }
+
/**
* Returns the index of the active tab, if no tab is active returns 0
*
@@ -105,6 +151,7 @@
*/
navigate(index) {
this._active = index;
+ this.addSteppedClass();
this.refreshActive();
}
diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/wizard/v1/wizard/clientlibs/contentframe/css.txt b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/wizard/v1/wizard/clientlibs/contentframe/css.txt
new file mode 100644
index 0000000000..c66062ae91
--- /dev/null
+++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/wizard/v1/wizard/clientlibs/contentframe/css.txt
@@ -0,0 +1,19 @@
+###############################################################################
+# 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
+
+wizard.css
diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/wizard/v1/wizard/clientlibs/contentframe/css/wizard.css b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/wizard/v1/wizard/clientlibs/contentframe/css/wizard.css
new file mode 100644
index 0000000000..a60e1fe698
--- /dev/null
+++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/wizard/v1/wizard/clientlibs/contentframe/css/wizard.css
@@ -0,0 +1,30 @@
+
+
+.cmp-adaptiveform-wizard__wizardpanel {
+ display: none;
+}
+
+.cmp-adaptiveform-wizard__wizardpanel--active {
+ display: block;
+}
+
+.cmp-adaptiveform-wizard__previousNav--hidden{
+ display: none;
+}
+.cmp-adaptiveform-wizard__nextNav--hidden{
+ display: none;
+}
\ No newline at end of file
diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/wizard/v1/wizard/clientlibs/site/css/wizard.css b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/wizard/v1/wizard/clientlibs/site/css/wizard.css
index eac9f77359..952cbc3d4d 100644
--- a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/wizard/v1/wizard/clientlibs/site/css/wizard.css
+++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/wizard/v1/wizard/clientlibs/site/css/wizard.css
@@ -55,10 +55,10 @@
.cmp-adaptiveform-wizard__previousNav{
}
-.cmp-adaptiveform-wizard__previousNav__hidden{
+.cmp-adaptiveform-wizard__previousNav--hidden{
display: none;
}
-.cmp-adaptiveform-wizard__nextNav__hidden{
+.cmp-adaptiveform-wizard__nextNav--hidden{
display: none;
}
.cmp-adaptiveform-wizard{
diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/wizard/v1/wizard/clientlibs/site/js/wizardview.js b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/wizard/v1/wizard/clientlibs/site/js/wizardview.js
index 71f22f814e..1283389070 100644
--- a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/wizard/v1/wizard/clientlibs/site/js/wizardview.js
+++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/wizard/v1/wizard/clientlibs/site/js/wizardview.js
@@ -41,17 +41,21 @@
tab: "cmp-adaptiveform-wizard__tab--active",
wizardpanel: "cmp-adaptiveform-wizard__wizardpanel--active"
},
+ stepped: {
+ tab: "cmp-adaptiveform-wizard__tab--stepped",
+ wizardpanel: "cmp-adaptiveform-wizard__wizardpanel--stepped",
+ },
label: `.${Wizard.bemBlock}__label`,
description: `.${Wizard.bemBlock}__longdescription`,
qm: `.${Wizard.bemBlock}__questionmark`,
widget: `.${Wizard.bemBlock}__widget`,
tooltipDiv: `.${Wizard.bemBlock}__shortdescription`,
previousButton: `.${Wizard.bemBlock}__previousNav`,
- previousButtonHidden: "cmp-adaptiveform-wizard__previousNav__hidden",
+ previousButtonHidden: "cmp-adaptiveform-wizard__previousNav--hidden",
nextButton: `.${Wizard.bemBlock}__nextNav`,
previousButtonV2: `.${Wizard.bemBlock}__nav--previous`,
nextButtonV2: `.${Wizard.bemBlock}__nav--next`,
- nextButtonHidden: "cmp-adaptiveform-wizard__nextNav__hidden",
+ nextButtonHidden: "cmp-adaptiveform-wizard__nextNav--hidden",
olTabList: `.${Wizard.bemBlock}__tabList`
};
@@ -331,7 +335,7 @@
this.navigate(index);
this.focusWithoutScroll(this.getCachedTabs()[index]);
}
-
+
#syncWizardNavLabels() {
const tabs = this.getCachedTabs();
const wizardPanels = this.getCachedWizardPanels();
@@ -382,12 +386,21 @@
}
} else {
let beforeTabNavElementId = childView.getInstanceManager().children[instanceIndex - 1].element.id + Wizard.#tabIdSuffix
- let beforeElement = this.#getTabNavElementById(beforeTabNavElementId);
+ let beforeElement = this.getTabNavElementById(beforeTabNavElementId);
beforeElement.after(navigationTabToBeRepeated);
}
this.cacheElements(this._elements.self);
let repeatedWizardPanel = this.#getWizardPanelElementById(childView.id + Wizard.#wizardPanelIdSuffix);
repeatedWizardPanel.setAttribute("aria-labelledby", childView.id + Wizard.#tabIdSuffix);
+
+ const steppedTabClass = Array.from(navigationTabToBeRepeated.classList).find(cls => cls.includes('--stepped'));
+ if (steppedTabClass) {
+ navigationTabToBeRepeated.classList.remove(steppedTabClass);
+ }
+ const steppedWizardPanelClass = Array.from(repeatedWizardPanel.classList).find(cls => cls.includes('--stepped'));
+ if (steppedWizardPanelClass) {
+ repeatedWizardPanel.classList.remove(steppedWizardPanelClass);
+ }
this.refreshActive();
this.#getTabIndexById();
if (childView.getInstanceManager().getModel().minOccur != undefined && childView.getInstanceManager().children.length > childView.getInstanceManager().getModel().minOccur) {
@@ -400,7 +413,7 @@
let removedTabPanelId = removedInstanceView.element.id + Wizard.#wizardPanelIdSuffix;
let removedTabNavId = removedTabPanelId.substring(0, removedTabPanelId.lastIndexOf("__")) + Wizard.#tabIdSuffix;
let wizardPanelElement = this.#getWizardPanelElementById(removedTabPanelId);
- let tabNavElement = this.#getTabNavElementById(removedTabNavId);
+ let tabNavElement = this.getTabNavElementById(removedTabNavId);
tabNavElement.remove();
wizardPanelElement.remove();
this.children.splice(this.children.indexOf(removedInstanceView), 1);
@@ -448,7 +461,7 @@
let tabId = childView.element.id + Wizard.#tabIdSuffix;
let wizardPanelId = childView.element.id + Wizard.#wizardPanelIdSuffix;
let instanceManagerId = childView.getInstanceManager().getId();
- let navigationTabToBeRepeated = this.#getTabNavElementById(tabId);
+ let navigationTabToBeRepeated = this.getTabNavElementById(tabId);
let wizardPanelToBeRepeated = this.#getWizardPanelElementById(wizardPanelId)
this._templateHTML[instanceManagerId] = {};
this._templateHTML[instanceManagerId]['navigationTab'] = navigationTabToBeRepeated;
@@ -456,17 +469,6 @@
}
}
- #getTabNavElementById(tabId) {
- let tabs = this.getCachedTabs();
- if (tabs) {
- for (let i = 0; i < tabs.length; i++) {
- if (tabs[i].id === tabId) {
- return tabs[i];
- }
- }
- }
- }
-
#getWizardPanelElementById(wizardPanelId) {
let wizardPanels = this.getCachedWizardPanels();
if (wizardPanels) {
@@ -516,7 +518,7 @@
}
updateChildVisibility(visible, state) {
- this.updateVisibilityOfNavigationElement(this.#getTabNavElementById(state.id + Wizard.#tabIdSuffix), visible);
+ this.updateVisibilityOfNavigationElement(this.getTabNavElementById(state.id + Wizard.#tabIdSuffix), visible);
let activeTabNavElement = this.getCachedTabs()[this._active];
this.#setNavigationRange();
this.#hideUnhideNavButtons(this._active);
diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/wizard/v1/wizard/wizard.html b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/wizard/v1/wizard/wizard.html
index 565ee7130d..f806f46cad 100644
--- a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/wizard/v1/wizard/wizard.html
+++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/wizard/v1/wizard/wizard.html
@@ -77,6 +77,6 @@
-
+
diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/wizard/v2/wizard/wizard.html b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/wizard/v2/wizard/wizard.html
index b377027624..28441ef100 100644
--- a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/wizard/v2/wizard/wizard.html
+++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/wizard/v2/wizard/wizard.html
@@ -78,6 +78,6 @@
-
+
diff --git a/ui.frontend/clientlib-dev.config.cjs b/ui.frontend/clientlib-dev.config.cjs
index de057d032e..10173a641f 100644
--- a/ui.frontend/clientlib-dev.config.cjs
+++ b/ui.frontend/clientlib-dev.config.cjs
@@ -46,7 +46,7 @@ module.exports = {
...libsBaseConfig,
name: 'core-forms-components-runtime-base',
categories: ['core.forms.components.runtime.base'],
- dependencies: ['granite.csrf.standalone.fetchsupport', 'af.rum'],
+ dependencies: ['granite.csrf.standalone.fetchsupport', 'af.rum', 'dompurify'],
assets: {
js: ['dist/main.js'],
resources: ['dist/main.js.map']
diff --git a/ui.frontend/package-lock.json b/ui.frontend/package-lock.json
index 4d25a3c47d..bfe27a044c 100644
--- a/ui.frontend/package-lock.json
+++ b/ui.frontend/package-lock.json
@@ -9,8 +9,8 @@
"version": "1.0.0",
"license": "Apache-2.0",
"dependencies": {
- "@aemforms/af-core": "^0.22.109",
- "@aemforms/af-custom-functions": "1.0.10",
+ "@aemforms/af-core": "^0.22.112",
+ "@aemforms/af-custom-functions": "1.0.13",
"@aemforms/af-formatters": "^0.22.109"
},
"devDependencies": {
@@ -26,6 +26,52 @@
"webpack-merge": "^5.8.0"
}
},
+ "../../../repos/af-custom-functions": {
+ "name": "@aemforms/af-custom-functions",
+ "version": "1.0.12",
+ "extraneous": true,
+ "license": "MIT License, Copyright 2024 Adobe Systems Incorporated",
+ "devDependencies": {
+ "jest": "^29.7.0",
+ "jest-environment-jsdom": "^29.7.0"
+ }
+ },
+ "../../../repos/af2-web-runtime/packages/forms-next-core": {
+ "name": "@aemforms/af-core",
+ "version": "0.22.111",
+ "extraneous": true,
+ "license": "Adobe Proprietary",
+ "dependencies": {
+ "@adobe/json-formula": "0.1.50",
+ "@aemforms/af-formatters": "^0.22.111"
+ },
+ "devDependencies": {
+ "@babel/preset-env": "^7.20.2",
+ "@types/jest": "29.2.4",
+ "@types/lodash": "^4.14.171",
+ "@typescript-eslint/eslint-plugin": "^4.28.2",
+ "@typescript-eslint/parser": "^4.28.2",
+ "babel-jest": "^29.4.1",
+ "blob-polyfill": "^7.0.20220408",
+ "eslint": "^7.30.0",
+ "eslint-config-standard": "^16.0.3",
+ "eslint-plugin-import": "^2.23.4",
+ "eslint-plugin-jest": "^24.3.6",
+ "eslint-plugin-node": "^11.1.0",
+ "eslint-plugin-promise": "^5.1.0",
+ "form-data": "^4.0.0",
+ "jest": "29.3",
+ "jest-environment-jsdom": "^29.3.1",
+ "jest-junit": "^12.2.0",
+ "nock": "^13.1.3",
+ "node-fetch": "^2.6.1",
+ "parse-multipart-data": "^1.5.0",
+ "ts-jest": "29.0",
+ "typedoc": "0.22.11",
+ "typedoc-plugin-markdown": "3.11.13",
+ "typescript": "^4.3.5"
+ }
+ },
"../../af2-web-runtime/packages/forms-next-formatters": {
"name": "@aemforms/af-formatters",
"version": "0.22.75",
@@ -61,23 +107,23 @@
}
},
"node_modules/@aemforms/af-core": {
- "version": "0.22.109",
- "resolved": "https://registry.npmjs.org/@aemforms/af-core/-/af-core-0.22.109.tgz",
- "integrity": "sha512-vJv7dxJrx0Ta0KdL6/hC/RytMzJyQwHRra52V3VQoIa8O9r6/zfNovOYbhlXhGj2iF996UasrGrem+f9WuolvA==",
+ "version": "0.22.112",
+ "resolved": "https://registry.npmjs.org/@aemforms/af-core/-/af-core-0.22.112.tgz",
+ "integrity": "sha512-cyUbPYu/l+w9elyTLEWhyQj2qLp/PlI2fXDX/2kpTcGfb+ckRqXRzpItbPqa66m4scjcNpK0RpwfThcSRb1S3w==",
"dependencies": {
"@adobe/json-formula": "0.1.50",
- "@aemforms/af-formatters": "^0.22.109"
+ "@aemforms/af-formatters": "^0.22.112"
}
},
"node_modules/@aemforms/af-custom-functions": {
- "version": "1.0.10",
- "resolved": "https://registry.npmjs.org/@aemforms/af-custom-functions/-/af-custom-functions-1.0.10.tgz",
- "integrity": "sha512-n3w9tHkJOI5ISVYAK2cCi5k/oTu3rGgByDmMIgOH1+Ry4mL9nM3cxBTKEkPF8Y8JiKF1aUHIKM+MeP6u5PiiUA=="
+ "version": "1.0.13",
+ "resolved": "https://registry.npmjs.org/@aemforms/af-custom-functions/-/af-custom-functions-1.0.13.tgz",
+ "integrity": "sha512-thtSn92qpkZg0uldYMaiN81MGxyttv+kyO2h34YA1Li8V79DYFp3JJWE3duhCgo+tslCLGamqryVwJbMdSJV6Q=="
},
"node_modules/@aemforms/af-formatters": {
- "version": "0.22.109",
- "resolved": "https://registry.npmjs.org/@aemforms/af-formatters/-/af-formatters-0.22.109.tgz",
- "integrity": "sha512-WG4JiC1TDfg/5KLQyGziOKscYJ0mD8JgPzXYw7ZqpMAobgCEwKRSfu2Bf+Wr9v13ASigS0KhLssxSZIfOX/QJA=="
+ "version": "0.22.112",
+ "resolved": "https://registry.npmjs.org/@aemforms/af-formatters/-/af-formatters-0.22.112.tgz",
+ "integrity": "sha512-hnscUtTMxfEWNcBiatYY5lzGCCtRn1lbCdKemPTqMMRIzBCWFJXx1mxV84ysyktXXW7R+PS64P0A2K8IBVtVFw=="
},
"node_modules/@ampproject/remapping": {
"version": "2.2.1",
@@ -11076,23 +11122,23 @@
"integrity": "sha512-dmlLYfbty8NPVIdxvI9cJ+ZdXsrRCFrCdmL1+aR2auEzXJ86rD0bm1qu+S4NOpFiZLKIyx0zvUTykms40vNjsA=="
},
"@aemforms/af-core": {
- "version": "0.22.109",
- "resolved": "https://registry.npmjs.org/@aemforms/af-core/-/af-core-0.22.109.tgz",
- "integrity": "sha512-vJv7dxJrx0Ta0KdL6/hC/RytMzJyQwHRra52V3VQoIa8O9r6/zfNovOYbhlXhGj2iF996UasrGrem+f9WuolvA==",
+ "version": "0.22.112",
+ "resolved": "https://registry.npmjs.org/@aemforms/af-core/-/af-core-0.22.112.tgz",
+ "integrity": "sha512-cyUbPYu/l+w9elyTLEWhyQj2qLp/PlI2fXDX/2kpTcGfb+ckRqXRzpItbPqa66m4scjcNpK0RpwfThcSRb1S3w==",
"requires": {
"@adobe/json-formula": "0.1.50",
- "@aemforms/af-formatters": "^0.22.109"
+ "@aemforms/af-formatters": "^0.22.112"
}
},
"@aemforms/af-custom-functions": {
- "version": "1.0.10",
- "resolved": "https://registry.npmjs.org/@aemforms/af-custom-functions/-/af-custom-functions-1.0.10.tgz",
- "integrity": "sha512-n3w9tHkJOI5ISVYAK2cCi5k/oTu3rGgByDmMIgOH1+Ry4mL9nM3cxBTKEkPF8Y8JiKF1aUHIKM+MeP6u5PiiUA=="
+ "version": "1.0.13",
+ "resolved": "https://registry.npmjs.org/@aemforms/af-custom-functions/-/af-custom-functions-1.0.13.tgz",
+ "integrity": "sha512-thtSn92qpkZg0uldYMaiN81MGxyttv+kyO2h34YA1Li8V79DYFp3JJWE3duhCgo+tslCLGamqryVwJbMdSJV6Q=="
},
"@aemforms/af-formatters": {
- "version": "0.22.109",
- "resolved": "https://registry.npmjs.org/@aemforms/af-formatters/-/af-formatters-0.22.109.tgz",
- "integrity": "sha512-WG4JiC1TDfg/5KLQyGziOKscYJ0mD8JgPzXYw7ZqpMAobgCEwKRSfu2Bf+Wr9v13ASigS0KhLssxSZIfOX/QJA=="
+ "version": "0.22.112",
+ "resolved": "https://registry.npmjs.org/@aemforms/af-formatters/-/af-formatters-0.22.112.tgz",
+ "integrity": "sha512-hnscUtTMxfEWNcBiatYY5lzGCCtRn1lbCdKemPTqMMRIzBCWFJXx1mxV84ysyktXXW7R+PS64P0A2K8IBVtVFw=="
},
"@ampproject/remapping": {
"version": "2.2.1",
diff --git a/ui.frontend/package.json b/ui.frontend/package.json
index 68ac54f75c..dd5ed20f1e 100644
--- a/ui.frontend/package.json
+++ b/ui.frontend/package.json
@@ -23,8 +23,8 @@
"webpack-merge": "^5.8.0"
},
"dependencies": {
- "@aemforms/af-core": "^0.22.109",
+ "@aemforms/af-core": "^0.22.112",
"@aemforms/af-formatters": "^0.22.109",
- "@aemforms/af-custom-functions": "1.0.10"
+ "@aemforms/af-custom-functions": "1.0.13"
}
}
diff --git a/ui.frontend/src/constants.js b/ui.frontend/src/constants.js
index c278bcabe8..04d6f8078e 100644
--- a/ui.frontend/src/constants.js
+++ b/ui.frontend/src/constants.js
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
+import { FIELD_TYPE } from "@aemforms/af-core";
/**
* @module FormView
@@ -365,6 +366,14 @@ export const Constants = {
* Prefix path for all AF HTTP APIs.
* @type {string}
*/
- API_PATH_PREFIX : "/adobe/forms/af"
+ API_PATH_PREFIX : "/adobe/forms/af",
+
+ /**
+ * Field type object.
+ * @type {object}
+ * @memberof module:FormView~Constants
+ * @namespace FIELD_TYPE
+ */
+ FIELD_TYPE: FIELD_TYPE
};
diff --git a/ui.frontend/src/customFunctions.js b/ui.frontend/src/customFunctions.js
index 681ab3d2df..8633feeb0a 100644
--- a/ui.frontend/src/customFunctions.js
+++ b/ui.frontend/src/customFunctions.js
@@ -98,5 +98,21 @@ export const customFunctions = {
* @param {object} globals - An object containing read-only form instance, read-only target field instance and methods for form modifications.
* @returns {string} - The captcha token.
*/
- fetchCaptchaToken: cf.fetchCaptchaToken
+ fetchCaptchaToken: cf.fetchCaptchaToken,
+
+ /**
+ * Converts a date to the number of days since the Unix epoch (1970-01-01).
+ *
+ * If the input date is a number, it is assumed to represent the number of days since the epoch,
+ * including both integer and decimal parts. In this case, only the integer part is returned as the number of days.
+ *
+ * @param {string|Date|number} date - The date to convert.
+ * Can be:
+ * - An ISO string (yyyy-mm-dd)
+ * - A Date object
+ * - A number representing the days since the epoch, where the integer part is the number of days and the decimal part is the fraction of the day
+ *
+ * @returns {number} - The number of days since the Unix epoch
+ */
+ dateToDaysSinceEpoch: cf.dateToDaysSinceEpoch
};
diff --git a/ui.frontend/src/utils.js b/ui.frontend/src/utils.js
index b201ae4eee..16e03a285a 100644
--- a/ui.frontend/src/utils.js
+++ b/ui.frontend/src/utils.js
@@ -269,6 +269,7 @@ class Utils {
}
/**
+ * @deprecated Use `registerCustomFunctionsV2` instead.
* Registers custom functions from clientlibs.
* @param {string} formId - The form ID.
*/
@@ -286,6 +287,30 @@ class Utils {
}
}
+ /**
+ * Registers custom functions from clientlibs.
+ * @param {object} formJson - The Sling model exporter representation of the form
+ */
+ static async registerCustomFunctionsV2(formJson) {
+ let funcConfig;
+ const customFunctionsUrl = formJson.properties['fd:customFunctionsUrl'];
+ if (customFunctionsUrl) {
+ funcConfig = await HTTPAPILayer.getJson(customFunctionsUrl);
+ } else {
+ funcConfig = await HTTPAPILayer.getCustomFunctionConfig(formJson.id);
+ }
+ console.debug("Fetched custom functions: " + JSON.stringify(funcConfig));
+ if (funcConfig && funcConfig.customFunction) {
+ const funcObj = funcConfig.customFunction.reduce((accumulator, func) => {
+ if (window[func.id]) {
+ accumulator[func.id] = window[func.id];
+ }
+ return accumulator;
+ }, {});
+ FunctionRuntime.registerFunctions(funcObj);
+ }
+ }
+
/**
* Sets up the Form Container.
* @param {Function} createFormContainer - The function to create a form container.
@@ -309,14 +334,15 @@ class Utils {
} else {
const _formJson = await HTTPAPILayer.getFormDefinition(_path, _pageLang);
console.debug("fetched model json", _formJson);
- await this.registerCustomFunctions(_formJson.id);
+ await this.registerCustomFunctionsV2( _formJson);
await this.registerCustomFunctionsByUrl(customFunctionUrl);
const urlSearchParams = new URLSearchParams(window.location.search);
const params = Object.fromEntries(urlSearchParams.entries());
let _prefillData = {};
if (_formJson?.properties?.['fd:formDataEnabled'] === true) {
// only execute when fd:formDataEnabled is present and set to true
- _prefillData = await HTTPAPILayer.getPrefillData(_formJson.id, params) || {};
+ _prefillData = await HTTPAPILayer.getJson(_formJson.properties['fd:dataUrl'] + "?" + Object.keys(params).map(p => p+"="+params[p]).join("&"))
+ _prefillData = _prefillData || {};
_prefillData = Utils.stripIfWrapped(_prefillData);
}
const formContainer = await createFormContainer({
diff --git a/ui.frontend/src/view/FormFileInputWidgetBase.js b/ui.frontend/src/view/FormFileInputWidgetBase.js
index 11104593ac..c34eeef9c0 100644
--- a/ui.frontend/src/view/FormFileInputWidgetBase.js
+++ b/ui.frontend/src/view/FormFileInputWidgetBase.js
@@ -41,7 +41,32 @@ class FormFileInputWidgetBase {
"dll": "application/x-msdownload",
"exe": "application/x-msdownload",
"msi": "application/x-msdownload",
- "msg": "application/vnd.ms-outlook"
+ "msg": "application/vnd.ms-outlook",
+ "dwg": "image/vnd.dwg",
+ "jxr": "image/vnd.ms-photo",
+ "psd": "image/vnd.adobe.photoshop",
+ "ico": "image/vnd.microsoft.icon",
+ "cab": "application/vnd.ms-cab-compressed",
+ "deb": "application/vnd.debian.binary-package",
+ "sqlite": "application/vnd.sqlite3",
+ "inf2": "image/vnd.cns.inf2",
+ "djv": "image/vnd.djvu",
+ "djvu": "image/vnd.djvu",
+ "dxf": "image/vnd.dxf",
+ "fbs": "image/vnd.fastbidsheet",
+ "fpx": "image/vnd.fpx",
+ "fst": "image/vnd.fst",
+ "mmr": "image/vnd.fujixerox.edmics-mmr",
+ "rlc": "image/vnd.fujixerox.edmics-rlc",
+ "pgb": "image/vnd.globalgraphics.pgb",
+ "mix": "image/vnd.mix",
+ "mdi": "image/vnd.ms-modi",
+ "npx": "image/vnd.net-fpx",
+ "radiance": "image/vnd.radiance",
+ "sealed.png": "image/vnd.sealed.png",
+ "softseal.gif": "image/vnd.sealedmedia.softseal.gif",
+ "softseal.jpg": "image/vnd.sealedmedia.softseal.jpg",
+ "svf": "image/vnd.svf"
}
initialFileValueFileNameMap;
diff --git a/ui.frontend/src/view/FormTabs.js b/ui.frontend/src/view/FormTabs.js
index e432c30ca4..462d87d8e8 100644
--- a/ui.frontend/src/view/FormTabs.js
+++ b/ui.frontend/src/view/FormTabs.js
@@ -286,9 +286,29 @@ class FormTabs extends FormPanel {
*/
navigate(tabId) {
this.#_active = tabId;
+ this.addSteppedClass(tabId);
this.#refreshActive();
}
+
+ addSteppedClass(tabId) {
+ var tabs = this.#getCachedTabs();
+ var tabpanels= this.#getCachedTabPanels();
+ const activeTabId = this.getActiveTabId(tabs);
+ const activeTabElement = this.#getTabNavElementById(activeTabId);
+ if (activeTabElement.classList.contains(this.#_selectors.active.tab)) {
+ activeTabElement.classList.add(this.#_selectors.stepped.tab);
+
+ const correspondingPanel = Array.from(tabpanels).find(panel =>
+ panel.getAttribute("aria-labelledby") === activeTabElement.id
+ );
+
+ if (correspondingPanel) {
+ correspondingPanel.classList.add(this.#_selectors.stepped.tabpanel);
+ }
+ }
+ }
+
/**
* Returns the id of the active tab, if no tab is active returns 0th element id
*
@@ -384,6 +404,14 @@ class FormTabs extends FormPanel {
this.#cacheElements(this._elements.self);
var repeatedTabPanel = this.#getTabPanelElementById(childView.id + this.#tabPanelIdSuffix);
repeatedTabPanel.setAttribute("aria-labelledby", childView.id + this.#tabIdSuffix);
+ const steppedTabClass = Array.from(navigationTabToBeRepeated.classList).find(cls => cls.includes('--stepped'));
+ if (steppedTabClass) {
+ navigationTabToBeRepeated.classList.remove(steppedTabClass);
+ }
+ const steppedTabpanelClass = Array.from(repeatedTabPanel.classList).find(cls => cls.includes('--stepped'));
+ if (steppedTabpanelClass) {
+ repeatedTabPanel.classList.remove(steppedTabpanelClass);
+ }
this.#bindEventsToTab(navigationTabToBeRepeated.id);
this.#refreshActive();
if (childView.getInstanceManager().getModel().minOccur != undefined && childView.getInstanceManager().children.length > childView.getInstanceManager().getModel().minOccur) {
diff --git a/ui.tests/test-module/libs/commons/formsConstants.js b/ui.tests/test-module/libs/commons/formsConstants.js
index 0f59444f59..c511260f7a 100644
--- a/ui.tests/test-module/libs/commons/formsConstants.js
+++ b/ui.tests/test-module/libs/commons/formsConstants.js
@@ -48,7 +48,8 @@ var formsConstants = {
"fragment": "/apps/forms-components-examples/components/form/fragment",
"fragmentcontainer": "/apps/forms-components-examples/components/form/fragmentcontainer",
"termsandconditions": "/apps/forms-components-examples/components/form/termsandconditions",
- "submitButton": "/apps/forms-components-examples/components/form/actions/submit"
+ "submitButton": "/apps/forms-components-examples/components/form/actions/submit",
+ "review": "/apps/forms-components-examples/components/form/review",
}
},
resourceType : {
diff --git a/ui.tests/test-module/libs/commons/guideSelectors.js b/ui.tests/test-module/libs/commons/guideSelectors.js
index 16540133ca..40b0f31bb7 100644
--- a/ui.tests/test-module/libs/commons/guideSelectors.js
+++ b/ui.tests/test-module/libs/commons/guideSelectors.js
@@ -167,6 +167,7 @@ var selectors = {
ruleEditor : {
action : {
+ configure: "#EditableToolbar [data-action='CONFIGURE']",
editRule : "#EditableToolbar [data-action='editexpression']",
createRuleButton : "#create-rule-button",
saveRule : ".exp-Save-Button",
@@ -181,16 +182,19 @@ var selectors = {
EVENT_AND_COMPARISON_OPERATOR : ".choice-model.u-coral-clearFix.EVENT_AND_COMPARISON_OPERATOR",
PRIMITIVE_EXPRESSION : ".choice-model.u-coral-clearFix.PRIMITIVE_EXPRESSION.choice-model-inline",
BLOCK_STATEMENT : ".choice-model.u-coral-clearFix.BLOCK_STATEMENT",
- PARAMETER : ".Parameters .choice-model.u-coral-clearFix.EXPRESSION"
+ PARAMETER : ".Parameters .choice-model.u-coral-clearFix.EXPRESSION",
+ STRING_LITERAL : ".choice-model.u-coral-clearFix .STRING_LITERAL",
},
ruleSummary : {
CREATED_RULE: "#rule-summary table[handle='table'] tr[title='Button - Click']",
+ DATE_PICKER_RULE: "#rule-summary table[handle='table'] tr[title='Date Input - Validate']",
SUBMISSION_SUCCESS_RULE: "#rule-summary table[handle='table'] tr[title='FORM - Successful Submission']",
SUBMISSION_FAILURE_RULE: "#rule-summary table[handle='table'] tr[title='FORM - Error in Submission']",
CUSTOM_SUBMIT_FORM_RULE: "#rule-summary table[handle='table'] tr[title='Submit - Click']",
},
operator : {
CONTAINS : "coral-selectlist [value='CONTAINS']",
+ EQUALS_TO : "coral-selectlist [value='EQUALS_TO']",
HIDE : "coral-selectlist [value='HIDE_STATEMENT']",
SAVE_FORM: "coral-selectlist [value='SAVE_FORM']",
FUNCTION_CALL : "coral-selectlist [value='FUNCTION_CALL']",
diff --git a/ui.tests/test-module/specs/accordion/accordion.repeatability.runtime.cy.js b/ui.tests/test-module/specs/accordion/accordion.repeatability.runtime.cy.js
index 2b0f4993fc..d716a09330 100644
--- a/ui.tests/test-module/specs/accordion/accordion.repeatability.runtime.cy.js
+++ b/ui.tests/test-module/specs/accordion/accordion.repeatability.runtime.cy.js
@@ -212,6 +212,41 @@ describe("Form with Accordion Container with repeatable elements", () => {
getAccordionButtons().should('have.length', 8);
})
});
+
+ it("if item-1 is repeated, the repeated instance should not have stepped class", () => {
+ getItemDivs().should('have.length', 5);
+ getAccordionPanels().should('have.length', 5);
+ getAccordionButtons().should('have.length', 5);
+ getExpandedPanelDiv().should('have.length', 1);
+ getExpandedButtonDiv().should('have.length', 1);
+ getAccordionButtonsAtIndex(0).should('have.class', 'cmp-accordion__button--expanded');
+ getAccordionPanelsAtIndex(0).should('have.class', 'cmp-accordion__panel--expanded');
+ cy.get("button").contains("+RP1").click().then(() => {
+ getItemDivs().should('have.length', 6);
+ getAccordionPanels().should('have.length', 6);
+ getAccordionButtons().should('have.length', 6);
+ getExpandedPanelDiv().should('have.length', 1);
+ getExpandedButtonDiv().should('have.length', 1);
+ getAccordionButtonsAtIndex(1).should('have.class', 'cmp-accordion__button--expanded');
+ getAccordionPanelsAtIndex(1).should('have.class', 'cmp-accordion__panel--expanded');
+ getAccordionButtonsAtIndex(0).should('have.class', 'cmp-accordion__button--stepped');
+ getAccordionPanelsAtIndex(0).should('have.class', 'cmp-accordion__panel--stepped');
+ });
+ cy.get("button").contains("+RP1").click().then(() => {
+ getItemDivs().should('have.length', 7);
+ getAccordionPanels().should('have.length', 7);
+ getAccordionButtons().should('have.length', 7);
+ getExpandedPanelDiv().should('have.length', 1);
+ getExpandedButtonDiv().should('have.length', 1);
+ getAccordionButtonsAtIndex(2).should('have.class', 'cmp-accordion__button--expanded');
+ getAccordionPanelsAtIndex(2).should('have.class', 'cmp-accordion__panel--expanded');
+ getAccordionButtonsAtIndex(1).should('have.class', 'cmp-accordion__button--stepped');
+ getAccordionPanelsAtIndex(1).should('have.class', 'cmp-accordion__panel--stepped');
+ getAccordionButtonsAtIndex(0).should('have.class', 'cmp-accordion__button--stepped');
+ getAccordionPanelsAtIndex(0).should('have.class', 'cmp-accordion__panel--stepped');
+ });
+ cy.expectNoConsoleErrors();
+ })
})
describe("Form with Accordion Container with nested repeatable elements", () => {
diff --git a/ui.tests/test-module/specs/button/button.runtime.cy.js b/ui.tests/test-module/specs/button/button.runtime.cy.js
index 163e35212f..99ae2cdc92 100644
--- a/ui.tests/test-module/specs/button/button.runtime.cy.js
+++ b/ui.tests/test-module/specs/button/button.runtime.cy.js
@@ -108,4 +108,10 @@ describe("Form Runtime with Button Input", () => {
cy.get('.cmp-adaptiveform-button__widget').eq(0).should('have.attr', 'type', 'button');
});
+ it(`label changes should reflect via rules`, () => {
+ const [id] = Object.entries(formContainer._fields)[5];
+ cy.get(`#${id}`).find('.cmp-adaptiveform-button__text').contains('change me');
+ cy.get(`#${id}`).find('.cmp-adaptiveform-button__widget').click();
+ cy.get(`#${id}`).find('.cmp-adaptiveform-button__text').contains('changed');
+ });
})
diff --git a/ui.tests/test-module/specs/checkboxgroup/checkboxgroup.runtime.cy.js b/ui.tests/test-module/specs/checkboxgroup/checkboxgroup.runtime.cy.js
index 85ee4b2cb8..f095eca80c 100644
--- a/ui.tests/test-module/specs/checkboxgroup/checkboxgroup.runtime.cy.js
+++ b/ui.tests/test-module/specs/checkboxgroup/checkboxgroup.runtime.cy.js
@@ -238,6 +238,17 @@ describe("Form Runtime with CheckBoxGroup Input", () => {
cy.get(`#${id}`).invoke('attr', 'data-cmp-required').should('eq', 'true');
});
+ it("reset of checkbox group resulting in invalidation", () => {
+ expect(formContainer, "formcontainer is initialized").to.not.be.null;
+ const [resetButton, resetButtonView] = Object.entries(formContainer._fields)[7];
+ const [checkBox2, checkBox2FieldView] = Object.entries(formContainer._fields)[1];
+ cy.get(`#${checkBox2}`).find("input").check(["0","3"]);
+ cy.get(`#${checkBox2}`).invoke('attr', 'data-cmp-valid').should('eq', 'true');
+ cy.get(`#${resetButton} button`).click().then(() => {
+ cy.get(`#${checkBox2}`).find("input").should('not.be.checked');
+ cy.get(`#${checkBox2}`).invoke('attr', 'data-cmp-valid').should('not.exist');
+ });
+ })
})
describe("setFocus on checkboxgroup via rules", () => {
diff --git a/ui.tests/test-module/specs/customactions.cy.js b/ui.tests/test-module/specs/customactions.cy.js
new file mode 100644
index 0000000000..880d3fa577
--- /dev/null
+++ b/ui.tests/test-module/specs/customactions.cy.js
@@ -0,0 +1,48 @@
+
+/*
+ *
+ * ADOBE CONFIDENTIAL
+ * ___________________
+ *
+ * Copyright 2024 Adobe Systems Incorporated
+ * All Rights Reserved.
+ *
+ * NOTICE: All information contained herein is, and remains
+ * the property of Adobe Systems Incorporated and its suppliers,
+ * if any. The intellectual and technical concepts contained
+ * herein are proprietary to Adobe Systems Incorporated and its
+ * suppliers and are protected by trade secret or copyright law.
+ * Dissemination of this information or reproduction of this material
+ * is strictly forbidden unless prior written permission is obtained
+ * from Adobe Systems Incorporated.
+ */
+
+describe('Custom Urls to fetch Custom Function & Prefill', () => {
+ const pagePath = "/content/forms/af/core-components-it/samples/embed/customactiontesting.html?wcmmode=disabled";
+ const formJsonPath = "/content/forms/af/core-components-it/samples/embed/customactiontesting/jcr:content/guideContainer.model.en.json";
+ const formJson = '{"id":"L2NvbnRlbnQvZm9ybXMvYWYvY29yZS1jb21wb25lbnRzLWl0L3NhbXBsZXMvZW1iZWQvY3VzdG9tYWN0aW9udGVzdGluZw==","fieldType":"form","lang":"en","title":"customactiontesting","action":"/adobe/forms/af/submit/L2NvbnRlbnQvZm9ybXMvYWYvY29yZS1jb21wb25lbnRzLWl0L3NhbXBsZXMvZW1iZWQvY3VzdG9tYWN0aW9udGVzdGluZw==","properties":{"themeRef":"/libs/fd/af/themes/canvas","schemaType":"none","fd:dor":{"dorType":"none"},"fd:path":"/content/forms/af/core-components-it/samples/embed/customactiontesting/jcr:content/guideContainer","fd:schemaType":"BASIC","fd:isHamburgerMenuEnabled":false,"fd:roleAttribute":null,"fd:formDataEnabled":true,"fd:autoSave":{"fd:enableAutoSave":false},"fd:customFunctionsUrl":"/customfunctionsprefix/adobe/forms/af/customfunctions/L2NvbnRlbnQvZm9ybXMvYWYvY29yZS1jb21wb25lbnRzLWl0L3NhbXBsZXMvZW1iZWQvY3VzdG9tYWN0aW9udGVzdGluZw==","fd:dataUrl":"/prefillprefix/adobe/forms/af/data/L2NvbnRlbnQvZm9ybXMvYWYvY29yZS1jb21wb25lbnRzLWl0L3NhbXBsZXMvZW1iZWQvY3VzdG9tYWN0aW9udGVzdGluZw=="},"columnCount":12,"gridClassNames":"aem-Grid aem-Grid--12 aem-Grid--default--12","events":{"custom:setProperty":["$event.payload"]},"metadata":{"grammar":"json-formula-1.0.0","version":"1.0.0"},"adaptiveform":"0.14.2",":type":"forms-components-examples/components/form/container","allowedComponents":{"components":[],"applicable":false}}';
+
+
+ /**
+ * initialization of form container before every test
+ * */
+ beforeEach(() => {
+ cy.intercept('GET', formJsonPath, {
+ statusCode: 200,
+ body: formJson
+ })
+ cy.openPage(pagePath);
+ });
+
+ it('should use fd:customFunctionUrl to fetch the custom functions', () => {
+ cy.intercept('GET', /^\/customfunctionsprefix.*/).as('customFunctionRequest');
+ cy.wait('@customFunctionRequest').its('response.statusCode').should('eq', 404);
+ cy.get('@customFunctionRequest').should('have.property', 'state', 'Complete');
+ })
+
+ it('should use fd:dataUrl to fetch the prefill data', () => {
+ cy.intercept('GET', /^\/prefillprefix.*/).as('prefillRequest');
+ cy.wait('@prefillRequest').its('response.statusCode').should('eq', 404);
+ cy.get('@prefillRequest').should('have.property', 'state', 'Complete');
+ })
+})
diff --git a/ui.tests/test-module/specs/fileinput/fileinputstring.runtime.cy.js b/ui.tests/test-module/specs/fileinput/fileinputstring.runtime.cy.js
index 18e41b1244..6fa604fe30 100644
--- a/ui.tests/test-module/specs/fileinput/fileinputstring.runtime.cy.js
+++ b/ui.tests/test-module/specs/fileinput/fileinputstring.runtime.cy.js
@@ -90,6 +90,15 @@ describe("Form with File Input - Prefill & Submit tests", () => {
const submitBtn = "submit1673953138924";
const pagePath = "/content/forms/af/core-components-it/samples/fileinput/fileinputstring/basic.html"
+ let toggle_array = [];
+
+ before(() => {
+ cy.fetchFeatureToggles().then((response) => {
+ if (response.status === 200) {
+ toggle_array = response.body.enabled;
+ }
+ });
+ });
beforeEach(() => {
cy.wrap(prefillId).as('prefillId');
@@ -113,72 +122,76 @@ describe("Form with File Input - Prefill & Submit tests", () => {
fileInputs.forEach((fileInput, idx) => {
- it(`${fileInput.type} - attach files, check model, view, preview attachment and submit the form`, () => {
- cy.previewForm(pagePath, {
- onBeforeLoad: (win) => {
- cy.stub(win, 'open'); // creating a stub to check file preview
- }
- });
-
- // attach the file
- cy.attachFile(fileInput.selector, fileInput.fileNames);
- if(fileInput.multiple)
- cy.attachFile(fileInput.selector, ['sample2.txt']);
-
- // submit the form
- cy.get(".cmp-adaptiveform-button__widget").click();
-
- // check for successful submission
- submitTest();
- })
-
- it(`${fileInput.type} - view prefill of submitted form, make changes to attachments and submit`, () => {
- cy.get("@prefillId").then(id => {
+ // these test are based on don't replace data of FA which is present as string inside main form data
+ // hence checking if FT is explicitly enabled
+ if (toggle_array.includes('FT_FORMS-16351')) {
+ it(`${fileInput.type} - attach files, check model, view, preview attachment and submit the form`, () => {
cy.previewForm(pagePath, {
- params: [`prefillId=${id}`],
- onBeforeLoad(win) {
+ onBeforeLoad: (win) => {
cy.stub(win, 'open'); // creating a stub to check file preview
}
});
- // check if files were prefilled
- checkFileNamesInFileAttachmentView(fileInput.selector, fileInput.fileNames);
-
- checkFilePreviewInFileAttachment(fileInput.selector);
-
- // check if guideBridge API returns file attachments correctly
- getFormObjTest(['empty.pdf', ...(fileInput.multiple ? ['sample2.txt', 'sample.txt']: []) ]).then(() => {
- // add new files after preview to both the component
- cy.attachFile(fileInput.selector, ['sample2.txt']).then(() => {
- // check if guideBridge API returns correctly after prefill and attaching more files
- getFormObjTest(['sample2.txt', ...(fileInput.multiple ? ['sample.txt', 'empty.pdf', 'sample2.txt']: []) ]).then(() => {
- // submit the form
- cy.get(".cmp-adaptiveform-button__widget").click();
- // check if submission is success
- submitTest();
- })
+ // attach the file
+ cy.attachFile(fileInput.selector, fileInput.fileNames);
+ if (fileInput.multiple)
+ cy.attachFile(fileInput.selector, ['sample2.txt']);
+
+ // submit the form
+ cy.get(".cmp-adaptiveform-button__widget").click();
+
+ // check for successful submission
+ submitTest();
+ })
+
+ it(`${fileInput.type} - view prefill of submitted form, make changes to attachments and submit`, () => {
+ cy.get("@prefillId").then(id => {
+ cy.previewForm(pagePath, {
+ params: [`prefillId=${id}`],
+ onBeforeLoad(win) {
+ cy.stub(win, 'open'); // creating a stub to check file preview
+ }
});
- });
- });
- });
+ // check if files were prefilled
+ checkFileNamesInFileAttachmentView(fileInput.selector, fileInput.fileNames);
+
+ checkFilePreviewInFileAttachment(fileInput.selector);
+
+ // check if guideBridge API returns file attachments correctly
+ getFormObjTest(['empty.pdf', ...(fileInput.multiple ? ['sample2.txt', 'sample.txt'] : [])]).then(() => {
+ // add new files after preview to both the component
+ cy.attachFile(fileInput.selector, ['sample2.txt']).then(() => {
+ // check if guideBridge API returns correctly after prefill and attaching more files
+ getFormObjTest(['sample2.txt', ...(fileInput.multiple ? ['sample.txt', 'empty.pdf', 'sample2.txt'] : [])]).then(() => {
+ // submit the form
+ cy.get(".cmp-adaptiveform-button__widget").click();
+ // check if submission is success
+ submitTest();
+ })
+ });
+ });
- it(`${fileInput.type} - prefill of submitted prefilled form`, () => {
- cy.get("@prefillId").then(id => {
- cy.previewForm(pagePath, {
- params: [`prefillId=${id}`],
- onBeforeLoad(win) {
- cy.stub(win, 'open'); // creating a stub to check file preview
- }
});
+ });
- // check if files were prefilled
- checkFileNamesInFileAttachmentView(fileInput.selector, ['sample2.txt', ...(fileInput.multiple ? ['sample.txt', 'empty.pdf']: []) ]);
- getFormObjTest(['sample2.txt', ...(fileInput.multiple ? ['sample.txt', 'empty.pdf', 'sample2.txt']: []) ]);
+ it(`${fileInput.type} - prefill of submitted prefilled form`, () => {
+ cy.get("@prefillId").then(id => {
+ cy.previewForm(pagePath, {
+ params: [`prefillId=${id}`],
+ onBeforeLoad(win) {
+ cy.stub(win, 'open'); // creating a stub to check file preview
+ }
+ });
- });
+ // check if files were prefilled
+ checkFileNamesInFileAttachmentView(fileInput.selector, ['sample2.txt', ...(fileInput.multiple ? ['sample.txt', 'empty.pdf'] : [])]);
+ getFormObjTest(['sample2.txt', ...(fileInput.multiple ? ['sample.txt', 'empty.pdf', 'sample2.txt'] : [])]);
- });
+ });
+
+ });
+ }
})
});
diff --git a/ui.tests/test-module/specs/radiobutton/radiobutton.runtime.cy.js b/ui.tests/test-module/specs/radiobutton/radiobutton.runtime.cy.js
index 661230be48..cee413e625 100644
--- a/ui.tests/test-module/specs/radiobutton/radiobutton.runtime.cy.js
+++ b/ui.tests/test-module/specs/radiobutton/radiobutton.runtime.cy.js
@@ -230,6 +230,18 @@ describe("Form with Radio Button Input", () => {
})
cy.get(`#${id}`).invoke('attr', 'data-cmp-required').should('eq', 'true');
})
+
+ it("reset of radiobutton resulting in invalidation", () => {
+ expect(formContainer, "formcontainer is initialized").to.not.be.null;
+ const [radioButton1, radioButton1FieldView] = Object.entries(formContainer._fields)[0];
+ const [resetButton, resetButtonFieldView] = Object.entries(formContainer._fields)[9];
+
+ cy.get(`#${radioButton1}`).find("input").check("1");
+ cy.get(`#${resetButton} button`).click().then(() => {
+ cy.get(`#${radioButton1}`).find("input[value='1']").should('not.be.checked');
+ cy.get(`#${radioButton1}`).invoke('attr', 'data-cmp-valid').should('not.exist');
+ })
+ })
})
describe("setFocus on radiobutton via rules", () => {
diff --git a/ui.tests/test-module/specs/recaptcha/recaptcha.authoring.cy.js b/ui.tests/test-module/specs/recaptcha/recaptcha.authoring.cy.js
index 6b4b92eb72..0d4422af2e 100644
--- a/ui.tests/test-module/specs/recaptcha/recaptcha.authoring.cy.js
+++ b/ui.tests/test-module/specs/recaptcha/recaptcha.authoring.cy.js
@@ -64,7 +64,7 @@ describe('Page - Authoring', function () {
// Checking some dynamic behaviours
cy.get(".cmp-adaptiveform-recaptcha__configuration").click().then(() => {
- cy.get("coral-selectlist-item[value='entScore']").click();
+ cy.get("coral-selectlist-item[value='entscore']").click();
cy.get("input[name='./recaptchaSize'][value='normal']").should("be.disabled");
cy.get("input[name='./recaptchaSize'][value='compact']").should("be.disabled");
})
diff --git a/ui.tests/test-module/specs/review/review.authoring.spec.js b/ui.tests/test-module/specs/review/review.authoring.spec.js
new file mode 100644
index 0000000000..460150da09
--- /dev/null
+++ b/ui.tests/test-module/specs/review/review.authoring.spec.js
@@ -0,0 +1,97 @@
+/*
+ * 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');
+
+describe.only('Page - Authoring', function () {
+
+ const dropComponent = function (responsiveGridDropZoneSelector, componentTitle, componentType) {
+ cy.selectLayer("Edit");
+ cy.insertComponent(responsiveGridDropZoneSelector, componentTitle, componentType);
+ cy.get('body').click(0, 0);
+ }
+
+ const getDropZoneSelector = function (responsiveGridDropZone) {
+ return sitesSelectors.overlays.overlay.component + "[data-path='" + responsiveGridDropZone + "']";
+ }
+
+ const dropTabsInContainer = function () {
+ const responsiveGridDropZoneSelector = getDropZoneSelector("/content/forms/af/core-components-it/blank/jcr:content/guideContainer/*");
+ dropComponent(responsiveGridDropZoneSelector, "Adaptive Form Horizontal Tabs", afConstants.components.forms.resourceType.tabsontop);
+ }
+ const dropPanelInTabComponent = function () {
+ const responsiveGridDropZoneSelector = sitesSelectors.overlays.overlay.component + "[data-text='Please drag Tab components here']:last";
+ dropComponent(responsiveGridDropZoneSelector, "Adaptive Form Panel", afConstants.components.forms.resourceType.panelcontainer);
+ }
+ const dropTextInputInTabComponent = function () {
+ const responsiveGridDropZoneSelector = sitesSelectors.overlays.overlay.component + "[data-text='Please drag Tab components here']:first";
+ dropComponent(responsiveGridDropZoneSelector, "Adaptive Form Text Box", afConstants.components.forms.resourceType.formtextinput);
+ }
+ const dropReviewInTabComponent = function () {
+ const responsiveGridDropZoneSelector = sitesSelectors.overlays.overlay.component + "[data-text='Please drag Tab components here']";
+ cy.get(responsiveGridDropZoneSelector).eq(1).then(($element) => {
+ dropComponent($element, "Adaptive Form Review", afConstants.components.forms.resourceType.review);
+ });
+ }
+ const dropTabsInSites = function () {
+ const dataPath = "/content/core-components-examples/library/adaptive-form/panelcontainer/jcr:content/root/responsivegrid/demo/component/guideContainer/*",
+ responsiveGridDropZoneSelector = sitesSelectors.overlays.overlay.component + "[data-path='" + dataPath + "']";
+ dropComponent(responsiveGridDropZoneSelector, "Adaptive Form Horizontal Tabs", afConstants.components.forms.resourceType.tabsontop);
+ }
+
+ context('Open Forms Editor', function () {
+ const pagePath = "/content/forms/af/core-components-it/blank",
+ tabsPath = pagePath + afConstants.FORM_EDITOR_FORM_CONTAINER_SUFFIX + "/tabsontop",
+ tabsContainerPathSelector = "[data-path='" + tabsPath + "']",
+ reviewPath = pagePath + afConstants.FORM_EDITOR_FORM_CONTAINER_SUFFIX + "/review",
+ reviewContainerPathSelector = "[data-path='" + reviewPath + "']",
+ editDialogConfigurationSelector = "[data-action='CONFIGURE']",
+ reviewBlockBemSelector = '.cmp-adaptiveform-review';
+ beforeEach(function () {
+ // this is done since cypress session results in 403 sometimes
+ cy.openAuthoring(pagePath);
+ });
+
+
+ it('Drop tabs and drop element in tabs on top', {retries: 3}, function () {
+ cy.cleanTest(tabsPath).then(function () {
+ dropTabsInContainer();
+ dropPanelInTabComponent();
+ dropTextInputInTabComponent();
+ dropPanelInTabComponent();
+ cy.openEditableToolbar(sitesSelectors.overlays.overlay.component + tabsContainerPathSelector);
+ cy.invokeEditableAction("[data-action='PANEL_SELECT']").then(() => {
+ cy.get("table.cmp-panelselector__table tr").eq(1).click().then(() => {
+ cy.get('body').click(0, 0);
+ dropReviewInTabComponent();
+
+ cy.openEditableToolbar(sitesSelectors.overlays.overlay.component + reviewContainerPathSelector);
+ cy.invokeEditableAction(editDialogConfigurationSelector);
+ cy.get("[name='./name']").should("exist");
+ cy.get("[name='./jcr:title']").should("exist");
+ cy.get("[name='./fd:editModeAction']").should("exist");
+ cy.get("[name='./fd:editModeAction'] coral-select-item").should("have.length", 4);
+ cy.get("[name='./linkedPanels']").should("exist");
+ cy.get("[name='./linkedPanels'] coral-select-item").should("have.length", 2);
+ cy.deleteComponentByPath(tabsPath);
+ })
+ });
+ });
+ });
+ });
+});
diff --git a/ui.tests/test-module/specs/review/review.repeatability.runtime.spec.js b/ui.tests/test-module/specs/review/review.repeatability.runtime.spec.js
new file mode 100644
index 0000000000..4dad25a9e0
--- /dev/null
+++ b/ui.tests/test-module/specs/review/review.repeatability.runtime.spec.js
@@ -0,0 +1,245 @@
+/*******************************************************************************
+ * 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 with Review component with repeatablity", () => {
+
+ const pagePath = "content/forms/af/core-components-it/samples/review/repeatability.html";
+ const bemBlock = 'cmp-adaptiveform-review';
+ let formContainer = null;
+
+ beforeEach(() => {
+ cy.previewForm(pagePath).then(p => {
+ formContainer = p;
+ })
+ });
+
+ const tabSelector = 'ol li';
+ const tab1 = () => {
+ return cy.get(tabSelector).eq(0);
+ }
+ const tab2 = () => {
+ return cy.get(tabSelector).eq(1);
+ }
+ const tab3 = () => {
+ return cy.get(tabSelector).eq(2);
+ }
+ const fillFirstTab = () => {
+ tab1().click();
+ cy.get('input[name="textinput1724402270046"]').type('john');
+ cy.get('input[name="textinput_16829110921724402298524"]').type('deo');
+ cy.get('input[name="emailinput1724402315995"]').type('abc@gmail.com');
+ cy.get('input[name="telephoneinput1724402330900"]').type('+91987654321');
+ cy.get('input[name="datepicker1724402449174"]').type('2020-10-10');
+ cy.get(`#gender_id-widget`).find("input").eq(0).click();
+ cy.get(`#interest_id-widget`).find("input").eq(0).click();
+ cy.get(`#interest_id-widget`).find("input").eq(1).click();
+ }
+ const fillSecondTab = () => {
+ tab2().click();
+ cy.get('input[name="textinput1724402488097"]').type('adobe system');
+ cy.get('input[name="textinput_19265691141724402493991"]').type('noida');
+ cy.get('select[name="dropdown1724402520718"]').select("UP")
+ cy.get('input[name="numberinput1724402569060"]').type('123456');
+ }
+ const fillSecondRepeatablePanel = () => {
+ tab2().click();
+ cy.get(`#add_address_id-widget`).click().then(() => {
+ cy.get('input[name="textinput1724402488097"]').eq(1).type('sector 132');
+ cy.get('input[name="textinput_19265691141724402493991"]').eq(1).type('delhi');
+ cy.get('select[name="dropdown1724402520718"]').eq(1).select("Delhi")
+ cy.get('input[name="numberinput1724402569060"]').eq(1).type('201301');
+ });
+ }
+
+ // Verify the first panel
+ const checkFirstPanel = () => {
+ cy.get(`.${bemBlock}__container .${bemBlock}__panel`).eq(0).within(() => {
+ cy.get(`.${bemBlock}__label-container`).should('exist');
+ cy.get(`.${bemBlock}__label`).contains('Personal Information');
+ cy.get(`.${bemBlock}__edit-button`).should('have.attr', 'data-cmp-visible', 'true');
+
+ cy.get(`.${bemBlock}__content`).should('exist').within(() => {
+ cy.get(`.${bemBlock}__field`).eq(0).within(() => {
+ cy.get(`.${bemBlock}__label`).contains('First name');
+ cy.get(`.${bemBlock}__value`).contains('john');
+ cy.get(`.${bemBlock}__edit-button`).should('have.attr', 'data-cmp-visible', 'false');
+ });
+
+ cy.get(`.${bemBlock}__field`).eq(1).within(() => {
+ cy.get(`.${bemBlock}__label`).contains('Last name');
+ cy.get(`.${bemBlock}__value`).contains('deo');
+ cy.get(`.${bemBlock}__edit-button`).should('have.attr', 'data-cmp-visible', 'false');
+ });
+
+ cy.get(`.${bemBlock}__field`).eq(2).within(() => {
+ cy.get(`.${bemBlock}__label`).contains('Email Address');
+ cy.get(`.${bemBlock}__value`).contains('abc@gmail.com');
+ cy.get(`.${bemBlock}__edit-button`).should('have.attr', 'data-cmp-visible', 'false');
+ });
+
+ cy.get(`.${bemBlock}__field`).eq(3).within(() => {
+ cy.get(`.${bemBlock}__label`).contains('Mobile Number');
+ cy.get(`.${bemBlock}__value`).contains('+91987654321');
+ cy.get(`.${bemBlock}__edit-button`).should('have.attr', 'data-cmp-visible', 'false');
+ });
+
+ cy.get(`.${bemBlock}__field`).eq(4).within(() => {
+ cy.get(`.${bemBlock}__label`).contains('DOB');
+ cy.get(`.${bemBlock}__value`).contains('2020-10-10');
+ cy.get(`.${bemBlock}__edit-button`).should('have.attr', 'data-cmp-visible', 'false');
+ });
+
+ cy.get(`.${bemBlock}__field`).eq(5).within(() => {
+ cy.get(`.${bemBlock}__label`).contains('Gender');
+ cy.get(`.${bemBlock}__value`).contains('Male');
+ cy.get(`.${bemBlock}__edit-button`).should('have.attr', 'data-cmp-visible', 'false');
+ });
+
+ cy.get(`.${bemBlock}__field`).eq(6).within(() => {
+ cy.get(`.${bemBlock}__label`).contains('Interest');
+ cy.get(`.${bemBlock}__value`).contains('Music , Football');
+ cy.get(`.${bemBlock}__edit-button`).should('have.attr', 'data-cmp-visible', 'false');
+ });
+ });
+ });
+ }
+ // Verify the second panel
+ const checkSecondPanel = () => {
+ cy.get(`.${bemBlock}__container .${bemBlock}__panel`).eq(1).within(() => {
+ cy.get(`.${bemBlock}__content`).should('exist').within(() => {
+ cy.get(`.${bemBlock}__label-container`).should('exist');
+ cy.get(`.${bemBlock}__label`).contains('Address');
+ cy.get(`.${bemBlock}__edit-button`).should('have.attr', 'data-cmp-visible', 'true');
+
+ cy.get(`.${bemBlock}__content`).should('exist').within(() => {
+ cy.get(`.${bemBlock}__field`).eq(0).within(() => {
+ cy.get(`.${bemBlock}__label`).contains('Address 1');
+ cy.get(`.${bemBlock}__value`).contains('adobe system');
+ cy.get(`.${bemBlock}__edit-button`).should('have.attr', 'data-cmp-visible', 'false');
+ });
+
+ cy.get(`.${bemBlock}__field`).eq(1).within(() => {
+ cy.get(`.${bemBlock}__label`).contains('City');
+ cy.get(`.${bemBlock}__value`).contains('noida');
+ cy.get(`.${bemBlock}__edit-button`).should('have.attr', 'data-cmp-visible', 'false');
+ });
+
+ cy.get(`.${bemBlock}__field`).eq(2).within(() => {
+ cy.get(`.${bemBlock}__label`).contains('State');
+ cy.get(`.${bemBlock}__value`).contains('UP');
+ cy.get(`.${bemBlock}__edit-button`).should('have.attr', 'data-cmp-visible', 'false');
+ });
+
+ cy.get(`.${bemBlock}__field`).eq(3).within(() => {
+ cy.get(`.${bemBlock}__label`).contains('Zip code');
+ cy.get(`.${bemBlock}__value`).contains('123456');
+ cy.get(`.${bemBlock}__edit-button`).should('have.attr', 'data-cmp-visible', 'false');
+ });
+ });
+ });
+ });
+ };
+ // Verify the second repeatable panel
+ const checkSecondRepeatablePanel = () => {
+ cy.get(`.${bemBlock}__panel--repeatable`).eq(1).within(() => {
+ cy.get(`.${bemBlock}__content`).should('exist').within(() => {
+ cy.get(`.${bemBlock}__field`).eq(0).within(() => {
+ cy.get(`.${bemBlock}__label`).contains('Address 1');
+ cy.get(`.${bemBlock}__value`).contains('sector 132');
+ cy.get(`.${bemBlock}__edit-button`).should('have.attr', 'data-cmp-visible', 'false');
+ });
+
+ cy.get(`.${bemBlock}__field`).eq(1).within(() => {
+ cy.get(`.${bemBlock}__label`).contains('City');
+ cy.get(`.${bemBlock}__value`).contains('delhi');
+ cy.get(`.${bemBlock}__edit-button`).should('have.attr', 'data-cmp-visible', 'false');
+ });
+
+ cy.get(`.${bemBlock}__field`).eq(2).within(() => {
+ cy.get(`.${bemBlock}__label`).contains('State');
+ cy.get(`.${bemBlock}__value`).contains('Delhi');
+ cy.get(`.${bemBlock}__edit-button`).should('have.attr', 'data-cmp-visible', 'false');
+ });
+
+ cy.get(`.${bemBlock}__field`).eq(3).within(() => {
+ cy.get(`.${bemBlock}__label`).contains('Zip code');
+ cy.get(`.${bemBlock}__value`).contains('201301');
+ cy.get(`.${bemBlock}__edit-button`).should('have.attr', 'data-cmp-visible', 'false');
+ });
+ });
+ });
+ };
+
+ it("should render tabs on top with review component", () => {
+ expect(formContainer, "formcontainer is initialized").to.not.be.null;
+ cy.get('.cmp-tabs').should('have.length', 1);
+ cy.get('.cmp-tabs__tab').should('have.length', 3);
+ tab3().click();
+ tab3().should('have.class', 'cmp-tabs__tab--active');
+ cy.get(`.${bemBlock}`).should('exist')
+ });
+
+ it("should render filled values with labels and edit buttons", () => {
+ fillFirstTab();
+ fillSecondTab();
+ // Check review component
+ tab3().click();
+ cy.get(`.${bemBlock}`).should('exist');
+ cy.get(`.${bemBlock}__container`).should('exist');
+ cy.get(`.${bemBlock}__container`).children(`.${bemBlock}__panel`).should('have.length', 2);
+ cy.get(`.${bemBlock}__container`).then(() => {
+ checkFirstPanel();
+ });
+ cy.get(`.${bemBlock}__container`).then(() => {
+ checkSecondPanel();
+ });
+ });
+
+ it("should focus on the first field of the panel when clicking the edit button of the panel", () => {
+ fillFirstTab();
+ fillSecondTab();
+ tab3().click();
+ cy.get(`.panelcontainer1724402234656 .${bemBlock}__label-container .${bemBlock}__edit-button`).click().then(() => {
+ cy.get('input[name="textinput1724402270046"]').should('be.focused').and('have.value', 'john');
+ });
+ });
+
+ it("should render repeatable panel with filled values and edit buttons", () => {
+ fillFirstTab();
+ fillSecondTab();
+ fillSecondRepeatablePanel()
+ tab3().click();
+ cy.get(`.${bemBlock}__container`).then(() => {
+ checkSecondPanel();
+ });
+ cy.get(`.${bemBlock}__container`).then(() => {
+ checkSecondRepeatablePanel();
+ });
+ });
+
+ it("should focus on the second repeatable panel when clicking the edit button of the repeatable panel", () => {
+ fillFirstTab();
+ fillSecondTab();
+ fillSecondRepeatablePanel()
+ tab3().click();
+ cy.get(`.${bemBlock}__panel--repeatable`).eq(1).find(`.${bemBlock}__label-container .${bemBlock}__edit-button`).click().then(() => {
+ cy.get('input[name="textinput1724402488097"]').eq(1).should('be.focused').and('have.value', 'sector 132');
+ });
+ });
+
+});
+
+
diff --git a/ui.tests/test-module/specs/review/review.runtime.spec.js b/ui.tests/test-module/specs/review/review.runtime.spec.js
new file mode 100644
index 0000000000..b5b39d7396
--- /dev/null
+++ b/ui.tests/test-module/specs/review/review.runtime.spec.js
@@ -0,0 +1,172 @@
+/*******************************************************************************
+ * 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 with Review component", () => {
+
+ const pagePath = "content/forms/af/core-components-it/samples/review/basic.html";
+ const bemBlock = 'cmp-adaptiveform-review';
+ let formContainer = null;
+
+ beforeEach(() => {
+ cy.previewForm(pagePath).then(p => {
+ formContainer = p;
+ })
+ });
+
+ const tabSelector = 'ol li';
+ const tab1 = () => {
+ return cy.get(tabSelector).eq(0);
+ }
+ const tab2 = () => {
+ return cy.get(tabSelector).eq(1);
+ }
+ const tab3 = () => {
+ return cy.get(tabSelector).eq(2);
+ }
+ const fillFirstTab = () => {
+ tab1().click();
+ cy.get('input[name="textinput1724402270046"]').type('john');
+ cy.get('input[name="textinput_16829110921724402298524"]').type('deo');
+ cy.get('input[name="emailinput1724402315995"]').type('abc@gmail.com');
+ cy.get('input[name="telephoneinput1724402330900"]').type('+91987654321');
+ cy.get('input[name="datepicker1724402449174"]').type('2020-10-10');
+ cy.get(`#gender_id-widget`).find("input").eq(0).click();
+ cy.get(`#interest_id-widget`).find("input").eq(0).click();
+ cy.get(`#interest_id-widget`).find("input").eq(1).click();
+ }
+ const fillSecondTab = () => {
+ tab2().click();
+ cy.get('input[name="textinput1724402488097"]').type('adobe system');
+ cy.get('input[name="textinput_19265691141724402493991"]').type('noida');
+ cy.get('select[name="dropdown1724402520718"]').select("UP")
+ cy.get('input[name="numberinput1724402569060"]').type('123456');
+ }
+
+ // Verify the first panel
+ const checkFirstPanel = () => {
+ cy.get(`.${bemBlock}__container .${bemBlock}__panel`).within(() => {
+ cy.get(`.${bemBlock}__label-container`).should('exist');
+ cy.get(`.${bemBlock}__label`).contains('Personal Information');
+ cy.get(`.${bemBlock}__edit-button`).should('have.attr', 'data-cmp-visible', 'false');
+
+ cy.get(`.${bemBlock}__content`).should('exist').within(() => {
+ cy.get(`.${bemBlock}__field`).eq(0).within(() => {
+ cy.get(`.${bemBlock}__label`).contains('First name');
+ cy.get(`.${bemBlock}__value`).contains('john');
+ cy.get(`.${bemBlock}__edit-button`).should('have.attr', 'data-cmp-visible', 'true');
+ });
+
+ cy.get(`.${bemBlock}__field`).eq(1).within(() => {
+ cy.get(`.${bemBlock}__label`).contains('Last name');
+ cy.get(`.${bemBlock}__value`).contains('deo');
+ cy.get(`.${bemBlock}__edit-button`).should('have.attr', 'data-cmp-visible', 'true');
+ });
+
+ cy.get(`.${bemBlock}__field`).eq(2).within(() => {
+ cy.get(`.${bemBlock}__label`).contains('Full name');
+ cy.get(`.${bemBlock}__value`).contains('johndeo');
+ cy.get(`.${bemBlock}__edit-button`).should('have.attr', 'data-cmp-visible', 'true');
+ cy.get(`.${bemBlock}__edit-button`).should('have.attr', 'disabled');
+ });
+
+ cy.get(`.${bemBlock}__field`).eq(3).within(() => {
+ cy.get(`.${bemBlock}__label`).contains('Email Address');
+ cy.get(`.${bemBlock}__value`).contains('abc@gmail.com');
+ cy.get(`.${bemBlock}__edit-button`).should('have.attr', 'data-cmp-visible', 'true');
+ });
+
+ cy.get(`.${bemBlock}__field`).eq(4).within(() => {
+ cy.get(`.${bemBlock}__label`).contains('Mobile Number');
+ cy.get(`.${bemBlock}__value`).contains('+91987654321');
+ cy.get(`.${bemBlock}__edit-button`).should('have.attr', 'data-cmp-visible', 'true');
+ });
+
+ cy.get(`.${bemBlock}__field`).eq(5).within(() => {
+ cy.get(`.${bemBlock}__label`).contains('DOB');
+ cy.get(`.${bemBlock}__value`).contains('2020-10-10');
+ cy.get(`.${bemBlock}__edit-button`).should('have.attr', 'data-cmp-visible', 'true');
+ });
+
+ cy.get(`.${bemBlock}__field`).eq(6).within(() => {
+ cy.get(`.${bemBlock}__label`).contains('Gender');
+ cy.get(`.${bemBlock}__value`).contains('Male');
+ cy.get(`.${bemBlock}__edit-button`).should('have.attr', 'data-cmp-visible', 'true');
+ });
+
+ cy.get(`.${bemBlock}__field`).eq(7).within(() => {
+ cy.get(`.${bemBlock}__label`).contains('Interest');
+ cy.get(`.${bemBlock}__value`).contains('Music , Football');
+ cy.get(`.${bemBlock}__edit-button`).should('have.attr', 'data-cmp-visible', 'true');
+ });
+ });
+ });
+ }
+
+ it("should render tabs on top with review component", () => {
+ expect(formContainer, "formcontainer is initialized").to.not.be.null;
+ cy.get('.cmp-tabs').should('have.length', 1);
+ cy.get('.cmp-tabs__tab').should('have.length', 3);
+ tab3().click();
+ tab3().should('have.class', 'cmp-tabs__tab--active');
+ cy.get(`.${bemBlock}`).should('exist')
+ });
+
+ it("should render linked panel (Tab 1) and check filled value", () => {
+ fillFirstTab();
+ fillSecondTab();
+ tab3().click();
+ cy.get(`.${bemBlock}`).should('exist');
+ cy.get(`.${bemBlock}__container`).should('exist');
+ cy.get(`.${bemBlock}__container`).should('contain', 'Personal Information');
+ cy.get(`.${bemBlock}__container`).then(() => {
+ checkFirstPanel();
+ });
+ });
+
+ it("should not render unlinked panel", () => {
+ tab3().click();
+ cy.get(`.${bemBlock}__panel`).should('have.length', 1);
+ cy.get(`.${bemBlock}__panel`).find('Address').should('not.exist');
+ cy.get(`.${bemBlock}__panel`).find('Address 1').should('not.exist');
+ cy.get(`.${bemBlock}__panel`).find('City').should('not.exist');
+ });
+
+ it("should focus on the first field of the panel when clicking the edit button of the panel", () => {
+ fillFirstTab();
+ tab3().click();
+ cy.get(`.textinput1724402270046 .${bemBlock}__edit-button`).click().then(() => {
+ cy.get('input[name="textinput1724402270046"]').should('be.focused').and('have.value', 'john');
+ });
+ });
+
+ it("Edit button should be disabled for disabled field", () => {
+ fillFirstTab();
+ tab3().click();
+ cy.get(`.textinput_19678416231729233302926 .${bemBlock}__edit-button`).should('have.attr', 'disabled');
+ });
+
+ it("Hidden field should not be visible", () => {
+ tab3().click();
+ cy.get(`.${bemBlock}__container .hidden_name`).should('not.exist');
+ });
+
+ it("Button field should not be visible", () => {
+ tab3().click();
+ cy.get(`.${bemBlock}__container .hidden_button`).should('not.exist');
+ });
+});
+
+
diff --git a/ui.tests/test-module/specs/ruleeditor/authoring/navigatePanel.authoring.cy.js b/ui.tests/test-module/specs/ruleeditor/authoring/navigatePanel.authoring.cy.js
new file mode 100644
index 0000000000..849dfed11b
--- /dev/null
+++ b/ui.tests/test-module/specs/ruleeditor/authoring/navigatePanel.authoring.cy.js
@@ -0,0 +1,119 @@
+const commons = require('../../../libs/commons/commons'),
+ sitesSelectors = require('../../../libs/commons/sitesSelectors'),
+ formsSelectors = require('../../../libs/commons/guideSelectors'),
+ afConstants = require('../../../libs/commons/formsConstants');
+
+describe('Rule editor navigate-in-panel rule authoring',function(){
+ let toggle_array = [];
+
+ before(() => {
+ cy.fetchFeatureToggles().then((response) => {
+ if (response.status === 200) {
+ toggle_array = response.body.enabled;
+ }
+ });
+ });
+
+ context('Open Forms Editor', function() {
+ const formPath = "/content/forms/af/core-components-it/samples/ruleeditor/navigate-in-panel/blank",
+ formContainerPath = formPath + afConstants.FORM_EDITOR_FORM_CONTAINER_SUFFIX,
+ buttonEditPath = formContainerPath + "/" + afConstants.components.forms.resourceType.formbutton.split("/").pop(),
+ panelEditPath = formContainerPath + "/" + afConstants.components.forms.resourceType.panelcontainer.split("/").pop(),
+ buttonEditPathSelector = "[data-path='" + buttonEditPath + "']";
+
+ it('should add rule to focus previousItem in Panel on button click', function () {
+ if (toggle_array.includes("FT_FORMS-10781")) {
+ cy.openAuthoring(formPath);
+ cy.selectLayer("Edit");
+ cy.get(sitesSelectors.overlays.overlay.component + "[data-path='" + formContainerPath + "/*']").should("exist");
+
+ cy.insertComponent(sitesSelectors.overlays.overlay.component + "[data-path='" + formContainerPath + "/*']",
+ "Adaptive Form Panel", afConstants.components.forms.resourceType.panelcontainer);
+ cy.get(sitesSelectors.overlays.overlay.component + "[data-path='" + formContainerPath + "/panelcontainer/*']").should("exist");
+ cy.wait(1000);
+
+ cy.insertComponent(sitesSelectors.overlays.overlay.component + "[data-path='" + formContainerPath + "/panelcontainer/*']",
+ "Adaptive Form Text Box", afConstants.components.forms.resourceType.formtextinput);
+ cy.insertComponent(sitesSelectors.overlays.overlay.component + "[data-path='" + formContainerPath + "/panelcontainer/*']",
+ "Adaptive Form Text Box", afConstants.components.forms.resourceType.formtextinput);
+
+ cy.insertComponent(sitesSelectors.overlays.overlay.component + "[data-path='" + formContainerPath + "/*']",
+ "Adaptive Form Button", afConstants.components.forms.resourceType.formbutton);
+ cy.openEditableToolbar(sitesSelectors.overlays.overlay.component + buttonEditPathSelector);
+
+ // Edit rule option not existing on button toolbar
+ cy.get(formsSelectors.ruleEditor.action.editRule).should("exist");
+ cy.initializeEventHandlerOnChannel("af-rule-editor-initialized").as("isRuleEditorInitialized");
+ cy.get(formsSelectors.ruleEditor.action.editRule).click();
+
+ // click on create option from rule editor header
+ cy.get("@isRuleEditorInitialized").its('done').should('equal', true);
+ createNavigateInPanelRule();
+
+ // check and close rule editor
+ cy.getRuleEditorIframe().find(formsSelectors.ruleEditor.action.closeRuleEditor).should("exist");
+ cy.getRuleEditorIframe().find(formsSelectors.ruleEditor.action.closeRuleEditor).click();
+
+ cy.get(sitesSelectors.overlays.overlay.component + buttonEditPathSelector).should("exist");
+ cy.selectLayer("Edit");
+ cy.deleteComponentByPath(buttonEditPath);
+ cy.deleteComponentByPath(panelEditPath);
+ }
+ })
+ })
+
+ const createNavigateInPanelRule = function() {
+ cy.getRuleEditorIframe().find(formsSelectors.ruleEditor.action.createRuleButton).should("be.visible").click();
+ cy.getRuleEditorIframe().find(formsSelectors.ruleEditor.action.sideToggleButton + ":first").click();
+
+ // // Forms Objects option is not existing on side panel
+ // cy.getRuleEditorIframe().find(formsSelectors.ruleEditor.action.sidePanelFormObjectTab).should("exist");
+ // cy.getRuleEditorIframe().find(formsSelectors.ruleEditor.action.sidePanelFormObjectTab).then($el => {
+ // expect($el.text().trim()).to.equal("Form Objects");
+ // })
+ //
+ // // Functions option is not existing on side panel
+ // cy.getRuleEditorIframe().find(formsSelectors.ruleEditor.action.sidePanelFunctionObjectTab).should("exist");
+ // cy.getRuleEditorIframe().find(formsSelectors.ruleEditor.action.sidePanelFunctionObjectTab).then($el => {
+ // expect($el.text().trim()).to.equal("Functions");
+ // })
+
+ cy.getRuleEditorIframe().find(formsSelectors.ruleEditor.choiceModels.STATEMENT + " .child-choice-name").should("exist");
+ cy.getRuleEditorIframe().find(formsSelectors.ruleEditor.choiceModels.STATEMENT + " .child-choice-name").click();
+
+ cy.getRuleEditorIframe().find(formsSelectors.ruleEditor.choiceModels.STATEMENT + " .expeditor-customoverlay.is-open coral-selectlist-item[value='EVENT_SCRIPTS']")
+ .click({force: true});
+
+ // select the component for which rule is to written i.e. Button here
+ cy.getRuleEditorIframe().find(formsSelectors.ruleEditor.choiceModels.EVENT_AND_COMPARISON_OPERATOR + " .choice-view-default").should("exist");
+ cy.getRuleEditorIframe().find(formsSelectors.ruleEditor.choiceModels.EVENT_AND_COMPARISON_OPERATOR + " .choice-view-default").click();
+
+ // IS CLICKED option not existing in 'OPERATIONS' dropdown
+ cy.getRuleEditorIframe().find(formsSelectors.ruleEditor.operator.IS_CLICKED).should("exist");
+ cy.getRuleEditorIframe().find(formsSelectors.ruleEditor.operator.IS_CLICKED).click();
+
+ // check and click on dropdown to view the actions available
+ cy.getRuleEditorIframe().find(formsSelectors.ruleEditor.choiceModels.BLOCK_STATEMENT + " .choice-view-default").should("exist");
+ cy.getRuleEditorIframe().find(formsSelectors.ruleEditor.choiceModels.BLOCK_STATEMENT + " .choice-view-default").click();
+
+ // select HIDE action from dropdown
+ cy.getRuleEditorIframe().find(formsSelectors.ruleEditor.operator.NAVIGATE_IN_PANEL).should("exist");
+ cy.getRuleEditorIframe().find(formsSelectors.ruleEditor.operator.NAVIGATE_IN_PANEL).click({force: true});
+
+ cy.getRuleEditorIframe().find(".PANEL_FOCUS_OPTION").should("exist");
+ cy.getRuleEditorIframe().find(".PANEL_FOCUS_OPTION").click();
+
+ cy.getRuleEditorIframe().find(".PANEL_FOCUS_OPTION coral-selectlist [value='PREVIOUS_ITEM']").click({force: true});
+
+ cy.getRuleEditorIframe().find(".terminal-view.PANEL.VARIABLE").should("be.visible");
+ cy.getRuleEditorIframe().find(".terminal-view.PANEL.VARIABLE").click();
+
+ cy.getRuleEditorIframe().find(".terminal-view.PANEL.VARIABLE coral-overlay.is-open .expression-selectlist coral-selectlist-item:first").click({force: true});
+
+ cy.getRuleEditorIframe().find(formsSelectors.ruleEditor.action.saveRule).should("exist");
+ cy.getRuleEditorIframe().find(formsSelectors.ruleEditor.action.saveRule).click();
+
+ // check if rule is created
+ cy.getRuleEditorIframe().find(formsSelectors.ruleEditor.ruleSummary.CREATED_RULE).should("exist");
+ }
+})
diff --git a/ui.tests/test-module/specs/ruleeditor/ruleEditor.authoring.cy.js b/ui.tests/test-module/specs/ruleeditor/authoring/ruleEditor.authoring.cy.js
similarity index 73%
rename from ui.tests/test-module/specs/ruleeditor/ruleEditor.authoring.cy.js
rename to ui.tests/test-module/specs/ruleeditor/authoring/ruleEditor.authoring.cy.js
index b2875adb53..f07808d426 100644
--- a/ui.tests/test-module/specs/ruleeditor/ruleEditor.authoring.cy.js
+++ b/ui.tests/test-module/specs/ruleeditor/authoring/ruleEditor.authoring.cy.js
@@ -1,7 +1,7 @@
-const commons = require('../../libs/commons/commons'),
- sitesSelectors = require('../../libs/commons/sitesSelectors'),
- formsSelectors = require('../../libs/commons/guideSelectors'),
- afConstants = require('../../libs/commons/formsConstants');
+const commons = require('../../../libs/commons/commons'),
+ sitesSelectors = require('../../../libs/commons/sitesSelectors'),
+ formsSelectors = require('../../../libs/commons/guideSelectors'),
+ afConstants = require('../../../libs/commons/formsConstants');
describe('Rule editor authoring sanity for core-components',function(){
let toggle_array = [];
@@ -76,6 +76,113 @@ describe('Rule editor authoring sanity for core-components',function(){
cy.getRuleEditorIframe().find(formsSelectors.ruleEditor.action.closeRuleEditor).click();
}
+ const createRuleToHideTextInputOnEqualityOperator = function() {
+ // Edit rule option not existing on button toolbar
+ cy.get(formsSelectors.ruleEditor.action.editRule).should("exist");
+ cy.initializeEventHandlerOnChannel("af-rule-editor-initialized").as("isRuleEditorInitialized");
+ cy.get(formsSelectors.ruleEditor.action.editRule).click();
+
+ // click on create option from rule editor header
+ cy.get("@isRuleEditorInitialized").its('done').should('equal', true);
+ cy.getRuleEditorIframe().find(formsSelectors.ruleEditor.action.createRuleButton).should("be.visible").click();
+
+ cy.getRuleEditorIframe().find(formsSelectors.ruleEditor.action.sideToggleButton + ":first").click();
+
+ cy.getRuleEditorIframe().find(formsSelectors.ruleEditor.choiceModels.STATEMENT + " .child-choice-name").should("exist");
+ cy.getRuleEditorIframe().find(formsSelectors.ruleEditor.choiceModels.STATEMENT + " .child-choice-name").click();
+
+ cy.getRuleEditorIframe().find(formsSelectors.ruleEditor.choiceModels.STATEMENT + " .expeditor-customoverlay.is-open coral-selectlist-item[value='EVENT_SCRIPTS']")
+ .click({force: true});
+
+ // select the component for which rule is to written i.e. Button here
+ cy.getRuleEditorIframe().find(formsSelectors.ruleEditor.choiceModels.EVENT_AND_COMPARISON_OPERATOR + " .choice-view-default").should("exist");
+ cy.getRuleEditorIframe().find(formsSelectors.ruleEditor.choiceModels.EVENT_AND_COMPARISON_OPERATOR + " .choice-view-default").click();
+
+ // EQUALS option not existing in 'OPERATIONS' dropdown
+ cy.getRuleEditorIframe().find(formsSelectors.ruleEditor.operator.EQUALS_TO).should("exist");
+ cy.getRuleEditorIframe().find(formsSelectors.ruleEditor.operator.EQUALS_TO).click();
+
+ cy.getRuleEditorIframe().find(formsSelectors.ruleEditor.choiceModels.STRING_LITERAL).type('abc');
+ cy.getRuleEditorIframe().find(".delete-else-button").click();
+
+ // check and click on dropdown to view the actions available
+ cy.getRuleEditorIframe().find(formsSelectors.ruleEditor.choiceModels.BLOCK_STATEMENT + " .choice-view-default").should("exist");
+ cy.getRuleEditorIframe().find(formsSelectors.ruleEditor.choiceModels.BLOCK_STATEMENT + " .choice-view-default").click();
+
+ // select HIDE action from dropdown
+ cy.getRuleEditorIframe().find(formsSelectors.ruleEditor.operator.HIDE).should("exist");
+ cy.getRuleEditorIframe().find(formsSelectors.ruleEditor.operator.HIDE).click();
+
+ cy.getRuleEditorIframe().find(".terminal-view.AFCOMPONENT.VARIABLE").should("be.visible");
+ cy.getRuleEditorIframe().find(".terminal-view.AFCOMPONENT.VARIABLE").click();
+
+ cy.getRuleEditorIframe().find(".terminal-view.AFCOMPONENT.VARIABLE coral-overlay.is-open .expression-selectlist coral-selectlist-item:first").click({force: true});
+
+ cy.intercept('POST', /content\/forms\/af\/core-components-it\/samples\/ruleeditor\/blank.*/).as('ruleEditorRequest');
+
+ cy.getRuleEditorIframe().find(formsSelectors.ruleEditor.action.saveRule).should("exist");
+ cy.getRuleEditorIframe().find(formsSelectors.ruleEditor.action.saveRule).click();
+
+ cy.wait('@ruleEditorRequest').then((interception) => {
+ expect(interception.response.statusCode).to.equal(201);
+ const submittedData = Object.fromEntries(new URLSearchParams(interception.request.body));
+ expect(submittedData[":content"]).contains("\"fd:events\":{\"change\":[\"if(contains($event.payload.changes[].propertyName, 'value'), if($field.$value == 'abc', {visible : false()}, {}), {})\"]}");
+ });
+
+ // check and close rule editor
+ cy.getRuleEditorIframe().find(formsSelectors.ruleEditor.action.closeRuleEditor).should("exist");
+ cy.getRuleEditorIframe().find(formsSelectors.ruleEditor.action.closeRuleEditor).click();
+ }
+
+ const createRuleToValidateDate = function() {
+ // Edit rule option not existing on button toolbar
+ cy.get(formsSelectors.ruleEditor.action.editRule).should("exist");
+ cy.initializeEventHandlerOnChannel("af-rule-editor-initialized").as("isRuleEditorInitialized");
+ cy.get(formsSelectors.ruleEditor.action.editRule).click();
+
+ // click on create option from rule editor header
+ cy.get("@isRuleEditorInitialized").its('done').should('equal', true);
+ cy.getRuleEditorIframe().find(formsSelectors.ruleEditor.action.createRuleButton).should("be.visible").click();
+
+ cy.getRuleEditorIframe().find(formsSelectors.ruleEditor.action.sideToggleButton + ":first").click();
+
+ cy.getRuleEditorIframe().find(formsSelectors.ruleEditor.choiceModels.STATEMENT + " .child-choice-name").should("exist");
+ cy.getRuleEditorIframe().find(formsSelectors.ruleEditor.choiceModels.STATEMENT + " .child-choice-name").click();
+
+ cy.getRuleEditorIframe().find(formsSelectors.ruleEditor.choiceModels.STATEMENT + " .expeditor-customoverlay.is-open coral-selectlist-item[value='VALIDATE_EXPRESSION']")
+ .click({force: true});
+
+ // select the component for which rule is to written i.e. Button here
+ cy.getRuleEditorIframe().find(".COMPARISON_EXPRESSION .sequence-view-cell .EXPRESSION").first().click();
+ cy.getRuleEditorIframe().find(".COMPARISON_EXPRESSION .sequence-view-cell .EXPRESSION").first().find("coral-selectlist-item[title='Date Input']:first").click();
+
+ cy.getRuleEditorIframe().find(".COMPARISON_EXPRESSION .sequence-view-cell .OPERATOR").click();
+ cy.getRuleEditorIframe().find("coral-selectlist-item[value='IS_BEFORE']").click();
+
+ cy.getRuleEditorIframe().find(".COMPARISON_EXPRESSION .sequence-view-cell .EXPRESSION").last().click();
+ cy.getRuleEditorIframe().find(".COMPARISON_EXPRESSION .sequence-view-cell .EXPRESSION").last().find(".selectlist-header").click();
+ cy.getRuleEditorIframe().find("coral-selectlist-item[value='FUNCTION_CALL']").click();
+ cy.getRuleEditorIframe().find(".COMPARISON_EXPRESSION .sequence-view-cell .EXPRESSION").last().click();
+ cy.getRuleEditorIframe().find(".COMPARISON_EXPRESSION .sequence-view-cell .EXPRESSION").last().find("coral-selectlist-item[value='today']").click();
+
+ cy.intercept('POST', /content\/forms\/af\/core-components-it\/samples\/ruleeditor\/blank.*/).as('ruleEditorRequest');
+
+ cy.getRuleEditorIframe().find(formsSelectors.ruleEditor.action.saveRule).should("exist");
+ cy.getRuleEditorIframe().find(formsSelectors.ruleEditor.action.saveRule).click();
+ cy.wait('@ruleEditorRequest').then((interception) => {
+ expect(interception.response.statusCode).to.equal(201);
+ const submittedData = Object.fromEntries(new URLSearchParams(interception.request.body));
+ expect(submittedData[":content"]).contains("dateToDaysSinceEpoch($field.$value) {
- cy.fetchFeatureToggles().then((response) => {
- if (response.status === 200) {
- toggle_array = response.body.enabled;
- }
- });
- });
-
- /**
- * initialization of form container before every test
- * */
- beforeEach(() => {
- cy.previewForm(formPath).then(p => {
- formContainer = p;
- });
- });
-
- if (cy.af.isLatestAddon()) {
- it("should have merged custom function list registered in FunctionRuntime from both clientlibs", () => {
- expect(formContainer, "formcontainer is initialized").to.not.be.null;
- let func;
- cy.window().then(win => {
- func = win.FormView.FunctionRuntime.customFunctions.testFunction1; // from corecomponent.it.customfunction
- expect(func).to.not.be.null;
- expect(func).to.not.be.undefined;
-
- func = win.FormView.FunctionRuntime.customFunctions.testSubmitFormPreprocessor; // from corecomponent.it.customfunction
- expect(func).to.not.be.null;
- expect(func).to.not.be.undefined;
-
- func = win.FormView.FunctionRuntime.customFunctions.testSetProperty; // from corecomponent.it.customfunction2
- expect(func).to.not.be.null;
- expect(func).to.not.be.undefined;
- })
- })
- }
-
- /**
- * Runtime ruleSanity for button to change label of textbox
- * [when button is clicked the textbox field label should change using custom function]
- */
- it("should change textinput label on button click", () => {
- if (cy.af.isLatestAddon() && toggle_array.includes("FT_FORMS-11541")) {
- expect(formContainer, "formcontainer is initialized").to.not.be.null;
- cy.get(`.cmp-adaptiveform-button__widget`).click()
- const [textbox1, textBox1FieldView] = Object.entries(formContainer._fields)[0];
- cy.get(`#${textbox1}`).find("div > label").should('have.text', "Changed Label")
- }
- })
-})
-
-describe("Rule editor submission handler runtime", () => {
- let toggle_array = [];
-
- before(() => {
- cy.fetchFeatureToggles().then((response) => {
- if (response.status === 200) {
- toggle_array = response.body.enabled;
- }
- });
- });
- const submitSuccessHardcodedHandler = "content/forms/af/core-components-it/samples/ruleeditor/submit/submitsuccesshardcodedhandler.html"
- const submitErrorHardcodedHandler = "content/forms/af/core-components-it/samples/ruleeditor/submit/submiterrorhardcodedhandler.html"
- const submitDefaultSuccessHandler = "content/forms/af/core-components-it/samples/ruleeditor/submit/submitdefaultsuccesshandler.html"
- const submitDefaultErrorHandler = "content/forms/af/core-components-it/samples/ruleeditor/submit/submitdefaulterrorhandler.html"
- const submitCustomSuccessHandler = "content/forms/af/core-components-it/samples/ruleeditor/submit/submitcustomsuccesshandler.html"
- const submitCustomErrorHandler = "content/forms/af/core-components-it/samples/ruleeditor/submit/submitcustomerrorhandler.html"
- const bemBlock = 'cmp-button'
- const IS = "adaptiveFormButton"
- const selectors = {
- submit: `[data-cmp-is="${IS}"]`
- }
-
- let formContainer = null;
-
- it("Hardcoded submitSuccess handler should handle successful form submission", () => {
- cy.previewForm(submitSuccessHardcodedHandler);
- cy.get(`.cmp-adaptiveform-button__widget`).click().then(x => {
- cy.get('body').should('contain', "Thank you for submitting the form.\n")
- });
- });
-
- it("Default submitSuccess handler should handle successful form submission", () => {
- if (cy.af.isLatestAddon() && toggle_array.includes("FT_FORMS-13209")) {
- cy.previewForm(submitDefaultSuccessHandler);
- cy.get(`.cmp-adaptiveform-button__widget`).click().then(x => {
- cy.get('body').should('contain', "Thank you for submitting the form.\n")
- });
- }
- });
-
- it("Custom submitSuccess handler should handle successful form submission", () => {
- if (cy.af.isLatestAddon() && toggle_array.includes("FT_FORMS-13209")) {
- cy.previewForm(submitCustomSuccessHandler);
- cy.get(`.cmp-adaptiveform-button__widget`).click().then(x => {
- cy.get('.modal .success-message').should('contain', "Thank you for submitting the form.")
- });
- }
- });
-
- it("Hardcoded submitError handler should handle form submission error", () => {
- cy.previewForm(submitErrorHardcodedHandler);
-
- cy.window().then(win => {
- let alertFired = false;
-
- // Stub the window alert to capture the alert message and set alertFired to true
- cy.stub(win, 'alert').callsFake((message) => {
- expect(message).to.equal('Encountered an internal error while submitting the form.');
- alertFired = true;
- });
-
- // Click the submit button
- cy.get('.cmp-adaptiveform-button__widget').click().then(() => {
- // Use cy.wrap to ensure Cypress waits for the promise to resolve
- cy.wrap(null).should(() => {
- expect(alertFired).to.be.true;
- });
- });
- });
- });
-
- it("Default submitError handler should handle form submission error", () => {
- if (cy.af.isLatestAddon() && toggle_array.includes("FT_FORMS-13209")) {
- cy.previewForm(submitDefaultErrorHandler);
-
- cy.window().then(win => {
- let alertFired = false;
-
- // Stub the window alert to capture the alert message and set alertFired to true
- cy.stub(win, 'alert').callsFake((message) => {
- expect(message).to.equal('Form submission failed!');
- alertFired = true;
- });
-
- // Click the submit button
- cy.get('.cmp-adaptiveform-button__widget').click().then(() => {
- // Use cy.wrap to ensure Cypress waits for the promise to resolve
- cy.wrap(null).should(() => {
- expect(alertFired).to.be.true;
- });
- });
- });
- }
- });
-
- it("Custom submitError handler should handle form submission error", () => {
- if (cy.af.isLatestAddon() && toggle_array.includes("FT_FORMS-13209")) {
- cy.previewForm(submitCustomErrorHandler);
- let alertFired = false;
- cy.get(`.cmp-adaptiveform-button__widget`).click().then(x => {
- cy.get('.modal .error-message').should('contain', "Custom Form submission failed!")
- });
- }
- });
-})
-
-describe("Rule editor save handler runtime", () => {
-
- let toggle_array = [];
-
- before(() => {
- cy.fetchFeatureToggles().then((response) => {
- if (response.status === 200) {
- toggle_array = response.body.enabled;
- }
- });
- });
-
- const saveRunTime = "content/forms/af/core-components-it/samples/ruleeditor/save/saveruntime.html"
-
- it("should save formData on button click", () => {
- if (toggle_array.includes("FT_FORMS-11581")) {
-
- const saveApiResponse = {
- 'draftId': 'ABC'
- };
- // Rule when button is clicked then save call should trigger
- cy.intercept('POST' , '**/adobe/forms/af/save/*', saveApiResponse).as('afSave');
-
- cy.previewForm(saveRunTime);
-
- cy.get(`.cmp-adaptiveform-button__widget`).click();
-
- cy.wait('@afSave').then(({request, response}) => {
- // Check the request payload
- expect(request.body).to.be.not.null;
-
- expect(response.statusCode).to.equal(200);
- expect(response.body).to.be.not.null;
- expect(response.body.draftId).to.equal('ABC');
- });
- }
- })
-})
-
-
-describe('Rule editor properties on form initialize test', () => {
- const pagePath = "content/forms/af/core-components-it/samples/ruleeditor/set_property_test.html"
- let formContainer = null;
-
- before(() => {
- cy.previewForm(pagePath).then(p => {
- formContainer = p;
- })
- });
-
- it("Check properties are properly set on form initialize", () => {
- const [checkBoxId, checkBoxFieldView] = Object.entries(formContainer._fields)[0]
- const [fileInputId, fileInputView] = Object.entries(formContainer._fields)[1]
-
- const checkProperties = (id, bemBlock, labelSelector, expectedLabel, expectedDescription) => {
- cy.get(`#${id}`).invoke('attr', 'data-cmp-required').should('eq', 'true');
- cy.get(`#${id} ${labelSelector}`)
- .should('have.text', expectedLabel);
- // cy.get(`#${id}__longdescription p`)
- // .should('have.text', expectedDescription);
-
- cy.get(`#${id}`).find(`.${bemBlock}__questionmark`).click();
- // long description should be shown
- cy.get(`#${id}`).find(`.${bemBlock}__longdescription`).invoke('attr', 'data-cmp-visible')
- .should('not.exist');
- cy.get(`#${id}`).find(`.${bemBlock}__longdescription`)
- .should('contain.text', expectedDescription);
- }
- checkProperties(checkBoxId, 'cmp-adaptiveform-checkboxgroup', '.cmp-adaptiveform-checkboxgroup__label', 'Updated CheckBox', 'This is a long description of checkboxgroup');
- checkProperties(fileInputId, 'cmp-adaptiveform-fileinput', '.cmp-adaptiveform-fileinput__label', 'Updated File Input Label', 'File Input Description');
- });
-})
diff --git a/ui.tests/test-module/specs/ruleeditor/customFunction.runtime.cy.js b/ui.tests/test-module/specs/ruleeditor/runtime/customFunction.runtime.cy.js
similarity index 100%
rename from ui.tests/test-module/specs/ruleeditor/customFunction.runtime.cy.js
rename to ui.tests/test-module/specs/ruleeditor/runtime/customFunction.runtime.cy.js
diff --git a/ui.tests/test-module/specs/ruleeditor/runtime/navigatePanel.runtime.cy.js b/ui.tests/test-module/specs/ruleeditor/runtime/navigatePanel.runtime.cy.js
new file mode 100644
index 0000000000..b96c97a475
--- /dev/null
+++ b/ui.tests/test-module/specs/ruleeditor/runtime/navigatePanel.runtime.cy.js
@@ -0,0 +1,107 @@
+describe("Rule editor navigate in panel runtime", () => {
+ let toggle_array = [];
+ before(() => {
+ cy.fetchFeatureToggles().then((response) => {
+ if (response.status === 200) {
+ toggle_array = response.body.enabled;
+ }
+ });
+ });
+
+ const formsPage = "content/forms/af/core-components-it/samples/ruleeditor/navigate-in-panel/basic.html"
+ let formContainer = null;
+
+ it("should navigate to next item in panel", () => {
+ if(toggle_array.includes("FT_FORMS-10781")) {
+ cy.previewForm(formsPage);
+ cy.get('#textinput-b1a2a445a1-widget').click();
+ cy.get('#button-46e2481a3f-widget > .cmp-adaptiveform-button__text').click();
+ cy.get('#button-46e2481a3f-widget > .cmp-adaptiveform-button__text').click();
+
+ cy.focused().should('have.attr', 'id', 'textinput-4ab644c5b9-widget');
+ }
+ });
+
+ it("should navigate to previous item in panel", () => {
+ if(toggle_array.includes("FT_FORMS-10781")) {
+ cy.previewForm(formsPage);
+ cy.get('#textinput-4ab644c5b9-widget').click();
+ cy.get('#button-56f02db62a-widget > .cmp-adaptiveform-button__text').click();
+ cy.get('#button-56f02db62a-widget > .cmp-adaptiveform-button__text').click();
+
+ cy.focused().should('have.attr', 'id', 'textinput-b1a2a445a1-widget');
+ }
+ });
+
+ it("should navigate to next panel in wizard", () => {
+ if(toggle_array.includes("FT_FORMS-10781")) {
+ cy.previewForm(formsPage);
+ cy.get('#textinput-e2985267ed-widget').click();
+ cy.get('#button-aae7ec869b-widget > .cmp-adaptiveform-button__text').click();
+ cy.get('#button-aae7ec869b-widget > .cmp-adaptiveform-button__text').click();
+
+ cy.focused().should('have.attr', 'id', 'textinput-adb1f55685-widget');
+ }
+ });
+
+ it("should navigate to previous panel in wizard", () => {
+ if(toggle_array.includes("FT_FORMS-10781")) {
+ cy.previewForm(formsPage);
+ cy.get('#textinput-e2985267ed-widget').click();
+ cy.get('#button-aae7ec869b-widget > .cmp-adaptiveform-button__text').click();
+ cy.get('#button-aae7ec869b-widget > .cmp-adaptiveform-button__text').click();
+ cy.get('#button-eb427c0e82-widget > .cmp-adaptiveform-button__text').click();
+ cy.get('#button-eb427c0e82-widget > .cmp-adaptiveform-button__text').click();
+
+ cy.focused().should('have.attr', 'id', 'textinput-e2985267ed-widget');
+ }
+ });
+
+ it("should navigate to next panel in HorizontalTabs", () => {
+ if(toggle_array.includes("FT_FORMS-10781")) {
+ cy.previewForm(formsPage);
+ cy.get('#textinput-cc08c3e84a-widget').click();
+ cy.get('#button-4e44ce2042-widget > .cmp-adaptiveform-button__text').click();
+ cy.get('#button-4e44ce2042-widget > .cmp-adaptiveform-button__text').click();
+
+ cy.focused().should('have.attr', 'id', 'textinput-77fa9bd1dc-widget');
+ }
+ });
+
+ it("should navigate to previous panel in HorizontalTabs", () => {
+ if(toggle_array.includes("FT_FORMS-10781")) {
+ cy.previewForm(formsPage);
+ cy.get('#textinput-cc08c3e84a-widget').click();
+ cy.get('#button-4e44ce2042-widget > .cmp-adaptiveform-button__text').click();
+ cy.get('#button-4e44ce2042-widget > .cmp-adaptiveform-button__text').click();
+ cy.get('#button-e75d381952-widget > .cmp-adaptiveform-button__text').click();
+ cy.get('#button-e75d381952-widget > .cmp-adaptiveform-button__text').click();
+
+ cy.focused().should('have.attr', 'id', 'textinput-cc08c3e84a-widget');
+ }
+ });
+
+ it("should navigate to next panel in VerticalTabs", () => {
+ if(toggle_array.includes("FT_FORMS-10781")) {
+ cy.previewForm(formsPage);
+ cy.get('#textinput-802818ca90-widget').click();
+ cy.get('#button-ff091257b7-widget > .cmp-adaptiveform-button__text').click();
+ cy.get('#button-ff091257b7-widget > .cmp-adaptiveform-button__text').click();
+
+ cy.focused().should('have.attr', 'id', 'textinput-d227c4c481-widget');
+ }
+ });
+
+ it("should navigate to previous panel in VerticalTabs", () => {
+ if(toggle_array.includes("FT_FORMS-10781")) {
+ cy.previewForm(formsPage);
+ cy.get('#textinput-802818ca90-widget').click();
+ cy.get('#button-ff091257b7-widget > .cmp-adaptiveform-button__text').click();
+ cy.get('#button-ff091257b7-widget > .cmp-adaptiveform-button__text').click();
+ cy.get('#button-2c88dbcca7-widget > .cmp-adaptiveform-button__text').click();
+ cy.get('#button-2c88dbcca7-widget > .cmp-adaptiveform-button__text').click();
+
+ cy.focused().should('have.attr', 'id', 'textinput-802818ca90-widget');
+ }
+ });
+})
diff --git a/ui.tests/test-module/specs/ruleeditor/runtime/ruleEditorSanity.runtime.cy.js b/ui.tests/test-module/specs/ruleeditor/runtime/ruleEditorSanity.runtime.cy.js
new file mode 100644
index 0000000000..92a57af3a3
--- /dev/null
+++ b/ui.tests/test-module/specs/ruleeditor/runtime/ruleEditorSanity.runtime.cy.js
@@ -0,0 +1,120 @@
+describe('Rule editor runtime sanity for core-components',function(){
+ const formPath = "/content/forms/af/core-components-it/samples/ruleeditor/basic.html";
+ let formContainer = null;
+ let toggle_array = [];
+
+ before(() => {
+ cy.fetchFeatureToggles().then((response) => {
+ if (response.status === 200) {
+ toggle_array = response.body.enabled;
+ }
+ });
+ });
+
+ /**
+ * initialization of form container before every test
+ * */
+ beforeEach(() => {
+ cy.previewForm(formPath).then(p => {
+ formContainer = p;
+ });
+ });
+
+ it("should have merged custom function list registered in FunctionRuntime from both clientlibs", () => {
+ expect(formContainer, "formcontainer is initialized").to.not.be.null;
+ let func;
+ cy.window().then(win => {
+ func = win.FormView.FunctionRuntime.customFunctions.testFunction1; // from corecomponent.it.customfunction
+ expect(func).to.not.be.null;
+ expect(func).to.not.be.undefined;
+
+ func = win.FormView.FunctionRuntime.customFunctions.testSubmitFormPreprocessor; // from corecomponent.it.customfunction
+ expect(func).to.not.be.null;
+ expect(func).to.not.be.undefined;
+
+ func = win.FormView.FunctionRuntime.customFunctions.testSetProperty; // from corecomponent.it.customfunction2
+ expect(func).to.not.be.null;
+ expect(func).to.not.be.undefined;
+ })
+ })
+
+ if (cy.af.isLatestAddon()) {
+ it("should validate start and end date", () => {
+ expect(formContainer, "formcontainer is initialized").to.not.be.null;
+ console.log(formContainer._fields);
+ const [startDate, startDateFieldView] = Object.entries(formContainer._fields)[0];
+ cy.get(`#${startDate}`).find("input").clear().type(getCurrentDateOffsetBy(-1)).blur().then(x => {
+ const startDateModel = formContainer._model.getElement(startDate);
+ expect(startDateModel.getState().valid).to.equal(true);
+ });
+ const [endDate, endDateFieldView] = Object.entries(formContainer._fields)[1];
+ cy.get(`#${endDate}`).find("input").clear().type(getCurrentDateOffsetBy(+1)).blur().then(x => {
+ const endDateModel = formContainer._model.getElement(endDate);
+ expect(endDateModel.getState().valid).to.equal(true);
+ });
+
+ //invalid start value
+ cy.get(`#${startDate}`).find("input").clear().type(getCurrentDateOffsetBy(+1)).blur().then(x => {
+ const startDateModel = formContainer._model.getElement(startDate);
+ expect(startDateModel.getState().valid).to.equal(false);
+ });
+ })
+ }
+
+ function getCurrentDateOffsetBy(days) {
+ const today = new Date();
+ today.setDate(today.getDate() + days);
+
+ const day = String(today.getDate()).padStart(2, '0'); // Get day and pad with leading zero if necessary
+ const month = String(today.getMonth() + 1).padStart(2, '0'); // Get month (0-based index) and pad with leading zero if necessary
+ const year = today.getFullYear(); // Get full year
+
+ return `${year}-${month}-${day}`; // Format the date as yyyy-mm-dd
+ }
+
+ /**
+ * Runtime ruleSanity for button to change label of textbox
+ * [when button is clicked the textbox field label should change using custom function]
+ */
+ it("should change textinput label on button click", () => {
+ if (toggle_array.includes("FT_FORMS-11541")) {
+ expect(formContainer, "formcontainer is initialized").to.not.be.null;
+ cy.get(`.cmp-adaptiveform-button__widget`).click()
+ const [textbox1, textBox1FieldView] = Object.entries(formContainer._fields)[2];
+ cy.get(`#${textbox1}`).find("div > label").should('have.text', "Changed Label")
+ }
+ })
+})
+
+describe('Rule editor properties on form initialize test', () => {
+ const pagePath = "content/forms/af/core-components-it/samples/ruleeditor/set_property_test.html"
+ let formContainer = null;
+
+ before(() => {
+ cy.previewForm(pagePath).then(p => {
+ formContainer = p;
+ })
+ });
+
+ it("Check properties are properly set on form initialize", () => {
+ const [checkBoxId, checkBoxFieldView] = Object.entries(formContainer._fields)[0]
+ const [fileInputId, fileInputView] = Object.entries(formContainer._fields)[1]
+
+ const checkProperties = (id, bemBlock, labelSelector, expectedLabel, expectedDescription) => {
+ cy.get(`#${id}`).invoke('attr', 'data-cmp-required').should('eq', 'true');
+ cy.get(`#${id} ${labelSelector}`)
+ .should('have.text', expectedLabel);
+ // cy.get(`#${id}__longdescription p`)
+ // .should('have.text', expectedDescription);
+
+ cy.get(`#${id}`).find(`.${bemBlock}__questionmark`).click();
+ // long description should be shown
+ cy.get(`#${id}`).find(`.${bemBlock}__longdescription`).invoke('attr', 'data-cmp-visible')
+ .should('not.exist');
+ cy.get(`#${id}`).find(`.${bemBlock}__longdescription`)
+ .should('contain.text', expectedDescription);
+ }
+ checkProperties(checkBoxId, 'cmp-adaptiveform-checkboxgroup', '.cmp-adaptiveform-checkboxgroup__label', 'Updated CheckBox', 'This is a long description of checkboxgroup');
+ checkProperties(fileInputId, 'cmp-adaptiveform-fileinput', '.cmp-adaptiveform-fileinput__label', 'Updated File Input Label', 'File Input Description');
+ });
+})
diff --git a/ui.tests/test-module/specs/ruleeditor/runtime/saveHandler.runtime.cy.js b/ui.tests/test-module/specs/ruleeditor/runtime/saveHandler.runtime.cy.js
new file mode 100644
index 0000000000..88f0d4dc0e
--- /dev/null
+++ b/ui.tests/test-module/specs/ruleeditor/runtime/saveHandler.runtime.cy.js
@@ -0,0 +1,38 @@
+describe("Rule editor save handler runtime", () => {
+
+ let toggle_array = [];
+
+ before(() => {
+ cy.fetchFeatureToggles().then((response) => {
+ if (response.status === 200) {
+ toggle_array = response.body.enabled;
+ }
+ });
+ });
+
+ const saveRunTime = "content/forms/af/core-components-it/samples/ruleeditor/save/saveruntime.html"
+
+ it("should save formData on button click", () => {
+ if (toggle_array.includes("FT_FORMS-11581")) {
+
+ const saveApiResponse = {
+ 'draftId': 'ABC'
+ };
+ // Rule when button is clicked then save call should trigger
+ cy.intercept('POST' , '**/adobe/forms/af/save/*', saveApiResponse).as('afSave');
+
+ cy.previewForm(saveRunTime);
+
+ cy.get(`.cmp-adaptiveform-button__widget`).click();
+
+ cy.wait('@afSave').then(({request, response}) => {
+ // Check the request payload
+ expect(request.body).to.be.not.null;
+
+ expect(response.statusCode).to.equal(200);
+ expect(response.body).to.be.not.null;
+ expect(response.body.draftId).to.equal('ABC');
+ });
+ }
+ })
+})
\ No newline at end of file
diff --git a/ui.tests/test-module/specs/ruleeditor/runtime/submitHandler.runtime.cy.js b/ui.tests/test-module/specs/ruleeditor/runtime/submitHandler.runtime.cy.js
new file mode 100644
index 0000000000..6183a34b4c
--- /dev/null
+++ b/ui.tests/test-module/specs/ruleeditor/runtime/submitHandler.runtime.cy.js
@@ -0,0 +1,106 @@
+describe("Rule editor submission handler runtime", () => {
+ let toggle_array = [];
+
+ before(() => {
+ cy.fetchFeatureToggles().then((response) => {
+ if (response.status === 200) {
+ toggle_array = response.body.enabled;
+ }
+ });
+ });
+ const submitSuccessHardcodedHandler = "content/forms/af/core-components-it/samples/ruleeditor/submit/submitsuccesshardcodedhandler.html"
+ const submitErrorHardcodedHandler = "content/forms/af/core-components-it/samples/ruleeditor/submit/submiterrorhardcodedhandler.html"
+ const submitDefaultSuccessHandler = "content/forms/af/core-components-it/samples/ruleeditor/submit/submitdefaultsuccesshandler.html"
+ const submitDefaultErrorHandler = "content/forms/af/core-components-it/samples/ruleeditor/submit/submitdefaulterrorhandler.html"
+ const submitCustomSuccessHandler = "content/forms/af/core-components-it/samples/ruleeditor/submit/submitcustomsuccesshandler.html"
+ const submitCustomErrorHandler = "content/forms/af/core-components-it/samples/ruleeditor/submit/submitcustomerrorhandler.html"
+ const bemBlock = 'cmp-button'
+ const IS = "adaptiveFormButton"
+ const selectors = {
+ submit: `[data-cmp-is="${IS}"]`
+ }
+
+ let formContainer = null;
+
+ it("Hardcoded submitSuccess handler should handle successful form submission", () => {
+ cy.previewForm(submitSuccessHardcodedHandler);
+ cy.get(`.cmp-adaptiveform-button__widget`).click().then(x => {
+ cy.get('body').should('contain', "Thank you for submitting the form.\n")
+ });
+ });
+
+ it("Default submitSuccess handler should handle successful form submission", () => {
+ if (toggle_array.includes("FT_FORMS-13209")) {
+ cy.previewForm(submitDefaultSuccessHandler);
+ cy.get(`.cmp-adaptiveform-button__widget`).click().then(x => {
+ cy.get('body').should('contain', "Thank you for submitting the form.\n")
+ });
+ }
+ });
+
+ it("Custom submitSuccess handler should handle successful form submission", () => {
+ if (toggle_array.includes("FT_FORMS-13209")) {
+ cy.previewForm(submitCustomSuccessHandler);
+ cy.get(`.cmp-adaptiveform-button__widget`).click().then(x => {
+ cy.get('.modal .success-message').should('contain', "Thank you for submitting the form.")
+ });
+ }
+ });
+
+ it("Hardcoded submitError handler should handle form submission error", () => {
+ cy.previewForm(submitErrorHardcodedHandler);
+
+ cy.window().then(win => {
+ let alertFired = false;
+
+ // Stub the window alert to capture the alert message and set alertFired to true
+ cy.stub(win, 'alert').callsFake((message) => {
+ expect(message).to.equal('Encountered an internal error while submitting the form.');
+ alertFired = true;
+ });
+
+ // Click the submit button
+ cy.get('.cmp-adaptiveform-button__widget').click().then(() => {
+ // Use cy.wrap to ensure Cypress waits for the promise to resolve
+ cy.wrap(null).should(() => {
+ expect(alertFired).to.be.true;
+ });
+ });
+ });
+ });
+
+
+ it("Default submitError handler should handle form submission error", () => {
+ if (toggle_array.includes("FT_FORMS-13209")) {
+ cy.previewForm(submitDefaultErrorHandler);
+
+ cy.window().then(win => {
+ let alertFired = false;
+
+ // Stub the window alert to capture the alert message and set alertFired to true
+ cy.stub(win, 'alert').callsFake((message) => {
+ expect(message).to.equal('Form submission failed!');
+ alertFired = true;
+ });
+
+ // Click the submit button
+ cy.get('.cmp-adaptiveform-button__widget').click().then(() => {
+ // Use cy.wrap to ensure Cypress waits for the promise to resolve
+ cy.wrap(null).should(() => {
+ expect(alertFired).to.be.true;
+ });
+ });
+ });
+ }
+ });
+
+ it("Custom submitError handler should handle form submission error", () => {
+ if (toggle_array.includes("FT_FORMS-13209")) {
+ cy.previewForm(submitCustomErrorHandler);
+ let alertFired = false;
+ cy.get(`.cmp-adaptiveform-button__widget`).click().then(x => {
+ cy.get('.modal .error-message').should('contain', "Custom Form submission failed!")
+ });
+ }
+ });
+})
\ No newline at end of file
diff --git a/ui.tests/test-module/specs/ruleeditor/uichange.runtime.cy.js b/ui.tests/test-module/specs/ruleeditor/runtime/uichange.runtime.cy.js
similarity index 100%
rename from ui.tests/test-module/specs/ruleeditor/uichange.runtime.cy.js
rename to ui.tests/test-module/specs/ruleeditor/runtime/uichange.runtime.cy.js
diff --git a/ui.tests/test-module/specs/sites/captchaEmbed.runtime.cy.js b/ui.tests/test-module/specs/sites/captchaEmbed.runtime.cy.js
index f5f511aacc..11d46e4986 100644
--- a/ui.tests/test-module/specs/sites/captchaEmbed.runtime.cy.js
+++ b/ui.tests/test-module/specs/sites/captchaEmbed.runtime.cy.js
@@ -16,6 +16,7 @@
describe("Captcha In Sites Runtime Test", () => {
const pagePath = "content/forms/sites/core-components-it/site-with-captcha-afv2-form.html";
const hCaptchaPagePath = "content/forms/sites/core-components-it/site-with-hcaptcha-afv2-form.html";
+ const turnstilePagePath = "content/forms/sites/core-components-it/site-with-turnstile-afv2-form.html";
const FT_HCAPTCHA = "FT_FORMS-12407";
let formContainer = null;
@@ -89,4 +90,20 @@ describe("Captcha In Sites Runtime Test", () => {
})
}
})
+
+ it("turnstile should render when form is embedded in site", () => {
+ if (cy.af.isLatestAddon() && toggle_array.includes(FT_HCAPTCHA)) {
+ cy.previewForm(turnstilePagePath).then(p => {
+ formContainer = p;
+ 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);
+ cy.get('.cmp-adaptiveform-turnstile__widget').should('exist').then($iframe => {
+ cy.wrap($iframe).then($iframe => {
+ cy.window().should('have.property', 'turnstile').and('not.be.undefined');
+ });
+ });
+ })
+ }
+ })
+
})
diff --git a/ui.tests/test-module/specs/sites/inlineFormInSites.runtime.cy.js b/ui.tests/test-module/specs/sites/inlineFormInSites.runtime.cy.js
new file mode 100644
index 0000000000..4fd7906efd
--- /dev/null
+++ b/ui.tests/test-module/specs/sites/inlineFormInSites.runtime.cy.js
@@ -0,0 +1,69 @@
+/*******************************************************************************
+ * 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.
+ ******************************************************************************/
+import 'cypress-wait-until';
+
+describe("Captcha In Sites Runtime Test", () => {
+ const siteWithRecaptchaScore = "content/forms/sites/core-components-it/site-with-captcha-inline-form.html";
+
+ let formContainer = null;
+
+ function updateEnterpriseConfig(score) {
+ const secretKey = Cypress.env('RECAPTCHA_ENT_API_KEY');
+ cy.openPage("/mnt/overlay/fd/af/cloudservices/recaptcha/properties.html?item=%2Fconf%2Fcore-components-it%2Fsamples%2Frecaptcha%2Fbasic%2Fsettings%2Fcloudconfigs%2Frecaptcha%2Fentscore").then(x => {
+ cy.get('#recaptcha-cloudconfiguration-secret-key').clear().type(secretKey);
+ cy.get('#recaptcha-cloudconfiguration-threshold-score').clear().type(score);
+ cy.get("#shell-propertiespage-doneactivator").click();
+ })
+ }
+
+ it("submission should pass for enterprise score based captcha",() => {
+ if (cy.af.isLatestAddon()) {
+ updateEnterpriseConfig(0.5);
+ cy.previewForm(siteWithRecaptchaScore).then((p) => {
+ formContainer = p;
+ expect(formContainer, "formcontainer is initialized").to.not.be.null;
+ cy.get(`div.grecaptcha-badge`).should('exist').then(() => {
+ cy.intercept('POST', /\/adobe\/forms\/af\/submit\/.*/).as('submitForm');
+ const submitForm = () => {
+ cy.get(`.cmp-adaptiveform-button__widget`).click();
+
+ // Wait for the submitForm request
+ return cy.wait('@submitForm',{ timeout: 50000 }).then((interception) => {
+ if (interception.response.statusCode === 200) {
+ // Request succeeded
+ cy.log('Submit request succeeded');
+ return cy.wrap(true);
+ } else {
+ // Request failed
+ cy.log(`Submit request failed, retrying...`);
+ return cy.wrap(false);
+ }
+ });
+ };
+ // Need to submit multiple times until the form is submitted successfully
+ // Due to below error while validating recaptcha enterprise
+ // https://cloud.google.com/recaptcha-enterprise/docs/faq#returned_browser_error_when_creating_an_assessment_what_should_i_do_about_this
+ cy.waitUntil(() => submitForm(), {
+ errorMsg: 'Maximum retry limit reached, request did not succeed',
+ timeout: 50000, // Total timeout (10 seconds)
+ interval: 5000, // Interval between retries (1 second)
+ });
+ });
+ });
+ }
+ })
+
+})
diff --git a/ui.tests/test-module/specs/tabsontop/tabsontop.runtime.cy.js b/ui.tests/test-module/specs/tabsontop/tabsontop.runtime.cy.js
index e47f9aa364..94634c0248 100644
--- a/ui.tests/test-module/specs/tabsontop/tabsontop.runtime.cy.js
+++ b/ui.tests/test-module/specs/tabsontop/tabsontop.runtime.cy.js
@@ -90,10 +90,13 @@ describe("Form with Panel Container", () => {
tab2().should('have.class', 'cmp-tabs__tab--active');
tab2().should('have.attr', 'aria-selected', 'true');
tab1().should('have.attr', 'aria-selected', 'false');
+ tab1().should('have.class', 'cmp-tabs__tab--stepped');
tab1().click();
tab1().should('have.class', 'cmp-tabs__tab--active');
+ tab1().should('have.class', 'cmp-tabs__tab--stepped');
tab1().should('have.attr', 'aria-selected', 'true');
tab2().should('have.attr', 'aria-selected', 'false');
+ tab2().should('have.class', 'cmp-tabs__tab--stepped');
});
it("switch tab in runtime using keyboard", () => {
diff --git a/ui.tests/test-module/specs/tabsontop/tabsontop.runtime.repeatability.cy.js b/ui.tests/test-module/specs/tabsontop/tabsontop.runtime.repeatability.cy.js
index 68b9332bfa..0e0becc251 100644
--- a/ui.tests/test-module/specs/tabsontop/tabsontop.runtime.repeatability.cy.js
+++ b/ui.tests/test-module/specs/tabsontop/tabsontop.runtime.repeatability.cy.js
@@ -145,4 +145,40 @@ describe("Form with TabsOnTop Container", () => {
})
});
+ it("After clicking on every tab, if tab-1 is repeated, the repeated instance should not have stepped class", () => {
+ getTabs().should('have.length', 4);
+ getTabPanels().should('have.length', 4);
+ getTabAtIndex(0).should('have.class', 'cmp-tabs__tab--active');
+ getTabPanelAtIndex(0).should('have.class', 'cmp-tabs__tabpanel--active');
+ getTabAtIndex(1).click();
+ getTabAtIndex(1).should('have.class', 'cmp-tabs__tab--active');
+ getTabPanelAtIndex(1).should('have.class', 'cmp-tabs__tabpanel--active');
+ getTabAtIndex(0).should('have.class', 'cmp-tabs__tab--stepped');
+ getTabPanelAtIndex(0).should('have.class', 'cmp-tabs__tabpanel--stepped');
+ getTabAtIndex(2).click();
+ getTabAtIndex(2).should('have.class', 'cmp-tabs__tab--active');
+ getTabPanelAtIndex(2).should('have.class', 'cmp-tabs__tabpanel--active');
+ getTabAtIndex(1).should('have.class', 'cmp-tabs__tab--stepped');
+ getTabPanelAtIndex(1).should('have.class', 'cmp-tabs__tabpanel--stepped');
+ getTabAtIndex(0).should('have.class', 'cmp-tabs__tab--stepped');
+ getTabPanelAtIndex(0).should('have.class', 'cmp-tabs__tabpanel--stepped');
+ getTabAtIndex(3).click();
+ getTabAtIndex(3).should('have.class', 'cmp-tabs__tab--active');
+ getTabPanelAtIndex(3).should('have.class', 'cmp-tabs__tabpanel--active');
+ getTabAtIndex(2).should('have.class', 'cmp-tabs__tab--stepped');
+ getTabPanelAtIndex(2).should('have.class', 'cmp-tabs__tabpanel--stepped');
+ getTabAtIndex(1).should('have.class', 'cmp-tabs__tab--stepped');
+ getTabPanelAtIndex(1).should('have.class', 'cmp-tabs__tabpanel--stepped');
+ getTabAtIndex(0).should('have.class', 'cmp-tabs__tab--stepped');
+ getTabPanelAtIndex(0).should('have.class', 'cmp-tabs__tabpanel--stepped');
+ cy.get("button").contains("+R1").click().then(() => {
+ getTabs().should('have.length', 5);
+ getTabPanels().should('have.length', 5);
+ getTabAtIndex(1).should('have.class', 'cmp-tabs__tab--active');
+ getTabAtIndex(1).should('not.have.class', 'cmp-tabs__tab--stepped');
+ getTabPanelAtIndex(1).should('have.class', 'cmp-tabs__tabpanel--active');
+ getTabPanelAtIndex(1).should('not.have.class', 'cmp-tabs__tabpanel--stepped');
+ })
+ })
+
})
diff --git a/ui.tests/test-module/specs/title/titleV2.authoring.cy.js b/ui.tests/test-module/specs/title/titleV2.authoring.cy.js
index 76476fdc35..095467dbcc 100644
--- a/ui.tests/test-module/specs/title/titleV2.authoring.cy.js
+++ b/ui.tests/test-module/specs/title/titleV2.authoring.cy.js
@@ -128,12 +128,14 @@ describe('Page - Authoring', function () {
cy.get('[placeholder="New policy"]').eq(1).type("Default policy");
cy.get('[title="Done"]').click();
}).then(() => {
- cy.openSiteAuthoring(pagePath);
- dropTitleInContainer();
- cy.contains('button', 'Preview').should("exist").click({force : true});
- cy.get('h2').should('exist');
- cy.contains('button', 'Edit').should("exist").click({force : true});
- cy.deleteComponentByTitle('Adaptive Form Title');
+ cy.openSiteAuthoring(pagePath);
+ dropTitleInContainer();
+ // Switching from edit layer to preview layer is flaky, so using previewForm method
+ cy.previewForm(pagePath + ".html");
+ cy.get("h2").should("be.visible");
+ cy.openPage("");
+ cy.openSiteAuthoring(pagePath);
+ cy.deleteComponentByTitle('Adaptive Form Title');
})
});
});
diff --git a/ui.tests/test-module/specs/turnstile/turnstile.authoring.cy.js b/ui.tests/test-module/specs/turnstile/turnstile.authoring.cy.js
new file mode 100644
index 0000000000..86cc5bb3e4
--- /dev/null
+++ b/ui.tests/test-module/specs/turnstile/turnstile.authoring.cy.js
@@ -0,0 +1,157 @@
+/*
+ * 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 Turnstile with Forms and Sites Editor
+ */
+describe('Page - Authoring', function () {
+
+ const FT_TURNSTILE = "FT_FORMS-12407";
+ const formturnstile = "/apps/forms-core-components-it/form/turnstile";
+ let toggle_array = [];
+
+ before(() => {
+ cy.fetchFeatureToggles().then((response) => {
+ if (response.status === 200) {
+ toggle_array = response.body.enabled;
+ }
+ });
+ });
+
+ // we can use these values to log in
+
+ const dropTurnstileInContainer = function () {
+ const dataPath = "/content/forms/af/core-components-it/samples/turnstile/basic/jcr:content/guideContainer/*",
+ responsiveGridDropZoneSelector = sitesSelectors.overlays.overlay.component + "[data-path='" + dataPath + "']";
+ cy.selectLayer("Edit");
+ cy.insertComponent(responsiveGridDropZoneSelector, "Adaptive Form Cloudflare® Turnstile", formturnstile);
+ cy.get('body').click(0, 0);
+ }
+
+ const dropTurnstileInSites = function () {
+ const dataPath = "/content/core-components-examples/library/adaptive-form/turnstile/jcr:content/root/responsivegrid/demo/component/guideContainer/*",
+ responsiveGridDropZoneSelector = sitesSelectors.overlays.overlay.component + "[data-path='" + dataPath + "']";
+ cy.selectLayer("Edit");
+ cy.insertComponent(responsiveGridDropZoneSelector, "Adaptive Form Cloudflare® Turnstile", formturnstile);
+ cy.get('body').click(0, 0);
+ }
+
+ const testTurnstileBehaviour = function (turnstileEditPathSelector, turnstileDrop, isSites) {
+ if (isSites) {
+ dropTurnstileInSites();
+ } else {
+ dropTurnstileInContainer();
+ }
+ cy.openEditableToolbar(sitesSelectors.overlays.overlay.component + turnstileEditPathSelector);
+ 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("[name='./cloudServicePath']")
+ .should("exist");
+ cy.get("[name='./size']")
+ .should("exist");
+ cy.get("[name='./name']")
+ .should("exist");
+
+ // Switching to invisible widget type key should disable size options
+ cy.get(".cmp-adaptiveform-turnstile__configuration").click().then(() => {
+ cy.get("coral-selectlist-item[value='invisible']").click();
+ cy.get("input[name='./size'][value='normal']").should("be.disabled");
+ cy.get("input[name='./size'][value='compact']").should("be.disabled");
+ })
+ cy.get('.cq-dialog-submit').click();
+ cy.reload();
+ // If invisible widget type key is selected, size options should be disabled
+ cy.openEditableToolbar(sitesSelectors.overlays.overlay.component + turnstileEditPathSelector);
+ cy.invokeEditableAction("[data-action='CONFIGURE']");
+ cy.get("input[name='./size'][value='normal']").should("be.disabled");
+ cy.get("input[name='./size'][value='compact']").should("be.disabled");
+ // Switching to managed/non-interactive widget type key should enable size options
+ cy.get(".cmp-adaptiveform-turnstile__configuration").click().then(() => {
+ cy.get("coral-selectlist-item[value='managed']").click();
+ cy.get("input[name='./size'][value='normal']").should("be.enabled");
+ cy.get("input[name='./size'][value='compact']").should("be.enabled");
+ })
+ cy.get('.cq-dialog-submit').click();
+ cy.reload();
+ // If managed/non-interactive widget type key is selected, size options should be enabled
+ cy.openEditableToolbar(sitesSelectors.overlays.overlay.component + turnstileEditPathSelector);
+ cy.invokeEditableAction("[data-action='CONFIGURE']");
+ cy.get("input[name='./size'][value='normal']").should("be.enabled");
+ cy.get("input[name='./size'][value='compact']").should("be.enabled");
+ cy.get('.cq-dialog-cancel').click();
+
+ cy.deleteComponentByPath(turnstileDrop);
+ }
+
+ context('Open Forms Editor', function() {
+ const pagePath = "/content/forms/af/core-components-it/samples/turnstile/basic",
+ turnstileEditPath = pagePath + afConstants.FORM_EDITOR_FORM_CONTAINER_SUFFIX + "/turnstile",
+ turnstileEditPathSelector = "[data-path='" + turnstileEditPath + "']",
+ turnstileDrop = pagePath + afConstants.FORM_EDITOR_FORM_CONTAINER_SUFFIX + "/" + formturnstile.split("/").pop();
+ beforeEach(function () {
+ if (cy.af.isLatestAddon()) {
+ // this is done since cypress session results in 403 sometimes
+ cy.openAuthoring(pagePath);
+ }
+ });
+
+ it('insert turnstile in form container', function () {
+ if (cy.af.isLatestAddon() && toggle_array.includes(FT_TURNSTILE)) {
+ dropTurnstileInContainer();
+ cy.deleteComponentByPath(turnstileDrop);
+ }
+ });
+
+ it ('open edit dialog of turnstile',{ retries: 3 }, function(){
+ if (cy.af.isLatestAddon() && toggle_array.includes(FT_TURNSTILE)) {
+ cy.cleanTest(turnstileDrop).then(function(){
+ testTurnstileBehaviour(turnstileEditPathSelector, turnstileDrop);
+ });
+ }
+ })
+ })
+
+ context('Open Sites Editor', function () {
+ const pagePath = "/content/core-components-examples/library/adaptive-form/turnstile",
+ turnstileEditPath = pagePath + afConstants.RESPONSIVE_GRID_DEMO_SUFFIX + "/guideContainer/turnstile",
+ turnstileEditPathSelector = "[data-path='" + turnstileEditPath + "']",
+ turnstileDrop = pagePath + afConstants.RESPONSIVE_GRID_DEMO_SUFFIX + '/guideContainer/' + formturnstile.split("/").pop();
+
+ beforeEach(function () {
+ if (cy.af.isLatestAddon()) {
+ // this is done since cypress session results in 403 sometimes
+ cy.openAuthoring(pagePath);
+ }
+ });
+
+ it('insert aem forms turnstile', function () {
+ if (cy.af.isLatestAddon() && toggle_array.includes(FT_TURNSTILE)) {
+ dropTurnstileInSites();
+ cy.deleteComponentByPath(turnstileDrop);
+ }
+ });
+
+ it('open edit dialog of aem forms turnstile', function() {
+ if (cy.af.isLatestAddon() && toggle_array.includes(FT_TURNSTILE)) {
+ testTurnstileBehaviour(turnstileEditPathSelector, turnstileDrop, true);
+ }
+ });
+ });
+
+});
diff --git a/ui.tests/test-module/specs/turnstile/turnstile.runtime.cy.js b/ui.tests/test-module/specs/turnstile/turnstile.runtime.cy.js
new file mode 100644
index 0000000000..3d6a79638a
--- /dev/null
+++ b/ui.tests/test-module/specs/turnstile/turnstile.runtime.cy.js
@@ -0,0 +1,228 @@
+/*
+ * 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.
+ */
+describe("Form Runtime with Turnstile Input", () => {
+
+ const FT_TURNSTILE = "FT_FORMS-12407";
+ const pagePath = "content/forms/af/core-components-it/samples/turnstile/managed.html"
+ const invisiblePagePath = "content/forms/af/core-components-it/samples/turnstile/invisible.html"
+ const bemBlock = 'cmp-adaptiveform-turnstile'
+ // The secret keys are part of public documentation and are not sensitive :
+ // https://developers.cloudflare.com/turnstile/troubleshooting/testing/
+ const alwaysPassSecretKey = "1x0000000000000000000000000000000AA";
+ const alwaysFailSecretKey = "2x0000000000000000000000000000000AA";
+ let formContainer = null
+
+ let toggle_array = [];
+
+ before(() => {
+ cy.fetchFeatureToggles().then((response) => {
+ if (response.status === 200) {
+ toggle_array = response.body.enabled;
+ }
+ });
+ });
+
+ function updateTurnstileSecretKey(secretKey, configName) {
+ cy.openPage(`mnt/overlay/fd/af/cloudservices/turnstile/properties.html?item=%2Fconf%2Fcore-components-it%2Fsettings%2Fcloudconfigs%2Fturnstile%2F${configName}`).then(x => {
+ cy.get('#captcha-cloudconfiguration-secret-key').clear().type(secretKey).then(x => {
+ cy.get("#shell-propertiespage-doneactivator").click();
+ });
+ });
+ }
+
+ // render the form with captcha, we have whitelisted the "Missing required parameters: sitekey" error
+ beforeEach(() => {
+ if (cy.af.isLatestAddon()) {
+ 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 || state.readOnly === true ? "" : "not."}have.attr`;
+ const value = 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)
+ })
+ }
+
+ it(" should get model and view initialized properly ", () => {
+ if (cy.af.isLatestAddon() && toggle_array.includes(FT_TURNSTILE)) {
+ 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]) => {
+ expect(field.getId()).to.equal(id)
+ expect(formContainer._model.getElement(id), `model and view are in sync`).to.equal(field.getModel())
+ });
+ }
+ })
+
+ it(" model's changes are reflected in the html ", () => {
+ if (cy.af.isLatestAddon() && toggle_array.includes(FT_TURNSTILE)) {
+ const [id, fieldView] = Object.entries(formContainer._fields)[0]
+ const model = formContainer._model.getElement(id)
+ cy.get('.cmp-adaptiveform-turnstile__widget').should('exist');
+
+ checkHTML(model.id, model.getState()).then(() => {
+ model.visible = false
+ return checkHTML(model.id, model.getState())
+ }).then(() => {
+ model.enable = false
+ return checkHTML(model.id, model.getState())
+ })
+ }
+ });
+
+ it(" html changes are reflected in model ", () => {
+ if (cy.af.isLatestAddon() && toggle_array.includes(FT_TURNSTILE)) {
+ const [id, fieldView] = Object.entries(formContainer._fields)[0]
+ const model = formContainer._model.getElement(id)
+ cy.log(model.getState().value)
+ cy.get(`#${id}`).click().then(x => {
+ cy.log(model.getState().value)
+ expect(model.getState().value).to.not.be.null
+ })
+ }
+ });
+
+
+ it("decoration element should not have same class name", () => {
+ if (cy.af.isLatestAddon() && toggle_array.includes(FT_TURNSTILE)) {
+ 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", bemBlock);
+ })
+ }
+ })
+
+ it("client side validation should fail if captcha is not filled", () => {
+ if (cy.af.isLatestAddon() && toggle_array.includes(FT_TURNSTILE)) {
+ expect(formContainer, "formcontainer is initialized").to.not.be.null;
+ cy.get(`.cmp-adaptiveform-button__widget`).click().then(x => {
+ cy.get('.cmp-adaptiveform-turnstile__errormessage').should('exist').contains('Please fill in this field.');
+ });
+ }
+ })
+
+ it("submission should pass for mandatory captcha", () => {
+ if (cy.af.isLatestAddon() && toggle_array.includes(FT_TURNSTILE)) {
+ updateTurnstileSecretKey(alwaysPassSecretKey, "managed");
+ cy.previewForm(pagePath).then((p) => {
+ formContainer = p;
+ });
+ expect(formContainer, "formcontainer is initialized").to.not.be.null;
+ cy.get('.cmp-adaptiveform-turnstile__widget').should('be.visible').then($iframe => {
+ cy.wrap($iframe).then($iframe => {
+ cy.window().should('have.property', 'turnstile').and('not.be.undefined')
+ .then((turnstile) => {
+ turnstile.execute();
+ return new Cypress.Promise(resolve => {
+ setTimeout(() => {
+ resolve();
+ }, 3000);
+ });
+ }).then(() => {
+ cy.get(`.cmp-adaptiveform-button__widget`).click().then(x => {
+ cy.get('body').should('contain', "Thank you for submitting the form.\n")
+ });
+ })
+ });
+ });
+ }
+ })
+
+ it("submission should fail for mandatory captcha", () => {
+ if (cy.af.isLatestAddon() && toggle_array.includes(FT_TURNSTILE)) {
+ updateTurnstileSecretKey(alwaysFailSecretKey, "managed");
+ cy.previewForm(pagePath).then((p) => {
+ formContainer = p;
+ });
+ expect(formContainer, "formcontainer is initialized").to.not.be.null;
+ cy.get('.cmp-adaptiveform-turnstile__widget').should('be.visible').then($iframe => {
+ cy.wrap($iframe).then($iframe => {
+ cy.window().should('have.property', 'turnstile').and('not.be.undefined')
+ .then((hcaptcha) => {
+ hcaptcha.execute();
+ return new Cypress.Promise(resolve => {
+ setTimeout(() => {
+ resolve();
+ }, 3000);
+ });
+ }).then(() => {
+ cy.on('window:alert', (message) => {
+ expect(message).to.equal('Encountered an internal error while submitting the form.');
+ });
+ cy.intercept('POST', /\/adobe\/forms\/af\/submit\/.*/).as('submitForm');
+ cy.get(`.cmp-adaptiveform-button__widget`).click();
+ cy.wait('@submitForm').then((interception) => {
+ expect(interception.response.statusCode).to.equal(400);
+ expect(interception.response.body).to.have.property('title', 'The CAPTCHA validation failed. Please try again.');
+ expect(interception.response.body).to.have.property('detail', 'com.adobe.aem.forms.af.rest.exception.CaptchaValidationException: Captcha validation failed.');
+ });
+ })
+ });
+ });
+ }
+ })
+
+ it("submission should pass for mandatory invisible captcha", () => {
+ if (cy.af.isLatestAddon() && toggle_array.includes(FT_TURNSTILE)) {
+ updateTurnstileSecretKey(alwaysPassSecretKey, "invisible");
+ cy.previewForm(invisiblePagePath).then((p) => {
+ formContainer = p;
+ });
+ expect(formContainer, "formcontainer is initialized").to.not.be.null;
+ cy.get(`.cmp-adaptiveform-turnstile__widget`).should('exist').then(() => {
+ cy.intercept('POST', /\/adobe\/forms\/af\/submit\/.*/, (req) => {
+ req.reply((res) => {
+ expect(res.statusCode).to.equal(200);
+ });
+ });
+ cy.get(`.cmp-adaptiveform-button__widget`).click();
+ });
+ }
+ });
+
+ it("submission should return 400 if invisible captcha validation fails", () => {
+ if (cy.af.isLatestAddon() && toggle_array.includes(FT_TURNSTILE)) {
+ updateTurnstileSecretKey(alwaysFailSecretKey, "invisible");
+ cy.previewForm(invisiblePagePath).then((p) => {
+ formContainer = p;
+ });
+ expect(formContainer, "formcontainer is initialized").to.not.be.null;
+ cy.get(`.cmp-adaptiveform-turnstile__widget`).should('exist').then(() => {
+ cy.intercept('POST', /\/adobe\/forms\/af\/submit\/.*/, (req) => {
+ req.reply((res) => {
+ expect(res.statusCode).to.equal(400);
+ });
+ });
+ cy.get(`.cmp-adaptiveform-button__widget`).click();
+ });
+ }
+ });
+
+})
diff --git a/ui.tests/test-module/specs/verticaltabs/verticaltabs.authoring.cy.js b/ui.tests/test-module/specs/verticaltabs/verticaltabs.authoring.cy.js
index 411753b934..bfdece45bb 100644
--- a/ui.tests/test-module/specs/verticaltabs/verticaltabs.authoring.cy.js
+++ b/ui.tests/test-module/specs/verticaltabs/verticaltabs.authoring.cy.js
@@ -49,6 +49,12 @@ describe.only('Page - Authoring', function () {
const responsiveGridDropZoneSelector = sitesSelectors.overlays.overlay.component + "[data-text='Please drag Tab components here']:last";
dropComponent(responsiveGridDropZoneSelector, "Adaptive Form Date Picker", afConstants.components.forms.resourceType.datepicker);
}
+
+ const dropPanelInVerticalTabComponent = function(tabsPath) {
+ const responsiveGridDropZoneSelector = sitesSelectors.overlays.overlay.component + "[data-path='" + tabsPath + "/*']";
+ dropComponent(responsiveGridDropZoneSelector, "Adaptive Form Panel", afConstants.components.forms.resourceType.panelcontainer);
+ }
+
const dropTabsInSites = function() {
const dataPath = "/content/core-components-examples/library/adaptive-form/panelcontainer/jcr:content/root/responsivegrid/demo/component/guideContainer/*",
responsiveGridDropZoneSelector = sitesSelectors.overlays.overlay.component + "[data-path='" + dataPath + "']";
@@ -118,6 +124,18 @@ describe.only('Page - Authoring', function () {
});
});
+ it('verify second panel is not visible after adding two panel',{ retries: 3 }, function(){
+ cy.cleanTest(tabsPath).then(function() {
+ dropTabsInContainer();
+ dropPanelInVerticalTabComponent(tabsPath);
+ dropPanelInVerticalTabComponent(tabsPath);
+ cy.reload();
+ cy.getContentIFrameBody().find('.cmp-verticaltabs__tabpanel').should('have.length', 2);
+ cy.getContentIFrameBody().find('.cmp-verticaltabs__tabpanel').eq(0).should('be.visible');
+ cy.getContentIFrameBody().find('.cmp-verticaltabs__tabpanel').eq(1).should('not.be.visible');
+ cy.deleteComponentByPath(tabsPath);
+ });
+ });
});
context('Open Sites Editor', function () {
diff --git a/ui.tests/test-module/specs/verticaltabs/verticaltabs.runtime.cy.js b/ui.tests/test-module/specs/verticaltabs/verticaltabs.runtime.cy.js
index 860105eca4..e1c422bcc0 100644
--- a/ui.tests/test-module/specs/verticaltabs/verticaltabs.runtime.cy.js
+++ b/ui.tests/test-module/specs/verticaltabs/verticaltabs.runtime.cy.js
@@ -90,8 +90,10 @@
tab2().should('have.class','cmp-verticaltabs__tab--active');
tab2().should('have.attr','aria-selected','true');
tab1().should('have.attr','aria-selected','false');
+ tab1().should('have.class','cmp-verticaltabs__tab--stepped');
tab1().click();
tab1().should('have.class','cmp-verticaltabs__tab--active');
+ tab1().should('have.class','cmp-verticaltabs__tab--stepped');
tab1().should('have.attr','aria-selected','true');
tab2().should('have.attr','aria-selected','false');
});
diff --git a/ui.tests/test-module/specs/verticaltabs/verticaltabs.runtime.repeatability.cy.js b/ui.tests/test-module/specs/verticaltabs/verticaltabs.runtime.repeatability.cy.js
index f8d0d04158..ac33bcc5a1 100644
--- a/ui.tests/test-module/specs/verticaltabs/verticaltabs.runtime.repeatability.cy.js
+++ b/ui.tests/test-module/specs/verticaltabs/verticaltabs.runtime.repeatability.cy.js
@@ -130,5 +130,41 @@ describe("Form with VerticalTabs Container", () => {
})
})
+ it("After clicking on every tab, if tab-1 is repeated, the repeated instance should not have stepped class", () => {
+ getTabs().should('have.length', 4);
+ getTabPanels().should('have.length', 4);
+ getTabAtIndex(0).should('have.class', 'cmp-verticaltabs__tab--active');
+ getTabPanelAtIndex(0).should('have.class', 'cmp-verticaltabs__tabpanel--active');
+ getTabAtIndex(1).click();
+ getTabAtIndex(1).should('have.class', 'cmp-verticaltabs__tab--active');
+ getTabPanelAtIndex(1).should('have.class', 'cmp-verticaltabs__tabpanel--active');
+ getTabAtIndex(0).should('have.class', 'cmp-verticaltabs__tab--stepped');
+ getTabPanelAtIndex(0).should('have.class', 'cmp-verticaltabs__tabpanel--stepped');
+ getTabAtIndex(2).click();
+ getTabAtIndex(2).should('have.class', 'cmp-verticaltabs__tab--active');
+ getTabPanelAtIndex(2).should('have.class', 'cmp-verticaltabs__tabpanel--active');
+ getTabAtIndex(1).should('have.class', 'cmp-verticaltabs__tab--stepped');
+ getTabPanelAtIndex(1).should('have.class', 'cmp-verticaltabs__tabpanel--stepped');
+ getTabAtIndex(0).should('have.class', 'cmp-verticaltabs__tab--stepped');
+ getTabPanelAtIndex(0).should('have.class', 'cmp-verticaltabs__tabpanel--stepped');
+ getTabAtIndex(3).click();
+ getTabAtIndex(3).should('have.class', 'cmp-verticaltabs__tab--active');
+ getTabPanelAtIndex(3).should('have.class', 'cmp-verticaltabs__tabpanel--active');
+ getTabAtIndex(2).should('have.class', 'cmp-verticaltabs__tab--stepped');
+ getTabPanelAtIndex(2).should('have.class', 'cmp-verticaltabs__tabpanel--stepped');
+ getTabAtIndex(1).should('have.class', 'cmp-verticaltabs__tab--stepped');
+ getTabPanelAtIndex(1).should('have.class', 'cmp-verticaltabs__tabpanel--stepped');
+ getTabAtIndex(0).should('have.class', 'cmp-verticaltabs__tab--stepped');
+ getTabPanelAtIndex(0).should('have.class', 'cmp-verticaltabs__tabpanel--stepped');
+ cy.get("button").contains("+R1").click().then(() => {
+ getTabs().should('have.length', 5);
+ getTabPanels().should('have.length', 5);
+ getTabAtIndex(1).should('have.class', 'cmp-verticaltabs__tab--active');
+ getTabAtIndex(1).should('not.have.class', 'cmp-verticaltabs__tab--stepped');
+ getTabPanelAtIndex(1).should('have.class', 'cmp-verticaltabs__tabpanel--active');
+ getTabPanelAtIndex(1).should('not.have.class', 'cmp-verticaltabs__tabpanel--stepped');
+ })
+ })
+
})
diff --git a/ui.tests/test-module/specs/wizard/wizard.authoring.cy.js b/ui.tests/test-module/specs/wizard/wizard.authoring.cy.js
index 954fc279ae..6471ab8805 100644
--- a/ui.tests/test-module/specs/wizard/wizard.authoring.cy.js
+++ b/ui.tests/test-module/specs/wizard/wizard.authoring.cy.js
@@ -205,6 +205,19 @@ describe('Page - Authoring', function () {
});
});
+ it('verify second panel is not visible after adding two panels', function () {
+ cy.cleanTest(wizardLayoutDrop).then(function () {
+ dropWizardInContainer();
+ addComponentInWizard("Adaptive Form Panel", afConstants.components.forms.resourceType.panelcontainer);
+ addComponentInWizard("Adaptive Form Panel", afConstants.components.forms.resourceType.panelcontainer);
+ cy.reload()
+ cy.getContentIFrameBody().find('.cmp-adaptiveform-wizard__wizardpanel').should('have.length', 2);
+ cy.getContentIFrameBody().find('.cmp-adaptiveform-wizard__wizardpanel').eq(0).should('be.visible');
+ cy.getContentIFrameBody().find('.cmp-adaptiveform-wizard__wizardpanel').eq(1).should('not.be.visible');
+ cy.deleteComponentByPath(wizardLayoutDrop);
+ });
+ });
+
it('save as fragment in Wizard',function () {
cy.cleanTest(wizardLayoutDrop).then(function () {
testSaveAsFragment(wizardEditPathSelector, wizardLayoutDrop);
diff --git a/ui.tests/test-module/specs/wizard/wizard.runtime.cy.js b/ui.tests/test-module/specs/wizard/wizard.runtime.cy.js
index f8d003c23b..7d75be997b 100644
--- a/ui.tests/test-module/specs/wizard/wizard.runtime.cy.js
+++ b/ui.tests/test-module/specs/wizard/wizard.runtime.cy.js
@@ -54,17 +54,21 @@ describe("Form with Wizard Layout Container", () => {
cy.get(".cmp-adaptiveform-wizard__nav--next").click({force: true});
cy.get(".cmp-adaptiveform-wizard__tab").eq(0).should('not.have.class', 'cmp-adaptiveform-wizard__tab--active');
+ cy.get(".cmp-adaptiveform-wizard__tab").eq(0).should('have.class', 'cmp-adaptiveform-wizard__tab--stepped');
cy.get(".cmp-adaptiveform-wizard__tab").eq(1).should('have.class', 'cmp-adaptiveform-wizard__tab--active');
cy.get(".cmp-adaptiveform-wizard__wizardpanel").eq(0).should('not.have.class', 'cmp-adaptiveform-wizard__wizardpanel--active');
+ cy.get(".cmp-adaptiveform-wizard__wizardpanel").eq(0).should('have.class', 'cmp-adaptiveform-wizard__wizardpanel--stepped');
cy.get(".cmp-adaptiveform-wizard__wizardpanel").eq(1).should('have.class', 'cmp-adaptiveform-wizard__wizardpanel--active');
cy.get(".cmp-adaptiveform-wizard__nav--next").click({force: true});
cy.get(".cmp-adaptiveform-wizard__tab").eq(0).should('not.have.class', 'cmp-adaptiveform-wizard__tab--active');
+ cy.get(".cmp-adaptiveform-wizard__tab").eq(0).should('have.class', 'cmp-adaptiveform-wizard__tab--stepped');
cy.get(".cmp-adaptiveform-wizard__tab").eq(1).should('have.class', 'cmp-adaptiveform-wizard__tab--active');
cy.get(".cmp-adaptiveform-wizard__wizardpanel").eq(0).should('not.have.class', 'cmp-adaptiveform-wizard__wizardpanel--active');
+ cy.get(".cmp-adaptiveform-wizard__wizardpanel").eq(0).should('have.class', 'cmp-adaptiveform-wizard__wizardpanel--stepped');
cy.get(".cmp-adaptiveform-wizard__wizardpanel").eq(1).should('have.class', 'cmp-adaptiveform-wizard__wizardpanel--active');
cy.expectNoConsoleErrors();
});
@@ -90,9 +94,11 @@ describe("Form with Wizard Layout Container", () => {
cy.get(".cmp-adaptiveform-wizard__nav--previous").click({force: true});
cy.get(".cmp-adaptiveform-wizard__tab").eq(0).should('have.class', 'cmp-adaptiveform-wizard__tab--active');
+ cy.get(".cmp-adaptiveform-wizard__tab").eq(0).should('have.class', 'cmp-adaptiveform-wizard__tab--stepped');
cy.get(".cmp-adaptiveform-wizard__tab").eq(1).should('not.have.class', 'cmp-adaptiveform-wizard__tab--active');
cy.get(".cmp-adaptiveform-wizard__wizardpanel").eq(0).should('have.class', 'cmp-adaptiveform-wizard__wizardpanel--active');
+ cy.get(".cmp-adaptiveform-wizard__wizardpanel").eq(0).should('have.class', 'cmp-adaptiveform-wizard__wizardpanel--stepped');
cy.get(".cmp-adaptiveform-wizard__wizardpanel").eq(1).should('not.have.class', 'cmp-adaptiveform-wizard__wizardpanel--active');
cy.expectNoConsoleErrors();
});
diff --git a/ui.tests/test-module/specs/wizard/wizard.runtime.repeatability.cy.js b/ui.tests/test-module/specs/wizard/wizard.runtime.repeatability.cy.js
index f749a1829b..6f03946a2f 100644
--- a/ui.tests/test-module/specs/wizard/wizard.runtime.repeatability.cy.js
+++ b/ui.tests/test-module/specs/wizard/wizard.runtime.repeatability.cy.js
@@ -151,6 +151,43 @@ describe("Form with Wizard Container", () => {
getWizardPanels().should('have.length', 6);
})
});
+
+ it("After clicking on every tab, if tab-1 is repeated, the repeated instance should not have stepped class", () => {
+ getTabs().should('have.length', 4);
+ getWizardPanels().should('have.length', 4);
+ getTabAtIndex(0).should('have.class', 'cmp-adaptiveform-wizard__tab--active');
+ getWizardPanelAtIndex(0).should('have.class', 'cmp-adaptiveform-wizard__wizardpanel--active');
+ cy.get(".cmp-adaptiveform-wizard__nav--next").click({force: true});
+ getTabAtIndex(1).should('have.class', 'cmp-adaptiveform-wizard__tab--active');
+ getWizardPanelAtIndex(1).should('have.class', 'cmp-adaptiveform-wizard__wizardpanel--active');
+ getTabAtIndex(0).should('have.class', 'cmp-adaptiveform-wizard__tab--stepped');
+ getWizardPanelAtIndex(0).should('have.class', 'cmp-adaptiveform-wizard__wizardpanel--stepped');
+ cy.get(".cmp-adaptiveform-wizard__nav--next").click({force: true});
+ getTabAtIndex(2).should('have.class', 'cmp-adaptiveform-wizard__tab--active');
+ getWizardPanelAtIndex(2).should('have.class', 'cmp-adaptiveform-wizard__wizardpanel--active');
+ getTabAtIndex(1).should('have.class', 'cmp-adaptiveform-wizard__tab--stepped');
+ getWizardPanelAtIndex(1).should('have.class', 'cmp-adaptiveform-wizard__wizardpanel--stepped');
+ getTabAtIndex(0).should('have.class', 'cmp-adaptiveform-wizard__tab--stepped');
+ getWizardPanelAtIndex(0).should('have.class', 'cmp-adaptiveform-wizard__wizardpanel--stepped');
+ cy.get(".cmp-adaptiveform-wizard__nav--next").click({force: true});
+ getTabAtIndex(3).should('have.class', 'cmp-adaptiveform-wizard__tab--active');
+ getWizardPanelAtIndex(3).should('have.class', 'cmp-adaptiveform-wizard__wizardpanel--active');
+ getTabAtIndex(2).should('have.class', 'cmp-adaptiveform-wizard__tab--stepped');
+ getWizardPanelAtIndex(2).should('have.class', 'cmp-adaptiveform-wizard__wizardpanel--stepped');
+ getTabAtIndex(1).should('have.class', 'cmp-adaptiveform-wizard__tab--stepped');
+ getWizardPanelAtIndex(1).should('have.class', 'cmp-adaptiveform-wizard__wizardpanel--stepped');
+ getTabAtIndex(0).should('have.class', 'cmp-adaptiveform-wizard__tab--stepped');
+ getWizardPanelAtIndex(0).should('have.class', 'cmp-adaptiveform-wizard__wizardpanel--stepped');
+ cy.get("button").contains("+R1").click().then(() => {
+ getTabs().should('have.length', 5);
+ getWizardPanels().should('have.length', 5);
+ getTabAtIndex(1).should('have.class', 'cmp-adaptiveform-wizard__tab--active');
+ getWizardPanelAtIndex(1).should('have.class', 'cmp-adaptiveform-wizard__wizardpanel--active');
+ getTabAtIndex(1).should('not.have.class', 'cmp-adaptiveform-wizard__tab--stepped');
+ getWizardPanelAtIndex(1).should('not.have.class', 'cmp-adaptiveform-wizard__wizardpanel--stepped');
+ })
+ cy.expectNoConsoleErrors();
+ })
})
describe('visibility of navigation buttons', function () {