add-cms
This commit is contained in:
93
source/admin/cypress/e2e/common/editorial_workflow.js
Normal file
93
source/admin/cypress/e2e/common/editorial_workflow.js
Normal file
@@ -0,0 +1,93 @@
|
||||
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';
|
||||
|
||||
export default function({ entries, getUser }) {
|
||||
it('successfully loads', () => {
|
||||
login(getUser());
|
||||
});
|
||||
|
||||
it('can create an entry', () => {
|
||||
login(getUser());
|
||||
createPostAndExit(entries[0]);
|
||||
});
|
||||
|
||||
it('can update an entry', () => {
|
||||
login(getUser());
|
||||
createPostAndExit(entries[0]);
|
||||
updateExistingPostAndExit(entries[0], entries[1]);
|
||||
});
|
||||
|
||||
it('can publish an editorial workflow entry', () => {
|
||||
login(getUser());
|
||||
createPostAndExit(entries[0]);
|
||||
goToWorkflow();
|
||||
updateWorkflowStatus(entries[0], workflowStatus.draft, workflowStatus.ready);
|
||||
publishWorkflowEntry(entries[0]);
|
||||
});
|
||||
|
||||
it('can change workflow status', () => {
|
||||
login(getUser());
|
||||
createPostAndExit(entries[0]);
|
||||
goToWorkflow();
|
||||
updateWorkflowStatus(entries[0], workflowStatus.draft, workflowStatus.review);
|
||||
updateWorkflowStatus(entries[0], workflowStatus.review, workflowStatus.ready);
|
||||
updateWorkflowStatus(entries[0], workflowStatus.ready, workflowStatus.review);
|
||||
updateWorkflowStatus(entries[0], workflowStatus.review, workflowStatus.draft);
|
||||
updateWorkflowStatus(entries[0], workflowStatus.draft, workflowStatus.ready);
|
||||
});
|
||||
|
||||
it('can change status on and publish multiple entries', () => {
|
||||
login(getUser());
|
||||
createPostAndExit(entries[0]);
|
||||
createPostAndExit(entries[1]);
|
||||
createPostAndExit(entries[2]);
|
||||
goToWorkflow();
|
||||
updateWorkflowStatus(entries[2], workflowStatus.draft, workflowStatus.ready);
|
||||
updateWorkflowStatus(entries[1], workflowStatus.draft, workflowStatus.ready);
|
||||
updateWorkflowStatus(entries[0], workflowStatus.draft, workflowStatus.ready);
|
||||
publishWorkflowEntry(entries[2]);
|
||||
publishWorkflowEntry(entries[1]);
|
||||
publishWorkflowEntry(entries[0]);
|
||||
goToCollections();
|
||||
assertPublishedEntry([entries[2], entries[1], entries[0]]);
|
||||
});
|
||||
|
||||
it('can delete an entry', () => {
|
||||
login(getUser());
|
||||
createPost(entries[0]);
|
||||
deleteEntryInEditor();
|
||||
assertOnCollectionsPage();
|
||||
assertEntryDeleted(entries[0]);
|
||||
});
|
||||
|
||||
it('can update workflow status from within the editor', () => {
|
||||
login(getUser());
|
||||
createPost(entries[0]);
|
||||
assertWorkflowStatusInEditor(editorStatus.draft);
|
||||
updateWorkflowStatusInEditor(editorStatus.review);
|
||||
assertWorkflowStatusInEditor(editorStatus.review);
|
||||
updateWorkflowStatusInEditor(editorStatus.ready);
|
||||
assertWorkflowStatusInEditor(editorStatus.ready);
|
||||
exitEditor();
|
||||
goToWorkflow();
|
||||
assertWorkflowStatus(entries[0], workflowStatus.ready);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
import '../../utils/dismiss-local-backup';
|
||||
import {
|
||||
login,
|
||||
createPostAndExit,
|
||||
goToWorkflow,
|
||||
goToCollections,
|
||||
updateWorkflowStatus,
|
||||
publishWorkflowEntry,
|
||||
assertPublishedEntry,
|
||||
} from '../../utils/steps';
|
||||
import { workflowStatus } from '../../utils/constants';
|
||||
|
||||
const versions = ['2.9.7', '2.10.24'];
|
||||
|
||||
export default function({ entries, getUser }) {
|
||||
versions.forEach(version => {
|
||||
it(`migrate from ${version} to latest`, () => {
|
||||
cy.task('switchToVersion', {
|
||||
version,
|
||||
});
|
||||
cy.reload();
|
||||
|
||||
login(getUser());
|
||||
createPostAndExit(entries[0]);
|
||||
createPostAndExit(entries[1]);
|
||||
createPostAndExit(entries[2]);
|
||||
goToWorkflow();
|
||||
updateWorkflowStatus(entries[2], workflowStatus.draft, workflowStatus.ready);
|
||||
// eslint-disable-next-line cypress/no-unnecessary-waiting
|
||||
cy.wait(1500); // older versions of the CMS didn't wait fully for the update to be resolved
|
||||
updateWorkflowStatus(entries[1], workflowStatus.draft, workflowStatus.ready);
|
||||
// eslint-disable-next-line cypress/no-unnecessary-waiting
|
||||
cy.wait(1500); // older versions of the CMS didn't wait fully for the update to be resolved
|
||||
updateWorkflowStatus(entries[0], workflowStatus.draft, workflowStatus.ready);
|
||||
// eslint-disable-next-line cypress/no-unnecessary-waiting
|
||||
cy.wait(1500); // older versions of the CMS didn't wait fully for the update to be resolved
|
||||
|
||||
cy.task('switchToVersion', {
|
||||
version: 'latest',
|
||||
});
|
||||
cy.reload();
|
||||
|
||||
// allow migration code to run for 5 minutes
|
||||
publishWorkflowEntry(entries[2], 5 * 60 * 1000);
|
||||
publishWorkflowEntry(entries[1]);
|
||||
publishWorkflowEntry(entries[0]);
|
||||
goToCollections();
|
||||
assertPublishedEntry([entries[2], entries[1], entries[0]]);
|
||||
});
|
||||
});
|
||||
}
|
||||
21
source/admin/cypress/e2e/common/entries.js
Normal file
21
source/admin/cypress/e2e/common/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',
|
||||
};
|
||||
59
source/admin/cypress/e2e/common/i18n.js
Normal file
59
source/admin/cypress/e2e/common/i18n.js
Normal file
@@ -0,0 +1,59 @@
|
||||
import { newPost, populateEntry, publishEntry, flushClockAndSave } from '../../utils/steps';
|
||||
|
||||
const enterTranslation = str => {
|
||||
cy.get(`[id^="title-field"]`)
|
||||
.first()
|
||||
.clear({ force: true });
|
||||
cy.get(`[id^="title-field"]`)
|
||||
.first()
|
||||
.type(str, { force: true });
|
||||
};
|
||||
|
||||
const createAndTranslate = entry => {
|
||||
newPost();
|
||||
// fill the main entry
|
||||
populateEntry(entry, () => undefined);
|
||||
|
||||
// fill the translation
|
||||
cy.get('.Pane2').within(() => {
|
||||
enterTranslation('de');
|
||||
|
||||
cy.contains('span', 'Writing in DE').click();
|
||||
cy.contains('span', 'fr').click();
|
||||
|
||||
enterTranslation('fr');
|
||||
});
|
||||
};
|
||||
|
||||
export const updateTranslation = () => {
|
||||
cy.get('.Pane2').within(() => {
|
||||
enterTranslation('fr fr');
|
||||
|
||||
cy.contains('span', 'Writing in FR').click();
|
||||
cy.contains('span', 'de').click();
|
||||
|
||||
enterTranslation('de de');
|
||||
});
|
||||
cy.contains('button', 'Save').click();
|
||||
};
|
||||
|
||||
export const assertTranslation = () => {
|
||||
cy.get('.Pane2').within(() => {
|
||||
cy.get(`[id^="title-field"]`).should('have.value', 'de');
|
||||
|
||||
cy.contains('span', 'Writing in DE').click();
|
||||
cy.contains('span', 'fr').click();
|
||||
|
||||
cy.get(`[id^="title-field"]`).should('have.value', 'fr');
|
||||
});
|
||||
};
|
||||
|
||||
export const createEntryTranslateAndPublish = entry => {
|
||||
createAndTranslate(entry);
|
||||
publishEntry();
|
||||
};
|
||||
|
||||
export const createEntryTranslateAndSave = entry => {
|
||||
createAndTranslate(entry);
|
||||
flushClockAndSave();
|
||||
};
|
||||
@@ -0,0 +1,54 @@
|
||||
import '../../utils/dismiss-local-backup';
|
||||
import {
|
||||
login,
|
||||
goToWorkflow,
|
||||
updateWorkflowStatus,
|
||||
exitEditor,
|
||||
publishWorkflowEntry,
|
||||
goToEntry,
|
||||
updateWorkflowStatusInEditor,
|
||||
publishEntryInEditor,
|
||||
assertPublishedEntryInEditor,
|
||||
assertUnpublishedEntryInEditor,
|
||||
assertUnpublishedChangesInEditor,
|
||||
} from '../../utils/steps';
|
||||
import { createEntryTranslateAndSave, assertTranslation, updateTranslation } from './i18n';
|
||||
import { workflowStatus, editorStatus, publishTypes } from '../../utils/constants';
|
||||
|
||||
export default function({ entry, getUser }) {
|
||||
const structures = ['multiple_folders', 'multiple_files', 'single_file'];
|
||||
structures.forEach(structure => {
|
||||
it(`can create and publish entry with translation in ${structure} mode`, () => {
|
||||
cy.task('updateConfig', { i18n: { structure } });
|
||||
|
||||
login(getUser());
|
||||
|
||||
createEntryTranslateAndSave(entry);
|
||||
assertUnpublishedEntryInEditor();
|
||||
exitEditor();
|
||||
goToWorkflow();
|
||||
updateWorkflowStatus(entry, workflowStatus.draft, workflowStatus.ready);
|
||||
publishWorkflowEntry(entry);
|
||||
goToEntry(entry);
|
||||
assertTranslation();
|
||||
assertPublishedEntryInEditor();
|
||||
});
|
||||
|
||||
it(`can update translated entry in ${structure} mode`, () => {
|
||||
cy.task('updateConfig', { i18n: { structure: 'multiple_folders' } });
|
||||
|
||||
login(getUser());
|
||||
|
||||
createEntryTranslateAndSave(entry);
|
||||
assertUnpublishedEntryInEditor();
|
||||
updateWorkflowStatusInEditor(editorStatus.ready);
|
||||
publishEntryInEditor(publishTypes.publishNow);
|
||||
exitEditor();
|
||||
goToEntry(entry);
|
||||
assertTranslation();
|
||||
assertPublishedEntryInEditor();
|
||||
updateTranslation();
|
||||
assertUnpublishedChangesInEditor();
|
||||
});
|
||||
});
|
||||
}
|
||||
177
source/admin/cypress/e2e/common/media_library.js
Normal file
177
source/admin/cypress/e2e/common/media_library.js
Normal file
@@ -0,0 +1,177 @@
|
||||
import '../../utils/dismiss-local-backup';
|
||||
import {
|
||||
login,
|
||||
goToMediaLibrary,
|
||||
newPost,
|
||||
populateEntry,
|
||||
exitEditor,
|
||||
goToWorkflow,
|
||||
updateWorkflowStatus,
|
||||
publishWorkflowEntry,
|
||||
goToEntry,
|
||||
goToCollections,
|
||||
} from '../../utils/steps';
|
||||
import { workflowStatus } from '../../utils/constants';
|
||||
|
||||
function uploadMediaFile() {
|
||||
assertNoImagesInLibrary();
|
||||
|
||||
const fixture = 'cypress/fixtures/media/netlify.png';
|
||||
cy.get('input[type="file"]').selectFile(fixture, { force: true });
|
||||
cy.contains('span', 'Uploading...').should('not.exist');
|
||||
|
||||
assertImagesInLibrary();
|
||||
}
|
||||
|
||||
function assertImagesInLibrary() {
|
||||
cy.get('img[class*="CardImage"]').should('exist');
|
||||
}
|
||||
|
||||
function assertNoImagesInLibrary() {
|
||||
cy.get('h1')
|
||||
.contains('Loading...')
|
||||
.should('not.exist');
|
||||
cy.get('img[class*="CardImage"]').should('not.exist');
|
||||
}
|
||||
|
||||
function deleteImage() {
|
||||
cy.get('img[class*="CardImage"]').click();
|
||||
cy.contains('button', 'Delete selected').click();
|
||||
assertNoImagesInLibrary();
|
||||
}
|
||||
|
||||
function chooseSelectedMediaFile() {
|
||||
cy.contains('button', 'Choose selected').should('not.be.disabled');
|
||||
cy.contains('button', 'Choose selected').click();
|
||||
}
|
||||
|
||||
function chooseAnImage() {
|
||||
cy.contains('button', 'Choose an image').click();
|
||||
}
|
||||
|
||||
function waitForEntryToLoad() {
|
||||
cy.contains('button', 'Saving...').should('not.exist');
|
||||
cy.clock().tick(5000);
|
||||
cy.contains('div', 'Loading entry...').should('not.exist');
|
||||
}
|
||||
|
||||
function matchImageSnapshot() {
|
||||
// cy.matchImageSnapshot();
|
||||
}
|
||||
|
||||
function newPostAndUploadImage() {
|
||||
newPost();
|
||||
chooseAnImage();
|
||||
uploadMediaFile();
|
||||
}
|
||||
|
||||
function newPostWithImage(entry) {
|
||||
newPostAndUploadImage();
|
||||
chooseSelectedMediaFile();
|
||||
populateEntry(entry);
|
||||
waitForEntryToLoad();
|
||||
}
|
||||
|
||||
function publishPostWithImage(entry) {
|
||||
newPostWithImage(entry);
|
||||
// eslint-disable-next-line cypress/no-unnecessary-waiting
|
||||
cy.wait(500);
|
||||
exitEditor();
|
||||
goToWorkflow();
|
||||
updateWorkflowStatus(entry, workflowStatus.draft, workflowStatus.ready);
|
||||
publishWorkflowEntry(entry);
|
||||
}
|
||||
|
||||
function closeMediaLibrary() {
|
||||
cy.get('button[class*="CloseButton"]').click();
|
||||
}
|
||||
|
||||
function switchToGridView() {
|
||||
cy.get('div[class*="ViewControls"]').within(() => {
|
||||
cy.get('button')
|
||||
.last()
|
||||
.click();
|
||||
});
|
||||
}
|
||||
|
||||
function assertGridEntryImage(entry) {
|
||||
cy.contains('li', entry.title).within(() => {
|
||||
cy.get('div[class*="CardImage"]').should('be.visible');
|
||||
});
|
||||
}
|
||||
|
||||
export default function({ entries, getUser }) {
|
||||
beforeEach(() => {
|
||||
login(getUser && getUser());
|
||||
});
|
||||
|
||||
it('can upload image from global media library', () => {
|
||||
goToMediaLibrary();
|
||||
uploadMediaFile();
|
||||
matchImageSnapshot();
|
||||
closeMediaLibrary();
|
||||
});
|
||||
|
||||
it('can delete image from global media library', () => {
|
||||
goToMediaLibrary();
|
||||
uploadMediaFile();
|
||||
closeMediaLibrary();
|
||||
goToMediaLibrary();
|
||||
deleteImage();
|
||||
matchImageSnapshot();
|
||||
closeMediaLibrary();
|
||||
});
|
||||
|
||||
it('can upload image from entry media library', () => {
|
||||
newPostAndUploadImage();
|
||||
matchImageSnapshot();
|
||||
closeMediaLibrary();
|
||||
exitEditor();
|
||||
});
|
||||
|
||||
it('can save entry with image', () => {
|
||||
newPostWithImage(entries[0]);
|
||||
matchImageSnapshot();
|
||||
exitEditor();
|
||||
});
|
||||
|
||||
it('can publish entry with image', () => {
|
||||
publishPostWithImage(entries[0]);
|
||||
goToEntry(entries[0]);
|
||||
waitForEntryToLoad();
|
||||
matchImageSnapshot();
|
||||
});
|
||||
|
||||
it('should not show draft entry image in global media library', () => {
|
||||
newPostWithImage(entries[0]);
|
||||
cy.clock().then(clock => {
|
||||
if (clock) {
|
||||
clock.tick(150);
|
||||
clock.tick(150);
|
||||
// eslint-disable-next-line cypress/no-unnecessary-waiting
|
||||
cy.wait(500);
|
||||
}
|
||||
exitEditor();
|
||||
goToMediaLibrary();
|
||||
assertNoImagesInLibrary();
|
||||
matchImageSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
it('should show published entry image in global media library', () => {
|
||||
publishPostWithImage(entries[0]);
|
||||
cy.clock().tick();
|
||||
goToMediaLibrary();
|
||||
assertImagesInLibrary();
|
||||
matchImageSnapshot();
|
||||
});
|
||||
|
||||
it('should show published entry image in grid view', () => {
|
||||
publishPostWithImage(entries[0]);
|
||||
goToCollections();
|
||||
switchToGridView();
|
||||
assertGridEntryImage(entries[0]);
|
||||
|
||||
matchImageSnapshot();
|
||||
});
|
||||
}
|
||||
76
source/admin/cypress/e2e/common/open_authoring.js
Normal file
76
source/admin/cypress/e2e/common/open_authoring.js
Normal file
@@ -0,0 +1,76 @@
|
||||
import '../../utils/dismiss-local-backup';
|
||||
import {
|
||||
login,
|
||||
createPostAndExit,
|
||||
updateExistingPostAndExit,
|
||||
goToWorkflow,
|
||||
deleteWorkflowEntry,
|
||||
updateWorkflowStatus,
|
||||
publishWorkflowEntry,
|
||||
} from '../../utils/steps';
|
||||
import { workflowStatus } from '../../utils/constants';
|
||||
|
||||
export default function({ entries, getUser, getForkUser }) {
|
||||
it('successfully loads', () => {
|
||||
login(getUser());
|
||||
});
|
||||
|
||||
it('can create an entry', () => {
|
||||
login(getUser());
|
||||
createPostAndExit(entries[0]);
|
||||
});
|
||||
|
||||
it('can update an entry', () => {
|
||||
login(getUser());
|
||||
createPostAndExit(entries[0]);
|
||||
updateExistingPostAndExit(entries[0], entries[1]);
|
||||
});
|
||||
|
||||
it('can publish an editorial workflow entry', () => {
|
||||
login(getUser());
|
||||
createPostAndExit(entries[0]);
|
||||
goToWorkflow();
|
||||
updateWorkflowStatus(entries[0], workflowStatus.draft, workflowStatus.ready);
|
||||
publishWorkflowEntry(entries[0]);
|
||||
});
|
||||
|
||||
it('successfully forks repository and loads', () => {
|
||||
login(getForkUser());
|
||||
});
|
||||
|
||||
it('can create an entry on fork', () => {
|
||||
login(getForkUser());
|
||||
createPostAndExit(entries[0]);
|
||||
});
|
||||
|
||||
it('can update a draft entry on fork', () => {
|
||||
login(getForkUser());
|
||||
createPostAndExit(entries[0]);
|
||||
updateExistingPostAndExit(entries[0], entries[1]);
|
||||
});
|
||||
|
||||
it('can change entry status from fork', () => {
|
||||
login(getForkUser());
|
||||
createPostAndExit(entries[0]);
|
||||
goToWorkflow();
|
||||
updateWorkflowStatus(entries[0], workflowStatus.draft, workflowStatus.review);
|
||||
});
|
||||
|
||||
it('can delete review entry from fork', () => {
|
||||
login(getForkUser());
|
||||
createPostAndExit(entries[0]);
|
||||
goToWorkflow();
|
||||
updateWorkflowStatus(entries[0], workflowStatus.draft, workflowStatus.review);
|
||||
deleteWorkflowEntry(entries[0]);
|
||||
});
|
||||
|
||||
it('can return entry to draft and delete it', () => {
|
||||
login(getForkUser());
|
||||
createPostAndExit(entries[0]);
|
||||
goToWorkflow();
|
||||
updateWorkflowStatus(entries[0], workflowStatus.draft, workflowStatus.review);
|
||||
|
||||
updateWorkflowStatus(entries[0], workflowStatus.review, workflowStatus.draft);
|
||||
deleteWorkflowEntry(entries[0]);
|
||||
});
|
||||
}
|
||||
14
source/admin/cypress/e2e/common/simple_workflow.js
Normal file
14
source/admin/cypress/e2e/common/simple_workflow.js
Normal file
@@ -0,0 +1,14 @@
|
||||
import '../../utils/dismiss-local-backup';
|
||||
import { login, createPostAndPublish, assertPublishedEntry } from '../../utils/steps';
|
||||
|
||||
export default function({ entries, getUser }) {
|
||||
it('successfully loads', () => {
|
||||
login(getUser());
|
||||
});
|
||||
|
||||
it('can create an entry', () => {
|
||||
login(getUser());
|
||||
createPostAndPublish(entries[0]);
|
||||
assertPublishedEntry(entries[0]);
|
||||
});
|
||||
}
|
||||
65
source/admin/cypress/e2e/common/spec_utils.js
Normal file
65
source/admin/cypress/e2e/common/spec_utils.js
Normal file
@@ -0,0 +1,65 @@
|
||||
export const before = (taskResult, options, backend) => {
|
||||
Cypress.config('taskTimeout', 7 * 60 * 1000);
|
||||
cy.task('setupBackend', { backend, options }).then(data => {
|
||||
taskResult.data = data;
|
||||
Cypress.config('defaultCommandTimeout', data.mockResponses ? 5 * 1000 : 1 * 60 * 1000);
|
||||
});
|
||||
};
|
||||
|
||||
export const after = (taskResult, backend) => {
|
||||
cy.task('teardownBackend', {
|
||||
backend,
|
||||
...taskResult.data,
|
||||
});
|
||||
};
|
||||
|
||||
export const beforeEach = (taskResult, backend) => {
|
||||
const spec = Cypress.mocha.getRunner().suite.ctx.currentTest.parent.title;
|
||||
const testName = Cypress.mocha.getRunner().suite.ctx.currentTest.title;
|
||||
cy.task('setupBackendTest', {
|
||||
backend,
|
||||
...taskResult.data,
|
||||
spec,
|
||||
testName,
|
||||
});
|
||||
|
||||
if (taskResult.data.mockResponses) {
|
||||
const fixture = `${spec}__${testName}.json`;
|
||||
console.log('loading fixture:', fixture);
|
||||
cy.stubFetch({ fixture });
|
||||
}
|
||||
|
||||
return cy.clock(0, ['Date']);
|
||||
};
|
||||
|
||||
export const afterEach = (taskResult, backend) => {
|
||||
const spec = Cypress.mocha.getRunner().suite.ctx.currentTest.parent.title;
|
||||
const testName = Cypress.mocha.getRunner().suite.ctx.currentTest.title;
|
||||
|
||||
cy.task('teardownBackendTest', {
|
||||
backend,
|
||||
...taskResult.data,
|
||||
spec,
|
||||
testName,
|
||||
});
|
||||
|
||||
if (!process.env.RECORD_FIXTURES) {
|
||||
const {
|
||||
suite: {
|
||||
ctx: {
|
||||
currentTest: { state, _retries: retries, _currentRetry: currentRetry },
|
||||
},
|
||||
},
|
||||
} = Cypress.mocha.getRunner();
|
||||
if (state === 'failed' && retries === currentRetry) {
|
||||
Cypress.runner.stop();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const seedRepo = (taskResult, backend) => {
|
||||
cy.task('seedRepo', {
|
||||
backend,
|
||||
...taskResult.data,
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,30 @@
|
||||
import fixture from './common/editorial_workflow';
|
||||
import * as specUtils from './common/spec_utils';
|
||||
import { entry1, entry2, entry3 } from './common/entries';
|
||||
|
||||
const backend = 'bitbucket';
|
||||
|
||||
describe('BitBucket Backend Editorial Workflow', () => {
|
||||
let taskResult = { data: {} };
|
||||
|
||||
before(() => {
|
||||
specUtils.before(taskResult, { publish_mode: 'editorial_workflow' }, backend);
|
||||
});
|
||||
|
||||
after(() => {
|
||||
specUtils.after(taskResult, backend);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
specUtils.beforeEach(taskResult, backend);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
specUtils.afterEach(taskResult, backend);
|
||||
});
|
||||
|
||||
fixture({
|
||||
entries: [entry1, entry2, entry3],
|
||||
getUser: () => taskResult.data.user,
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,31 @@
|
||||
import fixture from './common/editorial_workflow';
|
||||
import * as specUtils from './common/spec_utils';
|
||||
import { entry1, entry2, entry3 } from './common/entries';
|
||||
|
||||
const backend = 'git-gateway';
|
||||
const provider = 'github';
|
||||
|
||||
describe('Git Gateway (GitHub) Backend Editorial Workflow', () => {
|
||||
let taskResult = { data: {} };
|
||||
|
||||
before(() => {
|
||||
specUtils.before(taskResult, { publish_mode: 'editorial_workflow', provider }, backend);
|
||||
});
|
||||
|
||||
after(() => {
|
||||
specUtils.after(taskResult, backend);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
specUtils.beforeEach(taskResult, backend);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
specUtils.afterEach(taskResult, backend);
|
||||
});
|
||||
|
||||
fixture({
|
||||
entries: [entry1, entry2, entry3],
|
||||
getUser: () => taskResult.data.user,
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,31 @@
|
||||
import fixture from './common/editorial_workflow';
|
||||
import * as specUtils from './common/spec_utils';
|
||||
import { entry1, entry2, entry3 } from './common/entries';
|
||||
|
||||
const backend = 'git-gateway';
|
||||
const provider = 'gitlab';
|
||||
|
||||
describe('Git Gateway (GitLab) Backend Editorial Workflow', () => {
|
||||
let taskResult = { data: {} };
|
||||
|
||||
before(() => {
|
||||
specUtils.before(taskResult, { publish_mode: 'editorial_workflow', provider }, backend);
|
||||
});
|
||||
|
||||
after(() => {
|
||||
specUtils.after(taskResult, backend);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
specUtils.beforeEach(taskResult, backend);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
specUtils.afterEach(taskResult, backend);
|
||||
});
|
||||
|
||||
fixture({
|
||||
entries: [entry1, entry2, entry3],
|
||||
getUser: () => taskResult.data.user,
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,37 @@
|
||||
import fixture from './common/editorial_workflow';
|
||||
import * as specUtils from './common/spec_utils';
|
||||
import { entry1, entry2, entry3 } from './common/entries';
|
||||
|
||||
const backend = 'github';
|
||||
|
||||
describe('GitHub Backend Editorial Workflow - GraphQL API', () => {
|
||||
const taskResult = { data: {} };
|
||||
|
||||
before(() => {
|
||||
specUtils.before(
|
||||
taskResult,
|
||||
{
|
||||
backend: { use_graphql: true, open_authoring: false },
|
||||
publish_mode: 'editorial_workflow',
|
||||
},
|
||||
backend,
|
||||
);
|
||||
});
|
||||
|
||||
after(() => {
|
||||
specUtils.after(taskResult, backend);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
specUtils.beforeEach(taskResult, backend);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
specUtils.afterEach(taskResult, backend);
|
||||
});
|
||||
|
||||
fixture({
|
||||
entries: [entry1, entry2, entry3],
|
||||
getUser: () => taskResult.data.user,
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,38 @@
|
||||
import fixture from './common/open_authoring';
|
||||
import * as specUtils from './common/spec_utils';
|
||||
import { entry1, entry2, entry3 } from './common/entries';
|
||||
|
||||
const backend = 'github';
|
||||
|
||||
describe('GitHub Backend Editorial Workflow - GraphQL API - Open Authoring', () => {
|
||||
const taskResult = { data: {} };
|
||||
|
||||
before(() => {
|
||||
specUtils.before(
|
||||
taskResult,
|
||||
{
|
||||
backend: { use_graphql: true, open_authoring: true },
|
||||
publish_mode: 'editorial_workflow',
|
||||
},
|
||||
backend,
|
||||
);
|
||||
});
|
||||
|
||||
after(() => {
|
||||
specUtils.after(taskResult, backend);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
specUtils.beforeEach(taskResult, backend);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
specUtils.afterEach(taskResult, backend);
|
||||
});
|
||||
|
||||
fixture({
|
||||
entries: [entry1, entry2, entry3],
|
||||
getUser: () => taskResult.data.user,
|
||||
getForkUser: () => taskResult.data.forkUser,
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,37 @@
|
||||
import fixture from './common/editorial_workflow';
|
||||
import * as specUtils from './common/spec_utils';
|
||||
import { entry1, entry2, entry3 } from './common/entries';
|
||||
|
||||
const backend = 'github';
|
||||
|
||||
describe('GitHub Backend Editorial Workflow - REST API', () => {
|
||||
let taskResult = { data: {} };
|
||||
|
||||
before(() => {
|
||||
specUtils.before(
|
||||
taskResult,
|
||||
{
|
||||
backend: { use_graphql: false, open_authoring: false },
|
||||
publish_mode: 'editorial_workflow',
|
||||
},
|
||||
backend,
|
||||
);
|
||||
});
|
||||
|
||||
after(() => {
|
||||
specUtils.after(taskResult, backend);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
specUtils.beforeEach(taskResult, backend);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
specUtils.afterEach(taskResult, backend);
|
||||
});
|
||||
|
||||
fixture({
|
||||
entries: [entry1, entry2, entry3],
|
||||
getUser: () => taskResult.data.user,
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,38 @@
|
||||
import fixture from './common/open_authoring';
|
||||
import * as specUtils from './common/spec_utils';
|
||||
import { entry1, entry2, entry3 } from './common/entries';
|
||||
|
||||
const backend = 'github';
|
||||
|
||||
describe('GitHub Backend Editorial Workflow - REST API - Open Authoring', () => {
|
||||
let taskResult = { data: {} };
|
||||
|
||||
before(() => {
|
||||
specUtils.before(
|
||||
taskResult,
|
||||
{
|
||||
backend: { use_graphql: false, open_authoring: true },
|
||||
publish_mode: 'editorial_workflow',
|
||||
},
|
||||
backend,
|
||||
);
|
||||
});
|
||||
|
||||
after(() => {
|
||||
specUtils.after(taskResult, backend);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
specUtils.beforeEach(taskResult, backend);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
specUtils.afterEach(taskResult, backend);
|
||||
});
|
||||
|
||||
fixture({
|
||||
entries: [entry1, entry2, entry3],
|
||||
getUser: () => taskResult.data.user,
|
||||
getForkUser: () => taskResult.data.forkUser,
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,36 @@
|
||||
import fixture from './common/editorial_workflow';
|
||||
import * as specUtils from './common/spec_utils';
|
||||
import { entry1, entry2, entry3 } from './common/entries';
|
||||
|
||||
const backend = 'gitlab';
|
||||
|
||||
describe('GitLab Backend Editorial Workflow', () => {
|
||||
let taskResult = { data: {} };
|
||||
|
||||
before(() => {
|
||||
specUtils.before(taskResult, { publish_mode: 'editorial_workflow' }, backend);
|
||||
});
|
||||
|
||||
after(() => {
|
||||
specUtils.after(taskResult, backend);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
if (
|
||||
Cypress.mocha.getRunner().suite.ctx.currentTest.title ===
|
||||
'can change status on and publish multiple entries'
|
||||
) {
|
||||
Cypress.mocha.getRunner().suite.ctx.currentTest.skip();
|
||||
}
|
||||
specUtils.beforeEach(taskResult, backend);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
specUtils.afterEach(taskResult, backend);
|
||||
});
|
||||
|
||||
fixture({
|
||||
entries: [entry1, entry2, entry3],
|
||||
getUser: () => taskResult.data.user,
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,32 @@
|
||||
import fixture from './common/editorial_workflow';
|
||||
import * as specUtils from './common/spec_utils';
|
||||
import { entry1, entry2, entry3 } from './common/entries';
|
||||
|
||||
const backend = 'proxy';
|
||||
const mode = 'git';
|
||||
|
||||
describe.skip(`Proxy Backend Editorial Workflow - '${mode}' mode`, () => {
|
||||
let taskResult = { data: {} };
|
||||
|
||||
before(() => {
|
||||
specUtils.before(taskResult, { publish_mode: 'editorial_workflow', mode }, backend);
|
||||
Cypress.config('defaultCommandTimeout', 5 * 1000);
|
||||
});
|
||||
|
||||
after(() => {
|
||||
specUtils.after(taskResult, backend);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
specUtils.beforeEach(taskResult, backend);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
specUtils.afterEach(taskResult, backend);
|
||||
});
|
||||
|
||||
fixture({
|
||||
entries: [entry1, entry2, entry3],
|
||||
getUser: () => taskResult.data.user,
|
||||
});
|
||||
});
|
||||
312
source/admin/cypress/e2e/editorial_workflow_spec_test_backend.js
Normal file
312
source/admin/cypress/e2e/editorial_workflow_spec_test_backend.js
Normal file
@@ -0,0 +1,312 @@
|
||||
import '../utils/dismiss-local-backup';
|
||||
import {
|
||||
login,
|
||||
createPost,
|
||||
createPostAndExit,
|
||||
exitEditor,
|
||||
goToWorkflow,
|
||||
goToCollections,
|
||||
updateWorkflowStatus,
|
||||
publishWorkflowEntry,
|
||||
assertWorkflowStatusInEditor,
|
||||
assertPublishedEntry,
|
||||
deleteEntryInEditor,
|
||||
assertOnCollectionsPage,
|
||||
assertEntryDeleted,
|
||||
assertWorkflowStatus,
|
||||
updateWorkflowStatusInEditor,
|
||||
unpublishEntry,
|
||||
publishEntryInEditor,
|
||||
duplicateEntry,
|
||||
goToEntry,
|
||||
populateEntry,
|
||||
publishAndCreateNewEntryInEditor,
|
||||
publishAndDuplicateEntryInEditor,
|
||||
assertNotification,
|
||||
assertFieldValidationError,
|
||||
} from '../utils/steps';
|
||||
import { workflowStatus, editorStatus, publishTypes, notifications } 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('teardownBackend', { backend: 'test' });
|
||||
});
|
||||
|
||||
before(() => {
|
||||
Cypress.config('defaultCommandTimeout', 4000);
|
||||
cy.task('setupBackend', { backend: 'test' });
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
cy.task('updateConfig', { collections: [{ publish: true }] });
|
||||
});
|
||||
|
||||
it('successfully loads', () => {
|
||||
login();
|
||||
});
|
||||
|
||||
it('can create an entry', () => {
|
||||
login();
|
||||
createPost(entry1);
|
||||
|
||||
// new entry should show 'Delete unpublished entry'
|
||||
cy.contains('button', 'Delete unpublished entry');
|
||||
cy.url().should(
|
||||
'eq',
|
||||
`http://localhost:8080/#/collections/posts/entries/1970-01-01-${entry1.title
|
||||
.toLowerCase()
|
||||
.replace(/\s/, '-')}`,
|
||||
);
|
||||
exitEditor();
|
||||
});
|
||||
|
||||
it('can publish an editorial workflow entry', () => {
|
||||
login();
|
||||
createPostAndExit(entry1);
|
||||
goToWorkflow();
|
||||
updateWorkflowStatus(entry1, workflowStatus.draft, workflowStatus.ready);
|
||||
publishWorkflowEntry(entry1);
|
||||
});
|
||||
|
||||
it('can update an entry', () => {
|
||||
login();
|
||||
createPostAndExit(entry1);
|
||||
goToWorkflow();
|
||||
updateWorkflowStatus(entry1, workflowStatus.draft, workflowStatus.ready);
|
||||
publishWorkflowEntry(entry1);
|
||||
|
||||
goToEntry(entry1);
|
||||
populateEntry(entry2);
|
||||
// existing entry should show 'Delete unpublished changes'
|
||||
cy.contains('button', 'Delete unpublished changes');
|
||||
// existing entry slug should remain the same after save'
|
||||
cy.url().should(
|
||||
'eq',
|
||||
`http://localhost:8080/#/collections/posts/entries/1970-01-01-${entry1.title
|
||||
.toLowerCase()
|
||||
.replace(/\s/, '-')}`,
|
||||
);
|
||||
exitEditor();
|
||||
});
|
||||
|
||||
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);
|
||||
});
|
||||
|
||||
it('can unpublish an existing entry', () => {
|
||||
// first publish an entry
|
||||
login();
|
||||
createPostAndExit(entry1);
|
||||
goToWorkflow();
|
||||
updateWorkflowStatus(entry1, workflowStatus.draft, workflowStatus.ready);
|
||||
publishWorkflowEntry(entry1);
|
||||
// then unpublish it
|
||||
unpublishEntry(entry1);
|
||||
});
|
||||
|
||||
it('can duplicate an existing entry', () => {
|
||||
login();
|
||||
createPost(entry1);
|
||||
updateWorkflowStatusInEditor(editorStatus.ready);
|
||||
publishEntryInEditor(publishTypes.publishNow);
|
||||
duplicateEntry(entry1);
|
||||
});
|
||||
|
||||
it('cannot publish when "publish" is false', () => {
|
||||
cy.task('updateConfig', { collections: [{ publish: false }] });
|
||||
login();
|
||||
createPost(entry1);
|
||||
cy.contains('span', 'Publish').should('not.exist');
|
||||
exitEditor();
|
||||
goToWorkflow();
|
||||
updateWorkflowStatus(entry1, workflowStatus.draft, workflowStatus.ready);
|
||||
cy.contains('button', 'Publish new entry').should('not.exist');
|
||||
});
|
||||
|
||||
it('can create a new entry, publish and create new', () => {
|
||||
login();
|
||||
createPost(entry1);
|
||||
updateWorkflowStatusInEditor(editorStatus.ready);
|
||||
|
||||
publishAndCreateNewEntryInEditor(entry1);
|
||||
});
|
||||
|
||||
it('can create a new entry, publish and duplicate', () => {
|
||||
login();
|
||||
createPost(entry1);
|
||||
updateWorkflowStatusInEditor(editorStatus.ready);
|
||||
publishAndDuplicateEntryInEditor(entry1);
|
||||
});
|
||||
|
||||
const inSidebar = func => {
|
||||
cy.get('[class*=SidebarNavList]').within(func);
|
||||
};
|
||||
|
||||
const inGrid = func => {
|
||||
cy.get('[class*=CardsGrid]').within(func);
|
||||
};
|
||||
|
||||
it('can access nested collection items', () => {
|
||||
login();
|
||||
|
||||
inSidebar(() => cy.contains('a', 'Pages').click());
|
||||
inSidebar(() => cy.contains('a', /^Directory$/));
|
||||
inGrid(() => cy.contains('a', 'Root Page'));
|
||||
|
||||
inSidebar(() => cy.contains('a', /^Directory$/).click());
|
||||
|
||||
inSidebar(() => cy.contains('a', /^Sub Directory$/));
|
||||
inSidebar(() => cy.contains('a', 'Another Sub Directory'));
|
||||
|
||||
inSidebar(() => cy.contains('a', /^Sub Directory$/).click());
|
||||
inSidebar(() => cy.contains('a', 'Nested Directory'));
|
||||
cy.url().should(
|
||||
'eq',
|
||||
'http://localhost:8080/#/collections/pages/filter/directory/sub-directory',
|
||||
);
|
||||
|
||||
inSidebar(() => cy.contains('a', 'Pages').click());
|
||||
inSidebar(() => cy.contains('a', 'Pages').click());
|
||||
|
||||
inGrid(() => cy.contains('a', 'Another Sub Directory').should('not.exist'));
|
||||
});
|
||||
|
||||
it('can navigate to nested entry', () => {
|
||||
login();
|
||||
|
||||
inSidebar(() => cy.contains('a', 'Pages').click());
|
||||
inSidebar(() => cy.contains('a', /^Directory$/).click());
|
||||
inSidebar(() => cy.contains('a', 'Another Sub Directory').click());
|
||||
inGrid(() => cy.contains('a', 'Another Sub Directory'));
|
||||
});
|
||||
|
||||
it(`can create a new entry with custom path`, () => {
|
||||
login();
|
||||
|
||||
inSidebar(() => cy.contains('a', 'Pages').click());
|
||||
inSidebar(() => cy.contains('a', /^Directory$/).click());
|
||||
inSidebar(() => cy.contains('a', /^Sub Directory$/).click());
|
||||
cy.contains('a', 'New Page').click();
|
||||
|
||||
cy.get('[id^="path-field"]').should('have.value', 'directory/sub-directory');
|
||||
cy.get('[id^="path-field"]').type('/new-path');
|
||||
cy.get('[id^="title-field"]').type('New Path Title');
|
||||
cy.clock().then(clock => {
|
||||
clock.tick(150);
|
||||
});
|
||||
cy.contains('button', 'Save').click();
|
||||
assertNotification(notifications.saved);
|
||||
updateWorkflowStatusInEditor(editorStatus.ready);
|
||||
publishEntryInEditor(publishTypes.publishNow);
|
||||
exitEditor();
|
||||
|
||||
inSidebar(() => cy.contains('a', 'New Path Title'));
|
||||
inSidebar(() => cy.contains('a', /^Directory$/).click());
|
||||
inSidebar(() => cy.contains('a', /^Directory$/).click());
|
||||
inGrid(() => cy.contains('a', 'New Path Title').should('not.exist'));
|
||||
});
|
||||
|
||||
it(`can't create an entry with an existing path`, () => {
|
||||
login();
|
||||
|
||||
inSidebar(() => cy.contains('a', 'Pages').click());
|
||||
inSidebar(() => cy.contains('a', /^Directory$/).click());
|
||||
inSidebar(() => cy.contains('a', /^Sub Directory$/).click());
|
||||
|
||||
cy.contains('a', 'New Page').click();
|
||||
cy.get('[id^="title-field"]').type('New Path Title');
|
||||
cy.clock().then(clock => {
|
||||
clock.tick(150);
|
||||
});
|
||||
cy.contains('button', 'Save').click();
|
||||
|
||||
assertFieldValidationError({
|
||||
message: `Path 'directory/sub-directory' already exists`,
|
||||
fieldLabel: 'Path',
|
||||
});
|
||||
});
|
||||
|
||||
it('can move an existing entry to a new path', () => {
|
||||
login();
|
||||
|
||||
inSidebar(() => cy.contains('a', 'Pages').click());
|
||||
inSidebar(() => cy.contains('a', /^Directory$/).click());
|
||||
inGrid(() => cy.contains('a', /^Directory$/).click());
|
||||
|
||||
cy.get('[id^="path-field"]').should('have.value', 'directory');
|
||||
cy.get('[id^="path-field"]').clear();
|
||||
cy.get('[id^="path-field"]').type('new-directory');
|
||||
cy.get('[id^="title-field"]').clear();
|
||||
cy.get('[id^="title-field"]').type('New Directory');
|
||||
cy.clock().then(clock => {
|
||||
clock.tick(150);
|
||||
});
|
||||
cy.contains('button', 'Save').click();
|
||||
assertNotification(notifications.saved);
|
||||
updateWorkflowStatusInEditor(editorStatus.ready);
|
||||
publishEntryInEditor(publishTypes.publishNow);
|
||||
exitEditor();
|
||||
|
||||
inSidebar(() => cy.contains('a', 'New Directory').click());
|
||||
|
||||
inSidebar(() => cy.contains('a', /^Sub Directory$/));
|
||||
inSidebar(() => cy.contains('a', 'Another Sub Directory'));
|
||||
});
|
||||
});
|
||||
109
source/admin/cypress/e2e/field_validations_spec.js
Normal file
109
source/admin/cypress/e2e/field_validations_spec.js
Normal file
@@ -0,0 +1,109 @@
|
||||
import '../utils/dismiss-local-backup';
|
||||
import {
|
||||
login,
|
||||
validateObjectFieldsAndExit,
|
||||
validateNestedObjectFieldsAndExit,
|
||||
validateListFieldsAndExit,
|
||||
validateNestedListFieldsAndExit,
|
||||
} from '../utils/steps';
|
||||
import { setting1, setting2 } from '../utils/constants';
|
||||
|
||||
const nestedListConfig = {
|
||||
collections: [
|
||||
{},
|
||||
{},
|
||||
{
|
||||
name: 'settings',
|
||||
label: 'Settings',
|
||||
editor: { preview: false },
|
||||
files: [
|
||||
{},
|
||||
{},
|
||||
{
|
||||
name: 'hotel_locations',
|
||||
label: 'Hotel Locations',
|
||||
file: '_data/hotel_locations.yml',
|
||||
fields: [
|
||||
{
|
||||
label: 'Country',
|
||||
name: 'country',
|
||||
widget: 'string',
|
||||
},
|
||||
{
|
||||
label: 'Hotel Locations',
|
||||
name: 'hotel_locations',
|
||||
widget: 'list',
|
||||
fields: [
|
||||
{
|
||||
label: 'Cities',
|
||||
name: 'cities',
|
||||
widget: 'list',
|
||||
fields: [
|
||||
{
|
||||
label: 'City',
|
||||
name: 'city',
|
||||
widget: 'string',
|
||||
},
|
||||
{
|
||||
label: 'Number of Hotels in City',
|
||||
name: 'number_of_hotels_in_city',
|
||||
widget: 'number',
|
||||
},
|
||||
{
|
||||
label: 'City Locations',
|
||||
name: 'city_locations',
|
||||
widget: 'list',
|
||||
fields: [
|
||||
{
|
||||
label: 'Hotel Name',
|
||||
name: 'hotel_name',
|
||||
widget: 'string',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
describe('Test Backend Editorial Workflow', () => {
|
||||
after(() => {
|
||||
cy.task('teardownBackend', { backend: 'test' });
|
||||
});
|
||||
|
||||
before(() => {
|
||||
Cypress.config('defaultCommandTimeout', 4000);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
cy.task('setupBackend', { backend: 'test' });
|
||||
});
|
||||
|
||||
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 validate deeply nested list fields', () => {
|
||||
cy.task('updateConfig', nestedListConfig);
|
||||
|
||||
login();
|
||||
validateNestedListFieldsAndExit(setting2);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,38 @@
|
||||
import fixture from './common/i18n_editorial_workflow_spec';
|
||||
|
||||
const backend = 'test';
|
||||
|
||||
describe(`I18N Test Backend Editorial Workflow`, () => {
|
||||
const taskResult = { data: {} };
|
||||
|
||||
before(() => {
|
||||
Cypress.config('defaultCommandTimeout', 4000);
|
||||
cy.task('setupBackend', {
|
||||
backend,
|
||||
options: {
|
||||
publish_mode: 'editorial_workflow',
|
||||
i18n: {
|
||||
locales: ['en', 'de', 'fr'],
|
||||
},
|
||||
collections: [
|
||||
{
|
||||
folder: 'content/i18n',
|
||||
i18n: true,
|
||||
fields: [{ i18n: true }, {}, { i18n: 'duplicate' }],
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
after(() => {
|
||||
cy.task('teardownBackend', { backend });
|
||||
});
|
||||
|
||||
const entry = {
|
||||
title: 'first title',
|
||||
body: 'first body',
|
||||
};
|
||||
|
||||
fixture({ entry, getUser: () => taskResult.data.user });
|
||||
});
|
||||
@@ -0,0 +1,148 @@
|
||||
import * as specUtils from './common/spec_utils';
|
||||
import { login } from '../utils/steps';
|
||||
import { createEntryTranslateAndPublish } from './common/i18n';
|
||||
|
||||
const backend = 'proxy';
|
||||
const mode = 'fs';
|
||||
|
||||
const expectedEnContent = `---
|
||||
template: post
|
||||
title: first title
|
||||
date: 1970-01-01T01:00
|
||||
description: first description
|
||||
category: first category
|
||||
tags:
|
||||
- tag1
|
||||
---
|
||||
`;
|
||||
|
||||
const expectedDeContent = `---
|
||||
title: de
|
||||
date: 1970-01-01T01:00
|
||||
---
|
||||
`;
|
||||
|
||||
const expectedFrContent = `---
|
||||
title: fr
|
||||
date: 1970-01-01T01:00
|
||||
---
|
||||
`;
|
||||
|
||||
const contentSingleFile = `---
|
||||
en:
|
||||
template: post
|
||||
date: 1970-01-01T01:00
|
||||
title: first title
|
||||
body: first body
|
||||
description: first description
|
||||
category: first category
|
||||
tags:
|
||||
- tag1
|
||||
de:
|
||||
date: 1970-01-01T01:00
|
||||
title: de
|
||||
fr:
|
||||
date: 1970-01-01T01:00
|
||||
title: fr
|
||||
---
|
||||
`;
|
||||
|
||||
describe(`I18N Proxy Backend Simple Workflow - '${mode}' mode`, () => {
|
||||
const taskResult = { data: {} };
|
||||
|
||||
const entry = {
|
||||
title: 'first title',
|
||||
body: 'first body',
|
||||
description: 'first description',
|
||||
category: 'first category',
|
||||
tags: 'tag1',
|
||||
};
|
||||
|
||||
before(() => {
|
||||
specUtils.before(
|
||||
taskResult,
|
||||
{
|
||||
mode,
|
||||
publish_mode: 'simple',
|
||||
i18n: {
|
||||
locales: ['en', 'de', 'fr'],
|
||||
},
|
||||
collections: [{ i18n: true, fields: [{}, { i18n: true }, {}, { i18n: 'duplicate' }] }],
|
||||
},
|
||||
backend,
|
||||
);
|
||||
Cypress.config('taskTimeout', 15 * 1000);
|
||||
Cypress.config('defaultCommandTimeout', 5 * 1000);
|
||||
});
|
||||
|
||||
after(() => {
|
||||
specUtils.after(taskResult, backend);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
specUtils.beforeEach(taskResult, backend);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
specUtils.afterEach(taskResult, backend);
|
||||
});
|
||||
|
||||
it('can create entry with translation in locale_folders mode', () => {
|
||||
cy.task('updateConfig', { i18n: { structure: 'multiple_folders' } });
|
||||
|
||||
login(taskResult.data.user);
|
||||
|
||||
createEntryTranslateAndPublish(entry);
|
||||
|
||||
cy.readFile(`${taskResult.data.tempDir}/content/posts/en/1970-01-01-first-title.md`).should(
|
||||
'contain',
|
||||
expectedEnContent,
|
||||
);
|
||||
|
||||
cy.readFile(`${taskResult.data.tempDir}/content/posts/de/1970-01-01-first-title.md`).should(
|
||||
'eq',
|
||||
expectedDeContent,
|
||||
);
|
||||
|
||||
cy.readFile(`${taskResult.data.tempDir}/content/posts/fr/1970-01-01-first-title.md`).should(
|
||||
'eq',
|
||||
expectedFrContent,
|
||||
);
|
||||
});
|
||||
|
||||
it('can create entry with translation in single_file mode', () => {
|
||||
cy.task('updateConfig', { i18n: { structure: 'multiple_files' } });
|
||||
|
||||
login(taskResult.data.user);
|
||||
|
||||
createEntryTranslateAndPublish(entry);
|
||||
|
||||
cy.readFile(`${taskResult.data.tempDir}/content/posts/1970-01-01-first-title.en.md`).should(
|
||||
'contain',
|
||||
expectedEnContent,
|
||||
);
|
||||
|
||||
cy.readFile(`${taskResult.data.tempDir}/content/posts/1970-01-01-first-title.de.md`).should(
|
||||
'eq',
|
||||
expectedDeContent,
|
||||
);
|
||||
|
||||
cy.readFile(`${taskResult.data.tempDir}/content/posts/1970-01-01-first-title.fr.md`).should(
|
||||
'eq',
|
||||
expectedFrContent,
|
||||
);
|
||||
});
|
||||
|
||||
it('can create entry with translation in locale_file_extensions mode', () => {
|
||||
cy.task('updateConfig', { i18n: { structure: 'single_file' } });
|
||||
|
||||
login(taskResult.data.user);
|
||||
|
||||
createEntryTranslateAndPublish(entry);
|
||||
|
||||
cy.readFile(`${taskResult.data.tempDir}/content/posts/1970-01-01-first-title.md`).should(
|
||||
'eq',
|
||||
contentSingleFile,
|
||||
);
|
||||
});
|
||||
});
|
||||
94
source/admin/cypress/e2e/markdown_widget_backspace_spec.js
Normal file
94
source/admin/cypress/e2e/markdown_widget_backspace_spec.js
Normal file
@@ -0,0 +1,94 @@
|
||||
import '../utils/dismiss-local-backup';
|
||||
|
||||
describe('Markdown widget', () => {
|
||||
|
||||
before(() => {
|
||||
Cypress.config('defaultCommandTimeout', 4000);
|
||||
cy.task('setupBackend', { backend: 'test' });
|
||||
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
cy.loginAndNewPost();
|
||||
cy.clearMarkdownEditorContent();
|
||||
});
|
||||
|
||||
after(() => {
|
||||
cy.task('teardownBackend', { backend: 'test' });
|
||||
});
|
||||
|
||||
// describe('pressing backspace', () => {
|
||||
it('sets non-default block to default when empty', () => {
|
||||
cy.focused()
|
||||
.clickHeadingOneButton()
|
||||
.backspace()
|
||||
.confirmMarkdownEditorContent(`
|
||||
<p></p>
|
||||
`);
|
||||
});
|
||||
it('moves to previous block when no character left to delete', () => {
|
||||
cy.focused()
|
||||
.type('foo')
|
||||
.enter()
|
||||
.clickHeadingOneButton()
|
||||
.type('a')
|
||||
.backspace({times: 2})
|
||||
.confirmMarkdownEditorContent(`
|
||||
<p>foo</p>
|
||||
`);
|
||||
});
|
||||
it('does nothing at start of first block in document when non-empty and non-default', () => {
|
||||
cy.focused()
|
||||
.clickHeadingOneButton()
|
||||
.type('foo')
|
||||
.setCursorBefore('foo')
|
||||
.backspace({ times: 4 })
|
||||
.confirmMarkdownEditorContent(`
|
||||
<h1>foo</h1>
|
||||
`);
|
||||
});
|
||||
it('deletes individual characters in middle of non-empty non-default block in document', () => {
|
||||
cy.focused()
|
||||
.clickHeadingOneButton()
|
||||
.type('foo')
|
||||
.setCursorAfter('fo')
|
||||
.backspace({ times: 3 })
|
||||
.confirmMarkdownEditorContent(`
|
||||
<h1>o</h1>
|
||||
`);
|
||||
});
|
||||
it('at beginning of non-first block, moves default block content to previous block', () => {
|
||||
cy.focused()
|
||||
.clickHeadingOneButton()
|
||||
.type('foo')
|
||||
.enter()
|
||||
.type('bar')
|
||||
.setCursorBefore('bar')
|
||||
.backspace()
|
||||
.confirmMarkdownEditorContent(`
|
||||
<h1>foobar</h1>
|
||||
`);
|
||||
});
|
||||
it('at beginning of non-first block, moves non-default block content to previous block', () => {
|
||||
cy.focused()
|
||||
.type('foo')
|
||||
.enter()
|
||||
.clickHeadingOneButton()
|
||||
.type('bar')
|
||||
.enter()
|
||||
.clickHeadingTwoButton()
|
||||
.type('baz')
|
||||
.setCursorBefore('baz')
|
||||
.backspace()
|
||||
.confirmMarkdownEditorContent(`
|
||||
<p>foo</p>
|
||||
<h1>barbaz</h1>
|
||||
`)
|
||||
.setCursorBefore('bar')
|
||||
.backspace()
|
||||
.confirmMarkdownEditorContent(`
|
||||
<p>foobarbaz</p>
|
||||
`);
|
||||
// });
|
||||
});
|
||||
});
|
||||
136
source/admin/cypress/e2e/markdown_widget_code_block_spec.js
Normal file
136
source/admin/cypress/e2e/markdown_widget_code_block_spec.js
Normal file
@@ -0,0 +1,136 @@
|
||||
import { oneLineTrim, stripIndent } from 'common-tags';
|
||||
import '../utils/dismiss-local-backup';
|
||||
|
||||
describe('Markdown widget code block', () => {
|
||||
before(() => {
|
||||
Cypress.config('defaultCommandTimeout', 4000);
|
||||
cy.task('setupBackend', { backend: 'test' });
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
cy.loginAndNewPost();
|
||||
cy.clearMarkdownEditorContent();
|
||||
});
|
||||
|
||||
after(() => {
|
||||
cy.task('teardownBackend', { backend: 'test' });
|
||||
});
|
||||
describe('code block', () => {
|
||||
it('outputs code', () => {
|
||||
// eslint-disable-next-line cypress/no-unnecessary-waiting
|
||||
cy
|
||||
.insertCodeBlock()
|
||||
.type('foo')
|
||||
.enter()
|
||||
.type('bar')
|
||||
.confirmMarkdownEditorContent(
|
||||
`
|
||||
${codeBlock(`
|
||||
foo
|
||||
bar
|
||||
`)}
|
||||
`,
|
||||
)
|
||||
.wait(500)
|
||||
.clickModeToggle().confirmMarkdownEditorContent(`
|
||||
${codeBlockRaw(`
|
||||
foo
|
||||
bar
|
||||
`)}
|
||||
`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function codeBlockRaw(content) {
|
||||
return ['```', ...stripIndent(content).split('\n'), '```']
|
||||
.map(
|
||||
line => oneLineTrim`
|
||||
<div>
|
||||
<span>
|
||||
<span>
|
||||
<span>${line}</span>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
`,
|
||||
)
|
||||
.join('');
|
||||
}
|
||||
|
||||
function codeBlock(content) {
|
||||
const lines = stripIndent(content)
|
||||
.split('\n')
|
||||
.map(
|
||||
(line, idx) => `
|
||||
<div>
|
||||
<div>
|
||||
<div>${idx + 1}</div>
|
||||
</div>
|
||||
<pre><span>${line}</span></pre>
|
||||
</div>
|
||||
`,
|
||||
)
|
||||
.join('');
|
||||
|
||||
return oneLineTrim`
|
||||
<div>
|
||||
<div></div>
|
||||
<div>
|
||||
<div>
|
||||
<div><label>Code Block</label></div>
|
||||
<div><button><span><svg>
|
||||
<path></path>
|
||||
</svg></span></button>
|
||||
<div>
|
||||
<div>
|
||||
<div><textarea></textarea></div>
|
||||
<div>
|
||||
<div></div>
|
||||
</div>
|
||||
<div>
|
||||
<div></div>
|
||||
</div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div>
|
||||
<div>
|
||||
<div>
|
||||
<div>
|
||||
<div>
|
||||
<div>
|
||||
<pre><span>xxxxxxxxxx</span></pre>
|
||||
</div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div>
|
||||
<div> </div>
|
||||
</div>
|
||||
<div>
|
||||
${lines}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div></div>
|
||||
<div>
|
||||
<div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<span>
|
||||
<span>
|
||||
<span></span>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div></div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
109
source/admin/cypress/e2e/markdown_widget_enter_spec.js
Normal file
109
source/admin/cypress/e2e/markdown_widget_enter_spec.js
Normal file
@@ -0,0 +1,109 @@
|
||||
import '../utils/dismiss-local-backup';
|
||||
|
||||
describe('Markdown widget breaks', () => {
|
||||
before(() => {
|
||||
Cypress.config('defaultCommandTimeout', 4000);
|
||||
cy.task('setupBackend', { backend: 'test' });
|
||||
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
cy.loginAndNewPost();
|
||||
cy.clearMarkdownEditorContent();
|
||||
});
|
||||
|
||||
after(() => {
|
||||
cy.task('teardownBackend', { backend: 'test' });
|
||||
});
|
||||
|
||||
describe('pressing enter', () => {
|
||||
it('creates new default block from empty block', () => {
|
||||
cy.focused()
|
||||
.enter()
|
||||
.confirmMarkdownEditorContent(`
|
||||
<p></p>
|
||||
<p></p>
|
||||
`);
|
||||
});
|
||||
it('creates new default block when selection collapsed at end of block', () => {
|
||||
cy.focused()
|
||||
.type('foo')
|
||||
.enter()
|
||||
.confirmMarkdownEditorContent(`
|
||||
<p>foo</p>
|
||||
<p></p>
|
||||
`);
|
||||
});
|
||||
it('creates new default block when selection collapsed at end of non-default block', () => {
|
||||
cy.clickHeadingOneButton()
|
||||
.type('foo')
|
||||
.enter()
|
||||
.confirmMarkdownEditorContent(`
|
||||
<h1>foo</h1>
|
||||
<p></p>
|
||||
`);
|
||||
});
|
||||
it('creates new default block when selection collapsed in empty non-default block', () => {
|
||||
cy.clickHeadingOneButton()
|
||||
.enter()
|
||||
.confirmMarkdownEditorContent(`
|
||||
<h1></h1>
|
||||
<p></p>
|
||||
`);
|
||||
});
|
||||
it('splits block into two same-type blocks when collapsed selection at block start', () => {
|
||||
cy.clickHeadingOneButton()
|
||||
.type('foo')
|
||||
.setCursorBefore('foo')
|
||||
.enter()
|
||||
.confirmMarkdownEditorContent(`
|
||||
<h1></h1>
|
||||
<h1>foo</h1>
|
||||
`);
|
||||
});
|
||||
it('splits block into two same-type blocks when collapsed in middle of selection at block start', () => {
|
||||
cy.clickHeadingOneButton()
|
||||
.type('foo')
|
||||
.setCursorBefore('oo')
|
||||
.enter()
|
||||
.confirmMarkdownEditorContent(`
|
||||
<h1>f</h1>
|
||||
<h1>oo</h1>
|
||||
`);
|
||||
});
|
||||
it('deletes selected content and splits to same-type block when selection is expanded', () => {
|
||||
cy.clickHeadingOneButton()
|
||||
.type('foo bar')
|
||||
.setSelection('o b')
|
||||
.enter()
|
||||
.confirmMarkdownEditorContent(`
|
||||
<h1>fo</h1>
|
||||
<h1>ar</h1>
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('pressing shift+enter', () => {
|
||||
it('creates line break', () => {
|
||||
cy.focused()
|
||||
.enter({ shift: true })
|
||||
.confirmMarkdownEditorContent(`
|
||||
<p>
|
||||
<br>
|
||||
</p>
|
||||
`);
|
||||
});
|
||||
it('creates consecutive line break', () => {
|
||||
cy.focused()
|
||||
.enter({ shift: true, times: 4 })
|
||||
.confirmMarkdownEditorContent(`
|
||||
<p>
|
||||
<br>
|
||||
<br>
|
||||
<br>
|
||||
<br>
|
||||
</p>
|
||||
`);
|
||||
});
|
||||
});
|
||||
});
|
||||
106
source/admin/cypress/e2e/markdown_widget_hotkeys_spec.js
Normal file
106
source/admin/cypress/e2e/markdown_widget_hotkeys_spec.js
Normal file
@@ -0,0 +1,106 @@
|
||||
import '../utils/dismiss-local-backup';
|
||||
import {HOT_KEY_MAP} from "../utils/constants";
|
||||
const headingNumberToWord = ['', 'one', 'two', 'three', 'four', 'five', 'six'];
|
||||
const isMac = Cypress.platform === 'darwin';
|
||||
const modifierKey = isMac ? '{meta}' : '{ctrl}';
|
||||
// eslint-disable-next-line func-style
|
||||
const replaceMod = (str) => str.replace(/mod\+/g, modifierKey).replace(/shift\+/g, '{shift}');
|
||||
|
||||
describe('Markdown widget hotkeys', () => {
|
||||
describe('hot keys', () => {
|
||||
before(() => {
|
||||
Cypress.config('defaultCommandTimeout', 4000);
|
||||
cy.task('setupBackend', { backend: 'test' });
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
cy.loginAndNewPost();
|
||||
cy.clearMarkdownEditorContent();
|
||||
cy.focused()
|
||||
.type('foo')
|
||||
.setSelection('foo').as('selection');
|
||||
});
|
||||
|
||||
after(() => {
|
||||
cy.task('teardownBackend', { backend: 'test' });
|
||||
});
|
||||
|
||||
describe('bold', () => {
|
||||
it('pressing mod+b bolds the text', () => {
|
||||
cy.get('@selection')
|
||||
.type(replaceMod(HOT_KEY_MAP['bold']))
|
||||
.confirmMarkdownEditorContent(`
|
||||
<p>
|
||||
<strong>foo</strong>
|
||||
</p>
|
||||
`)
|
||||
.type(replaceMod(HOT_KEY_MAP['bold']));
|
||||
});
|
||||
});
|
||||
|
||||
describe('italic', () => {
|
||||
it('pressing mod+i italicizes the text', () => {
|
||||
cy.get('@selection')
|
||||
.type(replaceMod(HOT_KEY_MAP['italic']))
|
||||
.confirmMarkdownEditorContent(`
|
||||
<p>
|
||||
<em>foo</em>
|
||||
</p>
|
||||
`)
|
||||
.type(replaceMod(HOT_KEY_MAP['italic']));
|
||||
});
|
||||
});
|
||||
|
||||
describe('strikethrough', () => {
|
||||
it('pressing mod+shift+s displays a strike through the text', () => {
|
||||
cy.get('@selection')
|
||||
.type(replaceMod(HOT_KEY_MAP['strikethrough']))
|
||||
.confirmMarkdownEditorContent(`
|
||||
<p>
|
||||
<s>foo</s>
|
||||
</p>
|
||||
`).type(replaceMod(HOT_KEY_MAP['strikethrough']));
|
||||
});
|
||||
});
|
||||
|
||||
describe('code', () => {
|
||||
it('pressing mod+shift+c displays a code block around the text', () => {
|
||||
cy.get('@selection')
|
||||
.type(replaceMod(HOT_KEY_MAP['code']))
|
||||
.confirmMarkdownEditorContent(`
|
||||
<p>
|
||||
<code>foo</code>
|
||||
</p>
|
||||
`).type(replaceMod(HOT_KEY_MAP['code']));
|
||||
});
|
||||
});
|
||||
|
||||
describe('link', () => {
|
||||
before(() => {
|
||||
|
||||
});
|
||||
it('pressing mod+k transforms the text to a link', () => {
|
||||
cy.window().then((win) => {
|
||||
cy.get('@selection')
|
||||
.type(replaceMod(HOT_KEY_MAP['link']))
|
||||
cy.stub(win, 'prompt').returns('https://google.com');
|
||||
cy.confirmMarkdownEditorContent('<p><a>foo</a></p>')
|
||||
.type(replaceMod(HOT_KEY_MAP['link']));
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
describe('headings', () => {
|
||||
for (let i = 1; i <= 6; i++) {
|
||||
it(`pressing mod+${i} transforms the text to a heading`, () => {
|
||||
cy.get('@selection')
|
||||
.type(replaceMod(HOT_KEY_MAP[`heading-${headingNumberToWord[i]}`]))
|
||||
.confirmMarkdownEditorContent(`<h${i}>foo</h${i}>`)
|
||||
.type(replaceMod(HOT_KEY_MAP[`heading-${headingNumberToWord[i]}`]))
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
70
source/admin/cypress/e2e/markdown_widget_link_spec.js
Normal file
70
source/admin/cypress/e2e/markdown_widget_link_spec.js
Normal file
@@ -0,0 +1,70 @@
|
||||
import '../utils/dismiss-local-backup';
|
||||
|
||||
describe('Markdown widget link', () => {
|
||||
before(() => {
|
||||
Cypress.config('defaultCommandTimeout', 4000);
|
||||
cy.task('setupBackend', { backend: 'test' });
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
cy.loginAndNewPost();
|
||||
cy.clearMarkdownEditorContent();
|
||||
});
|
||||
|
||||
after(() => {
|
||||
cy.task('teardownBackend', { backend: 'test' });
|
||||
});
|
||||
|
||||
describe('link', () => {
|
||||
it('can add a new valid link', () => {
|
||||
const link = 'https://www.decapcms.org/';
|
||||
cy.window().then(win => {
|
||||
cy.stub(win, 'prompt').returns(link);
|
||||
});
|
||||
cy.focused().clickLinkButton();
|
||||
|
||||
cy.confirmMarkdownEditorContent(`<p><a>${link}</a></p>`);
|
||||
// eslint-disable-next-line cypress/no-unnecessary-waiting
|
||||
cy.wait(300);
|
||||
cy.clickModeToggle();
|
||||
|
||||
cy.confirmRawEditorContent(`<${link}>`);
|
||||
});
|
||||
|
||||
it('can add a new invalid link', () => {
|
||||
const link = 'www.decapcms.org';
|
||||
cy.window().then(win => {
|
||||
cy.stub(win, 'prompt').returns(link);
|
||||
});
|
||||
cy.focused().clickLinkButton();
|
||||
|
||||
cy.confirmMarkdownEditorContent(`<p><a>${link}</a></p>`);
|
||||
// eslint-disable-next-line cypress/no-unnecessary-waiting
|
||||
cy.wait(300);
|
||||
cy.clickModeToggle();
|
||||
|
||||
cy.confirmRawEditorContent(`[${link}](${link})`);
|
||||
});
|
||||
|
||||
it('can select existing text as link', () => {
|
||||
const link = 'https://www.decapcms.org';
|
||||
cy.window().then(win => {
|
||||
cy.stub(win, 'prompt').returns(link);
|
||||
});
|
||||
|
||||
const text = 'Decap CMS';
|
||||
cy.focused()
|
||||
.getMarkdownEditor()
|
||||
.type(text)
|
||||
.setSelection(text)
|
||||
.clickLinkButton();
|
||||
|
||||
cy.confirmMarkdownEditorContent(`<p><a>${text}</a></p>`);
|
||||
// eslint-disable-next-line cypress/no-unnecessary-waiting
|
||||
cy.wait(300);
|
||||
cy.clickModeToggle();
|
||||
|
||||
cy.confirmRawEditorContent(`[${text}](${link})`);
|
||||
});
|
||||
});
|
||||
});
|
||||
736
source/admin/cypress/e2e/markdown_widget_list_spec.js
Normal file
736
source/admin/cypress/e2e/markdown_widget_list_spec.js
Normal file
@@ -0,0 +1,736 @@
|
||||
import '../utils/dismiss-local-backup';
|
||||
|
||||
describe('Markdown widget', () => {
|
||||
describe('list', () => {
|
||||
before(() => {
|
||||
Cypress.config('defaultCommandTimeout', 4000);
|
||||
cy.task('setupBackend', { backend: 'test' });
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
cy.loginAndNewPost();
|
||||
cy.clearMarkdownEditorContent();
|
||||
});
|
||||
|
||||
after(() => {
|
||||
cy.task('teardownBackend', { backend: 'test' });
|
||||
});
|
||||
|
||||
// describe('toolbar buttons', () => {
|
||||
it('creates and focuses empty list', () => {
|
||||
cy.clickUnorderedListButton().confirmMarkdownEditorContent(`
|
||||
<ul>
|
||||
<li>
|
||||
<p></p>
|
||||
</li>
|
||||
</ul>
|
||||
`);
|
||||
});
|
||||
|
||||
it('removes list', () => {
|
||||
cy.clickUnorderedListButton().clickUnorderedListButton().confirmMarkdownEditorContent(`
|
||||
<p></p>
|
||||
`);
|
||||
});
|
||||
|
||||
it('converts a list item to a paragraph block which is a sibling of the parent list', () => {
|
||||
cy.clickUnorderedListButton().type('foo').enter().clickUnorderedListButton()
|
||||
.confirmMarkdownEditorContent(`
|
||||
<ul>
|
||||
<li>
|
||||
<p>foo</p>
|
||||
</li>
|
||||
</ul>
|
||||
<p></p>
|
||||
`);
|
||||
});
|
||||
|
||||
it('converts empty nested list item to empty paragraph block in parent list item', () => {
|
||||
cy
|
||||
.clickUnorderedListButton()
|
||||
.type('foo')
|
||||
.enter()
|
||||
.tabkey()
|
||||
.type('bar')
|
||||
.enter()
|
||||
.tabkey()
|
||||
.confirmMarkdownEditorContent(
|
||||
`
|
||||
<ul>
|
||||
<li>
|
||||
<p>foo</p>
|
||||
<ul>
|
||||
<li>
|
||||
<p>bar</p>
|
||||
<ul>
|
||||
<li>
|
||||
<p></p>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
`,
|
||||
)
|
||||
.clickUnorderedListButton()
|
||||
.confirmMarkdownEditorContent(
|
||||
`
|
||||
<ul>
|
||||
<li>
|
||||
<p>foo</p>
|
||||
<ul>
|
||||
<li>
|
||||
<p>bar</p>
|
||||
<p></p>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
`,
|
||||
)
|
||||
.backspace({ times: 4 })
|
||||
.clickUnorderedListButton().confirmMarkdownEditorContent(`
|
||||
<ul>
|
||||
<li>
|
||||
<p>foo</p>
|
||||
<p></p>
|
||||
</li>
|
||||
</ul>
|
||||
`);
|
||||
});
|
||||
|
||||
it('moves nested list item content to parent list item when in first block', () => {
|
||||
cy
|
||||
.clickUnorderedListButton()
|
||||
.type('foo')
|
||||
.enter()
|
||||
.tabkey()
|
||||
.type('bar')
|
||||
.enter()
|
||||
.tabkey()
|
||||
.type('baz')
|
||||
.clickUnorderedListButton()
|
||||
.confirmMarkdownEditorContent(
|
||||
`
|
||||
<ul>
|
||||
<li>
|
||||
<p>foo</p>
|
||||
<ul>
|
||||
<li>
|
||||
<p>bar</p>
|
||||
<p>baz</p>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
`,
|
||||
)
|
||||
.up()
|
||||
.clickUnorderedListButton()
|
||||
.confirmMarkdownEditorContent(
|
||||
`
|
||||
<ul>
|
||||
<li>
|
||||
<p>foo</p>
|
||||
<p>bar</p>
|
||||
<p>baz</p>
|
||||
</li>
|
||||
</ul>
|
||||
`,
|
||||
)
|
||||
.up()
|
||||
.clickUnorderedListButton().confirmMarkdownEditorContent(`
|
||||
<p>foo</p>
|
||||
<p>bar</p>
|
||||
<p>baz</p>
|
||||
`);
|
||||
});
|
||||
|
||||
it('affects only the current block with collapsed selection', () => {
|
||||
cy
|
||||
.focused()
|
||||
.type('foo')
|
||||
.enter()
|
||||
.type('bar')
|
||||
.enter()
|
||||
.type('baz')
|
||||
.up()
|
||||
.clickUnorderedListButton().confirmMarkdownEditorContent(`
|
||||
<p>foo</p>
|
||||
<ul>
|
||||
<li>
|
||||
<p>bar</p>
|
||||
</li>
|
||||
</ul>
|
||||
<p>baz</p>
|
||||
`);
|
||||
});
|
||||
|
||||
it('wrap each bottom-most block in a selection with a list item block', () => {
|
||||
// eslint-disable-next-line cypress/no-unnecessary-waiting
|
||||
cy
|
||||
.focused()
|
||||
.type('foo')
|
||||
.enter()
|
||||
.type('bar')
|
||||
.enter()
|
||||
.type('baz')
|
||||
.setSelection('foo', 'baz')
|
||||
.wait(500)
|
||||
.clickUnorderedListButton().confirmMarkdownEditorContent(`
|
||||
<ul>
|
||||
<li>
|
||||
<p>foo</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>bar</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>baz</p>
|
||||
</li>
|
||||
</ul>
|
||||
`);
|
||||
});
|
||||
|
||||
it('unwraps list item block from each selected list item and unwraps all of them from the outer list block', () => {
|
||||
// eslint-disable-next-line cypress/no-unnecessary-waiting
|
||||
cy
|
||||
.clickUnorderedListButton()
|
||||
.type('foo')
|
||||
.enter()
|
||||
.type('bar')
|
||||
.enter()
|
||||
.type('baz')
|
||||
.setSelection('foo', 'baz')
|
||||
.wait(500)
|
||||
.clickUnorderedListButton().confirmMarkdownEditorContent(`
|
||||
<p>foo</p>
|
||||
<p>bar</p>
|
||||
<p>baz</p>
|
||||
`);
|
||||
});
|
||||
|
||||
it('combines adjacent same-typed lists, not differently typed lists', () => {
|
||||
cy.focused()
|
||||
.type('foo')
|
||||
.enter()
|
||||
.type('bar')
|
||||
.enter()
|
||||
.type('baz')
|
||||
.up()
|
||||
.clickUnorderedListButton()
|
||||
.up()
|
||||
.clickUnorderedListButton()
|
||||
.confirmMarkdownEditorContent(
|
||||
`
|
||||
<ul>
|
||||
<li>
|
||||
<p>foo</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>bar</p>
|
||||
</li>
|
||||
</ul>
|
||||
<p>baz</p>
|
||||
`,
|
||||
)
|
||||
.down({ times: 2 })
|
||||
.focused()
|
||||
.clickUnorderedListButton()
|
||||
.confirmMarkdownEditorContent(
|
||||
`
|
||||
<ul>
|
||||
<li>
|
||||
<p>foo</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>bar</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>baz</p>
|
||||
</li>
|
||||
</ul>
|
||||
`,
|
||||
)
|
||||
.up()
|
||||
.enter()
|
||||
.type('qux')
|
||||
.tabkey()
|
||||
.confirmMarkdownEditorContent(
|
||||
`
|
||||
<ul>
|
||||
<li>
|
||||
<p>foo</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>bar</p>
|
||||
<ul>
|
||||
<li>
|
||||
<p>qux</p>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<p>baz</p>
|
||||
</li>
|
||||
</ul>
|
||||
`,
|
||||
)
|
||||
.up()
|
||||
.enter()
|
||||
.type('quux')
|
||||
.confirmMarkdownEditorContent(
|
||||
`
|
||||
<ul>
|
||||
<li>
|
||||
<p>foo</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>bar</p>
|
||||
<ul>
|
||||
<li>
|
||||
<p>quux</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>qux</p>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<p>baz</p>
|
||||
</li>
|
||||
</ul>
|
||||
`,
|
||||
)
|
||||
.clickOrderedListButton()
|
||||
.confirmMarkdownEditorContent(
|
||||
`
|
||||
<ul>
|
||||
<li>
|
||||
<p>foo</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>bar</p>
|
||||
<ol>
|
||||
<li>
|
||||
<p>quux</p>
|
||||
</li>
|
||||
</ol>
|
||||
<ul>
|
||||
<li>
|
||||
<p>qux</p>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<p>baz</p>
|
||||
</li>
|
||||
</ul>
|
||||
`,
|
||||
)
|
||||
.setSelection({
|
||||
anchorQuery: 'ul > li > ol p',
|
||||
anchorOffset: 1,
|
||||
focusQuery: 'ul > li > ul:last-child p',
|
||||
focusOffset: 2,
|
||||
});
|
||||
});
|
||||
|
||||
// while this works on dev environment, it will always fail in cypress - has something to do with text selection
|
||||
// it('affects only selected list items', () => {
|
||||
// cy
|
||||
// .clickUnorderedListButton()
|
||||
// .type('foo')
|
||||
// .enter()
|
||||
// .type('bar')
|
||||
// .enter()
|
||||
// .type('baz')
|
||||
// .setSelection('bar')
|
||||
// .clickUnorderedListButton()
|
||||
// .confirmMarkdownEditorContent(
|
||||
// `
|
||||
// <ul>
|
||||
// <li>
|
||||
// <p>foo</p>
|
||||
// </li>
|
||||
// </ul>
|
||||
// <p>bar</p>
|
||||
// <ul>
|
||||
// <li>
|
||||
// <p>baz</p>
|
||||
// </li>
|
||||
// </ul>
|
||||
// `,
|
||||
// )
|
||||
// .clickUnorderedListButton()
|
||||
// .setSelection('bar', 'baz')
|
||||
// .clickUnorderedListButton()
|
||||
// .confirmMarkdownEditorContent(
|
||||
// `
|
||||
// <ul>
|
||||
// <li>
|
||||
// <p>foo</p>
|
||||
// </li>
|
||||
// </ul>
|
||||
// <p>bar</p>
|
||||
// <p>baz</p>
|
||||
// `,
|
||||
// )
|
||||
// .clickUnorderedListButton()
|
||||
// .confirmMarkdownEditorContent(
|
||||
// `
|
||||
// <ul>
|
||||
// <li>
|
||||
// <p>foo</p>
|
||||
// </li>
|
||||
// <li>
|
||||
// <p>bar</p>
|
||||
// </li>
|
||||
// <li>
|
||||
// <p>baz</p>
|
||||
// </li>
|
||||
// </ul>
|
||||
// `,
|
||||
// )
|
||||
// .setSelection('baz')
|
||||
// .clickUnorderedListButton()
|
||||
// .confirmMarkdownEditorContent(
|
||||
// `
|
||||
// <ul>
|
||||
// <li>
|
||||
// <p>foo</p>
|
||||
// </li>
|
||||
// <li>
|
||||
// <p>bar</p>
|
||||
// </li>
|
||||
// </ul>
|
||||
// <p>baz</p>
|
||||
// `,
|
||||
// )
|
||||
// .clickUnorderedListButton()
|
||||
// .tabkey()
|
||||
// .setCursorAfter('baz')
|
||||
// .enter()
|
||||
// .tabkey()
|
||||
// .type('qux')
|
||||
// .confirmMarkdownEditorContent(
|
||||
// `
|
||||
// <ul>
|
||||
// <li>
|
||||
// <p>foo</p>
|
||||
// </li>
|
||||
// <li>
|
||||
// <p>bar</p>
|
||||
// <ul>
|
||||
// <li>
|
||||
// <p>baz</p>
|
||||
// <ul>
|
||||
// <li>
|
||||
// <p>qux</p>
|
||||
// </li>
|
||||
// </ul>
|
||||
// </li>
|
||||
// </ul>
|
||||
// </li>
|
||||
// </ul>
|
||||
// `,
|
||||
// )
|
||||
// .setSelection('baz')
|
||||
// .clickOrderedListButton()
|
||||
// .confirmMarkdownEditorContent(
|
||||
// `
|
||||
// <ul>
|
||||
// <li>
|
||||
// <p>foo</p>
|
||||
// </li>
|
||||
// <li>
|
||||
// <p>bar</p>
|
||||
// <ol>
|
||||
// <li>
|
||||
// <p>baz</p>
|
||||
// <ul>
|
||||
// <li>
|
||||
// <p>qux</p>
|
||||
// </li>
|
||||
// </ul>
|
||||
// </li>
|
||||
// </ol>
|
||||
// </li>
|
||||
// </ul>
|
||||
// `,
|
||||
// )
|
||||
// .setCursorAfter('qux')
|
||||
// .enter({ times: 2 })
|
||||
// .clickUnorderedListButton()
|
||||
// .confirmMarkdownEditorContent(`
|
||||
// <ul>
|
||||
// <li>
|
||||
// <p>foo</p>
|
||||
// </li>
|
||||
// <li>
|
||||
// <p>bar</p>
|
||||
// <ol>
|
||||
// <li>
|
||||
// <p>baz</p>
|
||||
// <ul>
|
||||
// <li>
|
||||
// <p>qux</p>
|
||||
// </li>
|
||||
// </ul>
|
||||
// </li>
|
||||
// </ol>
|
||||
// <ul>
|
||||
// <li>
|
||||
// <p></p>
|
||||
// </li>
|
||||
// </ul>
|
||||
// </li>
|
||||
// </ul>
|
||||
// `);
|
||||
// });
|
||||
// });
|
||||
// });
|
||||
|
||||
// describe('on Enter', () => {
|
||||
it('removes the list item and list if empty', () => {
|
||||
cy.clickUnorderedListButton().enter().confirmMarkdownEditorContent(`
|
||||
<p></p>
|
||||
`);
|
||||
});
|
||||
|
||||
it('creates a new list item in a non-empty list', () => {
|
||||
cy
|
||||
.clickUnorderedListButton()
|
||||
.type('foo')
|
||||
.enter()
|
||||
.confirmMarkdownEditorContent(
|
||||
`
|
||||
<ul>
|
||||
<li>
|
||||
<p>foo</p>
|
||||
</li>
|
||||
<li>
|
||||
<p></p>
|
||||
</li>
|
||||
</ul>
|
||||
`,
|
||||
)
|
||||
.type('bar')
|
||||
.enter().confirmMarkdownEditorContent(`
|
||||
<ul>
|
||||
<li>
|
||||
<p>foo</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>bar</p>
|
||||
</li>
|
||||
<li>
|
||||
<p></p>
|
||||
</li>
|
||||
</ul>
|
||||
`);
|
||||
});
|
||||
|
||||
it('creates a new default block below a list when hitting Enter twice on an empty list item of the list', () => {
|
||||
cy.clickUnorderedListButton().type('foo').enter({ times: 2 }).confirmMarkdownEditorContent(`
|
||||
<ul>
|
||||
<li>
|
||||
<p>foo</p>
|
||||
</li>
|
||||
</ul>
|
||||
<p></p>
|
||||
`);
|
||||
});
|
||||
// });
|
||||
|
||||
// describe('on Backspace', () => {
|
||||
it('removes the list item and list if empty', () => {
|
||||
cy.clickUnorderedListButton().backspace().confirmMarkdownEditorContent(`
|
||||
<p></p>
|
||||
`);
|
||||
});
|
||||
|
||||
it('removes the list item if list not empty', () => {
|
||||
cy.clickUnorderedListButton().type('foo').enter().backspace().confirmMarkdownEditorContent(`
|
||||
<ul>
|
||||
<li>
|
||||
<p>foo</p>
|
||||
<p></p>
|
||||
</li>
|
||||
</ul>
|
||||
`);
|
||||
});
|
||||
|
||||
it('does not remove list item if empty with non-default block', () => {
|
||||
cy.clickUnorderedListButton().clickHeadingOneButton().backspace()
|
||||
.confirmMarkdownEditorContent(`
|
||||
<ul>
|
||||
<li>
|
||||
<p></p>
|
||||
</li>
|
||||
</ul>
|
||||
`);
|
||||
});
|
||||
// });
|
||||
|
||||
// describe('on Tab', () => {
|
||||
it('does nothing in top level list', () => {
|
||||
cy
|
||||
.clickUnorderedListButton()
|
||||
.tabkey()
|
||||
.confirmMarkdownEditorContent(
|
||||
`
|
||||
<ul>
|
||||
<li>
|
||||
<p></p>
|
||||
</li>
|
||||
</ul>
|
||||
`,
|
||||
)
|
||||
.type('foo')
|
||||
.tabkey().confirmMarkdownEditorContent(`
|
||||
<ul>
|
||||
<li>
|
||||
<p>foo</p>
|
||||
</li>
|
||||
</ul>
|
||||
`);
|
||||
});
|
||||
|
||||
it('indents nested list items', () => {
|
||||
cy
|
||||
.clickUnorderedListButton()
|
||||
.type('foo')
|
||||
.enter()
|
||||
.type('bar')
|
||||
.tabkey()
|
||||
.confirmMarkdownEditorContent(
|
||||
`
|
||||
<ul>
|
||||
<li>
|
||||
<p>foo</p>
|
||||
<ul>
|
||||
<li>
|
||||
<p>bar</p>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
`,
|
||||
)
|
||||
.enter()
|
||||
.tabkey().confirmMarkdownEditorContent(`
|
||||
<ul>
|
||||
<li>
|
||||
<p>foo</p>
|
||||
<ul>
|
||||
<li>
|
||||
<p>bar</p>
|
||||
<ul>
|
||||
<li>
|
||||
<p></p>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
`);
|
||||
});
|
||||
|
||||
it('only nests up to one level down from the parent list', () => {
|
||||
cy.clickUnorderedListButton().type('foo').enter().tabkey().confirmMarkdownEditorContent(`
|
||||
<ul>
|
||||
<li>
|
||||
<p>foo</p>
|
||||
<ul>
|
||||
<li>
|
||||
<p></p>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
`);
|
||||
});
|
||||
|
||||
it('unindents nested list items with shift', () => {
|
||||
cy.clickUnorderedListButton().type('foo').enter().tabkey().tabkey({ shift: true })
|
||||
.confirmMarkdownEditorContent(`
|
||||
<ul>
|
||||
<li>
|
||||
<p>foo</p>
|
||||
</li>
|
||||
<li>
|
||||
<p></p>
|
||||
</li>
|
||||
</ul>
|
||||
`);
|
||||
});
|
||||
|
||||
it('indents and unindents from one level below parent back to document root', () => {
|
||||
cy
|
||||
.clickUnorderedListButton()
|
||||
.type('foo')
|
||||
.enter()
|
||||
.tabkey()
|
||||
.type('bar')
|
||||
.enter()
|
||||
.tabkey()
|
||||
.type('baz')
|
||||
.confirmMarkdownEditorContent(
|
||||
`
|
||||
<ul>
|
||||
<li>
|
||||
<p>foo</p>
|
||||
<ul>
|
||||
<li>
|
||||
<p>bar</p>
|
||||
<ul>
|
||||
<li>
|
||||
<p>baz</p>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
`,
|
||||
)
|
||||
.tabkey({ shift: true })
|
||||
.confirmMarkdownEditorContent(
|
||||
`
|
||||
<ul>
|
||||
<li>
|
||||
<p>foo</p>
|
||||
<ul>
|
||||
<li>
|
||||
<p>bar</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>baz</p>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
`,
|
||||
)
|
||||
.tabkey({ shift: true }).confirmMarkdownEditorContent(`
|
||||
<ul>
|
||||
<li>
|
||||
<p>foo</p>
|
||||
<ul>
|
||||
<li>
|
||||
<p>bar</p>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<p>baz</p>
|
||||
</li>
|
||||
</ul>
|
||||
`);
|
||||
});
|
||||
// });
|
||||
});
|
||||
});
|
||||
37
source/admin/cypress/e2e/markdown_widget_marks_spec.js
Normal file
37
source/admin/cypress/e2e/markdown_widget_marks_spec.js
Normal file
@@ -0,0 +1,37 @@
|
||||
import '../utils/dismiss-local-backup';
|
||||
|
||||
describe('Markdown widget', () => {
|
||||
describe('code mark', () => {
|
||||
before(() => {
|
||||
Cypress.config('defaultCommandTimeout', 4000);
|
||||
cy.task('setupBackend', { backend: 'test' });
|
||||
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
cy.loginAndNewPost();
|
||||
cy.clearMarkdownEditorContent();
|
||||
});
|
||||
|
||||
after(() => {
|
||||
cy.task('teardownBackend', { backend: 'test' });
|
||||
});
|
||||
|
||||
describe('toolbar button', () => {
|
||||
it('can combine code mark with other marks', () => {
|
||||
cy.clickItalicButton()
|
||||
.type('foo')
|
||||
.setSelection('oo')
|
||||
.clickCodeButton()
|
||||
.confirmMarkdownEditorContent(`
|
||||
<p>
|
||||
<em>f</em>
|
||||
<code>
|
||||
<em>oo</em>
|
||||
</code>
|
||||
</p>
|
||||
`);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
360
source/admin/cypress/e2e/markdown_widget_quote_spec.js
Normal file
360
source/admin/cypress/e2e/markdown_widget_quote_spec.js
Normal file
@@ -0,0 +1,360 @@
|
||||
import '../utils/dismiss-local-backup';
|
||||
|
||||
describe('Markdown widget', () => {
|
||||
describe('quote block', () => {
|
||||
before(() => {
|
||||
Cypress.config('defaultCommandTimeout', 4000);
|
||||
cy.task('setupBackend', { backend: 'test' });
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
cy.loginAndNewPost();
|
||||
cy.clearMarkdownEditorContent();
|
||||
});
|
||||
|
||||
after(() => {
|
||||
cy.task('teardownBackend', { backend: 'test' });
|
||||
});
|
||||
|
||||
// describe('toggle quote', () => {
|
||||
it('toggles empty quote block on and off in empty editor', () => {
|
||||
cy.clickQuoteButton()
|
||||
.confirmMarkdownEditorContent(`
|
||||
<blockquote>
|
||||
<p></p>
|
||||
</blockquote>
|
||||
`)
|
||||
.clickQuoteButton()
|
||||
.confirmMarkdownEditorContent(`
|
||||
<p></p>
|
||||
`);
|
||||
});
|
||||
it('toggles empty quote block on and off for current block', () => {
|
||||
cy.focused()
|
||||
.type('foo')
|
||||
.clickQuoteButton()
|
||||
.confirmMarkdownEditorContent(`
|
||||
<blockquote>
|
||||
<p>foo</p>
|
||||
</blockquote>
|
||||
`)
|
||||
.clickQuoteButton()
|
||||
.confirmMarkdownEditorContent(`
|
||||
<p>foo</p>
|
||||
`);
|
||||
});
|
||||
it('toggles entire quote block without expanded selection', () => {
|
||||
cy.clickQuoteButton()
|
||||
.type('foo')
|
||||
.enter()
|
||||
.type('bar')
|
||||
.clickQuoteButton()
|
||||
.confirmMarkdownEditorContent(`
|
||||
<p>foo</p>
|
||||
<p>bar</p>
|
||||
`);
|
||||
});
|
||||
it('toggles entire quote block with complex content', () => {
|
||||
cy.clickQuoteButton()
|
||||
.clickUnorderedListButton()
|
||||
.clickHeadingOneButton()
|
||||
.type('foo')
|
||||
.enter({ times: 2 }) // First Enter creates new list item. Second Enter turns that list item into a default block.
|
||||
.clickQuoteButton() // Unwrap the quote block.
|
||||
.confirmMarkdownEditorContent(`
|
||||
<ul>
|
||||
<li>
|
||||
<h1>foo</h1>
|
||||
</li>
|
||||
</ul>
|
||||
<p></p>
|
||||
`);
|
||||
});
|
||||
it('toggles empty quote block on and off for selected blocks', () => {
|
||||
// eslint-disable-next-line cypress/no-unnecessary-waiting
|
||||
cy.focused()
|
||||
.type('foo')
|
||||
.enter()
|
||||
.type('bar')
|
||||
.setSelection('foo', 'bar')
|
||||
.wait(500)
|
||||
.clickQuoteButton()
|
||||
.confirmMarkdownEditorContent(`
|
||||
<blockquote>
|
||||
<p>foo</p>
|
||||
<p>bar</p>
|
||||
</blockquote>
|
||||
`)
|
||||
.clickQuoteButton()
|
||||
.confirmMarkdownEditorContent(`
|
||||
<p>foo</p>
|
||||
<p>bar</p>
|
||||
`)
|
||||
.clickQuoteButton()
|
||||
.confirmMarkdownEditorContent(`
|
||||
<blockquote>
|
||||
<p>foo</p>
|
||||
<p>bar</p>
|
||||
</blockquote>
|
||||
`);
|
||||
});
|
||||
it('toggles empty quote block on and off for partially selected blocks', () => {
|
||||
// eslint-disable-next-line cypress/no-unnecessary-waiting
|
||||
cy.focused()
|
||||
.type('foo')
|
||||
.enter()
|
||||
.type('bar')
|
||||
.setSelection('oo', 'ba')
|
||||
.wait(500)
|
||||
.clickQuoteButton()
|
||||
.confirmMarkdownEditorContent(`
|
||||
<blockquote>
|
||||
<p>foo</p>
|
||||
<p>bar</p>
|
||||
</blockquote>
|
||||
`)
|
||||
.clickQuoteButton()
|
||||
.confirmMarkdownEditorContent(`
|
||||
<p>foo</p>
|
||||
<p>bar</p>
|
||||
`)
|
||||
.clickQuoteButton()
|
||||
.confirmMarkdownEditorContent(`
|
||||
<blockquote>
|
||||
<p>foo</p>
|
||||
<p>bar</p>
|
||||
</blockquote>
|
||||
`);
|
||||
});
|
||||
it('toggles quote block on and off for multiple selected list items', () => {
|
||||
// eslint-disable-next-line cypress/no-unnecessary-waiting
|
||||
cy.focused()
|
||||
.clickUnorderedListButton()
|
||||
.type('foo')
|
||||
.enter()
|
||||
.type('bar')
|
||||
.setSelection('foo', 'bar')
|
||||
.wait(500)
|
||||
.clickQuoteButton()
|
||||
.confirmMarkdownEditorContent(`
|
||||
<blockquote>
|
||||
<ul>
|
||||
<li>
|
||||
<p>foo</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>bar</p>
|
||||
</li>
|
||||
</ul>
|
||||
</blockquote>
|
||||
`)
|
||||
.clickQuoteButton()
|
||||
.confirmMarkdownEditorContent(`
|
||||
<ul>
|
||||
<li>
|
||||
<p>foo</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>bar</p>
|
||||
</li>
|
||||
</ul>
|
||||
`)
|
||||
.setCursorAfter('bar')
|
||||
.wait(500)
|
||||
.enter()
|
||||
.type('baz')
|
||||
.setSelection('bar', 'baz')
|
||||
.wait(500)
|
||||
.clickQuoteButton()
|
||||
.confirmMarkdownEditorContent(`
|
||||
<ul>
|
||||
<li>
|
||||
<p>foo</p>
|
||||
</li>
|
||||
</ul>
|
||||
<blockquote>
|
||||
<ul>
|
||||
<li>
|
||||
<p>bar</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>baz</p>
|
||||
</li>
|
||||
</ul>
|
||||
</blockquote>
|
||||
`)
|
||||
});
|
||||
it('creates new quote block if parent is not a quote, can deeply nest', () => {
|
||||
cy.clickQuoteButton()
|
||||
.clickUnorderedListButton()
|
||||
.clickQuoteButton()
|
||||
.clickUnorderedListButton()
|
||||
.clickQuoteButton()
|
||||
.clickUnorderedListButton()
|
||||
.clickQuoteButton()
|
||||
.type('foo')
|
||||
// Content should contains 4 <blockquote> tags and 3 <ul> tags
|
||||
.confirmMarkdownEditorContent(`
|
||||
<blockquote>
|
||||
<ul>
|
||||
<li>
|
||||
<blockquote>
|
||||
<ul>
|
||||
<li>
|
||||
<blockquote>
|
||||
<ul>
|
||||
<li>
|
||||
<blockquote>
|
||||
<p>foo</p>
|
||||
</blockquote>
|
||||
</li>
|
||||
</ul>
|
||||
</blockquote>
|
||||
</li>
|
||||
</ul>
|
||||
</blockquote>
|
||||
</li>
|
||||
</ul>
|
||||
</blockquote>
|
||||
`)
|
||||
/*
|
||||
* First Enter creates new paragraph within the innermost block quote.
|
||||
* Second Enter moves that paragraph one level up to become sibling of the previous quote block and direct child of a list item.
|
||||
* Third Enter to turn that paragraph into a list item and move it one level up.
|
||||
* Repeat the circle for three more times to reach the second list item of the outermost list block.
|
||||
* Then Enter again to turn that list item into a paragraph and move it one level up to become sibling of the outermost list and
|
||||
* direct child of the outermost block quote.
|
||||
*/
|
||||
.enter({ times: 10 })
|
||||
.type('bar')
|
||||
.confirmMarkdownEditorContent(`
|
||||
<blockquote>
|
||||
<ul>
|
||||
<li>
|
||||
<blockquote>
|
||||
<ul>
|
||||
<li>
|
||||
<blockquote>
|
||||
<ul>
|
||||
<li>
|
||||
<blockquote>
|
||||
<p>foo</p>
|
||||
</blockquote>
|
||||
</li>
|
||||
</ul>
|
||||
</blockquote>
|
||||
</li>
|
||||
</ul>
|
||||
</blockquote>
|
||||
</li>
|
||||
</ul>
|
||||
<p>bar</p>
|
||||
</blockquote>
|
||||
`)
|
||||
/* The GOAL is to delete all the text content inside this deeply nested block quote and turn it into a default paragraph block on top level.
|
||||
* We need:
|
||||
* 3 Backspace to delete the word “bar”.
|
||||
* 1 Backspace to remove the paragraph that contains bar and bring cursor to the end of the unordered list which is direct child of the outermost block quote.
|
||||
* 3 Backspace to remove the word “foo”.
|
||||
* 1 Backspace to remove the current block quote that the cursor is on, 1 Backspace to remove the list that wraps the block quote. Repeat this step for three times for a total of 6 Backspace until the cursor is on the outermost block quote.
|
||||
* 1 Backspace to remove to toggle off the outermost block quote and turn it into a default paragraph.
|
||||
* Total Backspaces required: 3 + 1 + 3 + ((1 + 1) * 3) + 1 = 14
|
||||
*/
|
||||
.backspace({ times: 14 })
|
||||
});
|
||||
// });
|
||||
|
||||
// describe('backspace inside quote', () => {
|
||||
it('joins two paragraphs', () => {
|
||||
cy.clickQuoteButton()
|
||||
.type('foo')
|
||||
.enter()
|
||||
.type('bar')
|
||||
.setCursorBefore('bar')
|
||||
.backspace()
|
||||
.confirmMarkdownEditorContent(`
|
||||
<blockquote>
|
||||
<p>foobar</p>
|
||||
</blockquote>
|
||||
`);
|
||||
});
|
||||
it('joins quote with previous quote', () => {
|
||||
cy.clickQuoteButton()
|
||||
.type('foo')
|
||||
.enter({ times: 2 })
|
||||
.clickQuoteButton()
|
||||
.type('bar')
|
||||
.confirmMarkdownEditorContent(`
|
||||
<blockquote>
|
||||
<p>foo</p>
|
||||
</blockquote>
|
||||
<blockquote>
|
||||
<p>bar</p>
|
||||
</blockquote>
|
||||
`)
|
||||
.setCursorBefore('bar')
|
||||
.backspace()
|
||||
.confirmMarkdownEditorContent(`
|
||||
<blockquote>
|
||||
<p>foo</p>
|
||||
<p>bar</p>
|
||||
</blockquote>
|
||||
`);
|
||||
});
|
||||
it('removes first block from quote when focused at first block at start', () => {
|
||||
// eslint-disable-next-line cypress/no-unnecessary-waiting
|
||||
cy.clickQuoteButton()
|
||||
.type('foo')
|
||||
.enter()
|
||||
.type('bar')
|
||||
.setCursorBefore('foo')
|
||||
.wait(500)
|
||||
.backspace()
|
||||
.confirmMarkdownEditorContent(`
|
||||
<p>foo</p>
|
||||
<blockquote>
|
||||
<p>bar</p>
|
||||
</blockquote>
|
||||
`)
|
||||
});
|
||||
// });
|
||||
|
||||
// describe('enter inside quote', () => {
|
||||
it('creates new block inside quote', () => {
|
||||
// eslint-disable-next-line cypress/no-unnecessary-waiting
|
||||
cy.clickQuoteButton()
|
||||
.type('foo')
|
||||
.enter()
|
||||
.confirmMarkdownEditorContent(`
|
||||
<blockquote>
|
||||
<p>foo</p>
|
||||
<p></p>
|
||||
</blockquote>
|
||||
`)
|
||||
.type('bar')
|
||||
.setCursorAfter('ba')
|
||||
.wait(500)
|
||||
.enter()
|
||||
.confirmMarkdownEditorContent(`
|
||||
<blockquote>
|
||||
<p>foo</p>
|
||||
<p>ba</p>
|
||||
<p>r</p>
|
||||
</blockquote>
|
||||
`);
|
||||
});
|
||||
it('creates new block after quote from empty last block', () => {
|
||||
cy.clickQuoteButton()
|
||||
.type('foo')
|
||||
.enter()
|
||||
.enter()
|
||||
.confirmMarkdownEditorContent(`
|
||||
<blockquote>
|
||||
<p>foo</p>
|
||||
</blockquote>
|
||||
<p></p>
|
||||
`)
|
||||
});
|
||||
// });
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,27 @@
|
||||
import fixture from './common/media_library';
|
||||
import { entry1 } from './common/entries';
|
||||
import * as specUtils from './common/spec_utils';
|
||||
|
||||
const backend = 'bitbucket';
|
||||
|
||||
describe('BitBucket Backend Media Library - REST API', () => {
|
||||
let taskResult = { data: {} };
|
||||
|
||||
before(() => {
|
||||
specUtils.before(taskResult, {}, backend);
|
||||
});
|
||||
|
||||
after(() => {
|
||||
specUtils.after(taskResult, backend);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
specUtils.beforeEach(taskResult, backend);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
specUtils.afterEach(taskResult, backend);
|
||||
});
|
||||
|
||||
fixture({ entries: [entry1], getUser: () => taskResult.data.user });
|
||||
});
|
||||
@@ -0,0 +1,28 @@
|
||||
import fixture from './common/media_library';
|
||||
import { entry1 } from './common/entries';
|
||||
import * as specUtils from './common/spec_utils';
|
||||
|
||||
const backend = 'bitbucket';
|
||||
const lfs = true;
|
||||
|
||||
describe('BitBucket Backend Media Library - Large Media', () => {
|
||||
let taskResult = { data: {} };
|
||||
|
||||
before(() => {
|
||||
specUtils.before(taskResult, { lfs }, backend);
|
||||
});
|
||||
|
||||
after(() => {
|
||||
specUtils.after(taskResult, backend);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
specUtils.beforeEach(taskResult, backend);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
specUtils.afterEach(taskResult, backend);
|
||||
});
|
||||
|
||||
fixture({ entries: [entry1], getUser: () => taskResult.data.user });
|
||||
});
|
||||
@@ -0,0 +1,28 @@
|
||||
import fixture from './common/media_library';
|
||||
import { entry1 } from './common/entries';
|
||||
import * as specUtils from './common/spec_utils';
|
||||
|
||||
const backend = 'git-gateway';
|
||||
const provider = 'github';
|
||||
|
||||
describe('Git Gateway (GitHub) Backend Media Library - Large Media', () => {
|
||||
let taskResult = { data: {} };
|
||||
|
||||
before(() => {
|
||||
specUtils.before(taskResult, { publish_mode: 'editorial_workflow', provider }, backend);
|
||||
});
|
||||
|
||||
after(() => {
|
||||
specUtils.after(taskResult, backend);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
specUtils.beforeEach(taskResult, backend);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
specUtils.afterEach(taskResult, backend);
|
||||
});
|
||||
|
||||
fixture({ entries: [entry1], getUser: () => taskResult.data.user });
|
||||
});
|
||||
@@ -0,0 +1,28 @@
|
||||
import fixture from './common/media_library';
|
||||
import { entry1 } from './common/entries';
|
||||
import * as specUtils from './common/spec_utils';
|
||||
|
||||
const backend = 'git-gateway';
|
||||
const provider = 'gitlab';
|
||||
|
||||
describe('Git Gateway (GitLab) Backend Media Library - Large Media', () => {
|
||||
let taskResult = { data: {} };
|
||||
|
||||
before(() => {
|
||||
specUtils.before(taskResult, { publish_mode: 'editorial_workflow', provider }, backend);
|
||||
});
|
||||
|
||||
after(() => {
|
||||
specUtils.after(taskResult, backend);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
specUtils.beforeEach(taskResult, backend);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
specUtils.afterEach(taskResult, backend);
|
||||
});
|
||||
|
||||
fixture({ entries: [entry1], getUser: () => taskResult.data.user });
|
||||
});
|
||||
@@ -0,0 +1,34 @@
|
||||
import fixture from './common/media_library';
|
||||
import { entry1 } from './common/entries';
|
||||
import * as specUtils from './common/spec_utils';
|
||||
|
||||
const backend = 'github';
|
||||
|
||||
describe('GitHub Backend Media Library - GraphQL API', () => {
|
||||
const taskResult = { data: {} };
|
||||
|
||||
before(() => {
|
||||
specUtils.before(
|
||||
taskResult,
|
||||
{
|
||||
backend: { use_graphql: true },
|
||||
publish_mode: 'editorial_workflow',
|
||||
},
|
||||
backend,
|
||||
);
|
||||
});
|
||||
|
||||
after(() => {
|
||||
specUtils.after(taskResult, backend);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
specUtils.beforeEach(taskResult, backend);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
specUtils.afterEach(taskResult, backend);
|
||||
});
|
||||
|
||||
fixture({ entries: [entry1], getUser: () => taskResult.data.user });
|
||||
});
|
||||
@@ -0,0 +1,34 @@
|
||||
import fixture from './common/media_library';
|
||||
import { entry1 } from './common/entries';
|
||||
import * as specUtils from './common/spec_utils';
|
||||
|
||||
const backend = 'github';
|
||||
|
||||
describe('GitHub Backend Media Library - REST API', () => {
|
||||
let taskResult = { data: {} };
|
||||
|
||||
before(() => {
|
||||
specUtils.before(
|
||||
taskResult,
|
||||
{
|
||||
backend: { use_graphql: false },
|
||||
publish_mode: 'editorial_workflow',
|
||||
},
|
||||
backend,
|
||||
);
|
||||
});
|
||||
|
||||
after(() => {
|
||||
specUtils.after(taskResult, backend);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
specUtils.beforeEach(taskResult, backend);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
specUtils.afterEach(taskResult, backend);
|
||||
});
|
||||
|
||||
fixture({ entries: [entry1], getUser: () => taskResult.data.user });
|
||||
});
|
||||
@@ -0,0 +1,27 @@
|
||||
import fixture from './common/media_library';
|
||||
import { entry1 } from './common/entries';
|
||||
import * as specUtils from './common/spec_utils';
|
||||
|
||||
const backend = 'gitlab';
|
||||
|
||||
describe('GitLab Backend Media Library - REST API', () => {
|
||||
let taskResult = { data: {} };
|
||||
|
||||
before(() => {
|
||||
specUtils.before(taskResult, { publish_mode: 'editorial_workflow' }, backend);
|
||||
});
|
||||
|
||||
after(() => {
|
||||
specUtils.after(taskResult, backend);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
specUtils.beforeEach(taskResult, backend);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
specUtils.afterEach(taskResult, backend);
|
||||
});
|
||||
|
||||
fixture({ entries: [entry1], getUser: () => taskResult.data.user });
|
||||
});
|
||||
@@ -0,0 +1,29 @@
|
||||
import fixture from './common/media_library';
|
||||
import * as specUtils from './common/spec_utils';
|
||||
import { entry1 } from './common/entries';
|
||||
|
||||
const backend = 'proxy';
|
||||
const mode = 'git';
|
||||
|
||||
describe(`Proxy Backend Media Library - '${mode}' mode`, () => {
|
||||
let taskResult = { data: {} };
|
||||
|
||||
before(() => {
|
||||
specUtils.before(taskResult, { publish_mode: 'editorial_workflow', mode }, backend);
|
||||
Cypress.config('defaultCommandTimeout', 5 * 1000);
|
||||
});
|
||||
|
||||
after(() => {
|
||||
specUtils.after(taskResult, backend);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
specUtils.beforeEach(taskResult, backend);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
specUtils.afterEach(taskResult, backend);
|
||||
});
|
||||
|
||||
fixture({ entries: [entry1], getUser: () => taskResult.data.user });
|
||||
});
|
||||
21
source/admin/cypress/e2e/media_library_spec_test_backend.js
Normal file
21
source/admin/cypress/e2e/media_library_spec_test_backend.js
Normal file
@@ -0,0 +1,21 @@
|
||||
import fixture from './common/media_library';
|
||||
|
||||
const entries = [
|
||||
{
|
||||
title: 'first title',
|
||||
body: 'first body',
|
||||
},
|
||||
];
|
||||
|
||||
describe('Test Backend Media Library', () => {
|
||||
after(() => {
|
||||
cy.task('teardownBackend', { backend: 'test' });
|
||||
});
|
||||
|
||||
before(() => {
|
||||
Cypress.config('defaultCommandTimeout', 4000);
|
||||
cy.task('setupBackend', { backend: 'test' });
|
||||
});
|
||||
|
||||
fixture({ entries });
|
||||
});
|
||||
71
source/admin/cypress/e2e/search_suggestion_spec.js
Normal file
71
source/admin/cypress/e2e/search_suggestion_spec.js
Normal file
@@ -0,0 +1,71 @@
|
||||
import { login } from '../utils/steps';
|
||||
|
||||
const search = (term, collection) => {
|
||||
cy.get('[class*=SearchInput]').clear({ force: true });
|
||||
cy.get('[class*=SearchInput]').type(term, { force: true });
|
||||
cy.get('[class*=SuggestionsContainer]').within(() => {
|
||||
cy.contains(collection).click();
|
||||
});
|
||||
};
|
||||
|
||||
const assertSearchHeading = title => {
|
||||
cy.get('[class*=SearchResultHeading]').should('have.text', title);
|
||||
};
|
||||
|
||||
const assertSearchResult = (text, collection) => {
|
||||
cy.get('[class*=ListCardLink] h2').contains(collection ?? text)
|
||||
};
|
||||
|
||||
const assertNotInSearch = text => {
|
||||
cy.get('[class*=ListCardLink] h2').contains(text).should('not.exist');
|
||||
};
|
||||
|
||||
describe('Search Suggestion', () => {
|
||||
before(() => {
|
||||
Cypress.config('defaultCommandTimeout', 4000);
|
||||
cy.task('setupBackend', { backend: 'test' });
|
||||
});
|
||||
|
||||
after(() => {
|
||||
cy.task('teardownBackend', { backend: 'test' });
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
login();
|
||||
});
|
||||
|
||||
it('can search in all collections', () => {
|
||||
search('this', 'All Collections');
|
||||
|
||||
assertSearchHeading('Search Results for "this"');
|
||||
|
||||
assertSearchResult('This is post # 20', 'Posts');
|
||||
assertSearchResult('This is a TOML front matter post', 'Posts');
|
||||
assertSearchResult('This is a JSON front matter post', 'Posts');
|
||||
assertSearchResult('This is a YAML front matter post', 'Posts');
|
||||
assertSearchResult('This FAQ item # 5', 'FAQ');
|
||||
});
|
||||
|
||||
it('can search in posts collection', () => {
|
||||
search('this', 'Posts');
|
||||
|
||||
assertSearchHeading('Search Results for "this" in Posts');
|
||||
|
||||
assertSearchResult('This is post # 20');
|
||||
assertSearchResult('This is a TOML front matter post');
|
||||
assertSearchResult('This is a JSON front matter post');
|
||||
assertSearchResult('This is a YAML front matter post');
|
||||
|
||||
assertNotInSearch('This FAQ item # 5');
|
||||
});
|
||||
|
||||
it('can search in faq collection', () => {
|
||||
search('this', 'FAQ');
|
||||
|
||||
assertSearchHeading('Search Results for "this" in FAQ');
|
||||
|
||||
assertSearchResult('This FAQ item # 5');
|
||||
|
||||
assertNotInSearch('This is post # 20');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,30 @@
|
||||
import fixture from './common/simple_workflow';
|
||||
import * as specUtils from './common/spec_utils';
|
||||
import { entry1, entry2, entry3 } from './common/entries';
|
||||
|
||||
const backend = 'bitbucket';
|
||||
|
||||
describe('BitBucket Backend Simple Workflow', () => {
|
||||
let taskResult = { data: {} };
|
||||
|
||||
before(() => {
|
||||
specUtils.before(taskResult, { publish_mode: 'simple' }, backend);
|
||||
});
|
||||
|
||||
after(() => {
|
||||
specUtils.after(taskResult, backend);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
specUtils.beforeEach(taskResult, backend);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
specUtils.afterEach(taskResult, backend);
|
||||
});
|
||||
|
||||
fixture({
|
||||
entries: [entry1, entry2, entry3],
|
||||
getUser: () => taskResult.data.user,
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,31 @@
|
||||
import fixture from './common/simple_workflow';
|
||||
import * as specUtils from './common/spec_utils';
|
||||
import { entry1, entry2, entry3 } from './common/entries';
|
||||
|
||||
const backend = 'git-gateway';
|
||||
const provider = 'github';
|
||||
|
||||
describe('Git Gateway (GitHub) Backend Simple Workflow', () => {
|
||||
let taskResult = { data: {} };
|
||||
|
||||
before(() => {
|
||||
specUtils.before(taskResult, { publish_mode: 'simple', provider }, backend);
|
||||
});
|
||||
|
||||
after(() => {
|
||||
specUtils.after(taskResult, backend);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
specUtils.beforeEach(taskResult, backend);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
specUtils.afterEach(taskResult, backend);
|
||||
});
|
||||
|
||||
fixture({
|
||||
entries: [entry1, entry2, entry3],
|
||||
getUser: () => taskResult.data.user,
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,31 @@
|
||||
import fixture from './common/simple_workflow';
|
||||
import * as specUtils from './common/spec_utils';
|
||||
import { entry1, entry2, entry3 } from './common/entries';
|
||||
|
||||
const backend = 'git-gateway';
|
||||
const provider = 'gitlab';
|
||||
|
||||
describe('Git Gateway (GitLab) Backend Simple Workflow', () => {
|
||||
let taskResult = { data: {} };
|
||||
|
||||
before(() => {
|
||||
specUtils.before(taskResult, { publish_mode: 'simple', provider }, backend);
|
||||
});
|
||||
|
||||
after(() => {
|
||||
specUtils.after(taskResult, backend);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
specUtils.beforeEach(taskResult, backend);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
specUtils.afterEach(taskResult, backend);
|
||||
});
|
||||
|
||||
fixture({
|
||||
entries: [entry1, entry2, entry3],
|
||||
getUser: () => taskResult.data.user,
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,37 @@
|
||||
import fixture from './common/simple_workflow';
|
||||
import * as specUtils from './common/spec_utils';
|
||||
import { entry1, entry2, entry3 } from './common/entries';
|
||||
|
||||
const backend = 'github';
|
||||
|
||||
describe('GitHub Backend Simple Workflow - GraphQL API', () => {
|
||||
const taskResult = { data: {} };
|
||||
|
||||
before(() => {
|
||||
specUtils.before(
|
||||
taskResult,
|
||||
{
|
||||
backend: { use_graphql: true },
|
||||
publish_mode: 'simple',
|
||||
},
|
||||
backend,
|
||||
);
|
||||
});
|
||||
|
||||
after(() => {
|
||||
specUtils.after(taskResult, backend);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
specUtils.beforeEach(taskResult, backend);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
specUtils.afterEach(taskResult, backend);
|
||||
});
|
||||
|
||||
fixture({
|
||||
entries: [entry1, entry2, entry3],
|
||||
getUser: () => taskResult.data.user,
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,37 @@
|
||||
import fixture from './common/simple_workflow';
|
||||
import * as specUtils from './common/spec_utils';
|
||||
import { entry1, entry2, entry3 } from './common/entries';
|
||||
|
||||
const backend = 'github';
|
||||
|
||||
describe('GitHub Backend Simple Workflow - REST API', () => {
|
||||
let taskResult = { data: {} };
|
||||
|
||||
before(() => {
|
||||
specUtils.before(
|
||||
taskResult,
|
||||
{
|
||||
backend: { use_graphql: false },
|
||||
publish_mode: 'simple',
|
||||
},
|
||||
backend,
|
||||
);
|
||||
});
|
||||
|
||||
after(() => {
|
||||
specUtils.after(taskResult, backend);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
specUtils.beforeEach(taskResult, backend);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
specUtils.afterEach(taskResult, backend);
|
||||
});
|
||||
|
||||
fixture({
|
||||
entries: [entry1, entry2, entry3],
|
||||
getUser: () => taskResult.data.user,
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,30 @@
|
||||
import fixture from './common/simple_workflow';
|
||||
import * as specUtils from './common/spec_utils';
|
||||
import { entry1, entry2, entry3 } from './common/entries';
|
||||
|
||||
const backend = 'gitlab';
|
||||
|
||||
describe('GitLab Backend Simple Workflow', () => {
|
||||
let taskResult = { data: {} };
|
||||
|
||||
before(() => {
|
||||
specUtils.before(taskResult, { publish_mode: 'simple' }, backend);
|
||||
});
|
||||
|
||||
after(() => {
|
||||
specUtils.after(taskResult, backend);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
specUtils.beforeEach(taskResult, backend);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
specUtils.afterEach(taskResult, backend);
|
||||
});
|
||||
|
||||
fixture({
|
||||
entries: [entry1, entry2, entry3],
|
||||
getUser: () => taskResult.data.user,
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,32 @@
|
||||
import fixture from './common/simple_workflow';
|
||||
import * as specUtils from './common/spec_utils';
|
||||
import { entry1, entry2, entry3 } from './common/entries';
|
||||
|
||||
const backend = 'proxy';
|
||||
const mode = 'fs';
|
||||
|
||||
describe(`Proxy Backend Simple Workflow - '${mode}' mode`, () => {
|
||||
const taskResult = { data: {} };
|
||||
|
||||
before(() => {
|
||||
specUtils.before(taskResult, { publish_mode: 'simple', mode }, backend);
|
||||
Cypress.config('defaultCommandTimeout', 5 * 1000);
|
||||
});
|
||||
|
||||
after(() => {
|
||||
specUtils.after(taskResult, backend);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
specUtils.beforeEach(taskResult, backend);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
specUtils.afterEach(taskResult, backend);
|
||||
});
|
||||
|
||||
fixture({
|
||||
entries: [entry1, entry2, entry3],
|
||||
getUser: () => taskResult.data.user,
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,35 @@
|
||||
import fixture from './common/simple_workflow';
|
||||
import * as specUtils from './common/spec_utils';
|
||||
import { entry1, entry2, entry3 } from './common/entries';
|
||||
|
||||
const backend = 'proxy';
|
||||
const mode = 'git';
|
||||
|
||||
describe(`Proxy Backend Simple Workflow - '${mode}' mode`, () => {
|
||||
let taskResult = { data: {} };
|
||||
|
||||
before(() => {
|
||||
specUtils.before(taskResult, { publish_mode: 'simple', mode }, backend);
|
||||
Cypress.config('defaultCommandTimeout', 5 * 1000);
|
||||
});
|
||||
|
||||
after(() => {
|
||||
specUtils.after(taskResult, backend);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
if (Cypress.mocha.getRunner().suite.ctx.currentTest.title === 'can create an entry') {
|
||||
Cypress.mocha.getRunner().suite.ctx.currentTest.skip();
|
||||
}
|
||||
specUtils.beforeEach(taskResult, backend);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
specUtils.afterEach(taskResult, backend);
|
||||
});
|
||||
|
||||
fixture({
|
||||
entries: [entry1, entry2, entry3],
|
||||
getUser: () => taskResult.data.user,
|
||||
});
|
||||
});
|
||||
102
source/admin/cypress/e2e/simple_workflow_spec_test_backend.js
Normal file
102
source/admin/cypress/e2e/simple_workflow_spec_test_backend.js
Normal file
@@ -0,0 +1,102 @@
|
||||
import '../utils/dismiss-local-backup';
|
||||
import {
|
||||
login,
|
||||
newPost,
|
||||
populateEntry,
|
||||
exitEditor,
|
||||
createPostAndPublish,
|
||||
assertPublishedEntry,
|
||||
editPostAndPublish,
|
||||
createPostPublishAndCreateNew,
|
||||
createPostPublishAndDuplicate,
|
||||
editPostPublishAndCreateNew,
|
||||
editPostPublishAndDuplicate,
|
||||
duplicatePostAndPublish,
|
||||
} from '../utils/steps';
|
||||
|
||||
const entry1 = {
|
||||
title: 'first title',
|
||||
body: 'first body',
|
||||
};
|
||||
const entry2 = {
|
||||
title: 'second title',
|
||||
body: 'second body',
|
||||
};
|
||||
|
||||
const backend = 'test';
|
||||
|
||||
describe('Test Backend Simple Workflow', () => {
|
||||
before(() => {
|
||||
Cypress.config('defaultCommandTimeout', 4000);
|
||||
cy.task('setupBackend', { backend, options: { publish_mode: 'simple' } });
|
||||
});
|
||||
|
||||
after(() => {
|
||||
cy.task('teardownBackend', { backend });
|
||||
});
|
||||
|
||||
it('successfully loads', () => {
|
||||
login();
|
||||
});
|
||||
|
||||
it('can create a new entry', () => {
|
||||
login();
|
||||
newPost();
|
||||
populateEntry(entry1, () => {});
|
||||
|
||||
// new entry should show 'Unsaved changes'
|
||||
cy.contains('div', 'Unsaved Changes');
|
||||
cy.url().should('eq', `http://localhost:8080/#/collections/posts/new`);
|
||||
exitEditor();
|
||||
});
|
||||
|
||||
it('can publish a new entry', () => {
|
||||
login();
|
||||
createPostAndPublish(entry1);
|
||||
assertPublishedEntry(entry1);
|
||||
});
|
||||
|
||||
it('can publish a new entry and create new', () => {
|
||||
login();
|
||||
createPostPublishAndCreateNew(entry1);
|
||||
assertPublishedEntry(entry1);
|
||||
});
|
||||
|
||||
it('can publish a new entry and duplicate', () => {
|
||||
login();
|
||||
createPostPublishAndDuplicate(entry1);
|
||||
assertPublishedEntry(entry1);
|
||||
});
|
||||
|
||||
it('can edit an existing entry and publish', () => {
|
||||
login();
|
||||
createPostAndPublish(entry1);
|
||||
assertPublishedEntry(entry1);
|
||||
|
||||
editPostAndPublish(entry1, entry2);
|
||||
});
|
||||
|
||||
it('can edit an existing entry, publish and create new', () => {
|
||||
login();
|
||||
createPostAndPublish(entry1);
|
||||
assertPublishedEntry(entry1);
|
||||
|
||||
editPostPublishAndCreateNew(entry1, entry2);
|
||||
});
|
||||
|
||||
it('can edit an existing entry, publish and duplicate', () => {
|
||||
login();
|
||||
createPostAndPublish(entry1);
|
||||
assertPublishedEntry(entry1);
|
||||
|
||||
editPostPublishAndDuplicate(entry1, entry2);
|
||||
});
|
||||
|
||||
it('can duplicate an existing entry', () => {
|
||||
login();
|
||||
createPostAndPublish(entry1);
|
||||
assertPublishedEntry(entry1);
|
||||
|
||||
duplicatePostAndPublish(entry1);
|
||||
});
|
||||
});
|
||||
102
source/admin/cypress/e2e/view_filters_spec.js
Normal file
102
source/admin/cypress/e2e/view_filters_spec.js
Normal file
@@ -0,0 +1,102 @@
|
||||
import { login } from '../utils/steps';
|
||||
|
||||
const filter = term => {
|
||||
cy.contains('span', 'Filter by').click();
|
||||
cy.contains(term).click();
|
||||
cy.contains('Contents').click();
|
||||
};
|
||||
|
||||
const assertEntriesCount = count => {
|
||||
cy.get('[class*=ListCardLink]').should('have.length', count);
|
||||
};
|
||||
|
||||
const assertInEntries = text => {
|
||||
cy.get('[class*=ListCardLink] h2').contains(text);
|
||||
};
|
||||
|
||||
const assertNotInEntries = text => {
|
||||
cy.get('[class*=ListCardLink] h2').contains(text).should('not.exist');
|
||||
};
|
||||
|
||||
describe('View Filter', () => {
|
||||
before(() => {
|
||||
Cypress.config('defaultCommandTimeout', 4000);
|
||||
cy.task('setupBackend', { backend: 'test' });
|
||||
});
|
||||
|
||||
after(() => {
|
||||
cy.task('teardownBackend', { backend: 'test' });
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
login();
|
||||
});
|
||||
|
||||
it('can apply string filter', () => {
|
||||
// enable filter
|
||||
filter('Posts With Index');
|
||||
|
||||
assertEntriesCount(20);
|
||||
for (let i = 1; i <= 20; i++) {
|
||||
assertInEntries(`This is post # ${i} --`);
|
||||
}
|
||||
assertNotInEntries('This is a YAML front matter post');
|
||||
assertNotInEntries('This is a JSON front matter post');
|
||||
assertNotInEntries('This is a TOML front matter post');
|
||||
|
||||
// disable filter
|
||||
filter('Posts With Index');
|
||||
assertEntriesCount(23);
|
||||
for (let i = 1; i <= 20; i++) {
|
||||
assertInEntries(`This is post # ${i} --`);
|
||||
}
|
||||
assertInEntries('This is a YAML front matter post');
|
||||
assertInEntries('This is a JSON front matter post');
|
||||
assertInEntries('This is a TOML front matter post');
|
||||
});
|
||||
|
||||
it('can apply boolean filter', () => {
|
||||
// enable filter
|
||||
filter('Drafts');
|
||||
|
||||
assertEntriesCount(10);
|
||||
for (let i = 1; i <= 20; i++) {
|
||||
const draft = i % 2 === 0;
|
||||
if (draft) {
|
||||
assertInEntries(`This is post # ${i} --`);
|
||||
} else {
|
||||
assertNotInEntries(`This is post # ${i} --`);
|
||||
}
|
||||
}
|
||||
assertNotInEntries('This is a YAML front matter post');
|
||||
assertNotInEntries('This is a JSON front matter post');
|
||||
assertNotInEntries('This is a TOML front matter post');
|
||||
|
||||
// disable filter
|
||||
filter('Drafts');
|
||||
assertEntriesCount(23);
|
||||
for (let i = 1; i <= 20; i++) {
|
||||
assertInEntries(`This is post # ${i} --`);
|
||||
}
|
||||
assertInEntries('This is a YAML front matter post');
|
||||
assertInEntries('This is a JSON front matter post');
|
||||
assertInEntries('This is a TOML front matter post');
|
||||
});
|
||||
|
||||
it('can apply multiple filters', () => {
|
||||
// enable filter
|
||||
filter('Posts Without Index');
|
||||
|
||||
assertEntriesCount(3);
|
||||
|
||||
assertInEntries('This is a YAML front matter post');
|
||||
assertInEntries('This is a JSON front matter post');
|
||||
assertInEntries('This is a TOML front matter post');
|
||||
|
||||
filter('Drafts');
|
||||
|
||||
assertEntriesCount(0);
|
||||
|
||||
cy.contains('div', 'No Entries');
|
||||
});
|
||||
});
|
||||
70
source/admin/cypress/e2e/view_groups_spec.js
Normal file
70
source/admin/cypress/e2e/view_groups_spec.js
Normal file
@@ -0,0 +1,70 @@
|
||||
import { login } from '../utils/steps';
|
||||
|
||||
const group = term => {
|
||||
cy.contains('span', 'Group by').click();
|
||||
cy.contains(term).click();
|
||||
cy.contains('Contents').click();
|
||||
};
|
||||
|
||||
const assertGroupsCount = count => {
|
||||
cy.get('[class*=GroupContainer]').should('have.length', count);
|
||||
};
|
||||
|
||||
const assertEachGroupCount = (id, count) => {
|
||||
cy.get(`[id='${id}']`).within(() => {
|
||||
assertEntriesCount(count);
|
||||
});
|
||||
};
|
||||
|
||||
const assertEntriesCount = count => {
|
||||
cy.get('[class*=ListCardLink]').should('have.length', count);
|
||||
};
|
||||
|
||||
const assertInEntries = text => {
|
||||
cy.get('[class*=ListCardLink] h2').contains('h2', text);
|
||||
};
|
||||
|
||||
describe('View Group', () => {
|
||||
before(() => {
|
||||
Cypress.config('defaultCommandTimeout', 4000);
|
||||
cy.task('setupBackend', { backend: 'test' });
|
||||
});
|
||||
|
||||
after(() => {
|
||||
cy.task('teardownBackend', { backend: 'test' });
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
login();
|
||||
});
|
||||
|
||||
it('can apply string group', () => {
|
||||
// enable group
|
||||
group('Year');
|
||||
|
||||
assertGroupsCount(2);
|
||||
const year = new Date().getFullYear();
|
||||
assertEachGroupCount(`Year${year}`, 20);
|
||||
assertEachGroupCount('Year2015', 3);
|
||||
|
||||
//disable group
|
||||
group('Year');
|
||||
|
||||
assertEntriesCount(23);
|
||||
for (let i = 1; i <= 20; i++) {
|
||||
assertInEntries(`This is post # ${i} --`);
|
||||
}
|
||||
assertInEntries('This is a YAML front matter post');
|
||||
assertInEntries('This is a JSON front matter post');
|
||||
assertInEntries('This is a TOML front matter post');
|
||||
|
||||
//enable group
|
||||
group('Drafts');
|
||||
|
||||
assertEntriesCount(23);
|
||||
assertGroupsCount(3);
|
||||
assertEachGroupCount('Draftstrue', 10);
|
||||
assertEachGroupCount('Draftsfalse', 10);
|
||||
assertEachGroupCount('missing_value', 3);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user