diff --git a/.circleci/ci/it-tests.js b/.circleci/ci/it-tests.js index 45ee0c6d20..ec391c0c74 100644 --- a/.circleci/ci/it-tests.js +++ b/.circleci/ci/it-tests.js @@ -23,7 +23,9 @@ const config = ci.restoreConfiguration(); console.log(config); const qpPath = '/home/circleci/cq'; const buildPath = '/home/circleci/build'; -const { TYPE, BROWSER, AEM, PRERELEASE, FT, CONTEXTPATH, FTCONFIG} = process.env; +const { TYPE, BROWSER, AEM, PRERELEASE, FT, CONTEXTPATH, FTCONFIG, CORE_COMPONENTS, WCM_COMPONENTS} = process.env; +const isLatestAddon = AEM === 'addon-latest'; +const jacocoAgent = process.env.JACOCO_AGENT; // 6.6.0 packages are published to same artifactory, once fixed use latest here const latestVersion = ci.fetchLatestArtifactVersion('com.adobe.aemds', 'adobe-aemfd-linux-pkg'); const classicFormAddonVersion = latestVersion !== null ? latestVersion : '6.0.1328'; // Use the latest version if available, otherwise default to '6.0.1256' @@ -31,8 +33,8 @@ const classicFormAddonVersion = latestVersion !== null ? latestVersion : '6.0.13 const classicFormReleasedAddonVersion = '6.0.1360'; try { + let wcmVersion = "2.32.4"; ci.stage("Integration Tests"); - let wcmVersion = "2.30.0"; ci.dir(qpPath, () => { // Connect to QP ci.sh('./qp.sh -v bind --server-hostname localhost --server-port 55555'); @@ -88,22 +90,22 @@ try { // Start CQ ci.sh(`./qp.sh -v start --id author --runmode author --port 4502 --qs-jar /home/circleci/cq/author/cq-quickstart.jar \ --bundle org.apache.sling:org.apache.sling.junit.core:1.0.23:jar \ - ${extras} \ --bundle com.adobe.cq:core.wcm.components.examples.ui.config:${wcmVersion}:zip \ --bundle com.adobe.cq:core.wcm.components.examples.ui.apps:${wcmVersion}:zip \ --bundle com.adobe.cq:core.wcm.components.examples.ui.content:${wcmVersion}:zip \ - ${ci.addQpFileDependency(config.modules['core-forms-components-apps'] /*, isLatestAddon ? true : false */)} \ - ${ci.addQpFileDependency(config.modules['core-forms-components-af-apps'] /*, isLatestAddon ? true : false */)} \ - ${ci.addQpFileDependency(config.modules['core-forms-components-core'])} \\ - ${ci.addQpFileDependency(config.modules['core-forms-components-af-core'])} \ - ${ci.addQpFileDependency(config.modules['core-forms-components-examples-apps'])} \ - ${ci.addQpFileDependency(config.modules['core-forms-components-examples-content'])} \ - ${ci.addQpFileDependency(config.modules['core-forms-components-examples-core'])} \ + ${extras} \ + ${!CORE_COMPONENTS ? ci.addQpFileDependency(config.modules['core-forms-components-apps'] /*, isLatestAddon ? true : false */) : ''} \ + ${!CORE_COMPONENTS ? ci.addQpFileDependency(config.modules['core-forms-components-af-apps'] /*, isLatestAddon ? true : false */) : ''} \ + ${!CORE_COMPONENTS ? ci.addQpFileDependency(config.modules['core-forms-components-core']) : ''} \ + ${!CORE_COMPONENTS ? ci.addQpFileDependency(config.modules['core-forms-components-af-core']) : ''} \ + ${!CORE_COMPONENTS ? ci.addQpFileDependency(config.modules['core-forms-components-examples-apps']) : ''} \ + ${!CORE_COMPONENTS ? ci.addQpFileDependency(config.modules['core-forms-components-examples-content']) : ''} \ + ${!CORE_COMPONENTS ? ci.addQpFileDependency(config.modules['core-forms-components-examples-core']) : ''} \ ${ci.addQpFileDependency(config.modules['core-forms-components-it-tests-config'])} \ ${ci.addQpFileDependency(config.modules['core-forms-components-it-tests-core'])} \ ${ci.addQpFileDependency(config.modules['core-forms-components-it-tests-apps'])} \ ${ci.addQpFileDependency(config.modules['core-forms-components-it-tests-content'])} \ - --vm-options \\\"-Xmx4096m -XX:MaxPermSize=1024m -Djava.awt.headless=true -javaagent:${process.env.JACOCO_AGENT}=destfile=crx-quickstart/jacoco-it.exec\\\" \ + --vm-options \\\"-Xmx4096m -Djava.awt.headless=true -javaagent:${jacocoAgent}=destfile=crx-quickstart/jacoco-it.exec\\\" \ ${preleaseOpts} ${contextPathOpts}`); if (AEM === 'classic' || AEM === 'classic-latest' || AEM === 'classic-latest-cp') { @@ -115,7 +117,7 @@ try { // add a sleep for 7 mins, add-on takes times to come up ci.sh(`sleep 8m`); } - }); +}); // Run integration tests /* @@ -129,15 +131,140 @@ try { }); } */ - + // add a sleep for 8 mins since 23482 version aem has become slow + //ci.sh(`sleep 2m`); // Run UI tests if (TYPE === 'cypress') { + if (AEM && AEM.includes("addon")) { + // explicitly add the rum bundle, since it is only available on publish tier + // upload webvitals and disable api region + const disableApiRegion = "curl -u admin:admin -X POST -d 'apply=true' -d 'propertylist=disable' -d 'disable=true' http://localhost:4502/system/console/configMgr/org.apache.sling.feature.apiregions.impl"; + ci.sh(disableApiRegion); + + // Only remove duplicate bundles when testing SNAPSHOT builds (not specific CORE_COMPONENTS versions) + if (!CORE_COMPONENTS) { + // Uninstall old af-core bundles to prevent adaptTo() conflicts + // First, log all af-core bundles to debug which one we're keeping + const allBundles = ci.sh('curl -s -u admin:admin http://localhost:4502/system/console/bundles.json | jq -r \'.data | map(select(.symbolicName == "com.adobe.aem.core-forms-components-af-core")) | sort_by(.id | tonumber) | reverse | .[] | "ID: \\(.id) | Version: \\(.version) | State: \\(.state)"\'', true); + console.log('Found af-core bundles:'); + console.log(allBundles); + + // Get SNAPSHOT bundle ID for later restart + const afCoreSnapshotId = ci.sh('curl -s -u admin:admin http://localhost:4502/system/console/bundles.json | jq -r \'.data | map(select(.symbolicName == "com.adobe.aem.core-forms-components-af-core" and (.version | contains("SNAPSHOT")))) | .[0].id\'', true); + + // Keep the SNAPSHOT version (from build) and uninstall all others + const oldBundlesInfo = ci.sh('curl -s -u admin:admin http://localhost:4502/system/console/bundles.json | jq -r \'.data | map(select(.symbolicName == "com.adobe.aem.core-forms-components-af-core" and (.version | contains("SNAPSHOT") | not))) | .[] | "\\(.id)|\\(.version)"\'', true); + if (oldBundlesInfo && oldBundlesInfo.trim() !== '' && oldBundlesInfo !== 'null') { + console.log('Uninstalling old af-core bundle versions to avoid conflicts'); + oldBundlesInfo.trim().split('\n').forEach(bundleInfo => { + if (bundleInfo && bundleInfo !== 'null' && bundleInfo.trim() !== '') { + const [bundleId, version] = bundleInfo.split('|'); + console.log(` Uninstalling bundle ${bundleId} (version ${version})`); + ci.sh(`curl -s -u admin:admin -F action=uninstall http://localhost:4502/system/console/bundles/${bundleId}`); + } + }); + } + + // Similarly, uninstall old core bundle versions + const allCoreBundles = ci.sh('curl -s -u admin:admin http://localhost:4502/system/console/bundles.json | jq -r \'.data | map(select(.symbolicName == "com.adobe.aem.core-forms-components-core")) | sort_by(.id | tonumber) | reverse | .[] | "ID: \\(.id) | Version: \\(.version) | State: \\(.state)"\'', true); + console.log('Found core bundles:'); + console.log(allCoreBundles); + + // Get SNAPSHOT bundle ID for later restart + const coreSnapshotId = ci.sh('curl -s -u admin:admin http://localhost:4502/system/console/bundles.json | jq -r \'.data | map(select(.symbolicName == "com.adobe.aem.core-forms-components-core" and (.version | contains("SNAPSHOT")))) | .[0].id\'', true); + + const oldCoreBundlesInfo = ci.sh('curl -s -u admin:admin http://localhost:4502/system/console/bundles.json | jq -r \'.data | map(select(.symbolicName == "com.adobe.aem.core-forms-components-core" and (.version | contains("SNAPSHOT") | not))) | .[] | "\\(.id)|\\(.version)"\'', true); + if (oldCoreBundlesInfo && oldCoreBundlesInfo.trim() !== '' && oldCoreBundlesInfo !== 'null') { + console.log('Uninstalling old core bundle versions to avoid conflicts'); + oldCoreBundlesInfo.trim().split('\n').forEach(bundleInfo => { + if (bundleInfo && bundleInfo !== 'null' && bundleInfo.trim() !== '') { + const [bundleId, version] = bundleInfo.split('|'); + console.log(` Uninstalling bundle ${bundleId} (version ${version})`); + ci.sh(`curl -s -u admin:admin -F action=uninstall http://localhost:4502/system/console/bundles/${bundleId}`); + } + }); + } + + // Restart SNAPSHOT bundles to ensure clean wiring after uninstalling old bundles + if ((oldBundlesInfo && oldBundlesInfo.trim() !== '' && oldBundlesInfo !== 'null') || + (oldCoreBundlesInfo && oldCoreBundlesInfo.trim() !== '' && oldCoreBundlesInfo !== 'null')) { + + // Stop SNAPSHOT bundles (using IDs fetched earlier) + if (afCoreSnapshotId && afCoreSnapshotId.trim() !== '' && afCoreSnapshotId !== 'null') { + console.log(`Stopping af-core SNAPSHOT bundle (ID: ${afCoreSnapshotId.trim()})...`); + ci.sh(`curl -s -u admin:admin -F action=stop http://localhost:4502/system/console/bundles/${afCoreSnapshotId.trim()}`); + } + if (coreSnapshotId && coreSnapshotId.trim() !== '' && coreSnapshotId !== 'null') { + console.log(`Stopping core SNAPSHOT bundle (ID: ${coreSnapshotId.trim()})...`); + ci.sh(`curl -s -u admin:admin -F action=stop http://localhost:4502/system/console/bundles/${coreSnapshotId.trim()}`); + } + + console.log('Waiting 10 seconds for bundles to stop...'); + ci.sh('sleep 10'); + + // Start SNAPSHOT bundles + if (afCoreSnapshotId && afCoreSnapshotId.trim() !== '' && afCoreSnapshotId !== 'null') { + console.log(`Starting af-core SNAPSHOT bundle (ID: ${afCoreSnapshotId.trim()})...`); + ci.sh(`curl -s -u admin:admin -F action=start http://localhost:4502/system/console/bundles/${afCoreSnapshotId.trim()}`); + } + if (coreSnapshotId && coreSnapshotId.trim() !== '' && coreSnapshotId !== 'null') { + console.log(`Starting core SNAPSHOT bundle (ID: ${coreSnapshotId.trim()})...`); + ci.sh(`curl -s -u admin:admin -F action=start http://localhost:4502/system/console/bundles/${coreSnapshotId.trim()}`); + } + + console.log('Waiting 30 seconds for OSGi to re-wire bundles...'); + ci.sh('sleep 10'); + + console.log('Checking bundle stability...'); + let attempts = 0; + const maxAttempts = 30; // 450 seconds additional wait if needed + while (attempts < maxAttempts) { + const inactiveBundles = ci.sh('curl -s -u admin:admin http://localhost:4502/system/console/bundles.json | jq -r \'[.data[] | select(.state != "Active" and .state != "Fragment")] | length\'', true); + const count = parseInt(inactiveBundles.trim()); + if (count === 0) { + console.log('All bundles are active'); + break; + } + console.log(` ${count} bundles not active yet, waiting... (attempt ${attempts + 1}/${maxAttempts})`); + ci.sh('sleep 15'); + attempts++; + } + + if (attempts >= maxAttempts) { + console.log('Warning: Some bundles still not active, checking critical bundles...'); + const criticalBundles = ci.sh('curl -s -u admin:admin http://localhost:4502/system/console/bundles.json | jq -r \'.data | map(select((.symbolicName | contains("core-forms-components")) and (.version | contains("SNAPSHOT")))) | .[] | "\\(.symbolicName): \\(.state)"\'', true); + console.log('Critical SNAPSHOT bundle states:'); + console.log(criticalBundles); + } + } + } + + // const installWebVitalBundle = `curl -u admin:admin \ + // -F bundlefile=@'${buildPath}/it/core/src/main/resources/com.adobe.granite.webvitals-1.2.2.jar' \ + // -F name='com.adobe.granite.webvitals' \ + // -F action=install \ + // http://localhost:4502/system/console/bundles`; + // ci.sh(installWebVitalBundle); + // // get the bundle id + // const webVitalBundleId = ci.sh("curl -s -u admin:admin http://localhost:4502/system/console/bundles.json | jq -r '.data | map(select(.symbolicName == \"com.adobe.granite.webvitals\")) | .[0].id'", true); + // console.log("Web Vital Bundle Id " + webVitalBundleId); + // if (webVitalBundleId) { + // // start the web vital bundle + // ci.sh(`curl -u admin:admin -F action=start http://localhost:4502/system/console/bundles/${webVitalBundleId}`) + // } + } const disableToggleOption = ((FTCONFIG != null && FTCONFIG === 'false') ? `-DdisableToggle=true` : ''); if (disableToggleOption) { ci.sh(`mvn clean install -pl=it/config ${disableToggleOption} -PautoInstallPackage`); } const [node, script, ...params] = process.argv; let testSuites = params.join(','); + if (CORE_COMPONENTS) { + // we run only some test suites for older core components + testSuites = "specs/prefill/customprefill.cy.js,specs/prefill/repeatableprefillwithzerooccurrencefortabaccordionwizard.cy.js,specs/actions/submit/submit.runtime.cy.js,specs/actions/render/render_with_openapi.cy.js"; + } + // add a sleep for 8 mins since 23482 version aem has become slow + //ci.sh(`sleep 9m`); // start running the tests ci.dir('ui.tests', () => { const contextPathOption = CONTEXTPATH ? `-Daem.contextPath=/${CONTEXTPATH}` : ''; diff --git a/.circleci/config.yml b/.circleci/config.yml index 410ea138b4..0d83d1c251 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -2,7 +2,7 @@ version: 2.1 orbs: codecov: codecov/codecov@1.0.5 - browser-tools: circleci/browser-tools@1.4.8 + browser-tools: circleci/browser-tools@1.4.3 common: restore_cache: &restore_cache @@ -15,8 +15,7 @@ common: cypress_test_steps: &cypress_test_steps steps: - checkout - - browser-tools/install-chrome: - chrome-version: latest + - browser-tools/install-chrome - browser-tools/install-chromedriver - run: command: | diff --git a/.circleci/docker-compose.yml b/.circleci/docker-compose.yml index f51f41ace0..dc4cc1468f 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:22450-openjdk11 + image: docker-adobe-cif-release.dr-uw2.adobeitc.com/circleci-aem-cloudready:27293-v2-openjdk21 depends_on: - circleci-qp # Add any additional configurations or environment variables if needed diff --git a/.circleci/settings.xml b/.circleci/settings.xml index af0f70f6ee..3599b63e11 100644 --- a/.circleci/settings.xml +++ b/.circleci/settings.xml @@ -62,7 +62,7 @@ - ossrh + central true @@ -75,7 +75,7 @@ - ossrh + central ${env.SONATYPE_USERNAME} ${env.SONATYPE_PASSWORD} diff --git a/.cursor/mcp.json b/.cursor/mcp.json new file mode 100644 index 0000000000..5a0b24b508 --- /dev/null +++ b/.cursor/mcp.json @@ -0,0 +1,43 @@ +{ + "mcpServers": { + "Corp Jira": { + "command": "docker", + "args": [ + "run", + "-i", + "--rm", + "--name", + "corp-jira-mthvu", + "--env-file", + "~/.easymcp/adobe-mcp-servers/src/corp-jira/.env", + "corp-jira" + ] + }, + "Corp GitHub": { + "command": "docker", + "args": [ + "run", + "-i", + "--rm", + "--name", + "corp-github-nuwhz", + "--env-file", + "~/.easymcp/adobe-mcp-servers/src/corp-github/.env", + "corp-github" + ] + }, + "PR Review MCP Server": { + "command": "docker", + "args": [ + "run", + "-i", + "--rm", + "--name", + "pr-review-mcp-server-kherg", + "--env-file", + "~/.easymcp/adobe-mcp-servers/src/pr-review-mcp/.env", + "pr-review-mcp-server" + ] + } + } +} \ No newline at end of file diff --git a/.gitignore b/.gitignore index 37ccf822c2..eb2c837826 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,8 @@ .project .settings .DS_Store +.codex/ +reasoning/ # auto-generated clientlib ui.af.apps/src/main/content/jcr_root/apps/core/fd/af-clientlibs/core-forms-components-runtime-base/** @@ -75,3 +77,9 @@ ui.tests/test-module/cypress # Ignore Cypress runtime JSON files ui.tests/test-module/**/*.cy.json + +# Agent working artifacts +docs/superpowers/ + +# Git worktrees +.worktrees/ diff --git a/.worktrees/dialog-hidden-properties b/.worktrees/dialog-hidden-properties new file mode 160000 index 0000000000..1230fe6605 --- /dev/null +++ b/.worktrees/dialog-hidden-properties @@ -0,0 +1 @@ +Subproject commit 1230fe660534f84a1871cfd96b084e33c2eab19a diff --git a/.worktrees/forms-authoring-cli b/.worktrees/forms-authoring-cli new file mode 160000 index 0000000000..d6d20926f7 --- /dev/null +++ b/.worktrees/forms-authoring-cli @@ -0,0 +1 @@ +Subproject commit d6d20926f7c8c166b7eddc88ff697161efe2df55 diff --git a/.worktrees/jcr-authoring-schema b/.worktrees/jcr-authoring-schema new file mode 160000 index 0000000000..02d34b0ee9 --- /dev/null +++ b/.worktrees/jcr-authoring-schema @@ -0,0 +1 @@ +Subproject commit 02d34b0ee9df8d9289a5baa77f53282230be2a8c diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000000..9d8ba79973 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,17 @@ +# aem-core-forms-components + +AEM Core Forms Components — Adaptive Forms v2 component library. + +## Key References + +### Architecture +- **Project overview**: [`docs/architecture/overview.md`](docs/architecture/overview.md) + Module structure, Java model hierarchy, form JSON model, IT infrastructure, Cypress patterns, JCR content XML patterns. +- **Runtime internals**: [`docs/architecture/runtime-internals.md`](docs/architecture/runtime-internals.md) + Deep dive into the form initialization pipeline, repeatable container/InstanceManager mechanics, FT_FORMS-24358 items-array export, common crash signatures, and debugging checklist. +- **Component versioning**: [`docs/architecture/component-versioning.md`](docs/architecture/component-versioning.md) + When a change needs a new component version (`v1` → `v2`) vs. a fix-in-place, and the full checklist of what to update when bumping a version (clientlib self-containment, `_cq_dialog`/`_cq_styleConfig` inheritance, `pom.xml`, runtime clientlib embed lists, etc.). + +### E2E Testing +- **Feature Toggle Tests**: [`docs/e2e-testing/feature-toggles.md`](docs/e2e-testing/feature-toggles.md) + How to add Cypress e2e tests for new feature toggles: OSGi config changes, system property wiring, and the isLatestAddon + fetchFeatureToggles test pattern. diff --git a/README.md b/README.md index 09b1faf1d2..4cbc425a61 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,20 @@ See [AEM Sites Core Components](https://docs.adobe.com/content/help/en/experienc - [Review](ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/review/v1/review) +### XFA-based Form + +- [XFA Page](ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/xfa-page/v1/xfa-page) + +To create XFA-based core component forms, you need to use the XFA Page component. This component serves as the foundation for rendering XFA forms. + +## Theme Editor + +When adding, creating, or modifying a component, update the style configuration so the new styles are available in authoring: + +- Add or update the component's `_cq_styleConfig/.content.xml`. +- Update the `_cq_themeConfig/.content.xml` to point to the correct version when a component is added or created. +- Keep the component README in sync if it documents style classes or variants. + ## System Requirements The latest version of the AEM Forms Core Components, require the below minimum system requirements: diff --git a/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/constants/ThemeConstants.java b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/constants/ThemeConstants.java new file mode 100644 index 0000000000..d8230e510a --- /dev/null +++ b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/constants/ThemeConstants.java @@ -0,0 +1,26 @@ +package com.adobe.cq.forms.core.components.internal.constants; + +/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ~ Copyright 2026 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. + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +public class ThemeConstants { + + public static final String RELATIVE_PATH_METADATA = "/metadata"; + public static final String THEME_OVERRIDE = "themeOverride"; + public static final String THEME_REF = "themeRef"; + public static final String PROPERTY_CLIENTLIB_CATEGORY = "clientlibCategory"; + +} diff --git a/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/form/FeatureToggleConstants.java b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/form/FeatureToggleConstants.java new file mode 100644 index 0000000000..2db537b7c4 --- /dev/null +++ b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/form/FeatureToggleConstants.java @@ -0,0 +1,109 @@ +/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ~ Copyright 2025 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.form; + +/** + * Feature toggle identifiers for form components. When a toggle is enabled, the corresponding + * behavior is active; when disabled or when the toggle service is absent, legacy behavior is used. + *

+ * Toggles are driven by system properties. Code checks only the system property; the property is + * set/unset by the Granite Toggle API when you wire a toggle to it via OSGi. Setup: + *

    + *
  1. Enable the toggle: In the Web Console (configMgr), find "Adobe Granite + * Dynamic Toggle Provider" and add the toggle ID (e.g. {@link #FT_FRAGMENT_MERGE_CONTAINER_RULES_EVENTS}) + * to the Enabled Toggles list. You can verify the toggle is active via the web console.
  2. + *
  3. Wire toggle to a system property: Add an OSGi factory configuration for + * {@code com.adobe.granite.toggle.monitor.systemproperty} (the ToggleMonitorSystemPropertyFactory). + * Use the same name for toggle and system property. Example JSON config: + * + *
    + * {@code
    + * "com.adobe.granite.toggle.monitor.systemproperty~forms-fragment-merge": {
    + *   "toggle.name": "FT_FORMS-24087",
    + *   "system.property": "FT_FORMS-24087",
    + *   "fail.used": false
    + * }
    + * }
    + * 
    + * + * The segment after {@code ~} is a unique instance name. When the toggle is enabled, the factory + * sets the system property; when disabled, it unsets it.
  4. + *
  5. In code: Use {@link com.adobe.cq.forms.core.components.util.ComponentUtils#isToggleEnabled(String)} + * with the toggle ID; it reads the system property (value {@code "true"} means enabled).
  6. + *
+ * You can also enable a toggle for local or override use by setting the JVM system property + * directly (e.g. {@code -DFT_FORMS-24087=true}) without the OSGi config. + */ +public final class FeatureToggleConstants { + + private FeatureToggleConstants() {} + + /** + * When enabled, fragment container rules and events are merged with the fragment placeholder + * (panel) rules and events: for rules the panel overrides the container for the same key; for + * events panel handlers run first then fragment handlers are appended. When disabled, only the + * panel rules and events are used; fragment container rules/events are not merged. + *

+ * System property: same name ({@code FT_FORMS-24087}); set to {@code "true"} to enable when + * using {@code ToggleMonitorSystemPropertyFactory} or for direct -D JVM override. + */ + public static final String FT_FRAGMENT_MERGE_CONTAINER_RULES_EVENTS = "FT_FORMS-24087"; + + /** + * When enabled, the default {@code custom:setProperty} event handler ({@code "$event.payload"}) + * is no longer injected into the model JSON by the server. The af2-web-runtime provides this + * default at the client side, reducing JSON payload size for every form component. + *

+ * Prerequisite: the af2-web-runtime must include the corresponding fallback + * in {@code Scriptable.getCompiledEvent} before this toggle is enabled. + *

+ * System property: same name ({@code FT_FORMS-24343}); set to {@code "true"} to enable. + */ + public static final String FT_SKIP_DEFAULT_SET_PROPERTY_EVENT = "FT_FORMS-24343"; + + /** + * When enabled, container components (panels, wizards, fragments, form containers) export their + * children as a flat {@code "items"} JSON array instead of the Sling Model Exporter's default + * {@code ":items"} map + {@code ":itemsOrder"} array. This reduces JSON payload size by + * eliminating the redundant resource-name keys and the separate ordering array. + *

+ * Prerequisite: the af2-web-runtime already supports the {@code items} array + * format natively via {@code Container._initialize()}. + *

+ * System property: same name ({@code FT_FORMS-24358}); set to {@code "true"} to enable. + */ + public static final String FT_SKIP_ITEMS_MAP = "FT_FORMS-24358"; + + /** + * When enabled, the form container exports {@code fd:changeEventBehaviour="deps"}, which makes the + * af2-web-runtime allow multiple fields in a single {@code when} rule expression. Previously this + * property was injected by the {@code CoreComponentCustomPropertiesProvider} addon service (which + * read the Granite toggle directly); the behavior now lives in the core component and is driven by + * the system property, so the addon provider is no longer required. + *

+ * System property: same name ({@code FT_FORMS-12053}); set to {@code "true"} to enable when using + * {@code ToggleMonitorSystemPropertyFactory} or for direct -D JVM override. + */ + public static final String FT_ALLOW_MULTIPLE_FIELDS_IN_WHEN = "FT_FORMS-12053"; + /** + * When enabled, the Server-Side Validation (SSV) cloud configuration dropdown is shown in the + * form container dialog, allowing authors to select a validator endpoint to be called before + * form submission. When disabled, the SSV option is hidden and no server-side validation occurs. + *

+ * System property: same name ({@code FT_FORMS-25252}); set to {@code "true"} to enable. + */ + public static final String FT_SERVER_SIDE_VALIDATION = "FT_FORMS-25252"; +} 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 5d7c410231..cdd07a9f5f 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 @@ -143,7 +143,21 @@ private FormConstants() { /** Form definition type indicating submission view */ public static final String FORM_DEFINITION_SUBMISSION = "submission"; + /** Request attribute set when the form definition is serialized with Publish view */ + public static final String REQ_ATTR_PUBLISH_VIEW = "fd:publishView"; + /** The resource type for date time input field v1 */ public static final String RT_FD_FORM_DATETIME_V1 = RT_FD_FORM_PREFIX + "datetime/v1/datetime"; + /** The resource type for image choice v1 */ + public static final String RT_FD_FORM_IMAGE_CHOICE_V1 = RT_FD_FORM_PREFIX + "imagechoice/v1/imagechoice"; + /** The resource type for table v1 */ + public static final String RT_FD_FORM_TABLE_V1 = RT_FD_FORM_PREFIX + "table/v1/table"; + + /** The resource type for table header v1 */ + public static final String RT_FD_FORM_TABLE_HEADER_V1 = RT_FD_FORM_PREFIX + "tableheader/v1/tableheader"; + + /** The resource type for table row v1 */ + public static final String RT_FD_FORM_TABLE_ROW_V1 = RT_FD_FORM_PREFIX + "tablerow/v1/tablerow"; + } diff --git a/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/form/FormStructureParserImpl.java b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/form/FormStructureParserImpl.java index 3fbb5d6748..c89768cabc 100644 --- a/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/form/FormStructureParserImpl.java +++ b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/form/FormStructureParserImpl.java @@ -17,9 +17,12 @@ import java.io.StringWriter; import java.io.Writer; +import java.nio.charset.StandardCharsets; +import org.apache.commons.lang3.StringUtils; import org.apache.sling.api.SlingHttpServletRequest; import org.apache.sling.api.resource.Resource; +import org.apache.sling.api.resource.ValueMap; import org.apache.sling.models.annotations.Model; import org.apache.sling.models.annotations.injectorspecific.InjectionStrategy; import org.apache.sling.models.annotations.injectorspecific.SlingObject; @@ -27,6 +30,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.adobe.cq.forms.core.components.internal.constants.ThemeConstants; import com.adobe.cq.forms.core.components.models.form.FormContainer; import com.adobe.cq.forms.core.components.models.form.FormStructureParser; import com.adobe.cq.forms.core.components.models.form.HtlUtil; @@ -55,15 +59,35 @@ public String getFormContainerPath() { } @Override - public String getThemeClientLibRefFromFormContainer() { + public String getClientLibRefFromFormContainer() { FormContainer formContainer = getFormContainer(resource); - return formContainer != null ? formContainer.getThemeClientLibRef() : null; + return formContainer != null ? formContainer.getClientLibRef() : null; } @Override - public String getClientLibRefFromFormContainer() { - FormContainer formContainer = getFormContainer(resource); - return formContainer != null ? formContainer.getClientLibRef() : null; + public String getThemeEditorThemeRef() { + String themeContentPath = null; + String themeClientLibRef = null; + if (request != null) { + themeContentPath = (String) request.getAttribute(ThemeConstants.THEME_OVERRIDE); // theme editor use-case + } + if (StringUtils.isBlank(themeContentPath)) { + if (request != null) { + themeContentPath = request.getParameter(ThemeConstants.THEME_OVERRIDE); // embed component use-case + } + } + // get client library from theme content path + if (StringUtils.isNotBlank(themeContentPath)) { + Resource themeResource = resource.getResourceResolver().getResource(themeContentPath + ThemeConstants.RELATIVE_PATH_METADATA); + if (themeResource != null) { + ValueMap themeProps = themeResource.getValueMap(); + themeClientLibRef = themeProps.get(ThemeConstants.PROPERTY_CLIENTLIB_CATEGORY, ""); + } else { + logger.error("Invalid Theme Name {}", themeContentPath); + } + } + + return themeClientLibRef; } @Override @@ -140,10 +164,17 @@ public String getFormDefinition() { if (isSubmissionView) { request.setAttribute(FormConstants.X_ADOBE_FORM_DEFINITION, FormConstants.FORM_DEFINITION_SUBMISSION); } + if (request != null) { + request.setAttribute(FormConstants.REQ_ATTR_PUBLISH_VIEW, Boolean.TRUE); + } objectWriter = mapper.writerWithView(Views.Publish.class); // objectWriter.getFactory().setCharacterEscapes(htmlCharacterEscapes); objectWriter.writeValue(writer, formContainer); result = writer.toString(); + if (result != null) { + double sizeKb = result.getBytes(StandardCharsets.UTF_8).length / 1024.0; + logger.info("Form definition JSON size: {} KB, form path: {}", sizeKb, resource.getPath()); + } } catch (Exception e) { logger.error("Unable to generate json from resource", e); } diff --git a/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/form/ReservedProperties.java b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/form/ReservedProperties.java index 545b2a6f5c..48a3e3b966 100644 --- a/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/form/ReservedProperties.java +++ b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/form/ReservedProperties.java @@ -103,6 +103,7 @@ private ReservedProperties() { public static final String PN_HIDE_TITLE = "hideTitle"; public static final String PN_IS_TITLE_RICH_TEXT = "isTitleRichText"; public static final String PN_ORIENTATION = "orientation"; + public static final String PN_SELECTION_TYPE = "selectionType"; public static final String PN_TYPE_MESSAGE = "typeMessage"; public static final String PN_REQUIRED_MESSAGE = "mandatoryMessage"; // reusing the same property name as in foundation public static final String PN_MINIMUM_MESSAGE = "minimumMessage"; @@ -131,6 +132,7 @@ private ReservedProperties() { public static final String PN_BUTTON_TEXT = "buttonText"; public static final String PN_WRAP_DATA = "wrapData"; public static final String PN_FRAGMENT_PATH = "fragmentPath"; + public static final String PN_LAZY = "lazy"; public static final String PN_BUTTON_TYPE = "buttonType"; public static final String PN_THANK_YOU_MSG_V1 = "thankyouMessage"; public static final String PN_THANK_YOU_MSG_V2 = "thankYouMessage"; @@ -169,8 +171,24 @@ private ReservedProperties() { public static final String FD_AUTO_SAVE_INTERVAL = "fd:autoSaveInterval"; public static final String FD_EXCLUDE_FROM_DOR_IF_HIDDEN = "fd:excludeFromDoRIfHidden"; public static final String FD_DIALOG_LABEL = "fd:dialogLabel"; + public static final String FD_FILE_ACCEPT_EXTENSIONS = "fd:acceptExtensions"; public static final String FD_DRAFT_ID = "fd:draftId"; + public static final String FD_CHANGE_EVENT_BEHAVIOUR = "fd:changeEventBehaviour"; + + /** + * Comma-separated proportional column widths JCR property, also passed into fd:dor for DOR table rendering (matches + * GuideTableElement key). + */ + public static final String PN_DOR_COLUMN_WIDTHS = "columnWidth"; + + /** When true, adaptive form table columns can be sorted at runtime (publish). */ + public static final String PN_ENABLE_SORTING = "enableSorting"; + + /** fd:viewType values for table sub-components — used as :type in exported JSON and matched in AF2→AF1 DOR transformer. */ + public static final String VT_TABLE = "table"; + public static final String VT_TABLE_ROW = "table-row"; + public static final String VT_TABLE_HEADER = "table-header"; // Begin: Form submission related properties public static final String FD_SUBMIT_PROPERTIES = "fd:submit"; @@ -182,6 +200,18 @@ private ReservedProperties() { public static final String PN_SUBMIT_EMAIL_CC = "cc"; public static final String PN_SUBMIT_EMAIL_BCC = "bcc"; public static final String PN_SUBMIT_SPREADSHEETURL = "spreadsheetUrl"; + + // AEP submit action properties + public static final String SS_AEP = "aep"; + public static final String PN_SUBMIT_AEP_SCHEMA_REF_ID = "schemaRefId"; + public static final String PN_SUBMIT_AEP_IMS_ORG_ID = "imsOrgId"; + public static final String PN_SUBMIT_AEP_SANDBOX_REGION = "sandboxRegion"; + public static final String PN_SUBMIT_AEP_SANDBOX_ID = "sandboxId"; + public static final String PN_SUBMIT_AEP_DATASET_ID = "datasetId"; + public static final String PN_SUBMIT_AEP_SANDBOX_NAME = "sandboxName"; + + public static final String PN_ENABLE_SERVER_SIDE_VALIDATION = "fd:enableServerSideValidation"; + public static final String PN_SSV_CLOUD_SERVICE_PATH = "fd:ssvCloudServicePath"; // End: Form submission related properties private static final Set reservedProperties = aggregateReservedProperties(); diff --git a/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/models/v1/form/FragmentImpl.java b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/models/v1/form/FragmentImpl.java index 6c0714ef30..eb161497c1 100644 --- a/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/models/v1/form/FragmentImpl.java +++ b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/models/v1/form/FragmentImpl.java @@ -15,7 +15,15 @@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ package com.adobe.cq.forms.core.components.internal.models.v1.form; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.ResourceBundle; +import java.util.UUID; import javax.annotation.Nonnull; import javax.annotation.PostConstruct; @@ -41,6 +49,7 @@ import com.adobe.cq.export.json.ComponentExporter; import com.adobe.cq.export.json.ExporterConstants; import com.adobe.cq.export.json.SlingModelFilter; +import com.adobe.cq.forms.core.components.internal.form.FeatureToggleConstants; import com.adobe.cq.forms.core.components.internal.form.FormConstants; import com.adobe.cq.forms.core.components.internal.form.ReservedProperties; import com.adobe.cq.forms.core.components.models.form.FormClientLibManager; @@ -48,10 +57,9 @@ import com.adobe.cq.forms.core.components.models.form.FormContainer; import com.adobe.cq.forms.core.components.models.form.Fragment; import com.adobe.cq.forms.core.components.util.ComponentUtils; -import com.adobe.cq.forms.core.components.views.Views; import com.day.cq.i18n.I18n; import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonView; +import com.fasterxml.jackson.annotation.JsonInclude; @Model( adaptables = { SlingHttpServletRequest.class, Resource.class }, @@ -78,6 +86,9 @@ public class FragmentImpl extends PanelImpl implements Fragment { @ValueMapValue(injectionStrategy = InjectionStrategy.OPTIONAL, name = ReservedProperties.PN_FRAGMENT_PATH) private String fragmentPath; + @ValueMapValue(injectionStrategy = InjectionStrategy.OPTIONAL, name = ReservedProperties.PN_LAZY) + private Boolean lazy; + private Resource fragmentContainer; @PostConstruct @@ -91,15 +102,25 @@ private void initFragmentModel() { formClientLibManager.addClientLibRef(clientLibRef); } } + if (Boolean.TRUE.equals(lazy)) { + fragmentContainer = null; + } } - @JsonView(Views.Author.class) public String getFragmentPath() { return fragmentPath; } + @JsonInclude(JsonInclude.Include.NON_DEFAULT) + public boolean getLazy() { + return Boolean.TRUE.equals(lazy); + } + @Override public @NotNull Map getExportedItems() { + if (ComponentUtils.isToggleEnabled(FeatureToggleConstants.FT_SKIP_ITEMS_MAP)) { + return Collections.emptyMap(); + } if (itemModels == null) { itemModels = getChildrenModels(request, ComponentExporter.class); } @@ -107,6 +128,9 @@ public String getFragmentPath() { } protected Map getChildrenModels(@Nullable SlingHttpServletRequest request, @NotNull Class modelClass) { + if (fragmentContainer == null) { + return new LinkedHashMap<>(); + } List filteredChildrenResources = getFilteredChildrenResources(fragmentContainer); SlingHttpServletRequest wrappedSlingHttpServletRequest = null; if (request != null) { @@ -154,7 +178,7 @@ public Object getAttribute(String attrName) { private @Nonnull I18n getFragmentContainerI18n(@Nonnull String localeLang) { // Get the locale from the lang setter ResourceBundle resourceBundle = null; - if (localeLang != null) { + if (localeLang != null && fragmentContainer != null) { Locale desiredLocale = new Locale(localeLang); // Get the resource resolver from the fragment container ResourceResolver resourceResolver = fragmentContainer.getResourceResolver(); @@ -222,6 +246,40 @@ public Map getProperties() { return properties; } + @Override + public Map getEvents() { + if (fragmentContainer != null && isFragmentMergeContainerRulesEventsEnabled()) { + Map userEvents = new LinkedHashMap<>(super.getEvents()); + Map fragmentEvents = getEventsForResource(fragmentContainer); + for (Map.Entry entry : fragmentEvents.entrySet()) { + String[] existing = userEvents.get(entry.getKey()); + if (existing != null) { + String[] combined = Arrays.copyOf(existing, existing.length + entry.getValue().length); + System.arraycopy(entry.getValue(), 0, combined, existing.length, entry.getValue().length); + userEvents.put(entry.getKey(), combined); + } else { + userEvents.put(entry.getKey(), entry.getValue()); + } + } + return userEvents; + } + return super.getEvents(); + } + + @Override + public Map getRules() { + if (fragmentContainer != null && isFragmentMergeContainerRulesEventsEnabled()) { + Map merged = new LinkedHashMap<>(getRulesForResource(fragmentContainer)); + merged.putAll(super.getRules()); + return merged; + } + return super.getRules(); + } + + private boolean isFragmentMergeContainerRulesEventsEnabled() { + return ComponentUtils.isToggleEnabled(FeatureToggleConstants.FT_FRAGMENT_MERGE_CONTAINER_RULES_EVENTS); + } + private String getClientLibForFragment() { String clientLibRef = null; if (fragmentContainer != null) { diff --git a/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/models/v1/form/ImageChoiceImpl.java b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/models/v1/form/ImageChoiceImpl.java new file mode 100644 index 0000000000..2c147ed45c --- /dev/null +++ b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/models/v1/form/ImageChoiceImpl.java @@ -0,0 +1,115 @@ +/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ~ Copyright 2026 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.Map; + +import javax.annotation.Nullable; +import javax.annotation.PostConstruct; + +import org.apache.sling.api.SlingHttpServletRequest; +import org.apache.sling.api.resource.Resource; +import org.apache.sling.models.annotations.Exporter; +import org.apache.sling.models.annotations.Model; +import org.apache.sling.models.annotations.injectorspecific.InjectionStrategy; +import org.apache.sling.models.annotations.injectorspecific.ValueMapValue; +import org.jetbrains.annotations.NotNull; + +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.internal.form.ReservedProperties; +import com.adobe.cq.forms.core.components.models.form.CheckBox; +import com.adobe.cq.forms.core.components.models.form.FieldType; +import com.adobe.cq.forms.core.components.models.form.ImageChoice; +import com.adobe.cq.forms.core.components.util.AbstractOptionsFieldImpl; +import com.fasterxml.jackson.annotation.JsonIgnore; + +@Model( + adaptables = { SlingHttpServletRequest.class, Resource.class }, + adapters = { ImageChoice.class, + ComponentExporter.class }, + resourceType = { FormConstants.RT_FD_FORM_IMAGE_CHOICE_V1 }) +@Exporter(name = ExporterConstants.SLING_MODEL_EXPORTER_NAME, extensions = ExporterConstants.SLING_MODEL_EXTENSION) +public class ImageChoiceImpl extends AbstractOptionsFieldImpl implements ImageChoice { + + @ValueMapValue(injectionStrategy = InjectionStrategy.OPTIONAL, name = ReservedProperties.PN_ORIENTATION) + @Nullable + protected String orientationJcr; + + @ValueMapValue(injectionStrategy = InjectionStrategy.OPTIONAL, name = ReservedProperties.PN_SELECTION_TYPE) + @Nullable + protected String selectionTypeJcr; + + @ValueMapValue(injectionStrategy = InjectionStrategy.OPTIONAL, name = ReservedProperties.PN_IMAGE_SRC) + @Nullable + protected String[] imageSrcArray; + + private CheckBox.Orientation orientation; + private SelectionType selectionType; + + @PostConstruct + private void initImageChoiceModel() { + orientation = CheckBox.Orientation.fromString(orientationJcr); + selectionType = SelectionType.fromString(selectionTypeJcr); + } + + @Override + public @NotNull Map getCustomLayoutProperties() { + Map customLayoutProperties = super.getCustomLayoutProperties(); + customLayoutProperties.put(ReservedProperties.PN_ORIENTATION, orientation.getValue()); + customLayoutProperties.put(ReservedProperties.PN_SELECTION_TYPE, selectionType.getValue()); + if (imageSrcArray != null) { + customLayoutProperties.put(ReservedProperties.PN_IMAGE_SRC, imageSrcArray.clone()); + } + return customLayoutProperties; + } + + @Override + @JsonIgnore + public CheckBox.Orientation getOrientation() { + return orientation; + } + + @Override + @JsonIgnore + public SelectionType getSelectionType() { + return selectionType; + } + + @Override + public String[] getImageSrc() { + return imageSrcArray != null ? imageSrcArray.clone() : null; + } + + @Override + public Integer getMinItems() { + return minItems; + } + + @Override + public Integer getMaxItems() { + return maxItems; + } + + @Override + public String getFieldType() { + FieldType defaultType = selectionType == SelectionType.MULTI + ? FieldType.CHECKBOX_GROUP + : FieldType.RADIO_GROUP; + return super.getFieldType(defaultType); + } +} diff --git a/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/models/v1/form/PanelImpl.java b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/models/v1/form/PanelImpl.java index b066e38587..6aebdfb7b5 100644 --- a/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/models/v1/form/PanelImpl.java +++ b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/models/v1/form/PanelImpl.java @@ -59,15 +59,15 @@ public class PanelImpl extends AbstractContainerImpl implements Panel { @ValueMapValue(injectionStrategy = InjectionStrategy.OPTIONAL, name = ReservedProperties.PN_DOR_EXCLUDE_TITLE) @Nullable - protected boolean dorExcludeTitle; + protected Boolean dorExcludeTitle; @ValueMapValue(injectionStrategy = InjectionStrategy.OPTIONAL, name = ReservedProperties.PN_DOR_EXCLUSION) @Nullable - protected boolean dorExclusion; + protected Boolean dorExclusion; @ValueMapValue(injectionStrategy = InjectionStrategy.OPTIONAL, name = ReservedProperties.PN_DOR_EXCLUDE_DESC) @Nullable - protected boolean dorExcludeDescription; + protected Boolean dorExcludeDescription; @ValueMapValue(injectionStrategy = InjectionStrategy.OPTIONAL, name = ReservedProperties.PN_BREAK_BEFORE_TEXT) @Nullable @@ -126,9 +126,15 @@ public Boolean isReadOnly() { public Map getDorProperties() { Map customDorProperties = new LinkedHashMap<>(); - customDorProperties.put(DOR_EXCLUSION, dorExclusion); - customDorProperties.put(DOR_EXCLUDE_TITLE, dorExcludeTitle); - customDorProperties.put(DOR_EXCLUDE_DESCRIPTION, dorExcludeDescription); + if (dorExclusion != null) { + customDorProperties.put(DOR_EXCLUSION, dorExclusion); + } + if (dorExcludeTitle != null) { + customDorProperties.put(DOR_EXCLUDE_TITLE, dorExcludeTitle); + } + if (dorExcludeDescription != null) { + customDorProperties.put(DOR_EXCLUDE_DESCRIPTION, dorExcludeDescription); + } if (breakBeforeText != null) { customDorProperties.put(BREAK_BEFORE_TEXT, breakBeforeText); diff --git a/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/models/v1/form/TableHeaderImpl.java b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/models/v1/form/TableHeaderImpl.java new file mode 100644 index 0000000000..cec915f247 --- /dev/null +++ b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/models/v1/form/TableHeaderImpl.java @@ -0,0 +1,42 @@ +/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ~ Copyright 2026 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.SlingHttpServletRequest; +import org.apache.sling.api.resource.Resource; +import org.apache.sling.models.annotations.Exporter; +import org.apache.sling.models.annotations.Model; +import org.jetbrains.annotations.NotNull; + +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.internal.form.ReservedProperties; +import com.adobe.cq.forms.core.components.models.form.Panel; + +@Model( + adaptables = { SlingHttpServletRequest.class, Resource.class }, + adapters = { Panel.class, ComponentExporter.class }, + resourceType = { FormConstants.RT_FD_FORM_TABLE_HEADER_V1 }) +@Exporter(name = ExporterConstants.SLING_MODEL_EXPORTER_NAME, extensions = ExporterConstants.SLING_MODEL_EXTENSION) +public class TableHeaderImpl extends PanelImpl { + + @Override + @NotNull + public String getExportedType() { + return ReservedProperties.VT_TABLE_HEADER; + } +} diff --git a/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/models/v1/form/TableImpl.java b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/models/v1/form/TableImpl.java new file mode 100644 index 0000000000..cf10ec50ca --- /dev/null +++ b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/models/v1/form/TableImpl.java @@ -0,0 +1,142 @@ +/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ~ Copyright 2026 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.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import javax.annotation.Nullable; + +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 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.internal.form.ReservedProperties; +import com.adobe.cq.forms.core.components.models.form.Panel; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; + +@Model( + adaptables = { SlingHttpServletRequest.class, Resource.class }, + adapters = { Panel.class, ComponentExporter.class }, + resourceType = { FormConstants.RT_FD_FORM_TABLE_V1 }) +@Exporter(name = ExporterConstants.SLING_MODEL_EXPORTER_NAME, extensions = ExporterConstants.SLING_MODEL_EXTENSION) +public class TableImpl extends PanelImpl { + + @ValueMapValue(injectionStrategy = InjectionStrategy.OPTIONAL, name = ReservedProperties.PN_DOR_COLUMN_WIDTHS) + @Nullable + protected String columnWidth; + + @ValueMapValue(injectionStrategy = InjectionStrategy.OPTIONAL, name = ReservedProperties.PN_ENABLE_SORTING) + @Nullable + protected Boolean enableSorting; + + /** + * When {@code true}, runtime (publish) enables client-side column sorting. + * Omitted from JSON when {@code false} (see {@link JsonInclude.Include#NON_DEFAULT}). + */ + @JsonProperty("enableSorting") + @JsonInclude(JsonInclude.Include.NON_DEFAULT) + public boolean isEnableSorting() { + return Boolean.TRUE.equals(enableSorting); + } + + /** + * Returns the raw comma-separated column width string from JCR (e.g. "1,1,4"). + * Used in edit mode as the {@code data-column-widths} attribute. + */ + @JsonIgnore + public String getColumnWidth() { + return columnWidth; + } + + /** + * True when {@link #getColumnWidthColStyles()} would return a non-empty list (valid authored widths). + */ + @JsonIgnore + public boolean isColumnWidthsConfigured() { + return !getColumnWidthColStyles().isEmpty(); + } + + @Override + @NotNull + public String getExportedType() { + return ReservedProperties.VT_TABLE; + } + + @Override + @JsonIgnore + @NotNull + public Map getDorProperties() { + Map props = new LinkedHashMap<>(super.getDorProperties()); + if (columnWidth != null && !columnWidth.isEmpty()) { + // Pass authored proportional widths into fd:dor so DoRTableElement can compute + // proportional XFA column widths (e.g. "1,2,1" → relative pixel widths). + props.put(ReservedProperties.PN_DOR_COLUMN_WIDTHS, columnWidth); + } + return props; + } + + /** + * One entry per column for {@code }: {@code width: N%} (no HTL string concatenation). + * For example, proportional {@code "1,1,4"} becomes {@code width: 16%}, {@code width: 16%}, {@code width: 66%}. + * Negative or non-numeric values are treated as 1. The last column absorbs any rounding remainder so widths sum to 100%. + */ + @JsonIgnore + public List getColumnWidthColStyles() { + if (columnWidth == null || columnWidth.isEmpty()) { + return Collections.emptyList(); + } + String[] parts = columnWidth.split(","); + int[] values = new int[parts.length]; + int sum = 0; + for (int i = 0; i < parts.length; i++) { + try { + values[i] = Integer.parseInt(parts[i].trim()); + } catch (NumberFormatException e) { + values[i] = 1; + } + if (values[i] < 0) { + values[i] = 0; + } + sum += values[i]; + } + if (sum == 0) { + return Collections.emptyList(); + } + List result = new ArrayList<>(); + int allocated = 0; + for (int i = 0; i < values.length; i++) { + int pct = (i == values.length - 1) + ? (100 - allocated) + : (int) Math.floor((values[i] * 100.0) / sum); + allocated += pct; + result.add("width: " + pct + "%"); + } + return result; + } +} diff --git a/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/models/v1/form/TableRowImpl.java b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/models/v1/form/TableRowImpl.java new file mode 100644 index 0000000000..5fff674f1d --- /dev/null +++ b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/models/v1/form/TableRowImpl.java @@ -0,0 +1,42 @@ +/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ~ Copyright 2026 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.SlingHttpServletRequest; +import org.apache.sling.api.resource.Resource; +import org.apache.sling.models.annotations.Exporter; +import org.apache.sling.models.annotations.Model; +import org.jetbrains.annotations.NotNull; + +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.internal.form.ReservedProperties; +import com.adobe.cq.forms.core.components.models.form.Panel; + +@Model( + adaptables = { SlingHttpServletRequest.class, Resource.class }, + adapters = { Panel.class, ComponentExporter.class }, + resourceType = { FormConstants.RT_FD_FORM_TABLE_ROW_V1 }) +@Exporter(name = ExporterConstants.SLING_MODEL_EXPORTER_NAME, extensions = ExporterConstants.SLING_MODEL_EXTENSION) +public class TableRowImpl extends PanelImpl { + + @Override + @NotNull + public String getExportedType() { + return ReservedProperties.VT_TABLE_ROW; + } +} diff --git a/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/models/v1/form/TermsAndConditionsImpl.java b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/models/v1/form/TermsAndConditionsImpl.java index ba467f2eab..390b2b4107 100644 --- a/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/models/v1/form/TermsAndConditionsImpl.java +++ b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/models/v1/form/TermsAndConditionsImpl.java @@ -46,6 +46,7 @@ public class TermsAndConditionsImpl extends PanelImpl implements TermsAndConditions { private static final String CUSTOM_TNC_PROPERTY = "fd:tnc"; + private static final String SHOW_AS_POPUP_PROPERTY = "fd:showAsPopup"; @JsonIgnore @ValueMapValue(injectionStrategy = InjectionStrategy.OPTIONAL, name = ReservedProperties.PN_SHOW_APPROVAL_OPTION) @@ -88,6 +89,7 @@ public boolean isShowAsPopup() { if (resource.getValueMap().containsKey(CUSTOM_TNC_PROPERTY)) { properties.put(CUSTOM_TNC_PROPERTY, true); } + properties.put(SHOW_AS_POPUP_PROPERTY, isShowAsPopup()); return properties; } diff --git a/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/models/v1/form/TextImpl.java b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/models/v1/form/TextImpl.java index de6d93342a..9381104053 100644 --- a/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/models/v1/form/TextImpl.java +++ b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/models/v1/form/TextImpl.java @@ -17,12 +17,12 @@ import org.apache.sling.api.SlingHttpServletRequest; import org.apache.sling.api.resource.Resource; -import org.apache.sling.models.annotations.Default; 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.SlingObject; import org.apache.sling.models.annotations.injectorspecific.ValueMapValue; +import org.jetbrains.annotations.Nullable; import com.adobe.cq.export.json.ComponentExporter; import com.adobe.cq.export.json.ExporterConstants; @@ -32,6 +32,8 @@ import com.adobe.cq.forms.core.components.models.form.Text; import com.adobe.cq.forms.core.components.util.AbstractFormComponentImpl; import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; @Model( adaptables = { SlingHttpServletRequest.class, Resource.class }, @@ -42,8 +44,8 @@ public class TextImpl extends AbstractFormComponentImpl implements Text { @ValueMapValue(injectionStrategy = InjectionStrategy.OPTIONAL, name = ReservedProperties.PN_TEXT_IS_RICH) - @Default(booleanValues = false) - private boolean textIsRich; + @Nullable + private Boolean textIsRich; @SlingObject private Resource resource; @@ -54,7 +56,14 @@ public String getValue() { } @Override + @JsonIgnore public boolean isRichText() { + return textIsRich != null && textIsRich; + } + + @JsonProperty("richText") + @JsonInclude(JsonInclude.Include.NON_NULL) + public Boolean getRichText() { return textIsRich; } 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 ee8580198c..af87b9ea9f 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 @@ -48,6 +48,7 @@ import com.adobe.cq.export.json.ComponentExporter; import com.adobe.cq.export.json.ContainerExporter; import com.adobe.cq.export.json.ExporterConstants; +import com.adobe.cq.forms.core.components.internal.form.FeatureToggleConstants; import com.adobe.cq.forms.core.components.internal.form.FormConstants; import com.adobe.cq.forms.core.components.internal.form.ReservedProperties; import com.adobe.cq.forms.core.components.internal.models.v1.form.FormMetaDataImpl; @@ -88,6 +89,7 @@ public class FormContainerImpl extends AbstractContainerImpl implements FormCont private static final String FD_DATA_URL = "fd:dataUrl"; private static final String FD_VIEW_PRINT_PATH = "fd:view/print"; private static final String EXCLUDE_FROM_DOR_IF_HIDDEN = "excludeFromDoRIfHidden"; + private static final String CHANGE_EVENT_BEHAVIOUR_DEPS = "deps"; /** Constant representing email submit action type */ private static final String SS_EMAIL = "email"; @@ -95,6 +97,9 @@ public class FormContainerImpl extends AbstractContainerImpl implements FormCont /** Constant representing spreadsheet submit action type */ private static final String SS_SPREADSHEET = "spreadsheet"; + /** Constant representing AEP submit action type */ + private static final String SS_AEP = ReservedProperties.SS_AEP; + @OSGiService(injectionStrategy = InjectionStrategy.OPTIONAL) private CoreComponentCustomPropertiesProvider coreComponentCustomPropertiesProvider; @@ -145,6 +150,10 @@ public class FormContainerImpl extends AbstractContainerImpl implements FormCont @Nullable private String roleAttribute; + @ValueMapValue(name = ReservedProperties.FD_CHANGE_EVENT_BEHAVIOUR, injectionStrategy = InjectionStrategy.OPTIONAL) + @Nullable + private String changeEventBehaviour; + @ValueMapValue(injectionStrategy = InjectionStrategy.OPTIONAL, name = ReservedProperties.PN_DATA) @Nullable private String data; @@ -387,6 +396,13 @@ public String getLanguageDirection() { properties.putAll(customProperties); } } + // fd:changeEventBehaviour="deps" lets the runtime allow multiple fields in a single "when" rule. + // Besides the CoreComponentCustomPropertiesProvider addon above, the core component also drives this + // default from the feature toggle system property (both emit the same "deps" value idempotently). A + // value set on the node still overrides this default (applied later, after super.getProperties()). + if (ComponentUtils.isToggleEnabled(FeatureToggleConstants.FT_ALLOW_MULTIPLE_FIELDS_IN_WHEN)) { + properties.put(ReservedProperties.FD_CHANGE_EVENT_BEHAVIOUR, CHANGE_EVENT_BEHAVIOUR_DEPS); + } properties.putAll(super.getProperties()); if (getSchemaType() != null) { properties.put(FD_SCHEMA_TYPE, getSchemaType()); @@ -410,6 +426,12 @@ public String getLanguageDirection() { } } properties.put(FD_ROLE_ATTRIBUTE, getRoleAttribute()); + // A value set on the node always takes precedence and is emitted regardless of the system-level + // toggle/provider state above (consistent with how other node-level custom properties win). When the + // toggle is disabled the default above is absent, so this simply sets the node-authored value. + if (StringUtils.isNotBlank(changeEventBehaviour)) { + properties.put(ReservedProperties.FD_CHANGE_EVENT_BEHAVIOUR, changeEventBehaviour); + } properties.put(FD_FORM_DATA_ENABLED, formDataEnabled); if (this.autoSaveConfig != null && this.autoSaveConfig.isEnableAutoSave()) { properties.put(ReservedProperties.FD_AUTO_SAVE_PROPERTY_WRAPPER, this.autoSaveConfig); @@ -492,7 +514,9 @@ private Map getSubmitProperties() { Map submitProps = null; - if (request == null || ComponentUtils.shouldIncludeSubmitProperties(request)) { + String actionName = resource.getValueMap().get(ReservedProperties.PN_SUBMIT_ACTION_NAME, String.class); + if (request == null || ComponentUtils.shouldIncludeSubmitProperties(request) + || (StringUtils.isNotBlank(actionName) && (SS_AEP.equals(actionName)))) { submitProps = new LinkedHashMap<>(); List submitActionProperties = Arrays.asList( ReservedProperties.PN_SUBMIT_ACTION_TYPE, @@ -507,6 +531,15 @@ private Map getSubmitProperties() { List submitSpreadsheetProperties = Arrays.asList( ReservedProperties.PN_SUBMIT_SPREADSHEETURL); + + List submitAepProperties = Arrays.asList( + ReservedProperties.PN_SUBMIT_AEP_SCHEMA_REF_ID, + ReservedProperties.PN_SUBMIT_AEP_IMS_ORG_ID, + ReservedProperties.PN_SUBMIT_AEP_SANDBOX_REGION, + ReservedProperties.PN_SUBMIT_AEP_SANDBOX_ID, + ReservedProperties.PN_SUBMIT_AEP_DATASET_ID, + ReservedProperties.PN_SUBMIT_AEP_SANDBOX_NAME); + ValueMap resourceMap = resource.getValueMap(); for (Map.Entry entry : resourceMap.entrySet()) { if (submitActionProperties.contains(entry.getKey())) { @@ -517,7 +550,14 @@ private Map getSubmitProperties() { } else if (submitSpreadsheetProperties.contains(entry.getKey())) { submitProps.computeIfAbsent(SS_SPREADSHEET, k -> new LinkedHashMap()); ((Map) submitProps.get(SS_SPREADSHEET)).put(entry.getKey(), entry.getValue()); + } else if (submitAepProperties.contains(entry.getKey())) { + submitProps.computeIfAbsent(SS_AEP, k -> new LinkedHashMap()); + ((Map) submitProps.get(SS_AEP)).put(entry.getKey(), entry.getValue()); } + // SSV properties (enableServerSideValidation, ssvCloudServicePath) are intentionally + // excluded from the form JSON. The submit servlet reads them directly from JCR. + // ssvCloudServicePath in particular must not be sent to clients as it exposes + // internal JCR paths. } } return submitProps; diff --git a/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/models/v3/form/FileInputImplV3.java b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/models/v3/form/FileInputImplV3.java index 9cd3d1567f..20ed3556c7 100644 --- a/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/models/v3/form/FileInputImplV3.java +++ b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/models/v3/form/FileInputImplV3.java @@ -18,6 +18,10 @@ package com.adobe.cq.forms.core.components.internal.models.v3.form; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; import java.util.Map; import org.apache.sling.api.SlingHttpServletRequest; @@ -48,6 +52,9 @@ public class FileInputImplV3 extends FileInputImplV2 { @Default(values = FileInput.DEFAULT_DRAGDROP_TEXT) protected String dragDropTextV3; + @ValueMapValue(injectionStrategy = InjectionStrategy.OPTIONAL, name = ReservedProperties.FD_FILE_ACCEPT_EXTENSIONS) + protected String[] acceptExtensions; + @Override public String getDragDropText() { return dragDropTextV3; @@ -60,4 +67,21 @@ public String getDragDropText() { customProperties.put(ReservedProperties.PN_DRAG_DROP_TEXT_V3, getDragDropText()); return customProperties; } + + @Override + public List getAcceptExtensions() { + if (acceptExtensions == null) { + return Collections.emptyList(); + } + return Arrays.stream(acceptExtensions) + .map(ext -> "." + ext) + .collect(java.util.stream.Collectors.toList()); + } + + @Override + public List getAccept() { + List combined = new ArrayList<>(super.getAccept()); + combined.addAll(getAcceptExtensions()); // adds .pdf, .docx etc. + return combined; + } } diff --git a/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/servlets/FormMetaDataDataSourceServlet.java b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/servlets/FormMetaDataDataSourceServlet.java index 1ecc5b7591..22ab9d1441 100644 --- a/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/servlets/FormMetaDataDataSourceServlet.java +++ b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/servlets/FormMetaDataDataSourceServlet.java @@ -53,6 +53,7 @@ import com.adobe.granite.ui.components.ds.DataSource; import com.adobe.granite.ui.components.ds.SimpleDataSource; import com.adobe.granite.ui.components.ds.ValueMapResource; +import com.day.cq.i18n.I18n; import com.day.cq.wcm.api.policies.ContentPolicy; import com.day.cq.wcm.foundation.forms.FormsManager; @@ -85,7 +86,8 @@ public enum FormMetaDataType { SUBMIT_ACTION("submitAction"), PREFILL_ACTION("prefillServiceProvider"), LANG("lang"), - FORMATTERS("formatters"); + FORMATTERS("formatters"), + SSV_CLOUD_CONFIG("ssvCloudServiceConfiguration"); private String value; @@ -173,6 +175,9 @@ private Boolean isLangPolicy(FormMetaDataType type, Map.Entry en private List getDataSourceResources(SlingHttpServletRequest request, ResourceResolver resourceResolver, FormMetaDataType type, String dataModel, Config config) { List resources = new ArrayList<>(); + if (type == FormMetaDataType.SSV_CLOUD_CONFIG) { + return getCloudConfigsByGroup(request, resourceResolver); + } FormMetaData formMetaData = resourceResolver.adaptTo(FormMetaData.class); if (formMetaData != null) { Iterator metaDataList = null; @@ -199,15 +204,7 @@ private List getDataSourceResources(SlingHttpServletRequest request, R } } } - // Only add "Custom" option for non-text-input components - String fieldType = ""; - if (config != null) { - String retrievedFieldType = getParameter(config, FIELD_TYPE, request, ""); - fieldType = retrievedFieldType != null ? retrievedFieldType : ""; - } - if (fieldType.isEmpty() || !"text-input".equals(fieldType)) { - resources.add(getResourceForDropdownDisplay(resourceResolver, "Custom", "custom")); - } + resources.add(getResourceForDropdownDisplay(resourceResolver, "Custom", "custom")); break; case SUBMIT_ACTION: // filter the submit actions by uniqueness and data model @@ -225,8 +222,69 @@ private List getDataSourceResources(SlingHttpServletRequest request, R break; case PREFILL_ACTION: metaDataList = formMetaData.getPrefillActions(); - resources = this.getResourceListFromComponentDescription(metaDataList, resourceResolver); + // Add an explicit empty option so authors can clear an already selected prefill service. + I18n i18n = new I18n(request.getResourceBundle(request.getLocale())); + resources.add(getResourceForDropdownDisplay(resourceResolver, i18n.get("None"), "")); + resources.addAll(this.getResourceListFromComponentDescription(metaDataList, resourceResolver)); + break; + default: + break; + } + } + return resources; + } + + private List getCloudConfigsByGroup(SlingHttpServletRequest request, ResourceResolver resourceResolver) { + List resources = new ArrayList<>(); + resources.add(getResourceForDropdownDisplay(resourceResolver, "None", "")); + String contentPath = (String) request.getAttribute(Value.CONTENTPATH_ATTRIBUTE); + Resource formResource = null; + if (StringUtils.isNotBlank(contentPath)) { + formResource = resourceResolver.getResource(contentPath); + } + if (formResource == null) { + formResource = request.getRequestPathInfo().getSuffixResource(); + } + String resolvedConfPath = null; + if (formResource != null) { + Resource r = formResource; + while (r != null) { + String confPath = r.getValueMap().get("cq:conf", String.class); + if (StringUtils.isNotBlank(confPath)) { + resolvedConfPath = confPath; break; + } + // cq:conf lives on jcr:content for cq:Page and folder nodes + Resource pageContent = r.getChild(JcrConstants.JCR_CONTENT); + if (pageContent != null) { + confPath = pageContent.getValueMap().get("cq:conf", String.class); + if (StringUtils.isNotBlank(confPath)) { + resolvedConfPath = confPath; + break; + } + } + r = r.getParent(); + } + } + // Fall back to /conf/global when no cq:conf is found in the hierarchy — + // this mirrors AEM's own context-aware configuration resolution behaviour. + if (resolvedConfPath == null) { + resolvedConfPath = "/conf/global"; + } + // Query for any jcr:content node under cloudconfigs/ that carries serviceEndPoint, + // regardless of nesting depth (handles both flat and service-type-subfolder layouts). + String cloudConfigsBase = resolvedConfPath + "/settings/cloudconfigs"; + String query = "SELECT * FROM [nt:base] AS s WHERE ISDESCENDANTNODE(s, '" + + cloudConfigsBase + "') AND s.[serviceEndPoint] IS NOT NULL"; + Iterator results = resourceResolver.findResources(query, "JCR-SQL2"); + while (results.hasNext()) { + Resource configContent = results.next(); + // configContent is the node with serviceEndPoint — its parent is the config root node + Resource configNode = configContent.getParent(); + if (configNode != null) { + String title = configContent.getValueMap().get("jcr:title", + configNode.getValueMap().get("jcr:title", configNode.getName())); + resources.add(getResourceForDropdownDisplay(resourceResolver, title, configNode.getPath())); } } return resources; diff --git a/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/models/form/FileConstraint.java b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/models/form/FileConstraint.java index 9b53a9632c..a266d076ab 100644 --- a/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/models/form/FileConstraint.java +++ b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/models/form/FileConstraint.java @@ -57,6 +57,17 @@ default List getAccept() { return DEFAULT_ACCEPT; } + /** + * Returns the list of file extensions which field can accept. The constraint is + * applicable for file attachment field + * + * @return the list of extensions supported by the field + * @since com.adobe.cq.forms.core.components.models.form 0.0.1 + */ + default List getAcceptExtensions() { + return Collections.emptyList(); + } + /** * The constraint is * applicable for file attachment field. If the type is {@code String}, the format will always be {@code data-url}. diff --git a/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/models/form/FileInput.java b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/models/form/FileInput.java index 59ead0d571..9843eccacf 100644 --- a/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/models/form/FileInput.java +++ b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/models/form/FileInput.java @@ -15,6 +15,9 @@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ package com.adobe.cq.forms.core.components.models.form; +import java.util.Collections; +import java.util.List; + import org.osgi.annotation.versioning.ConsumerType; import com.fasterxml.jackson.annotation.JsonIgnore; @@ -40,6 +43,17 @@ default Boolean isMultiple() { return false; } + /** + * Returns the list of file extensions which field can accept. The constraint is + * applicable for file attachment field + * + * @return the list of extensions supported by the field + */ + @JsonIgnore + default List getAcceptExtensions() { + return Collections.emptyList(); + } + /** * Returns the text to be displayed on the file input button * diff --git a/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/models/form/FormStructureParser.java b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/models/form/FormStructureParser.java index bbf4631d44..ff221e2f5b 100644 --- a/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/models/form/FormStructureParser.java +++ b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/models/form/FormStructureParser.java @@ -38,9 +38,10 @@ public interface FormStructureParser { String getClientLibRefFromFormContainer(); /** - * @returns reference to theme client lib stored in the form container + * + * @returns reference to the client lib of the theme from form container */ - String getThemeClientLibRefFromFormContainer(); + String getThemeEditorThemeRef(); /** * Checks if this resource contains a form container diff --git a/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/models/form/ImageChoice.java b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/models/form/ImageChoice.java new file mode 100644 index 0000000000..de6b4b2b35 --- /dev/null +++ b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/models/form/ImageChoice.java @@ -0,0 +1,111 @@ +/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ~ Copyright 2026 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 javax.annotation.Nullable; + +import org.apache.commons.lang3.StringUtils; +import org.osgi.annotation.versioning.ConsumerType; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonValue; + +/** + * Defines the form {@code ImageChoice} Sling Model used for the + * {@code /apps/core/fd/components/form/imagechoice/v1/imagechoice} component. + * + *

+ * The ImageChoice component allows users to select one or more options + * represented by images. It supports both single-select (radio button behavior) + * and multi-select (checkbox group behavior) modes. + *

+ * + * @since com.adobe.cq.forms.core.components.models.form 5.13.0 + */ +@ConsumerType +public interface ImageChoice extends Field, OptionsConstraint, ContainerConstraint { + + /** + * Defines the selection type for image choice. + * Possible values: {@code single}, {@code multi} + * + * @since com.adobe.cq.forms.core.components.models.form 5.13.0 + */ + enum SelectionType { + SINGLE("single"), + MULTI("multi"); + + private final String value; + + SelectionType(String value) { + this.value = value; + } + + public static SelectionType fromString(String value) { + for (SelectionType type : SelectionType.values()) { + if (StringUtils.equals(value, type.value)) { + return type; + } + } + return SINGLE; + } + + public String getValue() { + return value; + } + + @Override + @JsonValue + public String toString() { + return value; + } + } + + /** + * Returns the orientation of the image choice component. + * + * @return {@link CheckBox.Orientation} + * @since com.adobe.cq.forms.core.components.models.form 5.13.0 + */ + @JsonIgnore + default CheckBox.Orientation getOrientation() { + return CheckBox.Orientation.HORIZONTAL; + } + + /** + * Returns the selection type of the image choice component. + * {@code single} behaves like radio buttons (one selection), + * {@code multi} behaves like checkbox group (multiple selections). + * + * @return {@link SelectionType} + * @since com.adobe.cq.forms.core.components.models.form 5.13.0 + */ + @JsonIgnore + default SelectionType getSelectionType() { + return SelectionType.SINGLE; + } + + /** + * Returns the array of image source paths corresponding to each enum option. + * The array is parallel to the enums array - imageSrc[i] is the image for enum[i]. + * + * @return array of image source paths, or null if not set + * @since com.adobe.cq.forms.core.components.models.form 5.13.0 + */ + @JsonIgnore + @Nullable + String[] getImageSrc(); +} diff --git a/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/util/AbstractCheckboxImpl.java b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/util/AbstractCheckboxImpl.java index 6ea1c9dfac..e159eb3b4f 100644 --- a/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/util/AbstractCheckboxImpl.java +++ b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/util/AbstractCheckboxImpl.java @@ -46,7 +46,11 @@ public void initBaseCheckboxModel() { checkedValue = String.valueOf(getEnums()[0]); uncheckedValue = getEnums().length > 1 ? String.valueOf(getEnums()[1]) : null; } - enums = (checkedValue != null) ? (Boolean.TRUE.equals(enableUncheckedValue)) ? new String[] { checkedValue, uncheckedValue } + // Guard against null uncheckedValue: when enableUncheckedValue is true the JCR property + // may be absent (e.g. because the push mechanism skipped an empty-string value), which + // would put null into the enum array and crash the runtime on .toString() at init time. + String safeUncheckedValue = uncheckedValue != null ? uncheckedValue : ""; + enums = (checkedValue != null) ? (Boolean.TRUE.equals(enableUncheckedValue)) ? new String[] { checkedValue, safeUncheckedValue } : new String[] { checkedValue } : null; } diff --git a/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/util/AbstractContainerImpl.java b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/util/AbstractContainerImpl.java index 537092ce4f..c500b631ee 100644 --- a/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/util/AbstractContainerImpl.java +++ b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/util/AbstractContainerImpl.java @@ -39,12 +39,14 @@ import com.adobe.cq.export.json.ComponentExporter; import com.adobe.cq.export.json.SlingModelFilter; +import com.adobe.cq.forms.core.components.internal.form.FeatureToggleConstants; import com.adobe.cq.forms.core.components.models.form.Container; import com.adobe.cq.forms.core.components.models.form.ContainerConstraint; import com.adobe.cq.forms.core.components.models.form.FormComponent; import com.day.cq.wcm.foundation.model.export.AllowedComponentsExporter; import com.day.cq.wcm.foundation.model.responsivegrid.ResponsiveGrid; import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; /** * Abstract class which can be used as base class for {@link Container} implementations. @@ -121,7 +123,11 @@ public List getItems() { @NotNull @Override + @JsonInclude(JsonInclude.Include.NON_EMPTY) public String[] getExportedItemsOrder() { + if (ComponentUtils.isToggleEnabled(FeatureToggleConstants.FT_SKIP_ITEMS_MAP)) { + return ArrayUtils.EMPTY_STRING_ARRAY; + } if (exportedItemsOrder == null) { Map models = getExportedItems(); if (!models.isEmpty()) { @@ -168,13 +174,26 @@ protected Map getChildrenModels(@Nullable SlingHttpServletRequest } @Override + @JsonInclude(JsonInclude.Include.NON_EMPTY) public @NotNull Map getExportedItems() { + if (ComponentUtils.isToggleEnabled(FeatureToggleConstants.FT_SKIP_ITEMS_MAP)) { + return Collections.emptyMap(); + } if (itemModels == null) { itemModels = getChildrenModels(request, ComponentExporter.class); } return new LinkedHashMap<>(itemModels); } + @JsonProperty("items") + @JsonInclude(JsonInclude.Include.NON_EMPTY) + public List getExportedItemsArray() { + if (!ComponentUtils.isToggleEnabled(FeatureToggleConstants.FT_SKIP_ITEMS_MAP)) { + return Collections.emptyList(); + } + return getItems(); + } + protected List getFilteredChildrenResources() { return getFilteredChildrenResources(resource); } diff --git a/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/util/AbstractFormComponentImpl.java b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/util/AbstractFormComponentImpl.java index fa835dba69..135cace14c 100644 --- a/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/util/AbstractFormComponentImpl.java +++ b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/util/AbstractFormComponentImpl.java @@ -44,6 +44,7 @@ import com.adobe.aemds.guide.utils.GuideUtils; import com.adobe.cq.forms.core.components.datalayer.FormComponentData; import com.adobe.cq.forms.core.components.internal.datalayer.ComponentDataImpl; +import com.adobe.cq.forms.core.components.internal.form.FeatureToggleConstants; import com.adobe.cq.forms.core.components.internal.form.ReservedProperties; import com.adobe.cq.forms.core.components.models.form.BaseConstraint; import com.adobe.cq.forms.core.components.models.form.FieldType; @@ -62,6 +63,9 @@ import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import static com.adobe.cq.forms.core.components.util.ComponentUtils.isAuthorMode; +import static com.adobe.cq.forms.core.components.util.ComponentUtils.isToggleEnabled; + public class AbstractFormComponentImpl extends AbstractComponentImpl implements FormComponent { @ValueMapValue(injectionStrategy = InjectionStrategy.OPTIONAL, name = ReservedProperties.PN_DATAREF) @Nullable @@ -97,8 +101,8 @@ public class AbstractFormComponentImpl extends AbstractComponentImpl implements private Resource resource; @ValueMapValue(injectionStrategy = InjectionStrategy.OPTIONAL, name = ReservedProperties.PN_DOR_EXCLUSION) - @Default(booleanValues = false) - protected boolean dorExclusion; + @Nullable + protected Boolean dorExclusion; @ValueMapValue(injectionStrategy = InjectionStrategy.OPTIONAL, name = ReservedProperties.PN_DOR_COLSPAN) @Nullable @@ -305,19 +309,21 @@ protected boolean getEditMode() { public @NotNull Map getProperties() { Map properties = new LinkedHashMap<>(); Map customProperties = getCustomProperties(); - if (customProperties.size() > 0) { + if (!customProperties.isEmpty()) { customProperties.forEach(properties::putIfAbsent); } - if (getCustomLayoutProperties().size() != 0) { + if (!getCustomLayoutProperties().isEmpty()) { properties.put(CUSTOM_PROPERTY_WRAPPER, getCustomLayoutProperties()); } - if (getDorProperties().size() > 0) { + if (!getDorProperties().isEmpty()) { properties.put(CUSTOM_DOR_PROPERTY_WRAPPER, getDorProperties()); } properties.put(CUSTOM_JCR_PATH_PROPERTY_WRAPPER, getPath()); - Map rulesProperties = getRulesProperties(); - if (rulesProperties.size() > 0) { - properties.put(CUSTOM_RULE_PROPERTY_WRAPPER, rulesProperties); + if (isAuthorMode(request)) { + Map rulesProperties = getRulesProperties(); + if (!rulesProperties.isEmpty()) { + properties.put(CUSTOM_RULE_PROPERTY_WRAPPER, rulesProperties); + } } return properties; } @@ -325,32 +331,65 @@ protected boolean getEditMode() { @Override @NotNull public Map getRules() { + return getRulesForResource(resource); + } + + /** + * Returns rules (visible, required, etc.) for the given resource. Used when exporting + * fragment components so that rules configured on the referenced fragment are + * included in the stitched output as root-level "rules" (parallel to "events"). + * + * @param resource the resource that may have an fd:rules child + * @return map of rule name to expression, never null + */ + protected final Map getRulesForResource(Resource resource) { String[] VALID_RULES = new String[] { "description", "enabled", "enum", "enumNames", - "exclusiveMaximum", "exclusiveMinimum", "label", "maximum", "minimum", + "exclusiveMaximum", "exclusiveMinimum", "label", "maximum", "minimum", "properties", "readOnly", "required", "value", "visible" }; - Predicate> isRuleNameValid = obj -> Arrays.stream(VALID_RULES).anyMatch(validKey -> validKey.equals(obj - .getKey())); + // A rule target may be one of the fixed editable properties (which includes the whole + // `properties` bag), or a granular custom-variable target keyed `properties.` (e.g. + // properties.employer or properties.employer.category) that the runtime routes to the + // PropertiesManager per key. Preserve both the bag and the per-key targets. + Predicate> isRuleNameValid = obj -> obj.getKey().startsWith("properties.") + || Arrays.stream(VALID_RULES).anyMatch(validKey -> validKey.equals(obj.getKey())); Predicate> isRuleValid = isEntryNonEmpty.and(isRuleNameValid); + if (resource == null) { + return Collections.emptyMap(); + } Resource ruleNode = resource.getChild("fd:rules"); if (ruleNode != null) { ValueMap ruleNodeProps = ruleNode.getValueMap(); - Map rules = ruleNodeProps.entrySet() + return ruleNodeProps.entrySet() .stream() .filter(isRuleValid) .map(entry -> new AbstractMap.SimpleEntry<>(entry.getKey(), (String) entry.getValue())) .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); - return rules; } return Collections.emptyMap(); } @JsonIgnore private Map getRulesProperties() { - Resource ruleNode = resource.getChild(CUSTOM_RULE_PROPERTY_WRAPPER); + return getRulesPropertiesForResource(resource); + } + + /** + * Returns rules properties (fd:rules) for the given resource. Used when exporting + * fragment components so that rules configured on the referenced fragment are + * included in the stitched output. + * + * @param resource the resource that may have an fd:rules child + * @return map of rules properties, never null + */ + protected final Map getRulesPropertiesForResource(Resource resource) { Map customRulesProperties = new LinkedHashMap<>(); + if (resource == null) { + return customRulesProperties; + } + Resource ruleNode = resource.getChild(CUSTOM_RULE_PROPERTY_WRAPPER); if (ruleNode == null) { logger.debug("No rules node found for resource: {}", resource.getPath()); return customRulesProperties; @@ -435,17 +474,34 @@ private Stream> sanitizeEvent(Map.Entry getEvents() { - Resource eventNode = resource.getChild("fd:events"); + Map userEvents = new LinkedHashMap<>(); + if (!isToggleEnabled(FeatureToggleConstants.FT_SKIP_DEFAULT_SET_PROPERTY_EVENT)) { + userEvents.put("custom:setProperty", new String[] { "$event.payload" }); + } + userEvents.putAll(getEventsForResource(resource)); + return userEvents; + } + + /** + * Returns events (fd:events) for the given resource. Used when exporting + * fragment components so that events configured on the referenced fragment are + * included in the stitched output. + * + * @param resource the resource that may have an fd:events child + * @return map of event name to handler expressions, never null + */ + protected final Map getEventsForResource(Resource resource) { Set> eventSet = new HashSet<>(); - eventSet.add(new AbstractMap.SimpleEntry<>("custom_setProperty", "$event.payload")); - if (eventNode != null) { - ValueMap eventNodeProps = eventNode.getValueMap(); - eventSet.addAll(eventNodeProps.entrySet()); + if (resource != null) { + Resource eventNode = resource.getChild("fd:events"); + if (eventNode != null) { + ValueMap eventNodeProps = eventNode.getValueMap(); + eventSet.addAll(eventNodeProps.entrySet()); + } } - Map userEvents = eventSet.stream() + return eventSet.stream() .flatMap(this::sanitizeEvent) .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); - return userEvents; } /** @@ -574,7 +630,9 @@ private Map getCustomProperties() { @JsonIgnore public Map getDorProperties() { Map customDorProperties = new LinkedHashMap<>(); - customDorProperties.put("dorExclusion", dorExclusion); + if (dorExclusion != null) { + customDorProperties.put("dorExclusion", dorExclusion); + } if (dorColspan != null) { customDorProperties.put("dorColspan", dorColspan); } diff --git a/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/util/ComponentUtils.java b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/util/ComponentUtils.java index a048659ba8..3571b0a73f 100644 --- a/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/util/ComponentUtils.java +++ b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/util/ComponentUtils.java @@ -46,6 +46,7 @@ import com.adobe.cq.forms.core.components.internal.form.FormConstants; import com.adobe.cq.forms.core.components.models.form.BaseConstraint; import com.day.cq.i18n.I18n; +import com.day.cq.wcm.api.WCMMode; import com.day.cq.wcm.api.policies.ContentPolicy; import com.day.cq.wcm.api.policies.ContentPolicyManager; import com.fasterxml.jackson.databind.JsonNode; @@ -63,13 +64,55 @@ public class ComponentUtils { private static final String[] EDGE_DELIVERY_RESOURCE_TYPES = new String[] { "core/franklin/components/page/v1/page" }; private static final Logger logger = LoggerFactory.getLogger(ComponentUtils.class); - /** - * Private constructor to prevent instantiation of utility class. - */ private ComponentUtils() { // NOOP } + /** + * Checks whether a feature toggle is enabled via a system property. The property is set/unset + * by the Granite Toggle API when the toggle is wired via OSGi factory + * {@code com.adobe.granite.toggle.monitor.systemproperty} (see + * {@link com.adobe.cq.forms.core.components.internal.form.FeatureToggleConstants} for setup). + *

+ * Convention: the system property name is the toggle ID; value {@code "true"} (via + * {@link Boolean#parseBoolean(String)}) means enabled. + * + * @param toggleId the toggle identifier (also used as the system property name) + * @return true if the system property is set to "true", false otherwise + */ + public static boolean isToggleEnabledBySystemProperty(@NotNull String toggleId) { + return Boolean.parseBoolean(System.getProperty(toggleId, "false")); + } + + /** + * Checks whether a feature toggle is enabled (system property only). + * + * @param toggleId the toggle identifier (e.g. from {@link com.adobe.cq.forms.core.components.internal.form.FeatureToggleConstants}) + * @return true if the toggle is enabled, false otherwise + */ + public static boolean isToggleEnabled(@NotNull String toggleId) { + return isToggleEnabledBySystemProperty(toggleId); + } + + /** + * Determines whether the current request is in author mode. Returns {@code true} when the + * request is in WCM EDIT or DESIGN mode and has not been explicitly marked as a publish-view + * request (e.g. via {@link FormConstants#REQ_ATTR_PUBLISH_VIEW}). + * + * @param request the current request, may be {@code null} + * @return {@code true} if the request is in author mode, {@code false} otherwise + */ + public static boolean isAuthorMode(@Nullable SlingHttpServletRequest request) { + if (request == null) { + return false; + } + if (Boolean.TRUE.equals(request.getAttribute(FormConstants.REQ_ATTR_PUBLISH_VIEW))) { + return false; + } + WCMMode mode = WCMMode.fromRequest(request); + return mode == WCMMode.EDIT || mode == WCMMode.DESIGN; + } + /** * Returns the base64 encoded path * diff --git a/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/util/DefaultValueSerializer.java b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/util/DefaultValueSerializer.java index 481fc1eda0..6c448b8695 100644 --- a/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/util/DefaultValueSerializer.java +++ b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/util/DefaultValueSerializer.java @@ -31,7 +31,7 @@ */ public class DefaultValueSerializer extends StdSerializer { - private SimpleDateFormat formatter = new SimpleDateFormat(Base.DATE_FORMATTER); + private static final SimpleDateFormat formatter = new SimpleDateFormat(Base.DATE_FORMATTER); public DefaultValueSerializer() { this(null); @@ -41,6 +41,10 @@ public DefaultValueSerializer(Class t) { super(t); } + public static synchronized String formatDate(Date date) { + return formatter.format(date); + } + private void serialize(Object value, JsonGenerator gen) throws IOException { if (value instanceof Boolean) { gen.writeBoolean((Boolean) value); @@ -53,9 +57,9 @@ private void serialize(Object value, JsonGenerator gen) throws IOException { } else if (value instanceof String) { gen.writeString((String) value); } else if (value instanceof Date) { - gen.writeString(formatter.format(value)); + gen.writeString(formatDate((Date) value)); } else if (value instanceof Calendar) { - gen.writeString(formatter.format(((Calendar) value).getTime())); + gen.writeString(formatDate(((Calendar) value).getTime())); } } diff --git a/bundles/af-core/src/test/java/com/adobe/cq/forms/core/Utils.java b/bundles/af-core/src/test/java/com/adobe/cq/forms/core/Utils.java index ad6744e94a..a31e7212d5 100644 --- a/bundles/af-core/src/test/java/com/adobe/cq/forms/core/Utils.java +++ b/bundles/af-core/src/test/java/com/adobe/cq/forms/core/Utils.java @@ -28,6 +28,7 @@ import org.apache.commons.io.FilenameUtils; import org.apache.commons.io.IOUtils; +import org.apache.sling.api.scripting.SlingBindings; import org.apache.sling.testing.mock.sling.servlet.MockSlingHttpServletRequest; import org.jetbrains.annotations.NotNull; @@ -252,4 +253,22 @@ public static Method getPrivateMethod(Class clazz, String privateMethodName) { return null; } } + + /** + * Removes the request's {@link SlingBindings} attribute so the AF locale-resolution code in + * {@code GuideUtils} takes its {@code bindings == null} branches. + *

+ * Call this in tests that set an AF language parameter before adapting/using a model. Newer + * {@code aem-forms-sdk-api} versions resolve the locale via {@code GuideUtils}, and when a + * {@link SlingBindings} is present they look up services through {@code bindings.getSling()}: + * {@code getSanitizedLocale(...)} fetches the runtime-only Granite {@code ToggleRouter} (absent + * from the test classpath → {@link NoClassDefFoundError}), and {@code getLocaleParamFromRequest(...)} + * fetches {@code GuideLocalizationService} via {@code getSling()} without null-guarding it. Both + * are skipped entirely when the {@code SlingBindings} attribute is absent, and the locale is then + * taken from the {@code afAcceptLang} request parameter (which these tests set) — the same result + * expected in production for that parameter, since no {@code ToggleRouter} is registered in tests. + */ + public static void disableLocaleFeatureToggleLookup(MockSlingHttpServletRequest request) { + request.setAttribute(SlingBindings.class.getName(), null); + } } diff --git a/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v1/form/CheckBoxImplTest.java b/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v1/form/CheckBoxImplTest.java index a03844aee0..234ecc34be 100644 --- a/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v1/form/CheckBoxImplTest.java +++ b/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v1/form/CheckBoxImplTest.java @@ -58,6 +58,7 @@ public class CheckBoxImplTest { private static final String PATH_CHECKBOX_ENABLEUNCHECKEDOFF = CONTENT_ROOT + "/checkbox-enableUncheckedValueFalse"; private static final String PATH_CHECKBOX_WITHOUT_FIELDTYPE = CONTENT_ROOT + "/checkbox-without-fieldtype"; + private static final String PATH_CHECKBOX_ENABLEUNCHECKED_MISSING = CONTENT_ROOT + "/checkbox-enableUncheckedValueMissingFromJcr"; private final AemContext context = FormsCoreComponentTestContext.newAemContext(); @BeforeEach @@ -350,6 +351,15 @@ void shouldOnlyHaveOnEnumIfEnableUncheckedValueOff() { assertArrayEquals(new String[] { "on" }, checkbox.getEnums()); } + @Test + void shouldUseEmptyStringWhenUncheckedValueMissingFromJcr() { + // Regression: when enableUncheckedValue=true but the uncheckedValue JCR property is absent + // (e.g. because the content push mechanism skipped an empty-string value), the enum array + // must not contain null — null.toString() in the AFB runtime crashes the form at init time. + CheckBox checkbox = getCheckBoxUnderTest(PATH_CHECKBOX_ENABLEUNCHECKED_MISSING); + assertArrayEquals(new String[] { ".", "" }, checkbox.getEnums()); + } + private CheckBox getCheckBoxUnderTest(String resourcePath) { context.currentResource(resourcePath); MockSlingHttpServletRequest request = context.request(); diff --git a/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v1/form/DateTimeImplTest.java b/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v1/form/DateTimeImplTest.java index 367524dcd3..335d71c21d 100644 --- a/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v1/form/DateTimeImplTest.java +++ b/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v1/form/DateTimeImplTest.java @@ -35,6 +35,7 @@ import io.wcm.testing.mock.aem.junit5.AemContextExtension; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; @ExtendWith(AemContextExtension.class) public class DateTimeImplTest { @@ -77,10 +78,8 @@ void testGetName() { @Test void testDorProperties() { DateTime datetime = Utils.getComponentUnderTest(PATH_DATETIME_CUSTOMIZED, DateTime.class, context); - assertEquals(false, datetime.getDorProperties().get("dorExclusion")); - assertEquals(null, datetime.getDorProperties().get("dorColspan")); - assertEquals(null, datetime.getDorProperties().get("dorBindRef")); - + assertTrue("getDorProperties should return empty map when no dor properties are set in JCR", + datetime.getDorProperties().isEmpty()); } @Test diff --git a/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v1/form/FileConstraintTest.java b/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v1/form/FileConstraintTest.java index 8ff181a6b7..41f02bbc15 100644 --- a/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v1/form/FileConstraintTest.java +++ b/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v1/form/FileConstraintTest.java @@ -15,6 +15,8 @@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ package com.adobe.cq.forms.core.components.internal.models.v1.form; +import java.util.Collections; + import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mockito; @@ -42,4 +44,11 @@ void testGetAccept() { Mockito.when(fileConstraintMock.getAccept()).thenCallRealMethod(); assertThat(FileConstraint.DEFAULT_ACCEPT, is(fileConstraintMock.getAccept())); } + + @Test + void testGetAcceptExtensions() { + FileConstraint fileConstraintMock = Mockito.mock(FileConstraint.class); + Mockito.when(fileConstraintMock.getAcceptExtensions()).thenCallRealMethod(); + assertEquals(Collections.emptyList(), fileConstraintMock.getAcceptExtensions()); + } } diff --git a/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v1/form/FormStructureParserImplTest.java b/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v1/form/FormStructureParserImplTest.java index fca2bf17f6..cb3d935436 100644 --- a/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v1/form/FormStructureParserImplTest.java +++ b/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v1/form/FormStructureParserImplTest.java @@ -20,6 +20,7 @@ import java.util.stream.StreamSupport; import org.apache.commons.lang3.StringUtils; +import org.apache.sling.api.resource.ModifiableValueMap; import org.apache.sling.api.resource.Resource; import org.apache.sling.testing.mock.sling.servlet.MockSlingHttpServletRequest; import org.junit.jupiter.api.Assertions; @@ -28,6 +29,7 @@ import org.junit.jupiter.api.extension.ExtendWith; import com.adobe.cq.export.json.SlingModelFilter; +import com.adobe.cq.forms.core.components.internal.constants.ThemeConstants; import com.adobe.cq.forms.core.components.internal.form.FormConstants; import com.adobe.cq.forms.core.components.models.form.*; import com.adobe.cq.forms.core.context.FormsCoreComponentTestContext; @@ -163,17 +165,6 @@ void testGetClientLibRef() { assertNull(formStructureParser.getClientLibRefFromFormContainer()); } - @Test - void testGetThemeClientLibRef() { - String path = CONTENT_ROOT + "/myTestPage"; - FormStructureParser formStructureParser = getFormStructureParserUnderTest(path); - assertEquals("def", formStructureParser.getThemeClientLibRefFromFormContainer()); - - path = FORM_CONTAINER_PATH + "/container1"; - formStructureParser = getFormStructureParserUnderTest(path); - assertNull(formStructureParser.getThemeClientLibRefFromFormContainer()); - } - @Test public void containsFormContainer_should_return_true() { String path = JCR_CONTENT_PATH; @@ -190,6 +181,74 @@ public void containsFormContainer_should_return_false() { assertFalse(result); } + @Test + void testGetThemeEditorThemeRef() { + Resource formContainer = context.resourceResolver().getResource(FORM_CONTAINER_PATH); + ModifiableValueMap formContainerProps = formContainer.adaptTo(ModifiableValueMap.class); + formContainerProps.put(FormContainer.PN_CLIENT_LIB_REF, "/content/dam/formsanddocuments-themes/test-theme/jcr:content"); + + String path = CONTENT_ROOT + "/myTestPage"; + context.currentResource(path); + MockSlingHttpServletRequest request = context.request(); + request.setAttribute(ThemeConstants.THEME_OVERRIDE, "/content/dam/formsanddocuments-themes/test-theme/jcr:content"); + FormStructureParser formStructureParser = request.adaptTo(FormStructureParser.class); + String themeClientLibRef = formStructureParser.getThemeEditorThemeRef(); + assertEquals("fdtheme.test-theme", themeClientLibRef); + } + + @Test + void testGetThemeEditorThemeRefWithRequestAttribute() { + String path = CONTENT_ROOT + "/myTestPage"; + context.currentResource(path); + MockSlingHttpServletRequest request = context.request(); + request.setAttribute(ThemeConstants.THEME_OVERRIDE, "/content/dam/formsanddocuments-themes/test-theme/jcr:content"); + FormStructureParser formStructureParser = request.adaptTo(FormStructureParser.class); + String themeClientLibRef = formStructureParser.getThemeEditorThemeRef(); + assertEquals("fdtheme.test-theme", themeClientLibRef); + } + + @Test + void testGetThemeEditorThemeRefWithRequestParameter() { + String path = CONTENT_ROOT + "/myTestPage"; + context.currentResource(path); + MockSlingHttpServletRequest request = context.request(); + request.setParameterMap(Collections.singletonMap(ThemeConstants.THEME_OVERRIDE, + "/content/dam/formsanddocuments-themes/test-theme/jcr:content")); + FormStructureParser formStructureParser = request.adaptTo(FormStructureParser.class); + String themeClientLibRef = formStructureParser.getThemeEditorThemeRef(); + assertEquals("fdtheme.test-theme", themeClientLibRef); + } + + @Test + void testGetThemeEditorThemeRefWithInvalidThemePath() { + String path = CONTENT_ROOT + "/myTestPage"; + context.currentResource(path); + MockSlingHttpServletRequest request = context.request(); + request.setAttribute(ThemeConstants.THEME_OVERRIDE, "/content/dam/formsanddocuments-themes/invalid-theme"); + FormStructureParser formStructureParser = request.adaptTo(FormStructureParser.class); + String themeClientLibRef = formStructureParser.getThemeEditorThemeRef(); + assertNull(themeClientLibRef); + } + + @Test + void testGetThemeEditorThemeRefWithNonExistentTheme() { + String path = CONTENT_ROOT + "/myTestPage"; + context.currentResource(path); + MockSlingHttpServletRequest request = context.request(); + request.setAttribute(ThemeConstants.THEME_OVERRIDE, "/content/dam/formsanddocuments-themes/non-existent"); + FormStructureParser formStructureParser = request.adaptTo(FormStructureParser.class); + String themeClientLibRef = formStructureParser.getThemeEditorThemeRef(); + assertNull(themeClientLibRef); + } + + @Test + void testGetThemeEditorThemeRefWithoutTheme() { + String path = FORM_CONTAINER_PATH + "/container1"; + FormStructureParser formStructureParser = getFormStructureParserUnderTest(path); + String themeClientLibRef = formStructureParser.getThemeEditorThemeRef(); + assertNull(themeClientLibRef); + } + private FormStructureParser getFormStructureParserUnderTest(String resourcePath) { context.currentResource(resourcePath); MockSlingHttpServletRequest request = context.request(); diff --git a/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v1/form/FragmentImplTest.java b/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v1/form/FragmentImplTest.java index 643de1c8b3..f174a77545 100644 --- a/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v1/form/FragmentImplTest.java +++ b/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v1/form/FragmentImplTest.java @@ -30,6 +30,7 @@ import org.apache.sling.api.resource.Resource; import org.apache.sling.api.resource.ResourceResolver; import org.apache.sling.i18n.ResourceBundleProvider; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -39,6 +40,7 @@ import com.adobe.cq.export.json.ComponentExporter; import com.adobe.cq.export.json.SlingModelFilter; import com.adobe.cq.forms.core.Utils; +import com.adobe.cq.forms.core.components.internal.form.FeatureToggleConstants; 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.FormClientLibManager; @@ -54,6 +56,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; @ExtendWith(AemContextExtension.class) public class FragmentImplTest { @@ -64,6 +67,9 @@ public class FragmentImplTest { private static final String PATH_FRAGMENT_DAMPATH = CONTENT_ROOT + "/fragment-dampath"; private static final String PATH_FRAGMENT_WITHOUT_FIELDTYPE = CONTENT_ROOT + "/fragment-without-fieldtype"; private static final String PATH_FRAGMENT_WITH_FRAGMENT_PATH = CONTENT_ROOT + "/fragment-with-fragment-path"; + private static final String PATH_FRAGMENT_WITH_INVALID_PATH = CONTENT_ROOT + "/fragment-with-invalid-path"; + private static final String PATH_FRAGMENT_WITH_PLACEHOLDER_RULES = CONTENT_ROOT + "/fragment-with-placeholder-rules"; + private static final String PATH_FRAGMENT_LAZY = CONTENT_ROOT + "/fragment-lazy"; private final AemContext context = FormsCoreComponentTestContext.newAemContext(); @BeforeEach @@ -94,6 +100,18 @@ public Iterable filterChildResources(Iterable childResources }); } + @AfterEach + void tearDown() { + System.clearProperty(FeatureToggleConstants.FT_FRAGMENT_MERGE_CONTAINER_RULES_EVENTS); + System.clearProperty(FeatureToggleConstants.FT_SKIP_ITEMS_MAP); + } + + /** Returns a fragment with the merge toggle enabled (system property set for test). */ + private Fragment getFragmentWithMergeEnabled(String path) { + System.setProperty(FeatureToggleConstants.FT_FRAGMENT_MERGE_CONTAINER_RULES_EVENTS, "true"); + return Utils.getComponentUnderTest(path, Fragment.class, context); + } + @Test void testExportedType() throws Exception { Fragment fragment = Utils.getComponentUnderTest(PATH_FRAGMENT, Fragment.class, context); @@ -131,8 +149,96 @@ void testFragmentContainerResource() { } @Test - void testJSONExport() throws Exception { + void testFragmentContainerRulesNotInProperties() { Fragment fragment = Utils.getComponentUnderTest(PATH_FRAGMENT, Fragment.class, context); + Map properties = fragment.getProperties(); + assertNotNull(properties); + assertTrue("fd:rules is whitelisted and should not appear in properties (rules are at root level)", + !properties.containsKey("fd:rules")); + } + + @Test + void testFragmentContainerEventsIncludedInGetEvents() { + Fragment fragment = getFragmentWithMergeEnabled(PATH_FRAGMENT); + Map events = fragment.getEvents(); + assertNotNull(events); + assertTrue("Stitched fragment should include events from referenced fragment container", + events.containsKey("change")); + Assertions.assertArrayEquals(new String[] { "fragmentChangeHandler" }, events.get("change")); + assertTrue("Stitched fragment should include default custom:setProperty", events.containsKey("custom:setProperty")); + } + + @Test + void testFragmentContainerRulesParallelToEvents() { + Fragment fragment = getFragmentWithMergeEnabled(PATH_FRAGMENT); + Map rules = fragment.getRules(); + Map events = fragment.getEvents(); + assertNotNull(rules); + assertNotNull(events); + assertTrue("Stitched fragment should include root-level rules from referenced fragment container (parallel to events)", + rules.containsKey("visible")); + assertEquals("fragmentVisibleRule", rules.get("visible")); + assertTrue("Rules and events should both be present at same level", events.containsKey("change")); + } + + @Test + void testPlaceholderAndFragmentContainerRulesEventsMerged() { + Fragment fragment = getFragmentWithMergeEnabled(PATH_FRAGMENT_WITH_PLACEHOLDER_RULES); + Map rules = fragment.getRules(); + Map events = fragment.getEvents(); + Map properties = fragment.getProperties(); + + assertNotNull(rules); + assertNotNull(events); + assertNotNull(properties); + + assertTrue("Merged rules should include placeholder rule (required)", rules.containsKey("required")); + assertEquals("placeholderRequired", rules.get("required")); + assertTrue("Merged rules should include visible; panel has priority over fragment container", rules.containsKey("visible")); + assertEquals("panelVisibleRule", rules.get("visible")); + + assertTrue("Merged events should include placeholder event (click)", events.containsKey("click")); + Assertions.assertArrayEquals(new String[] { "placeholderClick" }, events.get("click")); + assertTrue("Merged events should include fragment container event (change)", events.containsKey("change")); + Assertions.assertArrayEquals(new String[] { "fragmentChangeHandler" }, events.get("change")); + assertTrue("Merged events should include default custom:setProperty", events.containsKey("custom:setProperty")); + assertTrue("Merged events should include initialize with panel handler first, then fragment appended", events.containsKey( + "initialize")); + Assertions.assertArrayEquals(new String[] { "panelInit", "fragInit" }, events.get("initialize")); + + assertTrue("fd:rules is whitelisted and should not appear in properties", !properties.containsKey("fd:rules")); + } + + @Test + void testNoMergeWhenToggleDisabled() throws Exception { + String key = FeatureToggleConstants.FT_FRAGMENT_MERGE_CONTAINER_RULES_EVENTS; + String saved = System.getProperty(key); + try { + System.setProperty(key, "false"); + Fragment fragment = Utils.getComponentUnderTest(PATH_FRAGMENT_WITH_PLACEHOLDER_RULES, Fragment.class, context); + Map rules = fragment.getRules(); + Map events = fragment.getEvents(); + + assertNotNull(rules); + assertNotNull(events); + assertEquals("When toggle is off, only panel rules are used", "panelVisibleRule", rules.get("visible")); + assertTrue("When toggle is off, only panel events are used", events.containsKey("initialize")); + Assertions.assertArrayEquals(new String[] { "panelInit" }, events.get("initialize"), + "When toggle is off, fragment container events are not merged; only panel initialize"); + Assertions.assertFalse(events.containsKey("change"), + "When toggle is off, fragment container events like change are not included"); + } finally { + if (saved != null) { + System.setProperty(key, saved); + } else { + System.clearProperty(key); + } + } + } + + @Test + void testJSONExport() throws Exception { + Fragment fragment = getFragmentWithMergeEnabled(PATH_FRAGMENT); Utils.testJSONExport(fragment, Utils.getTestExporterJSONPath(BASE, PATH_FRAGMENT)); } @@ -144,7 +250,7 @@ void testJSONExportWithDamPath() throws Exception { @Test void testJSONExportWithFragmentPath() throws Exception { - Fragment fragment = Utils.getComponentUnderTest(PATH_FRAGMENT_WITH_FRAGMENT_PATH, Fragment.class, context); + Fragment fragment = getFragmentWithMergeEnabled(PATH_FRAGMENT_WITH_FRAGMENT_PATH); Utils.testJSONExport(fragment, Utils.getTestExporterJSONPath(BASE, PATH_FRAGMENT_WITH_FRAGMENT_PATH), Views.Author.class); } @@ -273,4 +379,180 @@ void testGetFragmentContainerI18n() throws Exception { I18n result5 = (I18n) getFragmentContainerI18nMethod.invoke(fragmentImpl, ""); Assertions.assertNotNull(result5, "getFragmentContainerI18n should handle empty string localeLang"); } + + @Test + void testNullFragmentContainerHandling() throws Exception { + // This test verifies the fix when fragmentContainer is null + Fragment fragment = Utils.getComponentUnderTest(PATH_FRAGMENT_WITH_INVALID_PATH, Fragment.class, context); + FragmentImpl fragmentImpl = (FragmentImpl) fragment; + + // Verify that fragmentContainer is null due to invalid path + Assertions.assertNull(fragmentImpl.getFragmentContainer(), + "Fragment container should be null for invalid fragment path"); + + // Test that getChildrenModels returns empty map instead of throwing NPE + Map childrenModels = fragmentImpl.getChildrenModels(context.request(), + ComponentExporter.class); + Assertions.assertNotNull(childrenModels, "getChildrenModels should return non-null map even with null fragmentContainer"); + Assertions.assertTrue(childrenModels.isEmpty(), + "getChildrenModels should return empty map when fragmentContainer is null"); + + // Test that getExportedItems doesn't throw NPE + Map exportedItems = fragmentImpl.getExportedItems(); + Assertions.assertNotNull(exportedItems, "getExportedItems should return non-null map even with null fragmentContainer"); + Assertions.assertTrue(exportedItems.isEmpty(), + "getExportedItems should return empty map when fragmentContainer is null"); + + // Test that getExportedItemsOrder doesn't throw NPE + String[] exportedItemsOrder = fragmentImpl.getExportedItemsOrder(); + Assertions.assertNotNull(exportedItemsOrder, "getExportedItemsOrder should return non-null array even with null fragmentContainer"); + Assertions.assertEquals(0, exportedItemsOrder.length, + "getExportedItemsOrder should return empty array when fragmentContainer is null"); + + // Test getFragmentContainerI18n with null fragmentContainer + Method getFragmentContainerI18nMethod = FragmentImpl.class.getDeclaredMethod("getFragmentContainerI18n", String.class); + getFragmentContainerI18nMethod.setAccessible(true); + + // Should not throw NPE and should return non-null I18n object + I18n result = (I18n) getFragmentContainerI18nMethod.invoke(fragmentImpl, "en"); + Assertions.assertNotNull(result, + "getFragmentContainerI18n should return non-null I18n object even when fragmentContainer is null"); + } + + @Test + void testNullFragmentContainerWithNullRequest() throws Exception { + // Test with null request to cover both code paths + Resource resource = context.resourceResolver().getResource(PATH_FRAGMENT_WITH_INVALID_PATH); + Fragment fragment = resource.adaptTo(Fragment.class); + Assertions.assertNotNull(fragment, "Fragment should be created even with invalid path"); + + FragmentImpl fragmentImpl = (FragmentImpl) fragment; + + // Verify that fragmentContainer is null + Assertions.assertNull(fragmentImpl.getFragmentContainer(), + "Fragment container should be null for invalid fragment path"); + + // Test that getChildrenModels with null request returns empty map instead of throwing NPE + Map childrenModels = fragmentImpl.getChildrenModels(null, TextInput.class); + Assertions.assertNotNull(childrenModels, + "getChildrenModels should return non-null map with null request and null fragmentContainer"); + Assertions.assertTrue(childrenModels.isEmpty(), + "getChildrenModels should return empty map when fragmentContainer is null and request is null"); + } + + @Test + void testLazyFragmentDoesNotResolveContainer() { + Fragment fragment = Utils.getComponentUnderTest(PATH_FRAGMENT_LAZY, Fragment.class, context); + Assertions.assertNull(fragment.getFragmentContainer(), + "Fragment container should be null when lazy is true"); + } + + @Test + void testLazyFragmentChildrenEmpty() { + Fragment fragment = Utils.getComponentUnderTest(PATH_FRAGMENT_LAZY, Fragment.class, context); + List children = fragment.getFragmentChildren(); + Assertions.assertNotNull(children); + Assertions.assertTrue(children.isEmpty(), + "Lazy fragment should have no children since container is not resolved"); + } + + @Test + void testLazyFragmentExportedItemsEmpty() { + Fragment fragment = Utils.getComponentUnderTest(PATH_FRAGMENT_LAZY, Fragment.class, context); + Map items = fragment.getExportedItems(); + Assertions.assertNotNull(items); + Assertions.assertTrue(items.isEmpty(), + "Lazy fragment should export no items since container is not resolved"); + } + + @Test + void testLazyFragmentHasLazyProperty() { + FragmentImpl fragment = (FragmentImpl) Utils.getComponentUnderTest(PATH_FRAGMENT_LAZY, Fragment.class, context); + Assertions.assertTrue(fragment.getLazy(), + "getLazy() should return true when lazy is set"); + Map properties = fragment.getProperties(); + Assertions.assertFalse(properties.containsKey("fd:loadLazily"), + "fd:loadLazily should not be in properties map"); + Assertions.assertEquals(true, properties.get("fd:fragment")); + Assertions.assertEquals("fragment", properties.get("fd:viewType")); + Assertions.assertEquals("/content/affragment", fragment.getFragmentPath(), + "fragmentPath should be accessible for client-side lazy resolution"); + } + + @Test + void testLazyFragmentPathStillAccessible() { + Fragment fragment = Utils.getComponentUnderTest(PATH_FRAGMENT_LAZY, Fragment.class, context); + Assertions.assertEquals("/content/affragment", fragment.getFragmentPath(), + "fragmentPath should still be available for client-side lazy resolution"); + } + + @Test + void testLazyFragmentJSONExport() throws Exception { + Fragment fragment = Utils.getComponentUnderTest(PATH_FRAGMENT_LAZY, Fragment.class, context); + Utils.testJSONExport(fragment, BASE + "/exporter-fragment-lazy.json"); + } + + @Test + void testNonLazyFragmentDoesNotHaveLazyProperty() { + FragmentImpl fragment = (FragmentImpl) Utils.getComponentUnderTest(PATH_FRAGMENT, Fragment.class, context); + Assertions.assertFalse(fragment.getLazy(), + "Non-lazy fragment should return false for getLazy()"); + Assertions.assertNotNull(fragment.getFragmentContainer(), + "Non-lazy fragment should have resolved container"); + Assertions.assertEquals("/content/affragment", fragment.getFragmentPath(), + "fragmentPath should always be accessible via getter"); + } + + @Test + void testFragmentExportedItemsMapByDefault() { + Fragment fragment = Utils.getComponentUnderTest(PATH_FRAGMENT, Fragment.class, context); + Map exportedItems = fragment.getExportedItems(); + Assertions.assertNotNull(exportedItems); + Assertions.assertFalse(exportedItems.isEmpty(), + "getExportedItems should return non-empty map when toggle is OFF"); + String[] order = fragment.getExportedItemsOrder(); + Assertions.assertTrue(order.length > 0, + "getExportedItemsOrder should return non-empty array when toggle is OFF"); + } + + @Test + void testFragmentExportedItemsEmptyWhenToggleEnabled() { + System.setProperty(FeatureToggleConstants.FT_SKIP_ITEMS_MAP, "true"); + Fragment fragment = Utils.getComponentUnderTest(PATH_FRAGMENT, Fragment.class, context); + Map exportedItems = fragment.getExportedItems(); + Assertions.assertNotNull(exportedItems); + Assertions.assertTrue(exportedItems.isEmpty(), + "getExportedItems should return empty map when toggle is ON"); + String[] order = fragment.getExportedItemsOrder(); + Assertions.assertEquals(0, order.length, + "getExportedItemsOrder should return empty array when toggle is ON"); + } + + @Test + void testFragmentItemsArrayWhenToggleEnabled() { + System.setProperty(FeatureToggleConstants.FT_SKIP_ITEMS_MAP, "true"); + Fragment fragment = Utils.getComponentUnderTest(PATH_FRAGMENT, Fragment.class, context); + FragmentImpl fragmentImpl = (FragmentImpl) fragment; + java.util.List itemsArray = fragmentImpl.getExportedItemsArray(); + Assertions.assertNotNull(itemsArray); + Assertions.assertFalse(itemsArray.isEmpty(), + "getExportedItemsArray should return non-empty list when toggle is ON"); + } + + @Test + void testFragmentItemsArrayEmptyWhenToggleDisabled() { + Fragment fragment = Utils.getComponentUnderTest(PATH_FRAGMENT, Fragment.class, context); + FragmentImpl fragmentImpl = (FragmentImpl) fragment; + java.util.List itemsArray = fragmentImpl.getExportedItemsArray(); + Assertions.assertNotNull(itemsArray); + Assertions.assertTrue(itemsArray.isEmpty(), + "getExportedItemsArray should return empty list when toggle is OFF"); + } + + @Test + void testJSONExportWithItemsArrayToggle() throws Exception { + System.setProperty(FeatureToggleConstants.FT_SKIP_ITEMS_MAP, "true"); + Fragment fragment = Utils.getComponentUnderTest(PATH_FRAGMENT, Fragment.class, context); + Utils.testJSONExport(fragment, BASE + "/exporter-fragment-items-array.json"); + } } diff --git a/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v1/form/ImageChoiceImplTest.java b/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v1/form/ImageChoiceImplTest.java new file mode 100644 index 0000000000..02e0ad756d --- /dev/null +++ b/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v1/form/ImageChoiceImplTest.java @@ -0,0 +1,297 @@ +/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ~ Copyright 2026 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.Map; + +import org.apache.commons.lang3.reflect.FieldUtils; +import org.apache.sling.testing.mock.sling.servlet.MockSlingHttpServletRequest; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mockito; + +import com.adobe.cq.forms.core.Utils; +import com.adobe.cq.forms.core.components.datalayer.FormComponentData; +import com.adobe.cq.forms.core.components.internal.form.FormConstants; +import com.adobe.cq.forms.core.components.models.form.*; +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.*; + +@ExtendWith(AemContextExtension.class) +public class ImageChoiceImplTest { + private static final String BASE = "/form/imagechoice"; + private static final String CONTENT_ROOT = "/content"; + private static final String PATH_IMAGECHOICE = CONTENT_ROOT + "/imagechoice"; + private static final String PATH_IMAGECHOICE_CUSTOMIZED = CONTENT_ROOT + "/imagechoice-customized"; + private static final String PATH_IMAGECHOICE_MULTI = CONTENT_ROOT + "/imagechoice-multi"; + private static final String PATH_IMAGECHOICE_NO_IMAGES = CONTENT_ROOT + "/imagechoice-no-images"; + private static final String PATH_IMAGECHOICE_DATALAYER = CONTENT_ROOT + "/imagechoice-datalayer"; + private static final String PATH_IMAGECHOICE_OPTION_SCREEN_READER_LABEL = CONTENT_ROOT + "/imagechoice-option-screenreader-label"; + + private final AemContext context = FormsCoreComponentTestContext.newAemContext(); + + @BeforeEach + void setUp() { + context.load().json(BASE + FormsCoreComponentTestContext.TEST_CONTENT_JSON, CONTENT_ROOT); + } + + @Test + void testExportedType() { + ImageChoice imageChoice = getImageChoiceUnderTest(PATH_IMAGECHOICE); + assertEquals(FormConstants.RT_FD_FORM_IMAGE_CHOICE_V1, imageChoice.getExportedType()); + } + + @Test + void testFieldType_singleSelect() { + ImageChoice imageChoice = getImageChoiceUnderTest(PATH_IMAGECHOICE); + assertEquals(FieldType.RADIO_GROUP.getValue(), imageChoice.getFieldType()); + } + + @Test + void testFieldType_multiSelect() { + ImageChoice imageChoice = getImageChoiceUnderTest(PATH_IMAGECHOICE_MULTI); + assertEquals(FieldType.CHECKBOX_GROUP.getValue(), imageChoice.getFieldType()); + } + + @Test + void testGetLabel() { + ImageChoice imageChoice = getImageChoiceUnderTest(PATH_IMAGECHOICE_CUSTOMIZED); + assertEquals("Favorite Animal", imageChoice.getLabel().getValue()); + } + + @Test + void testGetName() { + ImageChoice imageChoice = getImageChoiceUnderTest(PATH_IMAGECHOICE_CUSTOMIZED); + assertEquals("imagechoice_12345", imageChoice.getName()); + } + + @Test + void testGetDataRef() { + ImageChoice imageChoice = getImageChoiceUnderTest(PATH_IMAGECHOICE_CUSTOMIZED); + assertEquals("a.b", imageChoice.getDataRef()); + } + + @Test + void testGetDescription() { + ImageChoice imageChoice = getImageChoiceUnderTest(PATH_IMAGECHOICE_CUSTOMIZED); + assertEquals("Select your favorite animal", imageChoice.getDescription()); + } + + @Test + void testGetScreenReaderText() { + ImageChoice imageChoice = getImageChoiceUnderTest(PATH_IMAGECHOICE_CUSTOMIZED); + assertEquals("custom screen reader text", imageChoice.getScreenReaderText()); + } + + @Test + void testIsVisible() { + ImageChoice imageChoice = getImageChoiceUnderTest(PATH_IMAGECHOICE); + assertEquals(true, imageChoice.isVisible()); + } + + @Test + void testIsVisibleForCustomized() { + ImageChoice imageChoice = getImageChoiceUnderTest(PATH_IMAGECHOICE_CUSTOMIZED); + assertEquals(false, imageChoice.isVisible()); + } + + @Test + void testIsEnabled() { + ImageChoice imageChoice = getImageChoiceUnderTest(PATH_IMAGECHOICE); + assertEquals(true, imageChoice.isEnabled()); + } + + @Test + void testIsReadOnly() { + ImageChoice imageChoice = getImageChoiceUnderTest(PATH_IMAGECHOICE); + assertEquals(false, imageChoice.isReadOnly()); + } + + @Test + void testIsReadOnlyForCustomized() { + ImageChoice imageChoice = getImageChoiceUnderTest(PATH_IMAGECHOICE_CUSTOMIZED); + assertEquals(true, imageChoice.isReadOnly()); + } + + @Test + void testGetEnum() { + ImageChoice imageChoice = getImageChoiceUnderTest(PATH_IMAGECHOICE_CUSTOMIZED); + assertArrayEquals(new String[] { "cat", "dog", "bird" }, imageChoice.getEnums()); + } + + @Test + void testGetEnumNames() { + ImageChoice imageChoice = getImageChoiceUnderTest(PATH_IMAGECHOICE_CUSTOMIZED); + assertArrayEquals(new String[] { "Cat", "Dog", "Bird" }, imageChoice.getEnumNames()); + } + + @Test + void testGetDefault_singleSelect() { + ImageChoice imageChoice = getImageChoiceUnderTest(PATH_IMAGECHOICE_CUSTOMIZED); + assertArrayEquals(new String[] { "cat" }, imageChoice.getDefault()); + } + + @Test + void testGetDefault_multiSelect() { + ImageChoice imageChoice = getImageChoiceUnderTest(PATH_IMAGECHOICE_MULTI); + assertArrayEquals(new String[] { "red", "blue" }, imageChoice.getDefault()); + } + + @Test + void testGetType() { + ImageChoice imageChoice = getImageChoiceUnderTest(PATH_IMAGECHOICE_CUSTOMIZED); + assertEquals(BaseConstraint.Type.STRING, imageChoice.getType()); + } + + @Test + void testGetOrientation_horizontal() { + ImageChoice imageChoice = getImageChoiceUnderTest(PATH_IMAGECHOICE); + assertEquals(CheckBox.Orientation.HORIZONTAL, imageChoice.getOrientation()); + } + + @Test + void testGetOrientation_vertical() { + ImageChoice imageChoice = getImageChoiceUnderTest(PATH_IMAGECHOICE_CUSTOMIZED); + assertEquals(CheckBox.Orientation.VERTICAL, imageChoice.getOrientation()); + } + + @Test + void testGetSelectionType_single() { + ImageChoice imageChoice = getImageChoiceUnderTest(PATH_IMAGECHOICE); + assertEquals(ImageChoice.SelectionType.SINGLE, imageChoice.getSelectionType()); + } + + @Test + void testGetSelectionType_multi() { + ImageChoice imageChoice = getImageChoiceUnderTest(PATH_IMAGECHOICE_MULTI); + assertEquals(ImageChoice.SelectionType.MULTI, imageChoice.getSelectionType()); + } + + @Test + void testGetImageSrc() { + ImageChoice imageChoice = getImageChoiceUnderTest(PATH_IMAGECHOICE_CUSTOMIZED); + assertArrayEquals(new String[] { + "/content/dam/animals/cat.png", + "/content/dam/animals/dog.png", + "/content/dam/animals/bird.png" + }, imageChoice.getImageSrc()); + } + + @Test + void testGetImageSrc_null() { + ImageChoice imageChoice = getImageChoiceUnderTest(PATH_IMAGECHOICE_NO_IMAGES); + assertNull(imageChoice.getImageSrc()); + } + + @Test + void testGetProperties() { + ImageChoice imageChoice = getImageChoiceUnderTest(PATH_IMAGECHOICE_CUSTOMIZED); + Map properties = imageChoice.getProperties(); + assertFalse(properties.isEmpty()); + Map customProperties = (Map) properties.get(Base.CUSTOM_PROPERTY_WRAPPER); + assertTrue((boolean) customProperties.get("tooltipVisible")); + assertEquals("vertical", customProperties.get("orientation")); + assertEquals("single", customProperties.get("selectionType")); + // selectionType must only be emitted under the layout wrapper, never as a stray top-level custom property + assertNull(properties.get("selectionType")); + } + + @Test + void testDorProperties() { + ImageChoice imageChoice = getImageChoiceUnderTest(PATH_IMAGECHOICE_CUSTOMIZED); + assertEquals(true, imageChoice.getDorProperties().get("dorExclusion")); + assertEquals("4", imageChoice.getDorProperties().get("dorColspan")); + } + + @Test + void testEnforceEnum() { + ImageChoice imageChoice = getImageChoiceUnderTest(PATH_IMAGECHOICE); + assertEquals(true, imageChoice.isEnforceEnum()); + } + + @Test + void testGetOptionScreenReaderLabels() { + ImageChoice imageChoice = getImageChoiceUnderTest(PATH_IMAGECHOICE_OPTION_SCREEN_READER_LABEL); + String[] screenReaderLabels = imageChoice.getOptionScreenReaderLabels(); + assertEquals("Favorite Pet: Cat", screenReaderLabels[0]); + assertEquals("Favorite Pet: Dog", screenReaderLabels[1]); + } + + @Test + void testDataLayerProperties() throws IllegalAccessException { + ImageChoice imageChoice = Utils.getComponentUnderTest(PATH_IMAGECHOICE_DATALAYER, ImageChoice.class, context); + FieldUtils.writeField(imageChoice, "dataLayerEnabled", true, true); + FormComponentData dataObject = (FormComponentData) imageChoice.getData(); + assert (dataObject != null); + assert (dataObject.getId()).equals("imagechoice-bafbf1a102"); + assert (dataObject.getType()).equals("core/fd/components/form/imagechoice/v1/imagechoice"); + assert (dataObject.getTitle()).equals("Favorite Color"); + assert (dataObject.getFieldType()).equals("radio-group"); + assert (dataObject.getDescription()).equals("Choose your favorite color"); + } + + @Test + void testDefaultInterface() { + ImageChoice imageChoiceMock = Mockito.mock(ImageChoice.class); + Mockito.when(imageChoiceMock.getOrientation()).thenCallRealMethod(); + assertEquals(CheckBox.Orientation.HORIZONTAL, imageChoiceMock.getOrientation()); + Mockito.when(imageChoiceMock.getSelectionType()).thenCallRealMethod(); + assertEquals(ImageChoice.SelectionType.SINGLE, imageChoiceMock.getSelectionType()); + } + + @Test + void testSelectionTypeFromString() { + assertEquals(ImageChoice.SelectionType.SINGLE, ImageChoice.SelectionType.fromString("single")); + assertEquals(ImageChoice.SelectionType.MULTI, ImageChoice.SelectionType.fromString("multi")); + assertEquals(ImageChoice.SelectionType.SINGLE, ImageChoice.SelectionType.fromString("invalid")); + assertEquals(ImageChoice.SelectionType.SINGLE, ImageChoice.SelectionType.fromString(null)); + } + + @Test + void testJSONExport() throws Exception { + ImageChoice imageChoice = getImageChoiceUnderTest(PATH_IMAGECHOICE); + Utils.testJSONExport(imageChoice, Utils.getTestExporterJSONPath(BASE, PATH_IMAGECHOICE)); + } + + @Test + void testJSONExportForCustomized() throws Exception { + ImageChoice imageChoice = getImageChoiceUnderTest(PATH_IMAGECHOICE_CUSTOMIZED); + Utils.testJSONExport(imageChoice, Utils.getTestExporterJSONPath(BASE, PATH_IMAGECHOICE_CUSTOMIZED)); + } + + @Test + void testJSONExportForMulti() throws Exception { + ImageChoice imageChoice = getImageChoiceUnderTest(PATH_IMAGECHOICE_MULTI); + Utils.testJSONExport(imageChoice, Utils.getTestExporterJSONPath(BASE, PATH_IMAGECHOICE_MULTI)); + } + + @Test + void testJSONExportForDataLayer() throws Exception { + ImageChoice imageChoice = Utils.getComponentUnderTest(PATH_IMAGECHOICE_DATALAYER, ImageChoice.class, context); + FieldUtils.writeField(imageChoice, "dataLayerEnabled", true, true); + Utils.testJSONExport(imageChoice, Utils.getTestExporterJSONPath(BASE, PATH_IMAGECHOICE_DATALAYER)); + } + + private ImageChoice getImageChoiceUnderTest(String resourcePath) { + context.currentResource(resourcePath); + MockSlingHttpServletRequest request = context.request(); + return request.adaptTo(ImageChoice.class); + } +} diff --git a/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v1/form/PanelImplTest.java b/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v1/form/PanelImplTest.java index 506291c089..ee86a48cba 100644 --- a/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v1/form/PanelImplTest.java +++ b/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v1/form/PanelImplTest.java @@ -16,19 +16,23 @@ package com.adobe.cq.forms.core.components.internal.models.v1.form; import java.util.HashSet; +import java.util.List; 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.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mockito; +import com.adobe.cq.export.json.ComponentExporter; import com.adobe.cq.export.json.SlingModelFilter; import com.adobe.cq.forms.core.Utils; +import com.adobe.cq.forms.core.components.internal.form.FeatureToggleConstants; 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.Panel; @@ -40,6 +44,8 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; @ExtendWith(AemContextExtension.class) public class PanelImplTest { @@ -137,4 +143,63 @@ void testNoFieldType() { Panel panel = Utils.getComponentUnderTest(PATH_PANEL_WITHOUT_FIELDTYPE, Panel.class, context); assertEquals(FieldType.PANEL.getValue(), panel.getFieldType()); } + + @Test + void testDorPropertiesEmptyWhenNotSet() { + Panel panel = Utils.getComponentUnderTest(PATH_PANEL, Panel.class, context); + Map dorProperties = panel.getDorProperties(); + assertTrue(dorProperties.isEmpty(), + "getDorProperties should return empty map when no dor properties are set in JCR"); + assertFalse(panel.getProperties().containsKey("fd:dor"), + "fd:dor should not appear in properties when no dor properties are set"); + } + + @AfterEach + void tearDown() { + System.clearProperty(FeatureToggleConstants.FT_SKIP_ITEMS_MAP); + } + + @Test + void testExportedItemsMapByDefault() { + Panel panel = Utils.getComponentUnderTest(PATH_PANEL, Panel.class, context); + Map exportedItems = panel.getExportedItems(); + assertFalse(exportedItems.isEmpty(), + "getExportedItems should return non-empty map when toggle is OFF"); + assertTrue(exportedItems.containsKey("textinput")); + String[] order = panel.getExportedItemsOrder(); + assertEquals(1, order.length); + assertEquals("textinput", order[0]); + } + + @Test + void testExportedItemsArrayWhenToggleEnabled() { + System.setProperty(FeatureToggleConstants.FT_SKIP_ITEMS_MAP, "true"); + Panel panel = Utils.getComponentUnderTest(PATH_PANEL, Panel.class, context); + Map exportedItems = panel.getExportedItems(); + assertTrue(exportedItems.isEmpty(), + "getExportedItems should return empty map when toggle is ON"); + String[] order = panel.getExportedItemsOrder(); + assertEquals(0, order.length); + PanelImpl panelImpl = (PanelImpl) panel; + List itemsArray = panelImpl.getExportedItemsArray(); + assertFalse(itemsArray.isEmpty(), + "getExportedItemsArray should return non-empty list when toggle is ON"); + assertEquals(1, itemsArray.size()); + } + + @Test + void testExportedItemsArrayEmptyWhenToggleDisabled() { + Panel panel = Utils.getComponentUnderTest(PATH_PANEL, Panel.class, context); + PanelImpl panelImpl = (PanelImpl) panel; + List itemsArray = panelImpl.getExportedItemsArray(); + assertTrue(itemsArray.isEmpty(), + "getExportedItemsArray should return empty list when toggle is OFF"); + } + + @Test + void testJSONExportWithItemsArrayToggle() throws Exception { + System.setProperty(FeatureToggleConstants.FT_SKIP_ITEMS_MAP, "true"); + Panel panel = Utils.getComponentUnderTest(PATH_PANEL, Panel.class, context); + Utils.testJSONExport(panel, BASE + "/exporter-panel-items-array.json"); + } } diff --git a/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v1/form/TableImplTest.java b/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v1/form/TableImplTest.java new file mode 100644 index 0000000000..bc60e470ac --- /dev/null +++ b/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v1/form/TableImplTest.java @@ -0,0 +1,204 @@ +/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ~ Copyright 2026 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.HashSet; +import java.util.List; +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.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import com.adobe.cq.export.json.SlingModelFilter; +import com.adobe.cq.forms.core.Utils; +import com.adobe.cq.forms.core.components.models.form.Panel; +import com.adobe.cq.forms.core.context.FormsCoreComponentTestContext; +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.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@ExtendWith(AemContextExtension.class) +public class TableImplTest { + + private static final String BASE = "/form/table"; + private static final String CONTENT_ROOT = "/content"; + private static final String PATH_TABLE = CONTENT_ROOT + "/table"; + private static final String PATH_TABLE_WITH_COLUMN_WIDTH = CONTENT_ROOT + "/table-with-column-width"; + private static final String PATH_TABLE_WITH_SORTING = CONTENT_ROOT + "/table-with-sorting"; + private static final String PATH_TABLE_WITH_EQUAL_WIDTHS = CONTENT_ROOT + "/table-with-equal-widths"; + private static final String PATH_TABLE_WITH_INVALID_WIDTH = CONTENT_ROOT + "/table-with-invalid-width"; + private static final String PATH_TABLE_WITH_NEGATIVE_WIDTH = CONTENT_ROOT + "/table-with-negative-width"; + private static final String PATH_TABLE_WITH_ZERO_WIDTHS = CONTENT_ROOT + "/table-with-zero-widths"; + private static final String PATH_TABLEHEADER = CONTENT_ROOT + "/tableheader"; + private static final String PATH_TABLEROW = CONTENT_ROOT + "/tablerow"; + + private final AemContext context = FormsCoreComponentTestContext.newAemContext(); + + @BeforeEach + void setUp() { + context.load().json(BASE + FormsCoreComponentTestContext.TEST_CONTENT_JSON, CONTENT_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 + void testGetDorProperties_noColumnWidthWhenNotAuthored() throws Exception { + Panel table = Utils.getComponentUnderTest(PATH_TABLE, Panel.class, context); + Map dorProps = table.getDorProperties(); + assertNull(dorProps.get("columnWidth"), + "columnWidth must be absent when no columnWidth is authored"); + } + + @Test + void testGetDorProperties_columnWidthPassedThroughWhenAuthored() throws Exception { + Panel table = Utils.getComponentUnderTest(PATH_TABLE_WITH_COLUMN_WIDTH, Panel.class, context); + Map dorProps = table.getDorProperties(); + assertEquals("1,2,1", dorProps.get("columnWidth"), + "columnWidth must carry the authored proportional value"); + } + + @Test + void testExportedType_table() throws Exception { + Panel table = Utils.getComponentUnderTest(PATH_TABLE, Panel.class, context); + assertEquals("table", table.getExportedType()); + } + + @Test + void testExportedType_tableHeader() throws Exception { + Panel header = Utils.getComponentUnderTest(PATH_TABLEHEADER, Panel.class, context); + assertEquals("table-header", header.getExportedType()); + } + + @Test + void testExportedType_tableRow() throws Exception { + Panel row = Utils.getComponentUnderTest(PATH_TABLEROW, Panel.class, context); + assertEquals("table-row", row.getExportedType()); + } + + @Test + void testIsEnableSorting_falseWhenNotAuthored() throws Exception { + TableImpl table = (TableImpl) Utils.getComponentUnderTest(PATH_TABLE, Panel.class, context); + assertFalse(table.isEnableSorting(), "enableSorting must be false when not authored"); + } + + @Test + void testIsEnableSorting_trueWhenAuthored() throws Exception { + TableImpl table = (TableImpl) Utils.getComponentUnderTest(PATH_TABLE_WITH_SORTING, Panel.class, context); + assertTrue(table.isEnableSorting(), "enableSorting must be true when authored"); + } + + @Test + void testGetColumnWidth_nullWhenNotAuthored() throws Exception { + TableImpl table = (TableImpl) Utils.getComponentUnderTest(PATH_TABLE, Panel.class, context); + assertNull(table.getColumnWidth(), "getColumnWidth must be null when not authored"); + } + + @Test + void testGetColumnWidth_returnedWhenAuthored() throws Exception { + TableImpl table = (TableImpl) Utils.getComponentUnderTest(PATH_TABLE_WITH_COLUMN_WIDTH, Panel.class, context); + assertEquals("1,2,1", table.getColumnWidth(), "getColumnWidth must return the authored value"); + } + + @Test + void testGetColumnWidthColStyles_emptyWhenNotAuthored() throws Exception { + TableImpl table = (TableImpl) Utils.getComponentUnderTest(PATH_TABLE, Panel.class, context); + assertTrue(table.getColumnWidthColStyles().isEmpty()); + assertFalse(table.isColumnWidthsConfigured()); + } + + @Test + void testGetColumnWidthColStyles_sumsTo100() throws Exception { + TableImpl table = (TableImpl) Utils.getComponentUnderTest(PATH_TABLE_WITH_EQUAL_WIDTHS, Panel.class, context); + List styles = table.getColumnWidthColStyles(); + assertEquals(3, styles.size()); + int total = styles.stream() + .mapToInt(s -> Integer.parseInt(s.replace("width: ", "").replace("%", "").trim())) + .sum(); + assertEquals(100, total, "column widths must sum to 100%"); + assertTrue(table.isColumnWidthsConfigured()); + } + + @Test + void testGetColumnWidthColStyles_proportionalWidths() throws Exception { + TableImpl table = (TableImpl) Utils.getComponentUnderTest(PATH_TABLE_WITH_COLUMN_WIDTH, Panel.class, context); + List styles = table.getColumnWidthColStyles(); + assertEquals(3, styles.size()); + int total = styles.stream() + .mapToInt(s -> Integer.parseInt(s.replace("width: ", "").replace("%", "").trim())) + .sum(); + assertEquals(100, total, "proportional widths must sum to 100%"); + } + + @Test + void testGetColumnWidthColStyles_invalidValueTreatedAs1() throws Exception { + TableImpl table = (TableImpl) Utils.getComponentUnderTest(PATH_TABLE_WITH_INVALID_WIDTH, Panel.class, context); + List styles = table.getColumnWidthColStyles(); + assertEquals(2, styles.size(), "invalid token should be treated as 1"); + int total = styles.stream() + .mapToInt(s -> Integer.parseInt(s.replace("width: ", "").replace("%", "").trim())) + .sum(); + assertEquals(100, total, "widths must sum to 100% even with invalid token"); + } + + @Test + void testGetColumnWidthColStyles_negativeValueTreatedAs0() throws Exception { + TableImpl table = (TableImpl) Utils.getComponentUnderTest(PATH_TABLE_WITH_NEGATIVE_WIDTH, Panel.class, context); + List styles = table.getColumnWidthColStyles(); + assertEquals(3, styles.size(), "negative value should be clamped to 0"); + int total = styles.stream() + .mapToInt(s -> Integer.parseInt(s.replace("width: ", "").replace("%", "").trim())) + .sum(); + assertEquals(100, total, "widths must sum to 100% even with negative token"); + } + + @Test + void testGetColumnWidthColStyles_allZeroReturnsEmpty() throws Exception { + TableImpl table = (TableImpl) Utils.getComponentUnderTest(PATH_TABLE_WITH_ZERO_WIDTHS, Panel.class, context); + assertTrue(table.getColumnWidthColStyles().isEmpty(), "all-zero widths must return empty list"); + assertFalse(table.isColumnWidthsConfigured()); + } +} diff --git a/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v1/form/TermsAndConditionsImplTest.java b/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v1/form/TermsAndConditionsImplTest.java index 228abc2260..fcc80af614 100644 --- a/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v1/form/TermsAndConditionsImplTest.java +++ b/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v1/form/TermsAndConditionsImplTest.java @@ -105,6 +105,8 @@ public void testCustomFDProperty() { Map props = tnc.getProperties(); Assert.assertTrue(props.containsKey("fd:tnc")); Assert.assertTrue((Boolean) props.get("fd:tnc")); + Assert.assertTrue(props.containsKey("fd:showAsPopup")); + Assert.assertTrue((Boolean) props.get("fd:showAsPopup")); } diff --git a/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v1/form/TextImplTest.java b/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v1/form/TextImplTest.java index 4eaf4c07cc..8d32998ea7 100644 --- a/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v1/form/TextImplTest.java +++ b/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v1/form/TextImplTest.java @@ -26,6 +26,7 @@ import com.adobe.cq.forms.core.components.internal.form.FormConstants; import com.adobe.cq.forms.core.components.models.form.*; import com.adobe.cq.forms.core.context.FormsCoreComponentTestContext; +import com.fasterxml.jackson.databind.ObjectMapper; import io.wcm.testing.mock.aem.junit5.AemContext; import io.wcm.testing.mock.aem.junit5.AemContextExtension; @@ -91,6 +92,15 @@ void testIsRichText() { assertEquals(false, textMock.isRichText()); } + @Test + void testRichTextAbsentFromJcr() throws Exception { + Text text = Utils.getComponentUnderTest(PATH_TEXT, Text.class, context); + assertEquals(false, text.isRichText()); + String json = new ObjectMapper().writeValueAsString(text); + assertFalse("richText must not appear in JSON when textIsRich is absent from JCR", + json.contains("\"richText\"")); + } + @Test void testGetDataRef() { Text text = Utils.getComponentUnderTest(PATH_TEXT_CUSTOMIZED, Text.class, context); diff --git a/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v1/form/TextInputImplTest.java b/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v1/form/TextInputImplTest.java index 9177860b83..1542522422 100644 --- a/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v1/form/TextInputImplTest.java +++ b/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v1/form/TextInputImplTest.java @@ -22,6 +22,7 @@ import org.apache.commons.lang3.reflect.FieldUtils; import org.apache.sling.api.resource.Resource; import org.apache.sling.testing.mock.sling.servlet.MockSlingHttpServletRequest; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -30,6 +31,7 @@ import com.adobe.cq.forms.core.Utils; import com.adobe.cq.forms.core.components.datalayer.FormComponentData; +import com.adobe.cq.forms.core.components.internal.form.FeatureToggleConstants; import com.adobe.cq.forms.core.components.internal.form.FormConstants; import com.adobe.cq.forms.core.components.models.form.*; import com.adobe.cq.forms.core.components.util.AbstractFieldImpl; @@ -134,7 +136,22 @@ void testDorProperties() { assertEquals(true, textInput.getDorProperties().get("dorExclusion")); assertEquals("4", textInput.getDorProperties().get("dorColspan")); assertEquals("Text1", textInput.getDorProperties().get("dorBindRef")); + } + @Test + void testDorPropertiesEmptyWhenNotSet() { + TextInput textInput = Utils.getComponentUnderTest(PATH_TEXTINPUT, TextInput.class, context); + assertTrue("getDorProperties should return empty map when no dor properties are set in JCR", + textInput.getDorProperties().isEmpty()); + assertFalse("fd:dor should not appear in properties when no dor properties are set", + textInput.getProperties().containsKey("fd:dor")); + } + + @Test + void testDorPropertiesIncludedWhenSet() { + TextInput textInput = Utils.getComponentUnderTest(PATH_TEXTINPUT_CUSTOMIZED, TextInput.class, context); + assertTrue("fd:dor should appear in properties when dor properties are set in JCR", + textInput.getProperties().containsKey("fd:dor")); } @Test @@ -576,4 +593,27 @@ void testEmptyValueEnumGetValue() { assertEquals("undefined", AbstractFieldImpl.EmptyValue.UNDEFINED.getValue()); assertEquals("", AbstractFieldImpl.EmptyValue.EMPTY_STRING.getValue()); } + + @AfterEach + void tearDown() { + System.clearProperty(FeatureToggleConstants.FT_SKIP_DEFAULT_SET_PROPERTY_EVENT); + } + + @Test + void testGetEventsContainsSetPropertyByDefault() { + TextInput textInput = Utils.getComponentUnderTest(PATH_TEXTINPUT, TextInput.class, context); + Map events = textInput.getEvents(); + assertTrue("getEvents should include custom:setProperty when toggle is OFF", + events.containsKey("custom:setProperty")); + assertArrayEquals(new String[] { "$event.payload" }, events.get("custom:setProperty")); + } + + @Test + void testGetEventsOmitsSetPropertyWhenToggleEnabled() { + System.setProperty(FeatureToggleConstants.FT_SKIP_DEFAULT_SET_PROPERTY_EVENT, "true"); + TextInput textInput = Utils.getComponentUnderTest(PATH_TEXTINPUT, TextInput.class, context); + Map events = textInput.getEvents(); + assertFalse("getEvents should NOT include custom:setProperty when toggle is ON", + events.containsKey("custom:setProperty")); + } } diff --git a/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v1/form/TitleImplTest.java b/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v1/form/TitleImplTest.java index 405ec23c99..8f7f99ac1b 100644 --- a/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v1/form/TitleImplTest.java +++ b/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v1/form/TitleImplTest.java @@ -170,6 +170,7 @@ void testTitleWithLocale() throws Exception { Map paramMap = new HashMap<>(); paramMap.put(GuideConstants.AF_LANGUAGE_PARAMETER, "de"); request.setParameterMap(paramMap); + Utils.disableLocaleFeatureToggleLookup(request); Title title = request.adaptTo(Title.class); assertEquals("Title", title.getText()); } 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 43b2d53428..5ea43396cb 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 @@ -34,6 +34,7 @@ import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.osgi.services.HttpClientBuilderFactory; +import org.apache.sling.api.resource.ModifiableValueMap; import org.apache.sling.api.resource.Resource; import org.apache.sling.i18n.ResourceBundleProvider; import org.apache.sling.testing.mock.sling.MockResourceBundle; @@ -47,12 +48,12 @@ import org.mockito.ArgumentCaptor; import org.mockito.Mockito; -import com.adobe.aemds.guide.service.CoreComponentCustomPropertiesProvider; import com.adobe.aemds.guide.service.GuideLocalizationService; import com.adobe.aemds.guide.utils.GuideConstants; import com.adobe.cq.export.json.ComponentExporter; import com.adobe.cq.export.json.SlingModelFilter; import com.adobe.cq.forms.core.Utils; +import com.adobe.cq.forms.core.components.internal.form.FeatureToggleConstants; import com.adobe.cq.forms.core.components.internal.form.FormConstants; import com.adobe.cq.forms.core.components.internal.form.ReservedProperties; import com.adobe.cq.forms.core.components.models.form.*; @@ -100,6 +101,9 @@ public class FormContainerImplTest { private static final String PATH_FORM_WITHOUT_FIELDTYPE = CONTENT_ROOT + "/formcontainerv2-without-fieldtype"; private static final String PATH_FORM_WITH_AUTO_SAVE = CONTENT_ROOT + "/formcontainerv2WithAutoSave"; + private static final String PATH_FORM_WITH_CHANGE_EVENT = CONTENT_ROOT + "/formcontainerv2ChangeEventBehaviour"; + private static final String PATH_FORM_WITH_SSV = CONTENT_ROOT + "/formcontainerv2-with-ssv"; + private static final String PATH_FORM_SSV_DISABLED = CONTENT_ROOT + "/formcontainerv2-ssv-disabled"; private static final String PATH_FORM_1_WITHOUT_REDIRECT = CONTENT_ROOT + "/formcontainerv2WithoutRedirect"; private static final String CONTENT_FORM_WITHOUT_PREFILL_ROOT = "/content/forms/af/formWithoutPrefill"; private static final String PATH_FORM_WITHOUT_PREFILL = CONTENT_FORM_WITHOUT_PREFILL_ROOT + "/formcontainerv2WithoutPrefill"; @@ -329,7 +333,14 @@ void testJSONExport() throws Exception { Utils.testJSONExport(formContainer, Utils.getTestExporterJSONPath(BASE, PATH_FORM_1)); } - @Ignore + @Test + void testJSONExportWithChangeEventBehaviour() throws Exception { + context.load().json(BASE + "/test-content-change-event-behaviour.json", PATH_FORM_WITH_CHANGE_EVENT); + FormContainer formContainer = Utils.getComponentUnderTest(PATH_FORM_WITH_CHANGE_EVENT, FormContainer.class, context); + Utils.testJSONExport(formContainer, Utils.getTestExporterJSONPath(BASE, PATH_FORM_WITH_CHANGE_EVENT)); + } + + @Test void testJSONExportWithAutoSaveEnable() throws Exception { context.load().json(BASE + "/test-content-auto-save.json", PATH_FORM_WITH_AUTO_SAVE); FormContainer formContainer = Utils.getComponentUnderTest(PATH_FORM_WITH_AUTO_SAVE, @@ -467,6 +478,7 @@ private FormContainer getFormContainerWithLocaleUnderTest(String resourcePath) t Map paramMap = new HashMap<>(); paramMap.put(GuideConstants.AF_LANGUAGE_PARAMETER, "de"); request.setParameterMap(paramMap); + Utils.disableLocaleFeatureToggleLookup(request); context.currentResource().adaptTo(FormContainer.class); return request.adaptTo(FormContainer.class); } @@ -489,6 +501,7 @@ private FormContainer getFormContainerWithRTLLocaleUnderTest(String resourcePath Map paramMap = new HashMap<>(); paramMap.put(GuideConstants.AF_LANGUAGE_PARAMETER, "ar-ae"); request.setParameterMap(paramMap); + Utils.disableLocaleFeatureToggleLookup(request); context.currentResource().adaptTo(FormContainer.class); return request.adaptTo(FormContainer.class); } @@ -611,36 +624,50 @@ public void testGetIsHamburgerMenuEnabled() { } /** - * Test to check if the properties are fetched from the CoreComponentCustomPropertiesProvider are part of form container properties or - * not - * Also, if same properties is set in form container, then it should override the properties from CoreComponentCustomPropertiesProvider - * - * @throws Exception + * When FT_ALLOW_MULTIPLE_FIELDS_IN_WHEN is enabled, the form container exports + * {@code fd:changeEventBehaviour="deps"} on its own (previously injected by the + * CoreComponentCustomPropertiesProvider addon). */ @Test - void testGetPropertiesForCoreComponentCustomPropertiesProvider() throws Exception { - CoreComponentCustomPropertiesProvider coreComponentCustomPropertiesProvider = Mockito.mock( - CoreComponentCustomPropertiesProvider.class); + void testChangeEventBehaviourFromToggle() throws Exception { + System.setProperty(FeatureToggleConstants.FT_ALLOW_MULTIPLE_FIELDS_IN_WHEN, "true"); + try { + FormContainer formContainer = Utils.getComponentUnderTest(PATH_FORM_1, FormContainer.class, context); + assertEquals("deps", formContainer.getProperties().get(ReservedProperties.FD_CHANGE_EVENT_BEHAVIOUR)); + } finally { + System.clearProperty(FeatureToggleConstants.FT_ALLOW_MULTIPLE_FIELDS_IN_WHEN); + } + } + + /** + * When the toggle is disabled, {@code fd:changeEventBehaviour} is not exported (unless set on the node). + */ + @Test + void testChangeEventBehaviourAbsentWhenToggleDisabled() throws Exception { FormContainer formContainer = Utils.getComponentUnderTest(PATH_FORM_1, FormContainer.class, context); - Utils.setInternalState(formContainer, "coreComponentCustomPropertiesProvider", coreComponentCustomPropertiesProvider); - Mockito.when(coreComponentCustomPropertiesProvider.getProperties()).thenReturn(new HashMap() { - { - put("fd:changeEventBehaviour", "deps"); - put("customProp", "customValue"); - } - }); - assertEquals("deps", formContainer.getProperties().get("fd:changeEventBehaviour")); - assertEquals("customPropValue", formContainer.getProperties().get("customProp")); + assertNull(formContainer.getProperties().get(ReservedProperties.FD_CHANGE_EVENT_BEHAVIOUR)); } @Test - void testGetPropertiesForCoreComponentCustomPropertiesProviderForNull() throws Exception { - CoreComponentCustomPropertiesProvider coreComponentCustomPropertiesProvider = Mockito.mock( - CoreComponentCustomPropertiesProvider.class); + void testGetChangeEventBehaviourFromNode() throws Exception { + Resource resource = context.resourceResolver().getResource(PATH_FORM_1); + resource.adaptTo(ModifiableValueMap.class).put(ReservedProperties.FD_CHANGE_EVENT_BEHAVIOUR, "deps"); FormContainer formContainer = Utils.getComponentUnderTest(PATH_FORM_1, FormContainer.class, context); - Utils.setInternalState(formContainer, "coreComponentCustomPropertiesProvider", coreComponentCustomPropertiesProvider); - Mockito.when(coreComponentCustomPropertiesProvider.getProperties()).thenReturn(null); - assertEquals("customPropValue", formContainer.getProperties().get("customProp")); + assertEquals("deps", formContainer.getProperties().get(ReservedProperties.FD_CHANGE_EVENT_BEHAVIOUR)); + } + + @Test + void testNodeChangeEventBehaviourOverridesToggleDefault() throws Exception { + System.setProperty(FeatureToggleConstants.FT_ALLOW_MULTIPLE_FIELDS_IN_WHEN, "true"); + try { + Resource resource = context.resourceResolver().getResource(PATH_FORM_1); + resource.adaptTo(ModifiableValueMap.class).put(ReservedProperties.FD_CHANGE_EVENT_BEHAVIOUR, "node-deps"); + FormContainer formContainer = Utils.getComponentUnderTest(PATH_FORM_1, FormContainer.class, context); + // node value wins over the toggle-driven default + assertEquals("node-deps", formContainer.getProperties().get(ReservedProperties.FD_CHANGE_EVENT_BEHAVIOUR)); + } finally { + System.clearProperty(FeatureToggleConstants.FT_ALLOW_MULTIPLE_FIELDS_IN_WHEN); + } } @Test @@ -917,4 +944,51 @@ void testSetLang() throws Exception { formContainer.setLang(null); assertEquals(formContainer.getLang(), "en"); } + + // ─── SSV submit-properties tests ────────────────────────────────────────── + + @SuppressWarnings("unchecked") + @Test + void testSsvPropertiesNotExposedInFormJson() throws Exception { + // SSV config (enableServerSideValidation, ssvCloudServicePath) must never appear in + // the exported form JSON. The submit servlet reads them directly from JCR. Exposing + // ssvCloudServicePath to clients leaks an internal JCR path. + context.request().setAttribute(FormConstants.X_ADOBE_FORM_DEFINITION, FormConstants.FORM_DEFINITION_SUBMISSION); + FormContainerImpl formContainer = Utils.getComponentUnderTest(PATH_FORM_WITH_SSV, FormContainerImpl.class, context); + + Map props = formContainer.getProperties(); + assertNotNull("fd:submit must be present in submission view", props.get(ReservedProperties.FD_SUBMIT_PROPERTIES)); + + Map submit = (Map) props.get(ReservedProperties.FD_SUBMIT_PROPERTIES); + assertNull("serverSideValidation block must NOT be in the form JSON to avoid leaking JCR paths", + submit.get("serverSideValidation")); + assertFalse("enableServerSideValidation must not be a top-level submit property", + submit.containsKey(ReservedProperties.PN_ENABLE_SERVER_SIDE_VALIDATION)); + assertFalse("ssvCloudServicePath must not be a top-level submit property", + submit.containsKey(ReservedProperties.PN_SSV_CLOUD_SERVICE_PATH)); + } + + @SuppressWarnings("unchecked") + @Test + void testSubmitPropertiesHasNoSsvBlockWhenDisabled() throws Exception { + context.request().setAttribute(FormConstants.X_ADOBE_FORM_DEFINITION, FormConstants.FORM_DEFINITION_SUBMISSION); + FormContainerImpl formContainer = Utils.getComponentUnderTest(PATH_FORM_SSV_DISABLED, FormContainerImpl.class, context); + + Map props = formContainer.getProperties(); + assertNotNull("fd:submit must be present in submission view", props.get(ReservedProperties.FD_SUBMIT_PROPERTIES)); + + Map submit = (Map) props.get(ReservedProperties.FD_SUBMIT_PROPERTIES); + assertNull("serverSideValidation block must be absent when SSV is not configured", submit.get("serverSideValidation")); + } + + @SuppressWarnings("unchecked") + @Test + void testSubmitPropertiesAbsentWithoutSubmissionViewHeader() throws Exception { + // Without the submission view request attribute, fd:submit must not be exported + FormContainer formContainer = Utils.getComponentUnderTest(PATH_FORM_WITH_SSV, FormContainer.class, context); + + Map props = formContainer.getProperties(); + assertNull("fd:submit must NOT be present in regular (non-submission-view) rendering", + props.get(ReservedProperties.FD_SUBMIT_PROPERTIES)); + } } diff --git a/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v2/form/TitleImplV2Test.java b/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v2/form/TitleImplV2Test.java index e62908a127..713339d9ce 100644 --- a/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v2/form/TitleImplV2Test.java +++ b/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v2/form/TitleImplV2Test.java @@ -197,6 +197,7 @@ void testTitleWithLocale() throws Exception { Map paramMap = new HashMap<>(); paramMap.put(GuideConstants.AF_LANGUAGE_PARAMETER, "de"); request.setParameterMap(paramMap); + Utils.disableLocaleFeatureToggleLookup(request); FormTitle title = request.adaptTo(FormTitle.class); assertEquals("Title", title.getText()); } diff --git a/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v3/form/FileInputImplV3Test.java b/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v3/form/FileInputImplV3Test.java index 4a8de1c148..b0a4478c95 100644 --- a/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v3/form/FileInputImplV3Test.java +++ b/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v3/form/FileInputImplV3Test.java @@ -17,6 +17,7 @@ import java.util.Arrays; import java.util.Collections; +import java.util.List; import java.util.Map; import org.apache.commons.lang3.reflect.FieldUtils; @@ -41,7 +42,7 @@ import io.wcm.testing.mock.aem.junit5.AemContext; import io.wcm.testing.mock.aem.junit5.AemContextExtension; -import static org.hamcrest.CoreMatchers.*; +import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertThat; @@ -261,7 +262,7 @@ void testGetMaxFileSize() { @Test void testGetAccept() { FileInput fileInput = Utils.getComponentUnderTest(PATH_FILEINPUT_CUSTOMIZED, FileInput.class, context); - assertThat(Arrays.asList("audio/*", "video/*", "image/*"), is(fileInput.getAccept())); + assertThat(Arrays.asList("audio/*", "video/*", "image/*", ".jpg", ".png"), is(fileInput.getAccept())); FileInput fileInputMock = Mockito.mock(FileInput.class); Mockito.when(fileInputMock.getAccept()).thenCallRealMethod(); assertThat(FileInput.DEFAULT_ACCEPT, is(fileInputMock.getAccept())); @@ -400,4 +401,21 @@ void testJSONExportDataLayer() throws Exception { FieldUtils.writeField(fileInput, "dataLayerEnabled", true, true); Utils.testJSONExport(fileInput, Utils.getTestExporterJSONPath(BASE, PATH_FILEINPUT_DATALAYER)); } + + @Test + void testGetAcceptExtensions() { + FileInput fileInput = Utils.getComponentUnderTest(PATH_FILEINPUT_DATALAYER, FileInput.class, context); + // assert fileInput.getAcceptExtensions() to return empty list + assertEquals(Collections.emptyList(), fileInput.getAcceptExtensions()); + FileInput fileInputMock = Mockito.mock(FileInput.class); + Mockito.when(fileInputMock.getAcceptExtensions()).thenCallRealMethod(); + assertEquals(Collections.emptyList(), fileInput.getAcceptExtensions()); + } + + @Test + void testGetAcceptExtensionsWithExtensions() { + FileInput fileInput = Utils.getComponentUnderTest(PATH_FILEINPUT_CUSTOMIZED, FileInput.class, context); + List extensions = fileInput.getAcceptExtensions(); + assertThat(Arrays.asList(".jpg", ".png"), is(extensions)); + } } diff --git a/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/servlets/FormMetaDataDataSourceServletTest.java b/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/servlets/FormMetaDataDataSourceServletTest.java index 9424c2ac56..92172d7133 100644 --- a/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/servlets/FormMetaDataDataSourceServletTest.java +++ b/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/servlets/FormMetaDataDataSourceServletTest.java @@ -104,6 +104,33 @@ public void testDataSource() throws Exception { assertEquals(1, Lists.newArrayList(resource.getChildren()).size()); } + @Test + public void testDataSourceForPrefillActionsAddsDefaultNoneOption() throws Exception { + context.currentResource("/apps/prefilltypedatasource"); + when(expressionResolver.resolve(any(), any(), any(), any(SlingHttpServletRequest.class))) + .then(returnsFirstArg()); + ArrayList componentDescriptions = new ArrayList<>(); + componentDescriptions.add(description); + when(formMetaDataMock.getPrefillActions()).thenReturn(componentDescriptions.iterator()); + when(description.getTitle()).thenReturn("Prefill Service"); + when(description.getResourceType()).thenReturn("form/prefill"); + FormMetaDataDataSourceServlet dataSourceServlet = new FormMetaDataDataSourceServlet(); + // set expression resolver mock + Utils.setInternalState(dataSourceServlet, "expressionResolver", expressionResolver); + dataSourceServlet.doGet(context.request(), context.response()); + DataSource dataSource = (com.adobe.granite.ui.components.ds.DataSource) context.request().getAttribute( + DataSource.class.getName()); + assertNotNull(dataSource); + Iterator iterator = dataSource.iterator(); + Resource defaultNoneOption = iterator.next(); + assertEquals("None", defaultNoneOption.getValueMap().get(PN_TEXT, String.class)); + assertEquals("", defaultNoneOption.getValueMap().get(PN_VALUE, String.class)); + Resource prefillOption = iterator.next(); + assertEquals("Prefill Service", prefillOption.getValueMap().get(PN_TEXT, String.class)); + assertEquals("form/prefill", prefillOption.getValueMap().get(PN_VALUE, String.class)); + assertFalse(iterator.hasNext()); + } + @Test public void testDataSourceForFormattersForNumberInput() throws Exception { context.currentResource("/apps/formattertypedatasourcenumberinput"); diff --git a/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/util/AbstractFormComponentImplTest.java b/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/util/AbstractFormComponentImplTest.java index 19eb8f156a..7463ce91d4 100644 --- a/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/util/AbstractFormComponentImplTest.java +++ b/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/util/AbstractFormComponentImplTest.java @@ -32,6 +32,7 @@ import com.adobe.cq.forms.core.context.FormsCoreComponentTestContext; import com.day.cq.wcm.api.Page; import com.day.cq.wcm.api.PageManager; +import com.day.cq.wcm.api.WCMMode; import io.wcm.testing.mock.aem.junit5.AemContext; import io.wcm.testing.mock.aem.junit5.AemContextExtension; @@ -48,6 +49,7 @@ public class AbstractFormComponentImplTest { private static final String PATH_COMPONENT_WITH_NO_VALIDATION_STATUS = CONTENT_ROOT + "/datepicker2"; private static final String PATH_COMPONENT_WITH_INVALID_VALIDATION_STATUS = CONTENT_ROOT + "/datepicker3"; private static final String PATH_COMPONENT_WITH_NO_RULE = CONTENT_ROOT + "/numberinput"; + private static final String PATH_COMPONENT_WITH_PROPERTIES_RULE = CONTENT_ROOT + "/textinputWithPropertiesRule"; private static final String AF_PATH = "/content/forms/af/testAf"; private static final String PAGE_PATH = "/content/testPage"; @@ -59,48 +61,127 @@ public void setUp() { } @Test - public void testInvalidRule() { + public void testInvalidRuleNotInPublish() { AbstractFormComponentImpl abstractFormComponentImpl = prepareTestClass(PATH_COMPONENT_WITH_INVALID_RULE); Map properties = abstractFormComponentImpl.getProperties(); - Map rulesProperties = (Map) properties.get("fd:rules"); - assertNotNull(rulesProperties); - assertEquals("invalid", rulesProperties.get("validationStatus")); + assertNull(properties.get("fd:rules"), "fd:rules should not appear in publish mode"); } @Test - public void testValidRule() { + public void testValidRuleNotInPublish() { AbstractFormComponentImpl abstractFormComponentImpl = prepareTestClass(PATH_COMPONENT_WITH_VALID_RULE); Map properties = abstractFormComponentImpl.getProperties(); - Map rulesProperties = (Map) properties.get("fd:rules"); - assertNotNull(rulesProperties); - assertEquals("valid", rulesProperties.get("validationStatus")); + assertNull(properties.get("fd:rules"), "fd:rules should not appear in publish mode"); } @Test public void testNoRule() { AbstractFormComponentImpl abstractFormComponentImpl = prepareTestClass(PATH_COMPONENT_WITH_NO_RULE); Map properties = abstractFormComponentImpl.getProperties(); - Object rulesProperties = properties.get("fd:rules"); - assertNull(rulesProperties); + assertNull(properties.get("fd:rules")); } @Test public void testNoValidationStatusRule() { AbstractFormComponentImpl abstractFormComponentImpl = prepareTestClass(PATH_COMPONENT_WITH_NO_VALIDATION_STATUS); Map properties = abstractFormComponentImpl.getProperties(); - Object rulesProperties = properties.get("fd:rules"); - assertNull(rulesProperties); + assertNull(properties.get("fd:rules")); } @Test - public void testInvalidValidationStatusRule() { + public void testInvalidValidationStatusRuleNotInPublish() { AbstractFormComponentImpl abstractFormComponentImpl = prepareTestClass(PATH_COMPONENT_WITH_INVALID_VALIDATION_STATUS); Map properties = abstractFormComponentImpl.getProperties(); + assertNull(properties.get("fd:rules"), "fd:rules should not appear in publish mode"); + } + + @Test + public void testValidRuleInAuthorMode() { + AbstractFormComponentImpl abstractFormComponentImpl = prepareTestClassWithAuthorMode(PATH_COMPONENT_WITH_VALID_RULE); + Map properties = abstractFormComponentImpl.getProperties(); + Map rulesProperties = (Map) properties.get("fd:rules"); + assertNotNull(rulesProperties, "fd:rules should appear in author mode"); + assertEquals("valid", rulesProperties.get("validationStatus")); + } + + @Test + public void testInvalidRuleInAuthorMode() { + AbstractFormComponentImpl abstractFormComponentImpl = prepareTestClassWithAuthorMode(PATH_COMPONENT_WITH_INVALID_RULE); + Map properties = abstractFormComponentImpl.getProperties(); + Map rulesProperties = (Map) properties.get("fd:rules"); + assertNotNull(rulesProperties, "fd:rules should appear in author mode"); + assertEquals("invalid", rulesProperties.get("validationStatus")); + } + + @Test + public void testNoRuleInAuthorMode() { + AbstractFormComponentImpl abstractFormComponentImpl = prepareTestClassWithAuthorMode(PATH_COMPONENT_WITH_NO_RULE); + Map properties = abstractFormComponentImpl.getProperties(); + assertNull(properties.get("fd:rules"), "fd:rules should not appear when no fd:rules node exists"); + } + + @Test + public void testInvalidValidationStatusInAuthorMode() { + AbstractFormComponentImpl abstractFormComponentImpl = prepareTestClassWithAuthorMode(PATH_COMPONENT_WITH_INVALID_VALIDATION_STATUS); + Map properties = abstractFormComponentImpl.getProperties(); Map rulesProperties = (Map) properties.get("fd:rules"); - assertNotNull(rulesProperties); + assertNotNull(rulesProperties, "fd:rules should appear in author mode even with invalid status"); assertEquals("invalid", rulesProperties.get("validationStatus")); } + @Test + public void testRulesExcludedWhenPublishViewAttributeSetOnAuthor() { + AbstractFormComponentImpl abstractFormComponentImpl = prepareTestClassWithAuthorMode(PATH_COMPONENT_WITH_VALID_RULE); + context.request().setAttribute(FormConstants.REQ_ATTR_PUBLISH_VIEW, Boolean.TRUE); + Map properties = abstractFormComponentImpl.getProperties(); + assertNull(properties.get("fd:rules"), + "fd:rules should not appear when publish view attribute is set even in author WCMMode"); + } + + @Test + public void testGranularPropertiesRulesPreserved() { + // A rule keyed `properties.` is a granular custom-variable target routed by the + // runtime to the PropertiesManager; the exporter must not drop flat or nested ones. + AbstractFormComponentImpl abstractFormComponentImpl = prepareTestClass(PATH_COMPONENT_WITH_PROPERTIES_RULE); + Map rules = abstractFormComponentImpl.getRules(); + assertEquals("some property expression", rules.get("properties.employer"), + "flat properties. rule should be preserved"); + assertEquals("some nested property expression", rules.get("properties.employer.category"), + "nested properties. rule should be preserved"); + } + + @Test + public void testBarePropertiesAndWhitelistedRulesPreserved() { + // The whole `properties` bag is an editable property, and standard whitelisted targets + // (e.g. visible) must keep working alongside the granular properties. targets. + AbstractFormComponentImpl abstractFormComponentImpl = prepareTestClass(PATH_COMPONENT_WITH_PROPERTIES_RULE); + Map rules = abstractFormComponentImpl.getRules(); + assertEquals("some expression", rules.get("visible")); + assertEquals("merge($field.properties, {a: 1})", rules.get("properties"), + "the whole properties bag rule should be preserved"); + } + + @Test + public void testUnknownRulesDropped() { + // Keys that are neither whitelisted nor a genuine properties. target are filtered + // out, including a `properties`-prefixed key without the dot separator. + AbstractFormComponentImpl abstractFormComponentImpl = prepareTestClass(PATH_COMPONENT_WITH_PROPERTIES_RULE); + Map rules = abstractFormComponentImpl.getRules(); + assertNull(rules.get("notARule"), "non-whitelisted, non-properties rule keys should be dropped"); + assertNull(rules.get("propertiesFoo"), "a properties-prefixed key without a dot is not a valid target"); + } + + @Test + public void testIsAuthorModeUtility() { + assertFalse(ComponentUtils.isAuthorMode(null), "null request should not be author mode"); + assertFalse(ComponentUtils.isAuthorMode(context.request()), "default request should not be author mode"); + WCMMode.EDIT.toRequest(context.request()); + assertTrue(ComponentUtils.isAuthorMode(context.request()), "EDIT mode should be author mode"); + context.request().setAttribute(FormConstants.REQ_ATTR_PUBLISH_VIEW, Boolean.TRUE); + assertFalse(ComponentUtils.isAuthorMode(context.request()), + "publish view attribute should override WCMMode"); + } + @Test public void testEmbedWithIframe() { Resource resource = Mockito.mock(Resource.class); @@ -154,4 +235,12 @@ private AbstractFormComponentImpl prepareTestClass(String path) { Utils.setInternalState(abstractFormComponentImpl, "resource", resource); return abstractFormComponentImpl; } + + private AbstractFormComponentImpl prepareTestClassWithAuthorMode(String path) { + AbstractFormComponentImpl abstractFormComponentImpl = prepareTestClass(path); + WCMMode.EDIT.toRequest(context.request()); + Utils.setInternalState(abstractFormComponentImpl, "request", context.request()); + return abstractFormComponentImpl; + } + } diff --git a/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/util/ComponentUtilsTest.java b/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/util/ComponentUtilsTest.java index 96553eecc2..d63329bf16 100644 --- a/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/util/ComponentUtilsTest.java +++ b/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/util/ComponentUtilsTest.java @@ -27,6 +27,7 @@ import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.osgi.services.HttpClientBuilderFactory; import org.apache.jackrabbit.JcrConstants; +import org.apache.sling.api.SlingHttpServletRequest; import org.apache.sling.api.resource.Resource; import org.apache.sling.api.resource.ResourceResolver; import org.apache.sling.api.resource.ValueMap; @@ -34,7 +35,9 @@ import org.junit.jupiter.api.Test; import org.mockito.Mockito; +import com.adobe.cq.forms.core.components.internal.form.FeatureToggleConstants; import com.adobe.cq.forms.core.components.internal.form.FormConstants; +import com.day.cq.wcm.api.WCMMode; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.any; @@ -97,6 +100,58 @@ public void testIsFragmentComponent() { assertFalse(ComponentUtils.isFragmentComponent(resource)); } + @Test + public void testIsToggleEnabledBySystemPropertyTrue() { + String key = "test.toggle." + System.nanoTime(); + try { + System.setProperty(key, "true"); + assertTrue(ComponentUtils.isToggleEnabledBySystemProperty(key)); + } finally { + System.clearProperty(key); + } + } + + @Test + public void testIsToggleEnabledBySystemPropertyFalse() { + String key = "test.toggle." + System.nanoTime(); + try { + System.setProperty(key, "false"); + assertFalse(ComponentUtils.isToggleEnabledBySystemProperty(key)); + } finally { + System.clearProperty(key); + } + } + + @Test + public void testIsToggleEnabledBySystemPropertyUnset() { + String key = "test.toggle.unset." + System.nanoTime(); + assertFalse(ComponentUtils.isToggleEnabledBySystemProperty(key)); + } + + @Test + public void testIsToggleEnabledRespectsSystemProperty() { + String key = FeatureToggleConstants.FT_FRAGMENT_MERGE_CONTAINER_RULES_EVENTS; + String saved = System.getProperty(key); + try { + System.setProperty(key, "true"); + assertTrue(ComponentUtils.isToggleEnabled(key)); + System.setProperty(key, "false"); + assertFalse(ComponentUtils.isToggleEnabled(key)); + } finally { + if (saved != null) { + System.setProperty(key, saved); + } else { + System.clearProperty(key); + } + } + } + + @Test + public void testIsToggleEnabledUnsetReturnsFalse() { + String key = "test.toggle.unset." + System.nanoTime(); + assertFalse(ComponentUtils.isToggleEnabled(key)); + } + @Test public void testParseNumber() { // Test valid long string @@ -153,6 +208,40 @@ public void testSubmitActionsCaching() throws Exception { verify(mockHttpClient, times(2)).execute(any()); } + @Test + public void testIsAuthorModeNullRequest() { + assertFalse(ComponentUtils.isAuthorMode(null)); + } + + @Test + public void testIsAuthorModeEditMode() { + SlingHttpServletRequest request = mock(SlingHttpServletRequest.class); + when(request.getAttribute(WCMMode.REQUEST_ATTRIBUTE_NAME)).thenReturn(WCMMode.EDIT); + assertTrue(ComponentUtils.isAuthorMode(request)); + } + + @Test + public void testIsAuthorModeDesignMode() { + SlingHttpServletRequest request = mock(SlingHttpServletRequest.class); + when(request.getAttribute(WCMMode.REQUEST_ATTRIBUTE_NAME)).thenReturn(WCMMode.DESIGN); + assertTrue(ComponentUtils.isAuthorMode(request)); + } + + @Test + public void testIsAuthorModeDisabledMode() { + SlingHttpServletRequest request = mock(SlingHttpServletRequest.class); + when(request.getAttribute(WCMMode.REQUEST_ATTRIBUTE_NAME)).thenReturn(WCMMode.DISABLED); + assertFalse(ComponentUtils.isAuthorMode(request)); + } + + @Test + public void testIsAuthorModePublishViewOverridesEditMode() { + SlingHttpServletRequest request = mock(SlingHttpServletRequest.class); + when(request.getAttribute(WCMMode.REQUEST_ATTRIBUTE_NAME)).thenReturn(WCMMode.EDIT); + when(request.getAttribute(FormConstants.REQ_ATTR_PUBLISH_VIEW)).thenReturn(Boolean.TRUE); + assertFalse(ComponentUtils.isAuthorMode(request)); + } + // Helper methods to access private cache fields via reflection private void resetCache() throws Exception { Field cacheField = CacheManager.class.getDeclaredField("SUBMIT_ACTIONS_CACHE"); diff --git a/bundles/af-core/src/test/resources/form/checkbox/exporter-checkbox-customized.json b/bundles/af-core/src/test/resources/form/checkbox/exporter-checkbox-customized.json index 3e6812ad19..f8d9262582 100644 --- a/bundles/af-core/src/test/resources/form/checkbox/exporter-checkbox-customized.json +++ b/bundles/af-core/src/test/resources/form/checkbox/exporter-checkbox-customized.json @@ -28,9 +28,6 @@ "afs:layout": { "tooltipVisible": false }, - "fd:dor": { - "dorExclusion": false - }, "fd:path": "/content/checkbox-customized" }, "enum": [ diff --git a/bundles/af-core/src/test/resources/form/checkbox/exporter-checkbox-without-fieldtype.json b/bundles/af-core/src/test/resources/form/checkbox/exporter-checkbox-without-fieldtype.json index 0c48f60936..c57bacf770 100644 --- a/bundles/af-core/src/test/resources/form/checkbox/exporter-checkbox-without-fieldtype.json +++ b/bundles/af-core/src/test/resources/form/checkbox/exporter-checkbox-without-fieldtype.json @@ -2,30 +2,32 @@ "id": "checkbox-wo-fieldtype1", "name": "abc", "fieldType": "checkbox", - "description" : "dummy", + "description": "dummy", "tooltip": "test-short-description", "properties": { - "afs:layout" : { + "afs:layout": { "tooltipVisible": false }, - "fd:dor": { - "dorExclusion":false - }, - "fd:path":"/content/checkbox-without-fieldtype", + "fd:path": "/content/checkbox-without-fieldtype", "customProp": "customPropValue" }, - "enforceEnum":true, - "enum" : ["on", "off"], + "enforceEnum": true, + "enum": [ + "on", + "off" + ], "label": { "value": "def" }, - "dataRef":"a.b", + "dataRef": "a.b", "screenReaderText": "Custom screen reader text", - "constraintMessages":{ - "type":"incorrect type" + "constraintMessages": { + "type": "incorrect type" }, - "events" : { - "custom:setProperty" : ["$event.payload"] + "events": { + "custom:setProperty": [ + "$event.payload" + ] }, "type": "string", ":type": "core/fd/components/form/checkbox/v1/checkbox" diff --git a/bundles/af-core/src/test/resources/form/checkbox/exporter-checkbox.json b/bundles/af-core/src/test/resources/form/checkbox/exporter-checkbox.json index d8a5ae3189..0093c319ed 100644 --- a/bundles/af-core/src/test/resources/form/checkbox/exporter-checkbox.json +++ b/bundles/af-core/src/test/resources/form/checkbox/exporter-checkbox.json @@ -2,30 +2,32 @@ "id": "checkbox-42948c9a6c", "name": "abc", "fieldType": "checkbox", - "description" : "dummy", + "description": "dummy", "tooltip": "test-short-description", "properties": { - "afs:layout" : { + "afs:layout": { "tooltipVisible": false }, - "fd:dor": { - "dorExclusion":false - }, - "fd:path":"/content/checkbox", + "fd:path": "/content/checkbox", "customProp": "customPropValue" }, - "enforceEnum":true, - "enum" : ["on", "off"], + "enforceEnum": true, + "enum": [ + "on", + "off" + ], "label": { "value": "def" }, - "dataRef":"a.b", + "dataRef": "a.b", "screenReaderText": "Custom screen reader text", - "constraintMessages":{ - "type":"incorrect type" + "constraintMessages": { + "type": "incorrect type" }, - "events" : { - "custom:setProperty" : ["$event.payload"] + "events": { + "custom:setProperty": [ + "$event.payload" + ] }, "type": "string", ":type": "core/fd/components/form/checkbox/v1/checkbox" diff --git a/bundles/af-core/src/test/resources/form/checkbox/test-content.json b/bundles/af-core/src/test/resources/form/checkbox/test-content.json index 778507bcac..486d3a5eff 100644 --- a/bundles/af-core/src/test/resources/form/checkbox/test-content.json +++ b/bundles/af-core/src/test/resources/form/checkbox/test-content.json @@ -94,6 +94,16 @@ "jcr:primaryType": "nt:unstructured" } }, + "checkbox-enableUncheckedValueMissingFromJcr" : { + "jcr:primaryType": "nt:unstructured", + "sling:resourceType" : "core/fd/components/form/checkbox/v1/checkbox", + "name" : "abc", + "jcr:title" : "def", + "type" : "string", + "checkedValue": ".", + "enableUncheckedValue": true, + "fieldType": "checkbox" + }, "checkbox-without-fieldtype" : { "id": "checkbox-wo-fieldtype1", "jcr:primaryType": "nt:unstructured", diff --git a/bundles/af-core/src/test/resources/form/checkboxgroup/exporter-checkboxgroup-datalayer.json b/bundles/af-core/src/test/resources/form/checkboxgroup/exporter-checkboxgroup-datalayer.json index 986b3f2b6a..9020836776 100644 --- a/bundles/af-core/src/test/resources/form/checkboxgroup/exporter-checkboxgroup-datalayer.json +++ b/bundles/af-core/src/test/resources/form/checkboxgroup/exporter-checkboxgroup-datalayer.json @@ -24,9 +24,6 @@ "afs:layout": { "orientation": "horizontal" }, - "fd:dor": { - "dorExclusion": false - }, "fd:path": "/content/checkboxgroup-datalayer" }, "enum": [ diff --git a/bundles/af-core/src/test/resources/form/checkboxgroup/exporter-checkboxgroup-without-fieldtype.json b/bundles/af-core/src/test/resources/form/checkboxgroup/exporter-checkboxgroup-without-fieldtype.json index 31424794c5..ca7aff2f14 100644 --- a/bundles/af-core/src/test/resources/form/checkboxgroup/exporter-checkboxgroup-without-fieldtype.json +++ b/bundles/af-core/src/test/resources/form/checkboxgroup/exporter-checkboxgroup-without-fieldtype.json @@ -15,9 +15,6 @@ "afs:layout": { "orientation": "horizontal" }, - "fd:dor": { - "dorExclusion": false - }, "fd:path": "/content/checkboxgroup-without-fieldtype" }, "events": { @@ -30,4 +27,4 @@ 1 ], ":type": "forms-components-examples/components/form/checkboxgroup" -} \ No newline at end of file +} diff --git a/bundles/af-core/src/test/resources/form/checkboxgroup/exporter-checkboxgroup.json b/bundles/af-core/src/test/resources/form/checkboxgroup/exporter-checkboxgroup.json index 3c5aa00844..e363de699d 100644 --- a/bundles/af-core/src/test/resources/form/checkboxgroup/exporter-checkboxgroup.json +++ b/bundles/af-core/src/test/resources/form/checkboxgroup/exporter-checkboxgroup.json @@ -50,9 +50,6 @@ "tooltipVisible": false, "orientation": "horizontal" }, - "fd:dor": { - "dorExclusion": false - }, "fd:path": "/content/checkboxgroup" }, "enum": [ @@ -61,4 +58,4 @@ 2 ], ":type": "core/fd/components/form/checkboxgroup/v1/checkboxgroup" -} \ No newline at end of file +} diff --git a/bundles/af-core/src/test/resources/form/componentswithrule/test-content.json b/bundles/af-core/src/test/resources/form/componentswithrule/test-content.json index 25ae1b9144..b575520292 100644 --- a/bundles/af-core/src/test/resources/form/componentswithrule/test-content.json +++ b/bundles/af-core/src/test/resources/form/componentswithrule/test-content.json @@ -15,6 +15,21 @@ "sling:resourceType": "forms-components-examples/components/form/numberinput", "fieldType": "number-input" }, + "textinputWithPropertiesRule": { + "jcr:primaryType": "nt:unstructured", + "name": "name", + "sling:resourceType": "forms-components-examples/components/form/textinput", + "fieldType": "text-input", + "fd:rules": { + "jcr:primaryType": "nt:unstructured", + "visible": "some expression", + "properties": "merge($field.properties, {a: 1})", + "properties.employer": "some property expression", + "properties.employer.category": "some nested property expression", + "notARule": "should be dropped", + "propertiesFoo": "should be dropped" + } + }, "datepicker": { "jcr:primaryType": "nt:unstructured", "name": "datepicker1666939496905", diff --git a/bundles/af-core/src/test/resources/form/datepicker/exporter-datepicker-backwardcompatible.json b/bundles/af-core/src/test/resources/form/datepicker/exporter-datepicker-backwardcompatible.json index 1c51515690..0fa719ec39 100644 --- a/bundles/af-core/src/test/resources/form/datepicker/exporter-datepicker-backwardcompatible.json +++ b/bundles/af-core/src/test/resources/form/datepicker/exporter-datepicker-backwardcompatible.json @@ -13,13 +13,10 @@ ] }, "properties": { - "fd:dor": { - "dorExclusion": false - }, "fd:path": "/content/datepicker-backwardcompatible" }, "format": "date", "exclusiveMaximum": "2024-07-23", "exclusiveMinimum": "2024-07-10", ":type": "core/fd/components/form/datepicker/v1/datepicker" -} \ No newline at end of file +} diff --git a/bundles/af-core/src/test/resources/form/datepicker/exporter-datepicker-customized.json b/bundles/af-core/src/test/resources/form/datepicker/exporter-datepicker-customized.json index 416e1448a1..bf5c7b8409 100644 --- a/bundles/af-core/src/test/resources/form/datepicker/exporter-datepicker-customized.json +++ b/bundles/af-core/src/test/resources/form/datepicker/exporter-datepicker-customized.json @@ -14,7 +14,7 @@ }, "readOnly": true, "default": "2022-02-07", - "lang" : "it-IT", + "lang": "it-IT", "label": { "visible": true, "value": "def" @@ -29,9 +29,6 @@ "afs:layout": { "tooltipVisible": false }, - "fd:dor": { - "dorExclusion": false - }, "fd:path": "/content/datepicker-customized" }, "rules": { @@ -44,4 +41,4 @@ "minimum": "2022-02-04", "maximum": "2022-02-09", ":type": "core/fd/components/form/datepicker/v1/datepicker" -} \ No newline at end of file +} diff --git a/bundles/af-core/src/test/resources/form/datepicker/exporter-datepicker-datalayer.json b/bundles/af-core/src/test/resources/form/datepicker/exporter-datepicker-datalayer.json index 8287bdfbe9..7e8d0c1342 100644 --- a/bundles/af-core/src/test/resources/form/datepicker/exporter-datepicker-datalayer.json +++ b/bundles/af-core/src/test/resources/form/datepicker/exporter-datepicker-datalayer.json @@ -13,9 +13,6 @@ ] }, "properties": { - "fd:dor": { - "dorExclusion": false - }, "fd:path": "/content/datepicker-datalayer" }, "format": "date", @@ -28,4 +25,4 @@ "fieldType": "date-input" } } -} \ No newline at end of file +} diff --git a/bundles/af-core/src/test/resources/form/datepicker/exporter-datepicker-displayValueExpression.json b/bundles/af-core/src/test/resources/form/datepicker/exporter-datepicker-displayValueExpression.json index 7c9f1a1300..3baaf4cf89 100644 --- a/bundles/af-core/src/test/resources/form/datepicker/exporter-datepicker-displayValueExpression.json +++ b/bundles/af-core/src/test/resources/form/datepicker/exporter-datepicker-displayValueExpression.json @@ -13,9 +13,6 @@ ] }, "properties": { - "fd:dor": { - "dorExclusion": false - }, "fd:path": "/content/datepicker-displayValueExpression" }, "format": "date", diff --git a/bundles/af-core/src/test/resources/form/datepicker/exporter-datepicker-message.json b/bundles/af-core/src/test/resources/form/datepicker/exporter-datepicker-message.json index 365f460597..0ba1fac3c0 100644 --- a/bundles/af-core/src/test/resources/form/datepicker/exporter-datepicker-message.json +++ b/bundles/af-core/src/test/resources/form/datepicker/exporter-datepicker-message.json @@ -33,13 +33,10 @@ "afs:layout": { "tooltipVisible": false }, - "fd:dor": { - "dorExclusion": false - }, "fd:path": "/content/datepicker-message" }, "format": "date", "exclusiveMinimum": "2022-02-05", "exclusiveMaximum": "2022-02-08", ":type": "core/fd/components/form/datepicker/v1/datepicker" -} \ No newline at end of file +} diff --git a/bundles/af-core/src/test/resources/form/datepicker/exporter-datepicker-with-both-display-formats.json b/bundles/af-core/src/test/resources/form/datepicker/exporter-datepicker-with-both-display-formats.json index 9ac769d19b..01e75e08ef 100644 --- a/bundles/af-core/src/test/resources/form/datepicker/exporter-datepicker-with-both-display-formats.json +++ b/bundles/af-core/src/test/resources/form/datepicker/exporter-datepicker-with-both-display-formats.json @@ -13,11 +13,8 @@ ] }, "properties": { - "fd:dor": { - "dorExclusion": false - }, "fd:path": "/content/datepicker-with-both-display-formats" }, "format": "date", ":type": "core/fd/components/form/datepicker/v1/datepicker" -} \ No newline at end of file +} diff --git a/bundles/af-core/src/test/resources/form/datepicker/exporter-datepicker-with-custom-display-format.json b/bundles/af-core/src/test/resources/form/datepicker/exporter-datepicker-with-custom-display-format.json index 68b9e812a9..749ebe4d76 100644 --- a/bundles/af-core/src/test/resources/form/datepicker/exporter-datepicker-with-custom-display-format.json +++ b/bundles/af-core/src/test/resources/form/datepicker/exporter-datepicker-with-custom-display-format.json @@ -13,11 +13,8 @@ ] }, "properties": { - "fd:dor": { - "dorExclusion": false - }, "fd:path": "/content/datepicker-with-custom-display-format" }, "format": "date", ":type": "core/fd/components/form/datepicker/v1/datepicker" -} \ No newline at end of file +} diff --git a/bundles/af-core/src/test/resources/form/datepicker/exporter-datepicker-without-fieldtype.json b/bundles/af-core/src/test/resources/form/datepicker/exporter-datepicker-without-fieldtype.json index 4b6b81c48a..aa6a726f5b 100644 --- a/bundles/af-core/src/test/resources/form/datepicker/exporter-datepicker-without-fieldtype.json +++ b/bundles/af-core/src/test/resources/form/datepicker/exporter-datepicker-without-fieldtype.json @@ -12,11 +12,8 @@ ] }, "properties": { - "fd:dor": { - "dorExclusion": false - }, "fd:path": "/content/datepicker-without-fieldtype" }, "format": "date", ":type": "core/fd/components/form/datepicker/v1/datepicker" -} \ No newline at end of file +} diff --git a/bundles/af-core/src/test/resources/form/datepicker/exporter-datepicker.json b/bundles/af-core/src/test/resources/form/datepicker/exporter-datepicker.json index 564ee02162..a6e036138a 100644 --- a/bundles/af-core/src/test/resources/form/datepicker/exporter-datepicker.json +++ b/bundles/af-core/src/test/resources/form/datepicker/exporter-datepicker.json @@ -12,12 +12,9 @@ ] }, "properties": { - "fd:dor": { - "dorExclusion": false - }, "fd:path": "/content/datepicker", "customProp": "customPropValue" }, "format": "date", ":type": "core/fd/components/form/datepicker/v1/datepicker" -} \ No newline at end of file +} diff --git a/bundles/af-core/src/test/resources/form/datetime/exporter-datetime-backwardcompatible.json b/bundles/af-core/src/test/resources/form/datetime/exporter-datetime-backwardcompatible.json index 5d5ba5de42..0b1bc52aba 100644 --- a/bundles/af-core/src/test/resources/form/datetime/exporter-datetime-backwardcompatible.json +++ b/bundles/af-core/src/test/resources/form/datetime/exporter-datetime-backwardcompatible.json @@ -13,9 +13,6 @@ ] }, "properties": { - "fd:dor": { - "dorExclusion": false - }, "fd:path": "/content/datetime-backwardcompatible" }, "format": "date-time", diff --git a/bundles/af-core/src/test/resources/form/datetime/exporter-datetime-customized.json b/bundles/af-core/src/test/resources/form/datetime/exporter-datetime-customized.json index 3f42d2fe84..e9a9c6da04 100644 --- a/bundles/af-core/src/test/resources/form/datetime/exporter-datetime-customized.json +++ b/bundles/af-core/src/test/resources/form/datetime/exporter-datetime-customized.json @@ -27,9 +27,6 @@ "afs:layout": { "tooltipVisible": false }, - "fd:dor": { - "dorExclusion": false - }, "fd:path": "/content/datetime-customized" }, "rules": { diff --git a/bundles/af-core/src/test/resources/form/datetime/exporter-datetime-datalayer.json b/bundles/af-core/src/test/resources/form/datetime/exporter-datetime-datalayer.json index 7129959cca..1f15b8f895 100644 --- a/bundles/af-core/src/test/resources/form/datetime/exporter-datetime-datalayer.json +++ b/bundles/af-core/src/test/resources/form/datetime/exporter-datetime-datalayer.json @@ -13,9 +13,6 @@ ] }, "properties": { - "fd:dor": { - "dorExclusion": false - }, "fd:path": "/content/dateime-datalayer" }, "format": "date", @@ -28,4 +25,4 @@ "fieldType": "date-time" } } -} \ No newline at end of file +} diff --git a/bundles/af-core/src/test/resources/form/datetime/exporter-datetime-displayValueExpression.json b/bundles/af-core/src/test/resources/form/datetime/exporter-datetime-displayValueExpression.json index e96eb58f6e..69ab7d5ff3 100644 --- a/bundles/af-core/src/test/resources/form/datetime/exporter-datetime-displayValueExpression.json +++ b/bundles/af-core/src/test/resources/form/datetime/exporter-datetime-displayValueExpression.json @@ -13,9 +13,6 @@ ] }, "properties": { - "fd:dor": { - "dorExclusion": false - }, "fd:path": "/content/dateime-displayValueExpression" }, "format": "date", diff --git a/bundles/af-core/src/test/resources/form/datetime/exporter-datetime-message.json b/bundles/af-core/src/test/resources/form/datetime/exporter-datetime-message.json index d63fb894da..e41958909e 100644 --- a/bundles/af-core/src/test/resources/form/datetime/exporter-datetime-message.json +++ b/bundles/af-core/src/test/resources/form/datetime/exporter-datetime-message.json @@ -23,9 +23,6 @@ "afs:layout": { "tooltipVisible": false }, - "fd:dor": { - "dorExclusion": false - }, "fd:path": "/content/datetime-message" }, "rules": { diff --git a/bundles/af-core/src/test/resources/form/datetime/exporter-datetime-without-fieldtype.json b/bundles/af-core/src/test/resources/form/datetime/exporter-datetime-without-fieldtype.json index cbcab03df7..20fa4e9f4c 100644 --- a/bundles/af-core/src/test/resources/form/datetime/exporter-datetime-without-fieldtype.json +++ b/bundles/af-core/src/test/resources/form/datetime/exporter-datetime-without-fieldtype.json @@ -12,9 +12,6 @@ ] }, "properties": { - "fd:dor": { - "dorExclusion": false - }, "fd:path": "/content/datetime-without-fieldtype" }, "format": "date-time", diff --git a/bundles/af-core/src/test/resources/form/datetime/exporter-datetime.json b/bundles/af-core/src/test/resources/form/datetime/exporter-datetime.json index be5a6d7c94..349556a637 100644 --- a/bundles/af-core/src/test/resources/form/datetime/exporter-datetime.json +++ b/bundles/af-core/src/test/resources/form/datetime/exporter-datetime.json @@ -14,9 +14,6 @@ }, "properties": { "customProp": "customPropValue", - "fd:dor": { - "dorExclusion": false - }, "fd:path": "/content/datetime" }, ":type": "core/fd/components/form/datetime/v1/datetime" diff --git a/bundles/af-core/src/test/resources/form/dropdown/exporter-dropdown-1.json b/bundles/af-core/src/test/resources/form/dropdown/exporter-dropdown-1.json index c55f047346..79ea4a599a 100644 --- a/bundles/af-core/src/test/resources/form/dropdown/exporter-dropdown-1.json +++ b/bundles/af-core/src/test/resources/form/dropdown/exporter-dropdown-1.json @@ -33,9 +33,6 @@ "afs:layout": { "tooltipVisible": false }, - "fd:dor": { - "dorExclusion": false - }, "fd:path": "/content/dropdown-1" }, "enum": [ @@ -44,4 +41,4 @@ 2 ], ":type": "core/fd/components/form/dropdown/v1/dropdown" -} \ No newline at end of file +} diff --git a/bundles/af-core/src/test/resources/form/dropdown/exporter-dropdown-datalayer.json b/bundles/af-core/src/test/resources/form/dropdown/exporter-dropdown-datalayer.json index 4b7d3b72e6..aab98beac4 100644 --- a/bundles/af-core/src/test/resources/form/dropdown/exporter-dropdown-datalayer.json +++ b/bundles/af-core/src/test/resources/form/dropdown/exporter-dropdown-datalayer.json @@ -21,9 +21,6 @@ ] }, "properties": { - "fd:dor": { - "dorExclusion": false - }, "fd:path": "/content/dropdown-datalayer" }, "enum": [ @@ -39,4 +36,4 @@ "fieldType": "drop-down" } } -} \ No newline at end of file +} diff --git a/bundles/af-core/src/test/resources/form/dropdown/exporter-dropdown-without-fieldtype.json b/bundles/af-core/src/test/resources/form/dropdown/exporter-dropdown-without-fieldtype.json index 93e51cdf4b..2699d98515 100644 --- a/bundles/af-core/src/test/resources/form/dropdown/exporter-dropdown-without-fieldtype.json +++ b/bundles/af-core/src/test/resources/form/dropdown/exporter-dropdown-without-fieldtype.json @@ -4,8 +4,16 @@ "name": "abc", "type": "string", "enforceEnum": true, - "enumNames": ["m", "f", "o"], - "enum": ["0", "1", "2"], + "enumNames": [ + "m", + "f", + "o" + ], + "enum": [ + "0", + "1", + "2" + ], "label": { "value": "def" }, @@ -15,10 +23,7 @@ ] }, "properties": { - "fd:dor": { - "dorExclusion": false - }, "fd:path": "/content/dropdown-without-fieldtype" }, ":type": "core/fd/components/form/dropdown/v1/dropdown" -} \ No newline at end of file +} diff --git a/bundles/af-core/src/test/resources/form/dropdown/exporter-dropdown.json b/bundles/af-core/src/test/resources/form/dropdown/exporter-dropdown.json index 324e93217e..b248b54937 100644 --- a/bundles/af-core/src/test/resources/form/dropdown/exporter-dropdown.json +++ b/bundles/af-core/src/test/resources/form/dropdown/exporter-dropdown.json @@ -4,8 +4,16 @@ "name": "abc", "type": "string", "enforceEnum": true, - "enumNames": ["m", "f", "o"], - "enum": ["0", "1", "2"], + "enumNames": [ + "m", + "f", + "o" + ], + "enum": [ + "0", + "1", + "2" + ], "label": { "value": "def" }, @@ -15,11 +23,8 @@ ] }, "properties": { - "fd:dor": { - "dorExclusion": false - }, "fd:path": "/content/dropdown", "customProp": "customPropValue" }, ":type": "core/fd/components/form/dropdown/v1/dropdown" -} \ No newline at end of file +} diff --git a/bundles/af-core/src/test/resources/form/dropdown/exporter-multiselect-dropdown.json b/bundles/af-core/src/test/resources/form/dropdown/exporter-multiselect-dropdown.json index c5909273f4..8a1ac15862 100644 --- a/bundles/af-core/src/test/resources/form/dropdown/exporter-multiselect-dropdown.json +++ b/bundles/af-core/src/test/resources/form/dropdown/exporter-multiselect-dropdown.json @@ -37,9 +37,6 @@ "afs:layout": { "tooltipVisible": false }, - "fd:dor": { - "dorExclusion": false - }, "fd:path": "/content/multiselect-dropdown" }, "enum": [ @@ -48,4 +45,4 @@ 2 ], ":type": "core/fd/components/form/dropdown/v1/dropdown" -} \ No newline at end of file +} diff --git a/bundles/af-core/src/test/resources/form/fileinput/exporter-fileinput-customized.json b/bundles/af-core/src/test/resources/form/fileinput/exporter-fileinput-customized.json index 33b58e1950..fa7ffad83e 100644 --- a/bundles/af-core/src/test/resources/form/fileinput/exporter-fileinput-customized.json +++ b/bundles/af-core/src/test/resources/form/fileinput/exporter-fileinput-customized.json @@ -2,7 +2,7 @@ "id": "fileinput-87a9efb7f0", "fieldType": "file-input", "name": "abc", - "visible" : false, + "visible": false, "enabled": true, "readOnly": false, "required": true, @@ -10,18 +10,20 @@ "value": "def", "visible": true }, - "screenReaderText":"Custom screen reader text", - "constraintMessages":{"type":"incorrect type"}, + "screenReaderText": "Custom screen reader text", + "constraintMessages": { + "type": "incorrect type" + }, "type": "file", - "description" : "dummy", - "maxFileSize" : "4MB", + "description": "dummy", + "maxFileSize": "4MB", "tooltip": "test-short-description", "accept": [ "audio/*", "video/*", "image/*" ], - "dataRef":"a.b", + "dataRef": "a.b", "events": { "custom:setProperty": [ "$event.payload" @@ -31,11 +33,8 @@ "afs:layout": { "tooltipVisible": false }, - "fd:dor": { - "dorExclusion": false - }, "fd:buttonText": "Attach", "fd:path": "/content/fileinput-customized" }, ":type": "core/fd/components/form/fileinput/v1/fileinput" -} \ No newline at end of file +} diff --git a/bundles/af-core/src/test/resources/form/fileinput/exporter-fileinput-datalayer.json b/bundles/af-core/src/test/resources/form/fileinput/exporter-fileinput-datalayer.json index d3b8fa0e70..2112003c2f 100644 --- a/bundles/af-core/src/test/resources/form/fileinput/exporter-fileinput-datalayer.json +++ b/bundles/af-core/src/test/resources/form/fileinput/exporter-fileinput-datalayer.json @@ -20,9 +20,6 @@ ] }, "properties": { - "fd:dor": { - "dorExclusion": false - }, "fd:buttonText": "Attach", "fd:path": "/content/fileinput-datalayer" }, @@ -35,4 +32,4 @@ "fieldType": "file-input" } } -} \ No newline at end of file +} diff --git a/bundles/af-core/src/test/resources/form/fileinput/exporter-fileinput-without-fieldtype.json b/bundles/af-core/src/test/resources/form/fileinput/exporter-fileinput-without-fieldtype.json index ea3e9d5c36..016d57aa53 100644 --- a/bundles/af-core/src/test/resources/form/fileinput/exporter-fileinput-without-fieldtype.json +++ b/bundles/af-core/src/test/resources/form/fileinput/exporter-fileinput-without-fieldtype.json @@ -19,11 +19,8 @@ ] }, "properties": { - "fd:dor": { - "dorExclusion": false - }, "fd:path": "/content/fileinput-without-fieldtype", "fd:buttonText": "Attach" }, ":type": "core/fd/components/form/fileinput/v1/fileinput" -} \ No newline at end of file +} diff --git a/bundles/af-core/src/test/resources/form/fileinput/exporter-fileinput.json b/bundles/af-core/src/test/resources/form/fileinput/exporter-fileinput.json index f1e380e62b..c17df370f7 100644 --- a/bundles/af-core/src/test/resources/form/fileinput/exporter-fileinput.json +++ b/bundles/af-core/src/test/resources/form/fileinput/exporter-fileinput.json @@ -19,12 +19,9 @@ ] }, "properties": { - "fd:dor": { - "dorExclusion": false - }, "fd:path": "/content/fileinput", "fd:buttonText": "Attach", "customProp": "customPropValue" }, ":type": "core/fd/components/form/fileinput/v1/fileinput" -} \ No newline at end of file +} diff --git a/bundles/af-core/src/test/resources/form/fileinput/exporter-multiselect-fileinput-withNoType.json b/bundles/af-core/src/test/resources/form/fileinput/exporter-multiselect-fileinput-withNoType.json index c091c433d5..40b237d164 100644 --- a/bundles/af-core/src/test/resources/form/fileinput/exporter-multiselect-fileinput-withNoType.json +++ b/bundles/af-core/src/test/resources/form/fileinput/exporter-multiselect-fileinput-withNoType.json @@ -1,40 +1,36 @@ { - "id": "fileinput-4573ac70ac", - "dataRef": "a.b", - "fieldType": "file-input", - "name": "abc", - "visible": false, - "description": "dummy", - "tooltip": "test-short-description", - "minItems": 1, - "maxItems": 2, - "constraintMessages": { - "type": "incorrect type" + "id": "fileinput-4573ac70ac", + "dataRef": "a.b", + "fieldType": "file-input", + "name": "abc", + "visible": false, + "description": "dummy", + "tooltip": "test-short-description", + "minItems": 1, + "maxItems": 2, + "constraintMessages": { + "type": "incorrect type" + }, + "maxFileSize": "4096MB", + "accept": [ + "text/*", + "application/pdf" + ], + "screenReaderText": "Custom screen reader text", + "label": { + "value": "def" + }, + "events": { + "custom:setProperty": [ + "$event.payload" + ] + }, + "properties": { + "afs:layout": { + "tooltipVisible": false }, - "maxFileSize": "4096MB", - "accept": [ - "text/*", - "application/pdf" - ], - "screenReaderText": "Custom screen reader text", - "label": { - "value": "def" - }, - "events": { - "custom:setProperty": [ - "$event.payload" - ] - }, - "properties": { - "afs:layout": { - "tooltipVisible": false - }, - "fd:dor": { - "dorExclusion": false - }, - "fd:path": "/content/multiselect-fileinput", - "fd:buttonText": "Attach" - }, - ":type": "core/fd/components/form/fileinput/v1/fileinput" - } - \ No newline at end of file + "fd:path": "/content/multiselect-fileinput", + "fd:buttonText": "Attach" + }, + ":type": "core/fd/components/form/fileinput/v1/fileinput" +} diff --git a/bundles/af-core/src/test/resources/form/fileinput/exporter-multiselect-fileinput.json b/bundles/af-core/src/test/resources/form/fileinput/exporter-multiselect-fileinput.json index db004508f1..bd9800760f 100644 --- a/bundles/af-core/src/test/resources/form/fileinput/exporter-multiselect-fileinput.json +++ b/bundles/af-core/src/test/resources/form/fileinput/exporter-multiselect-fileinput.json @@ -30,9 +30,6 @@ "afs:layout": { "tooltipVisible": false }, - "fd:dor": { - "dorExclusion": false - }, "fd:buttonText": "Attach", "fd:path": "/content/multiselect-fileinput" }, diff --git a/bundles/af-core/src/test/resources/form/fileinputv2/exporter-fileinput-customized.json b/bundles/af-core/src/test/resources/form/fileinputv2/exporter-fileinput-customized.json index 7a5d286612..0354596a62 100644 --- a/bundles/af-core/src/test/resources/form/fileinputv2/exporter-fileinput-customized.json +++ b/bundles/af-core/src/test/resources/form/fileinputv2/exporter-fileinput-customized.json @@ -2,7 +2,7 @@ "id": "fileinput-87a9efb7f0", "fieldType": "file-input", "name": "abc", - "visible" : false, + "visible": false, "enabled": true, "readOnly": false, "required": true, @@ -10,18 +10,20 @@ "value": "def", "visible": true }, - "screenReaderText":"Custom screen reader text", - "constraintMessages":{"type":"incorrect type"}, + "screenReaderText": "Custom screen reader text", + "constraintMessages": { + "type": "incorrect type" + }, "type": "file", - "description" : "dummy", - "maxFileSize" : "4MB", + "description": "dummy", + "maxFileSize": "4MB", "tooltip": "test-short-description", "accept": [ "audio/*", "video/*", "image/*" ], - "dataRef":"a.b", + "dataRef": "a.b", "events": { "custom:setProperty": [ "$event.payload" @@ -31,12 +33,9 @@ "afs:layout": { "tooltipVisible": false }, - "fd:dor": { - "dorExclusion": false - }, "fd:path": "/content/fileinput-customized", - "dragDropText":"Drag and Drop To Upload", + "dragDropText": "Drag and Drop To Upload", "fd:buttonText": "Attach" }, ":type": "core/fd/components/form/fileinput/v2/fileinput" -} \ No newline at end of file +} diff --git a/bundles/af-core/src/test/resources/form/fileinputv2/exporter-fileinput-datalayer.json b/bundles/af-core/src/test/resources/form/fileinputv2/exporter-fileinput-datalayer.json index adf5362e6a..b1a42abd8f 100644 --- a/bundles/af-core/src/test/resources/form/fileinputv2/exporter-fileinput-datalayer.json +++ b/bundles/af-core/src/test/resources/form/fileinputv2/exporter-fileinput-datalayer.json @@ -20,11 +20,8 @@ ] }, "properties": { - "fd:dor": { - "dorExclusion": false - }, "fd:path": "/content/fileinput-datalayer", - "dragDropText":"Drag and Drop To Upload", + "dragDropText": "Drag and Drop To Upload", "fd:buttonText": "Attach" }, ":type": "core/fd/components/form/fileinput/v2/fileinput", @@ -36,4 +33,4 @@ "fieldType": "file-input" } } -} \ No newline at end of file +} diff --git a/bundles/af-core/src/test/resources/form/fileinputv2/exporter-fileinput.json b/bundles/af-core/src/test/resources/form/fileinputv2/exporter-fileinput.json index 0b77fce63e..644cdce80a 100644 --- a/bundles/af-core/src/test/resources/form/fileinputv2/exporter-fileinput.json +++ b/bundles/af-core/src/test/resources/form/fileinputv2/exporter-fileinput.json @@ -19,13 +19,10 @@ ] }, "properties": { - "fd:dor": { - "dorExclusion": false - }, "fd:path": "/content/fileinput", - "dragDropText":"Drag and Drop To Upload", + "dragDropText": "Drag and Drop To Upload", "customProp": "customPropValue", "fd:buttonText": "Attach" }, ":type": "core/fd/components/form/fileinput/v2/fileinput" -} \ No newline at end of file +} diff --git a/bundles/af-core/src/test/resources/form/fileinputv2/exporter-multiselect-fileinput-withNoType.json b/bundles/af-core/src/test/resources/form/fileinputv2/exporter-multiselect-fileinput-withNoType.json index eb8feec77b..92cd693ee6 100644 --- a/bundles/af-core/src/test/resources/form/fileinputv2/exporter-multiselect-fileinput-withNoType.json +++ b/bundles/af-core/src/test/resources/form/fileinputv2/exporter-multiselect-fileinput-withNoType.json @@ -1,41 +1,37 @@ { - "id": "fileinput-4573ac70ac", - "dataRef": "a.b", - "fieldType": "file-input", - "name": "abc", - "visible": false, - "description": "dummy", - "tooltip": "test-short-description", - "minItems": 1, - "maxItems": 2, - "constraintMessages": { - "type": "incorrect type" + "id": "fileinput-4573ac70ac", + "dataRef": "a.b", + "fieldType": "file-input", + "name": "abc", + "visible": false, + "description": "dummy", + "tooltip": "test-short-description", + "minItems": 1, + "maxItems": 2, + "constraintMessages": { + "type": "incorrect type" + }, + "maxFileSize": "4096MB", + "accept": [ + "text/*", + "application/pdf" + ], + "screenReaderText": "Custom screen reader text", + "label": { + "value": "def" + }, + "events": { + "custom:setProperty": [ + "$event.payload" + ] + }, + "properties": { + "afs:layout": { + "tooltipVisible": false }, - "maxFileSize": "4096MB", - "accept": [ - "text/*", - "application/pdf" - ], - "screenReaderText": "Custom screen reader text", - "label": { - "value": "def" - }, - "events": { - "custom:setProperty": [ - "$event.payload" - ] - }, - "properties": { - "afs:layout": { - "tooltipVisible": false - }, - "fd:dor": { - "dorExclusion": false - }, - "fd:path": "/content/multiselect-fileinput", - "dragDropText":"Drag and Drop To Upload", - "fd:buttonText": "Attach" - }, - ":type": "core/fd/components/form/fileinput/v2/fileinput" - } - \ No newline at end of file + "fd:path": "/content/multiselect-fileinput", + "dragDropText": "Drag and Drop To Upload", + "fd:buttonText": "Attach" + }, + ":type": "core/fd/components/form/fileinput/v2/fileinput" +} diff --git a/bundles/af-core/src/test/resources/form/fileinputv2/exporter-multiselect-fileinput.json b/bundles/af-core/src/test/resources/form/fileinputv2/exporter-multiselect-fileinput.json index ba6f80cbc3..5cc991b982 100644 --- a/bundles/af-core/src/test/resources/form/fileinputv2/exporter-multiselect-fileinput.json +++ b/bundles/af-core/src/test/resources/form/fileinputv2/exporter-multiselect-fileinput.json @@ -30,11 +30,8 @@ "afs:layout": { "tooltipVisible": false }, - "fd:dor": { - "dorExclusion": false - }, "fd:path": "/content/multiselect-fileinput", - "dragDropText":"Drag and Drop To Upload", + "dragDropText": "Drag and Drop To Upload", "fd:buttonText": "Attach" }, ":type": "core/fd/components/form/fileinput/v2/fileinput" diff --git a/bundles/af-core/src/test/resources/form/fileinputv3/exporter-fileinput-customized.json b/bundles/af-core/src/test/resources/form/fileinputv3/exporter-fileinput-customized.json index 8da8e30cdf..9ccef68b84 100644 --- a/bundles/af-core/src/test/resources/form/fileinputv3/exporter-fileinput-customized.json +++ b/bundles/af-core/src/test/resources/form/fileinputv3/exporter-fileinput-customized.json @@ -2,7 +2,7 @@ "id": "fileinput-87a9efb7f0", "fieldType": "file-input", "name": "abc", - "visible" : false, + "visible": false, "enabled": true, "readOnly": false, "required": true, @@ -10,18 +10,22 @@ "value": "def", "visible": true }, - "screenReaderText":"Custom screen reader text", - "constraintMessages":{"type":"incorrect type"}, + "screenReaderText": "Custom screen reader text", + "constraintMessages": { + "type": "incorrect type" + }, "type": "file", - "description" : "dummy", - "maxFileSize" : "4MB", + "description": "dummy", + "maxFileSize": "4MB", "tooltip": "test-short-description", "accept": [ "audio/*", "video/*", - "image/*" + "image/*", + ".jpg", + ".png" ], - "dataRef":"a.b", + "dataRef": "a.b", "events": { "custom:setProperty": [ "$event.payload" @@ -31,12 +35,9 @@ "afs:layout": { "tooltipVisible": false }, - "fd:dor": { - "dorExclusion": false - }, "fd:path": "/content/fileinput-customized", - "fd:dragDropText":"Drag and Drop To Upload", + "fd:dragDropText": "Drag and Drop To Upload", "fd:buttonText": "Attach" }, ":type": "core/fd/components/form/fileinput/v3/fileinput" -} \ No newline at end of file +} diff --git a/bundles/af-core/src/test/resources/form/fileinputv3/exporter-fileinput-datalayer.json b/bundles/af-core/src/test/resources/form/fileinputv3/exporter-fileinput-datalayer.json index a1c8ea2f38..1fd4402ac2 100644 --- a/bundles/af-core/src/test/resources/form/fileinputv3/exporter-fileinput-datalayer.json +++ b/bundles/af-core/src/test/resources/form/fileinputv3/exporter-fileinput-datalayer.json @@ -20,11 +20,8 @@ ] }, "properties": { - "fd:dor": { - "dorExclusion": false - }, "fd:path": "/content/fileinput-datalayer", - "fd:dragDropText":"Drag and Drop To Upload", + "fd:dragDropText": "Drag and Drop To Upload", "fd:buttonText": "Attach" }, ":type": "core/fd/components/form/fileinput/v3/fileinput", @@ -36,4 +33,4 @@ "fieldType": "file-input" } } -} \ No newline at end of file +} diff --git a/bundles/af-core/src/test/resources/form/fileinputv3/exporter-fileinput.json b/bundles/af-core/src/test/resources/form/fileinputv3/exporter-fileinput.json index 6fd2e14572..8a8fb4e49e 100644 --- a/bundles/af-core/src/test/resources/form/fileinputv3/exporter-fileinput.json +++ b/bundles/af-core/src/test/resources/form/fileinputv3/exporter-fileinput.json @@ -19,13 +19,10 @@ ] }, "properties": { - "fd:dor": { - "dorExclusion": false - }, "fd:path": "/content/fileinput", "fd:dragDropText": "Drag and Drop To Upload", "customProp": "customPropValue", "fd:buttonText": "Attach" }, ":type": "core/fd/components/form/fileinput/v3/fileinput" -} \ No newline at end of file +} diff --git a/bundles/af-core/src/test/resources/form/fileinputv3/exporter-multiselect-fileinput-withNoType.json b/bundles/af-core/src/test/resources/form/fileinputv3/exporter-multiselect-fileinput-withNoType.json index f97aa8a8da..66143b3447 100644 --- a/bundles/af-core/src/test/resources/form/fileinputv3/exporter-multiselect-fileinput-withNoType.json +++ b/bundles/af-core/src/test/resources/form/fileinputv3/exporter-multiselect-fileinput-withNoType.json @@ -1,41 +1,37 @@ { - "id": "fileinput-4573ac70ac", - "dataRef": "a.b", - "fieldType": "file-input", - "name": "abc", - "visible": false, - "description": "dummy", - "tooltip": "test-short-description", - "minItems": 1, - "maxItems": 2, - "constraintMessages": { - "type": "incorrect type" + "id": "fileinput-4573ac70ac", + "dataRef": "a.b", + "fieldType": "file-input", + "name": "abc", + "visible": false, + "description": "dummy", + "tooltip": "test-short-description", + "minItems": 1, + "maxItems": 2, + "constraintMessages": { + "type": "incorrect type" + }, + "maxFileSize": "4096MB", + "accept": [ + "text/*", + "application/pdf" + ], + "screenReaderText": "Custom screen reader text", + "label": { + "value": "def" + }, + "events": { + "custom:setProperty": [ + "$event.payload" + ] + }, + "properties": { + "afs:layout": { + "tooltipVisible": false }, - "maxFileSize": "4096MB", - "accept": [ - "text/*", - "application/pdf" - ], - "screenReaderText": "Custom screen reader text", - "label": { - "value": "def" - }, - "events": { - "custom:setProperty": [ - "$event.payload" - ] - }, - "properties": { - "afs:layout": { - "tooltipVisible": false - }, - "fd:dor": { - "dorExclusion": false - }, - "fd:path": "/content/multiselect-fileinput", - "fd:dragDropText":"Drag and Drop To Upload", - "fd:buttonText": "Attach" - }, - ":type": "core/fd/components/form/fileinput/v3/fileinput" - } - \ No newline at end of file + "fd:path": "/content/multiselect-fileinput", + "fd:dragDropText": "Drag and Drop To Upload", + "fd:buttonText": "Attach" + }, + ":type": "core/fd/components/form/fileinput/v3/fileinput" +} diff --git a/bundles/af-core/src/test/resources/form/fileinputv3/exporter-multiselect-fileinput.json b/bundles/af-core/src/test/resources/form/fileinputv3/exporter-multiselect-fileinput.json index c7eb9ead90..820aee633f 100644 --- a/bundles/af-core/src/test/resources/form/fileinputv3/exporter-multiselect-fileinput.json +++ b/bundles/af-core/src/test/resources/form/fileinputv3/exporter-multiselect-fileinput.json @@ -30,11 +30,8 @@ "afs:layout": { "tooltipVisible": false }, - "fd:dor": { - "dorExclusion": false - }, "fd:path": "/content/multiselect-fileinput", - "fd:dragDropText":"Drag and Drop To Upload", + "fd:dragDropText": "Drag and Drop To Upload", "fd:buttonText": "Attach" }, ":type": "core/fd/components/form/fileinput/v3/fileinput" diff --git a/bundles/af-core/src/test/resources/form/fileinputv3/test-content.json b/bundles/af-core/src/test/resources/form/fileinputv3/test-content.json index cf986cda6e..79bbc6b61f 100644 --- a/bundles/af-core/src/test/resources/form/fileinputv3/test-content.json +++ b/bundles/af-core/src/test/resources/form/fileinputv3/test-content.json @@ -31,7 +31,11 @@ "custom" : "Custom screen reader text", "tooltip": "test-short-description", "maxFileSize" : "4", - "accept" : ["audio/*", "video/*", "image/*"] + "accept" : ["audio/*", "video/*", "image/*"], + "fd:acceptExtensions": [ + "jpg", + "png" + ] }, "multiselect-fileinput" : { "jcr:primaryType": "nt:unstructured", diff --git a/bundles/af-core/src/test/resources/form/formcontainer/datasource/test-content.json b/bundles/af-core/src/test/resources/form/formcontainer/datasource/test-content.json index 0bdf447db6..d50e925c0b 100644 --- a/bundles/af-core/src/test/resources/form/formcontainer/datasource/test-content.json +++ b/bundles/af-core/src/test/resources/form/formcontainer/datasource/test-content.json @@ -8,6 +8,13 @@ "guideDataModel": "basic" } }, + "prefilltypedatasource": { + "jcr:primaryType" : "nt:unstructured", + "datasource" : { + "sling:resourceType": "core/fd/components/form/container/v1/datasource", + "type": "prefillServiceProvider" + } + }, "form" : { "jcr:primaryType": "nt:unstructured", "action" : { diff --git a/bundles/af-core/src/test/resources/form/formcontainer/exporter-formContainer-cc-form-rest-submission.json b/bundles/af-core/src/test/resources/form/formcontainer/exporter-formContainer-cc-form-rest-submission.json index 8afb933194..fc1dc96df2 100644 --- a/bundles/af-core/src/test/resources/form/formcontainer/exporter-formContainer-cc-form-rest-submission.json +++ b/bundles/af-core/src/test/resources/form/formcontainer/exporter-formContainer-cc-form-rest-submission.json @@ -1,57 +1,54 @@ { - "id": "L2NvbnRlbnQvZm9ybXMvYWYvZGVtbw==", - "fieldType": "form", - "lang": "en", - "title": "uesprintdemo", - "properties": { - "themeRef": "", - "fd:path": "/content/forms/af/demo/jcr:content/formContainer-cc-form-rest-submission", - "fd:schemaType": "BASIC", - "fd:isHamburgerMenuEnabled": false, - "fd:roleAttribute": null, - "fd:formDataEnabled": false, - "fd:customFunctionsUrl": "/adobe/forms/af/customfunctions/L2NvbnRlbnQvZm9ybXMvYWYvZGVtbw==", - "fd:dataUrl": "/adobe/forms/af/data/L2NvbnRlbnQvZm9ybXMvYWYvZGVtbw==" - }, - "action": "/adobe/forms/af/submit/L2NvbnRlbnQvZm9ybXMvYWYvZGVtbw==", - "events": { + "id": "L2NvbnRlbnQvZm9ybXMvYWYvZGVtbw==", + "fieldType": "form", + "lang": "en", + "title": "uesprintdemo", + "properties": { + "themeRef": "", + "fd:path": "/content/forms/af/demo/jcr:content/formContainer-cc-form-rest-submission", + "fd:schemaType": "BASIC", + "fd:isHamburgerMenuEnabled": false, + "fd:roleAttribute": null, + "fd:formDataEnabled": false, + "fd:customFunctionsUrl": "/adobe/forms/af/customfunctions/L2NvbnRlbnQvZm9ybXMvYWYvZGVtbw==", + "fd:dataUrl": "/adobe/forms/af/data/L2NvbnRlbnQvZm9ybXMvYWYvZGVtbw==" + }, + "action": "/adobe/forms/af/submit/L2NvbnRlbnQvZm9ybXMvYWYvZGVtbw==", + "events": { + "custom:setProperty": [ + "$event.payload" + ] + }, + ":itemsOrder": [ + "textinput" + ], + ":items": { + "textinput": { + "id": "textinput-037dbdee3a", + "fieldType": "text-input", + "name": "abc", + "visible": false, + "description": "dummy", + "type": "string", + "label": { + "value": "def", + "visible": true + }, + "properties": { + "fd:path": "/content/forms/af/demo/jcr:content/formContainer-cc-form-rest-submission/textinput" + }, + "events": { "custom:setProperty": [ - "$event.payload" + "$event.payload" ] - }, - ":itemsOrder": [ - "textinput" - ], - ":items": { - "textinput": { - "id": "textinput-037dbdee3a", - "fieldType": "text-input", - "name": "abc", - "visible": false, - "description": "dummy", - "type": "string", - "label": { - "value": "def", - "visible": true - }, - "properties": { - "fd:dor": { - "dorExclusion": false - }, - "fd:path": "/content/forms/af/demo/jcr:content/formContainer-cc-form-rest-submission/textinput" - }, - "events": { - "custom:setProperty": [ - "$event.payload" - ] - }, - ":type": "core/fd/components/form/textinput/v1/textinput" - } - }, - "metadata": { - "grammar": "json-formula-1.0.0", - "version": "1.0.0" - }, - "adaptiveform": "0.15.2", - ":type": "fd/components/form/v1/form" -} \ No newline at end of file + }, + ":type": "core/fd/components/form/textinput/v1/textinput" + } + }, + "metadata": { + "grammar": "json-formula-1.0.0", + "version": "1.0.0" + }, + "adaptiveform": "0.15.2", + ":type": "fd/components/form/v1/form" +} diff --git a/bundles/af-core/src/test/resources/form/formcontainer/exporter-formContainer-cc-form-spreadsheet-submission.json b/bundles/af-core/src/test/resources/form/formcontainer/exporter-formContainer-cc-form-spreadsheet-submission.json index 8ebe3413d8..fd37da301e 100644 --- a/bundles/af-core/src/test/resources/form/formcontainer/exporter-formContainer-cc-form-spreadsheet-submission.json +++ b/bundles/af-core/src/test/resources/form/formcontainer/exporter-formContainer-cc-form-spreadsheet-submission.json @@ -1,57 +1,54 @@ { - "id": "L2NvbnRlbnQvZm9ybXMvYWYvZGVtbw==", - "fieldType": "form", - "lang": "en", - "title": "uesprintdemo", - "properties": { - "themeRef": "", - "fd:path": "/content/forms/af/demo/jcr:content/formContainer-cc-form-spreadsheet-submission", - "fd:schemaType": "BASIC", - "fd:isHamburgerMenuEnabled": false, - "fd:roleAttribute": null, - "fd:formDataEnabled": false, - "fd:customFunctionsUrl": "/adobe/forms/af/customfunctions/L2NvbnRlbnQvZm9ybXMvYWYvZGVtbw==", - "fd:dataUrl": "/adobe/forms/af/data/L2NvbnRlbnQvZm9ybXMvYWYvZGVtbw==" - }, - "action": "https://forms.adobe.com/adobe/forms/af/submit/L2NvbnRlbnQvZm9ybXMvYWYvZGVtby9qY3I6Y29udGVudC9mb3JtQ29udGFpbmVyLWNjLWZvcm0tc3ByZWFkc2hlZXQtc3VibWlzc2lvbi5tb2RlbC5qc29u", - "events": { + "id": "L2NvbnRlbnQvZm9ybXMvYWYvZGVtbw==", + "fieldType": "form", + "lang": "en", + "title": "uesprintdemo", + "properties": { + "themeRef": "", + "fd:path": "/content/forms/af/demo/jcr:content/formContainer-cc-form-spreadsheet-submission", + "fd:schemaType": "BASIC", + "fd:isHamburgerMenuEnabled": false, + "fd:roleAttribute": null, + "fd:formDataEnabled": false, + "fd:customFunctionsUrl": "/adobe/forms/af/customfunctions/L2NvbnRlbnQvZm9ybXMvYWYvZGVtbw==", + "fd:dataUrl": "/adobe/forms/af/data/L2NvbnRlbnQvZm9ybXMvYWYvZGVtbw==" + }, + "action": "https://forms.adobe.com/adobe/forms/af/submit/L2NvbnRlbnQvZm9ybXMvYWYvZGVtby9qY3I6Y29udGVudC9mb3JtQ29udGFpbmVyLWNjLWZvcm0tc3ByZWFkc2hlZXQtc3VibWlzc2lvbi5tb2RlbC5qc29u", + "events": { + "custom:setProperty": [ + "$event.payload" + ] + }, + ":itemsOrder": [ + "textinput" + ], + ":items": { + "textinput": { + "id": "textinput-0dca4c4bc1", + "fieldType": "text-input", + "name": "abc", + "visible": false, + "description": "dummy", + "type": "string", + "label": { + "value": "def", + "visible": true + }, + "properties": { + "fd:path": "/content/forms/af/demo/jcr:content/formContainer-cc-form-spreadsheet-submission/textinput" + }, + "events": { "custom:setProperty": [ - "$event.payload" + "$event.payload" ] - }, - ":itemsOrder": [ - "textinput" - ], - ":items": { - "textinput": { - "id": "textinput-0dca4c4bc1", - "fieldType": "text-input", - "name": "abc", - "visible": false, - "description": "dummy", - "type": "string", - "label": { - "value": "def", - "visible": true - }, - "properties": { - "fd:dor": { - "dorExclusion": false - }, - "fd:path": "/content/forms/af/demo/jcr:content/formContainer-cc-form-spreadsheet-submission/textinput" - }, - "events": { - "custom:setProperty": [ - "$event.payload" - ] - }, - ":type": "core/fd/components/form/textinput/v1/textinput" - } - }, - "metadata": { - "grammar": "json-formula-1.0.0", - "version": "1.0.0" - }, - "adaptiveform": "0.15.2", - ":type": "fd/components/form/v1/form" -} \ No newline at end of file + }, + ":type": "core/fd/components/form/textinput/v1/textinput" + } + }, + "metadata": { + "grammar": "json-formula-1.0.0", + "version": "1.0.0" + }, + "adaptiveform": "0.15.2", + ":type": "fd/components/form/v1/form" +} diff --git a/bundles/af-core/src/test/resources/form/formcontainer/exporter-formContainer-ue-form-rest-submission.json b/bundles/af-core/src/test/resources/form/formcontainer/exporter-formContainer-ue-form-rest-submission.json index 69c1f450a2..a43330654f 100644 --- a/bundles/af-core/src/test/resources/form/formcontainer/exporter-formContainer-ue-form-rest-submission.json +++ b/bundles/af-core/src/test/resources/form/formcontainer/exporter-formContainer-ue-form-rest-submission.json @@ -1,57 +1,54 @@ { - "id": "L2NvbnRlbnQvZm9ybXMvYWYvZGVtbw==", - "fieldType": "form", - "lang": "en", - "title": "uesprintdemo", - "action": "/adobe/forms/af/submit/L2NvbnRlbnQvZm9ybXMvYWYvZGVtbw==", - "properties": { - "themeRef": "", - "fd:path": "/content/forms/af/demo/jcr:content/formContainer-ue-form-rest-submission", - "fd:schemaType": "BASIC", - "fd:isHamburgerMenuEnabled": false, - "fd:roleAttribute": null, - "fd:formDataEnabled": false, - "fd:customFunctionsUrl": "/adobe/forms/af/customfunctions/L2NvbnRlbnQvZm9ybXMvYWYvZGVtbw==", - "fd:dataUrl": "/adobe/forms/af/data/L2NvbnRlbnQvZm9ybXMvYWYvZGVtbw==" - }, - "events": { + "id": "L2NvbnRlbnQvZm9ybXMvYWYvZGVtbw==", + "fieldType": "form", + "lang": "en", + "title": "uesprintdemo", + "action": "/adobe/forms/af/submit/L2NvbnRlbnQvZm9ybXMvYWYvZGVtbw==", + "properties": { + "themeRef": "", + "fd:path": "/content/forms/af/demo/jcr:content/formContainer-ue-form-rest-submission", + "fd:schemaType": "BASIC", + "fd:isHamburgerMenuEnabled": false, + "fd:roleAttribute": null, + "fd:formDataEnabled": false, + "fd:customFunctionsUrl": "/adobe/forms/af/customfunctions/L2NvbnRlbnQvZm9ybXMvYWYvZGVtbw==", + "fd:dataUrl": "/adobe/forms/af/data/L2NvbnRlbnQvZm9ybXMvYWYvZGVtbw==" + }, + "events": { + "custom:setProperty": [ + "$event.payload" + ] + }, + ":itemsOrder": [ + "textinput" + ], + ":items": { + "textinput": { + "id": "textinput-5c60b85622", + "fieldType": "text-input", + "name": "abc", + "visible": false, + "description": "dummy", + "type": "string", + "label": { + "value": "def", + "visible": true + }, + "properties": { + "fd:path": "/content/forms/af/demo/jcr:content/formContainer-ue-form-rest-submission/textinput" + }, + "events": { "custom:setProperty": [ - "$event.payload" + "$event.payload" ] - }, - ":itemsOrder": [ - "textinput" - ], - ":items": { - "textinput": { - "id": "textinput-5c60b85622", - "fieldType": "text-input", - "name": "abc", - "visible": false, - "description": "dummy", - "type": "string", - "label": { - "value": "def", - "visible": true - }, - "properties": { - "fd:dor": { - "dorExclusion": false - }, - "fd:path": "/content/forms/af/demo/jcr:content/formContainer-ue-form-rest-submission/textinput" - }, - "events": { - "custom:setProperty": [ - "$event.payload" - ] - }, - ":type": "core/fd/components/form/textinput/v1/textinput" - } - }, - "metadata": { - "version": "1.0.0", - "grammar": "json-formula-1.0.0" - }, - "adaptiveform": "0.15.2", - ":type": "fd/franklin/components/form/v1/form" -} \ No newline at end of file + }, + ":type": "core/fd/components/form/textinput/v1/textinput" + } + }, + "metadata": { + "version": "1.0.0", + "grammar": "json-formula-1.0.0" + }, + "adaptiveform": "0.15.2", + ":type": "fd/franklin/components/form/v1/form" +} diff --git a/bundles/af-core/src/test/resources/form/formcontainer/exporter-formContainer-ue-form-spreadsheet-submission.json b/bundles/af-core/src/test/resources/form/formcontainer/exporter-formContainer-ue-form-spreadsheet-submission.json index 8c539657d5..501cbda8be 100644 --- a/bundles/af-core/src/test/resources/form/formcontainer/exporter-formContainer-ue-form-spreadsheet-submission.json +++ b/bundles/af-core/src/test/resources/form/formcontainer/exporter-formContainer-ue-form-spreadsheet-submission.json @@ -1,57 +1,54 @@ { - "id": "L2NvbnRlbnQvZm9ybXMvYWYvZGVtbw==", - "fieldType": "form", - "lang": "en", - "title": "uesprintdemo", - "action": "", - "properties": { - "themeRef": "", - "fd:path": "/content/forms/af/demo/jcr:content/formContainer-ue-form-spreadsheet-submission", - "fd:schemaType": "BASIC", - "fd:isHamburgerMenuEnabled": false, - "fd:roleAttribute": null, - "fd:formDataEnabled": false, - "fd:customFunctionsUrl": "/adobe/forms/af/customfunctions/L2NvbnRlbnQvZm9ybXMvYWYvZGVtbw==", - "fd:dataUrl": "/adobe/forms/af/data/L2NvbnRlbnQvZm9ybXMvYWYvZGVtbw==" - }, - "events": { + "id": "L2NvbnRlbnQvZm9ybXMvYWYvZGVtbw==", + "fieldType": "form", + "lang": "en", + "title": "uesprintdemo", + "action": "", + "properties": { + "themeRef": "", + "fd:path": "/content/forms/af/demo/jcr:content/formContainer-ue-form-spreadsheet-submission", + "fd:schemaType": "BASIC", + "fd:isHamburgerMenuEnabled": false, + "fd:roleAttribute": null, + "fd:formDataEnabled": false, + "fd:customFunctionsUrl": "/adobe/forms/af/customfunctions/L2NvbnRlbnQvZm9ybXMvYWYvZGVtbw==", + "fd:dataUrl": "/adobe/forms/af/data/L2NvbnRlbnQvZm9ybXMvYWYvZGVtbw==" + }, + "events": { + "custom:setProperty": [ + "$event.payload" + ] + }, + ":itemsOrder": [ + "textinput" + ], + ":items": { + "textinput": { + "id": "textinput-6feb04776f", + "fieldType": "text-input", + "name": "abc", + "visible": false, + "description": "dummy", + "type": "string", + "label": { + "value": "def", + "visible": true + }, + "properties": { + "fd:path": "/content/forms/af/demo/jcr:content/formContainer-ue-form-spreadsheet-submission/textinput" + }, + "events": { "custom:setProperty": [ - "$event.payload" + "$event.payload" ] - }, - ":itemsOrder": [ - "textinput" - ], - ":items": { - "textinput": { - "id": "textinput-6feb04776f", - "fieldType": "text-input", - "name": "abc", - "visible": false, - "description": "dummy", - "type": "string", - "label": { - "value": "def", - "visible": true - }, - "properties": { - "fd:dor": { - "dorExclusion": false - }, - "fd:path": "/content/forms/af/demo/jcr:content/formContainer-ue-form-spreadsheet-submission/textinput" - }, - "events": { - "custom:setProperty": [ - "$event.payload" - ] - }, - ":type": "core/fd/components/form/textinput/v1/textinput" - } - }, - "metadata": { - "version": "1.0.0", - "grammar": "json-formula-1.0.0" - }, - "adaptiveform": "0.15.2", - ":type": "fd/franklin/components/form/v1/form" -} \ No newline at end of file + }, + ":type": "core/fd/components/form/textinput/v1/textinput" + } + }, + "metadata": { + "version": "1.0.0", + "grammar": "json-formula-1.0.0" + }, + "adaptiveform": "0.15.2", + ":type": "fd/franklin/components/form/v1/form" +} 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 7e137961c7..8e88763726 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 @@ -13,7 +13,7 @@ "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==" }, @@ -49,9 +49,6 @@ ] }, "properties": { - "fd:dor": { - "dorExclusion": false - }, "fd:path": "/content/forms/af/demo/jcr:content/formcontainerv2/textinput" }, ":type": "core/fd/components/form/textinput/v1/textinput" diff --git a/bundles/af-core/src/test/resources/form/formcontainer/exporter-formcontainerv2ChangeEventBehaviour.json b/bundles/af-core/src/test/resources/form/formcontainer/exporter-formcontainerv2ChangeEventBehaviour.json new file mode 100644 index 0000000000..01772e778f --- /dev/null +++ b/bundles/af-core/src/test/resources/form/formcontainer/exporter-formcontainerv2ChangeEventBehaviour.json @@ -0,0 +1,55 @@ +{ + "id": "L2NvbnRlbnQvZm9ybXMvYWYvZGVtbw==", + "fieldType": "form", + "lang": "en", + "properties": { + "thankyouPage": "/a/b/c", + "fd:path": "/content/forms/af/demo/jcr:content/formcontainerv2ChangeEventBehaviour", + "fd:schemaType": "BASIC", + "fd:isHamburgerMenuEnabled": false, + "fd:roleAttribute": null, + "fd:changeEventBehaviour": "deps", + "fd:formDataEnabled": true, + "fd:customFunctionsUrl": "/adobe/forms/af/customfunctions/L2NvbnRlbnQvZm9ybXMvYWYvZGVtbw==", + "fd:dataUrl": "/adobe/forms/af/data/L2NvbnRlbnQvZm9ybXMvYWYvZGVtbw==" + }, + "action": "/adobe/forms/af/submit/L2NvbnRlbnQvZm9ybXMvYWYvZGVtbw==", + "events": { + "custom:setProperty": [ + "$event.payload" + ] + }, + ":itemsOrder": [ + "textinput" + ], + "adaptiveform": "0.15.2", + "metadata": { + "grammar": "json-formula-1.0.0", + "version": "1.0.0" + }, + ":type": "core/fd/components/form/container/v2/container", + ":items": { + "textinput": { + "id": "textinput-121545fe05", + "fieldType": "text-input", + "name": "abc", + "visible": false, + "description": "dummy", + "type": "string", + "label": { + "value": "def", + "visible": true + }, + "properties": { + "customProp": "customPropValue", + "fd:path": "/content/forms/af/demo/jcr:content/formcontainerv2ChangeEventBehaviour/textinput" + }, + "events": { + "custom:setProperty": [ + "$event.payload" + ] + }, + ":type": "core/fd/components/form/textinput/v1/textinput" + } + } +} diff --git a/bundles/af-core/src/test/resources/form/formcontainer/exporter-formcontainerv2WithAutoSave.json b/bundles/af-core/src/test/resources/form/formcontainer/exporter-formcontainerv2WithAutoSave.json index f96f77690c..ed05656f32 100644 --- a/bundles/af-core/src/test/resources/form/formcontainer/exporter-formcontainerv2WithAutoSave.json +++ b/bundles/af-core/src/test/resources/form/formcontainer/exporter-formcontainerv2WithAutoSave.json @@ -46,9 +46,6 @@ }, "properties": { "customProp": "customPropValue", - "fd:dor": { - "dorExclusion": false - }, "fd:path": "/content/forms/af/demo/jcr:content/formcontainerv2WithAutoSave/textinput" }, "events": { diff --git a/bundles/af-core/src/test/resources/form/formcontainer/exporter-withSubmissionAttribute.json b/bundles/af-core/src/test/resources/form/formcontainer/exporter-withSubmissionAttribute.json index 13c18538d2..a0f6c4af53 100644 --- a/bundles/af-core/src/test/resources/form/formcontainer/exporter-withSubmissionAttribute.json +++ b/bundles/af-core/src/test/resources/form/formcontainer/exporter-withSubmissionAttribute.json @@ -1,73 +1,70 @@ { - "id": "L2NvbnRlbnQvZm9ybXMvYWYvZGVtbw==", - "fieldType": "form", - "lang": "en", - "title": "afv2", - "action": "https://forms.adobe.com/adobe/forms/af/submit/L2NvbnRlbnQvZm9ybXMvYWYvZGVtby9qY3I6Y29udGVudC9mb3JtY29udGFpbmVyLXN1Ym1pc3Npb25WaWV3Lm1vZGVsLmpzb24=", - "properties": { - "themeRef": "/libs/fd/af/themes/canvas", - "schemaType": "none", - "useExternalEmailTemplate": "true", - "fd:path": "/content/forms/af/demo/jcr:content/formcontainer-submissionView", - "fd:schemaType": "BASIC", - "fd:isHamburgerMenuEnabled": false, - "fd:roleAttribute": null, - "fd:formDataEnabled": false, - "fd:customFunctionsUrl": "/adobe/forms/af/customfunctions/L2NvbnRlbnQvZm9ybXMvYWYvZGVtbw==", - "fd:dataUrl": "/adobe/forms/af/data/L2NvbnRlbnQvZm9ybXMvYWYvZGVtbw==", - "fd:submit": { - "spreadsheet": { - "spreadsheetUrl": "http://localhost/testurl" - }, - "email": { - "subject": "Test Subject", - "mailto": [ - "test@example.com" - ], - "from": "sender@example.com" - }, - "actionType": "fd/af/components/guidesubmittype/franklin/spreadsheet", - "actionName": "spreadsheet" - } - }, - "events": { + "id": "L2NvbnRlbnQvZm9ybXMvYWYvZGVtbw==", + "fieldType": "form", + "lang": "en", + "title": "afv2", + "action": "https://forms.adobe.com/adobe/forms/af/submit/L2NvbnRlbnQvZm9ybXMvYWYvZGVtby9qY3I6Y29udGVudC9mb3JtY29udGFpbmVyLXN1Ym1pc3Npb25WaWV3Lm1vZGVsLmpzb24=", + "properties": { + "themeRef": "/libs/fd/af/themes/canvas", + "schemaType": "none", + "useExternalEmailTemplate": "true", + "fd:path": "/content/forms/af/demo/jcr:content/formcontainer-submissionView", + "fd:schemaType": "BASIC", + "fd:isHamburgerMenuEnabled": false, + "fd:roleAttribute": null, + "fd:formDataEnabled": false, + "fd:customFunctionsUrl": "/adobe/forms/af/customfunctions/L2NvbnRlbnQvZm9ybXMvYWYvZGVtbw==", + "fd:dataUrl": "/adobe/forms/af/data/L2NvbnRlbnQvZm9ybXMvYWYvZGVtbw==", + "fd:submit": { + "spreadsheet": { + "spreadsheetUrl": "http://localhost/testurl" + }, + "email": { + "subject": "Test Subject", + "mailto": [ + "test@example.com" + ], + "from": "sender@example.com" + }, + "actionType": "fd/af/components/guidesubmittype/franklin/spreadsheet", + "actionName": "spreadsheet" + } + }, + "events": { + "custom:setProperty": [ + "$event.payload" + ] + }, + ":itemsOrder": [ + "textinput" + ], + "metadata": { + "grammar": "json-formula-1.0.0", + "version": "1.0.0" + }, + "adaptiveform": "0.14.2", + ":items": { + "textinput": { + "id": "textinput-5bac58a9c9", + "fieldType": "text-input", + "name": "abc", + "visible": false, + "description": "dummy", + "type": "string", + "label": { + "value": "def", + "visible": true + }, + "properties": { + "fd:path": "/content/forms/af/demo/jcr:content/formcontainer-submissionView/textinput" + }, + "events": { "custom:setProperty": [ - "$event.payload" + "$event.payload" ] - }, - ":itemsOrder": [ - "textinput" - ], - "metadata": { - "grammar": "json-formula-1.0.0", - "version": "1.0.0" - }, - "adaptiveform": "0.14.2", - ":items": { - "textinput": { - "id": "textinput-5bac58a9c9", - "fieldType": "text-input", - "name": "abc", - "visible": false, - "description": "dummy", - "type": "string", - "label": { - "value": "def", - "visible": true - }, - "properties": { - "fd:dor": { - "dorExclusion": false - }, - "fd:path": "/content/forms/af/demo/jcr:content/formcontainer-submissionView/textinput" - }, - "events": { - "custom:setProperty": [ - "$event.payload" - ] - }, - ":type": "core/fd/components/form/textinput/v1/textinput" - } - }, - ":type": "forms-components-examples/components/form/container" + }, + ":type": "core/fd/components/form/textinput/v1/textinput" + } + }, + ":type": "forms-components-examples/components/form/container" } diff --git a/bundles/af-core/src/test/resources/form/formcontainer/exporter-withoutSubmissionAttribute.json b/bundles/af-core/src/test/resources/form/formcontainer/exporter-withoutSubmissionAttribute.json index c715d24bc4..a41a074a3c 100644 --- a/bundles/af-core/src/test/resources/form/formcontainer/exporter-withoutSubmissionAttribute.json +++ b/bundles/af-core/src/test/resources/form/formcontainer/exporter-withoutSubmissionAttribute.json @@ -1,59 +1,56 @@ { - "id": "L2NvbnRlbnQvZm9ybXMvYWYvZGVtbw==", - "fieldType": "form", - "lang": "en", - "title": "afv2", - "properties": { - "themeRef": "/libs/fd/af/themes/canvas", - "schemaType": "none", - "useExternalEmailTemplate": "true", - "fd:path": "/content/forms/af/demo/jcr:content/formcontainer-submissionView", - "fd:schemaType": "BASIC", - "fd:isHamburgerMenuEnabled": false, - "fd:roleAttribute": null, - "fd:formDataEnabled": false, - "fd:customFunctionsUrl": "/adobe/forms/af/customfunctions/L2NvbnRlbnQvZm9ybXMvYWYvZGVtbw==", - "fd:dataUrl": "/adobe/forms/af/data/L2NvbnRlbnQvZm9ybXMvYWYvZGVtbw==" - }, - "action": "https://forms.adobe.com/adobe/forms/af/submit/L2NvbnRlbnQvZm9ybXMvYWYvZGVtby9qY3I6Y29udGVudC9mb3JtY29udGFpbmVyLXN1Ym1pc3Npb25WaWV3Lm1vZGVsLmpzb24=", - "events": { + "id": "L2NvbnRlbnQvZm9ybXMvYWYvZGVtbw==", + "fieldType": "form", + "lang": "en", + "title": "afv2", + "properties": { + "themeRef": "/libs/fd/af/themes/canvas", + "schemaType": "none", + "useExternalEmailTemplate": "true", + "fd:path": "/content/forms/af/demo/jcr:content/formcontainer-submissionView", + "fd:schemaType": "BASIC", + "fd:isHamburgerMenuEnabled": false, + "fd:roleAttribute": null, + "fd:formDataEnabled": false, + "fd:customFunctionsUrl": "/adobe/forms/af/customfunctions/L2NvbnRlbnQvZm9ybXMvYWYvZGVtbw==", + "fd:dataUrl": "/adobe/forms/af/data/L2NvbnRlbnQvZm9ybXMvYWYvZGVtbw==" + }, + "action": "https://forms.adobe.com/adobe/forms/af/submit/L2NvbnRlbnQvZm9ybXMvYWYvZGVtby9qY3I6Y29udGVudC9mb3JtY29udGFpbmVyLXN1Ym1pc3Npb25WaWV3Lm1vZGVsLmpzb24=", + "events": { + "custom:setProperty": [ + "$event.payload" + ] + }, + ":itemsOrder": [ + "textinput" + ], + ":items": { + "textinput": { + "id": "textinput-5bac58a9c9", + "fieldType": "text-input", + "name": "abc", + "visible": false, + "description": "dummy", + "type": "string", + "label": { + "value": "def", + "visible": true + }, + "properties": { + "fd:path": "/content/forms/af/demo/jcr:content/formcontainer-submissionView/textinput" + }, + "events": { "custom:setProperty": [ - "$event.payload" + "$event.payload" ] - }, - ":itemsOrder": [ - "textinput" - ], - ":items": { - "textinput": { - "id": "textinput-5bac58a9c9", - "fieldType": "text-input", - "name": "abc", - "visible": false, - "description": "dummy", - "type": "string", - "label": { - "value": "def", - "visible": true - }, - "properties": { - "fd:dor": { - "dorExclusion": false - }, - "fd:path": "/content/forms/af/demo/jcr:content/formcontainer-submissionView/textinput" - }, - "events": { - "custom:setProperty": [ - "$event.payload" - ] - }, - ":type": "core/fd/components/form/textinput/v1/textinput" - } - }, - "metadata": { - "version": "1.0.0", - "grammar": "json-formula-1.0.0" - }, - "adaptiveform": "0.14.2", - ":type": "forms-components-examples/components/form/container" + }, + ":type": "core/fd/components/form/textinput/v1/textinput" + } + }, + "metadata": { + "version": "1.0.0", + "grammar": "json-formula-1.0.0" + }, + "adaptiveform": "0.14.2", + ":type": "forms-components-examples/components/form/container" } diff --git a/bundles/af-core/src/test/resources/form/formcontainer/test-content-change-event-behaviour.json b/bundles/af-core/src/test/resources/form/formcontainer/test-content-change-event-behaviour.json new file mode 100644 index 0000000000..9c07ba2d29 --- /dev/null +++ b/bundles/af-core/src/test/resources/form/formcontainer/test-content-change-event-behaviour.json @@ -0,0 +1,20 @@ +{ + "jcr:primaryType": "nt:unstructured", + "sling:resourceType" : "core/fd/components/form/container/v2/container", + "thankyouPage": "/a/b/c", + "thankyouMessage": "message", + "fieldType" : "form", + "prefillService" : "abc", + "fd:changeEventBehaviour": "deps", + "textinput" : { + "jcr:primaryType": "nt:unstructured", + "sling:resourceType" : "core/fd/components/form/textinput/v1/textinput", + "name" : "abc", + "jcr:title" : "def", + "hideTitle" : false, + "description" : "dummy", + "fd:translationIds" : "{\"jcr:title\":\"guideContainer##textinput##jcr:title##5598\",\"placeholder\":\"guideContainer##textinput##description##5648\"}", + "visible" : false, + "customProp": "customPropValue" + } +} diff --git a/bundles/af-core/src/test/resources/form/formcontainer/test-page-content.json b/bundles/af-core/src/test/resources/form/formcontainer/test-page-content.json index 7a9ca98bf2..5f528186c5 100644 --- a/bundles/af-core/src/test/resources/form/formcontainer/test-page-content.json +++ b/bundles/af-core/src/test/resources/form/formcontainer/test-page-content.json @@ -210,6 +210,22 @@ "visible": false }, "actionType": "fd/af/components/guidesubmittype/rest" + }, + "formcontainerv2-with-ssv": { + "jcr:primaryType": "nt:unstructured", + "sling:resourceType": "core/fd/components/form/container/v2/container", + "fieldType": "form", + "title": "SSV Form", + "fd:enableServerSideValidation": true, + "fd:ssvCloudServicePath": "/conf/global/settings/cloudconfigs/ssv-config", + "actionName": "rest" + }, + "formcontainerv2-ssv-disabled": { + "jcr:primaryType": "nt:unstructured", + "sling:resourceType": "core/fd/components/form/container/v2/container", + "fieldType": "form", + "title": "No-SSV Form", + "actionName": "rest" } } } diff --git a/bundles/af-core/src/test/resources/form/formstructparser/test-content.json b/bundles/af-core/src/test/resources/form/formstructparser/test-content.json index 9ad6a5f74a..ff6f8de10e 100644 --- a/bundles/af-core/src/test/resources/form/formstructparser/test-content.json +++ b/bundles/af-core/src/test/resources/form/formstructparser/test-content.json @@ -14,7 +14,7 @@ "thankyouPage": "/a/b/c", "thankyouMessage": "message", "clientLibRef" : "abc", - "themeClientLibRef": "def", + "themeRef": "/content/dam/formsanddocuments-themes/test-theme", "datepicker": { "jcr:primaryType": "nt:unstructured", "sling:resourceType": "core/fd/components/form/datepicker/v1/datepicker", @@ -89,5 +89,27 @@ } } } + }, + "dam": { + "jcr:primaryType": "nt:unstructured", + "formsanddocuments-themes": { + "jcr:primaryType": "nt:unstructured", + "test-theme": { + "jcr:primaryType": "nt:unstructured", + "jcr:content": { + "jcr:primaryType": "dam:AssetContent", + "metadata": { + "jcr:primaryType": "nt:unstructured", + "clientlibCategory": "fdtheme.test-theme" + } + } + }, + "invalid-theme": { + "jcr:primaryType": "nt:unstructured", + "jcr:content": { + "jcr:primaryType": "dam:AssetContent" + } + } + } } } diff --git a/bundles/af-core/src/test/resources/form/fragment/exporter-fragment-dampath.json b/bundles/af-core/src/test/resources/form/fragment/exporter-fragment-dampath.json index 14471df334..3d32e7043b 100644 --- a/bundles/af-core/src/test/resources/form/fragment/exporter-fragment-dampath.json +++ b/bundles/af-core/src/test/resources/form/fragment/exporter-fragment-dampath.json @@ -6,6 +6,7 @@ "repeatable": true, "minOccur": 0, "maxOccur": 4, + "fragmentPath": "/content/dam/formsanddocuments/affragment", "label": { "value": "Fragment" }, @@ -15,11 +16,6 @@ ] }, "properties": { - "fd:dor": { - "dorExclusion": false, - "dorExcludeTitle": false, - "dorExcludeDescription": false - }, "fd:path": "/content/fragment-dampath", "fd:fragment": true, "fd:viewType": "fragment" @@ -42,9 +38,6 @@ ] }, "properties": { - "fd:dor": { - "dorExclusion": false - }, "fd:path": "/content/forms/af/affragment/jcr:content/guideContainer/textinput" }, ":type": "core/fd/components/form/textinput/v1/textinput" diff --git a/bundles/af-core/src/test/resources/form/fragment/exporter-fragment-items-array.json b/bundles/af-core/src/test/resources/form/fragment/exporter-fragment-items-array.json new file mode 100644 index 0000000000..ed05dc73be --- /dev/null +++ b/bundles/af-core/src/test/resources/form/fragment/exporter-fragment-items-array.json @@ -0,0 +1,41 @@ +{ + "id": "fragment-b6e9b38d43", + "fieldType": "panel", + "name": "fragment-123", + "type": "object", + "repeatable": true, + "minOccur": 0, + "maxOccur": 4, + "fragmentPath": "/content/affragment", + "events": { + "custom:setProperty": ["$event.payload"] + }, + "properties": { + "customProp": "customPropValue", + "fd:path": "/content/fragment", + "fd:fragment": true, + "fd:viewType": "fragment" + }, + "label": { + "value": "Fragment" + }, + ":type": "core/fd/components/form/fragment/v1/fragment", + "items": [ + { + "id": "textinput-233cc688ba", + "fieldType": "text-input", + "name": "fragmenttextinput", + "type": "string", + "label": { + "value": "Text Input" + }, + "events": { + "custom:setProperty": ["$event.payload"] + }, + "properties": { + "fd:path": "/content/affragment/jcr:content/guideContainer/textinput" + }, + ":type": "core/fd/components/form/textinput/v1/textinput" + } + ] +} diff --git a/bundles/af-core/src/test/resources/form/fragment/exporter-fragment-lazy.json b/bundles/af-core/src/test/resources/form/fragment/exporter-fragment-lazy.json new file mode 100644 index 0000000000..20c194f8d6 --- /dev/null +++ b/bundles/af-core/src/test/resources/form/fragment/exporter-fragment-lazy.json @@ -0,0 +1,23 @@ +{ + "id": "fragment-de3d70f5f1", + "fieldType": "panel", + "name": "fragment-lazy", + "type": "object", + "repeatable": true, + "minOccur": 0, + "maxOccur": 4, + "fragmentPath": "/content/affragment", + "lazy": true, + "events": { + "custom:setProperty": ["$event.payload"] + }, + "properties": { + "fd:path": "/content/fragment-lazy", + "fd:fragment": true, + "fd:viewType": "fragment" + }, + "label": { + "value": "Lazy Fragment" + }, + ":type": "core/fd/components/form/fragment/v1/fragment" +} diff --git a/bundles/af-core/src/test/resources/form/fragment/exporter-fragment-with-fragment-path.json b/bundles/af-core/src/test/resources/form/fragment/exporter-fragment-with-fragment-path.json index f9cb5a81ed..36b8eeb6df 100644 --- a/bundles/af-core/src/test/resources/form/fragment/exporter-fragment-with-fragment-path.json +++ b/bundles/af-core/src/test/resources/form/fragment/exporter-fragment-with-fragment-path.json @@ -9,26 +9,22 @@ "fragmentPath": "/content/affragment", "properties": { "customProp": "customPropValue", - "fd:dor": { - "dorExclusion": false, - "dorExcludeTitle": false, - "dorExcludeDescription": false - }, "fd:path": "/content/fragment-with-fragment-path", "fd:fragment": true, "fd:viewType": "fragment" }, + "events": { + "custom:setProperty": ["$event.payload"], + "change": ["fragmentChangeHandler"], + "initialize": ["fragInit"] + }, + "rules": { + "visible": "fragmentVisibleRule" + }, "label": { "value": "Fragment" }, - "events": { - "custom:setProperty": [ - "$event.payload" - ] - }, - ":itemsOrder": [ - "textinput" - ], + ":itemsOrder": ["textinput"], ":items": { "textinput": { "id": "textinput-233cc688ba", @@ -39,18 +35,13 @@ "value": "Text Input" }, "properties": { - "fd:dor": { - "dorExclusion": false - }, "fd:path": "/content/affragment/jcr:content/guideContainer/textinput" }, "events": { - "custom:setProperty": [ - "$event.payload" - ] + "custom:setProperty": ["$event.payload"] }, ":type": "core/fd/components/form/textinput/v1/textinput" } }, ":type": "core/fd/components/form/fragment/v1/fragment" -} \ No newline at end of file +} diff --git a/bundles/af-core/src/test/resources/form/fragment/exporter-fragment.json b/bundles/af-core/src/test/resources/form/fragment/exporter-fragment.json index 54d400f6f8..673640c282 100644 --- a/bundles/af-core/src/test/resources/form/fragment/exporter-fragment.json +++ b/bundles/af-core/src/test/resources/form/fragment/exporter-fragment.json @@ -6,28 +6,25 @@ "repeatable": true, "minOccur": 0, "maxOccur": 4, - "label": { - "value": "Fragment" - }, - "events": { - "custom:setProperty": [ - "$event.payload" - ] - }, + "fragmentPath": "/content/affragment", "properties": { - "fd:dor": { - "dorExclusion": false, - "dorExcludeTitle": false, - "dorExcludeDescription": false - }, + "customProp": "customPropValue", "fd:path": "/content/fragment", "fd:fragment": true, - "fd:viewType": "fragment", - "customProp": "customPropValue" + "fd:viewType": "fragment" + }, + "events": { + "custom:setProperty": ["$event.payload"], + "change": ["fragmentChangeHandler"], + "initialize": ["fragInit"] + }, + "rules": { + "visible": "fragmentVisibleRule" }, - ":itemsOrder": [ - "textinput" - ], + "label": { + "value": "Fragment" + }, + ":itemsOrder": ["textinput"], ":items": { "textinput": { "id": "textinput-233cc688ba", @@ -37,19 +34,14 @@ "label": { "value": "Text Input" }, - "events": { - "custom:setProperty": [ - "$event.payload" - ] - }, "properties": { - "fd:dor": { - "dorExclusion": false - }, "fd:path": "/content/affragment/jcr:content/guideContainer/textinput" }, + "events": { + "custom:setProperty": ["$event.payload"] + }, ":type": "core/fd/components/form/textinput/v1/textinput" } }, ":type": "core/fd/components/form/fragment/v1/fragment" -} \ No newline at end of file +} diff --git a/bundles/af-core/src/test/resources/form/fragment/test-content.json b/bundles/af-core/src/test/resources/form/fragment/test-content.json index eb61e61c96..7f57860d03 100644 --- a/bundles/af-core/src/test/resources/form/fragment/test-content.json +++ b/bundles/af-core/src/test/resources/form/fragment/test-content.json @@ -56,6 +56,58 @@ "fragmentPath": "/content/affragment", "customProp": "customPropValue" }, + "fragment-with-invalid-path": { + "jcr:primaryType": "nt:unstructured", + "wrapData": true, + "jcr:title": "Fragment with Invalid Path", + "minOccur": "0", + "jcr:lastModifiedBy": "admin", + "name": "fragment-456", + "maxOccur": "4", + "jcr:lastModified": "Fri Jun 02 2023 12:34:24 GMT+0530", + "repeatable": true, + "sling:resourceType": "core/fd/components/form/fragment/v1/fragment", + "fieldType": "panel", + "fragmentPath": "/content/nonexistent/fragment" + }, + "fragment-lazy": { + "jcr:primaryType": "nt:unstructured", + "wrapData": true, + "jcr:title": "Lazy Fragment", + "minOccur": "0", + "jcr:lastModifiedBy": "admin", + "name": "fragment-lazy", + "maxOccur": "4", + "jcr:lastModified": "Fri Jun 02 2023 12:34:24 GMT+0530", + "repeatable": true, + "sling:resourceType": "core/fd/components/form/fragment/v1/fragment", + "fieldType": "panel", + "fragmentPath": "/content/affragment", + "lazy": true + }, + "fragment-with-placeholder-rules": { + "jcr:primaryType": "nt:unstructured", + "wrapData": true, + "jcr:title": "Fragment with Placeholder Rules", + "minOccur": "0", + "name": "fragment-with-panel-rules", + "maxOccur": "4", + "repeatable": true, + "sling:resourceType": "core/fd/components/form/fragment/v1/fragment", + "fieldType": "panel", + "fragmentPath": "/content/affragment", + "fd:rules": { + "jcr:primaryType": "nt:unstructured", + "validationStatus": "invalid", + "required": "placeholderRequired", + "visible": "panelVisibleRule" + }, + "fd:events": { + "jcr:primaryType": "nt:unstructured", + "click": "placeholderClick", + "initialize": "panelInit" + } + }, "affragment": { "jcr:primaryType": "nt:unstructured", "jcr:title": "AF Fragment (v2)", @@ -70,6 +122,16 @@ "sling:resourceType": "core/fd/components/form/fragmentcontainer/v1/fragmentcontainer", "fieldType": "form", "dorType": "none", + "fd:rules": { + "jcr:primaryType": "nt:unstructured", + "validationStatus": "valid", + "visible": "fragmentVisibleRule" + }, + "fd:events": { + "jcr:primaryType": "nt:unstructured", + "change": "fragmentChangeHandler", + "initialize": "fragInit" + }, "textinput": { "jcr:primaryType": "nt:unstructured", "jcr:title": "Text Input", diff --git a/bundles/af-core/src/test/resources/form/image/exporter-image-customized.json b/bundles/af-core/src/test/resources/form/image/exporter-image-customized.json index 61d573feb0..b5a302284c 100644 --- a/bundles/af-core/src/test/resources/form/image/exporter-image-customized.json +++ b/bundles/af-core/src/test/resources/form/image/exporter-image-customized.json @@ -1,6 +1,6 @@ { "id": "image-ad1e095fba", - "dataRef" : "a.b", + "dataRef": "a.b", "fieldType": "image", "name": "abc", "value": "/content/image-customized.img.png", @@ -12,10 +12,7 @@ ] }, "properties": { - "fd:path": "/content/image-customized", - "fd:dor": { - "dorExclusion": false - } + "fd:path": "/content/image-customized" }, ":type": "core/fd/components/form/image/v1/image" -} \ No newline at end of file +} diff --git a/bundles/af-core/src/test/resources/form/image/exporter-image-datalayer.json b/bundles/af-core/src/test/resources/form/image/exporter-image-datalayer.json index e35eb4ebd5..6c5d03326f 100644 --- a/bundles/af-core/src/test/resources/form/image/exporter-image-datalayer.json +++ b/bundles/af-core/src/test/resources/form/image/exporter-image-datalayer.json @@ -10,10 +10,7 @@ ] }, "properties": { - "fd:path": "/content/image-datalayer", - "fd:dor": { - "dorExclusion": false - } + "fd:path": "/content/image-datalayer" }, ":type": "core/fd/components/form/image/v1/image", "dataLayer": { @@ -23,4 +20,4 @@ "fieldType": "image" } } -} \ No newline at end of file +} diff --git a/bundles/af-core/src/test/resources/form/image/exporter-image.json b/bundles/af-core/src/test/resources/form/image/exporter-image.json index 05db11022f..87b56a27c1 100644 --- a/bundles/af-core/src/test/resources/form/image/exporter-image.json +++ b/bundles/af-core/src/test/resources/form/image/exporter-image.json @@ -10,10 +10,7 @@ "properties": { "fd:path": "/content/image", "fd:repoPath": "/content/dam/formsanddocuments/abc.jpeg", - "fd:dor": { - "dorExclusion": false - }, "customProp": "customPropValue" }, ":type": "core/fd/components/form/image/v1/image" -} \ No newline at end of file +} diff --git a/bundles/af-core/src/test/resources/form/imagechoice/exporter-imagechoice-customized.json b/bundles/af-core/src/test/resources/form/imagechoice/exporter-imagechoice-customized.json new file mode 100644 index 0000000000..de690004de --- /dev/null +++ b/bundles/af-core/src/test/resources/form/imagechoice/exporter-imagechoice-customized.json @@ -0,0 +1,55 @@ +{ + "id": "imagechoice-1e685e32c4", + "dataRef": "a.b", + "fieldType": "radio-group", + "name": "imagechoice_12345", + "visible": false, + "description": "Select your favorite animal", + "tooltip": "Pick one animal", + "type": "string", + "required": true, + "enabled": true, + "constraintMessages": { + "required": "This is a required field" + }, + "readOnly": true, + "enforceEnum": true, + "enumNames": [ + "Cat", + "Dog", + "Bird" + ], + "default": "cat", + "screenReaderText": "custom screen reader text", + "label": { + "value": "Favorite Animal" + }, + "properties": { + "afs:layout": { + "tooltipVisible": true, + "orientation": "vertical", + "selectionType": "single", + "imageSrc": [ + "/content/dam/animals/cat.png", + "/content/dam/animals/dog.png", + "/content/dam/animals/bird.png" + ] + }, + "fd:dor": { + "dorExclusion": true, + "dorColspan": "4" + }, + "fd:path": "/content/imagechoice-customized" + }, + "events": { + "custom:setProperty": [ + "$event.payload" + ] + }, + "enum": [ + "cat", + "dog", + "bird" + ], + ":type": "core/fd/components/form/imagechoice/v1/imagechoice" +} diff --git a/bundles/af-core/src/test/resources/form/imagechoice/exporter-imagechoice-datalayer.json b/bundles/af-core/src/test/resources/form/imagechoice/exporter-imagechoice-datalayer.json new file mode 100644 index 0000000000..fa69508589 --- /dev/null +++ b/bundles/af-core/src/test/resources/form/imagechoice/exporter-imagechoice-datalayer.json @@ -0,0 +1,47 @@ +{ + "id": "imagechoice-bafbf1a102", + "fieldType": "radio-group", + "name": "FavoriteColor", + "visible": true, + "description": "Choose your favorite color", + "type": "string", + "enabled": true, + "readOnly": false, + "enforceEnum": true, + "enumNames": [ + "Red", + "Blue" + ], + "label": { + "value": "Favorite Color" + }, + "properties": { + "afs:layout": { + "orientation": "horizontal", + "selectionType": "single", + "imageSrc": [ + "/content/dam/colors/red.png", + "/content/dam/colors/blue.png" + ] + }, + "fd:path": "/content/imagechoice-datalayer" + }, + "events": { + "custom:setProperty": [ + "$event.payload" + ] + }, + "enum": [ + "red", + "blue" + ], + ":type": "core/fd/components/form/imagechoice/v1/imagechoice", + "dataLayer": { + "imagechoice-bafbf1a102": { + "dc:description": "Choose your favorite color", + "@type": "core/fd/components/form/imagechoice/v1/imagechoice", + "fieldType": "radio-group", + "dc:title": "Favorite Color" + } + } +} diff --git a/bundles/af-core/src/test/resources/form/imagechoice/exporter-imagechoice-multi.json b/bundles/af-core/src/test/resources/form/imagechoice/exporter-imagechoice-multi.json new file mode 100644 index 0000000000..022f856144 --- /dev/null +++ b/bundles/af-core/src/test/resources/form/imagechoice/exporter-imagechoice-multi.json @@ -0,0 +1,42 @@ +{ + "id": "imagechoice-33251b3cfa", + "fieldType": "checkbox-group", + "name": "imagechoice_multi", + "type": "string[]", + "enforceEnum": true, + "enumNames": [ + "Red", + "Green", + "Blue" + ], + "default": [ + "red", + "blue" + ], + "label": { + "value": "Select Colors" + }, + "properties": { + "afs:layout": { + "orientation": "horizontal", + "selectionType": "multi", + "imageSrc": [ + "/content/dam/colors/red.png", + "/content/dam/colors/green.png", + "/content/dam/colors/blue.png" + ] + }, + "fd:path": "/content/imagechoice-multi" + }, + "events": { + "custom:setProperty": [ + "$event.payload" + ] + }, + "enum": [ + "red", + "green", + "blue" + ], + ":type": "core/fd/components/form/imagechoice/v1/imagechoice" +} diff --git a/bundles/af-core/src/test/resources/form/imagechoice/exporter-imagechoice.json b/bundles/af-core/src/test/resources/form/imagechoice/exporter-imagechoice.json new file mode 100644 index 0000000000..baa378afba --- /dev/null +++ b/bundles/af-core/src/test/resources/form/imagechoice/exporter-imagechoice.json @@ -0,0 +1,35 @@ +{ + "id": "imagechoice-5b1500d241", + "fieldType": "radio-group", + "name": "imagechoice_12345", + "type": "string", + "enforceEnum": true, + "enumNames": [ + "Option 1", + "Option 2" + ], + "label": { + "value": "Image Choice" + }, + "properties": { + "afs:layout": { + "orientation": "horizontal", + "selectionType": "single", + "imageSrc": [ + "/content/dam/image1.png", + "/content/dam/image2.png" + ] + }, + "fd:path": "/content/imagechoice" + }, + "events": { + "custom:setProperty": [ + "$event.payload" + ] + }, + "enum": [ + "0", + "1" + ], + ":type": "core/fd/components/form/imagechoice/v1/imagechoice" +} diff --git a/bundles/af-core/src/test/resources/form/imagechoice/test-content.json b/bundles/af-core/src/test/resources/form/imagechoice/test-content.json new file mode 100644 index 0000000000..14bd29dc48 --- /dev/null +++ b/bundles/af-core/src/test/resources/form/imagechoice/test-content.json @@ -0,0 +1,156 @@ +{ + "imagechoice": { + "jcr:primaryType": "nt:unstructured", + "sling:resourceType": "core/fd/components/form/imagechoice/v1/imagechoice", + "name": "imagechoice_12345", + "jcr:title": "Image Choice", + "orientation": "horizontal", + "selectionType": "single", + "enum": [ + "0", + "1" + ], + "enumNames": [ + "Option 1", + "Option 2" + ], + "imageSrc": [ + "/content/dam/image1.png", + "/content/dam/image2.png" + ], + "fieldType": "radio-group", + "type": "string" + }, + "imagechoice-customized": { + "jcr:primaryType": "nt:unstructured", + "sling:resourceType": "core/fd/components/form/imagechoice/v1/imagechoice", + "name": "imagechoice_12345", + "jcr:title": "Favorite Animal", + "description": "Select your favorite animal", + "enabled": true, + "orientation": "vertical", + "selectionType": "single", + "dataRef": "a.b", + "custom": "custom screen reader text", + "tooltip": "Pick one animal", + "required": "true", + "dorExclusion": true, + "dorColspan": "4", + "assistPriority": "custom", + "enum": [ + "cat", + "dog", + "bird" + ], + "type": "string", + "enumNames": [ + "Cat", + "Dog", + "Bird" + ], + "imageSrc": [ + "/content/dam/animals/cat.png", + "/content/dam/animals/dog.png", + "/content/dam/animals/bird.png" + ], + "visible": false, + "readOnly": true, + "fieldType": "radio-group", + "mandatoryMessage": "This is a required field", + "default": "cat", + "tooltipVisible": "true" + }, + "imagechoice-multi": { + "jcr:primaryType": "nt:unstructured", + "sling:resourceType": "core/fd/components/form/imagechoice/v1/imagechoice", + "name": "imagechoice_multi", + "jcr:title": "Select Colors", + "orientation": "horizontal", + "selectionType": "multi", + "enum": [ + "red", + "green", + "blue" + ], + "type": "string[]", + "enumNames": [ + "Red", + "Green", + "Blue" + ], + "imageSrc": [ + "/content/dam/colors/red.png", + "/content/dam/colors/green.png", + "/content/dam/colors/blue.png" + ], + "fieldType": "checkbox-group", + "default": ["red", "blue"] + }, + "imagechoice-no-images": { + "jcr:primaryType": "nt:unstructured", + "sling:resourceType": "core/fd/components/form/imagechoice/v1/imagechoice", + "name": "imagechoice_noimages", + "jcr:title": "No Images", + "orientation": "horizontal", + "selectionType": "single", + "enum": [ + "a", + "b" + ], + "enumNames": [ + "Option A", + "Option B" + ], + "fieldType": "radio-group", + "type": "string" + }, + "imagechoice-datalayer": { + "id": "imagechoice-bafbf1a102", + "sling:resourceType": "core/fd/components/form/imagechoice/v1/imagechoice", + "fieldType": "radio-group", + "name": "FavoriteColor", + "jcr:title": "Favorite Color", + "description": "Choose your favorite color", + "visible": true, + "type": "string", + "enabled": true, + "readOnly": false, + "selectionType": "single", + "enforceEnum": true, + "enumNames": [ + "Red", + "Blue" + ], + "enum": [ + "red", + "blue" + ], + "imageSrc": [ + "/content/dam/colors/red.png", + "/content/dam/colors/blue.png" + ] + }, + "imagechoice-option-screenreader-label": { + "id": "imagechoice-1076f3bd737", + "jcr:primaryType": "nt:unstructured", + "sling:resourceType": "core/fd/components/form/imagechoice/v1/imagechoice", + "name": "imagechoice", + "isTitleRichText": "true", + "jcr:title": "Favorite Pet", + "orientation": "vertical", + "selectionType": "single", + "enum": [ + "0", + "1" + ], + "enumNames": [ + "

Cat

", + "

Dog

" + ], + "imageSrc": [ + "/content/dam/pets/cat.png", + "/content/dam/pets/dog.png" + ], + "fieldType": "radio-group" + } +} diff --git a/bundles/af-core/src/test/resources/form/numberinput/exporter-numberinput-backwardcompatible-2.json b/bundles/af-core/src/test/resources/form/numberinput/exporter-numberinput-backwardcompatible-2.json index d605eb9bf0..12078a844b 100644 --- a/bundles/af-core/src/test/resources/form/numberinput/exporter-numberinput-backwardcompatible-2.json +++ b/bundles/af-core/src/test/resources/form/numberinput/exporter-numberinput-backwardcompatible-2.json @@ -15,9 +15,6 @@ }, "name": "Phone Number", "properties": { - "fd:dor": { - "dorExclusion": false - }, "fd:path": "/content/numberinput-backwardcompatible-2" }, "type": "number" diff --git a/bundles/af-core/src/test/resources/form/numberinput/exporter-numberinput-backwardcompatible.json b/bundles/af-core/src/test/resources/form/numberinput/exporter-numberinput-backwardcompatible.json index 2b0c89f9dd..c7368cccda 100644 --- a/bundles/af-core/src/test/resources/form/numberinput/exporter-numberinput-backwardcompatible.json +++ b/bundles/af-core/src/test/resources/form/numberinput/exporter-numberinput-backwardcompatible.json @@ -15,9 +15,6 @@ }, "name": "Phone Number", "properties": { - "fd:dor": { - "dorExclusion": false - }, "fd:path": "/content/numberinput-backwardcompatible" }, "type": "number" diff --git a/bundles/af-core/src/test/resources/form/numberinput/exporter-numberinput-customized.json b/bundles/af-core/src/test/resources/form/numberinput/exporter-numberinput-customized.json index 8524d6ab96..197f8ce74d 100644 --- a/bundles/af-core/src/test/resources/form/numberinput/exporter-numberinput-customized.json +++ b/bundles/af-core/src/test/resources/form/numberinput/exporter-numberinput-customized.json @@ -28,10 +28,7 @@ "afs:layout": { "tooltipVisible": false }, - "fd:dor": { - "dorExclusion": false - }, "fd:path": "/content/numberinput-customized" }, ":type": "core/fd/components/form/numberinput/v1/numberinput" -} \ No newline at end of file +} diff --git a/bundles/af-core/src/test/resources/form/numberinput/exporter-numberinput-datalayer.json b/bundles/af-core/src/test/resources/form/numberinput/exporter-numberinput-datalayer.json index cefbb1f46c..987abfb94b 100644 --- a/bundles/af-core/src/test/resources/form/numberinput/exporter-numberinput-datalayer.json +++ b/bundles/af-core/src/test/resources/form/numberinput/exporter-numberinput-datalayer.json @@ -13,9 +13,6 @@ ] }, "properties": { - "fd:dor": { - "dorExclusion": false - }, "fd:path": "/content/numberinput-datalayer" }, ":type": "core/fd/components/form/numberinput/v1/numberinput", @@ -27,4 +24,4 @@ "fieldType": "number-input" } } -} \ No newline at end of file +} diff --git a/bundles/af-core/src/test/resources/form/numberinput/exporter-numberinput-displayvalueExpression.json b/bundles/af-core/src/test/resources/form/numberinput/exporter-numberinput-displayvalueExpression.json index 40076d98d0..63d5f2a61b 100644 --- a/bundles/af-core/src/test/resources/form/numberinput/exporter-numberinput-displayvalueExpression.json +++ b/bundles/af-core/src/test/resources/form/numberinput/exporter-numberinput-displayvalueExpression.json @@ -13,9 +13,6 @@ ] }, "properties": { - "fd:dor": { - "dorExclusion": false - }, "fd:path": "/content/numberinput-displayvalueExpression" }, ":type": "core/fd/components/form/numberinput/v1/numberinput" diff --git a/bundles/af-core/src/test/resources/form/numberinput/exporter-numberinput-with-both-display-formats.json b/bundles/af-core/src/test/resources/form/numberinput/exporter-numberinput-with-both-display-formats.json index 6fb1ed87e3..e042ab27e8 100644 --- a/bundles/af-core/src/test/resources/form/numberinput/exporter-numberinput-with-both-display-formats.json +++ b/bundles/af-core/src/test/resources/form/numberinput/exporter-numberinput-with-both-display-formats.json @@ -13,10 +13,7 @@ ] }, "properties": { - "fd:dor": { - "dorExclusion": false - }, "fd:path": "/content/numberinput-with-both-display-formats" }, ":type": "core/fd/components/form/numberinput/v1/numberinput" -} \ No newline at end of file +} diff --git a/bundles/af-core/src/test/resources/form/numberinput/exporter-numberinput-with-custom-display-format.json b/bundles/af-core/src/test/resources/form/numberinput/exporter-numberinput-with-custom-display-format.json index eccca02a97..d8c323c37d 100644 --- a/bundles/af-core/src/test/resources/form/numberinput/exporter-numberinput-with-custom-display-format.json +++ b/bundles/af-core/src/test/resources/form/numberinput/exporter-numberinput-with-custom-display-format.json @@ -13,10 +13,7 @@ ] }, "properties": { - "fd:dor": { - "dorExclusion": false - }, "fd:path": "/content/numberinput-with-custom-display-format" }, ":type": "core/fd/components/form/numberinput/v1/numberinput" -} \ No newline at end of file +} diff --git a/bundles/af-core/src/test/resources/form/numberinput/exporter-numberinput.json b/bundles/af-core/src/test/resources/form/numberinput/exporter-numberinput.json index a4fa710e07..9d8b79b638 100644 --- a/bundles/af-core/src/test/resources/form/numberinput/exporter-numberinput.json +++ b/bundles/af-core/src/test/resources/form/numberinput/exporter-numberinput.json @@ -11,14 +11,11 @@ "$event.payload" ] }, - "minimum" : 100.22, - "maximum" : 102.44, + "minimum": 100.22, + "maximum": 102.44, "properties": { - "fd:dor": { - "dorExclusion": false - }, "fd:path": "/content/numberinput", "customProp": "customPropValue" }, ":type": "core/fd/components/form/numberinput/v1/numberinput" -} \ No newline at end of file +} diff --git a/bundles/af-core/src/test/resources/form/panel/exporter-array-panel.json b/bundles/af-core/src/test/resources/form/panel/exporter-array-panel.json index 288288f1a1..ce1b41d9a3 100644 --- a/bundles/af-core/src/test/resources/form/panel/exporter-array-panel.json +++ b/bundles/af-core/src/test/resources/form/panel/exporter-array-panel.json @@ -19,11 +19,6 @@ "afs:layout": { "tooltipVisible": false }, - "fd:dor": { - "dorExclusion": false, - "dorExcludeTitle": false, - "dorExcludeDescription": false - }, "fd:path": "/content/array-panel" }, ":itemsOrder": [ @@ -48,9 +43,6 @@ ] }, "properties": { - "fd:dor": { - "dorExclusion": false - }, "fd:path": "/content/array-panel/textinput" }, ":type": "core/fd/components/form/textinput/v1/textinput" diff --git a/bundles/af-core/src/test/resources/form/panel/exporter-bound-panel.json b/bundles/af-core/src/test/resources/form/panel/exporter-bound-panel.json index cd5e0c708b..4ad7ab2827 100644 --- a/bundles/af-core/src/test/resources/form/panel/exporter-bound-panel.json +++ b/bundles/af-core/src/test/resources/form/panel/exporter-bound-panel.json @@ -18,11 +18,6 @@ "afs:layout": { "tooltipVisible": false }, - "fd:dor": { - "dorExclusion": false, - "dorExcludeTitle": false, - "dorExcludeDescription": false - }, "fd:path": "/content/bound-panel" }, ":itemsOrder": [ @@ -47,9 +42,6 @@ ] }, "properties": { - "fd:dor": { - "dorExclusion": false - }, "fd:path": "/content/bound-panel/textinput" }, ":type": "core/fd/components/form/textinput/v1/textinput" diff --git a/bundles/af-core/src/test/resources/form/panel/exporter-panel-items-array.json b/bundles/af-core/src/test/resources/form/panel/exporter-panel-items-array.json new file mode 100644 index 0000000000..2b149349cc --- /dev/null +++ b/bundles/af-core/src/test/resources/form/panel/exporter-panel-items-array.json @@ -0,0 +1,38 @@ +{ + "id": "panel-d29e24f4ca", + "fieldType": "panel", + "name": "abc", + "label": { + "value": "dependent names" + }, + "events": { + "custom:setProperty": [ + "$event.payload" + ] + }, + "properties": { + "fd:path": "/content/panel", + "customProp": "customPropValue" + }, + ":type": "core/fd/components/form/panel/v1/panel", + "items": [ + { + "id": "textinput-3a2afcbdfd", + "fieldType": "text-input", + "name": "abc", + "type": "string", + "label": { + "value": "def" + }, + "events": { + "custom:setProperty": [ + "$event.payload" + ] + }, + "properties": { + "fd:path": "/content/panel/textinput" + }, + ":type": "core/fd/components/form/textinput/v1/textinput" + } + ] +} diff --git a/bundles/af-core/src/test/resources/form/panel/exporter-panel.json b/bundles/af-core/src/test/resources/form/panel/exporter-panel.json index 23967a6687..ec3d05132a 100644 --- a/bundles/af-core/src/test/resources/form/panel/exporter-panel.json +++ b/bundles/af-core/src/test/resources/form/panel/exporter-panel.json @@ -11,11 +11,6 @@ ] }, "properties": { - "fd:dor": { - "dorExclusion": false, - "dorExcludeTitle": false, - "dorExcludeDescription": false - }, "fd:path": "/content/panel", "customProp": "customPropValue" }, @@ -38,9 +33,6 @@ ] }, "properties": { - "fd:dor": { - "dorExclusion": false - }, "fd:path": "/content/panel/textinput" }, ":type": "core/fd/components/form/textinput/v1/textinput" diff --git a/bundles/af-core/src/test/resources/form/panel/exporter-rules-panel.json b/bundles/af-core/src/test/resources/form/panel/exporter-rules-panel.json index c94237d216..01cc76c9a6 100644 --- a/bundles/af-core/src/test/resources/form/panel/exporter-rules-panel.json +++ b/bundles/af-core/src/test/resources/form/panel/exporter-rules-panel.json @@ -22,11 +22,6 @@ "afs:layout": { "tooltipVisible": false }, - "fd:dor": { - "dorExclusion": false, - "dorExcludeTitle": false, - "dorExcludeDescription": false - }, "fd:path": "/content/rules-panel" }, ":itemsOrder": [ @@ -50,9 +45,6 @@ ] }, "properties": { - "fd:dor": { - "dorExclusion": false - }, "fd:path": "/content/rules-panel/textinput" }, ":type": "core/fd/components/form/textinput/v1/textinput" diff --git a/bundles/af-core/src/test/resources/form/panelcontainer/exporter-array-panelcontainer.json b/bundles/af-core/src/test/resources/form/panelcontainer/exporter-array-panelcontainer.json index 148110b761..4666fdff58 100644 --- a/bundles/af-core/src/test/resources/form/panelcontainer/exporter-array-panelcontainer.json +++ b/bundles/af-core/src/test/resources/form/panelcontainer/exporter-array-panelcontainer.json @@ -1,14 +1,14 @@ { - "id":"panelcontainer-c551a729ed", - "description":"dependent names description", - "tooltip":"dependent names tooltip", - "fieldType":"panel", - "name":"abc", + "id": "panelcontainer-c551a729ed", + "description": "dependent names description", + "tooltip": "dependent names tooltip", + "fieldType": "panel", + "name": "abc", "repeatable": true, - "minOccur":1, - "maxOccur":10, + "minOccur": 1, + "maxOccur": 10, ":items": { - "textinput" : { + "textinput": { "id": "textinput-fae1400d3e", "fieldType": "text-input", "name": "abc", @@ -25,15 +25,14 @@ ] }, "properties": { - "fd:dor": { - "dorExclusion": false - }, "fd:path": "/content/array-panelcontainer/textinput" }, ":type": "core/fd/components/form/textinput/v1/textinput" } }, - ":itemsOrder" : ["textinput"], + ":itemsOrder": [ + "textinput" + ], "label": { "value": "dependent names" }, @@ -46,12 +45,7 @@ "afs:layout": { "tooltipVisible": false }, - "fd:dor": { - "dorExclusion": false, - "dorExcludeTitle": false, - "dorExcludeDescription": false - }, "fd:path": "/content/array-panelcontainer" }, ":type": "core/fd/components/form/panelcontainer/v1/panelcontainer" -} \ No newline at end of file +} diff --git a/bundles/af-core/src/test/resources/form/panelcontainer/exporter-panelcontainer-customized.json b/bundles/af-core/src/test/resources/form/panelcontainer/exporter-panelcontainer-customized.json index 4c5f3e1077..6bbd9ce910 100644 --- a/bundles/af-core/src/test/resources/form/panelcontainer/exporter-panelcontainer-customized.json +++ b/bundles/af-core/src/test/resources/form/panelcontainer/exporter-panelcontainer-customized.json @@ -1,14 +1,14 @@ { - "id":"panelcontainer-d7d0a241a4", - "description":"dependent names description", - "tooltip":"dependent names tooltip", - "fieldType":"panel", - "name":"abc", + "id": "panelcontainer-d7d0a241a4", + "description": "dependent names description", + "tooltip": "dependent names tooltip", + "fieldType": "panel", + "name": "abc", "visible": false, "enabled": false, "readOnly": true, ":items": { - "textinput" : { + "textinput": { "id": "textinput-7505d0a80f", "fieldType": "text-input", "name": "abc", @@ -25,15 +25,14 @@ ] }, "properties": { - "fd:dor": { - "dorExclusion": false - }, "fd:path": "/content/panelcontainer-customized/textinput" }, ":type": "core/fd/components/form/textinput/v1/textinput" } }, - ":itemsOrder": ["textinput"], + ":itemsOrder": [ + "textinput" + ], "label": { "value": "dependent names" }, @@ -59,4 +58,4 @@ "fd:path": "/content/panelcontainer-customized" }, ":type": "core/fd/components/form/panelcontainer/v1/panelcontainer" -} \ No newline at end of file +} diff --git a/bundles/af-core/src/test/resources/form/panelcontainer/exporter-panelcontainer-datalayer.json b/bundles/af-core/src/test/resources/form/panelcontainer/exporter-panelcontainer-datalayer.json index 71210fb79d..038ee97194 100644 --- a/bundles/af-core/src/test/resources/form/panelcontainer/exporter-panelcontainer-datalayer.json +++ b/bundles/af-core/src/test/resources/form/panelcontainer/exporter-panelcontainer-datalayer.json @@ -15,11 +15,6 @@ ] }, "properties": { - "fd:dor": { - "dorExclusion": false, - "dorExcludeTitle": false, - "dorExcludeDescription": false - }, "fd:path": "/content/panelcontainer-datalayer" }, ":itemsOrder": [ @@ -44,9 +39,6 @@ ] }, "properties": { - "fd:dor": { - "dorExclusion": false - }, "fd:path": "/content/panelcontainer-datalayer/textinput" }, ":type": "core/fd/components/form/textinput/v1/textinput" @@ -61,4 +53,4 @@ "fieldType": "panel" } } -} \ No newline at end of file +} diff --git a/bundles/af-core/src/test/resources/form/panelcontainer/exporter-panelcontainer.json b/bundles/af-core/src/test/resources/form/panelcontainer/exporter-panelcontainer.json index bef37ad96b..3231324685 100644 --- a/bundles/af-core/src/test/resources/form/panelcontainer/exporter-panelcontainer.json +++ b/bundles/af-core/src/test/resources/form/panelcontainer/exporter-panelcontainer.json @@ -17,9 +17,6 @@ ] }, "properties": { - "fd:dor": { - "dorExclusion": false - }, "fd:path": "/content/panelcontainer/textinput" }, ":type": "core/fd/components/form/textinput/v1/textinput" @@ -35,11 +32,6 @@ ] }, "properties": { - "fd:dor": { - "dorExclusion": false, - "dorExcludeTitle": false, - "dorExcludeDescription": false - }, "fd:path": "/content/panelcontainer", "customProp": "customPropValue" }, diff --git a/bundles/af-core/src/test/resources/form/panelcontainer/exporter-rules-panelcontainer.json b/bundles/af-core/src/test/resources/form/panelcontainer/exporter-rules-panelcontainer.json index 2561545e00..0412ae4ef0 100644 --- a/bundles/af-core/src/test/resources/form/panelcontainer/exporter-rules-panelcontainer.json +++ b/bundles/af-core/src/test/resources/form/panelcontainer/exporter-rules-panelcontainer.json @@ -1,11 +1,11 @@ { - "id":"panelcontainer-da2cf7dff5", - "description":"dependent names description", - "tooltip":"dependent names tooltip", - "fieldType":"panel", - "name":"abc", + "id": "panelcontainer-da2cf7dff5", + "description": "dependent names description", + "tooltip": "dependent names tooltip", + "fieldType": "panel", + "name": "abc", ":items": { - "textinput" : { + "textinput": { "id": "textinput-2d7a7460fa", "fieldType": "text-input", "name": "abc", @@ -23,15 +23,14 @@ ] }, "properties": { - "fd:dor": { - "dorExclusion": false - }, "fd:path": "/content/rules-panelcontainer/textinput" }, ":type": "core/fd/components/form/textinput/v1/textinput" } - }, - ":itemsOrder": ["textinput"], + }, + ":itemsOrder": [ + "textinput" + ], "label": { "value": "dependent names" }, @@ -47,15 +46,10 @@ "afs:layout": { "tooltipVisible": false }, - "fd:dor": { - "dorExclusion": false, - "dorExcludeTitle": false, - "dorExcludeDescription": false - }, "fd:path": "/content/rules-panelcontainer" }, "rules": { "visible": "some expression" }, ":type": "core/fd/components/form/panelcontainer/v1/panelcontainer" -} \ No newline at end of file +} diff --git a/bundles/af-core/src/test/resources/form/radiobutton/exporter-radiobutton-datalayer.json b/bundles/af-core/src/test/resources/form/radiobutton/exporter-radiobutton-datalayer.json index 6662d77b6d..0dc0ae2964 100644 --- a/bundles/af-core/src/test/resources/form/radiobutton/exporter-radiobutton-datalayer.json +++ b/bundles/af-core/src/test/resources/form/radiobutton/exporter-radiobutton-datalayer.json @@ -24,9 +24,6 @@ "afs:layout": { "orientation": "horizontal" }, - "fd:dor": { - "dorExclusion": false - }, "fd:path": "/content/radiobutton-datalayer" }, "enum": [ @@ -42,4 +39,4 @@ "fieldType": "radio-group" } } -} \ No newline at end of file +} diff --git a/bundles/af-core/src/test/resources/form/radiobutton/exporter-radiobutton.json b/bundles/af-core/src/test/resources/form/radiobutton/exporter-radiobutton.json index 13b70b9056..5fb8b38fbe 100644 --- a/bundles/af-core/src/test/resources/form/radiobutton/exporter-radiobutton.json +++ b/bundles/af-core/src/test/resources/form/radiobutton/exporter-radiobutton.json @@ -20,9 +20,6 @@ "afs:layout": { "orientation": "vertical" }, - "fd:dor": { - "dorExclusion": false - }, "fd:path": "/content/radiobutton", "customProp": "customPropValue" }, diff --git a/bundles/af-core/src/test/resources/form/recaptcha/exporter-recaptcha.json b/bundles/af-core/src/test/resources/form/recaptcha/exporter-recaptcha.json index d5f597853f..636bd13dc3 100644 --- a/bundles/af-core/src/test/resources/form/recaptcha/exporter-recaptcha.json +++ b/bundles/af-core/src/test/resources/form/recaptcha/exporter-recaptcha.json @@ -16,9 +16,6 @@ ] }, "properties": { - "fd:dor": { - "dorExclusion": false - }, "fd:path": "/content/recaptcha", "fd:captcha": { "provider": "recaptcha", diff --git a/bundles/af-core/src/test/resources/form/scribble/exporter-scribble.json b/bundles/af-core/src/test/resources/form/scribble/exporter-scribble.json index 66b6213b46..330bcf6726 100644 --- a/bundles/af-core/src/test/resources/form/scribble/exporter-scribble.json +++ b/bundles/af-core/src/test/resources/form/scribble/exporter-scribble.json @@ -4,9 +4,6 @@ "name": "scribble1710484027357", "type": "string", "properties": { - "fd:dor": { - "dorExclusion": false - }, "fd:path": "/content/scribble", "fd:viewType": "signature", "fd:dialogLabel": "Scribble" diff --git a/bundles/af-core/src/test/resources/form/switch/exporter-switch-customized.json b/bundles/af-core/src/test/resources/form/switch/exporter-switch-customized.json index af510eabc4..88e1944187 100644 --- a/bundles/af-core/src/test/resources/form/switch/exporter-switch-customized.json +++ b/bundles/af-core/src/test/resources/form/switch/exporter-switch-customized.json @@ -28,9 +28,6 @@ "afs:layout": { "tooltipVisible": false }, - "fd:dor": { - "dorExclusion": false - }, "fd:path": "/content/switch-customized" }, "enum": [ diff --git a/bundles/af-core/src/test/resources/form/switch/exporter-switch.json b/bundles/af-core/src/test/resources/form/switch/exporter-switch.json index 46f3f2de1a..b5ac1e64bf 100644 --- a/bundles/af-core/src/test/resources/form/switch/exporter-switch.json +++ b/bundles/af-core/src/test/resources/form/switch/exporter-switch.json @@ -29,9 +29,6 @@ "afs:layout": { "tooltipVisible": false }, - "fd:dor": { - "dorExclusion": false - }, "fd:path": "/content/switch", "customProp": "customPropValue" }, diff --git a/bundles/af-core/src/test/resources/form/table/test-content.json b/bundles/af-core/src/test/resources/form/table/test-content.json new file mode 100644 index 0000000000..b8f78a18b9 --- /dev/null +++ b/bundles/af-core/src/test/resources/form/table/test-content.json @@ -0,0 +1,71 @@ +{ + "table": { + "jcr:primaryType": "nt:unstructured", + "sling:resourceType": "core/fd/components/form/table/v1/table", + "name": "myTable", + "jcr:title": "My Table", + "fieldType": "panel" + }, + "table-with-column-width": { + "jcr:primaryType": "nt:unstructured", + "sling:resourceType": "core/fd/components/form/table/v1/table", + "name": "myTableWithWidths", + "jcr:title": "My Table With Column Widths", + "fieldType": "panel", + "columnWidth": "1,2,1" + }, + "table-with-sorting": { + "jcr:primaryType": "nt:unstructured", + "sling:resourceType": "core/fd/components/form/table/v1/table", + "name": "myTableWithSorting", + "jcr:title": "My Table With Sorting", + "fieldType": "panel", + "enableSorting": true + }, + "table-with-equal-widths": { + "jcr:primaryType": "nt:unstructured", + "sling:resourceType": "core/fd/components/form/table/v1/table", + "name": "myTableWithEqualWidths", + "jcr:title": "My Table Equal Widths", + "fieldType": "panel", + "columnWidth": "1,1,1" + }, + "table-with-invalid-width": { + "jcr:primaryType": "nt:unstructured", + "sling:resourceType": "core/fd/components/form/table/v1/table", + "name": "myTableWithInvalidWidth", + "jcr:title": "My Table Invalid Width", + "fieldType": "panel", + "columnWidth": "abc,1" + }, + "table-with-negative-width": { + "jcr:primaryType": "nt:unstructured", + "sling:resourceType": "core/fd/components/form/table/v1/table", + "name": "myTableWithNegativeWidth", + "jcr:title": "My Table Negative Width", + "fieldType": "panel", + "columnWidth": "-1,2,3" + }, + "table-with-zero-widths": { + "jcr:primaryType": "nt:unstructured", + "sling:resourceType": "core/fd/components/form/table/v1/table", + "name": "myTableWithZeroWidths", + "jcr:title": "My Table Zero Widths", + "fieldType": "panel", + "columnWidth": "0,0" + }, + "tableheader": { + "jcr:primaryType": "nt:unstructured", + "sling:resourceType": "core/fd/components/form/tableheader/v1/tableheader", + "name": "myTableHeader", + "jcr:title": "My Table Header", + "fieldType": "panel" + }, + "tablerow": { + "jcr:primaryType": "nt:unstructured", + "sling:resourceType": "core/fd/components/form/tablerow/v1/tablerow", + "name": "myTableRow", + "jcr:title": "My Table Row", + "fieldType": "panel" + } +} diff --git a/bundles/af-core/src/test/resources/form/termsandconditions/exporter-termsandconditions.json b/bundles/af-core/src/test/resources/form/termsandconditions/exporter-termsandconditions.json index 0a4f38eebb..6f72c3d44e 100644 --- a/bundles/af-core/src/test/resources/form/termsandconditions/exporter-termsandconditions.json +++ b/bundles/af-core/src/test/resources/form/termsandconditions/exporter-termsandconditions.json @@ -4,13 +4,9 @@ "name": "termsandconditions1694159302516", "type": "object", "properties": { - "fd:dor": { - "dorExclusion": false, - "dorExcludeTitle": false, - "dorExcludeDescription": false - }, "fd:path": "/content/termsandconditions", "fd:tnc": true, + "fd:showAsPopup": true, "customProp": "customPropValue" }, "label": { @@ -31,17 +27,13 @@ "fieldType": "plain-text", "name": "consenttext", "value": "Text related to the terms and conditions come here", - "richText": false, "events": { "custom:setProperty": [ "$event.payload" ] }, "properties": { - "fd:path": "/content/termsandconditions/text", - "fd:dor": { - "dorExclusion": false - } + "fd:path": "/content/termsandconditions/text" }, ":type": "core/fd/components/form/text/v1/text" }, @@ -61,9 +53,6 @@ ] }, "properties": { - "fd:dor": { - "dorExclusion": false - }, "fd:path": "/content/termsandconditions/approvalcheckbox" }, "enum": [ diff --git a/bundles/af-core/src/test/resources/form/termsandconditions/exporter-termsandconditionsNoWrapData.json b/bundles/af-core/src/test/resources/form/termsandconditions/exporter-termsandconditionsNoWrapData.json index a2e0b6ead3..41dfbd898a 100644 --- a/bundles/af-core/src/test/resources/form/termsandconditions/exporter-termsandconditionsNoWrapData.json +++ b/bundles/af-core/src/test/resources/form/termsandconditions/exporter-termsandconditionsNoWrapData.json @@ -3,13 +3,9 @@ "fieldType": "panel", "name": "termsandconditions1694159302516", "properties": { - "fd:dor": { - "dorExclusion": false, - "dorExcludeTitle": false, - "dorExcludeDescription": false - }, "fd:path": "/content/termsandconditionsNoWrapData", - "fd:tnc": true + "fd:tnc": true, + "fd:showAsPopup": true }, "label": { "value": "Terms And Conditions" @@ -41,9 +37,6 @@ ] }, "properties": { - "fd:dor": { - "dorExclusion": false - }, "fd:path": "/content/termsandconditionsNoWrapData/approvalcheckbox" }, "enum": [ @@ -72,9 +65,6 @@ "afs:layout": { "orientation": "vertical" }, - "fd:dor": { - "dorExclusion": false - }, "fd:path": "/content/termsandconditionsNoWrapData/link" }, "enum": [ diff --git a/bundles/af-core/src/test/resources/form/text/exporter-text-datalayer.json b/bundles/af-core/src/test/resources/form/text/exporter-text-datalayer.json index 143d36f683..01205ff719 100644 --- a/bundles/af-core/src/test/resources/form/text/exporter-text-datalayer.json +++ b/bundles/af-core/src/test/resources/form/text/exporter-text-datalayer.json @@ -3,16 +3,12 @@ "fieldType": "plain-text", "name": "text1680491190398", "value": "This is an AF", - "richText": false, "events": { "custom:setProperty": [ "$event.payload" ] }, "properties": { - "fd:dor": { - "dorExclusion": false - }, "fd:path": "/content/text-datalayer" }, ":type": "core/fd/components/form/text/v1/text", @@ -23,4 +19,4 @@ "fieldType": "plain-text" } } -} \ No newline at end of file +} diff --git a/bundles/af-core/src/test/resources/form/text/exporter-text-with-viewtype.json b/bundles/af-core/src/test/resources/form/text/exporter-text-with-viewtype.json index a3625ed9f6..30459b8d8b 100644 --- a/bundles/af-core/src/test/resources/form/text/exporter-text-with-viewtype.json +++ b/bundles/af-core/src/test/resources/form/text/exporter-text-with-viewtype.json @@ -2,21 +2,13 @@ "id": "text-2c82909883", "fieldType": "plain-text", "name": "abc", - "richText": false, - "properties": - { - "fd:dor": - { - "dorExclusion": false - }, + "properties": { "fd:path": "/content/text-with-viewtype" }, - "events": - { - "custom:setProperty": - [ + "events": { + "custom:setProperty": [ "$event.payload" ] }, ":type": "text123" -} \ No newline at end of file +} diff --git a/bundles/af-core/src/test/resources/form/text/exporter-text.json b/bundles/af-core/src/test/resources/form/text/exporter-text.json index c1c6fc941b..cfbcb08eb2 100644 --- a/bundles/af-core/src/test/resources/form/text/exporter-text.json +++ b/bundles/af-core/src/test/resources/form/text/exporter-text.json @@ -1,14 +1,10 @@ { "id": "text-ac0bcff4f3", "fieldType": "plain-text", - "richText": false, "name": "abc", ":type": "core/fd/components/form/text/v1/text", "properties": { "fd:path":"/content/text", - "fd:dor": { - "dorExclusion": false - }, "customProp": "customPropValue" }, "events": { @@ -16,4 +12,4 @@ "$event.payload" ] } -} \ No newline at end of file +} diff --git a/bundles/af-core/src/test/resources/form/textinput/exporter-multiline-textinput.json b/bundles/af-core/src/test/resources/form/textinput/exporter-multiline-textinput.json index 39b223aee1..164f4d9ec0 100644 --- a/bundles/af-core/src/test/resources/form/textinput/exporter-multiline-textinput.json +++ b/bundles/af-core/src/test/resources/form/textinput/exporter-multiline-textinput.json @@ -17,9 +17,6 @@ ] }, "properties": { - "fd:dor": { - "dorExclusion": false - }, "fd:path": "/content/multiline-textinput" }, ":type": "core/fd/components/form/textinput/v1/textinput" diff --git a/bundles/af-core/src/test/resources/form/textinput/exporter-textinput-blank-dataref.json b/bundles/af-core/src/test/resources/form/textinput/exporter-textinput-blank-dataref.json index 6d6c1e3e90..e53443df71 100644 --- a/bundles/af-core/src/test/resources/form/textinput/exporter-textinput-blank-dataref.json +++ b/bundles/af-core/src/test/resources/form/textinput/exporter-textinput-blank-dataref.json @@ -12,9 +12,6 @@ ] }, "properties": { - "fd:dor": { - "dorExclusion": false - }, "fd:path": "/content/textinput-blank-dataref" }, ":type": "core/fd/components/form/textinput/v1/textinput" diff --git a/bundles/af-core/src/test/resources/form/textinput/exporter-textinput-blank-validationExpression.json b/bundles/af-core/src/test/resources/form/textinput/exporter-textinput-blank-validationExpression.json index 3cb5c353df..8e3ba447c0 100644 --- a/bundles/af-core/src/test/resources/form/textinput/exporter-textinput-blank-validationExpression.json +++ b/bundles/af-core/src/test/resources/form/textinput/exporter-textinput-blank-validationExpression.json @@ -12,9 +12,6 @@ ] }, "properties": { - "fd:dor": { - "dorExclusion": false - }, "fd:path": "/content/textinput-blank-validationExpression" }, ":type": "core/fd/components/form/textinput/v1/textinput" diff --git a/bundles/af-core/src/test/resources/form/textinput/exporter-textinput-datalayer.json b/bundles/af-core/src/test/resources/form/textinput/exporter-textinput-datalayer.json index 350245149a..2848b7b50c 100644 --- a/bundles/af-core/src/test/resources/form/textinput/exporter-textinput-datalayer.json +++ b/bundles/af-core/src/test/resources/form/textinput/exporter-textinput-datalayer.json @@ -21,9 +21,6 @@ "afs:layout": { "tooltipVisible": false }, - "fd:dor": { - "dorExclusion": false - }, "fd:path": "/content/textinput-datalayer" }, ":type": "core/fd/components/form/textinput/v1/textinput", diff --git a/bundles/af-core/src/test/resources/form/textinput/exporter-textinput-displayValueExpression.json b/bundles/af-core/src/test/resources/form/textinput/exporter-textinput-displayValueExpression.json index 1f0cbde4fd..0271631929 100644 --- a/bundles/af-core/src/test/resources/form/textinput/exporter-textinput-displayValueExpression.json +++ b/bundles/af-core/src/test/resources/form/textinput/exporter-textinput-displayValueExpression.json @@ -13,9 +13,6 @@ ] }, "properties": { - "fd:dor": { - "dorExclusion": false - }, "fd:path": "/content/textinput-displayValueExpression" }, ":type": "core/fd/components/form/textinput/v1/textinput" diff --git a/bundles/af-core/src/test/resources/form/textinput/exporter-textinput-format.json b/bundles/af-core/src/test/resources/form/textinput/exporter-textinput-format.json index 4e1d9617a2..551038ba5d 100644 --- a/bundles/af-core/src/test/resources/form/textinput/exporter-textinput-format.json +++ b/bundles/af-core/src/test/resources/form/textinput/exporter-textinput-format.json @@ -29,9 +29,6 @@ "afs:layout": { "tooltipVisible": false }, - "fd:dor": { - "dorExclusion": false - }, "fd:path": "/content/textinput-format" }, ":type": "core/fd/components/form/textinput/v1/textinput" diff --git a/bundles/af-core/src/test/resources/form/textinput/exporter-textinput-placeholder-autocomplete.json b/bundles/af-core/src/test/resources/form/textinput/exporter-textinput-placeholder-autocomplete.json index 328b15f9d3..4d0014d7e8 100644 --- a/bundles/af-core/src/test/resources/form/textinput/exporter-textinput-placeholder-autocomplete.json +++ b/bundles/af-core/src/test/resources/form/textinput/exporter-textinput-placeholder-autocomplete.json @@ -12,9 +12,6 @@ ] }, "properties": { - "fd:dor": { - "dorExclusion": false - }, "fd:path": "/content/textinput-placeholder-autocomplete" }, "placeholder": "test-placeholder", diff --git a/bundles/af-core/src/test/resources/form/textinput/exporter-textinput-with-viewtype.json b/bundles/af-core/src/test/resources/form/textinput/exporter-textinput-with-viewtype.json index 627e670878..c731dda168 100644 --- a/bundles/af-core/src/test/resources/form/textinput/exporter-textinput-with-viewtype.json +++ b/bundles/af-core/src/test/resources/form/textinput/exporter-textinput-with-viewtype.json @@ -12,9 +12,6 @@ ] }, "properties": { - "fd:dor": { - "dorExclusion": false - }, "fd:path": "/content/textinput-with-viewtype" }, "placeholder": "test-placeholder", diff --git a/bundles/af-core/src/test/resources/form/textinput/exporter-textinput.json b/bundles/af-core/src/test/resources/form/textinput/exporter-textinput.json index 77bb63ba2c..9f92d75b53 100644 --- a/bundles/af-core/src/test/resources/form/textinput/exporter-textinput.json +++ b/bundles/af-core/src/test/resources/form/textinput/exporter-textinput.json @@ -12,9 +12,6 @@ ] }, "properties": { - "fd:dor": { - "dorExclusion": false - }, "fd:path": "/content/textinput", "customPropString": "test-custom-string", "customPropStringArray": ["a", "b"], diff --git a/bundles/af-core/src/test/resources/form/textinput/exporter-textinput_unboundFormElement.json b/bundles/af-core/src/test/resources/form/textinput/exporter-textinput_unboundFormElement.json index 6a40860e03..3c62c1de5c 100644 --- a/bundles/af-core/src/test/resources/form/textinput/exporter-textinput_unboundFormElement.json +++ b/bundles/af-core/src/test/resources/form/textinput/exporter-textinput_unboundFormElement.json @@ -13,9 +13,6 @@ ] }, "properties": { - "fd:dor": { - "dorExclusion": false - }, "fd:path": "/content/textinput_unboundFormElement" }, ":type": "core/fd/components/form/textinput/v1/textinput" diff --git a/bundles/af-core/src/test/resources/form/textinput/test-content.json b/bundles/af-core/src/test/resources/form/textinput/test-content.json index 626c32b7f5..9fad69379c 100644 --- a/bundles/af-core/src/test/resources/form/textinput/test-content.json +++ b/bundles/af-core/src/test/resources/form/textinput/test-content.json @@ -139,9 +139,6 @@ "afs:layout": { "tooltipVisible": false }, - "fd:dor": { - "dorExclusion": false - }, "fd:path": "/content/forms/af/af2/jcr:content/guideContainer/textinput" } }, diff --git a/bundles/af-core/src/test/resources/form/titleV2/exporter-title-noprops.json b/bundles/af-core/src/test/resources/form/titleV2/exporter-title-noprops.json index 922d111bc0..9c55b93da9 100644 --- a/bundles/af-core/src/test/resources/form/titleV2/exporter-title-noprops.json +++ b/bundles/af-core/src/test/resources/form/titleV2/exporter-title-noprops.json @@ -2,9 +2,6 @@ "id": "title-c509f1aa61", "fieldType": "plain-text", "properties": { - "fd:dor": { - "dorExclusion": false - }, "fd:path": "/content/title-noprops", "fd:htmlelementType": "h2" }, diff --git a/bundles/af-core/src/test/resources/form/titleV2/exporter-title-type.json b/bundles/af-core/src/test/resources/form/titleV2/exporter-title-type.json index 883683bec1..7cfd26a7ed 100644 --- a/bundles/af-core/src/test/resources/form/titleV2/exporter-title-type.json +++ b/bundles/af-core/src/test/resources/form/titleV2/exporter-title-type.json @@ -3,9 +3,6 @@ "fieldType": "plain-text", "value": "Title_custom", "properties": { - "fd:dor": { - "dorExclusion": false - }, "fd:path": "/content/title-type", "fd:htmlelementType": "h3" }, diff --git a/bundles/af-core/src/test/resources/form/titleV2/exporter-title-wrongtype.json b/bundles/af-core/src/test/resources/form/titleV2/exporter-title-wrongtype.json index fac07172c2..b7c551dcaf 100644 --- a/bundles/af-core/src/test/resources/form/titleV2/exporter-title-wrongtype.json +++ b/bundles/af-core/src/test/resources/form/titleV2/exporter-title-wrongtype.json @@ -2,9 +2,6 @@ "id": "title-4a1ad605c0", "fieldType": "plain-text", "properties": { - "fd:dor": { - "dorExclusion": false - }, "fd:path": "/content/title-wrongtype", "fd:htmlelementType": null }, diff --git a/bundles/af-core/src/test/resources/form/titleV2/exporter-title.json b/bundles/af-core/src/test/resources/form/titleV2/exporter-title.json index bc12102e9d..eae00244e5 100644 --- a/bundles/af-core/src/test/resources/form/titleV2/exporter-title.json +++ b/bundles/af-core/src/test/resources/form/titleV2/exporter-title.json @@ -3,9 +3,6 @@ "fieldType": "plain-text", "value": "Title", "properties": { - "fd:dor": { - "dorExclusion": false - }, "fd:path": "/content/title", "fd:htmlelementType": null }, 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 index 1afda00aa6..2be79354eb 100644 --- a/bundles/af-core/src/test/resources/form/turnstile/exporter-turnstile.json +++ b/bundles/af-core/src/test/resources/form/turnstile/exporter-turnstile.json @@ -8,9 +8,6 @@ "enabled": true, "readOnly": false, "properties": { - "fd:dor": { - "dorExclusion": false - }, "fd:path": "/content/turnstile", "fd:captcha": { "config": { diff --git a/bundles/af-core/src/test/resources/schema/0.15.2/adaptive-form-properties.schema.json b/bundles/af-core/src/test/resources/schema/0.15.2/adaptive-form-properties.schema.json index 55edb3c17a..0d762b1c1c 100644 --- a/bundles/af-core/src/test/resources/schema/0.15.2/adaptive-form-properties.schema.json +++ b/bundles/af-core/src/test/resources/schema/0.15.2/adaptive-form-properties.schema.json @@ -297,6 +297,11 @@ "title": "The path to the fragment that is used to render the fragment", "description": "This property would only be present if fd:viewType is set to fragment" }, + "lazy": { + "type": "boolean", + "title": "Whether the panel's children should be loaded lazily by the client", + "description": "When lazy is true, the panel must also have a fragmentPath property and items, :items, and :itemsOrder must be absent. The client is responsible for fetching and resolving the fragment's children on demand using the fragmentPath." + }, "id": { "type": "string", "title": "unique identifier for a form element" diff --git a/bundles/af-core/src/test/resources/schema/0.15.2/adaptive-form.schema.json b/bundles/af-core/src/test/resources/schema/0.15.2/adaptive-form.schema.json index 189875cd22..29ef85d093 100644 --- a/bundles/af-core/src/test/resources/schema/0.15.2/adaptive-form.schema.json +++ b/bundles/af-core/src/test/resources/schema/0.15.2/adaptive-form.schema.json @@ -1099,7 +1099,8 @@ "uniqueItems", "validationExpression", "visible", - "fragmentPath" + "fragmentPath", + "lazy" ], "errorMessage": { "enum": "${0} property is not allowed in Panel" @@ -1116,6 +1117,12 @@ ":items", ":itemsOrder" ] + }, + { + "required": [ + "lazy", + "fragmentPath" + ] } ] } diff --git a/docs/architecture/component-versioning.md b/docs/architecture/component-versioning.md new file mode 100644 index 0000000000..8b2c155a79 --- /dev/null +++ b/docs/architecture/component-versioning.md @@ -0,0 +1,26 @@ +# Component Versioning + +When a change requires a new component version (`v1` → `v2`), and what to check when bumping. + +## When to bump + +Bump when the change breaks a contract external code depends on: + +- DOM structure (elements added/removed/reordered, class renames) that could break authored CSS or test selectors. +- JS runtime contract (clientlib category, init/attach lifecycle, public events, exported model/JSON shape). +- Authoring contract (dialog field renames that would corrupt existing authored content). + +Do not bump for bug fixes, including accessibility fixes, or additive/non-breaking changes. Fix these forward in the existing version. Example: the datepicker `v1`→`v2` cycle (below) shows accessibility fixes being folded back into `v1` rather than kept as a `v2`-only fix. + +## Checklist + +Based on past version bumps in this repo (button/submit/reset v2, title v2, wizard v2, radiobutton v2, checkboxgroup v2, fileinput v2–v4, container v2, datepicker v2). None of these bumps completed every item below in a single commit — treat this as a review checklist, not a fixed template. + +1. **New version folder**: own `.content.xml` (title suffixed `(v)`), HTL, `README.md`. Done consistently in every past bump. +2. **Clientlib**: only needed if JS/CSS actually changed. Several past bumps (button, submit, reset, title, wizard v2) shipped no clientlib of their own and continued sharing `v1`'s. Where a clientlib was added (radiobutton, checkboxgroup, fileinput, container, datepicker v2), it was a full standalone copy — never a path into the previous version's clientlib folder. +3. **`_cq_dialog` / `_cq_design_dialog` / `_cq_styleConfig` / `_cq_template.xml`**: inherit via `sling:resourceSuperType` when unchanged (the common case). Add an own copy only where fields actually differ (e.g. fileinput v3/v4, container v2). Check `extraClientlibs`, `helpPath`, and `trackingFeature` values that hardcode a version number — these do not update via inheritance. +4. **Java model**: add `FormConstants.RT_FD_FORM__V` and update the Sling Model's `resourceType` array only if the exported model/JSON shape changed. Most past bumps (button, submit, reset, wizard, radiobutton, checkboxgroup, fileinput v4) did not touch Java at all — HTML/JS-only changes did not need it. +5. **`ui.af.apps/pom.xml`**: the `` step controls which version is visible in the component browser. This was the step most often missed in past bumps and added later in bulk cleanup PRs — verify it explicitly rather than assuming it was done alongside the version folder. +6. **Runtime clientlib embed lists** (`ui.af.apps/.../core-forms-components-runtime-all/.content.xml`, `it/apps/.../custom-forms-components-runtime-all/.content.xml`): update only if step 2 added a new clientlib category. See the inline comment in those files for the replace-vs-add-alongside rule. +7. **IT content / examples**: update any `.content.xml` with `sling:resourceSuperType` pointing at the old version, if the new version becomes the default. This was skipped entirely in some past bumps (e.g. datepicker, throughout its `v2` add-and-removal). +8. **E2E spec**: add a Cypress spec for the new version's runtime behavior. Naming has not been fully consistent historically (`v.runtime.cy.js` in most cases; `.spec.js` in older ones). diff --git a/docs/architecture/overview.md b/docs/architecture/overview.md new file mode 100644 index 0000000000..bde15e70e4 --- /dev/null +++ b/docs/architecture/overview.md @@ -0,0 +1,393 @@ +# Architecture Overview — aem-core-forms-components + +AEM Core Forms Components is the server-side component library for Adaptive Forms v2 (AF2). It provides Java Sling models, JCR content definitions, OSGi configuration, integration test infrastructure, and Cypress e2e tests for the AF2 runtime. + +--- + +## 1. Module Structure + +``` +aem-core-forms-components/ +├── bundles/af-core/ Java models, utilities, feature toggle constants +│ ├── src/main/java/ +│ │ └── .../components/ +│ │ ├── internal/form/ Component model implementations (FragmentImpl, etc.) +│ │ ├── util/ Shared utilities (AbstractContainerImpl, AbstractFormComponentImpl, ComponentUtils) +│ │ └── internal/form/FeatureToggleConstants.java All feature toggle string IDs +│ └── src/test/ +│ ├── java/ Unit tests (FragmentImplTest, PanelImplTest, TextInputImplTest) +│ └── resources/form/ Test fixture JSON (exporter-*.json, test-content.json) +├── it/ +│ ├── config/ IT OSGi configs (toggle enablement, Sling configs) +│ │ └── .../apps/system/config/ +│ │ └── com.adobe.granite.toggle.impl.dev.DynamicToggleProviderImpl.cfg.json +│ └── content/ IT test pages and fragment definitions (JCR content packages) +│ └── .../content/forms/af/core-components-it/samples/ +│ ├── fragment/ Fragment test pages + fragment definitions +│ ├── panelcontainer/ Panel container test pages +│ ├── textinput/ Text input test pages +│ └── [component]/ One directory per component +└── ui.tests/test-module/ Cypress e2e test suite + ├── cypress.config.js Base URL: localhost:4502, retries: 2 + ├── specs/ Test files organized by component (149 spec files) + ├── libs/support/ + │ ├── commands.js Custom Cypress commands (previewForm, fetchFeatureToggles, etc.) + │ └── functions.js cy.af helpers (isLatestAddon, isReleasedAddon, getFormJsonUrl) + └── libs/commons/ Shared constants and selectors +``` + +--- + +## 2. Java Model Hierarchy + +Three key base classes, each responsible for one concern. + +### `AbstractFormComponentImpl` + +Base class for all form fields (text inputs, checkboxes, dropdowns, etc.). + +Key method: `getEvents()` — injects the default `custom:setProperty` handler (`"$event.payload"`) into every component's events unless `FT_FORMS-24343` is enabled. When that toggle is on, the server omits this handler and delegates the behavior to af2-web-runtime instead. + +### `AbstractContainerImpl` + +Base class for all container components (panels, wizards, form containers). + +Key methods: +- `getExportedItems()` — returns empty map when `FT_FORMS-24358` is enabled (flat array mode). +- `getExportedItemsOrder()` — returns empty array when `FT_FORMS-24358` is enabled. +- `getExportedItemsArray()` — returns the flat items list **only** when `FT_FORMS-24358` is enabled. This is the preferred rendering path for AF2 runtime. + +### `FragmentImpl` + +Extends `AbstractContainerImpl`. Represents an embedded form fragment (a separately authored sub-form included by reference). + +Key methods: +- `getRules()` — merges fragment container rules with placeholder rules. Placeholder rules win on conflict. Merge only happens when `FT_FORMS-24087` is enabled. +- `getEvents()` — merges and appends fragment container events after placeholder events when `FT_FORMS-24087` is enabled. + +### Toggle check utility + +```java +ComponentUtils.isToggleEnabled(String toggleId) +``` + +Reads `System.getProperty(toggleId)`, returns `true` only when the value is exactly `"true"`. The system property is populated at runtime by the OSGi `DynamicToggleProviderImpl` config deployed via `it/config`. + +--- + +## 3. Form JSON Model + +The server renders a page at `.model.json`. This is consumed directly by af2-web-runtime to hydrate the form. + +### Root structure + +```json +{ + "fieldType": "form", + "items": [ + { "fieldType": "text-input", ... }, + { "fieldType": "panel", "items": [...] } + ], + ":items": { "field1": { ... } }, + ":itemsOrder": ["field1", "field2"] +} +``` + +- `items` (flat array) — present when `FT_FORMS-24358` is **ON**. AF2 runtime reads from this key. +- `:items` / `:itemsOrder` — Sling default map/order representation, present when `FT_FORMS-24358` is **OFF**. + +### Key properties + +| Property | Description | +|---|---| +| `fieldType` | Component type consumed by af2-web-runtime (`"text-input"`, `"panel"`, `"form"`, `"fragment"`) | +| `sling:resourceType` | Maps the JCR node to its Sling component implementation | +| `fd:version` | Form version string — `"2.1"` indicates Adaptive Forms v2 | +| `fd:type="fragment"` | Set on `guideContainer` to mark a page as a fragment definition | +| `events` | Map of event name → array of handler script strings | +| `rules` | Map of rule type → expression string (`"visible"`, `"required"`, `"enabled"`, `"calculate"`) | + +--- + +## 4. IT Test Infrastructure + +### Content page locations + +| Content type | Path pattern | +|---|---| +| Form pages | `content/forms/af/core-components-it/samples//.html` | +| Fragment definitions | `content/forms/af/core-components-it/samples/fragment//` | + +Fragment definition pages use `sling:resourceType="forms-components-examples/components/form/fragmentcontainer"` and `fd:type="fragment"` on their `guideContainer` node. + +### Deploy commands + +Deploy OSGi configs (toggle enablement, Sling configs): +```bash +mvn clean install -pl it/config -PautoInstallPackage \ + -Daem.host=localhost -Daem.port=4502 \ + -Daem.username=admin -Daem.password=admin +``` + +Deploy test content (form pages, fragments): +```bash +mvn clean install -pl it/content -PautoInstallPackage \ + -Daem.host=localhost -Daem.port=4502 \ + -Daem.username=admin -Daem.password=admin +``` + +### Verify deployed content + +```bash +curl -s -u admin:admin \ + "http://localhost:4502/content/forms/af/core-components-it/samples/.model.json" \ + | python3 -m json.tool +``` + +--- + +## 5. Cypress Test Patterns + +### Key commands (defined in `libs/support/commands.js`) + +| Command | Description | +|---|---| +| `cy.previewForm(pagePath)` | Loads form at `?wcmmode=disabled`, waits for AF runtime init, returns `formContainer` | +| `cy.previewFormWithPanel(pagePath)` | Same as above but waits for panel-specific initialization | +| `cy.fetchFeatureToggles()` | `GET /etc.clientlibs/toggles.json`, returns `{ enabled: ["FT_FORMS-XXXXX", ...] }` | + +### Key `cy.af` helpers (defined in `libs/support/functions.js`) + +| Helper | Description | +|---|---| +| `cy.af.isLatestAddon()` | `true` when `forms.far` env var is `null` or `"addon-latest"` | +| `cy.af.isReleasedAddon()` | `true` when `forms.far` is `null` or `"addon"` | +| `cy.af.getFormJsonUrl(pagePath)` | Returns `.model.json` URL | + +### The `formContainer` object (AF2 runtime) + +`formContainer` is the root runtime object returned by `cy.previewForm`. Key properties: + +| Property / Method | Description | +|---|---| +| `formContainer._fields` | `{ [id]: fieldView }` map of all registered field views | +| `formContainer._model` | AF2 model tree root | +| `formContainer._model.getElement(id)` | Look up a model element by ID | +| `formContainer.getAllFields()` | Returns `_fields` | +| `Object.entries(formContainer._fields)[N]` | Get Nth field as `[id, view]` pair | + +### Typical toggle-gated test structure + +```js +it('should behave differently when FT_FORMS-XXXXX is enabled', () => { + cy.fetchFeatureToggles().then(toggles => { + if (cy.af.isLatestAddon() && toggles.enabled.includes('FT_FORMS-XXXXX')) { + // assertions for toggle-on behavior + } else { + // assertions for toggle-off (legacy) behavior + } + }); +}); +``` + +--- + +## 6. JCR Content XML Patterns + +### Fragment definition page + +The `guideContainer` on the fragment's own page carries `fd:type="fragment"` to signal it is a reusable sub-form: + +```xml + + + + +``` + +### Fragment placeholder in a parent form + +A `` node embedded in a parent form references the fragment by path: + +```xml + + + + +``` + +### Rules and events + +- Rules and events can be defined on any node — field or container. +- Rule expressions are strings evaluated by the AF2 rule engine. +- Event expressions use `$form.` for absolute component references and `$self` for the current component. +- When `FT_FORMS-24087` is enabled, `FragmentImpl` merges rules and events from both the fragment definition and the placeholder, with placeholder values taking precedence for rules (and appending after for events). + +--- + +## 7. Running Tests + +```bash +cd ui.tests/test-module + +# Run a specific spec file +npm run cypress:run:specific -- "specs/fragment/fragment.featuretoggles.cy.js" + +# Run the full test suite +npm run cypress:run + +# Open the interactive Cypress runner +npm run cypress:open +``` + +Test files live under `specs/` organized by component name. There are approximately 149 spec files covering the full component set. + +--- + +## 8. New Component Checklist + +Use this when authoring a new form component or reviewing a PR that introduces one. Every item here reflects a real defect class found in production PRs. + +### 8.1 Required artifacts + +Every new component must ship all of the following. A PR missing any of these is incomplete. + +| Artifact | Location | Example | +|---|---|---| +| Resource type constant | `FormConstants.java` | `RT_FD_FORM_IMAGE_CHOICE_V1` | +| Public interface | `models/form/.java` | `ImageChoice.java` | +| Sling Model impl | `internal/models/v1/form/Impl.java` | `ImageChoiceImpl.java` | +| Unit test | `test/.../v1/form/ImplTest.java` | `ImageChoiceImplTest.java` | +| Test fixture JSON | `test/resources/form//test-content.json` | — | +| **JSON exporter golden files** | `test/resources/form//exporter-*.json` | see `radiobutton/` for examples | +| `ui.af.apps` component definition | `ui.af.apps/.../form//v1//` | `.content.xml`, HTL template, `_cq_dialog/`, clientlibs | +| Example component overlay | `examples/ui.apps/.../form//` | `.content.xml`, `_cq_template.xml` | +| Example content page | `examples/ui.content/.../adaptive-form//` | `.content.xml` | +| IT content page | `it/content/.../samples//` | one page per component | +| Cypress spec | `ui.tests/test-module/specs//` | `.cy.js` | + +The `ui.af.apps` component definition is the most commonly missed item. Without it, the Sling Model's `resourceType` annotation has nothing to resolve against — the component cannot be used in AEM even though all Java code compiles. + +### 8.2 `ReservedProperties` — the double-emission trap + +**Every JCR property name used by a component must be registered in `ReservedProperties.java`.** + +`AbstractFormComponentImpl.getCustomProperties()` reads all node properties and emits any key **not in `ReservedProperties`** as a raw top-level custom property in the form JSON. If a property is also put into `customLayoutProperties` (under `fd:layout`), it appears twice in the JSON: + +```json +{ + "selectionType": "single", // stray top-level — NOT expected by AF2 runtime + "fd:layout": { + "selectionType": "single" // intended + } +} +``` + +`ReservedProperties` is self-maintaining — the `aggregateReservedProperties()` method collects all `String` fields automatically. Just add the constant: + +```java +// ReservedProperties.java +public static final String PN_SELECTION_TYPE = "selectionType"; +``` + +Then reference it in the impl instead of a local private constant. + +**Check:** Search the impl for any `private static final String PN_` — if that string is not in `ReservedProperties`, it is a bug. + +### 8.3 `getFieldType()` contract + +Always delegate to the parent, never hardcode: + +```java +// Correct — reads JCR fieldType first, falls back to default +@Override +public String getFieldType() { + return super.getFieldType(FieldType.RADIO_GROUP); +} + +// Wrong — silently ignores any fieldType stored in JCR +@Override +public String getFieldType() { + if (someCondition) return FieldType.CHECKBOX_GROUP.getValue(); + return FieldType.RADIO_GROUP.getValue(); +} +``` + +When a component's effective field type depends on another property (e.g. `selectionType`), derive the *default* from that property and still pass it to `super.getFieldType()`: + +```java +@Override +public String getFieldType() { + FieldType defaultType = selectionType == SelectionType.MULTI + ? FieldType.CHECKBOX_GROUP : FieldType.RADIO_GROUP; + return super.getFieldType(defaultType); +} +``` + +### 8.4 Reuse existing enums — don't duplicate + +`CheckBox.Orientation` (HORIZONTAL/VERTICAL) is the canonical orientation enum in the public API since 2.0.0. New option-style components should reuse it rather than declaring an identical one: + +```java +// Wrong — verbatim copy of CheckBox.Orientation +enum Orientation { HORIZONTAL("horizontal"), VERTICAL("vertical"); ... } + +// Correct — reuse +import com.adobe.cq.forms.core.components.models.form.CheckBox; +// return type: CheckBox.Orientation +``` + +The same applies to any other enum that already exists in the `models/form/` package. + +### 8.5 `ContainerConstraint` for option fields + +Option-style fields (radio, checkbox group, image choice) that support `minItems`/`maxItems` selection limits must: + +1. Implement `ContainerConstraint` in their public interface (see `CheckBoxGroup.java`) +2. Override `getMinItems()` and `getMaxItems()` in the impl to return the `minItems`/`maxItems` fields injected by `AbstractBaseImpl` +3. Add test coverage for both methods + +The `ContainerConstraint.getMinItems()` default returns null — without the override the injected JCR values are never exposed. + +### 8.6 Required test patterns + +Beyond basic getter tests, every component test must include: + +```java +// 1. JSON export — verifies the full serialized contract against a golden file +@Test +void testJSONExport() throws Exception { + MyComponent comp = getComponentUnderTest(PATH_DEFAULT); + Utils.testJSONExport(comp, Utils.getTestExporterJSONPath(BASE, PATH_DEFAULT)); +} + +// 2. getProperties() completeness — verify layout properties are under fd:layout, +// not leaking as stray top-level custom properties +@Test +void testGetProperties() { + MyComponent comp = getComponentUnderTest(PATH_CUSTOMIZED); + Map props = comp.getProperties(); + Map layout = (Map) props.get(Base.CUSTOM_PROPERTY_WRAPPER); + // assert expected keys are in layout + assertEquals("value", layout.get("myProp")); + // assert the same key is NOT at the top level (ReservedProperties guard) + assertNull(props.get("myProp")); +} +``` + +Create one golden file per fixture variant (default, customized, datalayer). See `radiobutton/` for the naming convention (`exporter-radiobutton.json`, `exporter-radiobutton-customized.json`, etc.). diff --git a/docs/architecture/runtime-internals.md b/docs/architecture/runtime-internals.md new file mode 100644 index 0000000000..56e36dad4b --- /dev/null +++ b/docs/architecture/runtime-internals.md @@ -0,0 +1,253 @@ +# AF2 Runtime Internals + +Reference guide for the AF2 form initialization pipeline. Use this to understand how the runtime works and to orient quickly when debugging failures. + +--- + +## 1. Bundle Composition + +The Cypress IT test pages load a single minified JS bundle that contains code compiled from **two sources**: + +| Source | Role | +|---|---| +| `ui.af.apps/.../formcontainerview.js` | Production `FormContainerV2` | +| `it/apps/.../formcontainerview.js` | IT test `FormContainerV2` (older version) | + +Both register an `onDocumentReady` handler that calls `Utils.setupFormContainer`. This means **`setupFormContainer` is invoked twice per page load** against the same DOM element in IT test pages. + +`setupFormContainer` guards against this with a data attribute set synchronously before any async work: + +```javascript +// ui.frontend/src/utils.js +} else if (elements[i].dataset.cmpAdaptiveformcontainerInitialized) { + continue; +} else { + elements[i].dataset.cmpAdaptiveformcontainerInitialized = 'true'; + // ... rest of init +``` + +Setting the flag synchronously (before any `await`) is important — it prevents a second concurrent call from slipping through before the first one finishes. + +--- + +## 2. Form Initialization Flow + +``` +onDocumentReady() + └─ Utils.setupFormContainer(createFormContainer, selector, IS) + └─ for each formContainer DOM element: + ├─ [guard: skip if data-cmp-adaptiveformcontainer-initialized is set] + ├─ mark element initialized (synchronous, before any await) + ├─ fetch form JSON (HTTPAPILayer.getFormDefinition) + ├─ createFormContainer({ _formJson, _prefillData, _path, _element }) + │ └─ new FormContainerV2(...) + │ └─ createFormInstance(_formJson) ← af2-web-runtime + │ └─ Container._initialize() ← builds model tree + │ └─ for repeatable children: creates N instances + ├─ formContainer.subscribe() + └─ Utils.initializeAllFields(formContainer) + └─ for each field selector (in defined order): + └─ Utils.#createFormContainerFields(elements, creator, formContainer) + └─ for each matching DOM element: + ├─ [guard: skip if already in formContainer._fields] + ├─ [guard: skip if data-cmp-adaptiveformcontainer-path ≠ form path] + ├─ creator({ element, formContainer }) → fieldView + └─ formContainer.addField(fieldView) + ├─ _fields[id] = fieldView + ├─ model = formContainer.getModel(id) ← undefined if wrong container + ├─ fieldView.setModel(model) ← crashes if model undefined + ├─ fieldView.syncMarkupWithModel() + ├─ parent resolution (deferred if parent view not yet added) + └─ fieldView.subscribe() +``` + +### Critical invariant + +Every DOM element processed by `initializeAllFields` must have its ID present in the form container's model. If `getModel(id)` returns `undefined`, `fieldView.setModel(undefined)` crashes immediately at `model.getState()`. + +This invariant is enforced by two guards in `#createFormContainerFields`: +1. `formContainer.getField(elementId) == null` — skip already-registered fields +2. `formPathInField == formPath` — skip fields that belong to a different form container + +If either guard is missing or bypassed, the invariant can be violated. + +--- + +## 3. Model and View Lifecycle + +### Model tree (`af2-web-runtime`) + +`createFormInstance(_formJson)` builds the complete model tree synchronously. Each node gets a stable ID (from the JSON for top-level items, or UUID-based for dynamically created instances). All nodes are registered in `form._fields` indexed by ID. + +### View registration (`FormContainer.addField`) + +Views are registered lazily — they're created by `initializeAllFields` scanning the DOM after the model is built. The model always exists before any view is created. + +### Parent–child view resolution + +`addField` resolves parent views as fields are registered: +- If the parent view already exists in `_fields`, set it immediately. +- If not, defer: add to `_deferredParents[parentId]`. When the parent view is later registered, deferred children are resolved. + +This handles the case where child DOM elements appear before their parent in the DOM scan order. + +--- + +## 4. Repeatable Containers + +### What "repeatable" means + +A panel with `type='array'` and `items.length === 1` in the JSON is a repeatable container. The server sends **exactly one item** (the template) regardless of `minOccur`. The client creates all required instances. + +### Model initialization + +`Container._initialize` detects a repeatable child when `type === 'array'` AND `items.length === 1`. + +``` +initialItems = Math.max(1, minItems) + +for i in range(initialItems): + if i === 0: _addChild(template, cloneIds=false) → preserves template IDs + else: _addChild(template, cloneIds=true) → generates UUID-based IDs +``` + +All instances are registered in `form._fields`. `_addChild` also mutates `itemJson.id = retVal.id` on the template — so after the loop the template JSON carries the ID of the first created instance. + +### DOM synchronization (`InstanceManager`) + +When the first instance's **view** calls `setParent`, `InstanceManager.setRepeatableParentView` fires. This triggers `#syncInstancesHTML()` which: + +1. Iterates model instances that have no corresponding DOM element (instances 1+) +2. Clones the template HTML +3. Replaces all `id` and `for` attributes with UUID-based values matching the model +4. Inserts the new HTML into the DOM + +**Important**: this DOM mutation happens **during** `initializeAllFields`, not after. Any code that scans the DOM for field elements after this point will see the UUID-based elements. + +### Timing hazard + +If two `setupFormContainer` calls process the same form element (double-init scenario), the sequence is: + +1. Form1 runs `initializeAllFields` → InstanceManager inserts UUID DOM elements (using Form1's UUIDs) +2. Form2 runs `initializeAllFields` → finds those UUID DOM elements → `getModel(UUID)` on Form2's model (which has **different** UUIDs) → `undefined` → crash + +The `data-cmp-adaptiveformcontainer-initialized` guard (§1) prevents this by ensuring only one `setupFormContainer` call ever processes a given element. + +--- + +## 5. Form JSON Formats + +The runtime supports two JSON representations of container children, depending on server configuration: + +### Sling format (`:items` / `:itemsOrder`) + +```json +{ + ":items": { "field1": { "fieldType": "text-input", ... } }, + ":itemsOrder": ["field1"] +} +``` + +`sitesModelToFormModel` (af2-web-runtime) converts this to the `items` array format before model construction. It only fires when **both** `:items` AND `:itemsOrder` are present. + +### Items array format (`items`) + +```json +{ + "items": [{ "fieldType": "text-input", ... }] +} +``` + +Used when `FT_FORMS-24358` is enabled. `sitesModelToFormModel` is a no-op and `items` passes through directly. + +Both formats produce identical model trees. The difference is JSON size and the conversion step — `createFormInstance` behavior is the same either way. + +### Verifying the server output + +```bash +curl -s -u admin:admin \ + "http://localhost:4502//_jcr_content/guideContainer.model.json" \ + | python3 -c "import sys,json; d=json.load(sys.stdin); print(':items:', ':items' in d, ' items:', 'items' in d)" +``` + +--- + +## 6. Key Source Files + +### Java (server-side) + +| File | Role | +|---|---| +| `bundles/af-core/.../util/AbstractContainerImpl.java` | `getExportedItems`, `getExportedItemsOrder`, `getExportedItemsArray` | +| `bundles/af-core/.../util/AbstractFormComponentImpl.java` | `getEvents` — per-component event injection | +| `bundles/af-core/.../form/internal/FragmentImpl.java` | Fragment event/rule merging | +| `bundles/af-core/.../form/FeatureToggleConstants.java` | All feature toggle ID constants | +| `it/config/.../DynamicToggleProviderImpl.cfg.json` | OSGi config that wires toggles as JVM system properties | + +### JavaScript (this repo) + +| File | Role | +|---|---| +| `ui.frontend/src/utils.js` | `setupFormContainer`, `initializeAllFields`, `#createFormContainerFields` | +| `ui.frontend/src/view/FormContainer.js` | `addField`, `getModel`, `getAllFields`, `subscribe` | +| `ui.frontend/src/view/InstanceManager.js` | `setRepeatableParentView`, `#syncInstancesHTML`, `#addChildInstance` | +| `ui.af.apps/.../formcontainerview.js` | Production `onDocumentReady` + `FormContainerV2` | +| `it/apps/.../formcontainerview.js` | IT test `onDocumentReady` + `FormContainerV2` (also in bundle) | + +### JavaScript (af2-web-runtime, external) + +| File | Role | +|---|---| +| `packages/forms-next-core/src/Container.ts` | `_initialize`, `_addChild`, `_canHaveRepeatingChildren` | +| `packages/forms-next-core/src/utils/FormCreationUtils.ts` | Creates `InstanceManager` for repeatable children | + +--- + +## 7. Common Failure Patterns + +### `TypeError: Cannot read properties of undefined (reading 'getState')` + +**Stack**: `FormFieldBase.setModel` → `[Component].setModel` → `FormContainerV2.addField` → `#createFormContainerFields` → `Utils.initializeAllFields` + +`getModel(id)` returned `undefined`. The field view's ID is not in this form container's model. + +Causes to check (in order): +1. **Double initialization** — a second `setupFormContainer` call processed the element after the first already ran `initializeAllFields`. The first call's `InstanceManager` may have inserted UUID DOM elements that the second call cannot resolve. Fix: ensure the `data-cmp-adaptiveformcontainer-initialized` guard is in place. +2. **Wrong form container** — `data-cmp-adaptiveformcontainer-path` on the DOM element doesn't match the container's `data-cmp-path`. The path guard in `#createFormContainerFields` should catch this; if it doesn't, check that the attribute is set during server-side rendering. +3. **UUID–model mismatch** — a UUID-based DOM element was inserted by `InstanceManager.#syncInstancesHTML` for a different model instance than the one being processed. Check that the model's IDs and DOM IDs are in sync. + +### Fields registered but `formContainer._fields` appears empty + +- Check whether `cy.previewForm` waited for `Constants.FORM_CONTAINER_INITIALISED`. This event fires after `initializeAllFields` completes. +- Check the JSON format: verify `:items`/`:itemsOrder` vs `items` via the model JSON endpoint. +- Check `sitesModelToFormModel` conversion: it only runs when both `:items` AND `:itemsOrder` are present. If one is absent, the conversion is skipped. + +### Exception suppression masking crashes + +`cy.on('uncaught:exception', () => false)` silently swallows all application errors and causes tests to pass despite runtime crashes. Avoid using it as a blanket suppressor. Prefer targeted suppression: + +```javascript +cy.on('uncaught:exception', (err) => { + // Suppress only Cypress-internal iframe teardown noise, not application errors + if (err.message.includes('contentWindow')) return false; + return true; // re-throw everything else +}); +``` + +--- + +## 8. Debugging Checklist + +1. **Get the raw model JSON** — `curl -u admin:admin http://localhost:4502//_jcr_content/guideContainer.model.json` — verify structure, check `items` vs `:items`/`:itemsOrder`, confirm children are present. + +2. **Check for double initialization** — look for two different line numbers for `setupFormContainer` or `onDocumentReady` in the **same bundle hash** in a stack trace. That confirms two separate registrations in the compiled bundle. + +3. **Check element ownership** — every field's DOM element must have `data-cmp-adaptiveformcontainer-path` matching the form container's `data-cmp-path`. Mismatches mean the element belongs to a different container. + +4. **Inspect UUID DOM elements** — after page load, `document.querySelectorAll('[id]')` and look for UUID-style IDs (short alphanumeric, e.g. `TS4CEFHdFLA`). These are created by `InstanceManager.#syncInstancesHTML` for repeatable panel instances beyond the first. They should exist in `form._fields` if the owning form container initialized correctly. + +5. **Check model fields** — `formContainer._model._fields` contains every model node indexed by ID. A DOM element whose ID is absent from this map will crash `addField`. + +6. **Check toggle state** — `GET /etc.clientlibs/toggles.json` returns `{ enabled: [...] }`. Confirm which toggles are active and whether the code path you're debugging depends on any of them. + +7. **Remove exception suppression** — if the test has `cy.on('uncaught:exception', () => false)`, remove it temporarily and re-run. Real crashes will surface as test failures with stack traces. diff --git a/docs/e2e-testing/feature-toggles.md b/docs/e2e-testing/feature-toggles.md new file mode 100644 index 0000000000..641d26b5f0 --- /dev/null +++ b/docs/e2e-testing/feature-toggles.md @@ -0,0 +1,228 @@ +# Feature Toggle E2E Testing + +Reference guide for writing Cypress integration tests that are gated behind feature toggles. + +--- + +## Overview + +Feature toggles in this project are string identifiers (e.g., `FT_FORMS-24087`) defined as constants in: + +``` +bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/form/FeatureToggleConstants.java +``` + +At runtime, a toggle is either enabled or disabled. Tests use the toggle state to conditionally run assertions — this ensures feature-flagged behavior is only validated when the toggle is actually on. + +--- + +## Architecture: Two-Layer Toggle System + +### Layer 1 — Granite Toggle Service (client-side) + +Controls which toggles are surfaced to the browser via: + +``` +GET /etc.clientlibs/toggles.json +``` + +Response shape: +```json +{ "enabled": ["FT_FORMS-XXXXX", "FT_FORMS-YYYYY"] } +``` + +Config file (IT environment): +``` +it/config/src/main/content/jcr_root/apps/system/config/ + com.adobe.granite.toggle.impl.dev.DynamicToggleProviderImpl.cfg.json +``` + +Add the toggle name to the `enabled` array in that file to turn it on in IT. + +### Layer 2 — JVM System Property (server-side) + +Java code checks toggles via: + +```java +ComponentUtils.isToggleEnabled(toggleId) +// implemented as: "true".equals(System.getProperty(toggleId)) +``` + +Source: +``` +bundles/af-core/src/main/java/.../util/ComponentUtils.java +``` + +To wire a Granite toggle through to the JVM, create an OSGi factory config in the **same directory** as the Granite config above: + +Filename: `com.adobe.granite.toggle.monitor.systemproperty~.cfg.json` + +```json +{ + "toggle.name": "FT_FORMS-XXXXX", + "system.property": "FT_FORMS-XXXXX", + "fail.used": false +} +``` + +This causes the Granite toggle service to set the JVM system property whenever the toggle is enabled, bridging client-side and server-side activation. + +The product already ships `ToggleMonitorSystemPropertyFactory` configs for all `FT_FORMS-*` toggles. You do not need to create these. + +--- + +## IT Config Files + +When adding a new toggle, the only config file that requires an update is: + +| File | Change | +|------|--------| +| `com.adobe.granite.toggle.impl.dev.DynamicToggleProviderImpl.cfg.json` | Add toggle name to `enabled` array | + +--- + +## Fragment Container Events Pattern (FT_FORMS-24087) + +The fragment's `guideContainer` can carry `fd:events` at the container level using the same simple string format as field events. + +**Merge order**: placeholder events run FIRST, then fragment container events are appended. This means the last writer wins for the same property, making the merge directly observable in tests. + +**Observable test scenario**: the placeholder's `initialize` event sets a field to `"from-placeholder"` and the fragment container's `initialize` event sets the same field to `"from-frag-container"`. + +- Toggle ON: final value = `"from-frag-container"` (both ran, fragment container event applied last) +- Toggle OFF: final value = `"from-placeholder"` (only placeholder event ran) + +**Placeholder name coupling**: guideContainer-level event scripts that reference child fields must use the full path `$form..`. This couples the fragment event to a specific placeholder name, which is acceptable for IT test content. + +Example XML for `fd:events` at guideContainer level: +```xml + + + + +``` + +The corresponding placeholder panel in the parent form: +```xml + + + +``` + +--- + +## Cypress Test Pattern + +Canonical example: `ui.tests/test-module/specs/xfa/xfa.runtime.cy.js` + +```javascript +describe("Form Runtime - My Feature Toggle", () => { + if (cy.af.isLatestAddon()) { // wrap entire body — toggle only tested on latest addon + const pagePath = "content/forms/af/.../.html"; + let formContainer = null; + let toggle_array = []; + + before(() => { // use before(), not beforeEach() + cy.fetchFeatureToggles().then((response) => { + if (response.status === 200) { + toggle_array = response.body.enabled; + } + }); + }); + + it("description of expected behavior", () => { + if (toggle_array.includes("FT_FORMS-XXXXX")) { // guard each test individually + cy.previewForm(pagePath).then(p => { formContainer = p; }); + // assertions... + } + }); + } +}); +``` + +Key points: +- `cy.af.isLatestAddon()` — wraps the entire describe body; toggle tests only run on the latest addon build. This is a synchronous guard evaluated at test-collection time. +- `cy.fetchFeatureToggles()` — fetches `/etc.clientlibs/toggles.json` and returns the full response. Call once in `before()`, not `beforeEach()`. +- `toggle_array.includes("FT_FORMS-XXXXX")` — guards each `it()` block so the test is a no-op (passes silently) when the toggle is off. + +--- + +## Test Content + +IT content lives under: +``` +it/content/src/main/content/jcr_root/content/forms/af/core-components-it/samples/ +``` + +**Reuse an existing page** when: +- The toggle changes internal JSON format or rendering metadata but the observable form behavior (from the user's perspective) is the same. +- Example: FT_FORMS-24343, FT_FORMS-24358 reuse existing fragment/panel pages. + +**Create a new page** when: +- The toggle introduces behavior that requires specific content to be present and observable (e.g., new component type, new rule action). +- Example: FT_FORMS-24087 required a new page at `fragment/container-rules/` because it added container-level rule support that did not exist in any existing sample. + +--- + +## Existing Feature Toggle Test Files + +| File | Toggles Covered | +|------|----------------| +| `ui.tests/test-module/specs/fragment/fragment.featuretoggles.cy.js` | FT_FORMS-24087, FT_FORMS-24343, FT_FORMS-24358 | +| `ui.tests/test-module/specs/panelcontainer/panelcontainer.featuretoggles.cy.js` | FT_FORMS-24358 | + +--- + +## Common Pitfalls + +### Pitfall 1 — Cypress async trap + +`expect()` and direct JavaScript access to `formContainer._fields` are synchronous. They execute before `cy.previewForm().then()` resolves, leaving `formContainer` as `null`. All assertions that depend on `formContainer` must be placed inside the `.then()` callback. `cy.get()` is safe outside `.then()` because it queues in the Cypress command chain. + +Wrong: +```javascript +cy.previewForm(pagePath).then(p => { formContainer = p; }); +expect(formContainer, "initialized").to.not.be.null; // runs before .then() — always null +``` + +Correct: +```javascript +cy.previewForm(pagePath).then(p => { + formContainer = p; + expect(formContainer, "initialized").to.not.be.null; // inside .then() — works + cy.get(`#${Object.keys(formContainer._fields)[0]}`).should("be.visible"); +}); +``` + +### Pitfall 2 — custom:setProperty on a fragment panel throws + +Dispatching `custom:setProperty` with `{value: '...'}` on a fragment panel component throws `TypeError: Cannot set property value of #
which has only a getter`. Fragment panels render as `
` elements in the DOM, which have a read-only `value` property. Always target a child input field, never the panel itself. + +### Pitfall 3 — Stale IT content in AEM + +Content packages don't always force-overwrite cached nodes after `mvn install`. If a test page is behaving unexpectedly, verify the deployed content with: +```bash +curl -s -u admin:admin "http://localhost:4502/content/forms/af/core-components-it/samples/.model.json" | python3 -m json.tool | grep -A5 "events" +``` + +--- + +## Adding a New Toggle Test: Checklist + +1. **Define the constant** — add `public static final String FT_FORMS_XXXXX = "FT_FORMS-XXXXX";` to `FeatureToggleConstants.java`. + +2. **Enable in Granite config** — add `"FT_FORMS-XXXXX"` to the `enabled` array in `DynamicToggleProviderImpl.cfg.json`. + +3. **Provision test content** — decide whether to reuse an existing IT sample page or create a new one under `it/content/.../samples/`. Create new JCR content only when the toggled behavior requires it. + +4. **Write the Cypress test** — use the pattern above. Place the file in the spec directory that matches the component under test (e.g., `specs/fragment/fragment.featuretoggles.cy.js`). If a `*.featuretoggles.cy.js` file already exists for that component, add a new `describe` block to it. + +5. **Verify locally** — run the relevant Cypress spec against a local IT instance with the toggle enabled, then again with it disabled to confirm the guard works. diff --git a/examples/ui.apps/src/main/content/jcr_root/apps/forms-components-examples/clientlibs/forms-clientlib-site/styles/components/adaptive-form/imagechoice.less b/examples/ui.apps/src/main/content/jcr_root/apps/forms-components-examples/clientlibs/forms-clientlib-site/styles/components/adaptive-form/imagechoice.less new file mode 100644 index 0000000000..200ef93755 --- /dev/null +++ b/examples/ui.apps/src/main/content/jcr_root/apps/forms-components-examples/clientlibs/forms-clientlib-site/styles/components/adaptive-form/imagechoice.less @@ -0,0 +1,52 @@ +.cmp-adaptiveform-imagechoice { + .containerMixin(); + .descriptionMixin(); + .validInvalidMixin(); + border: none; + margin: 0; + padding: 0; + min-inline-size: 0; + &__widget { + .itemWidgetMixin(); + display: flex; + flex-wrap: wrap; + gap: 12px; + &.horizontal { + flex-direction: row; + } + &.vertical { + flex-direction: column; + } + } + &__option { + position: relative; + } + &__option-label { + display: flex; + flex-direction: column; + align-items: center; + cursor: pointer; + border: 2px solid transparent; + border-radius: 4px; + padding: 8px; + &:hover { + border-color: @gray; + } + } + &__option-image { + display: block; + border-radius: 4px; + overflow: hidden; + } + &__image { + display: block; + max-width: 150px; + max-height: 120px; + object-fit: contain; + } + &__option-text { + margin-top: 4px; + text-align: center; + font-size: 14px; + } +} diff --git a/examples/ui.apps/src/main/content/jcr_root/apps/forms-components-examples/clientlibs/forms-clientlib-site/styles/components/index.less b/examples/ui.apps/src/main/content/jcr_root/apps/forms-components-examples/clientlibs/forms-clientlib-site/styles/components/index.less index 2bf20e2945..c56f07bc92 100644 --- a/examples/ui.apps/src/main/content/jcr_root/apps/forms-components-examples/clientlibs/forms-clientlib-site/styles/components/index.less +++ b/examples/ui.apps/src/main/content/jcr_root/apps/forms-components-examples/clientlibs/forms-clientlib-site/styles/components/index.less @@ -26,6 +26,7 @@ @import 'adaptive-form/fileinput.less'; @import 'adaptive-form/footer.less'; @import 'adaptive-form/image.less'; +@import 'adaptive-form/imagechoice.less'; @import 'adaptive-form/numberinput.less'; @import 'adaptive-form/pageheader.less'; @import 'adaptive-form/panelcontainer.less'; diff --git a/examples/ui.apps/src/main/content/jcr_root/apps/forms-components-examples/components/form/checkboxgroup/.content.xml b/examples/ui.apps/src/main/content/jcr_root/apps/forms-components-examples/components/form/checkboxgroup/.content.xml index e8ccbbc826..699201714a 100644 --- a/examples/ui.apps/src/main/content/jcr_root/apps/forms-components-examples/components/form/checkboxgroup/.content.xml +++ b/examples/ui.apps/src/main/content/jcr_root/apps/forms-components-examples/components/form/checkboxgroup/.content.xml @@ -3,5 +3,5 @@ jcr:primaryType="cq:Component" jcr:title="Adaptive Form CheckBox Group" jcr:description="Add checkboxes to select one or more of the available options." - sling:resourceSuperType="core/fd/components/form/checkboxgroup/v1/checkboxgroup" + sling:resourceSuperType="core/fd/components/form/checkboxgroup/v2/checkboxgroup" componentGroup="Core Components Examples - Adaptive Form"/> diff --git a/examples/ui.apps/src/main/content/jcr_root/apps/forms-components-examples/components/form/imagechoice/.content.xml b/examples/ui.apps/src/main/content/jcr_root/apps/forms-components-examples/components/form/imagechoice/.content.xml new file mode 100644 index 0000000000..8ebb6827ef --- /dev/null +++ b/examples/ui.apps/src/main/content/jcr_root/apps/forms-components-examples/components/form/imagechoice/.content.xml @@ -0,0 +1,7 @@ + + diff --git a/examples/ui.apps/src/main/content/jcr_root/apps/forms-components-examples/components/form/imagechoice/_cq_template.xml b/examples/ui.apps/src/main/content/jcr_root/apps/forms-components-examples/components/form/imagechoice/_cq_template.xml new file mode 100644 index 0000000000..6e219079f0 --- /dev/null +++ b/examples/ui.apps/src/main/content/jcr_root/apps/forms-components-examples/components/form/imagechoice/_cq_template.xml @@ -0,0 +1,10 @@ + + diff --git a/examples/ui.apps/src/main/content/jcr_root/apps/forms-components-examples/components/form/table/.content.xml b/examples/ui.apps/src/main/content/jcr_root/apps/forms-components-examples/components/form/table/.content.xml new file mode 100644 index 0000000000..94b7b43b75 --- /dev/null +++ b/examples/ui.apps/src/main/content/jcr_root/apps/forms-components-examples/components/form/table/.content.xml @@ -0,0 +1,8 @@ + + diff --git a/examples/ui.apps/src/main/content/jcr_root/apps/forms-components-examples/components/form/table/_cq_template.xml b/examples/ui.apps/src/main/content/jcr_root/apps/forms-components-examples/components/form/table/_cq_template.xml new file mode 100644 index 0000000000..da8b08472f --- /dev/null +++ b/examples/ui.apps/src/main/content/jcr_root/apps/forms-components-examples/components/form/table/_cq_template.xml @@ -0,0 +1,73 @@ + + + + +
+ + +
+ + + + + + + + + + +
diff --git a/examples/ui.content/src/main/content/jcr_root/content/core-components-examples/library/adaptive-form/.content.xml b/examples/ui.content/src/main/content/jcr_root/content/core-components-examples/library/adaptive-form/.content.xml index 65ace20958..c381b90879 100644 --- a/examples/ui.content/src/main/content/jcr_root/content/core-components-examples/library/adaptive-form/.content.xml +++ b/examples/ui.content/src/main/content/jcr_root/content/core-components-examples/library/adaptive-form/.content.xml @@ -25,6 +25,7 @@ + diff --git a/examples/ui.content/src/main/content/jcr_root/content/core-components-examples/library/adaptive-form/imagechoice/.content.xml b/examples/ui.content/src/main/content/jcr_root/content/core-components-examples/library/adaptive-form/imagechoice/.content.xml new file mode 100644 index 0000000000..80a04a0181 --- /dev/null +++ b/examples/ui.content/src/main/content/jcr_root/content/core-components-examples/library/adaptive-form/imagechoice/.content.xml @@ -0,0 +1,114 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/it/apps/src/main/content/jcr_root/apps/core/fd/af-clientlibs/core-forms-components-runtime-all/resources/i18n/fa.json b/it/apps/src/main/content/jcr_root/apps/core/fd/af-clientlibs/core-forms-components-runtime-all/resources/i18n/fa.json index c44b15e5a5..d7505cc003 100644 --- a/it/apps/src/main/content/jcr_root/apps/core/fd/af-clientlibs/core-forms-components-runtime-all/resources/i18n/fa.json +++ b/it/apps/src/main/content/jcr_root/apps/core/fd/af-clientlibs/core-forms-components-runtime-all/resources/i18n/fa.json @@ -1,6 +1,6 @@ { "FileCloseAccessText" : "اضغط على Enter لحذف الملف", - "FileSizeGreater" : "حجم الملفات (${0}) أكبر من الحجم المتوقع: ${1} ميجابايت.", + "FileSizeGreater" : "حجم الملفات (${0}) أكبر من الحجم المتوقع: ${1}.", "FileNameInvalid" : "لا تقم بإرفاق ملفات يبدأ اسم الملف بـ (.)، أو يحتوي على \\ / : * ? \" < > | ; % $، أو هو كلمة محجوزة مثل nul، prn، con، lpt، أو com.", "FileMimeTypeInvalid" : "أنواع الملفات (${0}) غير مدعومة", "InternalFormSubmissionError" : "حدث خطأ داخلي أثناء تقديم النموذج.", diff --git a/it/apps/src/main/content/jcr_root/apps/core/fd/af-clientlibs/core-forms-components-runtime-all/resources/i18n/ru-ru.json b/it/apps/src/main/content/jcr_root/apps/core/fd/af-clientlibs/core-forms-components-runtime-all/resources/i18n/ru-ru.json index 27a334824a..b0c9801bf0 100755 --- a/it/apps/src/main/content/jcr_root/apps/core/fd/af-clientlibs/core-forms-components-runtime-all/resources/i18n/ru-ru.json +++ b/it/apps/src/main/content/jcr_root/apps/core/fd/af-clientlibs/core-forms-components-runtime-all/resources/i18n/ru-ru.json @@ -1,6 +1,6 @@ { "FileCloseAccessText" : "Нажмите Enter, чтобы удалить файл", - "FileSizeGreater" : "Файл(ы) ${0} больше ожидаемого размера: ${1}MB.", + "FileSizeGreater" : "Файл(ы) ${0} больше ожидаемого размера: ${1}.", "FileNameInvalid" : "Не прикрепляйте файлы, у которых имя файла начинается с (.), содержит \\ / : * ? \" < > | ; % $ или является зарезервированным ключевым словом, таким как nul, prn, con, lpt или com.", "FileMimeTypeInvalid" : "Файл(ы) ${0} имеют неподдерживаемый тип файла", "InternalFormSubmissionError" : "При отправке формы произошла внутренняя ошибка." diff --git a/it/apps/src/main/content/jcr_root/apps/core/fd/af-clientlibs/core-forms-components-runtime-all/resources/i18n/th.json b/it/apps/src/main/content/jcr_root/apps/core/fd/af-clientlibs/core-forms-components-runtime-all/resources/i18n/th.json index b0fced45ad..5b94fd6559 100644 --- a/it/apps/src/main/content/jcr_root/apps/core/fd/af-clientlibs/core-forms-components-runtime-all/resources/i18n/th.json +++ b/it/apps/src/main/content/jcr_root/apps/core/fd/af-clientlibs/core-forms-components-runtime-all/resources/i18n/th.json @@ -1,6 +1,6 @@ { "FileCloseAccessText" : "กด Enter เพื่อลบไฟล์ ", - "FileSizeGreater" : "ไฟล์ ${0} ใหญ่กว่าขนาดที่คาดไว้: ${1}MB", + "FileSizeGreater" : "ไฟล์ ${0} ใหญ่กว่าขนาดที่คาดไว้: ${1}", "FileNameInvalid" : "ห้ามแนบไฟล์ที่ชื่อไฟล์ขึ้นต้นด้วย (.) มี \\ / : * ? \" < > | ; % $ หรือเป็นคีย์เวิร์ดที่สงวนไว้ เช่น nul, prn, con, lpt หรือ com", "FileMimeTypeInvalid" : "ไฟล์ ${0} เป็นประเภทไฟล์ที่ไม่รองรับ", "InternalFormSubmissionError" : "พบข้อผิดพลาดภายในขณะส่งแบบฟอร์ม", diff --git a/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/clientlibs/clientlib-it-custom-locale/resources/i18n/hi.json b/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/clientlibs/clientlib-it-custom-locale/resources/i18n/hi.json index 1f62b34c22..6713921764 100644 --- a/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/clientlibs/clientlib-it-custom-locale/resources/i18n/hi.json +++ b/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/clientlibs/clientlib-it-custom-locale/resources/i18n/hi.json @@ -1,6 +1,6 @@ { "FileCloseAccessText" : "फ़ाइल को हटाने के लिए एंटर दबाएं", - "FileSizeGreater" : "फ़ाइल(फ़ाइलें) ${0} अपेक्षित आकार से अधिक हैं: ${1}MB.", + "FileSizeGreater" : "फ़ाइल(फ़ाइलें) ${0} अपेक्षित आकार से अधिक हैं: ${1}.", "FileNameInvalid" : "फ़ाइल नाम (.) से शुरू होता है, \\ / : * ? \" < > | ; % $ को मद्देनजर न लें, या nul, prn, con, lpt, या com जैसे आपत्तिजनक शब्द हैं।", "FileMimeTypeInvalid" : "फ़ाइल(फ़ाइलें) ${0} असमर्थित फ़ाइल प्रकार हैं", "InternalFormSubmissionError" : "फ़ॉर्म प्रस्तुत करते समय आंतरिक त्रुटि हुई है." diff --git a/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/clientlibs/clientlib-it-custom-locale/resources/i18n/ko-kr.json b/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/clientlibs/clientlib-it-custom-locale/resources/i18n/ko-kr.json index 02a51407a0..af95a587ee 100644 --- a/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/clientlibs/clientlib-it-custom-locale/resources/i18n/ko-kr.json +++ b/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/clientlibs/clientlib-it-custom-locale/resources/i18n/ko-kr.json @@ -1,6 +1,6 @@ { "FileCloseAccessText": "Enter 키를 눌러 파일 삭제", - "FileSizeGreater": "파일 ${0}이(가) 예상 크기 ${1}MB를 초과합니다.", + "FileSizeGreater": "파일 ${0}이(가) 예상 크기 ${1}를 초과합니다.", "FileNameInvalid": "파일 이름이 (.)으로 시작하거나, \\ / : * ? \" < > | ; % $를 포함하거나, nul, prn, con, lpt 또는 com과 같이 예약된 키워드인 파일은 첨부하지 마십시오.", "FileMimeTypeInvalid": "파일 ${0}은(는) 지원되지 않는 파일 유형입니다.", "InternalFormSubmissionError": "temp" diff --git a/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/clientlibs/clientlib-it-custom-locale/resources/i18n/th.json b/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/clientlibs/clientlib-it-custom-locale/resources/i18n/th.json index 9aef056c30..cf10add6c7 100644 --- a/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/clientlibs/clientlib-it-custom-locale/resources/i18n/th.json +++ b/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/clientlibs/clientlib-it-custom-locale/resources/i18n/th.json @@ -1,6 +1,6 @@ { "FileCloseAccessText" : "กด Enter เพื่อลบไฟล์ ", - "FileSizeGreater" : "ไฟล์ ${0} ใหญ่กว่าขนาดที่คาดไว้: ${1}MB", + "FileSizeGreater" : "ไฟล์ ${0} ใหญ่กว่าขนาดที่คาดไว้: ${1}", "FileNameInvalid" : "ห้ามแนบไฟล์ที่ชื่อไฟล์ขึ้นต้นด้วย (.) มี \\ / : * ? \" < > | ; % $ หรือเป็นคีย์เวิร์ดที่สงวนไว้ เช่น nul, prn, con, lpt หรือ com", "FileMimeTypeInvalid" : "ไฟล์ ${0} เป็นประเภทไฟล์ที่ไม่รองรับ", "InternalFormSubmissionError" : "พบข้อผิดพลาดภายในขณะส่งแบบฟอร์ม", 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 1b446738e2..333a1ce790 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 @@ -1,4 +1,8 @@ + + \ No newline at end of file diff --git a/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/components/customfileupload/README.md b/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/components/customfileupload/README.md new file mode 100644 index 0000000000..06bb1d6aed --- /dev/null +++ b/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/components/customfileupload/README.md @@ -0,0 +1,100 @@ + +Adaptive Form Custom File Upload (IT) +==== +Integration-test component that extends the Adaptive Forms file input to +simulate uploads against a presign endpoint and store the uploaded URL(s) in +the form model. + +## Features + +* Validates file size, filename, and mime type. +* Uploads valid files to preasign URL. +* Stores uploaded URL(s) in the model. + +Example export data (single file): + +``` +{ + "customfileupload1769598345994": { + "name": "sample.pdf", + "size": 0, + "mediaType": "application/octet-stream", + "data": "/services/s3/presign/sample.pdf" + } +} +``` + +Example export data (multiple files): + +``` +{ + "customfileupload1769598345994": [ + { + "name": "sample.txt", + "size": 0, + "mediaType": "application/octet-stream", + "data": "/services/s3/presign/sample.txt" + }, + { + "name": "sample2.txt", + "size": 0, + "mediaType": "application/octet-stream", + "data": "/services/s3/presign/sample2.txt" + } + ] +} +``` + +## Use Object + +The component uses the `com.adobe.cq.forms.core.components.models.form.FileInput` +Sling Model. + +## Client Libraries + +The component provides the client library categories under its component path: + +* `forms-core-components-it.components.customfileupload.site` + +## BEM Description +``` +BLOCK cmp-adaptiveform-fileinput + ELEMENT cmp-adaptiveform-fileinput__label + ELEMENT cmp-adaptiveform-fileinput__label-container + ELEMENT cmp-adaptiveform-fileinput__widget + ELEMENT cmp-adaptiveform-fileinput__questionmark + ELEMENT cmp-adaptiveform-fileinput__shortdescription + ELEMENT cmp-adaptiveform-fileinput__longdescription + ELEMENT cmp-adaptiveform-fileinput__filelist + ELEMENT cmp-adaptiveform-fileinput__fileitem + ELEMENT cmp-adaptiveform-fileinput__filename + ELEMENT cmp-adaptiveform-fileinput__filedelete + ELEMENT cmp-adaptiveform-fileinput__widgetlabel +``` + +## Where It Is Used + +* Sample page: + `/content/forms/af/core-components-it/samples/fileinput/customfileinput/custom-file-uploader.html` +* Cypress spec: + `ui.tests/test-module/specs/customfileupload/customfileupload.runtime.cy.js` + +## Information +* **Vendor**: Adobe +* **Version**: IT +* **Compatibility**: Cloud +* **Status**: internal test component diff --git a/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/components/customfileupload/_cq_template.xml b/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/components/customfileupload/_cq_template.xml new file mode 100644 index 0000000000..dbfaacce95 --- /dev/null +++ b/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/components/customfileupload/_cq_template.xml @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/components/customfileupload/clientlibs/.content.xml b/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/components/customfileupload/clientlibs/.content.xml new file mode 100644 index 0000000000..491392d539 --- /dev/null +++ b/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/components/customfileupload/clientlibs/.content.xml @@ -0,0 +1,3 @@ + + diff --git a/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/components/customfileupload/clientlibs/site/.content.xml b/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/components/customfileupload/clientlibs/site/.content.xml new file mode 100644 index 0000000000..e2362df897 --- /dev/null +++ b/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/components/customfileupload/clientlibs/site/.content.xml @@ -0,0 +1,6 @@ + + \ No newline at end of file diff --git a/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/components/customfileupload/clientlibs/site/js.txt b/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/components/customfileupload/clientlibs/site/js.txt new file mode 100644 index 0000000000..1dd24c2350 --- /dev/null +++ b/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/components/customfileupload/clientlibs/site/js.txt @@ -0,0 +1,19 @@ +############################################################################### +# Copyright 2026 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 +fileinputwidget.js +fileinputview.js diff --git a/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/components/customfileupload/clientlibs/site/js/fileinputview.js b/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/components/customfileupload/clientlibs/site/js/fileinputview.js new file mode 100644 index 0000000000..c2b7f2cc5b --- /dev/null +++ b/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/components/customfileupload/clientlibs/site/js/fileinputview.js @@ -0,0 +1,140 @@ +/******************************************************************************* + * Copyright 2026 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 FileInputV4 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 = "adaptiveFormCustomFileInput"; + static bemBlock = 'cmp-adaptiveform-fileinput' + static selectors = { + self: "[data-" + this.NS + '-is="' + this.IS + '"]', + widget: `.${FileInputV4.bemBlock}__widget`, + label: `.${FileInputV4.bemBlock}__label`, + description: `.${FileInputV4.bemBlock}__longdescription`, + qm: `.${FileInputV4.bemBlock}__questionmark`, + errorDiv: `.${FileInputV4.bemBlock}__errormessage`, + tooltipDiv: `.${FileInputV4.bemBlock}__shortdescription`, + fileListDiv : `.${FileInputV4.bemBlock}__filelist`, + attachButtonLabel : `.${FileInputV4.bemBlock}__widgetlabel`, + dragArea: `.${FileInputV4.bemBlock}__dragarea` + }; + + constructor(params) { + super(params); + } + widgetFields = { + widget: this.getWidget(), + fileListDiv: this.getFileListDiv(), + model: () => this._model, + dragArea: this.getDragArea() + }; + + getWidget() { + return this.element.querySelector(FileInputV4.selectors.widget); + } + + getDragArea() { + return this.element.querySelector(FileInputV4.selectors.dragArea); + } + + getDescription() { + return this.element.querySelector(FileInputV4.selectors.description); + } + + getLabel() { + return this.element.querySelector(FileInputV4.selectors.label); + } + + getErrorDiv() { + return this.element.querySelector(FileInputV4.selectors.errorDiv); + } + + getTooltipDiv() { + return this.element.querySelector(FileInputV4.selectors.tooltipDiv); + } + + getQuestionMarkDiv() { + return this.element.querySelector(FileInputV4.selectors.qm); + } + + getFileListDiv() { + return this.element.querySelector(FileInputV4.selectors.fileListDiv); + } + + getAttachButtonLabel() { + return this.element.querySelector(FileInputV4.selectors.attachButtonLabel); + } + + updateEnabled(enabled, state) { + if (this.widget) { + this.element.setAttribute(FormView.Constants.DATA_ATTRIBUTE_ENABLED, enabled); + const isDisabled = !enabled || state.readOnly; + if (isDisabled) { + this.widget.setAttribute("disabled", "disabled"); + } else { + this.widget.removeAttribute("disabled"); + } + } + } + + updateValue(value) { + if (this.widgetObject == null) { + this.widgetObject = new CustomFileInputWidget(this.widgetFields); + } + this.widgetObject.setValue(value); + super.updateEmptyStatus(); + } + + setModel(model) { + super.setModel(model); + if (this.widgetObject == null) { + this.widgetObject = new CustomFileInputWidget(this.widgetFields); + } + this.getAttachButtonLabel().addEventListener('focus', () => { + this.setActive(); + }) + this.getAttachButtonLabel().addEventListener('blur', () => { + this.setInactive(); + }) + } + + syncWidget() { + let widgetElement = this.getWidget ? this.getWidget() : null; + if (widgetElement) { + widgetElement.id = this.getId() + "__widget"; + const closestElement = widgetElement?.previousElementSibling; + if (closestElement && closestElement.tagName.toLowerCase() === 'label') { + closestElement.setAttribute('for', this.getId() + "__widget"); + } else { + this.getAttachButtonLabel().removeAttribute('for'); + } + } + } + } + + FormView.Utils.setupField(({element, formContainer}) => { + return new FileInputV4({element, formContainer}) + }, FileInputV4.selectors.self); + +})(); diff --git a/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/components/customfileupload/clientlibs/site/js/fileinputwidget.js b/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/components/customfileupload/clientlibs/site/js/fileinputwidget.js new file mode 100644 index 0000000000..0987c78fc1 --- /dev/null +++ b/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/components/customfileupload/clientlibs/site/js/fileinputwidget.js @@ -0,0 +1,299 @@ +/******************************************************************************* + * Copyright 2026 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 file input widget. It implements the file preview, + * file list, handling invalid file size, file name, file mime type functionality + */ + +if (typeof window.CustomFileInputWidget === 'undefined') { + /** + * Custom file widget that validates selected files, uploads them to the presign endpoint, + * and persists the resulting URL(s) in the form model data for export. + */ + window.CustomFileInputWidget = class extends FormView.FormFileInputWidget { + + /** + * Constructor for CustomFileInputWidget + * @param {Object} params - The parameters for initializing the widget + */ + constructor(params) { + super(params); + } + + /** + * Attaches event handlers to the widget and drag area + * @param {HTMLElement} widget - The file input element + * @param {HTMLElement} dragArea - The drag and drop area element + * @param {Object} model - The form model + */ + attachEventHandlers(widget, dragArea, model) { + super.attachEventHandlers(widget, dragArea, model) + const customBtn = dragArea?.querySelector(".cmp-adaptiveform-fileinput__widgetlabel") + customBtn?.addEventListener("click", () => { + widget.click(); + }); + customBtn?.addEventListener('keydown', function(event) { + // Check if the Enter key is pressed + if (event.key === 'Enter' || event.keyCode === 13) { + // Trigger the click event of the file input + widget.click(); + } + }); + } + + /** + * Creates a file list item DOM element + * @param {string} fileName - The name of the file + * @param {number} fileSize - The size of the file in bytes + * @param {string} comment - Optional comment for the file + * @param {string} fileUrl - The URL of the uploaded file + * @returns {HTMLElement} The file item DOM element + */ + fileItem(fileName, fileSize, comment, fileUrl) { + let self = this; + let id = `${Date.now() + '_' + Math.floor(Math.random() * 10000)}_file_size` + let fileItem = document.createElement('li'); + fileItem.setAttribute("class", "cmp-adaptiveform-fileinput__fileitem"); + let fileNameDom = document.createElement('a'); + let fileSizeDom = document.createElement('span'); + + fileSizeDom.setAttribute('class', "cmp-adaptiveform-fileinput__filesize"); + fileSizeDom.setAttribute('id', id); + fileSizeDom.setAttribute('hidden', true); + fileSizeDom.textContent = this.formatBytes(fileSize); + + fileNameDom.setAttribute('tabindex', '0'); + fileNameDom.setAttribute('class', "cmp-adaptiveform-fileinput__filename"); + fileNameDom.setAttribute('aria-describedby', id); + fileNameDom.textContent = fileName; + fileNameDom.addEventListener('keypress', function(e) { + if (e.keyCode === 13 || e.charCode === 32) { + e.target.click(); + } + }); + fileNameDom.addEventListener('click', function(e) { + self.handleFilePreview(e); + }); + fileItem.appendChild(fileNameDom); + if(fileUrl != null){ + fileNameDom.dataset.key = fileUrl; + } + let fileEndContainer = document.createElement('span'); + let fileClose = document.createElement('button'); + fileEndContainer.setAttribute('class', "cmp-adaptiveform-fileinput__fileendcontainer"); + fileClose.setAttribute('tabindex', '0'); + fileClose.setAttribute('class', "cmp-adaptiveform-fileinput__filedelete"); + fileClose.setAttribute('aria-label', FormView.LanguageUtils.getTranslatedString(this.lang, "FileCloseAccessText") + fileName); + fileClose.textContent = "x"; + fileClose.addEventListener('keypress', function(e) { + if (e.keyCode === 13 || e.charCode === 32) { + e.target.click(); + } + }); + fileClose.addEventListener('click', function(e) { + self.handleClick(e); + }); + fileEndContainer.appendChild(fileSizeDom); + fileEndContainer.appendChild(fileClose); + fileItem.appendChild(fileEndContainer); + return fileItem; + } + + handleChange(filesUploaded) { + if (!this.isFileUpdate) { + let currFileName = '', + inValidSizefileNames = '', + inValidNamefileNames = '', + inValidMimeTypefileNames = '', + self = this, + uris = [], + files = filesUploaded; + // Initially set the invalid flag to false + let isInvalidSize = false, + isInvalidFileName = false, + isInvalidMimeType = false; + //this.resetIfNotMultiSelect(); + if (typeof files !== "undefined") { + let invalidFilesIndexes = []; + let validFiles = []; + Array.from(files).forEach(function (file, fileIndex) { + let isCurrentInvalidFileSize = false, + isCurrentInvalidFileName = false, + isCurrentInvalidMimeType = false; + currFileName = file.name.split("\\").pop(); + // Now size is in MB + let size = file.size / 1024 / 1024; + // check if file size limit is within limits + if ((size > parseFloat(this.options.maxFileSize))) { + isInvalidSize = isCurrentInvalidFileSize = true; + inValidSizefileNames = currFileName + "," + inValidSizefileNames; + } else if (!CustomFileInputWidget.isValid(currFileName)) { + // check if file names are valid (ie) there are no control characters in file names + isInvalidFileName = isCurrentInvalidFileName = true; + inValidNamefileNames = currFileName + "," + inValidNamefileNames; + } else { + let isMatch = false; + let extension = currFileName.split('.').pop(); + let mimeType = (file.type) ? file.type : self.extensionToMimeTypeMap[extension]; + if (mimeType != undefined && mimeType.trim().length > 0) { + isMatch = this.regexMimeTypeList.some(function (rx) { + return rx.test(mimeType); + }); + } + if (!isMatch) { + isInvalidMimeType = isCurrentInvalidMimeType = true; + inValidMimeTypefileNames = currFileName + "," + inValidMimeTypefileNames; + } + } + + // if the file is not invalid, show it and push it to internal array + if (!isCurrentInvalidFileSize && !isCurrentInvalidFileName && !isCurrentInvalidMimeType) { + // We'll update this.values with the complete URL later + validFiles.push(file); + } else { + invalidFilesIndexes.push(fileIndex); + } + }, this); + + // Handle file upload for valid files + if (validFiles.length > 0) { + // Upload file and get URL + let fileUrl = this.uploadFile(validFiles); + if (this.isMultiSelect()) { + Array.from(validFiles).forEach(function (file, fileIndex) { + let completeUrl = fileUrl + "/" + file.name; + uris.push(completeUrl); + this.fileArr.push(completeUrl); + this.values.push(completeUrl); + }, this); + } else { + let completeUrl = fileUrl + "/" + validFiles[0].name; + this.values = [completeUrl]; + this.fileArr = [completeUrl]; + uris.push(completeUrl); + } + } + + if (invalidFilesIndexes.length > 0 && this.widget !== null) { + this.deleteFilesFromInputDom(invalidFilesIndexes); + } + } + + // set the new model value. + this.model.value = this.fileArr; + + const filenames = this.fileList.querySelectorAll('.cmp-adaptiveform-fileinput__filename'); + const filesizes = this.fileList.querySelectorAll('.cmp-adaptiveform-fileinput__filesize'); + Array.from(uris).forEach(function (uri, fileIndex) { + filenames[fileIndex].dataset.key = uri; + filesizes[fileIndex].hidden = true; + }, this); + + if (isInvalidSize) { + this.showInvalidMessage(inValidSizefileNames.substring(0, inValidSizefileNames.lastIndexOf(',')), this.invalidFeature.SIZE); + } else if (isInvalidFileName) { + this.showInvalidMessage(inValidNamefileNames.substring(0, inValidNamefileNames.lastIndexOf(',')), this.invalidFeature.NAME); + } else if (isInvalidMimeType) { + this.showInvalidMessage(inValidMimeTypefileNames.substring(0, inValidMimeTypefileNames.lastIndexOf(',')), this.invalidFeature.MIMETYPE); + } + } + this.widget.value = null; + } + + uploadFile (filesObject) { + let s3UploadUrl = "/services/s3/presign", // URL for getting S3 presigned URL + multiple = this.isMultiSelect(); // Check if multiple files can be uploaded; + + let self = this; + if (multiple) { + let uploadPromises = []; + Array.from(filesObject).forEach(function (file, fileIndex) { + uploadPromises.push(self.uploadFileToS3(file, s3UploadUrl, file.name)); + }, this); + + // Wait for all files to upload + Promise.all(uploadPromises).then(function(results) { + console.log("file uploaded successfully"); + //self.handleMultipleFileUpload(results); + }).catch(function(error) { + console.error("Error uploading files:", error); + //self.$element.trigger("adobeFileUploader.fileUploadFailed"); + }); + } else { + this.uploadFileToS3(filesObject[0], s3UploadUrl, filesObject[0].name).then(function(result) { + console.log("file uploaded successfully"); + }).catch(function(error) { + console.error("Error uploading file:", error); + // self.$element.trigger("adobeFileUploader.fileUploadFailed"); + }); + } + return s3UploadUrl; + } + + // Helper method to handle file upload to S3 + uploadFileToS3(file, presignEndpoint, fileName) { + console.log("Uploading file:", fileName); + // Return a resolved promise with a sample URL + // This is just for testing - replace with actual implementation + return new Promise((resolve) => { + // Simulate network delay + setTimeout(() => { + // For testing: construct a sample URL using the fileName + const sampleUrl = presignEndpoint + "/" + fileName; + + // Return an object with url and other metadata + resolve({ + success: true, + url: sampleUrl, + fileName: fileName, + fileSize: file.size, + fileType: file.type, + lastModified: file.lastModified + }); + }, 500); // 500ms delay to simulate network request + }); + + /* Real implementation would look like this: + return fetch(presignEndpoint, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + fileName: fileName, + contentType: file.type + }) + }) + .then(response => response.json()) + .then(presignedData => { + return fetch(presignedData.url, { + method: 'PUT', + body: file, + headers: presignedData.headers || {} + }); + }) + .then(() => { + return { + success: true, + url: this.fileUrl, + fileName: fileName + }; + }); + */ + } + } +} \ No newline at end of file diff --git a/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/components/customfileupload/customfileupload.html b/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/components/customfileupload/customfileupload.html new file mode 100644 index 0000000000..18dcf8343e --- /dev/null +++ b/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/components/customfileupload/customfileupload.html @@ -0,0 +1,64 @@ + + + + + +
+
+
+
+
+
+
+
+
+
${file.dragDropText @context='html'}
+ + +
+
    +
    +
    +
    +
    +
    +
    diff --git a/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/components/page-customfileuploader/.content.xml b/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/components/page-customfileuploader/.content.xml new file mode 100644 index 0000000000..7f8bc3b4b8 --- /dev/null +++ b/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/components/page-customfileuploader/.content.xml @@ -0,0 +1,22 @@ + + + \ No newline at end of file diff --git a/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/components/page-customfileuploader/customfooterlibs.html b/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/components/page-customfileuploader/customfooterlibs.html new file mode 100644 index 0000000000..a2ddf8284f --- /dev/null +++ b/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/components/page-customfileuploader/customfooterlibs.html @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + diff --git a/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/components/page-dynamicoptionsv1/.content.xml b/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/components/page-dynamicoptionsv1/.content.xml new file mode 100644 index 0000000000..953acf9f4d --- /dev/null +++ b/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/components/page-dynamicoptionsv1/.content.xml @@ -0,0 +1,22 @@ + + + diff --git a/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/components/page-dynamicoptionsv1/customfooterlibs.html b/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/components/page-dynamicoptionsv1/customfooterlibs.html new file mode 100644 index 0000000000..5ed2339488 --- /dev/null +++ b/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/components/page-dynamicoptionsv1/customfooterlibs.html @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + 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 36309fd0b6..95c4e89144 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 @@ -24,7 +24,10 @@ "FT_FORMS-16351", "FT_FORMS-14518", "FT_FORMS-13519", - "FT_FORMS-17107" + "FT_FORMS-17107", + "FT_FORMS-24087", + "FT_FORMS-24343", + "FT_FORMS-25252" ], "disabledToggles": [ ] diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/.content.xml b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/.content.xml new file mode 100755 index 0000000000..c5a8153a6a --- /dev/null +++ b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/.content.xml @@ -0,0 +1,11 @@ + + + + + + diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/_jcr_content/folderThumbnail b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/_jcr_content/folderThumbnail new file mode 100755 index 0000000000..56194b1144 Binary files /dev/null and b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/_jcr_content/folderThumbnail differ diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/_jcr_content/folderThumbnail.dir/.content.xml b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/_jcr_content/folderThumbnail.dir/.content.xml new file mode 100755 index 0000000000..d1b2c6467e --- /dev/null +++ b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/_jcr_content/folderThumbnail.dir/.content.xml @@ -0,0 +1,10 @@ + + + + diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a.jpeg/.content.xml b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a.jpeg/.content.xml new file mode 100755 index 0000000000..cbbe8d1d5a --- /dev/null +++ b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a.jpeg/.content.xml @@ -0,0 +1,38 @@ + + + + + + diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a.jpeg/_jcr_content/renditions/cq5dam.thumbnail.140.100.png b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a.jpeg/_jcr_content/renditions/cq5dam.thumbnail.140.100.png new file mode 100755 index 0000000000..f352235916 Binary files /dev/null and b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a.jpeg/_jcr_content/renditions/cq5dam.thumbnail.140.100.png differ diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a.jpeg/_jcr_content/renditions/cq5dam.thumbnail.140.100.png.dir/.content.xml b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a.jpeg/_jcr_content/renditions/cq5dam.thumbnail.140.100.png.dir/.content.xml new file mode 100755 index 0000000000..636a3c9d86 --- /dev/null +++ b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a.jpeg/_jcr_content/renditions/cq5dam.thumbnail.140.100.png.dir/.content.xml @@ -0,0 +1,7 @@ + + + + diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a.jpeg/_jcr_content/renditions/cq5dam.thumbnail.319.319.png b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a.jpeg/_jcr_content/renditions/cq5dam.thumbnail.319.319.png new file mode 100755 index 0000000000..5e894ec643 Binary files /dev/null and b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a.jpeg/_jcr_content/renditions/cq5dam.thumbnail.319.319.png differ diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a.jpeg/_jcr_content/renditions/cq5dam.thumbnail.319.319.png.dir/.content.xml b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a.jpeg/_jcr_content/renditions/cq5dam.thumbnail.319.319.png.dir/.content.xml new file mode 100755 index 0000000000..636a3c9d86 --- /dev/null +++ b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a.jpeg/_jcr_content/renditions/cq5dam.thumbnail.319.319.png.dir/.content.xml @@ -0,0 +1,7 @@ + + + + diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a.jpeg/_jcr_content/renditions/cq5dam.thumbnail.48.48.png b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a.jpeg/_jcr_content/renditions/cq5dam.thumbnail.48.48.png new file mode 100755 index 0000000000..e1e7a92de4 Binary files /dev/null and b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a.jpeg/_jcr_content/renditions/cq5dam.thumbnail.48.48.png differ diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a.jpeg/_jcr_content/renditions/cq5dam.thumbnail.48.48.png.dir/.content.xml b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a.jpeg/_jcr_content/renditions/cq5dam.thumbnail.48.48.png.dir/.content.xml new file mode 100755 index 0000000000..636a3c9d86 --- /dev/null +++ b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a.jpeg/_jcr_content/renditions/cq5dam.thumbnail.48.48.png.dir/.content.xml @@ -0,0 +1,7 @@ + + + + diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a.jpeg/_jcr_content/renditions/cq5dam.web.1280.1280.jpeg b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a.jpeg/_jcr_content/renditions/cq5dam.web.1280.1280.jpeg new file mode 100755 index 0000000000..ec29a9990e Binary files /dev/null and b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a.jpeg/_jcr_content/renditions/cq5dam.web.1280.1280.jpeg differ diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a.jpeg/_jcr_content/renditions/cq5dam.web.1280.1280.jpeg.dir/.content.xml b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a.jpeg/_jcr_content/renditions/cq5dam.web.1280.1280.jpeg.dir/.content.xml new file mode 100755 index 0000000000..636a3c9d86 --- /dev/null +++ b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a.jpeg/_jcr_content/renditions/cq5dam.web.1280.1280.jpeg.dir/.content.xml @@ -0,0 +1,7 @@ + + + + diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a.jpeg/_jcr_content/renditions/original b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a.jpeg/_jcr_content/renditions/original new file mode 100755 index 0000000000..6ef2f919ef Binary files /dev/null and b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a.jpeg/_jcr_content/renditions/original differ diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a.jpeg/_jcr_content/renditions/original.dir/.content.xml b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a.jpeg/_jcr_content/renditions/original.dir/.content.xml new file mode 100755 index 0000000000..61c8312d66 --- /dev/null +++ b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a.jpeg/_jcr_content/renditions/original.dir/.content.xml @@ -0,0 +1,8 @@ + + + + diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a1.jpeg/.content.xml b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a1.jpeg/.content.xml new file mode 100755 index 0000000000..4e4d647468 --- /dev/null +++ b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a1.jpeg/.content.xml @@ -0,0 +1,38 @@ + + + + + + diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a1.jpeg/_jcr_content/renditions/cq5dam.thumbnail.140.100.png b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a1.jpeg/_jcr_content/renditions/cq5dam.thumbnail.140.100.png new file mode 100755 index 0000000000..5cc5f6305e Binary files /dev/null and b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a1.jpeg/_jcr_content/renditions/cq5dam.thumbnail.140.100.png differ diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a1.jpeg/_jcr_content/renditions/cq5dam.thumbnail.140.100.png.dir/.content.xml b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a1.jpeg/_jcr_content/renditions/cq5dam.thumbnail.140.100.png.dir/.content.xml new file mode 100755 index 0000000000..636a3c9d86 --- /dev/null +++ b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a1.jpeg/_jcr_content/renditions/cq5dam.thumbnail.140.100.png.dir/.content.xml @@ -0,0 +1,7 @@ + + + + diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a1.jpeg/_jcr_content/renditions/cq5dam.thumbnail.319.319.png b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a1.jpeg/_jcr_content/renditions/cq5dam.thumbnail.319.319.png new file mode 100755 index 0000000000..5276cf9428 Binary files /dev/null and b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a1.jpeg/_jcr_content/renditions/cq5dam.thumbnail.319.319.png differ diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a1.jpeg/_jcr_content/renditions/cq5dam.thumbnail.319.319.png.dir/.content.xml b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a1.jpeg/_jcr_content/renditions/cq5dam.thumbnail.319.319.png.dir/.content.xml new file mode 100755 index 0000000000..636a3c9d86 --- /dev/null +++ b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a1.jpeg/_jcr_content/renditions/cq5dam.thumbnail.319.319.png.dir/.content.xml @@ -0,0 +1,7 @@ + + + + diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a1.jpeg/_jcr_content/renditions/cq5dam.thumbnail.48.48.png b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a1.jpeg/_jcr_content/renditions/cq5dam.thumbnail.48.48.png new file mode 100755 index 0000000000..95f13f648d Binary files /dev/null and b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a1.jpeg/_jcr_content/renditions/cq5dam.thumbnail.48.48.png differ diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a1.jpeg/_jcr_content/renditions/cq5dam.thumbnail.48.48.png.dir/.content.xml b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a1.jpeg/_jcr_content/renditions/cq5dam.thumbnail.48.48.png.dir/.content.xml new file mode 100755 index 0000000000..636a3c9d86 --- /dev/null +++ b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a1.jpeg/_jcr_content/renditions/cq5dam.thumbnail.48.48.png.dir/.content.xml @@ -0,0 +1,7 @@ + + + + diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a1.jpeg/_jcr_content/renditions/cq5dam.web.1280.1280.jpeg b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a1.jpeg/_jcr_content/renditions/cq5dam.web.1280.1280.jpeg new file mode 100755 index 0000000000..03ba56a2d1 Binary files /dev/null and b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a1.jpeg/_jcr_content/renditions/cq5dam.web.1280.1280.jpeg differ diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a1.jpeg/_jcr_content/renditions/cq5dam.web.1280.1280.jpeg.dir/.content.xml b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a1.jpeg/_jcr_content/renditions/cq5dam.web.1280.1280.jpeg.dir/.content.xml new file mode 100755 index 0000000000..636a3c9d86 --- /dev/null +++ b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a1.jpeg/_jcr_content/renditions/cq5dam.web.1280.1280.jpeg.dir/.content.xml @@ -0,0 +1,7 @@ + + + + diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a1.jpeg/_jcr_content/renditions/original b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a1.jpeg/_jcr_content/renditions/original new file mode 100755 index 0000000000..6c21cce3cc Binary files /dev/null and b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a1.jpeg/_jcr_content/renditions/original differ diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a1.jpeg/_jcr_content/renditions/original.dir/.content.xml b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a1.jpeg/_jcr_content/renditions/original.dir/.content.xml new file mode 100755 index 0000000000..61c8312d66 --- /dev/null +++ b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a1.jpeg/_jcr_content/renditions/original.dir/.content.xml @@ -0,0 +1,8 @@ + + + + diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/c.jpeg/.content.xml b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/c.jpeg/.content.xml new file mode 100755 index 0000000000..51a47e9bb5 --- /dev/null +++ b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/c.jpeg/.content.xml @@ -0,0 +1,38 @@ + + + + + + diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/c.jpeg/_jcr_content/renditions/cq5dam.thumbnail.140.100.png b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/c.jpeg/_jcr_content/renditions/cq5dam.thumbnail.140.100.png new file mode 100755 index 0000000000..ad96923225 Binary files /dev/null and b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/c.jpeg/_jcr_content/renditions/cq5dam.thumbnail.140.100.png differ diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/c.jpeg/_jcr_content/renditions/cq5dam.thumbnail.140.100.png.dir/.content.xml b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/c.jpeg/_jcr_content/renditions/cq5dam.thumbnail.140.100.png.dir/.content.xml new file mode 100755 index 0000000000..636a3c9d86 --- /dev/null +++ b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/c.jpeg/_jcr_content/renditions/cq5dam.thumbnail.140.100.png.dir/.content.xml @@ -0,0 +1,7 @@ + + + + diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/c.jpeg/_jcr_content/renditions/cq5dam.thumbnail.319.319.png b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/c.jpeg/_jcr_content/renditions/cq5dam.thumbnail.319.319.png new file mode 100755 index 0000000000..bb09b96ae4 Binary files /dev/null and b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/c.jpeg/_jcr_content/renditions/cq5dam.thumbnail.319.319.png differ diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/c.jpeg/_jcr_content/renditions/cq5dam.thumbnail.319.319.png.dir/.content.xml b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/c.jpeg/_jcr_content/renditions/cq5dam.thumbnail.319.319.png.dir/.content.xml new file mode 100755 index 0000000000..636a3c9d86 --- /dev/null +++ b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/c.jpeg/_jcr_content/renditions/cq5dam.thumbnail.319.319.png.dir/.content.xml @@ -0,0 +1,7 @@ + + + + diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/c.jpeg/_jcr_content/renditions/cq5dam.thumbnail.48.48.png b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/c.jpeg/_jcr_content/renditions/cq5dam.thumbnail.48.48.png new file mode 100755 index 0000000000..99bc97b2ff Binary files /dev/null and b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/c.jpeg/_jcr_content/renditions/cq5dam.thumbnail.48.48.png differ diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/c.jpeg/_jcr_content/renditions/cq5dam.thumbnail.48.48.png.dir/.content.xml b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/c.jpeg/_jcr_content/renditions/cq5dam.thumbnail.48.48.png.dir/.content.xml new file mode 100755 index 0000000000..636a3c9d86 --- /dev/null +++ b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/c.jpeg/_jcr_content/renditions/cq5dam.thumbnail.48.48.png.dir/.content.xml @@ -0,0 +1,7 @@ + + + + diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/c.jpeg/_jcr_content/renditions/cq5dam.web.1280.1280.jpeg b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/c.jpeg/_jcr_content/renditions/cq5dam.web.1280.1280.jpeg new file mode 100755 index 0000000000..66d7f9c2a5 Binary files /dev/null and b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/c.jpeg/_jcr_content/renditions/cq5dam.web.1280.1280.jpeg differ diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/c.jpeg/_jcr_content/renditions/cq5dam.web.1280.1280.jpeg.dir/.content.xml b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/c.jpeg/_jcr_content/renditions/cq5dam.web.1280.1280.jpeg.dir/.content.xml new file mode 100755 index 0000000000..636a3c9d86 --- /dev/null +++ b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/c.jpeg/_jcr_content/renditions/cq5dam.web.1280.1280.jpeg.dir/.content.xml @@ -0,0 +1,7 @@ + + + + diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/c.jpeg/_jcr_content/renditions/original b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/c.jpeg/_jcr_content/renditions/original new file mode 100755 index 0000000000..04b99954d4 Binary files /dev/null and b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/c.jpeg/_jcr_content/renditions/original differ diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/c.jpeg/_jcr_content/renditions/original.dir/.content.xml b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/c.jpeg/_jcr_content/renditions/original.dir/.content.xml new file mode 100755 index 0000000000..61c8312d66 --- /dev/null +++ b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/c.jpeg/_jcr_content/renditions/original.dir/.content.xml @@ -0,0 +1,8 @@ + + + + diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/tennis_court.jpeg/.content.xml b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/tennis_court.jpeg/.content.xml new file mode 100755 index 0000000000..8b40a93643 --- /dev/null +++ b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/tennis_court.jpeg/.content.xml @@ -0,0 +1,38 @@ + + + + + + diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/tennis_court.jpeg/_jcr_content/renditions/cq5dam.thumbnail.140.100.png b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/tennis_court.jpeg/_jcr_content/renditions/cq5dam.thumbnail.140.100.png new file mode 100755 index 0000000000..f544c5a987 Binary files /dev/null and b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/tennis_court.jpeg/_jcr_content/renditions/cq5dam.thumbnail.140.100.png differ diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/tennis_court.jpeg/_jcr_content/renditions/cq5dam.thumbnail.140.100.png.dir/.content.xml b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/tennis_court.jpeg/_jcr_content/renditions/cq5dam.thumbnail.140.100.png.dir/.content.xml new file mode 100755 index 0000000000..636a3c9d86 --- /dev/null +++ b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/tennis_court.jpeg/_jcr_content/renditions/cq5dam.thumbnail.140.100.png.dir/.content.xml @@ -0,0 +1,7 @@ + + + + diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/tennis_court.jpeg/_jcr_content/renditions/cq5dam.thumbnail.319.319.png b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/tennis_court.jpeg/_jcr_content/renditions/cq5dam.thumbnail.319.319.png new file mode 100755 index 0000000000..7723550979 Binary files /dev/null and b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/tennis_court.jpeg/_jcr_content/renditions/cq5dam.thumbnail.319.319.png differ diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/tennis_court.jpeg/_jcr_content/renditions/cq5dam.thumbnail.319.319.png.dir/.content.xml b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/tennis_court.jpeg/_jcr_content/renditions/cq5dam.thumbnail.319.319.png.dir/.content.xml new file mode 100755 index 0000000000..636a3c9d86 --- /dev/null +++ b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/tennis_court.jpeg/_jcr_content/renditions/cq5dam.thumbnail.319.319.png.dir/.content.xml @@ -0,0 +1,7 @@ + + + + diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/tennis_court.jpeg/_jcr_content/renditions/cq5dam.thumbnail.48.48.png b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/tennis_court.jpeg/_jcr_content/renditions/cq5dam.thumbnail.48.48.png new file mode 100755 index 0000000000..45484a2c18 Binary files /dev/null and b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/tennis_court.jpeg/_jcr_content/renditions/cq5dam.thumbnail.48.48.png differ diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/tennis_court.jpeg/_jcr_content/renditions/cq5dam.thumbnail.48.48.png.dir/.content.xml b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/tennis_court.jpeg/_jcr_content/renditions/cq5dam.thumbnail.48.48.png.dir/.content.xml new file mode 100755 index 0000000000..636a3c9d86 --- /dev/null +++ b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/tennis_court.jpeg/_jcr_content/renditions/cq5dam.thumbnail.48.48.png.dir/.content.xml @@ -0,0 +1,7 @@ + + + + diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/tennis_court.jpeg/_jcr_content/renditions/cq5dam.web.1280.1280.jpeg b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/tennis_court.jpeg/_jcr_content/renditions/cq5dam.web.1280.1280.jpeg new file mode 100755 index 0000000000..2c236fa421 Binary files /dev/null and b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/tennis_court.jpeg/_jcr_content/renditions/cq5dam.web.1280.1280.jpeg differ diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/tennis_court.jpeg/_jcr_content/renditions/cq5dam.web.1280.1280.jpeg.dir/.content.xml b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/tennis_court.jpeg/_jcr_content/renditions/cq5dam.web.1280.1280.jpeg.dir/.content.xml new file mode 100755 index 0000000000..636a3c9d86 --- /dev/null +++ b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/tennis_court.jpeg/_jcr_content/renditions/cq5dam.web.1280.1280.jpeg.dir/.content.xml @@ -0,0 +1,7 @@ + + + + diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/tennis_court.jpeg/_jcr_content/renditions/original b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/tennis_court.jpeg/_jcr_content/renditions/original new file mode 100755 index 0000000000..fe516e38d2 Binary files /dev/null and b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/tennis_court.jpeg/_jcr_content/renditions/original differ diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/tennis_court.jpeg/_jcr_content/renditions/original.dir/.content.xml b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/tennis_court.jpeg/_jcr_content/renditions/original.dir/.content.xml new file mode 100755 index 0000000000..61c8312d66 --- /dev/null +++ b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/tennis_court.jpeg/_jcr_content/renditions/original.dir/.content.xml @@ -0,0 +1,8 @@ + + + + diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/thumbnail.jpg/.content.xml b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/thumbnail.jpg/.content.xml new file mode 100755 index 0000000000..faf851c875 --- /dev/null +++ b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/thumbnail.jpg/.content.xml @@ -0,0 +1,59 @@ + + + + + + diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/thumbnail.jpg/_jcr_content/renditions/cq5dam.thumbnail.140.100.png b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/thumbnail.jpg/_jcr_content/renditions/cq5dam.thumbnail.140.100.png new file mode 100755 index 0000000000..39e1c18842 Binary files /dev/null and b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/thumbnail.jpg/_jcr_content/renditions/cq5dam.thumbnail.140.100.png differ diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/thumbnail.jpg/_jcr_content/renditions/cq5dam.thumbnail.140.100.png.dir/.content.xml b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/thumbnail.jpg/_jcr_content/renditions/cq5dam.thumbnail.140.100.png.dir/.content.xml new file mode 100755 index 0000000000..636a3c9d86 --- /dev/null +++ b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/thumbnail.jpg/_jcr_content/renditions/cq5dam.thumbnail.140.100.png.dir/.content.xml @@ -0,0 +1,7 @@ + + + + diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/thumbnail.jpg/_jcr_content/renditions/cq5dam.thumbnail.319.319.png b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/thumbnail.jpg/_jcr_content/renditions/cq5dam.thumbnail.319.319.png new file mode 100755 index 0000000000..b6d685b9ed Binary files /dev/null and b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/thumbnail.jpg/_jcr_content/renditions/cq5dam.thumbnail.319.319.png differ diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/thumbnail.jpg/_jcr_content/renditions/cq5dam.thumbnail.319.319.png.dir/.content.xml b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/thumbnail.jpg/_jcr_content/renditions/cq5dam.thumbnail.319.319.png.dir/.content.xml new file mode 100755 index 0000000000..636a3c9d86 --- /dev/null +++ b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/thumbnail.jpg/_jcr_content/renditions/cq5dam.thumbnail.319.319.png.dir/.content.xml @@ -0,0 +1,7 @@ + + + + diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/thumbnail.jpg/_jcr_content/renditions/cq5dam.thumbnail.48.48.png b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/thumbnail.jpg/_jcr_content/renditions/cq5dam.thumbnail.48.48.png new file mode 100755 index 0000000000..d025e7098c Binary files /dev/null and b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/thumbnail.jpg/_jcr_content/renditions/cq5dam.thumbnail.48.48.png differ diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/thumbnail.jpg/_jcr_content/renditions/cq5dam.thumbnail.48.48.png.dir/.content.xml b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/thumbnail.jpg/_jcr_content/renditions/cq5dam.thumbnail.48.48.png.dir/.content.xml new file mode 100755 index 0000000000..636a3c9d86 --- /dev/null +++ b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/thumbnail.jpg/_jcr_content/renditions/cq5dam.thumbnail.48.48.png.dir/.content.xml @@ -0,0 +1,7 @@ + + + + diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/thumbnail.jpg/_jcr_content/renditions/cq5dam.web.1280.1280.jpeg b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/thumbnail.jpg/_jcr_content/renditions/cq5dam.web.1280.1280.jpeg new file mode 100755 index 0000000000..c2b6691dc1 Binary files /dev/null and b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/thumbnail.jpg/_jcr_content/renditions/cq5dam.web.1280.1280.jpeg differ diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/thumbnail.jpg/_jcr_content/renditions/cq5dam.web.1280.1280.jpeg.dir/.content.xml b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/thumbnail.jpg/_jcr_content/renditions/cq5dam.web.1280.1280.jpeg.dir/.content.xml new file mode 100755 index 0000000000..636a3c9d86 --- /dev/null +++ b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/thumbnail.jpg/_jcr_content/renditions/cq5dam.web.1280.1280.jpeg.dir/.content.xml @@ -0,0 +1,7 @@ + + + + diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/thumbnail.jpg/_jcr_content/renditions/original b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/thumbnail.jpg/_jcr_content/renditions/original new file mode 100755 index 0000000000..a5621768e7 Binary files /dev/null and b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/thumbnail.jpg/_jcr_content/renditions/original differ diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/thumbnail.jpg/_jcr_content/renditions/original.dir/.content.xml b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/thumbnail.jpg/_jcr_content/renditions/original.dir/.content.xml new file mode 100755 index 0000000000..1642d40408 --- /dev/null +++ b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/thumbnail.jpg/_jcr_content/renditions/original.dir/.content.xml @@ -0,0 +1,8 @@ + + + + diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/samples/checkboxgroup/checkboxgroupv1/.content.xml b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/samples/checkboxgroup/checkboxgroupv1/.content.xml new file mode 100755 index 0000000000..de0e81ebaf --- /dev/null +++ b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/samples/checkboxgroup/checkboxgroupv1/.content.xml @@ -0,0 +1,6 @@ + + diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/samples/checkboxgroup/basic/.content.xml b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/samples/checkboxgroup/checkboxgroupv1/basic/.content.xml similarity index 82% rename from it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/samples/checkboxgroup/basic/.content.xml rename to it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/samples/checkboxgroup/checkboxgroupv1/basic/.content.xml index 5f2376f365..5c5fbd3bd4 100755 --- a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/samples/checkboxgroup/basic/.content.xml +++ b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/samples/checkboxgroup/checkboxgroupv1/basic/.content.xml @@ -1,8 +1,8 @@ - + title="basic"/> diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/samples/checkboxgroup/checkboxgroupv2/.content.xml b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/samples/checkboxgroup/checkboxgroupv2/.content.xml new file mode 100755 index 0000000000..de0e81ebaf --- /dev/null +++ b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/samples/checkboxgroup/checkboxgroupv2/.content.xml @@ -0,0 +1,6 @@ + + diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/samples/checkboxgroup/checkboxgroupv2/basic/.content.xml b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/samples/checkboxgroup/checkboxgroupv2/basic/.content.xml new file mode 100755 index 0000000000..5c5fbd3bd4 --- /dev/null +++ b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/samples/checkboxgroup/checkboxgroupv2/basic/.content.xml @@ -0,0 +1,21 @@ + + + + + + diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/samples/customfunctions/populate-checkboxv2/.content.xml b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/samples/customfunctions/populate-checkboxv2/.content.xml new file mode 100755 index 0000000000..aecaaab4b2 --- /dev/null +++ b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/samples/customfunctions/populate-checkboxv2/.content.xml @@ -0,0 +1,23 @@ + + + + + + \ No newline at end of file diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/samples/dynamicenumenumnames/dynamicoptionsv2/.content.xml b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/samples/dynamicenumenumnames/dynamicoptionsv2/.content.xml new file mode 100644 index 0000000000..f526f99e84 --- /dev/null +++ b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/samples/dynamicenumenumnames/dynamicoptionsv2/.content.xml @@ -0,0 +1,23 @@ + + + + + + diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/samples/fileinput/customfileinput/.content.xml b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/samples/fileinput/customfileinput/.content.xml new file mode 100755 index 0000000000..5f8ea308be --- /dev/null +++ b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/samples/fileinput/customfileinput/.content.xml @@ -0,0 +1,5 @@ + + diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/samples/fileinput/customfileinput/custom-file-uploader/.content.xml b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/samples/fileinput/customfileinput/custom-file-uploader/.content.xml new file mode 100755 index 0000000000..4754db43d5 --- /dev/null +++ b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/samples/fileinput/customfileinput/custom-file-uploader/.content.xml @@ -0,0 +1,22 @@ + + + + + + diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/samples/imagechoice/.content.xml b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/samples/imagechoice/.content.xml new file mode 100755 index 0000000000..3f764b2959 --- /dev/null +++ b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/samples/imagechoice/.content.xml @@ -0,0 +1,6 @@ + + diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/samples/imagechoice/testimagechoice/.content.xml b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/samples/imagechoice/testimagechoice/.content.xml new file mode 100755 index 0000000000..f3dc65db15 --- /dev/null +++ b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/samples/imagechoice/testimagechoice/.content.xml @@ -0,0 +1,23 @@ + + + + + + diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/samples/table/.content.xml b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/samples/table/.content.xml new file mode 100644 index 0000000000..7256712059 --- /dev/null +++ b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/samples/table/.content.xml @@ -0,0 +1,7 @@ + + diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/samples/table/basic/.content.xml b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/samples/table/basic/.content.xml new file mode 100644 index 0000000000..d727511cdf --- /dev/null +++ b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/samples/table/basic/.content.xml @@ -0,0 +1,21 @@ + + + + + + diff --git a/it/content/src/main/content/jcr_root/content/forms/af/core-components-it/samples/af2-form-translation/.content.xml b/it/content/src/main/content/jcr_root/content/forms/af/core-components-it/samples/af2-form-translation/.content.xml index e4df0943fb..dab11d370f 100755 --- a/it/content/src/main/content/jcr_root/content/forms/af/core-components-it/samples/af2-form-translation/.content.xml +++ b/it/content/src/main/content/jcr_root/content/forms/af/core-components-it/samples/af2-form-translation/.content.xml @@ -44,7 +44,7 @@ jcr:lastModifiedBy="admin" jcr:primaryType="nt:unstructured" jcr:title="Check Box Group" - sling:resourceType="forms-components-examples/components/form/checkboxgroup" + sling:resourceType="core/fd/components/form/checkboxgroup/v1/checkboxgroup" enum="[0,1]" enumNames="[Item 1, Item 2]" fieldType="checkbox-group" @@ -224,7 +224,7 @@ jcr:lastModifiedBy="admin" jcr:primaryType="nt:unstructured" jcr:title="<p><b>Select Animal</b></p>" - sling:resourceType="forms-components-examples/components/form/checkboxgroup" + sling:resourceType="core/fd/components/form/checkboxgroup/v1/checkboxgroup" areOptionsRichText="true" enabled="{Boolean}true" enum="[0,1]" diff --git a/it/content/src/main/content/jcr_root/content/forms/af/core-components-it/samples/checkboxgroup/checkboxgroupv1/.content.xml b/it/content/src/main/content/jcr_root/content/forms/af/core-components-it/samples/checkboxgroup/checkboxgroupv1/.content.xml new file mode 100755 index 0000000000..8f415a05ec --- /dev/null +++ b/it/content/src/main/content/jcr_root/content/forms/af/core-components-it/samples/checkboxgroup/checkboxgroupv1/.content.xml @@ -0,0 +1,5 @@ + +