From 4119165c0e9cbaacd02174f7c222b00d231df15f Mon Sep 17 00:00:00 2001 From: Roy Teeuwen Date: Fri, 13 Oct 2023 09:24:20 +0200 Subject: [PATCH] solves #22: add's support for Experience Fragment classes (#23) Co-authored-by: Roy Teeuwen Co-authored-by: Stefan Seifert --- changes.xml | 5 +- .../mock/aem/context/AemContextImpl.java | 2 + .../mock/aem/xf/MockExperienceFragment.java | 78 ++++++++ .../MockExperienceFragmentAdapterFactory.java | 62 +++++++ .../aem/xf/MockExperienceFragmentBase.java | 75 ++++++++ .../xf/MockExperienceFragmentVariation.java | 74 ++++++++ .../aem/xf/MockExperienceFragmentTest.java | 96 ++++++++++ .../resources/json-import-samples/xf.json | 173 ++++++++++++++++++ 8 files changed, 564 insertions(+), 1 deletion(-) create mode 100644 core/src/main/java/io/wcm/testing/mock/aem/xf/MockExperienceFragment.java create mode 100644 core/src/main/java/io/wcm/testing/mock/aem/xf/MockExperienceFragmentAdapterFactory.java create mode 100644 core/src/main/java/io/wcm/testing/mock/aem/xf/MockExperienceFragmentBase.java create mode 100644 core/src/main/java/io/wcm/testing/mock/aem/xf/MockExperienceFragmentVariation.java create mode 100644 core/src/test/java/io/wcm/testing/mock/aem/xf/MockExperienceFragmentTest.java create mode 100644 core/src/test/resources/json-import-samples/xf.json diff --git a/changes.xml b/changes.xml index 09dc8bad..1b72a833 100644 --- a/changes.xml +++ b/changes.xml @@ -23,7 +23,10 @@ xsi:schemaLocation="http://maven.apache.org/changes/1.0.0 http://maven.apache.org/plugins/maven-changes-plugin/xsd/changes-1.0.0.xsd"> - + + + Add mocks for ExperienceFragment and ExperienceFragmentVariation, adaptable from page objects. + Update to latest Sling Mock. diff --git a/core/src/main/java/io/wcm/testing/mock/aem/context/AemContextImpl.java b/core/src/main/java/io/wcm/testing/mock/aem/context/AemContextImpl.java index d94be1b0..71556dcf 100644 --- a/core/src/main/java/io/wcm/testing/mock/aem/context/AemContextImpl.java +++ b/core/src/main/java/io/wcm/testing/mock/aem/context/AemContextImpl.java @@ -24,6 +24,7 @@ import java.util.Map; import java.util.Set; +import io.wcm.testing.mock.aem.xf.MockExperienceFragmentAdapterFactory; import org.apache.sling.api.SlingHttpServletRequest; import org.apache.sling.api.resource.Resource; import org.apache.sling.api.resource.ResourceResolverFactory; @@ -76,6 +77,7 @@ protected void registerDefaultServices() { registerInjectActivateService(new MockAemAdapterFactory()); registerInjectActivateService(new MockAemDamAdapterFactory()); registerInjectActivateService(new MockLayerAdapterFactory()); + registerInjectActivateService(new MockExperienceFragmentAdapterFactory()); // other services registerInjectActivateService(new MockAssetHandler()); diff --git a/core/src/main/java/io/wcm/testing/mock/aem/xf/MockExperienceFragment.java b/core/src/main/java/io/wcm/testing/mock/aem/xf/MockExperienceFragment.java new file mode 100644 index 000000000..31125709 --- /dev/null +++ b/core/src/main/java/io/wcm/testing/mock/aem/xf/MockExperienceFragment.java @@ -0,0 +1,78 @@ +/* + * #%L + * wcm.io + * %% + * Copyright (C) 2019 wcm.io + * %% + * 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. + * #L% + */ +package io.wcm.testing.mock.aem.xf; + +import static com.adobe.cq.xf.ExperienceFragmentsConstants.PN_XF_VARIANT_TYPE; +import static com.adobe.cq.xf.ExperienceFragmentsConstants.RT_EXPERIENCE_FRAGMENT_PAGE; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +import org.apache.commons.lang3.StringUtils; +import org.apache.sling.api.resource.ValueMap; + +import com.adobe.cq.xf.ExperienceFragment; +import com.adobe.cq.xf.ExperienceFragmentVariation; +import com.day.cq.commons.Filter; +import com.day.cq.wcm.api.Page; + +/** + * Mock implementation of {@link ExperienceFragment}. + */ +class MockExperienceFragment extends MockExperienceFragmentBase implements ExperienceFragment { + + MockExperienceFragment(Page page) { + super(page); + } + + @Override + public List getVariations() { + List variations = new ArrayList<>(); + Iterator it = getPage().listChildren(element -> element.getContentResource().isResourceType(RT_EXPERIENCE_FRAGMENT_PAGE)); + while (it.hasNext()) { + Page thePage = it.next(); + variations.add(thePage.adaptTo(ExperienceFragmentVariation.class)); + } + return variations; + } + + @Override + public List getVariations(String... type) { + final Set typeValues = new HashSet<>(List.of(type)); + Filter typeFilter = element -> { + ValueMap properties = element.getProperties(); + String variantType = properties.get(PN_XF_VARIANT_TYPE, ""); + if (StringUtils.isEmpty(variantType)) { + return false; + } + return typeValues.contains(variantType); + }; + List variations = new ArrayList<>(); + for (Iterator it = getPage().listChildren(typeFilter); it.hasNext();) { + Page page = it.next(); + variations.add(page.adaptTo(ExperienceFragmentVariation.class)); + } + return variations; + } + +} diff --git a/core/src/main/java/io/wcm/testing/mock/aem/xf/MockExperienceFragmentAdapterFactory.java b/core/src/main/java/io/wcm/testing/mock/aem/xf/MockExperienceFragmentAdapterFactory.java new file mode 100644 index 000000000..429bc632 --- /dev/null +++ b/core/src/main/java/io/wcm/testing/mock/aem/xf/MockExperienceFragmentAdapterFactory.java @@ -0,0 +1,62 @@ +/* + * #%L + * wcm.io + * %% + * Copyright (C) 2019 wcm.io + * %% + * 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. + * #L% + */ +package io.wcm.testing.mock.aem.xf; + +import org.apache.sling.api.adapter.AdapterFactory; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.osgi.annotation.versioning.ProviderType; +import org.osgi.service.component.annotations.Component; + +import com.adobe.cq.xf.ExperienceFragment; +import com.adobe.cq.xf.ExperienceFragmentVariation; +import com.adobe.cq.xf.ExperienceFragmentsConstants; +import com.day.cq.wcm.api.Page; + +/** + * Mock adapter factory for AEM Experience Fragment-related adaptions. + */ +@Component(service = AdapterFactory.class, + property = { + AdapterFactory.ADAPTABLE_CLASSES + "=com.day.cq.wcm.api.Page", + AdapterFactory.ADAPTER_CLASSES + "=com.adobe.cq.xf.ExperienceFragment", + AdapterFactory.ADAPTER_CLASSES + "=com.adobe.cq.xf.ExperienceFragmentVariation" + }) +@ProviderType +public final class MockExperienceFragmentAdapterFactory implements AdapterFactory { + + @SuppressWarnings("unchecked") + @Override + public @Nullable AdapterType getAdapter(@NotNull Object object, @NotNull Class type) { + if (object instanceof Page) { + Page page = (Page)object; + if (page.getContentResource().isResourceType(ExperienceFragmentsConstants.RT_EXPERIENCE_FRAGMENT_MASTER) && (type == ExperienceFragment.class)) { + return (AdapterType)new MockExperienceFragment(page); + + } + if (page.getContentResource().isResourceType(ExperienceFragmentsConstants.RT_EXPERIENCE_FRAGMENT_PAGE) && (type == ExperienceFragmentVariation.class)) { + return (AdapterType)new MockExperienceFragmentVariation(page); + + } + } + return null; + } + +} diff --git a/core/src/main/java/io/wcm/testing/mock/aem/xf/MockExperienceFragmentBase.java b/core/src/main/java/io/wcm/testing/mock/aem/xf/MockExperienceFragmentBase.java new file mode 100644 index 000000000..f4ef94fe --- /dev/null +++ b/core/src/main/java/io/wcm/testing/mock/aem/xf/MockExperienceFragmentBase.java @@ -0,0 +1,75 @@ +/* + * #%L + * wcm.io + * %% + * Copyright (C) 2019 wcm.io + * %% + * 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. + * #L% + */ +package io.wcm.testing.mock.aem.xf; + +import java.util.Arrays; +import java.util.List; + +import org.apache.sling.api.adapter.SlingAdaptable; +import org.apache.sling.api.resource.Resource; +import org.apache.sling.api.resource.ValueMap; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import com.day.cq.commons.inherit.HierarchyNodeInheritanceValueMap; +import com.day.cq.commons.inherit.InheritanceValueMap; +import com.day.cq.wcm.api.Page; + +class MockExperienceFragmentBase extends SlingAdaptable { + + private final Page page; + + protected MockExperienceFragmentBase(Page page) { + this.page = page; + } + + public String getPath() { + return page.getPath(); + } + + public ValueMap getProperties() { + return page.getProperties(); + } + + public List getCloudserviceConfigurationsPaths() { + return Arrays.asList(getInheritedProperties().getInherited("cq:cloudserviceconfigs", new String[0])); + } + + protected InheritanceValueMap getInheritedProperties() { + return new HierarchyNodeInheritanceValueMap(page.getContentResource()); + } + + @Override + @SuppressWarnings({ "unchecked", "null" }) + public @Nullable AdapterType adaptTo(@NotNull Class type) { + if (type == Resource.class) { + return (AdapterType)page.adaptTo(Resource.class); + } + if (type == Page.class) { + return (AdapterType)page; + } + return super.adaptTo(type); + } + + protected Page getPage() { + return page; + } + +} diff --git a/core/src/main/java/io/wcm/testing/mock/aem/xf/MockExperienceFragmentVariation.java b/core/src/main/java/io/wcm/testing/mock/aem/xf/MockExperienceFragmentVariation.java new file mode 100644 index 000000000..09614969 --- /dev/null +++ b/core/src/main/java/io/wcm/testing/mock/aem/xf/MockExperienceFragmentVariation.java @@ -0,0 +1,74 @@ +/* + * #%L + * wcm.io + * %% + * Copyright (C) 2019 wcm.io + * %% + * 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. + * #L% + */ +package io.wcm.testing.mock.aem.xf; + +import static com.adobe.cq.xf.ExperienceFragmentsConstants.CUSTOM_XF_VARIANT_TYPE; +import static com.adobe.cq.xf.ExperienceFragmentsConstants.PN_XF_VARIANT_TYPE; + +import org.apache.sling.api.resource.ValueMap; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import com.adobe.cq.xf.ExperienceFragment; +import com.adobe.cq.xf.ExperienceFragmentVariation; +import com.day.cq.commons.inherit.InheritanceValueMap; +import com.day.cq.wcm.api.Page; + +/** + * Mock implementation of {@link ExperienceFragmentVariation}. + */ +class MockExperienceFragmentVariation extends MockExperienceFragmentBase implements ExperienceFragmentVariation { + + private ExperienceFragment parent; + + MockExperienceFragmentVariation(Page page) { + super(page); + } + + @Override + public ExperienceFragment getParent() { + if (this.parent == null) { + this.parent = new MockExperienceFragment(getPage().getParent()); + } + return this.parent; + } + + @Override + public String getType() { + ValueMap properties = getPage().getProperties(); + String type = properties.get(PN_XF_VARIANT_TYPE, String.class); + return (type != null) ? type : CUSTOM_XF_VARIANT_TYPE; + } + + @Override + @SuppressWarnings("unchecked") + public @Nullable AdapterType adaptTo(@NotNull Class type) { + if (type == Page.class) { + return (AdapterType)getPage(); + } + return super.adaptTo(type); + } + + @Override + public InheritanceValueMap getPropertiesTree() { + return getInheritedProperties(); + } + +} diff --git a/core/src/test/java/io/wcm/testing/mock/aem/xf/MockExperienceFragmentTest.java b/core/src/test/java/io/wcm/testing/mock/aem/xf/MockExperienceFragmentTest.java new file mode 100644 index 000000000..09e72b2a --- /dev/null +++ b/core/src/test/java/io/wcm/testing/mock/aem/xf/MockExperienceFragmentTest.java @@ -0,0 +1,96 @@ +/* + * #%L + * wcm.io + * %% + * Copyright (C) 2019 wcm.io + * %% + * 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. + * #L% + */ +package io.wcm.testing.mock.aem.xf; + +import static com.adobe.cq.xf.ExperienceFragmentsConstants.TYPE_XF_VARIANT_FACEBOOK; +import static com.adobe.cq.xf.ExperienceFragmentsConstants.TYPE_XF_VARIANT_WEB; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +import org.apache.sling.api.resource.Resource; +import org.apache.sling.testing.mock.sling.loader.ContentLoader; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; + +import com.adobe.cq.xf.ExperienceFragment; +import com.adobe.cq.xf.ExperienceFragmentVariation; +import com.day.cq.wcm.api.Page; + +import io.wcm.testing.mock.aem.context.TestAemContext; +import io.wcm.testing.mock.aem.junit.AemContext; + +public class MockExperienceFragmentTest { + + @Rule + public AemContext context = TestAemContext.newAemContext(); + + @Before + public void setUp() { + ContentLoader contentLoader = this.context.load(); + contentLoader.json("/json-import-samples/xf.json", "/content/experience-fragments/sample"); + } + + @Test + @SuppressWarnings("null") + public void testExperienceFragment() { + Resource xfResource = this.context.resourceResolver().getResource("/content/experience-fragments/sample"); + Page xfPage = xfResource.adaptTo(Page.class); + assertNull(xfPage.adaptTo(ExperienceFragmentVariation.class)); + ExperienceFragment experienceFragment = xfPage.adaptTo(ExperienceFragment.class); + assertNotNull(experienceFragment); + + Resource adaptedResource = experienceFragment.adaptTo(Resource.class); + assertEquals(xfResource, adaptedResource); + Page adaptedPage = experienceFragment.adaptTo(Page.class); + assertEquals(xfPage, adaptedPage); + + assertEquals("/content/experience-fragments/sample", experienceFragment.getPath()); + assertEquals("/conf/something", experienceFragment.getCloudserviceConfigurationsPaths().get(0)); + assertEquals(2, experienceFragment.getVariations().size()); + assertEquals(1, experienceFragment.getVariations(TYPE_XF_VARIANT_FACEBOOK).size()); + assertEquals("/content/experience-fragments/sample/master", experienceFragment.getVariations().get(0).getPath()); + assertEquals("Header", experienceFragment.getProperties().get("jcr:title")); + } + + @Test + @SuppressWarnings("null") + public void testExperienceFragmentVariation() { + Resource masterResource = this.context.resourceResolver().getResource("/content/experience-fragments/sample/master"); + Page masterPage = masterResource.adaptTo(Page.class); + assertNull(masterPage.adaptTo(ExperienceFragment.class)); + ExperienceFragmentVariation variation = masterPage.adaptTo(ExperienceFragmentVariation.class); + assertNotNull(variation); + + Resource adaptedResource = variation.adaptTo(Resource.class); + assertEquals(masterResource, adaptedResource); + Page adaptedPage = variation.adaptTo(Page.class); + assertEquals(masterPage, adaptedPage); + + assertEquals("/content/experience-fragments/sample/master", variation.getPath()); + assertEquals("/conf/something", variation.getCloudserviceConfigurationsPaths().get(0)); + assertEquals("/conf/something", variation.getPropertiesTree().getInherited("cq:cloudserviceconfigs", "")); + assertEquals(TYPE_XF_VARIANT_WEB, variation.getType()); + assertEquals("/content/experience-fragments/sample", variation.getParent().getPath()); + assertEquals("Header", variation.getProperties().get("jcr:title")); + } + +} diff --git a/core/src/test/resources/json-import-samples/xf.json b/core/src/test/resources/json-import-samples/xf.json new file mode 100644 index 000000000..1d84a523 --- /dev/null +++ b/core/src/test/resources/json-import-samples/xf.json @@ -0,0 +1,173 @@ +{ + "jcr:primaryType": "cq:Page", + "jcr:createdBy": "admin", + "jcr:created": "Tue Sep 19 2023 08:22:56 GMT+0200", + "jcr:content": { + "jcr:primaryType": "cq:PageContent", + "jcr:createdBy": "admin", + "jcr:title": "Header", + "cq:template": "/libs/cq/experience-fragments/components/experiencefragment/template", + "jcr:created": "Tue Sep 19 2023 08:22:56 GMT+0200", + "cq:tags": [], + "sling:resourceType": "cq/experience-fragments/components/experiencefragment", + "cq:cloudserviceconfigs": [ + "/conf/something" + ] + }, + "master": { + "jcr:primaryType": "cq:Page", + "jcr:createdBy": "admin", + "jcr:created": "Tue Sep 19 2023 08:22:56 GMT+0200", + "jcr:content": { + "jcr:primaryType": "cq:PageContent", + "jcr:createdBy": "admin", + "jcr:title": "Header", + "cq:template": "/conf/sample/settings/wcm/templates/xf-web-variation", + "cq:xfMasterVariation": true, + "jcr:created": "Tue Sep 19 2023 08:22:56 GMT+0200", + "cq:xfVariantType": "web", + "cq:tags": [], + "sling:resourceType": "cq/experience-fragments/components/xfpage", + "root": { + "jcr:primaryType": "nt:unstructured", + "layout": "responsiveGrid", + "id": "main-header", + "sling:resourceType": "core/wcm/components/container/v1/container", + "image": { + "jcr:primaryType": "nt:unstructured", + "fileReference": "/content/dam/sample/Group 3458@2x.png", + "sling:resourceType": "core/wcm/components/image/v3/image", + "cq:responsive": { + "jcr:primaryType": "nt:unstructured", + "default": { + "jcr:primaryType": "nt:unstructured", + "offset": "1", + "width": "1" + } + } + }, + "navigation": { + "jcr:primaryType": "nt:unstructured", + "disableShadowing": "false", + "navigationRoot": "/content/sample/en", + "sling:resourceType": "core/wcm/components/navigation/v2/navigation", + "collectAllPages": "true", + "structureDepth": "1", + "structureStart": "1", + "cq:responsive": { + "jcr:primaryType": "nt:unstructured", + "default": { + "jcr:primaryType": "nt:unstructured", + "offset": "0", + "width": "5" + } + } + }, + "search": { + "jcr:primaryType": "nt:unstructured", + "sling:resourceType": "core/wcm/components/search/v1/search", + "cq:responsive": { + "jcr:primaryType": "nt:unstructured", + "default": { + "jcr:primaryType": "nt:unstructured", + "offset": "0", + "width": "2" + } + } + }, + "languagenavigation": { + "jcr:primaryType": "nt:unstructured", + "navigationRoot": "/content/sample", + "sling:resourceType": "core/wcm/components/languagenavigation/v2/languagenavigation", + "structureDepth": "1", + "cq:responsive": { + "jcr:primaryType": "nt:unstructured", + "default": { + "jcr:primaryType": "nt:unstructured", + "offset": "0", + "width": "2" + } + } + } + } + } + }, + "second-variation": { + "jcr:primaryType": "cq:Page", + "jcr:createdBy": "admin", + "jcr:created": "Tue Sep 19 2023 08:22:56 GMT+0200", + "jcr:content": { + "jcr:primaryType": "cq:PageContent", + "jcr:createdBy": "admin", + "jcr:title": "Header", + "cq:template": "/conf/sample/settings/wcm/templates/xf-web-variation", + "cq:xfMasterVariation": true, + "jcr:created": "Tue Sep 19 2023 08:22:56 GMT+0200", + "cq:xfVariantType": "facebook", + "cq:tags": [], + "sling:resourceType": "cq/experience-fragments/components/xfpage", + "root": { + "jcr:primaryType": "nt:unstructured", + "layout": "responsiveGrid", + "id": "main-header", + "sling:resourceType": "core/wcm/components/container/v1/container", + "image": { + "jcr:primaryType": "nt:unstructured", + "fileReference": "/content/dam/sample/Group 3458@2x.png", + "sling:resourceType": "core/wcm/components/image/v3/image", + "cq:responsive": { + "jcr:primaryType": "nt:unstructured", + "default": { + "jcr:primaryType": "nt:unstructured", + "offset": "1", + "width": "1" + } + } + }, + "navigation": { + "jcr:primaryType": "nt:unstructured", + "disableShadowing": "false", + "navigationRoot": "/content/sample/en", + "sling:resourceType": "core/wcm/components/navigation/v2/navigation", + "collectAllPages": "true", + "structureDepth": "1", + "structureStart": "1", + "cq:responsive": { + "jcr:primaryType": "nt:unstructured", + "default": { + "jcr:primaryType": "nt:unstructured", + "offset": "0", + "width": "5" + } + } + }, + "search": { + "jcr:primaryType": "nt:unstructured", + "sling:resourceType": "core/wcm/components/search/v1/search", + "cq:responsive": { + "jcr:primaryType": "nt:unstructured", + "default": { + "jcr:primaryType": "nt:unstructured", + "offset": "0", + "width": "2" + } + } + }, + "languagenavigation": { + "jcr:primaryType": "nt:unstructured", + "navigationRoot": "/content/sample", + "sling:resourceType": "core/wcm/components/languagenavigation/v2/languagenavigation", + "structureDepth": "1", + "cq:responsive": { + "jcr:primaryType": "nt:unstructured", + "default": { + "jcr:primaryType": "nt:unstructured", + "offset": "0", + "width": "2" + } + } + } + } + } +} +} \ No newline at end of file