799c7e6936
Co-authored-by: Denys Konovalov <kontakt@denyskon.de> Co-authored-by: Mathieu COSYNS <64072917+Mathieu-COSYNS@users.noreply.github.com>
573 lines
18 KiB
TypeScript
573 lines
18 KiB
TypeScript
import format from 'date-fns/format';
|
|
import 'cypress-real-events';
|
|
|
|
import { editorStatus, notifications, publishTypes, workflowStatus } from './constants';
|
|
|
|
import { WorkflowStatus } from '@staticcms/core/constants/publishModes';
|
|
import type { Author, Post, User } from '../interface';
|
|
|
|
export interface LoginProps {
|
|
user?: User;
|
|
editorialWorkflow?: boolean;
|
|
}
|
|
|
|
export function login(options?: LoginProps) {
|
|
const { user, editorialWorkflow = false } = options ?? {};
|
|
|
|
cy.viewport(1200, 1200);
|
|
if (user) {
|
|
cy.visit('/', {
|
|
onBeforeLoad: () => {
|
|
// https://github.com/cypress-io/cypress/issues/1208
|
|
window.indexedDB.deleteDatabase('localforage');
|
|
window.localStorage.setItem('static-cms-user', JSON.stringify(user));
|
|
if (user.netlifySiteURL) {
|
|
window.localStorage.setItem('netlifySiteURL', user.netlifySiteURL);
|
|
}
|
|
},
|
|
});
|
|
if (user.netlifySiteURL && user.email && user.password) {
|
|
cy.get('input[name="email"]').clear().type(user.email);
|
|
cy.get('input[name="password"]').clear().type(user.password);
|
|
cy.contains('button', 'Login').click();
|
|
}
|
|
} else {
|
|
cy.visit('/');
|
|
cy.contains('button', 'Login').click();
|
|
}
|
|
|
|
if (editorialWorkflow) {
|
|
cy.contains('div', 'Editorial Workflow');
|
|
} else {
|
|
cy.contains('a', 'New Post');
|
|
}
|
|
}
|
|
|
|
export function assertNotification(message: string) {
|
|
cy.get('[data-testid="toast-messages"]', { timeout: 10000 })
|
|
.should('be.visible')
|
|
.within(() => {
|
|
cy.contains(message);
|
|
cy.contains(message).invoke('hide');
|
|
});
|
|
}
|
|
|
|
export function exitEditor() {
|
|
cy.get('[data-testid="breadcrumb-link"]').first().click();
|
|
}
|
|
|
|
export function goToWorkflow() {
|
|
cy.get('[data-testid="sidebar-nav-Dashboard"]').click();
|
|
}
|
|
|
|
export function goToMediaLibrary() {
|
|
cy.get('[data-testid="sidebar-nav-Media]').click();
|
|
}
|
|
|
|
export function assertUnpublishedEntryInEditor() {
|
|
cy.contains('button', 'Delete unpublished entry');
|
|
}
|
|
|
|
export function assertPublishedEntryInEditor() {
|
|
cy.contains('button', 'Delete published entry');
|
|
}
|
|
|
|
export function assertUnpublishedChangesInEditor() {
|
|
cy.contains('button', 'Delete unpublished changes');
|
|
}
|
|
|
|
export function goToEntry(entry: Post) {
|
|
cy.contains('a', entry.Title).click();
|
|
}
|
|
|
|
export function updateWorkflowStatus(
|
|
{ Title }: Post,
|
|
fromColumnHeading: WorkflowStatus,
|
|
toColumnHeading: WorkflowStatus,
|
|
) {
|
|
cy.get(`[data-testid="drop-zone-${fromColumnHeading}"]`).within(() => {
|
|
cy.get(`[data-testid="drag-handle-${Title}"]`).dragTo(
|
|
`[data-testid="drop-zone-${toColumnHeading}"]`,
|
|
);
|
|
});
|
|
|
|
assertNotification(notifications.updated);
|
|
}
|
|
|
|
export function publishWorkflowEntry({ Title }: Post, timeout = 3000) {
|
|
cy.get(`[data-testid="drop-zone-${WorkflowStatus.PENDING_PUBLISH}"]`).within(() => {
|
|
cy.get(`[data-testid="drag-handle-${Title}"]`, { timeout })
|
|
.realHover()
|
|
.within(() => {
|
|
cy.get('[data-testid="workflow-dashboard-publish"]').click();
|
|
});
|
|
});
|
|
|
|
cy.get('[data-testid="confirm-button"]').click();
|
|
|
|
assertNotification(notifications.published);
|
|
}
|
|
|
|
export function deleteWorkflowEntry({ Title }: Post) {
|
|
cy.contains('a', Title)
|
|
.parent()
|
|
.within(() => {
|
|
cy.contains('button', 'Delete new entry').click({ force: true });
|
|
});
|
|
|
|
assertNotification(notifications.deletedUnpublished);
|
|
}
|
|
|
|
const STATUS_BUTTON_TEXT = 'Status:';
|
|
|
|
export function assertWorkflowStatusInEditor(status: string) {
|
|
cy.contains('button', STATUS_BUTTON_TEXT).as('setStatusButton');
|
|
cy.get('@setStatusButton').click();
|
|
cy.contains('[role="menuitem"] div', status)
|
|
.parent()
|
|
.within(() => {
|
|
cy.get('svg');
|
|
});
|
|
cy.get('@setStatusButton').click();
|
|
}
|
|
|
|
export function assertPublishedEntry(entry: Post | Post[]) {
|
|
if (Array.isArray(entry)) {
|
|
const entries = entry.reverse();
|
|
cy.wrap(entry.slice(0, entries.length)).each((_el, idx) => {
|
|
cy.contains('a', entries[idx].Title);
|
|
});
|
|
} else {
|
|
cy.contains('a', entry.Title);
|
|
}
|
|
}
|
|
|
|
export function deleteEntryInEditor() {
|
|
cy.contains('button', 'Delete').click();
|
|
assertNotification(notifications.deletedUnpublished);
|
|
}
|
|
|
|
export function assertOnCollectionsPage() {
|
|
cy.url().should('contain', '/#/collections/posts');
|
|
}
|
|
|
|
export function assertEntryDeleted(entry: Post) {
|
|
cy.get('body').then($body => {
|
|
const entriesHeaders = $body.find('a');
|
|
if (entriesHeaders.length > 0) {
|
|
if (Array.isArray(entry)) {
|
|
const titles = entry.map(e => e.title);
|
|
cy.get('a').each(el => {
|
|
expect(titles).not.to.include(el.text());
|
|
});
|
|
} else {
|
|
cy.get('a').each(el => {
|
|
expect(entry.Title).not.to.equal(el.text());
|
|
});
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
export function assertWorkflowStatus({ Title }: Post, status: string) {
|
|
cy.contains('h2', status).parent().contains('a', Title);
|
|
}
|
|
|
|
export function updateWorkflowStatusInEditor(newStatus: string) {
|
|
selectDropdownItem(STATUS_BUTTON_TEXT, newStatus);
|
|
assertNotification(notifications.updated);
|
|
}
|
|
|
|
export function publishEntryInEditor(publishType: string) {
|
|
selectDropdownItem('Publish', publishType);
|
|
assertNotification(notifications.published);
|
|
}
|
|
|
|
export function publishAndCreateNewEntryInEditor() {
|
|
selectDropdownItem('Publish', publishTypes.publishAndCreateNew);
|
|
assertNotification(notifications.published);
|
|
cy.url().should('eq', `http://localhost:8080/#/collections/posts/new`);
|
|
cy.get('[data-testid="field-Title"]').should('have.value', '');
|
|
}
|
|
|
|
export function publishAndDuplicateEntryInEditor(entry: Post) {
|
|
selectDropdownItem('Publish', publishTypes.publishAndDuplicate);
|
|
assertNotification(notifications.published);
|
|
cy.url().should('eq', `http://localhost:8080/#/collections/posts/new`);
|
|
cy.get('[data-testid="field-Title"]').should('have.value', entry.Title);
|
|
}
|
|
|
|
function selectDropdownItem(label: string, item: string) {
|
|
cy.contains('button', label).click();
|
|
cy.contains('[role="menuitem"] div', item).click();
|
|
}
|
|
|
|
function flushClockAndSave() {
|
|
cy.wait(260);
|
|
|
|
cy.contains('button', 'Save').should('not.be.disabled').click();
|
|
|
|
assertNotification(notifications.saved);
|
|
}
|
|
|
|
export function populateEntry(entry: Post, onDone = flushClockAndSave) {
|
|
const keys = Object.keys(entry) as (keyof Post)[];
|
|
for (const key of keys) {
|
|
const value = entry[key];
|
|
if (key === 'Body') {
|
|
cy.getMarkdownEditor().click().clear({ force: true }).type(value, { force: true });
|
|
cy.getMarkdownEditor().first().click().clear({ force: true }).type(value, { force: true });
|
|
} else {
|
|
cy.get(`[data-testid="field-${key}"]`).click();
|
|
cy.focused().clear({ force: true });
|
|
cy.focused().type(value, { force: true });
|
|
}
|
|
}
|
|
|
|
onDone();
|
|
}
|
|
|
|
function newPost() {
|
|
cy.contains('a', 'New Post').click();
|
|
}
|
|
|
|
export function createPost(entry: Post) {
|
|
newPost();
|
|
populateEntry(entry);
|
|
}
|
|
|
|
export function createPostAndExit(entry: Post) {
|
|
createPost(entry);
|
|
exitEditor();
|
|
}
|
|
|
|
function publishEntry({ createNew = false, duplicate = false } = {}) {
|
|
cy.wait(500);
|
|
|
|
if (createNew) {
|
|
selectDropdownItem('Publish', publishTypes.publishAndCreateNew);
|
|
} else if (duplicate) {
|
|
selectDropdownItem('Publish', publishTypes.publishAndDuplicate);
|
|
} else {
|
|
selectDropdownItem('Publish', publishTypes.publishNow);
|
|
}
|
|
|
|
assertNotification(notifications.saved);
|
|
|
|
// eslint-disable-next-line cypress/no-unnecessary-waiting
|
|
cy.wait(500);
|
|
}
|
|
|
|
export function createPostAndPublish(entry: Post) {
|
|
newPost();
|
|
populateEntry(entry, publishEntry);
|
|
exitEditor();
|
|
}
|
|
|
|
export function createPostPublishAndCreateNew(entry: Post) {
|
|
newPost();
|
|
populateEntry(entry, () => publishEntry({ createNew: true }));
|
|
cy.url().should('eq', `http://localhost:8080/#/collections/posts/new`);
|
|
|
|
cy.get('[data-testid="field-Title"] input').should('have.value', '');
|
|
|
|
exitEditor();
|
|
}
|
|
|
|
export function createPostPublishAndDuplicate(entry: Post) {
|
|
newPost();
|
|
populateEntry(entry, () => publishEntry({ duplicate: true }));
|
|
cy.url().should('eq', `http://localhost:8080/#/collections/posts/new?duplicate=true`);
|
|
cy.get('[data-testid="field-Title"] input').should('have.value', entry.Title);
|
|
|
|
exitEditor();
|
|
}
|
|
|
|
export function editPostAndPublish(entry1: Post, entry2: Post) {
|
|
goToEntry(entry1);
|
|
cy.wait(1000);
|
|
cy.get('[data-testid="editor-extra-menu"]', { timeout: 5000 }).should('be.enabled');
|
|
cy.get('[data-testid="editor-extra-menu"]').click();
|
|
cy.get('[data-testid="delete-button"]');
|
|
cy.contains('[data-testid="publish-dropdown"]', 'Published');
|
|
|
|
populateEntry(entry2, publishEntry);
|
|
// existing entry slug should remain the same after save
|
|
cy.url().should(
|
|
'eq',
|
|
`http://localhost:8080/#/collections/posts/entries/${format(
|
|
new Date(),
|
|
'yyyy-MM-dd',
|
|
)}-${entry1.Title.toLowerCase().replace(/\s/, '-')}`,
|
|
);
|
|
}
|
|
|
|
export function editPostPublishAndCreateNew(entry1: Post, entry2: Post) {
|
|
goToEntry(entry1);
|
|
cy.wait(1000);
|
|
cy.get('[data-testid="editor-extra-menu"]', { timeout: 5000 }).should('be.enabled');
|
|
cy.get('[data-testid="editor-extra-menu"]').click();
|
|
cy.get('[data-testid="delete-button"]');
|
|
cy.contains('[data-testid="publish-dropdown"]', 'Published');
|
|
|
|
populateEntry(entry2, () => publishEntry({ createNew: true }));
|
|
cy.url().should('eq', `http://localhost:8080/#/collections/posts/new`);
|
|
cy.get('[data-testid="field-Title"] input').should('have.value', '');
|
|
}
|
|
|
|
export function editPostPublishAndDuplicate(entry1: Post, entry2: Post) {
|
|
goToEntry(entry1);
|
|
cy.wait(1000);
|
|
cy.get('[data-testid="editor-extra-menu"]', { timeout: 5000 }).should('be.enabled');
|
|
cy.get('[data-testid="editor-extra-menu"]').click();
|
|
cy.get('[data-testid="delete-button"]');
|
|
cy.contains('[data-testid="publish-dropdown"]', 'Published');
|
|
|
|
populateEntry(entry2, () => publishEntry({ duplicate: true }));
|
|
cy.url().should('eq', `http://localhost:8080/#/collections/posts/new?duplicate=true`);
|
|
cy.get('[data-testid="field-Title"] input').should('have.value', entry2.Title);
|
|
}
|
|
|
|
export function duplicatePostAndPublish(entry1: Post) {
|
|
goToEntry(entry1);
|
|
cy.wait(1000);
|
|
cy.get('[data-testid="editor-extra-menu"]', { timeout: 5000 }).should('be.enabled');
|
|
cy.get('[data-testid="editor-extra-menu"]').click();
|
|
cy.get('[data-testid="delete-button"]');
|
|
selectDropdownItem('Published', 'Duplicate');
|
|
publishEntry();
|
|
|
|
cy.url().should(
|
|
'eq',
|
|
`http://localhost:8080/#/collections/posts/entries/${format(
|
|
new Date(),
|
|
'yyyy-MM-dd',
|
|
)}-${entry1.Title.toLowerCase().replace(/\s/, '-')}-1`,
|
|
);
|
|
}
|
|
|
|
export function updateExistingPostAndExit(fromEntry: Post, toEntry: Post) {
|
|
goToWorkflow();
|
|
cy.contains('a', fromEntry.Title).click({ force: true });
|
|
populateEntry(toEntry);
|
|
exitEditor();
|
|
goToWorkflow();
|
|
cy.contains('a', toEntry.Title);
|
|
}
|
|
|
|
export function unpublishEntry(entry: Post) {
|
|
cy.contains('a', entry.Title).click({ force: true });
|
|
selectDropdownItem('Published', 'Unpublish');
|
|
assertNotification(notifications.unpublished);
|
|
goToWorkflow();
|
|
assertWorkflowStatus(entry, workflowStatus.ready);
|
|
}
|
|
|
|
export function duplicateEntry(entry: Post) {
|
|
selectDropdownItem('Published', 'Duplicate');
|
|
cy.url().should('contain', '/#/collections/posts/new?duplicate=true');
|
|
flushClockAndSave();
|
|
updateWorkflowStatusInEditor(editorStatus.ready);
|
|
publishEntryInEditor(publishTypes.publishNow);
|
|
exitEditor();
|
|
cy.get('a').should($h2s => {
|
|
expect($h2s.eq(0)).to.contain(entry.Title);
|
|
expect($h2s.eq(1)).to.contain(entry.Title);
|
|
});
|
|
}
|
|
|
|
export interface ValidateObjectFieldsProps {
|
|
limit: string;
|
|
author: string;
|
|
}
|
|
|
|
function validateObjectFields({ limit, author }: ValidateObjectFieldsProps) {
|
|
cy.contains('a', 'Settings').click();
|
|
cy.contains('a', 'Site Settings').click();
|
|
|
|
discardDraft();
|
|
|
|
cy.contains('label', 'Number of posts on frontpage').click();
|
|
cy.focused().type(limit);
|
|
flushClockAndSave();
|
|
assertNotification(notifications.error.missingField);
|
|
cy.get('[data-testid="field-Default Author"]').should('have.class', 'CMS_Field_error');
|
|
cy.contains('label', 'Default Author').click();
|
|
cy.focused().type(author);
|
|
flushClockAndSave();
|
|
assertNotification(notifications.saved);
|
|
cy.get('[data-testid="field-Default Author"]').should('not.have.class', 'CMS_Field_error');
|
|
}
|
|
|
|
function validateNestedObjectFields({ limit, author }: ValidateObjectFieldsProps) {
|
|
cy.contains('a', 'Settings').click();
|
|
cy.contains('a', 'Site Settings').click();
|
|
|
|
discardDraft();
|
|
|
|
cy.contains('label', 'Default Author').click();
|
|
cy.focused().type(author);
|
|
flushClockAndSave();
|
|
assertNotification(notifications.error.missingField);
|
|
cy.get('input[type=number]').type(limit + 1);
|
|
flushClockAndSave();
|
|
assertFieldValidationError(notifications.validation.range);
|
|
cy.get('input[type=number]').clear().type('-1');
|
|
flushClockAndSave();
|
|
assertFieldValidationError(notifications.validation.range);
|
|
cy.get('input[type=number]').clear().type(limit);
|
|
flushClockAndSave();
|
|
assertNotification(notifications.saved);
|
|
}
|
|
|
|
function validateListFields({ name, description }: Author) {
|
|
cy.contains('a', 'Settings').click();
|
|
cy.contains('a', 'Authors').click();
|
|
|
|
discardDraft();
|
|
|
|
cy.contains('button', 'Add').click();
|
|
flushClockAndSave();
|
|
assertNotification(notifications.error.missingField);
|
|
cy.get('[data-testid="list-field-Authors"]').should('have.class', 'CMS_WidgetList_error');
|
|
cy.get('[data-testid="list-item-field-Author"]').eq(2).as('listControl');
|
|
cy.get('@listControl').should('have.class', 'CMS_WidgetList_ListItem_error');
|
|
cy.get('@listControl').get('[data-testid="field-Name"]').should('have.class', 'CMS_Field_error');
|
|
cy.get('input').eq(2).type(name);
|
|
cy.get('textarea').eq(2).type(description);
|
|
flushClockAndSave();
|
|
assertNotification(notifications.saved);
|
|
cy.get('[data-testid="list-field-Authors"]').should('not.have.class', 'CMS_WidgetList_error');
|
|
}
|
|
|
|
function validateNestedListFields() {
|
|
cy.contains('a', 'Settings').click();
|
|
cy.contains('a', 'Hotel Locations').click();
|
|
|
|
discardDraft();
|
|
|
|
// add first city list item
|
|
cy.contains('button', 'Add Hotel Locations').click();
|
|
cy.contains('button', 'Add Cities').click();
|
|
cy.contains('label', 'City').next().type('Washington DC');
|
|
cy.contains('label', 'Number of Hotels in City').next().type('5');
|
|
cy.contains('button', 'Add City Locations').click();
|
|
cy.get('[data-testid="field-Hotel Name"]').should('exist');
|
|
|
|
// add second city list item
|
|
cy.contains('button', 'Add Cities').click();
|
|
|
|
cy.get('[data-testid="list-item-field-Cities"]')
|
|
.eq(0)
|
|
.within(() => {});
|
|
cy.get('[data-testid="list-item-field-Cities"]').eq(1).as('secondCitiesListControl');
|
|
|
|
cy.get('@secondCitiesListControl').contains('label', 'City').next().type('Boston');
|
|
cy.get('@secondCitiesListControl').contains('button', 'Add City Locations').click();
|
|
|
|
flushClockAndSave();
|
|
assertNotification(notifications.error.missingField);
|
|
|
|
// assert on fields
|
|
cy.get('[data-testid="list-field-Hotel Locations"]').should('have.class', 'CMS_WidgetList_error');
|
|
cy.get('[data-testid="list-item-field-Cities"]').should(
|
|
'have.class',
|
|
'CMS_WidgetList_ListItem_error',
|
|
);
|
|
|
|
cy.get('[data-testid="list-item-field-Cities"]')
|
|
.eq(0)
|
|
.within(() => {
|
|
cy.get('[data-testid="field-City"]').should('not.have.class', 'CMS_Field_error');
|
|
cy.get('[data-testid="field-Number of Hotels in City"]').should(
|
|
'not.have.class',
|
|
'CMS_Field_error',
|
|
);
|
|
cy.get('[data-testid="list-field-City Locations"]').should(
|
|
'have.class',
|
|
'CMS_WidgetList_error',
|
|
);
|
|
cy.get('[data-testid="field-Hotel Name"]').should('have.class', 'CMS_Field_error');
|
|
});
|
|
|
|
cy.get('[data-testid="list-item-field-Cities"]')
|
|
.eq(1)
|
|
.within(() => {
|
|
cy.get('[data-testid="field-City"]').should('not.have.class', 'CMS_Field_error');
|
|
cy.get('[data-testid="field-Number of Hotels in City"]').should(
|
|
'have.class',
|
|
'CMS_Field_error',
|
|
);
|
|
cy.get('[data-testid="list-field-City Locations"]').should(
|
|
'have.class',
|
|
'CMS_WidgetList_error',
|
|
);
|
|
cy.get('[data-testid="field-Hotel Name"]').should('have.class', 'CMS_Field_error');
|
|
});
|
|
|
|
// list control aliases
|
|
cy.contains('label', 'Hotel Name').next().type('The Ritz Carlton');
|
|
flushClockAndSave();
|
|
assertNotification(notifications.error.missingField);
|
|
|
|
// fill out rest of form and save
|
|
cy.get('@secondCitiesListControl').contains('label', 'Number of Hotels in City').type('3');
|
|
cy.get('@secondCitiesListControl').contains('label', 'Hotel Name').type('Grand Hyatt');
|
|
cy.contains('label', 'Country').next().type('United States');
|
|
flushClockAndSave();
|
|
assertNotification(notifications.saved);
|
|
}
|
|
|
|
export function validateObjectFieldsAndExit(setting: ValidateObjectFieldsProps) {
|
|
validateObjectFields(setting);
|
|
exitEditor();
|
|
}
|
|
|
|
export function validateNestedObjectFieldsAndExit(setting: ValidateObjectFieldsProps) {
|
|
validateNestedObjectFields(setting);
|
|
exitEditor();
|
|
}
|
|
|
|
export function validateListFieldsAndExit(setting: Author) {
|
|
validateListFields(setting);
|
|
exitEditor();
|
|
}
|
|
|
|
export function validateNestedListFieldsAndExit() {
|
|
validateNestedListFields();
|
|
exitEditor();
|
|
}
|
|
|
|
export interface AssertFieldValidationErrorProps {
|
|
message: string;
|
|
fieldLabel: string;
|
|
}
|
|
|
|
export function assertFieldValidationError({
|
|
message,
|
|
fieldLabel,
|
|
}: AssertFieldValidationErrorProps) {
|
|
cy.contains('label', fieldLabel).siblings('[data-testid="error"]').contains(message);
|
|
cy.get(`[data-testid="field-${fieldLabel}"]`).should('have.class', 'CMS_Field_error');
|
|
}
|
|
|
|
function discardDraft() {
|
|
cy.get('[data-testid="editor-extra-menu"]', { timeout: 5000 })
|
|
.should(_ => {})
|
|
.then($el => {
|
|
if ($el.length) {
|
|
cy.wait(1000);
|
|
cy.get('[data-testid="editor-extra-menu"]', { timeout: 5000 }).should('be.enabled');
|
|
cy.get('[data-testid="editor-extra-menu"]').click();
|
|
cy.get('[data-testid="discard-button"]')
|
|
.should(_ => {})
|
|
.then($el => {
|
|
if ($el.length) {
|
|
$el.trigger('click');
|
|
cy.get('[data-testid="confirm-button"]').click();
|
|
}
|
|
});
|
|
}
|
|
});
|
|
}
|