>
@@ -336,7 +354,11 @@
|
<%=shortBundleId.toString()%>
|
- <%--BundleTitle--%>
+ <%--BundleFilter--%>
+
+ <%= filterName %>
+ |
+ <%--BundleContents--%>
<%try{ %>
<% if(bundleAssets.keySet().size()>0){ %>
diff --git a/dotcms-integration/src/test/java/com/dotmarketing/portlets/templates/business/TemplateAPITest.java b/dotcms-integration/src/test/java/com/dotmarketing/portlets/templates/business/TemplateAPITest.java
index 1c87d8c96076..09bdaf5689e0 100644
--- a/dotcms-integration/src/test/java/com/dotmarketing/portlets/templates/business/TemplateAPITest.java
+++ b/dotcms-integration/src/test/java/com/dotmarketing/portlets/templates/business/TemplateAPITest.java
@@ -12,6 +12,7 @@
import com.dotcms.IntegrationTestBase;
import com.dotcms.api.web.HttpServletRequestThreadLocal;
import com.dotcms.content.elasticsearch.ESQueryCache;
+import com.dotcms.content.elasticsearch.business.ESContentletAPIImpl;
import com.dotcms.contenttype.model.type.ContentType;
import com.dotcms.datagen.*;
import com.dotcms.rendering.velocity.viewtools.DotTemplateTool;
@@ -63,6 +64,7 @@
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
+import org.quartz.JobExecutionException;
import javax.servlet.http.HttpServletRequest;
@@ -317,6 +319,39 @@ public void publishTemplate_expects_live_true() throws Exception {
assertTrue(templateSaved.isLive());
}
+
+ /**
+ * Method to test: {@link TemplateAPIImpl#publishTemplate(Template, User, boolean)}
+ * When: Publish a Template with the UniqueField Database Validation set to true
+ * should: Template should be live true
+ */
+ @Test
+ public void publishTemplateWithUniqueFieldDatbaseValidationEnabled() throws Exception {
+ final boolean oldEnabledDataBaseValidation = ESContentletAPIImpl.getFeatureFlagDbUniqueFieldValidation();
+
+ try {
+ ESContentletAPIImpl.setFeatureFlagDbUniqueFieldValidation(true);
+ final Host host = hostAPI.findDefaultHost(user, false);
+ final String body = " I'm mostly empty ";
+ final String title = "empty test template " + UUIDGenerator.generateUuid();
+ final Template template = new Template();
+ template.setTitle(title);
+ template.setBody(body);
+ final Template templateSaved = templateAPI.saveTemplate(template, host, user, false);
+ assertTrue(UtilMethods.isSet(templateSaved.getInode()));
+ assertTrue(UtilMethods.isSet(templateSaved.getIdentifier()));
+ assertEquals(templateSaved.getBody(), body);
+ assertEquals(templateSaved.getTitle(), title);
+ assertFalse(templateSaved.isLive());
+
+ templateAPI.publishTemplate(templateSaved, user, false);
+ assertTrue(templateSaved.isLive());
+ } finally {
+
+ ESContentletAPIImpl.setFeatureFlagDbUniqueFieldValidation(oldEnabledDataBaseValidation);
+ }
+ }
+
/**
* Method to test: unpublishTemplate
* Given Scenario: Create a template, publish and unpublish it
diff --git a/e2e/dotcms-e2e-node/frontend/locators/globalLocators.ts b/e2e/dotcms-e2e-node/frontend/locators/globalLocators.ts
index e613d4d2e616..f1a99a2e1959 100644
--- a/e2e/dotcms-e2e-node/frontend/locators/globalLocators.ts
+++ b/e2e/dotcms-e2e-node/frontend/locators/globalLocators.ts
@@ -33,6 +33,11 @@ export const contentGeneric = {
label: "Content (Generic)"
}
+export const fileAsset = {
+ locator: "attach_fileFile Asset",
+ label: "File Asset"
+}
+
export {
} from './navigation/menuLocators';
diff --git a/e2e/dotcms-e2e-node/frontend/tests/contentSearch/contentData.ts b/e2e/dotcms-e2e-node/frontend/tests/contentSearch/contentData.ts
index e7a44c8b7db4..57aa0c7d21ef 100644
--- a/e2e/dotcms-e2e-node/frontend/tests/contentSearch/contentData.ts
+++ b/e2e/dotcms-e2e-node/frontend/tests/contentSearch/contentData.ts
@@ -20,4 +20,12 @@ export const contentProperties = {
deleteWfAction: "Delete"
}
+export const fileAssetContent = {
+ title: "File Asset title",
+ body: "This is a sample file asset content",
+ fromURL:"https://upload.wikimedia.org/wikipedia/commons/0/03/DotCMS-logo.svg",
+ newFileName:"New file asset",
+ newFileText:"This is a new file asset content",
+ host:"default"
+}
diff --git a/e2e/dotcms-e2e-node/frontend/tests/contentSearch/contentEditing.spec.ts b/e2e/dotcms-e2e-node/frontend/tests/contentSearch/contentEditing.spec.ts
index e98b53a8e79e..4d52ecd95d1f 100644
--- a/e2e/dotcms-e2e-node/frontend/tests/contentSearch/contentEditing.spec.ts
+++ b/e2e/dotcms-e2e-node/frontend/tests/contentSearch/contentEditing.spec.ts
@@ -1,4 +1,4 @@
-import {expect, test} from '@playwright/test';
+import {expect, Page, test} from '@playwright/test';
import {dotCMSUtils, waitForVisibleAndCallback} from '../../utils/dotCMSUtils';
import {
GroupEntriesLocators,
@@ -6,8 +6,8 @@ import {
ToolEntriesLocators
} from '../../locators/navigation/menuLocators';
import {ContentUtils} from "../../utils/contentUtils";
-import {iFramesLocators, contentGeneric} from "../../locators/globalLocators";
-import {genericContent1, contentProperties} from "./contentData";
+import {iFramesLocators, contentGeneric, fileAsset} from "../../locators/globalLocators";
+import {genericContent1, contentProperties, fileAssetContent} from "./contentData";
import {assert} from "console";
const cmsUtils = new dotCMSUtils();
@@ -35,7 +35,9 @@ test.beforeEach('Navigate to content portlet', async ({page}) => {
await waitForVisibleAndCallback(breadcrumbLocator, () => expect(breadcrumbLocator).toContainText('Search All'));
});
-
+/**
+ * test to add a new piece of content (generic content)
+ */
test('Add a new pice of content', async ({page}) => {
const contentUtils = new ContentUtils(page);
const iframe = page.frameLocator(iFramesLocators.main_iframe);
@@ -43,6 +45,8 @@ test('Add a new pice of content', async ({page}) => {
// Adding new rich text content
await contentUtils.addNewContentAction(page, contentGeneric.locator, contentGeneric.label);
await contentUtils.fillRichTextForm(page, genericContent1.title, genericContent1.body, contentProperties.publishWfAction);
+ await contentUtils.workflowExecutionValidationAndClose(page, 'Content saved');
+
await waitForVisibleAndCallback(iframe.locator('#results_table tbody tr').first(), async () => {});
await contentUtils.validateContentExist(page, genericContent1.title).then(assert);
@@ -73,5 +77,44 @@ test('Delete a piece of content', async ({ page }) => {
}
);
+/**
+ * Test to make sure we are validating the required of text fields on the content creation
+ * */
+test('Validate required on text fields', async ({page}) => {
+ const contentUtils = new ContentUtils(page);
+ const iframe = page.frameLocator(iFramesLocators.main_iframe).first();
+
+ await contentUtils.addNewContentAction(page, contentGeneric.locator, contentGeneric.label);
+ await contentUtils.fillRichTextForm(page, '', genericContent1.body, contentProperties.publishWfAction);
+ await expect(iframe.getByText('Error x')).toBeVisible();
+ await expect(iframe.getByText('The field Title is required.')).toBeVisible();
+});
+
+/** Please enable after fixing the issue #30748
+ * Test to make sure we are validating the required of blockEditor fields on the content creation
+ */
+/**
+test('Validate required on blockContent fields', async ({page}) => {
+ const contentUtils = new ContentUtils(page);
+ const iframe = page.frameLocator(iFramesLocators.main_iframe).first();
+
+ await contentUtils.addNewContentAction(page, contentGeneric.locator, contentGeneric.label);
+ await contentUtils.fillRichTextForm(page, genericContent1.title, '', contentProperties.publishWfAction);
+ await expect(iframe.getByText('Error x')).toBeVisible();
+ await expect(iframe.getByText('The field Title is required.')).toBeVisible();
+});
+*/
+/**
+ * Test to validate you are able to add file assets importing from url
+ */
+test('Validate you are able to add file assets importing from url', async ({page}) => {
+ const contentUtils = new ContentUtils(page);
+ const iframe = page.frameLocator(iFramesLocators.main_iframe);
+ await contentUtils.addNewContentAction(page, fileAsset.locator, fileAsset.label);
+ await contentUtils.fillFileAssetForm(page, fileAssetContent.host, fileAssetContent.title, contentProperties.publishWfAction, null, fileAssetContent.fromURL );
+ //fileName?: string, fromURL?: string, newFileName?: string, newFileText?: string) {
+ await contentUtils.workflowExecutionValidationAndClose(page, 'Content saved');
+ await contentUtils.validateContentExist(page, fileAssetContent.title).then(assert);
+});
\ No newline at end of file
diff --git a/e2e/dotcms-e2e-node/frontend/tests/contentSearch/portletIntegrity.spec.ts b/e2e/dotcms-e2e-node/frontend/tests/contentSearch/portletIntegrity.spec.ts
index 876ec9b7cf0c..5b9a55722856 100644
--- a/e2e/dotcms-e2e-node/frontend/tests/contentSearch/portletIntegrity.spec.ts
+++ b/e2e/dotcms-e2e-node/frontend/tests/contentSearch/portletIntegrity.spec.ts
@@ -55,6 +55,7 @@ test('Search filter', async ({page}) => {
// Adding new rich text content
await contentUtils.addNewContentAction(page, contentGeneric.locator, contentGeneric.label);
await contentUtils.fillRichTextForm(page, genericContent1.title, genericContent1.body, contentProperties.publishWfAction);
+ await contentUtils.workflowExecutionValidationAndClose(page, 'Content saved');
// Validate the content has been created
await expect.soft(iframe.getByRole('link', {name: genericContent1.title}).first()).toBeVisible();
diff --git a/e2e/dotcms-e2e-node/frontend/utils/contentUtils.ts b/e2e/dotcms-e2e-node/frontend/utils/contentUtils.ts
index 267f9e4ad408..a12eff7db9b6 100644
--- a/e2e/dotcms-e2e-node/frontend/utils/contentUtils.ts
+++ b/e2e/dotcms-e2e-node/frontend/utils/contentUtils.ts
@@ -1,5 +1,5 @@
import {expect, FrameLocator, Locator, Page} from '@playwright/test';
-import {contentGeneric, iFramesLocators} from '../locators/globalLocators';
+import {contentGeneric, iFramesLocators, fileAsset } from '../locators/globalLocators';
import {waitForVisibleAndCallback} from './dotCMSUtils';
import {contentProperties} from "../tests/contentSearch/contentData";
@@ -27,14 +27,53 @@ export class ContentUtils {
await dotIframe.locator('#title').fill(title);
//Fill body
await dotIframe.locator('#block-editor-body div').nth(1).fill(body);
-
- //await dotIframe.locator(iFramesLocators.wysiwygFrame).contentFrame().locator('#tinymce').fill(body);
//Click on action
await dotIframe.getByText(action).first().click();
- //Wait for the content to be saved
+ }
+
+ /**
+ * Fill the file asset form
+ * @param page
+ * @param host
+ * @param title
+ * @param action
+ * @param fileName
+ * @param fromURL
+ * @param newFileName
+ * @param newFileText
+ */
+ async fillFileAssetForm(page: Page, host: string, title: string, action:string, fileName?: string, fromURL?: string, newFileName?: string, newFileText?: string) {
+ const dotIframe = page.frameLocator(iFramesLocators.dot_iframe);
+
+ const headingLocator = page.getByRole('heading');
+ await waitForVisibleAndCallback(headingLocator, () => expect.soft(headingLocator).toContainText( fileAsset.label ));
+
+ await dotIframe.locator('#HostSelector-hostFolderSelect').fill(host);
+ if (newFileName && newFileText) {
+ await dotIframe.getByTestId('editor-file-name').fill(newFileName);
+ await dotIframe.getByLabel('Editor content;Press Alt+F1').fill(newFileText);
+ await dotIframe.getByRole('button', { name: 'Save' }).click();
+ } else {
+ if (fromURL) {
+ await dotIframe.getByRole('button', {name: ' Import from URL'}).click();
+ await dotIframe.getByTestId('url-input').fill(fromURL);
+ await dotIframe.getByRole('button', { name: ' Import' }).click();
+ }
+ }
+ const titleField = dotIframe.locator('#title');
+ await waitForVisibleAndCallback(headingLocator, () => titleField.fill(title));
+ await dotIframe.getByText(action).first().click();
+ }
- await expect(dotIframe.getByText('Content saved')).toBeVisible({timeout: 9000});
- await expect(dotIframe.getByText('Content saved')).toBeHidden();
+ /**
+ * Validate the workflow execution and close the modal
+ * @param page
+ */
+ async workflowExecutionValidationAndClose(page: Page, message: string) {
+ const dotIframe = page.frameLocator(iFramesLocators.dot_iframe);
+
+ await expect(dotIframe.getByText(message)).toBeVisible({timeout: 9000});
+ await expect(dotIframe.getByText(message)).toBeHidden();
//Click on close
const closeBtnLocator = page.getByTestId('close-button').getByRole('button');
await waitForVisibleAndCallback(closeBtnLocator, () => closeBtnLocator.click());
@@ -48,16 +87,10 @@ export class ContentUtils {
*/
async addNewContentAction(page: Page, typeLocator: string, typeString: string) {
const iframe = page.frameLocator(iFramesLocators.main_iframe);
+
const structureINodeLocator = iframe.locator('#structure_inode');
await waitForVisibleAndCallback(structureINodeLocator, () => expect(structureINodeLocator).toBeVisible());
- //TODO remove this
- await page.waitForTimeout(1000);
- const structureINodeDivLocator = iframe.locator('#widget_structure_inode div').first();
- await waitForVisibleAndCallback(structureINodeDivLocator, () => structureINodeDivLocator.click());
- //TODO remove this
- await page.waitForTimeout(1000);
- const typeLocatorByTextLocator = iframe.getByText(typeLocator);
- await waitForVisibleAndCallback(typeLocatorByTextLocator, () => typeLocatorByTextLocator.click());
+ await this.selectTypeOnFilter(page, typeLocator);
await iframe.locator('#dijit_form_DropDownButton_0').click();
await expect(iframe.getByLabel('actionPrimaryMenu')).toBeVisible();
@@ -72,12 +105,13 @@ export class ContentUtils {
* @param typeLocator
* @param typeString
*/
- async selectTypeOnFilter(page: Page, typeLocator: string, typeString: string) {
+ async selectTypeOnFilter(page: Page, typeLocator: string) {
const iframe = page.frameLocator(iFramesLocators.main_iframe);
- await expect.soft(iframe.locator('#structure_inode')).toBeVisible();
- await iframe.locator('#widget_structure_inode div').first().click();
- await iframe.getByText(typeLocator).click();
+ const structureINodeDivLocator = iframe.locator('#widget_structure_inode div').first();
+ await waitForVisibleAndCallback(structureINodeDivLocator, () => structureINodeDivLocator.click());
+ const typeLocatorByTextLocator = iframe.getByText(typeLocator);
+ await waitForVisibleAndCallback(typeLocatorByTextLocator, () => typeLocatorByTextLocator.click());
}
/**
@@ -151,6 +185,7 @@ export class ContentUtils {
return;
}
await this.fillRichTextForm(page, newTitle, newBody, action);
+ await this.workflowExecutionValidationAndClose(page, 'Content saved');
}
/**
@@ -172,7 +207,6 @@ export class ContentUtils {
await iframe.locator('#widget_showingSelect div').first().click();
const dropDownMenu = iframe.getByRole('option', { name: 'Archived' });
await waitForVisibleAndCallback(dropDownMenu, () => dropDownMenu.click());
- await page.waitForTimeout(1000)
} else if (contentState === 'archived') {
await this.performWorkflowAction(page, title, contentProperties.deleteWfAction);
return;
@@ -198,10 +232,15 @@ export class ContentUtils {
}
const actionBtnLocator = iframe.getByRole('menuitem', { name: action });
await waitForVisibleAndCallback(actionBtnLocator, () => actionBtnLocator.getByText(action).click());
- await expect.soft(iframe.getByText('Workflow executed')).toBeVisible();
- await expect.soft(iframe.getByText('Workflow executed')).toBeHidden();
+ const executionConfirmation = iframe.getByText('Workflow executed');
+ await waitForVisibleAndCallback(executionConfirmation, () => expect(executionConfirmation).toBeVisible());
+ await waitForVisibleAndCallback(executionConfirmation, () => expect(executionConfirmation).toBeHidden());
}
+ /**
+ * Get the content state from the results table on the content portle
+ * @param page
+ */
async getContentState(page: Page, title: string): Promise {
const iframe = page.frameLocator(iFramesLocators.main_iframe);
await iframe.locator('#results_table tbody tr').first().waitFor({ state: 'visible' });
diff --git a/e2e/dotcms-e2e-node/frontend/utils/dotCMSUtils.ts b/e2e/dotcms-e2e-node/frontend/utils/dotCMSUtils.ts
index d84c1bdb69dc..cc47cc1aa72a 100644
--- a/e2e/dotcms-e2e-node/frontend/utils/dotCMSUtils.ts
+++ b/e2e/dotcms-e2e-node/frontend/utils/dotCMSUtils.ts
@@ -7,8 +7,8 @@ export class dotCMSUtils {
/**
* Login to dotCMS
* @param page
- * @param username
- * @param password
+ * @param username
+ * @param password
*/
async login(page: Page, username: string, password: string) {
await page.goto('/dotAdmin');
@@ -34,16 +34,29 @@ export class dotCMSUtils {
}
};
-
+/**
+ * Wait for the locator to be in the provided state
+ * @param locator
+ * @param state
+ */
export const waitFor = async (locator: Locator, state: "attached" | "detached" | "visible" | "hidden"): Promise => {
await locator.waitFor({state: state});
}
+/**
+ * Wait for the locator to be visible
+ * @param locator
+ */
export const waitForAndCallback = async (locator: Locator, state: "attached" | "detached" | "visible" | "hidden", callback: () => Promise): Promise => {
await waitFor(locator, state);
await callback();
};
+/**
+ * Wait for the locator to be visible and execute the callback
+ * @param locator
+ * @param callback
+ */
export const waitForVisibleAndCallback = async (locator: Locator, callback: () => Promise): Promise => {
await waitForAndCallback(locator, 'visible', callback);
};
\ No newline at end of file
|