feat(backend-github): GitHub GraphQL API support (#2456)
* add GitHub GraphQL api initial support * support mutiple backends for e2e tests - initial commit * add github backend e2e test (currently skipped), fix bugs per tests * refactor e2e tests, add fork workflow tests, support fork workflow in GraphQL api * remove log message that might contain authentication token * return empty error when commit is not found when using GraphQL (align with base class) * disable github backend tests * fix bugs introduced after rebase of GraphQL and OpenAuthoring features * test: update tests per openAuthoring changes, split tests into multiple files * fix: pass in headers for pagination requests, avoid async iterator as it requires a polyfill on old browsers * test(e2e): disable github backend tests
This commit is contained in:
committed by
Shawn Erquhart
parent
083a336ba4
commit
ece136c92e
@ -1,349 +0,0 @@
|
||||
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 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.",
|
||||
},
|
||||
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();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
function validateObjectFields({ limit, author }) {
|
||||
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);
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
function exitEditor() {
|
||||
cy.contains('a[href^="#/collections/"]', 'Writing in').click();
|
||||
}
|
||||
|
||||
function deleteEntryInEditor() {
|
||||
cy.contains('button', 'Delete').click();
|
||||
assertNotification(notifications.deletedUnpublished);
|
||||
}
|
||||
|
||||
function assertEntryDeleted(entry) {
|
||||
if (Array.isArray(entry)) {
|
||||
const titles = entry.map(e => e.title);
|
||||
cy.get('a h2').each((el, idx) => {
|
||||
expect(titles).not.to.include(el.text());
|
||||
});
|
||||
} else {
|
||||
cy.get('a h2').each((el, idx) => {
|
||||
expect(entry.title).not.to.equal(el.text());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function createAndDeletePost(entry) {
|
||||
createPost(entry);
|
||||
deleteEntryInEditor();
|
||||
}
|
||||
|
||||
function createPostAndExit(entry) {
|
||||
createPost(entry);
|
||||
exitEditor();
|
||||
}
|
||||
|
||||
function createPublishedPost(entry) {
|
||||
createPost(entry);
|
||||
updateWorkflowStatusInEditor(editorStatus.ready);
|
||||
publishInEditor();
|
||||
}
|
||||
|
||||
function createPublishedPostAndExit(entry) {
|
||||
createPublishedPost(entry);
|
||||
exitEditor();
|
||||
}
|
||||
|
||||
function validateObjectFieldsAndExit(setting) {
|
||||
validateObjectFields(setting);
|
||||
exitEditor();
|
||||
}
|
||||
|
||||
function validateNestedObjectFieldsAndExit(setting) {
|
||||
validateNestedObjectFields(setting);
|
||||
exitEditor();
|
||||
}
|
||||
|
||||
function validateListFieldsAndExit(setting) {
|
||||
validateListFields(setting);
|
||||
exitEditor();
|
||||
}
|
||||
|
||||
function goToWorkflow() {
|
||||
cy.contains('a', 'Workflow').click();
|
||||
}
|
||||
|
||||
function goToCollections() {
|
||||
cy.contains('a', 'Content').click();
|
||||
}
|
||||
|
||||
function updateWorkflowStatus({ title }, fromColumnHeading, toColumnHeading) {
|
||||
cy.contains('h2', fromColumnHeading)
|
||||
.parent()
|
||||
.contains('a', title)
|
||||
.trigger('dragstart', {
|
||||
dataTransfer: {},
|
||||
force: true,
|
||||
});
|
||||
cy.contains('h2', toColumnHeading)
|
||||
.parent()
|
||||
.trigger('drop', {
|
||||
dataTransfer: {},
|
||||
force: true,
|
||||
});
|
||||
assertNotification(notifications.updated);
|
||||
}
|
||||
|
||||
function assertWorkflowStatus({ title }, status) {
|
||||
cy.contains('h2', status)
|
||||
.parent()
|
||||
.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);
|
||||
}
|
||||
|
||||
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();
|
||||
});
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
function assertPublishedEntry(entry) {
|
||||
if (Array.isArray(entry)) {
|
||||
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);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
cy.get('a h2')
|
||||
.first()
|
||||
.contains(entry.title);
|
||||
}
|
||||
}
|
||||
|
||||
function assertNotification(message) {
|
||||
if (Array.isArray(message)) {
|
||||
const messages = message.reverse();
|
||||
cy.get('.notif__container div')
|
||||
.should('have.length.of', messages.length)
|
||||
.each((el, idx) => {
|
||||
cy.wrap(el)
|
||||
.contains(messages[idx])
|
||||
.invoke('hide');
|
||||
});
|
||||
} else {
|
||||
cy.get('.notif__container').within(() => {
|
||||
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');
|
||||
}
|
||||
|
||||
it('successfully loads', () => {
|
||||
login();
|
||||
});
|
||||
|
||||
it('can create an entry', () => {
|
||||
login();
|
||||
createPostAndExit(entry1);
|
||||
});
|
||||
|
||||
it('can validate object fields', () => {
|
||||
login();
|
||||
validateObjectFieldsAndExit(setting1);
|
||||
});
|
||||
|
||||
it('can validate fields nested in an object field', () => {
|
||||
login();
|
||||
validateNestedObjectFieldsAndExit(setting1);
|
||||
});
|
||||
|
||||
it('can validate list fields', () => {
|
||||
login();
|
||||
validateListFieldsAndExit(setting2);
|
||||
});
|
||||
|
||||
it('can publish an editorial workflow entry', () => {
|
||||
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);
|
||||
});
|
||||
|
||||
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]);
|
||||
});
|
||||
|
||||
it('can delete an entry', () => {
|
||||
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);
|
||||
});
|
||||
});
|
||||
});
|
@ -0,0 +1,5 @@
|
||||
import fixture from './github/editorial_workflow';
|
||||
|
||||
describe.skip('Github Backend Editorial Workflow - GraphQL API', () => {
|
||||
fixture({ use_graphql: true });
|
||||
});
|
@ -0,0 +1,5 @@
|
||||
import fixture from './github/open_authoring';
|
||||
|
||||
describe.skip('Github Backend Editorial Workflow - GraphQL API - Open Authoring', () => {
|
||||
fixture({ use_graphql: true });
|
||||
});
|
@ -0,0 +1,5 @@
|
||||
import fixture from './github/editorial_workflow';
|
||||
|
||||
describe.skip('Github Backend Editorial Workflow - REST API', () => {
|
||||
fixture({ use_graphql: false });
|
||||
});
|
@ -0,0 +1,5 @@
|
||||
import fixture from './github/open_authoring';
|
||||
|
||||
describe.skip('Github Backend Editorial Workflow - REST API - Open Authoring', () => {
|
||||
fixture({ use_graphql: false });
|
||||
});
|
126
cypress/integration/editorial_workflow_spec_test_backend.js
Normal file
126
cypress/integration/editorial_workflow_spec_test_backend.js
Normal file
@ -0,0 +1,126 @@
|
||||
import '../utils/dismiss-local-backup';
|
||||
import {
|
||||
login,
|
||||
createPost,
|
||||
createPostAndExit,
|
||||
exitEditor,
|
||||
goToWorkflow,
|
||||
goToCollections,
|
||||
updateWorkflowStatus,
|
||||
publishWorkflowEntry,
|
||||
assertWorkflowStatusInEditor,
|
||||
assertPublishedEntry,
|
||||
deleteEntryInEditor,
|
||||
assertOnCollectionsPage,
|
||||
assertEntryDeleted,
|
||||
assertWorkflowStatus,
|
||||
updateWorkflowStatusInEditor,
|
||||
validateObjectFieldsAndExit,
|
||||
validateNestedObjectFieldsAndExit,
|
||||
validateListFieldsAndExit,
|
||||
} from '../utils/steps';
|
||||
import { setting1, setting2, workflowStatus, editorStatus } from '../utils/constants';
|
||||
|
||||
const entry1 = {
|
||||
title: 'first title',
|
||||
body: 'first body',
|
||||
};
|
||||
const entry2 = {
|
||||
title: 'second title',
|
||||
body: 'second body',
|
||||
};
|
||||
const entry3 = {
|
||||
title: 'third title',
|
||||
body: 'third body',
|
||||
};
|
||||
|
||||
describe('Test Backend Editorial Workflow', () => {
|
||||
after(() => {
|
||||
cy.task('restoreDefaults');
|
||||
});
|
||||
|
||||
before(() => {
|
||||
Cypress.config('defaultCommandTimeout', 4000);
|
||||
cy.task('setupBackend', { backend: 'test' });
|
||||
});
|
||||
|
||||
it('successfully loads', () => {
|
||||
login();
|
||||
});
|
||||
|
||||
it('can create an entry', () => {
|
||||
login();
|
||||
createPostAndExit(entry1);
|
||||
});
|
||||
|
||||
it('can validate object fields', () => {
|
||||
login();
|
||||
validateObjectFieldsAndExit(setting1);
|
||||
});
|
||||
|
||||
it('can validate fields nested in an object field', () => {
|
||||
login();
|
||||
validateNestedObjectFieldsAndExit(setting1);
|
||||
});
|
||||
|
||||
it('can validate list fields', () => {
|
||||
login();
|
||||
validateListFieldsAndExit(setting2);
|
||||
});
|
||||
|
||||
it('can publish an editorial workflow entry', () => {
|
||||
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);
|
||||
});
|
||||
|
||||
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]);
|
||||
});
|
||||
|
||||
it('can delete an entry', () => {
|
||||
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);
|
||||
});
|
||||
});
|
117
cypress/integration/github/editorial_workflow.js
Normal file
117
cypress/integration/github/editorial_workflow.js
Normal file
@ -0,0 +1,117 @@
|
||||
import '../../utils/dismiss-local-backup';
|
||||
import {
|
||||
login,
|
||||
createPost,
|
||||
createPostAndExit,
|
||||
updateExistingPostAndExit,
|
||||
exitEditor,
|
||||
goToWorkflow,
|
||||
goToCollections,
|
||||
updateWorkflowStatus,
|
||||
publishWorkflowEntry,
|
||||
assertWorkflowStatusInEditor,
|
||||
assertPublishedEntry,
|
||||
deleteEntryInEditor,
|
||||
assertOnCollectionsPage,
|
||||
assertEntryDeleted,
|
||||
assertWorkflowStatus,
|
||||
updateWorkflowStatusInEditor,
|
||||
} from '../../utils/steps';
|
||||
import { workflowStatus, editorStatus } from '../../utils/constants';
|
||||
import { entry1, entry2, entry3 } from './entries';
|
||||
|
||||
export default function({ use_graphql }) {
|
||||
let taskResult = { data: {} };
|
||||
|
||||
const backend = 'github';
|
||||
|
||||
before(() => {
|
||||
Cypress.config('taskTimeout', 1200000);
|
||||
Cypress.config('defaultCommandTimeout', 60000);
|
||||
cy.task('setupBackend', { backend }).then(data => {
|
||||
taskResult.data = data;
|
||||
});
|
||||
|
||||
cy.task('updateBackendOptions', { backend, options: { use_graphql, open_authoring: false } });
|
||||
});
|
||||
|
||||
after(() => {
|
||||
cy.task('teardownBackend', { backend, ...taskResult.data });
|
||||
cy.task('restoreDefaults');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
cy.task('teardownBackendTest', { backend, ...taskResult.data });
|
||||
});
|
||||
|
||||
it('successfully loads', () => {
|
||||
login(taskResult.data.user);
|
||||
});
|
||||
|
||||
it('can create an entry', () => {
|
||||
login(taskResult.data.user);
|
||||
createPostAndExit(entry1);
|
||||
});
|
||||
|
||||
it('can update an entry', () => {
|
||||
login(taskResult.data.user);
|
||||
createPostAndExit(entry1);
|
||||
updateExistingPostAndExit(entry1, entry2);
|
||||
});
|
||||
|
||||
it('can publish an editorial workflow entry', () => {
|
||||
login(taskResult.data.user);
|
||||
createPostAndExit(entry1);
|
||||
goToWorkflow();
|
||||
updateWorkflowStatus(entry1, workflowStatus.draft, workflowStatus.ready);
|
||||
publishWorkflowEntry(entry1);
|
||||
});
|
||||
|
||||
it('can change workflow status', () => {
|
||||
login(taskResult.data.user);
|
||||
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(taskResult.data.user);
|
||||
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(taskResult.data.user);
|
||||
createPost(entry1);
|
||||
deleteEntryInEditor();
|
||||
assertOnCollectionsPage();
|
||||
assertEntryDeleted(entry1);
|
||||
});
|
||||
|
||||
it('can update workflow status from within the editor', () => {
|
||||
login(taskResult.data.user);
|
||||
createPost(entry1);
|
||||
assertWorkflowStatusInEditor(editorStatus.draft);
|
||||
updateWorkflowStatusInEditor(editorStatus.review);
|
||||
assertWorkflowStatusInEditor(editorStatus.review);
|
||||
updateWorkflowStatusInEditor(editorStatus.ready);
|
||||
assertWorkflowStatusInEditor(editorStatus.ready);
|
||||
exitEditor();
|
||||
goToWorkflow();
|
||||
assertWorkflowStatus(entry1, workflowStatus.ready);
|
||||
});
|
||||
}
|
21
cypress/integration/github/entries.js
Normal file
21
cypress/integration/github/entries.js
Normal file
@ -0,0 +1,21 @@
|
||||
export const entry1 = {
|
||||
title: 'first title',
|
||||
body: 'first body',
|
||||
description: 'first description',
|
||||
category: 'first category',
|
||||
tags: 'tag1',
|
||||
};
|
||||
export const entry2 = {
|
||||
title: 'second title',
|
||||
body: 'second body',
|
||||
description: 'second description',
|
||||
category: 'second category',
|
||||
tags: 'tag2',
|
||||
};
|
||||
export const entry3 = {
|
||||
title: 'third title',
|
||||
body: 'third body',
|
||||
description: 'third description',
|
||||
category: 'third category',
|
||||
tags: 'tag3',
|
||||
};
|
99
cypress/integration/github/open_authoring.js
Normal file
99
cypress/integration/github/open_authoring.js
Normal file
@ -0,0 +1,99 @@
|
||||
import '../../utils/dismiss-local-backup';
|
||||
import {
|
||||
login,
|
||||
createPostAndExit,
|
||||
updateExistingPostAndExit,
|
||||
goToWorkflow,
|
||||
deleteWorkflowEntry,
|
||||
updateWorkflowStatus,
|
||||
publishWorkflowEntry,
|
||||
} from '../../utils/steps';
|
||||
import { workflowStatus } from '../../utils/constants';
|
||||
import { entry1, entry2 } from './entries';
|
||||
|
||||
export default function({ use_graphql }) {
|
||||
let taskResult = { data: {} };
|
||||
|
||||
const backend = 'github';
|
||||
|
||||
before(() => {
|
||||
Cypress.config('taskTimeout', 1200000);
|
||||
Cypress.config('defaultCommandTimeout', 60000);
|
||||
cy.task('setupBackend', { backend }).then(data => {
|
||||
taskResult.data = data;
|
||||
});
|
||||
cy.task('updateBackendOptions', { backend, options: { use_graphql, open_authoring: true } });
|
||||
});
|
||||
|
||||
after(() => {
|
||||
cy.task('teardownBackend', { backend, ...taskResult.data });
|
||||
cy.task('restoreDefaults');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
cy.task('teardownBackendTest', { backend, ...taskResult.data });
|
||||
});
|
||||
|
||||
it('successfully loads', () => {
|
||||
login(taskResult.data.user);
|
||||
});
|
||||
|
||||
it('can create an entry', () => {
|
||||
login(taskResult.data.user);
|
||||
createPostAndExit(entry1);
|
||||
});
|
||||
|
||||
it('can update an entry', () => {
|
||||
login(taskResult.data.user);
|
||||
createPostAndExit(entry1);
|
||||
updateExistingPostAndExit(entry1, entry2);
|
||||
});
|
||||
|
||||
it('can publish an editorial workflow entry', () => {
|
||||
login(taskResult.data.user);
|
||||
createPostAndExit(entry1);
|
||||
goToWorkflow();
|
||||
updateWorkflowStatus(entry1, workflowStatus.draft, workflowStatus.ready);
|
||||
publishWorkflowEntry(entry1);
|
||||
});
|
||||
|
||||
it('successfully forks repository and loads', () => {
|
||||
login(taskResult.data.forkUser);
|
||||
});
|
||||
|
||||
it('can create an entry on fork', () => {
|
||||
login(taskResult.data.forkUser);
|
||||
createPostAndExit(entry1);
|
||||
});
|
||||
|
||||
it('can update a draft entry on fork', () => {
|
||||
login(taskResult.data.user);
|
||||
createPostAndExit(entry1);
|
||||
updateExistingPostAndExit(entry1, entry2);
|
||||
});
|
||||
|
||||
it('can change entry status from fork', () => {
|
||||
login(taskResult.data.forkUser);
|
||||
createPostAndExit(entry1);
|
||||
goToWorkflow();
|
||||
updateWorkflowStatus(entry1, workflowStatus.draft, workflowStatus.review);
|
||||
});
|
||||
|
||||
it('can delete review entry from fork', () => {
|
||||
login(taskResult.data.forkUser);
|
||||
createPostAndExit(entry1);
|
||||
goToWorkflow();
|
||||
updateWorkflowStatus(entry1, workflowStatus.draft, workflowStatus.review);
|
||||
deleteWorkflowEntry(entry1);
|
||||
});
|
||||
|
||||
it('can return entry to draft and delete it', () => {
|
||||
login(taskResult.data.forkUser);
|
||||
createPostAndExit(entry1);
|
||||
goToWorkflow();
|
||||
updateWorkflowStatus(entry1, workflowStatus.draft, workflowStatus.review);
|
||||
|
||||
updateWorkflowStatus(entry1, workflowStatus.review, workflowStatus.draft);
|
||||
deleteWorkflowEntry(entry1);
|
||||
});
|
||||
}
|
193
cypress/plugins/github.js
Normal file
193
cypress/plugins/github.js
Normal file
@ -0,0 +1,193 @@
|
||||
const Octokit = require('@octokit/rest');
|
||||
const fs = require('fs-extra');
|
||||
const path = require('path');
|
||||
const simpleGit = require('simple-git/promise');
|
||||
|
||||
const GIT_SSH_COMMAND = 'ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no';
|
||||
|
||||
function getGitHubClient(token) {
|
||||
const client = new Octokit({
|
||||
auth: `token ${token}`,
|
||||
baseUrl: 'https://api.github.com',
|
||||
});
|
||||
return client;
|
||||
}
|
||||
|
||||
function getEnvs() {
|
||||
const {
|
||||
GITHUB_REPO_OWNER: owner,
|
||||
GITHUB_REPO_NAME: repo,
|
||||
GITHUB_REPO_TOKEN: token,
|
||||
GITHUB_OPEN_AUTHORING_OWNER: forkOwner,
|
||||
GITHUB_OPEN_AUTHORING_TOKEN: forkToken,
|
||||
} = process.env;
|
||||
if (!owner || !repo || !token || !forkOwner || !forkToken) {
|
||||
throw new Error(
|
||||
'Please set GITHUB_REPO_OWNER, GITHUB_REPO_NAME, GITHUB_REPO_TOKEN, GITHUB_OPEN_AUTHORING_OWNER, GITHUB_OPEN_AUTHORING_TOKEN environment variables',
|
||||
);
|
||||
}
|
||||
return { owner, repo, token, forkOwner, forkToken };
|
||||
}
|
||||
|
||||
async function prepareTestGitHubRepo() {
|
||||
const { owner, repo, token } = getEnvs();
|
||||
|
||||
// postfix a random string to avoid collisions
|
||||
const postfix = Math.random()
|
||||
.toString(32)
|
||||
.substring(2);
|
||||
const testRepoName = `${repo}-${Date.now()}-${postfix}`;
|
||||
|
||||
const client = getGitHubClient(token);
|
||||
|
||||
console.log('Creating repository', testRepoName);
|
||||
await client.repos.createForAuthenticatedUser({
|
||||
name: testRepoName,
|
||||
});
|
||||
|
||||
const tempDir = path.join('.temp', testRepoName);
|
||||
await fs.remove(tempDir);
|
||||
let git = simpleGit().env({ ...process.env, GIT_SSH_COMMAND });
|
||||
|
||||
const repoUrl = `git@github.com:${owner}/${repo}.git`;
|
||||
|
||||
console.log('Cloning repository', repoUrl);
|
||||
await git.clone(repoUrl, tempDir);
|
||||
git = simpleGit(tempDir).env({ ...process.env, GIT_SSH_COMMAND });
|
||||
|
||||
console.log('Pushing to new repository', testRepoName);
|
||||
|
||||
await git.removeRemote('origin');
|
||||
await git.addRemote(
|
||||
'origin',
|
||||
`https://${token}:x-oauth-basic@github.com/${owner}/${testRepoName}`,
|
||||
);
|
||||
await git.push(['-u', 'origin', 'master']);
|
||||
|
||||
return { owner, repo: testRepoName, tempDir };
|
||||
}
|
||||
|
||||
async function getAuthenticatedUser(token) {
|
||||
const client = getGitHubClient(token);
|
||||
const { data: user } = await client.users.getAuthenticated();
|
||||
return { ...user, token, backendName: 'github' };
|
||||
}
|
||||
|
||||
async function getUser() {
|
||||
const { token } = getEnvs();
|
||||
return getAuthenticatedUser(token);
|
||||
}
|
||||
|
||||
async function getForkUser() {
|
||||
const { forkToken } = getEnvs();
|
||||
return getAuthenticatedUser(forkToken);
|
||||
}
|
||||
|
||||
async function deleteRepositories({ owner, repo, tempDir }) {
|
||||
const { forkOwner, token, forkToken } = getEnvs();
|
||||
|
||||
const errorHandler = e => {
|
||||
if (e.status !== 404) {
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
|
||||
console.log('Deleting repository', `${owner}/${repo}`);
|
||||
await fs.remove(tempDir);
|
||||
|
||||
let client = getGitHubClient(token);
|
||||
await client.repos
|
||||
.delete({
|
||||
owner,
|
||||
repo,
|
||||
})
|
||||
.catch(errorHandler);
|
||||
|
||||
console.log('Deleting forked repository', `${forkOwner}/${repo}`);
|
||||
client = getGitHubClient(forkToken);
|
||||
await client.repos
|
||||
.delete({
|
||||
owner: forkOwner,
|
||||
repo,
|
||||
})
|
||||
.catch(errorHandler);
|
||||
}
|
||||
|
||||
async function resetOriginRepo({ owner, repo, tempDir }) {
|
||||
console.log('Resetting origin repo:', `${owner}/repo`);
|
||||
const { token } = getEnvs();
|
||||
const client = getGitHubClient(token);
|
||||
|
||||
const { data: prs } = await client.pulls.list({
|
||||
repo,
|
||||
owner,
|
||||
state: 'open',
|
||||
});
|
||||
const numbers = prs.map(pr => pr.number);
|
||||
console.log('Closing prs:', numbers);
|
||||
await Promise.all(
|
||||
numbers.map(pull_number =>
|
||||
client.pulls.update({
|
||||
owner,
|
||||
repo,
|
||||
pull_number,
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
const { data: branches } = await client.repos.listBranches({ owner, repo });
|
||||
const refs = branches.filter(b => b.name !== 'master').map(b => `heads/${b.name}`);
|
||||
|
||||
console.log('Deleting refs', refs);
|
||||
await Promise.all(
|
||||
refs.map(ref =>
|
||||
client.git.deleteRef({
|
||||
owner,
|
||||
repo,
|
||||
ref,
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
console.log('Resetting master');
|
||||
const git = simpleGit(tempDir).env({ ...process.env, GIT_SSH_COMMAND });
|
||||
await git.push(['--force', 'origin', 'master']);
|
||||
console.log('Done resetting origin repo:', `${owner}/repo`);
|
||||
}
|
||||
|
||||
async function resetForkedRepo({ repo }) {
|
||||
const { forkToken, forkOwner } = getEnvs();
|
||||
const client = getGitHubClient(forkToken);
|
||||
|
||||
const { data: repos } = await client.repos.list();
|
||||
if (repos.some(r => r.name === repo)) {
|
||||
console.log('Resetting forked repo:', `${forkOwner}/${repo}`);
|
||||
const { data: branches } = await client.repos.listBranches({ owner: forkOwner, repo });
|
||||
const refs = branches.filter(b => b.name !== 'master').map(b => `heads/${b.name}`);
|
||||
|
||||
console.log('Deleting refs', refs);
|
||||
await Promise.all(
|
||||
refs.map(ref =>
|
||||
client.git.deleteRef({
|
||||
owner: forkOwner,
|
||||
repo,
|
||||
ref,
|
||||
}),
|
||||
),
|
||||
);
|
||||
console.log('Done resetting forked repo:', `${forkOwner}/repo`);
|
||||
}
|
||||
}
|
||||
|
||||
async function resetRepositories({ owner, repo, tempDir }) {
|
||||
await resetOriginRepo({ owner, repo, tempDir });
|
||||
await resetForkedRepo({ repo });
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
prepareTestGitHubRepo,
|
||||
deleteRepositories,
|
||||
getUser,
|
||||
getForkUser,
|
||||
resetRepositories,
|
||||
};
|
@ -10,8 +10,106 @@
|
||||
|
||||
// This function is called when a project is opened or re-opened (e.g. due to
|
||||
// the project's config changing)
|
||||
require('dotenv').config();
|
||||
const fs = require('fs-extra');
|
||||
const path = require('path');
|
||||
const yaml = require('js-yaml');
|
||||
const {
|
||||
prepareTestGitHubRepo,
|
||||
deleteRepositories,
|
||||
getUser,
|
||||
getForkUser,
|
||||
resetRepositories,
|
||||
} = require('./github');
|
||||
|
||||
module.exports = (on, config) => {
|
||||
// `on` is used to hook into various events Cypress emits
|
||||
// `config` is the resolved Cypress config
|
||||
const devTestDirectory = path.join(__dirname, '..', '..', 'dev-test');
|
||||
const backendsDirectory = path.join(devTestDirectory, 'backends');
|
||||
|
||||
async function copyBackendFiles(backend) {
|
||||
for (let file of ['config.yml', 'index.html']) {
|
||||
await fs.copyFile(
|
||||
path.join(backendsDirectory, backend, file),
|
||||
path.join(devTestDirectory, file),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async function updateConfig(configModifier) {
|
||||
const configFile = path.join(devTestDirectory, 'config.yml');
|
||||
const configContent = await fs.readFile(configFile);
|
||||
const config = yaml.safeLoad(configContent);
|
||||
await configModifier(config);
|
||||
await fs.writeFileSync(configFile, yaml.safeDump(config));
|
||||
return null;
|
||||
}
|
||||
|
||||
async function setupGitHub() {
|
||||
const [user, forkUser, repoData] = await Promise.all([
|
||||
getUser(),
|
||||
getForkUser(),
|
||||
prepareTestGitHubRepo(),
|
||||
]);
|
||||
|
||||
await updateConfig(config => {
|
||||
config.backend.repo = `${repoData.owner}/${repoData.repo}`;
|
||||
});
|
||||
|
||||
return { ...repoData, user, forkUser };
|
||||
}
|
||||
|
||||
async function teardownGitHub(taskData) {
|
||||
await deleteRepositories(taskData);
|
||||
return null;
|
||||
}
|
||||
|
||||
async function teardownBackendTest(taskData) {
|
||||
await resetRepositories(taskData);
|
||||
return null;
|
||||
}
|
||||
|
||||
module.exports = async on => {
|
||||
// `on` is used to hook into various events Cypress emits
|
||||
on('task', {
|
||||
async setupBackend({ backend }) {
|
||||
console.log('Preparing environment for backend', backend);
|
||||
await copyBackendFiles(backend);
|
||||
|
||||
if (backend === 'github') {
|
||||
return await setupGitHub();
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
async teardownBackend(taskData) {
|
||||
const { backend } = taskData;
|
||||
console.log('Tearing down backend', backend);
|
||||
if (backend === 'github') {
|
||||
return await teardownGitHub(taskData);
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
async teardownBackendTest(taskData) {
|
||||
const { backend } = taskData;
|
||||
console.log('Tearing down single test for backend', backend);
|
||||
if (backend === 'github') {
|
||||
return await teardownBackendTest(taskData);
|
||||
}
|
||||
},
|
||||
async updateBackendOptions({ backend, options }) {
|
||||
console.log('Updating backend', backend, 'with options', options);
|
||||
if (backend === 'github') {
|
||||
return await updateConfig(config => {
|
||||
config.backend = { ...config.backend, ...options };
|
||||
});
|
||||
}
|
||||
return null;
|
||||
},
|
||||
async restoreDefaults() {
|
||||
console.log('Restoring defaults');
|
||||
await copyBackendFiles('test');
|
||||
|
||||
return null;
|
||||
},
|
||||
});
|
||||
};
|
||||
|
27
cypress/utils/constants.js
Normal file
27
cypress/utils/constants.js
Normal file
@ -0,0 +1,27 @@
|
||||
const workflowStatus = { draft: 'Drafts', review: 'In Review', ready: 'Ready' };
|
||||
const editorStatus = { draft: 'Draft', review: 'In review', ready: 'Ready' };
|
||||
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.",
|
||||
},
|
||||
validation: {
|
||||
range: {
|
||||
fieldLabel: 'Number of posts on frontpage',
|
||||
message: 'Number of posts on frontpage must be between 1 and 10.',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
workflowStatus,
|
||||
editorStatus,
|
||||
setting1,
|
||||
setting2,
|
||||
notifications,
|
||||
};
|
292
cypress/utils/steps.js
Normal file
292
cypress/utils/steps.js
Normal file
@ -0,0 +1,292 @@
|
||||
const { notifications, workflowStatus } = require('./constants');
|
||||
|
||||
function login(user) {
|
||||
cy.viewport(1200, 1200);
|
||||
if (user) {
|
||||
cy.visit('/', {
|
||||
onBeforeLoad: () => {
|
||||
window.localStorage.setItem('netlify-cms-user', JSON.stringify(user));
|
||||
},
|
||||
});
|
||||
} else {
|
||||
cy.visit('/');
|
||||
cy.contains('button', 'Login').click();
|
||||
}
|
||||
}
|
||||
|
||||
function assertNotification(message) {
|
||||
if (Array.isArray(message)) {
|
||||
console.log(message);
|
||||
const messages = message.reverse();
|
||||
cy.get('.notif__container div')
|
||||
.should('have.length.of', messages.length)
|
||||
.each((el, idx) => {
|
||||
cy.wrap(el)
|
||||
.contains(messages[idx])
|
||||
.invoke('hide');
|
||||
});
|
||||
} else {
|
||||
cy.get('.notif__container').within(() => {
|
||||
cy.contains(message).invoke('hide');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function exitEditor() {
|
||||
cy.contains('a[href^="#/collections/"]', 'Writing in').click();
|
||||
}
|
||||
|
||||
function goToWorkflow() {
|
||||
cy.contains('a', 'Workflow').click();
|
||||
}
|
||||
|
||||
function goToCollections() {
|
||||
cy.contains('a', 'Content').click();
|
||||
}
|
||||
|
||||
function updateWorkflowStatus({ title }, fromColumnHeading, toColumnHeading) {
|
||||
cy.contains('h2', fromColumnHeading)
|
||||
.parent()
|
||||
.contains('a', title)
|
||||
.trigger('dragstart', {
|
||||
dataTransfer: {},
|
||||
force: true,
|
||||
});
|
||||
cy.contains('h2', toColumnHeading)
|
||||
.parent()
|
||||
.trigger('drop', {
|
||||
dataTransfer: {},
|
||||
force: true,
|
||||
});
|
||||
assertNotification(notifications.updated);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
function deleteWorkflowEntry({ title }) {
|
||||
cy.contains('a', title)
|
||||
.parent()
|
||||
.within(() => {
|
||||
cy.contains('button', 'Delete new entry').click({ force: true });
|
||||
});
|
||||
|
||||
assertNotification(notifications.deletedUnpublished);
|
||||
}
|
||||
|
||||
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();
|
||||
});
|
||||
}
|
||||
|
||||
function assertPublishedEntry(entry) {
|
||||
if (Array.isArray(entry)) {
|
||||
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);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
cy.get('a h2')
|
||||
.first()
|
||||
.contains(entry.title);
|
||||
}
|
||||
}
|
||||
|
||||
function deleteEntryInEditor() {
|
||||
cy.contains('button', 'Delete').click();
|
||||
assertNotification(notifications.deletedUnpublished);
|
||||
}
|
||||
|
||||
function assertOnCollectionsPage() {
|
||||
cy.url().should('contain', '/#/collections/posts');
|
||||
cy.contains('h2', 'Collections');
|
||||
}
|
||||
|
||||
function assertEntryDeleted(entry) {
|
||||
if (Array.isArray(entry)) {
|
||||
const titles = entry.map(e => e.title);
|
||||
cy.get('a h2').each(el => {
|
||||
expect(titles).not.to.include(el.text());
|
||||
});
|
||||
} else {
|
||||
cy.get('a h2').each(el => {
|
||||
expect(entry.title).not.to.equal(el.text());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function assertWorkflowStatus({ title }, status) {
|
||||
cy.contains('h2', status)
|
||||
.parent()
|
||||
.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);
|
||||
}
|
||||
|
||||
function populateEntry(entry) {
|
||||
const keys = Object.keys(entry);
|
||||
for (let key of keys) {
|
||||
const value = entry[key];
|
||||
if (key === 'body') {
|
||||
cy.get('[data-slate-editor]')
|
||||
.click()
|
||||
.clear()
|
||||
.type(value);
|
||||
} else {
|
||||
cy.get(`[id^="${key}-field"]`)
|
||||
.clear()
|
||||
.type(value);
|
||||
}
|
||||
}
|
||||
|
||||
cy.get('input')
|
||||
.first()
|
||||
.click();
|
||||
cy.contains('button', 'Save').click();
|
||||
assertNotification(notifications.saved);
|
||||
}
|
||||
|
||||
function createPost(entry) {
|
||||
cy.contains('a', 'New Post').click();
|
||||
populateEntry(entry);
|
||||
}
|
||||
|
||||
function createPostAndExit(entry) {
|
||||
createPost(entry);
|
||||
exitEditor();
|
||||
}
|
||||
|
||||
function updateExistingPostAndExit(fromEntry, toEntry) {
|
||||
goToWorkflow();
|
||||
cy.contains('h2', fromEntry.title)
|
||||
.parent()
|
||||
.click({ force: true });
|
||||
populateEntry(toEntry);
|
||||
exitEditor();
|
||||
goToWorkflow();
|
||||
cy.contains('h2', toEntry.title);
|
||||
}
|
||||
|
||||
function validateObjectFields({ limit, author }) {
|
||||
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);
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
function validateObjectFieldsAndExit(setting) {
|
||||
validateObjectFields(setting);
|
||||
exitEditor();
|
||||
}
|
||||
|
||||
function validateNestedObjectFieldsAndExit(setting) {
|
||||
validateNestedObjectFields(setting);
|
||||
exitEditor();
|
||||
}
|
||||
|
||||
function validateListFieldsAndExit(setting) {
|
||||
validateListFields(setting);
|
||||
exitEditor();
|
||||
}
|
||||
|
||||
function assertFieldValidationError({ message, fieldLabel }) {
|
||||
cy.contains('label', fieldLabel)
|
||||
.siblings('ul[class*=ControlErrorsList]')
|
||||
.contains(message);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
login,
|
||||
createPost,
|
||||
createPostAndExit,
|
||||
updateExistingPostAndExit,
|
||||
exitEditor,
|
||||
goToWorkflow,
|
||||
goToCollections,
|
||||
updateWorkflowStatus,
|
||||
publishWorkflowEntry,
|
||||
deleteWorkflowEntry,
|
||||
assertWorkflowStatusInEditor,
|
||||
assertPublishedEntry,
|
||||
deleteEntryInEditor,
|
||||
assertOnCollectionsPage,
|
||||
assertEntryDeleted,
|
||||
assertWorkflowStatus,
|
||||
updateWorkflowStatusInEditor,
|
||||
validateObjectFieldsAndExit,
|
||||
validateNestedObjectFieldsAndExit,
|
||||
validateListFieldsAndExit,
|
||||
};
|
Reference in New Issue
Block a user