This commit is contained in:
2025-08-25 20:24:23 +08:00
parent 30106e0129
commit 0ae8d7a709
1044 changed files with 321581 additions and 0 deletions

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

View File

@@ -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]]);
});
});
}

View 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',
};

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

View File

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

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

View 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]);
});
}

View 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]);
});
}

View 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,
});
};