Daniel Lautzenheiser 799c7e6936
feat: v4.0.0 (#1016)
Co-authored-by: Denys Konovalov <kontakt@denyskon.de>
Co-authored-by: Mathieu COSYNS <64072917+Mathieu-COSYNS@users.noreply.github.com>
2024-01-03 15:14:09 -05:00

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();
}
});
}
});
}