Skip to content

Commit

Permalink
Merge branch 'main' into 30820-custom-hook-content-analytics-sdk
Browse files Browse the repository at this point in the history
  • Loading branch information
oidacra authored Dec 18, 2024
2 parents d49793f + 9bd0ce7 commit ea2aa1c
Show file tree
Hide file tree
Showing 9 changed files with 175 additions and 31 deletions.
2 changes: 1 addition & 1 deletion core-web/libs/dotcms-scss/shared/_colors.scss
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ $success: $color-accessible-text-green;
--primary-800: var(--color-palette-primary-800);
--primary-900: var(--color-palette-primary-900);

--color-palette-primary-op-10: var(--color-palette-primary-op-10);
--color-palette-primary-op-10: hsla(var(--color-primary-h), var(--color-primary-s), 60%, 0.1);
--color-palette-primary-op-20: hsla(var(--color-primary-h), var(--color-primary-s), 60%, 0.2);
--color-palette-primary-op-30: hsla(var(--color-primary-h), var(--color-primary-s), 60%, 0.3);
--color-palette-primary-op-40: hsla(var(--color-primary-h), var(--color-primary-s), 60%, 0.4);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -524,6 +524,8 @@ public void setLive ( final Versionable versionable ) throws DotDataException, D
newInfo.setLiveInode(versionable.getInode());
newInfo.setPublishDate(new Date());
versionableFactory.saveContentletVersionInfo( newInfo, true );

uniqueFieldValidationStrategyResolver.get().afterPublish(versionable.getInode());
} else {

final VersionInfo info = versionableFactory.getVersionInfo( versionable.getVersionId() );
Expand All @@ -534,8 +536,6 @@ public void setLive ( final Versionable versionable ) throws DotDataException, D
info.setLiveInode( versionable.getInode() );
this.versionableFactory.saveVersionInfo( info, true );
}

uniqueFieldValidationStrategyResolver.get().afterPublish(versionable.getInode());
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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 = "<html><body> I'm mostly empty </body></html>";
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
Expand Down
5 changes: 5 additions & 0 deletions e2e/dotcms-e2e-node/frontend/locators/globalLocators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ export const contentGeneric = {
label: "Content (Generic)"
}

export const fileAsset = {
locator: "attach_fileFile Asset",
label: "File Asset"
}

export {
} from './navigation/menuLocators';

Original file line number Diff line number Diff line change
Expand Up @@ -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"
}

Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import {expect, test} from '@playwright/test';
import {expect, Page, test} from '@playwright/test';
import {dotCMSUtils, waitForVisibleAndCallback} from '../../utils/dotCMSUtils';
import {
GroupEntriesLocators,
MenuEntriesLocators,
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();
Expand Down Expand Up @@ -35,14 +35,18 @@ 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);

// 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);
Expand Down Expand Up @@ -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);
});
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
81 changes: 60 additions & 21 deletions e2e/dotcms-e2e-node/frontend/utils/contentUtils.ts
Original file line number Diff line number Diff line change
@@ -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";

Expand Down Expand Up @@ -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());
Expand All @@ -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();
Expand All @@ -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());
}

/**
Expand Down Expand Up @@ -151,6 +185,7 @@ export class ContentUtils {
return;
}
await this.fillRichTextForm(page, newTitle, newBody, action);
await this.workflowExecutionValidationAndClose(page, 'Content saved');
}

/**
Expand All @@ -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;
Expand All @@ -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<string | null> {
const iframe = page.frameLocator(iFramesLocators.main_iframe);
await iframe.locator('#results_table tbody tr').first().waitFor({ state: 'visible' });
Expand Down
19 changes: 16 additions & 3 deletions e2e/dotcms-e2e-node/frontend/utils/dotCMSUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand All @@ -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<void> => {
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<void>): Promise<void> => {
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<void>): Promise<void> => {
await waitForAndCallback(locator, 'visible', callback);
};

0 comments on commit ea2aa1c

Please sign in to comment.