diff --git a/dotCMS/src/main/java/com/dotcms/rest/api/v1/page/AbstractContentletStylingView.java b/dotCMS/src/main/java/com/dotcms/rest/api/v1/page/AbstractContentView.java similarity index 72% rename from dotCMS/src/main/java/com/dotcms/rest/api/v1/page/AbstractContentletStylingView.java rename to dotCMS/src/main/java/com/dotcms/rest/api/v1/page/AbstractContentView.java index 6089a1e92404..55bdcbb1926e 100644 --- a/dotCMS/src/main/java/com/dotcms/rest/api/v1/page/AbstractContentletStylingView.java +++ b/dotCMS/src/main/java/com/dotcms/rest/api/v1/page/AbstractContentView.java @@ -1,18 +1,22 @@ package com.dotcms.rest.api.v1.page; +import com.dotcms.annotations.Nullable; +import com.dotmarketing.portlets.contentlet.model.Contentlet; +import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema.RequiredMode; import java.util.Map; import org.immutables.value.Value; @Value.Style(typeImmutable = "*", typeAbstract = "Abstract*") @Value.Immutable -@JsonSerialize(as = ContentletStylingView.class) -@JsonDeserialize(as = ContentletStylingView.class) +@JsonSerialize(as = ContentView.class) +@JsonDeserialize(as = ContentView.class) @Schema(description = "Contentlet with Styles info") -public interface AbstractContentletStylingView { +public interface AbstractContentView { @JsonProperty("containerId") @Schema( @@ -36,11 +40,13 @@ public interface AbstractContentletStylingView { ) String contentletId(); - @JsonProperty("styleProperties") + @JsonProperty(Contentlet.STYLE_PROPERTIES_KEY) + @Nullable + @JsonInclude(JsonInclude.Include.NON_NULL) @Schema( description = "Styles defined for the Contentlet", example = "{\"color\": \"#FF0000\", \"margin\": \"10px\"}", - requiredMode = Schema.RequiredMode.REQUIRED + requiredMode = RequiredMode.NOT_REQUIRED ) Map styleProperties(); } \ No newline at end of file diff --git a/dotCMS/src/main/java/com/dotcms/rest/api/v1/page/PageResource.java b/dotCMS/src/main/java/com/dotcms/rest/api/v1/page/PageResource.java index 3e6f05115645..c306c2eeafed 100644 --- a/dotCMS/src/main/java/com/dotcms/rest/api/v1/page/PageResource.java +++ b/dotCMS/src/main/java/com/dotcms/rest/api/v1/page/PageResource.java @@ -813,10 +813,10 @@ public final Response addContent(@Context final HttpServletRequest request, this.validateContainerEntries(pageContainerForm.getContainerEntries()); // Save content and Get the saved contentlets - final List savedContent = pageResourceHelper.saveContent( + final List savedContent = pageResourceHelper.saveContent( pageId, this.reduce(pageContainerForm.getContainerEntries()), language, variantName); - return Response.ok(new ResponseEntityContentletStylingView(savedContent)).build(); + return Response.ok(new ResponseEntityContentView(savedContent)).build(); } catch(HTMLPageAssetNotFoundException e) { final String errorMsg = String.format("HTMLPageAssetNotFoundException on PageResource.addContent, pageId: %s: ", pageId); @@ -891,7 +891,7 @@ class ContainerData { data.contentIds.addAll(containerEntry.getContentIds()); // Merge styles. Duplicated keys overwrite previous ones (last one wins) - final Map> incomingStyles = Optional.ofNullable( + final Map> incomingStyles = Optional.of( containerEntry.getStylePropertiesMap()).orElse(Collections.emptyMap()); data.stylePropertiesMap.putAll(incomingStyles); diff --git a/dotCMS/src/main/java/com/dotcms/rest/api/v1/page/PageResourceHelper.java b/dotCMS/src/main/java/com/dotcms/rest/api/v1/page/PageResourceHelper.java index 6a947cd2fbd6..19a2142fce80 100644 --- a/dotCMS/src/main/java/com/dotcms/rest/api/v1/page/PageResourceHelper.java +++ b/dotCMS/src/main/java/com/dotcms/rest/api/v1/page/PageResourceHelper.java @@ -55,6 +55,7 @@ import com.dotmarketing.portlets.templates.design.bean.ContainerUUID; import com.dotmarketing.portlets.templates.design.bean.TemplateLayout; import com.dotmarketing.portlets.templates.model.Template; +import com.dotmarketing.util.Config; import com.dotmarketing.util.Logger; import com.dotmarketing.util.PageMode; import com.dotmarketing.util.UtilMethods; @@ -153,7 +154,7 @@ public HttpServletRequest decorateRequest(final HttpServletRequest request) { * @throws BadRequestException if style validation fails */ @WrapInTransaction - public List saveContent(final String pageId, + public List saveContent(final String pageId, final List containerEntries, final Language language, String variantName) throws DotDataException { @@ -232,7 +233,7 @@ public void run() { } /** - * Validates the style properties during the saving process. + * Validates the style properties for a given contentlet and container during the saving process. * @param stylePropertiesMap The map of style properties. * @param contentIds The list of contentlet ids. * @param containerId The id of the container. @@ -271,18 +272,19 @@ private void stylePropertiesValidation( * @return A list of the saved Contentlets with the containerId, uuid, contentletId and * styleProperties. */ - private List buildSaveContentResponse(List savedMultiTrees) { + private List buildSaveContentResponse(List savedMultiTrees) { return savedMultiTrees.stream() .map(multiTree -> { - ContentletStylingView.Builder builder = ContentletStylingView.builder() + ContentView.Builder builder = ContentView.builder() .containerId(multiTree.getContainer()) .contentletId(multiTree.getContentlet()) .uuid(multiTree.getRelationType()); - // Add Style properties if present - final Map styleProperties = multiTree.getStyleProperties(); - if (styleProperties != null && !styleProperties.isEmpty()) { - builder.putAllStyleProperties(styleProperties); + // Include style properties in the response if Style Editor FF is enabled + final boolean isStyleEditorEnabled = Config.getBooleanProperty("FEATURE_FLAG_UVE_STYLE_EDITOR", false); + if (isStyleEditorEnabled) { + final Map styleProperties = multiTree.getStyleProperties(); + builder.putAllStyleProperties(styleProperties != null ? styleProperties : new HashMap<>()); } return builder.build(); diff --git a/dotCMS/src/main/java/com/dotcms/rest/api/v1/page/ResponseEntityContentletStylingView.java b/dotCMS/src/main/java/com/dotcms/rest/api/v1/page/ResponseEntityContentView.java similarity index 69% rename from dotCMS/src/main/java/com/dotcms/rest/api/v1/page/ResponseEntityContentletStylingView.java rename to dotCMS/src/main/java/com/dotcms/rest/api/v1/page/ResponseEntityContentView.java index 4f1c3043ce87..b7874bbf2e70 100644 --- a/dotCMS/src/main/java/com/dotcms/rest/api/v1/page/ResponseEntityContentletStylingView.java +++ b/dotCMS/src/main/java/com/dotcms/rest/api/v1/page/ResponseEntityContentView.java @@ -6,14 +6,14 @@ /** * Response wrapper for contentlets with its container, page and styles information. */ -public class ResponseEntityContentletStylingView extends ResponseEntityView> { +public class ResponseEntityContentView extends ResponseEntityView> { /** * Constructor for contentlets with its container, page and styles information. * * @param contentletStylingList The list of ContentletStylingView objects to be included in the response. */ - public ResponseEntityContentletStylingView(List contentletStylingList) { + public ResponseEntityContentView(List contentletStylingList) { super(contentletStylingList); } } \ No newline at end of file diff --git a/dotCMS/src/main/java/com/dotmarketing/factories/MultiTreeAPIImpl.java b/dotCMS/src/main/java/com/dotmarketing/factories/MultiTreeAPIImpl.java index f85d86af6496..1ff3df005b8d 100644 --- a/dotCMS/src/main/java/com/dotmarketing/factories/MultiTreeAPIImpl.java +++ b/dotCMS/src/main/java/com/dotmarketing/factories/MultiTreeAPIImpl.java @@ -126,6 +126,8 @@ public class MultiTreeAPIImpl implements MultiTreeAPI { private static final String SELECT_CHILD_BY_PARENT_RELATION_PERSONALIZATION_VARIANT_LANGUAGE = SELECT_CHILD_BY_PARENT_RELATION_PERSONALIZATION_VARIANT + " AND child IN (SELECT DISTINCT identifier FROM contentlet, multi_tree " + "WHERE multi_tree.child = contentlet.identifier AND multi_tree.parent1 = ? AND language_id = ?)"; + private static final String SELECT_NOT_EMPTY_CONTENTLET_STYLES_BY_PAGE = + SELECT_ALL + "WHERE parent1 = ? AND style_properties IS NOT NULL"; @WrapInTransaction @Override @@ -661,7 +663,7 @@ public void overridesMultitreesByPersonalization(final String pageId, * @param multiTrees {@link List} of {@link MultiTree} to safe * @param languageIdOpt {@link Optional} {@link Long} optional language, if present will deletes only the contentlets that have a version on this language. * Since it is by identifier, when deleting for instance in spanish, will remove the english and any other lang version too. - * @throws DotDataException + * @throws DotDataException If there is an issue retrieving data from the DB. */ @Override @WrapInTransaction @@ -683,6 +685,16 @@ public void overridesMultitreesByPersonalization(final String pageId, Logger.debug(MultiTreeAPIImpl.class, ()->String.format("Saving page's content: %s", multiTrees)); Set originalContentletIds = new HashSet<>(); final DotConnect db = new DotConnect(); + + // Preserves already existing styles if Style Editor is disabled + final boolean isStyleEditorEnabled = Config.getBooleanProperty("FEATURE_FLAG_UVE_STYLE_EDITOR", false); + if (!isStyleEditorEnabled) { + // Delete incoming styles (DB is the only source of truth) + multiTrees.forEach(mTree -> mTree.setStyleProperties(null)); + // Restore existing styles from DB + preserveStylesBeforeSaving(pageId, multiTrees); + } + if (languageIdOpt.isPresent()) { if (DbConnectionFactory.isMySql()) { deleteMultiTreeToMySQL(pageId, personalization, languageIdOpt, variantId); @@ -1570,6 +1582,62 @@ private Set getOriginalContentlets(final String sqlQuery, final List dataMap.get("child").toString()).collect(Collectors.toSet()); } + /** + * Restores the style properties for a list of MultiTree objects by looking up + * their existing values in the database. + * This prevents style data loss when overwriting existing records. It matches + * records based on the composite key of Container + Contentlet. + * + * @param pageId The identifier of the page being processed. + * @param multiTrees The list of MultiTree objects to be saved. + * NOTE: This list is modified in-place. + * The same list of multiTrees will be enriched with their original styles. + * @throws DotDataException If there is an issue retrieving data from the DB. + */ + private void preserveStylesBeforeSaving(final String pageId, List multiTrees) throws DotDataException { + // Gets existing multiTrees by Page from DB + final List multiTreesFromDB = fetchStylesFromDB(pageId); + + if (multiTreesFromDB.isEmpty()) { + return; + } + + // Create a "Lookup Map" from the DB multiTrees. + // Key: Unique combination of Container + Contentlet + // Value: Contentlet styleProperties + final Map> dbStyleMap = multiTreesFromDB.stream() + .collect(Collectors.toMap( + mt -> mt.getContainer() + "_" + mt.getContentlet(), + MultiTree::getStyleProperties, + // In case of duplicates, keep last entrance value (shouldn't happen) + (existing, replacement) -> replacement + )); + + // Update the multiTrees list to preserve existing styleProperties + for (MultiTree multiTree : multiTrees) { + String key = multiTree.getContainer() + "_" + multiTree.getContentlet(); + + // If this relationship already existed in DB, preserves the old styles + if (dbStyleMap.containsKey(key)) { + multiTree.setStyleProperties(dbStyleMap.get(key)); + } + } + } + + /** + * Fetches the style properties from the database for a given page. + * + * @param pageId The ID of the page to fetch the style properties from. + * @return The list of MultiTree objects with the style properties. + * @throws DotDataException If there is an issue retrieving data from the DB. + */ + protected List fetchStylesFromDB(String pageId) throws DotDataException { + final DotConnect db = new DotConnect() + .setSQL(SELECT_NOT_EMPTY_CONTENTLET_STYLES_BY_PAGE) + .addParam(pageId); + return TransformerLocator.createMultiTreeTransformer(db.loadObjectResults()).asList(); + } + /** * Takes the list of Contentlet IDs present in an HTML Page before any change is made, and the list of * {@link MultiTree} objects representing the updated Contentlets. Then, compares both lists and determines what diff --git a/dotCMS/src/main/java/com/dotmarketing/portlets/contentlet/model/Contentlet.java b/dotCMS/src/main/java/com/dotmarketing/portlets/contentlet/model/Contentlet.java index 853c1322fefa..b52396c66d60 100644 --- a/dotCMS/src/main/java/com/dotmarketing/portlets/contentlet/model/Contentlet.java +++ b/dotCMS/src/main/java/com/dotmarketing/portlets/contentlet/model/Contentlet.java @@ -155,7 +155,7 @@ public class Contentlet implements Serializable, Permissionable, Categorizable, public static final String DONT_VALIDATE_ME = "_dont_validate_me"; public static final String DISABLE_WORKFLOW = "__disable_workflow__"; public static final String VALIDATE_EMPTY_FILE = "_validateEmptyFile_"; - public static final String STYLE_PROPERTIES_KEY = "styleProperties"; + public static final String STYLE_PROPERTIES_KEY = "dotStyleProperties"; // means the contentlet is being used on unit test mode. // this is only for unit test. do not use on production. diff --git a/dotcms-integration/src/test/java/com/dotcms/rest/api/v1/page/PageResourceTest.java b/dotcms-integration/src/test/java/com/dotcms/rest/api/v1/page/PageResourceTest.java index 32591dbce540..63c47e5e72ae 100644 --- a/dotcms-integration/src/test/java/com/dotcms/rest/api/v1/page/PageResourceTest.java +++ b/dotcms-integration/src/test/java/com/dotcms/rest/api/v1/page/PageResourceTest.java @@ -93,6 +93,7 @@ import com.dotmarketing.portlets.structure.model.Structure; import com.dotmarketing.portlets.templates.design.bean.TemplateLayout; import com.dotmarketing.portlets.templates.model.Template; +import com.dotmarketing.util.Config; import com.dotmarketing.util.Logger; import com.dotmarketing.util.PageMode; import com.dotmarketing.util.PaginatedArrayList; @@ -329,89 +330,98 @@ public void test_addContent_invalid_content_type() throws Exception { */ @Test public void test_addContent_with_styleProperties() throws Exception { - // Create a page with container - final PageRenderTestUtil.PageRenderTest pageRenderTest = PageRenderTestUtil.createPage(1, host); - final HTMLPageAsset testPage = pageRenderTest.getPage(); - final Container container = pageRenderTest.getFirstContainer(); - - // Create contentlet - final ContentTypeAPI contentTypeAPI = APILocator.getContentTypeAPI(APILocator.systemUser()); - final ContentType contentGenericType = contentTypeAPI.find("webPageContent"); - final Contentlet contentlet = new ContentletDataGen(contentGenericType.id()) - .languageId(1) - .folder(APILocator.getFolderAPI().findSystemFolder()) - .host(host) - .setProperty("title", "Test Content with Style Properties") - .setProperty("body", TestDataUtils.BLOCK_EDITOR_DUMMY_CONTENT) - .nextPersisted(); - - contentlet.setIndexPolicy(IndexPolicy.WAIT_FOR); - contentlet.setIndexPolicyDependencies(IndexPolicy.WAIT_FOR); - contentlet.setBoolProperty(Contentlet.IS_TEST_MODE, true); - APILocator.getContentletAPI().publish(contentlet, user, false); - - // Prepare styleProperties - final Map styleProperties = new HashMap<>(); - styleProperties.put("backgroundColor", "red"); - styleProperties.put("fontSize", "16px"); - styleProperties.put("padding", "10px"); - - // Create ContainerEntry with styleProperties - final List entries = new ArrayList<>(); - final String containerUUID = UUIDGenerator.generateUuid(); - final Map> stylePropertiesMap = new HashMap<>(); - stylePropertiesMap.put(contentlet.getIdentifier(), styleProperties); - - final PageContainerForm.ContainerEntry containerEntry = - new PageContainerForm.ContainerEntry( - null, - container.getIdentifier(), - containerUUID, - list(contentlet.getIdentifier()), - stylePropertiesMap - ); + // Save the original feature flag value + final boolean originalFeatureFlagValue = Config.getBooleanProperty("FEATURE_FLAG_UVE_STYLE_EDITOR", false); - entries.add(containerEntry); - final PageContainerForm pageContainerForm = new PageContainerForm(entries, null); - - // Save content with styleProperties - final Response addContentResponse = this.pageResourceWithHelper.addContent( - request, - response, - testPage.getIdentifier(), - VariantAPI.DEFAULT_VARIANT.name(), - pageContainerForm - ); - - // Verify response is successful - assertNotNull(addContentResponse); - assertEquals(200, addContentResponse.getStatus()); - - // Retrieve MultiTree and verify styleProperties are saved - final MultiTreeAPI multiTreeAPI = APILocator.getMultiTreeAPI(); - final List multiTrees = multiTreeAPI.getMultiTrees(testPage.getIdentifier()); - - assertNotNull("MultiTrees should not be null", multiTrees); - assertFalse("MultiTrees should not be empty", multiTrees.isEmpty()); - - // Find the MultiTree for our contentlet - final Optional multiTreeOpt = multiTrees.stream() - .filter(mt -> mt.getContentlet().equals(contentlet.getIdentifier())) - .findFirst(); - - assertTrue("MultiTree for the contentlet should exist", multiTreeOpt.isPresent()); - - final MultiTree multiTree = multiTreeOpt.get(); - final Map savedStyleProperties = multiTree.getStyleProperties(); - - // Verify styleProperties were saved correctly - assertNotNull("StyleProperties should not be null", savedStyleProperties); - assertFalse("StyleProperties should not be empty", savedStyleProperties.isEmpty()); - assertEquals("backgroundColor should match", "red", savedStyleProperties.get("backgroundColor")); - assertEquals("fontSize should match", "16px", savedStyleProperties.get("fontSize")); - assertEquals("padding should match", "10px", savedStyleProperties.get("padding")); + try { + // Enable the Style Editor feature flag + Config.setProperty("FEATURE_FLAG_UVE_STYLE_EDITOR", true); + final PageRenderTestUtil.PageRenderTest pageRenderTest = PageRenderTestUtil.createPage(1, host); + final HTMLPageAsset testPage = pageRenderTest.getPage(); + final Container container = pageRenderTest.getFirstContainer(); - Logger.info(this, "StyleProperties saved successfully: " + savedStyleProperties); + // Create contentlet + final ContentTypeAPI contentTypeAPI = APILocator.getContentTypeAPI(APILocator.systemUser()); + final ContentType contentGenericType = contentTypeAPI.find("webPageContent"); + final Contentlet contentlet = new ContentletDataGen(contentGenericType.id()) + .languageId(1) + .folder(APILocator.getFolderAPI().findSystemFolder()) + .host(host) + .setProperty("title", "Test Content with Style Properties") + .setProperty("body", TestDataUtils.BLOCK_EDITOR_DUMMY_CONTENT) + .nextPersisted(); + + contentlet.setIndexPolicy(IndexPolicy.WAIT_FOR); + contentlet.setIndexPolicyDependencies(IndexPolicy.WAIT_FOR); + contentlet.setBoolProperty(Contentlet.IS_TEST_MODE, true); + APILocator.getContentletAPI().publish(contentlet, user, false); + + // Prepare styleProperties + final Map styleProperties = new HashMap<>(); + styleProperties.put("backgroundColor", "red"); + styleProperties.put("fontSize", "16px"); + styleProperties.put("padding", "10px"); + + // Create ContainerEntry with styleProperties + final List entries = new ArrayList<>(); + final String containerUUID = UUIDGenerator.generateUuid(); + final Map> stylePropertiesMap = new HashMap<>(); + stylePropertiesMap.put(contentlet.getIdentifier(), styleProperties); + + final PageContainerForm.ContainerEntry containerEntry = + new PageContainerForm.ContainerEntry( + null, + container.getIdentifier(), + containerUUID, + list(contentlet.getIdentifier()), + stylePropertiesMap + ); + + entries.add(containerEntry); + final PageContainerForm pageContainerForm = new PageContainerForm(entries, null); + + // Save content with styleProperties + final Response addContentResponse = this.pageResourceWithHelper.addContent( + request, + response, + testPage.getIdentifier(), + VariantAPI.DEFAULT_VARIANT.name(), + pageContainerForm + ); + + // Verify response is successful + assertNotNull(addContentResponse); + assertEquals(200, addContentResponse.getStatus()); + + // Retrieve MultiTree and verify styleProperties are saved + final MultiTreeAPI multiTreeAPI = APILocator.getMultiTreeAPI(); + final List multiTrees = multiTreeAPI.getMultiTrees(testPage.getIdentifier()); + + assertNotNull("MultiTrees should not be null", multiTrees); + assertFalse("MultiTrees should not be empty", multiTrees.isEmpty()); + + // Find the MultiTree for our contentlet + final Optional multiTreeOpt = multiTrees.stream() + .filter(mt -> mt.getContentlet().equals(contentlet.getIdentifier())) + .findFirst(); + + assertTrue("MultiTree for the contentlet should exist", multiTreeOpt.isPresent()); + + final MultiTree multiTree = multiTreeOpt.get(); + final Map savedStyleProperties = multiTree.getStyleProperties(); + + // Verify styleProperties were saved correctly + assertNotNull("StyleProperties should not be null", savedStyleProperties); + assertFalse("StyleProperties should not be empty", savedStyleProperties.isEmpty()); + assertEquals("backgroundColor should match", "red", savedStyleProperties.get("backgroundColor")); + assertEquals("fontSize should match", "16px", savedStyleProperties.get("fontSize")); + assertEquals("padding should match", "10px", savedStyleProperties.get("padding")); + + Logger.info(this, "StyleProperties saved successfully: " + savedStyleProperties); + } finally { + // Restore the original feature flag value + Config.setProperty("FEATURE_FLAG_UVE_STYLE_EDITOR", originalFeatureFlagValue); + } } /** diff --git a/dotcms-postman/src/main/resources/postman/Define_Contentlets_StyleProperties.postman_collection.json b/dotcms-postman/src/main/resources/postman/Define_Contentlets_StyleProperties.postman_collection.json index 6093710323fd..0a78c5c7ea12 100644 --- a/dotcms-postman/src/main/resources/postman/Define_Contentlets_StyleProperties.postman_collection.json +++ b/dotcms-postman/src/main/resources/postman/Define_Contentlets_StyleProperties.postman_collection.json @@ -422,6 +422,56 @@ { "name": "Defining Contentlet Styles", "item": [ + { + "name": "Turn ON Style Editor", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 200\", function () {", + " pm.response.to.have.status(200);", + "});", + "", + "pm.test(\"FEATURE_FLAG_UVE_STYLE_EDITOR is enabled\", function () {", + " const entity = pm.response.json().entity;", + " pm.expect(entity).to.exist;", + " pm.expect(entity).to.eql('FEATURE_FLAG_UVE_STYLE_EDITOR saved/updated');", + "});" + ], + "type": "text/javascript", + "packages": {}, + "requests": {} + } + } + ], + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"key\": \"FEATURE_FLAG_UVE_STYLE_EDITOR\",\n \"value\": \"true\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{serverURL}}/api/v1/system-table/", + "host": [ + "{{serverURL}}" + ], + "path": [ + "api", + "v1", + "system-table", + "" + ] + } + }, + "response": [] + }, { "name": "Save Content With Basic Styles", "event": [ @@ -453,10 +503,10 @@ "pm.test(\"First entry has correct styleProperties\", function () {", " const first = json.entity.find(item => item.contentletId === contentlet1);", " pm.expect(first).to.exist;", - " pm.expect(first.styleProperties).to.be.an(\"object\");", - " pm.expect(first.styleProperties.width).to.eql(\"100px\");", - " pm.expect(first.styleProperties.color).to.eql(\"#FF0000\");", - " pm.expect(first.styleProperties.margin).to.eql(\"10px\");", + " pm.expect(first.dotStyleProperties).to.be.an(\"object\");", + " pm.expect(first.dotStyleProperties.width).to.eql(\"100px\");", + " pm.expect(first.dotStyleProperties.color).to.eql(\"#FF0000\");", + " pm.expect(first.dotStyleProperties.margin).to.eql(\"10px\");", "});", "", "// ============================", @@ -465,7 +515,7 @@ "pm.test(\"Second entry has no style properties\", function () {", " const second = json.entity.find(item => item.contentletId === contentlet2);", " pm.expect(second).to.exist;", - " pm.expect(second.styleProperties).to.eql({});", + " pm.expect(second.dotStyleProperties).to.eql({});", "});" ], "type": "text/javascript", @@ -537,12 +587,12 @@ "pm.test(`Validate ${contentlet1} has correct styleProperties`, function () {", " const contentlet = entity.find(item => item.contentletId === contentlet1);", " pm.expect(contentlet).to.exist;", - " pm.expect(contentlet.styleProperties).to.be.an(\"object\");", - " pm.expect(contentlet.styleProperties.height).to.eql(300);", - " pm.expect(contentlet.styleProperties.visible).to.be.true;", + " pm.expect(contentlet.dotStyleProperties).to.be.an(\"object\");", + " pm.expect(contentlet.dotStyleProperties.height).to.eql(300);", + " pm.expect(contentlet.dotStyleProperties.visible).to.be.true;", "", " // Deep styleProperties object", - " const styleLayout = contentlet.styleProperties.layout;", + " const styleLayout = contentlet.dotStyleProperties.layout;", " pm.expect(styleLayout).to.be.an(\"object\");", " pm.expect(styleLayout.display).to.eql(\"flex\");", " pm.expect(styleLayout.gap).to.eql(16);", @@ -711,7 +761,7 @@ " const systemContainer = json.entity.find(item => item.containerId === \"SYSTEM_CONTAINER\");", " pm.expect(systemContainer).to.exist;", " pm.expect(systemContainer.contentletId).to.eql(contentlet1);", - " pm.expect(systemContainer.styleProperties.width).to.eql(\"100px\");", + " pm.expect(systemContainer.dotStyleProperties.width).to.eql(\"100px\");", "});", "", "// ============================", @@ -721,7 +771,7 @@ " const customContainer = json.entity.find(item => item.containerId === CUSTOM_CONTAINER);", " pm.expect(customContainer).to.exist;", " pm.expect(customContainer.contentletId).to.eql(contentlet1);", - " pm.expect(customContainer.styleProperties.color).to.eql(\"#FF0000\");", + " pm.expect(customContainer.dotStyleProperties.color).to.eql(\"#FF0000\");", "});" ], "type": "text/javascript", @@ -769,6 +819,56 @@ "description": "Test when users make a request sending the style properties for some contentlets.\nThis will save the style properties for the specified contentlets, in case style properties are not send the contentlet style properties will be null." }, "response": [] + }, + { + "name": "Turn OFF Style Editor", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 200\", function () {", + " pm.response.to.have.status(200);", + "});", + "", + "pm.test(\"FEATURE_FLAG_UVE_STYLE_EDITOR is disabled\", function () {", + " const entity = pm.response.json().entity;", + " pm.expect(entity).to.exist;", + " pm.expect(entity).to.eql('FEATURE_FLAG_UVE_STYLE_EDITOR Deleted');", + "});" + ], + "type": "text/javascript", + "packages": {}, + "requests": {} + } + } + ], + "request": { + "method": "DELETE", + "header": [], + "body": { + "mode": "raw", + "raw": "{\"key\":\"FEATURE_FLAG_UVE_STYLE_EDITOR\"}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{serverURL}}/api/v1/system-table/_delete", + "host": [ + "{{serverURL}}" + ], + "path": [ + "api", + "v1", + "system-table", + "_delete" + ] + } + }, + "response": [] } ], "description": "### Defining Contentlet Styles:\n\nTest the Contentlet Style definition in the `testPageStyles_1`.\n\n- Save Contentlets with Basic Styles\n \n - `Contentlet 1` with basic Style Properties\n \n - `Contentlet 2` without Styles\n \n- Save Contentlet with Complex Styles\n \n - `Contentlet 1` with deep object definition Style Properties\n \n- BadRequest trying to set Styles for a contentlet that does not exists, should fail.\n \n- Save Styles for the same Contentlet but in different Containers, each of them does not affect the other one.\n \n - `Contentlet 1` saved with \"width = 100px\" style value in the `SYSTEM_CONTAINER`\n \n - `Contentlet 1` saved with \"color = \"#FF0000\" style in the custom Container `Styles_Container`" @@ -964,7 +1064,7 @@ " // filter by contentlet", " const contentlet = contentletList.find(c => c.identifier === contentlet1);", " pm.expect(contentlet).to.exist;", - " pm.expect(contentlet.styleProperties.color).to.eql(\"#FF0000\");", + " pm.expect(contentlet.dotStyleProperties.color).to.eql(\"#FF0000\");", "});" ], "type": "text/javascript", @@ -1028,7 +1128,7 @@ " // filter by contentlet", " const contentlet = contentletList.find(c => c.identifier === contentlet1);", " pm.expect(contentlet).to.exist;", - " pm.expect(contentlet.styleProperties.width).to.eql(\"100px\");", + " pm.expect(contentlet.dotStyleProperties.width).to.eql(\"100px\");", "});" ], "type": "text/javascript", @@ -1142,7 +1242,7 @@ " const contentlet = contentletList.find(c => c.identifier === contentlet1);", " pm.expect(contentlet).to.exist;", " // Validate that styles are not present", - " pm.expect(contentlet.styleProperties).to.be.undefined;", + " pm.expect(contentlet.dotStyleProperties).to.be.undefined;", "});" ], "type": "text/javascript", @@ -1186,6 +1286,687 @@ } ], "description": "### Retrieve Contentlet Styles:\n\nTest that the Contentlets have specific Styles defined when the feature flag `FEATURE_FLAG_UVE_STYLE_EDITOR` is enabled.\n\n- Turn ON `FEATURE_FLAG_UVE_STYLE_EDITOR` to allow the visualization of the Style Properties.\n \n- Save the **same** **Contentlet** in the **same** **Container** but in **different** **Pages**\n \n - `Contentlet 1` in the `SYSTEM_CONTAINER` **Container**, inside the **Page**`testPageStyles_1`\n \n - `Contentlet 1` in the `SYSTEM_CONTAINER` **Container**, inside the **Page**`testPageStyles_2`\n \n- Validate that the **Contentlet** `Contentlet 1` within the `testPageStyles_1` **Page** have the \"color = #FF0000\" Style.\n \n- Validate that the **Contentlet** `Contentlet 1` within the `testPageStyles_2` **Page** have the \"width = 100px\" Style.\n \n- Turn OFF `FEATURE_FLAG_UVE_STYLE_EDITOR`\n \n - Get **Contentlet** `Contentlet 1` does not contain `styleProperties` field" + }, + { + "name": "Persist Contentlet Styles", + "item": [ + { + "name": "Turn ON Style Editor", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 200\", function () {", + " pm.response.to.have.status(200);", + "});", + "", + "pm.test(\"FEATURE_FLAG_UVE_STYLE_EDITOR is enabled\", function () {", + " const entity = pm.response.json().entity;", + " pm.expect(entity).to.exist;", + " pm.expect(entity).to.eql('FEATURE_FLAG_UVE_STYLE_EDITOR saved/updated');", + "});" + ], + "type": "text/javascript", + "packages": {}, + "requests": {} + } + } + ], + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"key\": \"FEATURE_FLAG_UVE_STYLE_EDITOR\",\n \"value\": \"true\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{serverURL}}/api/v1/system-table/", + "host": [ + "{{serverURL}}" + ], + "path": [ + "api", + "v1", + "system-table", + "" + ] + } + }, + "response": [] + }, + { + "name": "Save Content With Styles FF On", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "const contentlet1 = pm.collectionVariables.get(\"Contentlet_1\");", + "const contentlet2 = pm.collectionVariables.get(\"Contentlet_2\");", + "", + "pm.test(\"Status code is 200\", function () {", + " pm.response.to.have.status(200);", + "});", + "", + "// Parse JSON", + "const json = pm.response.json();", + "", + "pm.test(\"Entity array exists\", function () {", + " pm.expect(json.entity).to.be.an(\"array\").that.is.not.empty;", + "});", + "", + "pm.test(\"No errors returned\", function () {", + " pm.expect(json.errors).to.be.an(\"array\").that.is.empty;", + "});", + "", + "// ============================", + "// Validate first merged entry", + "// ============================", + "pm.test(\"First entry has correct styleProperties\", function () {", + " const first = json.entity.find(item => item.contentletId === contentlet1);", + " pm.expect(first).to.exist;", + " pm.expect(first.dotStyleProperties).to.be.an(\"object\");", + " pm.expect(first.dotStyleProperties.width).to.eql(\"100px\");", + " pm.expect(first.dotStyleProperties.color).to.eql(\"#FF0000\");", + " pm.expect(first.dotStyleProperties.margin).to.eql(\"10px\");", + "});", + "", + "// ============================", + "// Validate second merged entry", + "// ============================", + "pm.test(\"Second entry has no style properties\", function () {", + " const second = json.entity.find(item => item.contentletId === contentlet2);", + " pm.expect(second).to.exist;", + " pm.expect(second.dotStyleProperties).to.eql({});", + "});" + ], + "type": "text/javascript", + "packages": {}, + "requests": {} + } + }, + { + "listen": "prerequest", + "script": { + "exec": [ + "" + ], + "type": "text/javascript", + "packages": {}, + "requests": {} + } + } + ], + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "[\n {\n \"identifier\": \"SYSTEM_CONTAINER\",\n \"uuid\": \"1\",\n \"contentletsId\": [\n \"{{Contentlet_1}}\",\n \"{{Contentlet_2}}\"\n ],\n \"styleProperties\": {\n \"{{Contentlet_1}}\": {\n \"width\": \"100px\",\n \"color\": \"#FF0000\",\n \"margin\": \"10px\"\n }\n }\n }\n]", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{serverURL}}/api/v1/page/{{firstPageIdentifier}}/content", + "host": [ + "{{serverURL}}" + ], + "path": [ + "api", + "v1", + "page", + "{{firstPageIdentifier}}", + "content" + ] + }, + "description": "Test when users make a request sending the style properties for some contentlets.\nThis will save the style properties for the specified contentlets, in case style properties are not send the contentlet style properties will be null." + }, + "response": [] + }, + { + "name": "Turn OFF Style Editor", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 200\", function () {", + " pm.response.to.have.status(200);", + "});", + "", + "pm.test(\"FEATURE_FLAG_UVE_STYLE_EDITOR is disabled\", function () {", + " const entity = pm.response.json().entity;", + " pm.expect(entity).to.exist;", + " pm.expect(entity).to.eql('FEATURE_FLAG_UVE_STYLE_EDITOR Deleted');", + "});" + ], + "type": "text/javascript", + "packages": {}, + "requests": {} + } + } + ], + "request": { + "method": "DELETE", + "header": [], + "body": { + "mode": "raw", + "raw": "{\"key\":\"FEATURE_FLAG_UVE_STYLE_EDITOR\"}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{serverURL}}/api/v1/system-table/_delete", + "host": [ + "{{serverURL}}" + ], + "path": [ + "api", + "v1", + "system-table", + "_delete" + ] + } + }, + "response": [] + }, + { + "name": "Save Content Styles FF Off", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "const contentlet1 = pm.collectionVariables.get(\"Contentlet_1\");", + "const contentlet2 = pm.collectionVariables.get(\"Contentlet_2\");", + "", + "pm.test(\"Status code is 200\", function () {", + " pm.response.to.have.status(200);", + "});", + "", + "// Parse JSON", + "const json = pm.response.json();", + "", + "pm.test(\"Entity array exists\", function () {", + " pm.expect(json.entity).to.be.an(\"array\").that.is.not.empty;", + "});", + "", + "pm.test(\"No errors returned\", function () {", + " pm.expect(json.errors).to.be.an(\"array\").that.is.empty;", + "});", + "", + "// ============================", + "// Validate first merged entry", + "// ============================", + "pm.test(\"Contentlet_1 entry has correct styleProperties\", function () {", + " const first = json.entity.find(item => item.contentletId === contentlet1);", + " pm.expect(first).to.exist;", + " pm.expect(first.dotStyleProperties).to.be.undefined;", + "});", + "", + "// ============================", + "// Validate second merged entry", + "// ============================", + "pm.test(\"Second entry has no style properties\", function () {", + " const second = json.entity.find(item => item.contentletId === contentlet2);", + " pm.expect(second).to.exist;", + " pm.expect(second.dotStyleProperties).to.be.undefined;", + "});" + ], + "type": "text/javascript", + "packages": {}, + "requests": {} + } + }, + { + "listen": "prerequest", + "script": { + "exec": [ + "" + ], + "type": "text/javascript", + "packages": {}, + "requests": {} + } + } + ], + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "[\n {\n \"identifier\": \"SYSTEM_CONTAINER\",\n \"uuid\": \"1\",\n \"contentletsId\": [\n \"{{Contentlet_1}}\",\n \"{{Contentlet_2}}\"\n ],\n \"styleProperties\": {\n \"{{Contentlet_1}}\": {\n \"width\": \"100px\",\n \"color\": \"#FF0000\",\n \"margin\": \"10px\"\n }\n }\n }\n]", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{serverURL}}/api/v1/page/{{firstPageIdentifier}}/content", + "host": [ + "{{serverURL}}" + ], + "path": [ + "api", + "v1", + "page", + "{{firstPageIdentifier}}", + "content" + ] + }, + "description": "Test when users make a request sending the style properties for some contentlets.\nThis will save the style properties for the specified contentlets, in case style properties are not send the contentlet style properties will be null." + }, + "response": [] + }, + { + "name": "Styles NOT Present FF Off", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "const contentlet1 = pm.collectionVariables.get(\"Contentlet_1\");", + "const contentlet2 = pm.collectionVariables.get(\"Contentlet_2\");", + "", + "pm.test(\"Status code is 200\", function () {", + " pm.response.to.have.status(200);", + "});", + "", + "pm.test(\"Validate Contentlet_1 has NOT styleProperties\", function () {", + " // filter by container", + " const entity = pm.response.json().entity;", + " const contentletList = entity.containers.SYSTEM_CONTAINER.contentlets[\"uuid-1\"];", + "", + " // filter by contentlet", + " const contentlet = contentletList.find(c => c.identifier === contentlet1);", + " pm.expect(contentlet).to.exist;", + " pm.expect(contentlet.dotStyleProperties).to.be.undefined;", + "});", + "", + "pm.test(\"Validate Contentlet_2 has NOT styleProperties\", function () {", + " // filter by container", + " const entity = pm.response.json().entity;", + " const contentletList = entity.containers.SYSTEM_CONTAINER.contentlets[\"uuid-1\"];", + "", + " // filter by contentlet", + " const contentlet = contentletList.find(c => c.identifier === contentlet2);", + " pm.expect(contentlet).to.exist;", + " pm.expect(contentlet.dotStyleProperties).to.be.undefined;", + "});" + ], + "type": "text/javascript", + "packages": {}, + "requests": {} + } + } + ], + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{serverURL}}/api/v1/page/render/{{firstPageUrl}}?language_id=1&mode=EDIT_MODE&host_id={{hostIdentifier}}", + "host": [ + "{{serverURL}}" + ], + "path": [ + "api", + "v1", + "page", + "render", + "{{firstPageUrl}}" + ], + "query": [ + { + "key": "language_id", + "value": "1" + }, + { + "key": "mode", + "value": "EDIT_MODE" + }, + { + "key": "host_id", + "value": "{{hostIdentifier}}" + } + ] + } + }, + "response": [] + }, + { + "name": "Turn ON Style Editor", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 200\", function () {", + " pm.response.to.have.status(200);", + "});", + "", + "pm.test(\"FEATURE_FLAG_UVE_STYLE_EDITOR is enabled\", function () {", + " const entity = pm.response.json().entity;", + " pm.expect(entity).to.exist;", + " pm.expect(entity).to.eql('FEATURE_FLAG_UVE_STYLE_EDITOR saved/updated');", + "});" + ], + "type": "text/javascript", + "packages": {}, + "requests": {} + } + } + ], + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"key\": \"FEATURE_FLAG_UVE_STYLE_EDITOR\",\n \"value\": \"true\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{serverURL}}/api/v1/system-table/", + "host": [ + "{{serverURL}}" + ], + "path": [ + "api", + "v1", + "system-table", + "" + ] + } + }, + "response": [] + }, + { + "name": "Persisted Styles FF On", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "const contentlet1 = pm.collectionVariables.get(\"Contentlet_1\");", + "", + "pm.test(\"Status code is 200\", function () {", + " pm.response.to.have.status(200);", + "});", + "", + "pm.test(\"Validate Contentlet_1 persists its styleProperties\", function () {", + " // filter by container", + " const entity = pm.response.json().entity;", + " const contentletList = entity.containers.SYSTEM_CONTAINER.contentlets[\"uuid-1\"];", + "", + " // filter by contentlet", + " const contentlet = contentletList.find(c => c.identifier === contentlet1);", + " pm.expect(contentlet).to.exist;", + " // Validate that styles defined with FF on are present", + " pm.expect(contentlet.dotStyleProperties.width).to.eql(\"100px\");", + " pm.expect(contentlet.dotStyleProperties.color).to.eql(\"#FF0000\");", + " pm.expect(contentlet.dotStyleProperties.margin).to.eql(\"10px\");", + "});" + ], + "type": "text/javascript", + "packages": {}, + "requests": {} + } + } + ], + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{serverURL}}/api/v1/page/render/{{firstPageUrl}}?language_id=1&mode=EDIT_MODE&host_id={{hostIdentifier}}", + "host": [ + "{{serverURL}}" + ], + "path": [ + "api", + "v1", + "page", + "render", + "{{firstPageUrl}}" + ], + "query": [ + { + "key": "language_id", + "value": "1" + }, + { + "key": "mode", + "value": "EDIT_MODE" + }, + { + "key": "host_id", + "value": "{{hostIdentifier}}" + } + ] + } + }, + "response": [] + }, + { + "name": "Delete Styles FF On (empty)", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "const contentlet1 = pm.collectionVariables.get(\"Contentlet_1\");", + "const contentlet2 = pm.collectionVariables.get(\"Contentlet_2\");", + "", + "pm.test(\"Status code is 200\", function () {", + " pm.response.to.have.status(200);", + "});", + "", + "// Parse JSON", + "const json = pm.response.json();", + "", + "pm.test(\"Entity array exists\", function () {", + " pm.expect(json.entity).to.be.an(\"array\").that.is.not.empty;", + "});", + "", + "pm.test(\"No errors returned\", function () {", + " pm.expect(json.errors).to.be.an(\"array\").that.is.empty;", + "});", + "", + "// ============================", + "// Validate first merged entry", + "// ============================", + "pm.test(\"First entry has no styleProperties\", function () {", + " const first = json.entity.find(item => item.contentletId === contentlet1);", + " pm.expect(first).to.exist;", + " pm.expect(first.dotStyleProperties).to.eql({});", + "});", + "", + "// ============================", + "// Validate second merged entry", + "// ============================", + "pm.test(\"Second entry has no style properties\", function () {", + " const second = json.entity.find(item => item.contentletId === contentlet2);", + " pm.expect(second).to.exist;", + " pm.expect(second.dotStyleProperties).to.eql({});", + "});" + ], + "type": "text/javascript", + "packages": {}, + "requests": {} + } + }, + { + "listen": "prerequest", + "script": { + "exec": [ + "" + ], + "type": "text/javascript", + "packages": {}, + "requests": {} + } + } + ], + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "[\n {\n \"identifier\": \"SYSTEM_CONTAINER\",\n \"uuid\": \"1\",\n \"contentletsId\": [\n \"{{Contentlet_1}}\",\n \"{{Contentlet_2}}\"\n ]\n }\n]", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{serverURL}}/api/v1/page/{{firstPageIdentifier}}/content", + "host": [ + "{{serverURL}}" + ], + "path": [ + "api", + "v1", + "page", + "{{firstPageIdentifier}}", + "content" + ] + }, + "description": "Test when users make a request sending the style properties for some contentlets.\nThis will save the style properties for the specified contentlets, in case style properties are not send the contentlet style properties will be null." + }, + "response": [] + }, + { + "name": "Styles NOT Present FF On", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "const contentlet1 = pm.collectionVariables.get(\"Contentlet_1\");", + "", + "pm.test(\"Status code is 200\", function () {", + " pm.response.to.have.status(200);", + "});", + "", + "pm.test(\"Validate Contentlet_1 styleProperties were deleted\", function () {", + " // filter by container", + " const entity = pm.response.json().entity;", + " const contentletList = entity.containers.SYSTEM_CONTAINER.contentlets[\"uuid-1\"];", + "", + " // filter by contentlet", + " const contentlet = contentletList.find(c => c.identifier === contentlet1);", + " pm.expect(contentlet).to.exist;", + " // Validate that styles defined with FF on are NOT present", + " pm.expect(contentlet.dotStyleProperties).to.be.undefined;", + "});" + ], + "type": "text/javascript", + "packages": {}, + "requests": {} + } + } + ], + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{serverURL}}/api/v1/page/render/{{firstPageUrl}}?language_id=1&mode=EDIT_MODE&host_id={{hostIdentifier}}", + "host": [ + "{{serverURL}}" + ], + "path": [ + "api", + "v1", + "page", + "render", + "{{firstPageUrl}}" + ], + "query": [ + { + "key": "language_id", + "value": "1" + }, + { + "key": "mode", + "value": "EDIT_MODE" + }, + { + "key": "host_id", + "value": "{{hostIdentifier}}" + } + ] + } + }, + "response": [] + }, + { + "name": "Turn OFF Style Editor", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 200\", function () {", + " pm.response.to.have.status(200);", + "});", + "", + "pm.test(\"FEATURE_FLAG_UVE_STYLE_EDITOR is disabled\", function () {", + " const entity = pm.response.json().entity;", + " pm.expect(entity).to.exist;", + " pm.expect(entity).to.eql('FEATURE_FLAG_UVE_STYLE_EDITOR Deleted');", + "});" + ], + "type": "text/javascript", + "packages": {}, + "requests": {} + } + } + ], + "request": { + "method": "DELETE", + "header": [], + "body": { + "mode": "raw", + "raw": "{\"key\":\"FEATURE_FLAG_UVE_STYLE_EDITOR\"}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{serverURL}}/api/v1/system-table/_delete", + "host": [ + "{{serverURL}}" + ], + "path": [ + "api", + "v1", + "system-table", + "_delete" + ] + } + }, + "response": [] + } + ], + "description": "### Persist Contentlet Styles:\n\nTest that ensures Contentlets have the Styles defined when the feature flag `FEATURE_FLAG_UVE_STYLE_EDITOR` is enabled.\n\nIf the **FF is OFF** even if I **send** or not **styles** in the content definition it **won't affect** the **styles** that I **defined** when the FF **was ON**\n\n- Turn **ON** `FEATURE_FLAG_UVE_STYLE_EDITOR` to allow the visualization of the Style Properties.\n \n - Save Styles with **FF ON**\n \n - `Contentlet 1` has Styles `{width, color, margin}`.\n \n - `Contentlet 2` doesn't have Styles defined.\n \n- Turn **OFF** `FEATURE_FLAG_UVE_STYLE_EDITOR`\n \n - Save Styles with **FF OFF**\n \n - `Contentlet 1` send Styles in the request, but `styleProperties` field is not present in the response.\n \n - `Contentlet 2` doesn't define Styles in the request, and`styleProperties` field is not present in the response.\n \n - Get **Contentlet** `Contentlet 1` does not contain `styleProperties` field.\n \n- Turn **ON** `FEATURE_FLAG_UVE_STYLE_EDITOR.`\n \n - Get **Contentlet** `Contentlet 1` have `styleProperties = {width, color, margin}`.\n \n - Delete the Styles for the `Contentlet 1` and `Contentlet 2` by sending `styleProperties = {}`\n \n - Get **Contentlet** `Contentlet 1` does not contain `styleProperties` field.\n \n- Turn **OFF** `FEATURE_FLAG_UVE_STYLE_EDITOR`" } ], "auth": { diff --git a/dotcms-postman/src/main/resources/postman/GraphQLTests.json b/dotcms-postman/src/main/resources/postman/GraphQLTests.json index 5e2d799380d8..c53820d23149 100644 --- a/dotcms-postman/src/main/resources/postman/GraphQLTests.json +++ b/dotcms-postman/src/main/resources/postman/GraphQLTests.json @@ -4576,44 +4576,18 @@ "response": [] }, { - "name": "Save Content With Basic Styles Copy", + "name": "Turn ON Style Editor", "event": [ { "listen": "test", "script": { "exec": [ - "const contentlet1 = pm.collectionVariables.get(\"Contentlet_1\");", - "", "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", "});", "", - "// Parse JSON", - "const json = pm.response.json();", - "", - "pm.test(\"No errors returned\", function () {", - " pm.expect(json.errors).to.be.an(\"array\").that.is.empty;", - "});", - "", - "pm.test(\"Contentlet_1 has correct styleProperties\", function () {", - " const first = json.entity.find(item => item.contentletId === contentlet1);", - " pm.expect(first).to.exist;", - " pm.expect(first.styleProperties).to.be.an(\"object\");", - " pm.expect(first.styleProperties.width).to.eql(\"100px\");", - " pm.expect(first.styleProperties.color).to.eql(\"#FF0000\");", - " pm.expect(first.styleProperties.margin).to.eql(\"10px\");", - "});" - ], - "type": "text/javascript", - "packages": {}, - "requests": {} - } - }, - { - "listen": "prerequest", - "script": { - "exec": [ - "" + "// Wait for cache to invalidate", + "setTimeout(function(){}, 5000);" ], "type": "text/javascript", "packages": {}, @@ -4626,7 +4600,7 @@ "header": [], "body": { "mode": "raw", - "raw": "[\n {\n \"identifier\": \"SYSTEM_CONTAINER\",\n \"uuid\": \"1\",\n \"contentletsId\": [\n \"{{Contentlet_1}}\"\n ],\n \"styleProperties\": {\n \"{{Contentlet_1}}\": {\n \"width\": \"100px\",\n \"color\": \"#FF0000\",\n \"margin\": \"10px\"\n }\n }\n }\n]", + "raw": "{\n \"key\": \"FEATURE_FLAG_UVE_STYLE_EDITOR\",\n \"value\": \"true\"\n}", "options": { "raw": { "language": "json" @@ -4634,32 +4608,58 @@ } }, "url": { - "raw": "{{serverURL}}/api/v1/page/{{pageIdentifier}}/content", + "raw": "{{serverURL}}/api/v1/system-table", "host": [ "{{serverURL}}" ], "path": [ "api", "v1", - "page", - "{{pageIdentifier}}", - "content" + "system-table", + "" ] - }, - "description": "Test when users make a request sending the style properties for some contentlets.\nThis will save the style properties for the specified contentlets, in case style properties are not send the contentlet style properties will be null." + } }, "response": [] }, { - "name": "Turn ON Style Editor", + "name": "Save Content With Basic Styles", "event": [ { "listen": "test", "script": { "exec": [ + "const contentlet1 = pm.collectionVariables.get(\"Contentlet_1\");", + "", "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", "});", + "", + "// Parse JSON", + "const json = pm.response.json();", + "", + "pm.test(\"No errors returned\", function () {", + " pm.expect(json.errors).to.be.an(\"array\").that.is.empty;", + "});", + "", + "pm.test(\"Contentlet_1 has correct styleProperties\", function () {", + " const first = json.entity.find(item => item.contentletId === contentlet1);", + " pm.expect(first).to.exist;", + " pm.expect(first.dotStyleProperties).to.be.an(\"object\");", + " pm.expect(first.dotStyleProperties.width).to.eql(\"100px\");", + " pm.expect(first.dotStyleProperties.color).to.eql(\"#FF0000\");", + " pm.expect(first.dotStyleProperties.margin).to.eql(\"10px\");", + "});" + ], + "type": "text/javascript", + "packages": {}, + "requests": {} + } + }, + { + "listen": "prerequest", + "script": { + "exec": [ "" ], "type": "text/javascript", @@ -4673,7 +4673,7 @@ "header": [], "body": { "mode": "raw", - "raw": "{\n \"key\": \"FEATURE_FLAG_UVE_STYLE_EDITOR\",\n \"value\": \"true\"\n}", + "raw": "[\n {\n \"identifier\": \"SYSTEM_CONTAINER\",\n \"uuid\": \"1\",\n \"contentletsId\": [\n \"{{Contentlet_1}}\"\n ],\n \"styleProperties\": {\n \"{{Contentlet_1}}\": {\n \"width\": \"100px\",\n \"color\": \"#FF0000\",\n \"margin\": \"10px\"\n }\n }\n }\n]", "options": { "raw": { "language": "json" @@ -4681,17 +4681,19 @@ } }, "url": { - "raw": "{{serverURL}}/api/v1/system-table", + "raw": "{{serverURL}}/api/v1/page/{{pageIdentifier}}/content", "host": [ "{{serverURL}}" ], "path": [ "api", "v1", - "system-table", - "" + "page", + "{{pageIdentifier}}", + "content" ] - } + }, + "description": "Test when users make a request sending the style properties for some contentlets.\nThis will save the style properties for the specified contentlets, in case style properties are not send the contentlet style properties will be null." }, "response": [] }, @@ -4757,10 +4759,10 @@ "", "// Checking Style Values as a top level field", "pm.test(\"Top level Styles have correct width\", function () {", - " pm.expect(targetContentlet.styleProperties).to.not.be.null;", - " pm.expect(targetContentlet.styleProperties.width).to.eql('100px');", - " pm.expect(targetContentlet.styleProperties.color).to.eql('#FF0000');", - " pm.expect(targetContentlet.styleProperties.margin).to.eql('10px');", + " pm.expect(targetContentlet.dotStyleProperties).to.not.be.null;", + " pm.expect(targetContentlet.dotStyleProperties.width).to.eql('100px');", + " pm.expect(targetContentlet.dotStyleProperties.color).to.eql('#FF0000');", + " pm.expect(targetContentlet.dotStyleProperties.margin).to.eql('10px');", "});" ], "type": "text/javascript", @@ -4780,7 +4782,7 @@ "body": { "mode": "graphql", "graphql": { - "query": "query getPageData($url: String!) {\n page(url: $url) {\n containers {\n containerContentlets {\n uuid\n contentlets {\n identifier\n title\n styles: _map(key: \"styleProperties\")\n styleProperties\n }\n }\n }\n }\n}\n", + "query": "query getPageData($url: String!) {\n page(url: $url) {\n containers {\n containerContentlets {\n uuid\n contentlets {\n identifier\n title\n styles: _map(key: \"dotStyleProperties\")\n dotStyleProperties\n }\n }\n }\n }\n}\n", "variables": "{\n \"url\": \"{{pageUrl}}\"\n}" } }, @@ -6103,7 +6105,7 @@ "response": [] }, { - "name": "RequestVanityURLBaseType_ReturnsAllFields Copy", + "name": "RequestVanityURLBaseType_ReturnsAllFields", "event": [ { "listen": "test", @@ -9442,7 +9444,7 @@ "name": "Cats", "item": [ { - "name": "No value for cats field should return empty list Copy", + "name": "No value for cats field should return empty list", "item": [ { "name": "pre_ImportBundleWithContext",