From 19824fd1dde8400b828b8cac5e984b81f7456570 Mon Sep 17 00:00:00 2001 From: Luis Correia Date: Mon, 25 Mar 2019 18:17:26 +0000 Subject: [PATCH] chore: improve number widget tests (#2223) --- .../integration/editorial_workflow_spec.js | 378 +++++++++++------- cypress/utils/README.md | 9 + cypress/utils/dismiss-local-backup.js | 17 + dev-test/config.yml | 8 +- 4 files changed, 258 insertions(+), 154 deletions(-) create mode 100644 cypress/utils/README.md create mode 100644 cypress/utils/dismiss-local-backup.js diff --git a/cypress/integration/editorial_workflow_spec.js b/cypress/integration/editorial_workflow_spec.js index 6dcfd5eb..890cd0f5 100644 --- a/cypress/integration/editorial_workflow_spec.js +++ b/cypress/integration/editorial_workflow_spec.js @@ -1,117 +1,164 @@ +import '../utils/dismiss-local-backup'; + describe('Editorial Workflow', () => { - const workflowStatus = { draft: 'Drafts', review: 'In Review', ready: 'Ready' } - const editorStatus = { draft: 'Draft', review: 'In review', ready: 'Ready' } - const entry1 = { title: 'first title', body: 'first body' } - const entry2 = { title: 'second title', body: 'second body' } - const entry3 = { title: 'third title', body: 'third body' } - const setting1 = { limit: 10, author: 'John Doe' } - const setting2 = { name: 'Andrew Wommack', description: 'A Gospel Teacher' } + const workflowStatus = { draft: 'Drafts', review: 'In Review', ready: 'Ready' }; + const editorStatus = { draft: 'Draft', review: 'In review', ready: 'Ready' }; + const entry1 = { title: 'first title', body: 'first body' }; + const entry2 = { title: 'second title', body: 'second body' }; + const entry3 = { title: 'third title', body: 'third body' }; + const setting1 = { limit: 10, author: 'John Doe' }; + const setting2 = { name: 'Andrew Wommack', description: 'A Gospel Teacher' }; const notifications = { saved: 'Entry saved', published: 'Entry published', updated: 'Entry status updated', deletedUnpublished: 'Unpublished changes deleted', - error: { missingField: 'Oops, you\'ve missed a required field. Please complete before saving.' } - } + error: { + missingField: "Oops, you've missed a required field. Please complete before saving.", + }, + validation: { + range: { + fieldLabel: 'Number of posts on frontpage', + message: 'Number of posts on frontpage must be between 1 and 10.', + }, + }, + }; describe('Test Backend', () => { function login() { - cy.viewport(1200, 1200) - cy.visit('/') - cy.contains('button', 'Login').click() + cy.viewport(1200, 1200); + cy.visit('/'); + cy.contains('button', 'Login').click(); } function createPost({ title, body }) { - cy.contains('a', 'New Post').click() - cy.get('input').first().type(title) - cy.get('[data-slate-editor]').click().type(body) - cy.get('input').first().click() - cy.contains('button', 'Save').click() - assertNotification(notifications.saved) + cy.contains('a', 'New Post').click(); + cy.get('input') + .first() + .type(title); + cy.get('[data-slate-editor]') + .click() + .type(body); + cy.get('input') + .first() + .click(); + cy.contains('button', 'Save').click(); + assertNotification(notifications.saved); } function validateObjectFields({ limit, author }) { - cy.get('a[href^="#/collections/settings"]').click() - cy.get('a[href^="#/collections/settings/entries/general"]').click() + cy.get('a[href^="#/collections/settings"]').click(); + cy.get('a[href^="#/collections/settings/entries/general"]').click(); cy.get('input[type=number]').type(limit); - cy.contains('button', 'Save').click() - assertNotification(notifications.error.missingField) - cy.contains('label', 'Default Author').click() - cy.focused().type(author) - cy.contains('button', 'Save').click() - assertNotification(notifications.saved) + cy.contains('button', 'Save').click(); + assertNotification(notifications.error.missingField); + cy.contains('label', 'Default Author').click(); + cy.focused().type(author); + cy.contains('button', 'Save').click(); + assertNotification(notifications.saved); + } + + function validateNestedObjectFields({ limit, author }) { + cy.get('a[href^="#/collections/settings"]').click(); + cy.get('a[href^="#/collections/settings/entries/general"]').click(); + cy.contains('label', 'Default Author').click(); + cy.focused().type(author); + cy.contains('button', 'Save').click(); + assertNotification(notifications.error.missingField); + cy.get('input[type=number]').type(limit + 1); + cy.contains('button', 'Save').click(); + assertFieldValidationError(notifications.validation.range); + cy.get('input[type=number]') + .clear() + .type(-1); + cy.contains('button', 'Save').click(); + assertFieldValidationError(notifications.validation.range); + cy.get('input[type=number]') + .clear() + .type(limit); + cy.contains('button', 'Save').click(); + assertNotification(notifications.saved); } function validateListFields({ name, description }) { - cy.get('a[href^="#/collections/settings"]').click() - cy.get('a[href^="#/collections/settings/entries/authors"]').click() - cy.contains('button', 'Add').click() - cy.contains('button', 'Save').click() - assertNotification(notifications.error.missingField) - cy.get('input').eq(2).type(name) - cy.get('[data-slate-editor]').eq(2).type(description) - cy.contains('button', 'Save').click() + cy.get('a[href^="#/collections/settings"]').click(); + cy.get('a[href^="#/collections/settings/entries/authors"]').click(); + cy.contains('button', 'Add').click(); + cy.contains('button', 'Save').click(); + assertNotification(notifications.error.missingField); + cy.get('input') + .eq(2) + .type(name); + cy.get('[data-slate-editor]') + .eq(2) + .type(description); + cy.contains('button', 'Save').click(); } function exitEditor() { - cy.contains('a[href^="#/collections/"]', 'Writing in').click() + cy.contains('a[href^="#/collections/"]', 'Writing in').click(); } function deleteEntryInEditor() { - cy.contains('button', 'Delete').click() - assertNotification(notifications.deletedUnpublished) + cy.contains('button', 'Delete').click(); + assertNotification(notifications.deletedUnpublished); } function assertEntryDeleted(entry) { if (Array.isArray(entry)) { - const titles = entry.map(e => e.title) + const titles = entry.map(e => e.title); cy.get('a h2').each((el, idx) => { - expect(titles).not.to.include(el.text()) - }) + expect(titles).not.to.include(el.text()); + }); } else { cy.get('a h2').each((el, idx) => { - expect(entry.title).not.to.equal(el.text()) - }) + expect(entry.title).not.to.equal(el.text()); + }); } } function createAndDeletePost(entry) { - createPost(entry) - deleteEntryInEditor() + createPost(entry); + deleteEntryInEditor(); } function createPostAndExit(entry) { - createPost(entry) - exitEditor() + createPost(entry); + exitEditor(); } function createPublishedPost(entry) { - createPost(entry) - updateWorkflowStatusInEditor(editorStatus.ready) - publishInEditor() + createPost(entry); + updateWorkflowStatusInEditor(editorStatus.ready); + publishInEditor(); } function createPublishedPostAndExit(entry) { - createPublishedPost(entry) - exitEditor() + createPublishedPost(entry); + exitEditor(); } function validateObjectFieldsAndExit(setting) { - validateObjectFields(setting) - exitEditor() + validateObjectFields(setting); + exitEditor(); + } + + function validateNestedObjectFieldsAndExit(setting) { + validateNestedObjectFields(setting); + exitEditor(); } function validateListFieldsAndExit(setting) { - validateListFields(setting) - exitEditor() + validateListFields(setting); + exitEditor(); } function goToWorkflow() { - cy.contains('a', 'Workflow').click() + cy.contains('a', 'Workflow').click(); } function goToCollections() { - cy.contains('a', 'Content').click() + cy.contains('a', 'Content').click(); } function updateWorkflowStatus({ title }, fromColumnHeading, toColumnHeading) { @@ -121,157 +168,182 @@ describe('Editorial Workflow', () => { .trigger('dragstart', { dataTransfer: {}, force: true, - }) + }); cy.contains('h2', toColumnHeading) .parent() .trigger('drop', { dataTransfer: {}, force: true, - }) - assertNotification(notifications.updated) + }); + assertNotification(notifications.updated); } function assertWorkflowStatus({ title }, status) { cy.contains('h2', status) .parent() - .contains('a', title) + .contains('a', title); } function updateWorkflowStatusInEditor(newStatus) { - cy.contains('[role="button"]', 'Set status').as('setStatusButton') - cy.get('@setStatusButton').parent().within(() => { - cy.get('@setStatusButton').click() - cy.contains('[role="menuitem"] span', newStatus).click() - }) - assertNotification(notifications.updated) + cy.contains('[role="button"]', 'Set status').as('setStatusButton'); + cy.get('@setStatusButton') + .parent() + .within(() => { + cy.get('@setStatusButton').click(); + cy.contains('[role="menuitem"] span', newStatus).click(); + }); + assertNotification(notifications.updated); } function assertWorkflowStatusInEditor(status) { - cy.contains('[role="button"]', 'Set status').as('setStatusButton') - cy.get('@setStatusButton').parent().within(() => { - cy.get('@setStatusButton').click() - cy.contains('[role="menuitem"] span', status).parent().within(() => { - cy.get('svg') - }) - cy.get('@setStatusButton').click() - }) + cy.contains('[role="button"]', 'Set status').as('setStatusButton'); + cy.get('@setStatusButton') + .parent() + .within(() => { + cy.get('@setStatusButton').click(); + cy.contains('[role="menuitem"] span', status) + .parent() + .within(() => { + cy.get('svg'); + }); + cy.get('@setStatusButton').click(); + }); } function publishWorkflowEntry({ title }) { - cy.contains('h2', workflowStatus.ready).parent().within(() => { - cy.contains('a', title).parent().within(() => { - cy.contains('button', 'Publish new entry').click({ force: true }) - }) - }) - assertNotification(notifications.published) + cy.contains('h2', workflowStatus.ready) + .parent() + .within(() => { + cy.contains('a', title) + .parent() + .within(() => { + cy.contains('button', 'Publish new entry').click({ force: true }); + }); + }); + assertNotification(notifications.published); } function assertPublishedEntry(entry) { if (Array.isArray(entry)) { - const entries = entry.reverse() + const entries = entry.reverse(); cy.get('a h2').then(els => { cy.wrap(els.slice(0, entries.length)).each((el, idx) => { - cy.wrap(el).contains(entries[idx].title) - }) - }) + cy.wrap(el).contains(entries[idx].title); + }); + }); } else { - cy.get('a h2').first().contains(entry.title) + cy.get('a h2') + .first() + .contains(entry.title); } } function assertNotification(message) { if (Array.isArray(message)) { - const messages = message.reverse() + const messages = message.reverse(); cy.get('.notif__container div') - .should('have.length.of', messages.length, ) + .should('have.length.of', messages.length) .each((el, idx) => { - cy.wrap(el).contains(messages[idx]).invoke('hide') - }) + cy.wrap(el) + .contains(messages[idx]) + .invoke('hide'); + }); } else { cy.get('.notif__container').within(() => { - cy.contains(message).invoke('hide') - }) + cy.contains(message).invoke('hide'); + }); } } + function assertFieldValidationError({ message, fieldLabel }) { + cy.contains('label', fieldLabel) + .siblings('ul[class*=ControlErrorsList]') + .contains(message); + } + function assertOnCollectionsPage() { - cy.url().should('contain', '/#/collections/posts') - cy.contains('h2', 'Collections') + cy.url().should('contain', '/#/collections/posts'); + cy.contains('h2', 'Collections'); } it('successfully loads', () => { - login() - }) + login(); + }); it('can create an entry', () => { - login() - createPostAndExit(entry1) - }) + login(); + createPostAndExit(entry1); + }); it('can validate object fields', () => { - login() - validateObjectFieldsAndExit(setting1) - }) + login(); + validateObjectFieldsAndExit(setting1); + }); + + it('can validate fields nested in an object field', () => { + login(); + validateNestedObjectFieldsAndExit(setting1); + }); it('can validate list fields', () => { - login() - validateListFieldsAndExit(setting2) - }) + login(); + validateListFieldsAndExit(setting2); + }); it('can publish an editorial workflow entry', () => { - login() - createPostAndExit(entry1) - goToWorkflow() - updateWorkflowStatus(entry1, workflowStatus.draft, workflowStatus.ready) - publishWorkflowEntry(entry1) - }) + login(); + createPostAndExit(entry1); + goToWorkflow(); + updateWorkflowStatus(entry1, workflowStatus.draft, workflowStatus.ready); + publishWorkflowEntry(entry1); + }); it('can change workflow status', () => { - login() - createPostAndExit(entry1) - goToWorkflow() - updateWorkflowStatus(entry1, workflowStatus.draft, workflowStatus.review) - updateWorkflowStatus(entry1, workflowStatus.review, workflowStatus.ready) - updateWorkflowStatus(entry1, workflowStatus.ready, workflowStatus.review) - updateWorkflowStatus(entry1, workflowStatus.review, workflowStatus.draft) - updateWorkflowStatus(entry1, workflowStatus.draft, workflowStatus.ready) - }) + login(); + createPostAndExit(entry1); + goToWorkflow(); + updateWorkflowStatus(entry1, workflowStatus.draft, workflowStatus.review); + updateWorkflowStatus(entry1, workflowStatus.review, workflowStatus.ready); + updateWorkflowStatus(entry1, workflowStatus.ready, workflowStatus.review); + updateWorkflowStatus(entry1, workflowStatus.review, workflowStatus.draft); + updateWorkflowStatus(entry1, workflowStatus.draft, workflowStatus.ready); + }); it('can change status on and publish multiple entries', () => { - login() - createPostAndExit(entry1) - createPostAndExit(entry2) - createPostAndExit(entry3) - goToWorkflow() - updateWorkflowStatus(entry3, workflowStatus.draft, workflowStatus.ready) - updateWorkflowStatus(entry2, workflowStatus.draft, workflowStatus.ready) - updateWorkflowStatus(entry1, workflowStatus.draft, workflowStatus.ready) - publishWorkflowEntry(entry3) - publishWorkflowEntry(entry2) - publishWorkflowEntry(entry1) - goToCollections() - assertPublishedEntry([entry3, entry2, entry1]) - }) + login(); + createPostAndExit(entry1); + createPostAndExit(entry2); + createPostAndExit(entry3); + goToWorkflow(); + updateWorkflowStatus(entry3, workflowStatus.draft, workflowStatus.ready); + updateWorkflowStatus(entry2, workflowStatus.draft, workflowStatus.ready); + updateWorkflowStatus(entry1, workflowStatus.draft, workflowStatus.ready); + publishWorkflowEntry(entry3); + publishWorkflowEntry(entry2); + publishWorkflowEntry(entry1); + goToCollections(); + assertPublishedEntry([entry3, entry2, entry1]); + }); it('can delete an entry', () => { - login() - createPost(entry1) - deleteEntryInEditor() - assertOnCollectionsPage() - assertEntryDeleted(entry1) - }) + login(); + createPost(entry1); + deleteEntryInEditor(); + assertOnCollectionsPage(); + assertEntryDeleted(entry1); + }); it('can update workflow status from within the editor', () => { - login() - createPost(entry1) - assertWorkflowStatusInEditor(editorStatus.draft) - updateWorkflowStatusInEditor(editorStatus.review) - assertWorkflowStatusInEditor(editorStatus.review) - updateWorkflowStatusInEditor(editorStatus.ready) - assertWorkflowStatusInEditor(editorStatus.ready) - exitEditor() - goToWorkflow() - assertWorkflowStatus(entry1, workflowStatus.ready) - }) - }) -}) + login(); + createPost(entry1); + assertWorkflowStatusInEditor(editorStatus.draft); + updateWorkflowStatusInEditor(editorStatus.review); + assertWorkflowStatusInEditor(editorStatus.review); + updateWorkflowStatusInEditor(editorStatus.ready); + assertWorkflowStatusInEditor(editorStatus.ready); + exitEditor(); + goToWorkflow(); + assertWorkflowStatus(entry1, workflowStatus.ready); + }); + }); +}); diff --git a/cypress/utils/README.md b/cypress/utils/README.md new file mode 100644 index 00000000..c6b2b90e --- /dev/null +++ b/cypress/utils/README.md @@ -0,0 +1,9 @@ +## Utilities for integration tests + +Utils in this dir must be explicitly included in each spec file, e.g.: + +``` +import '../utils/dismiss-local-backup'; +``` + +For routines to be executed on all tests, please use the `cypress/plugins.index.js` file instead: https://docs.cypress.io/guides/core-concepts/writing-and-organizing-tests.html#Plugin-files diff --git a/cypress/utils/dismiss-local-backup.js b/cypress/utils/dismiss-local-backup.js new file mode 100644 index 00000000..8cdb5015 --- /dev/null +++ b/cypress/utils/dismiss-local-backup.js @@ -0,0 +1,17 @@ +import { getPhrases } from 'Constants/defaultPhrases'; + +// Prevents unsaved changes in dev local storage from being used +Cypress.on('window:confirm', message => { + const { + editor: { + editor: { confirmLoadBackup }, + }, + } = getPhrases(); + + switch (message) { + case confirmLoadBackup: + return false; + default: + return true; + } +}); diff --git a/dev-test/config.yml b/dev-test/config.yml index e20a7e02..172985ac 100644 --- a/dev-test/config.yml +++ b/dev-test/config.yml @@ -60,7 +60,13 @@ collections: # A list of collections the CMS should be able to edit name: posts widget: 'object' fields: - - { label: 'Number of posts on frontpage', name: front_limit, widget: number } + - { + label: 'Number of posts on frontpage', + name: front_limit, + widget: number, + min: 1, + max: 10, + } - { label: 'Default Author', name: author, widget: string } - { label: 'Default Thumbnail', name: thumb, widget: image, class: 'thumb', required: false }