From f49a6ce6d519252be6bb603ed65c1c25f70f99fb Mon Sep 17 00:00:00 2001 From: Suzanne Aitchison Date: Fri, 20 Aug 2021 09:04:11 +0100 Subject: [PATCH] Revise editor publish logic (#14546) * revise publish logic * refactor arguments --- app/javascript/article-form/actions.js | 9 +- app/javascript/article-form/articleForm.jsx | 41 ++- .../publishingFlows/publishOrSavePost.spec.js | 258 ++++++++++++++++++ 3 files changed, 291 insertions(+), 17 deletions(-) create mode 100644 cypress/integration/seededFlows/publishingFlows/publishOrSavePost.spec.js diff --git a/app/javascript/article-form/actions.js b/app/javascript/article-form/actions.js index 7fb15f08c..08ed5d3ce 100644 --- a/app/javascript/article-form/actions.js +++ b/app/javascript/article-form/actions.js @@ -44,7 +44,7 @@ function processPayload(payload) { return neededPayload; } -export function submitArticle(payload, clearStorage, errorCb, failureCb) { +export function submitArticle({ payload, onSuccess, onError }) { const method = payload.id ? 'PUT' : 'POST'; const url = payload.id ? `/articles/${payload.id}` : '/articles'; fetch(url, { @@ -62,14 +62,13 @@ export function submitArticle(payload, clearStorage, errorCb, failureCb) { .then((response) => response.json()) .then((response) => { if (response.current_state_path) { - clearStorage(); + onSuccess(); window.location.replace(response.current_state_path); } else { - // If there is an error and the method is POST, we know they are trying to publish. - errorCb(response, method === 'POST'); + onError(response); } }) - .catch(failureCb); + .catch(onError); } function generateUploadFormdata(payload) { diff --git a/app/javascript/article-form/articleForm.jsx b/app/javascript/article-form/articleForm.jsx index 0c4644edc..f4e8a0ef9 100644 --- a/app/javascript/article-form/articleForm.jsx +++ b/app/javascript/article-form/articleForm.jsx @@ -262,18 +262,38 @@ export class ArticleForm extends Component { onPublish = (e) => { e.preventDefault(); - this.setState({ submitting: true, published: true }); - const { state } = this; - state.published = true; - submitArticle(state, this.removeLocalStorage, this.handleArticleError); + this.setState({ submitting: true }); + const payload = { + ...this.state, + published: true, + }; + + submitArticle({ + payload, + onSuccess: () => { + this.removeLocalStorage(); + this.setState({ published: true, submitting: false }); + }, + onError: this.handleArticleError, + }); }; onSaveDraft = (e) => { e.preventDefault(); - this.setState({ submitting: true, published: false }); - const { state } = this; - state.published = false; - submitArticle(state, this.removeLocalStorage, this.handleArticleError); + this.setState({ submitting: true }); + const payload = { + ...this.state, + published: false, + }; + + submitArticle({ + payload, + onSuccess: () => { + this.removeLocalStorage(); + this.setState({ published: false, submitting: false }); + }, + onError: this.handleArticleError, + }); }; onClearChanges = (e) => { @@ -310,14 +330,11 @@ export class ArticleForm extends Component { }); }; - handleArticleError = (response, publishFailed = false) => { + handleArticleError = (response) => { window.scrollTo(0, 0); - const { published } = this.state; this.setState({ errors: response, submitting: false, - // Even if it's an update that failed, published will still be set to true - published: published && !publishFailed, }); }; diff --git a/cypress/integration/seededFlows/publishingFlows/publishOrSavePost.spec.js b/cypress/integration/seededFlows/publishingFlows/publishOrSavePost.spec.js new file mode 100644 index 000000000..916f46857 --- /dev/null +++ b/cypress/integration/seededFlows/publishingFlows/publishOrSavePost.spec.js @@ -0,0 +1,258 @@ +describe('Publish or save a post', () => { + describe('v1 Editor', () => { + const validPublishedArticleContent = + '---\ntitle: Test title\npublished: true\ndescription:\ntags:\n---\nSome content'; + + const invalidPublishedArticleContent = + '---\ntitle: Test title\npublished: true\ndescription:\ntags:\n---\nSome content {%tag %}'; + + const validDraftArticleContent = + '---\ntitle: Test title\npublished: false\ndescription:\ntags:\n---\nSome content'; + + beforeEach(() => { + cy.testSetup(); + cy.fixture('users/articleEditorV1User.json').as('user'); + + cy.get('@user').then((user) => { + cy.loginAndVisit(user, '/new'); + }); + }); + + it('Publishes a post without errors', () => { + cy.findByRole('form', { name: /^Edit post$/i }).within(() => { + cy.findByLabelText('Post Content') + .clear() + .type(validPublishedArticleContent); + cy.findByRole('button', { name: 'Save changes' }).click(); + }); + // The post should now be published + cy.findByRole('heading', { name: 'Test title' }); + cy.findByRole('heading', { name: 'Discussion (0)' }); + cy.findByRole('link', { name: 'Edit' }); + cy.findByRole('link', { name: 'Manage' }); + cy.findByRole('link', { name: 'Stats' }); + }); + + it('Saves a draft without errors', () => { + cy.findByRole('form', { name: /^Edit post$/i }).within(() => { + cy.findByLabelText('Post Content') + .clear() + .type(validDraftArticleContent); + cy.findByRole('button', { name: 'Save changes' }).click(); + }); + + // The Draft view should be shown + cy.findByText(/Unpublished Post/); + cy.findByRole('heading', { name: 'Test title' }); + cy.findByRole('link', { name: 'Click to edit' }); + }); + + it('Shows an error message when markdown is incorrect', () => { + cy.findByRole('form', { name: /^Edit post$/i }).within(() => { + cy.findByLabelText('Post Content') + .clear() + .type(invalidPublishedArticleContent, { + parseSpecialCharSequences: false, + }); + cy.findByRole('button', { name: 'Save changes' }).click(); + }); + + cy.findByRole('heading', { name: 'Whoops, something went wrong:' }); + // We should still be on the form page + cy.findByRole('form', { name: /^Edit post$/i }); + }); + + it('Shows an error message when network request fails', () => { + cy.intercept('POST', '/articles', { statusCode: 500 }); + cy.findByRole('form', { name: /^Edit post$/i }).within(() => { + cy.findByLabelText('Post Content') + .clear() + .type(validPublishedArticleContent); + cy.findByRole('button', { name: 'Save changes' }).click(); + }); + cy.findByRole('heading', { name: 'Whoops, something went wrong:' }); + // We should still be on the form page + cy.findByRole('form', { name: /^Edit post$/i }); + }); + }); + + describe('v2 Editor', () => { + beforeEach(() => { + cy.testSetup(); + cy.fixture('users/articleEditorV2User.json').as('user'); + + cy.get('@user').then((user) => { + cy.loginAndVisit(user, '/new'); + }); + }); + + it('Publishes a post without errors', () => { + cy.findByRole('form', { name: /^Edit post$/i }).within(() => { + cy.findByLabelText('Post Title').clear().type('Test title'); + cy.findByLabelText('Post Content').clear().type('something'); + cy.findByRole('button', { name: 'Publish' }).click(); + }); + // The post should now be published + cy.findByRole('heading', { name: 'Test title' }); + cy.findByRole('heading', { name: 'Discussion (0)' }); + cy.findByRole('link', { name: 'Edit' }); + cy.findByRole('link', { name: 'Manage' }); + cy.findByRole('link', { name: 'Stats' }); + }); + + it('Saves a draft without errors', () => { + cy.findByRole('form', { name: /^Edit post$/i }).within(() => { + cy.findByLabelText('Post Title').clear().type('Test title'); + cy.findByLabelText('Post Content').clear().type('something'); + cy.findByRole('button', { name: 'Save draft' }).click(); + }); + + // The Draft view should be shown + cy.findByText(/Unpublished Post/); + cy.findByRole('heading', { name: 'Test title' }); + cy.findByRole('link', { name: 'Click to edit' }); + }); + + it('Shows an error message when publishing with incorrect markdown', () => { + cy.findByRole('form', { name: /^Edit post$/i }).within(() => { + cy.findByLabelText('Post Title').clear().type('Test title'); + + cy.findByLabelText('Post Content').clear().type('{% tag %}', { + parseSpecialCharSequences: false, + }); + cy.findByRole('button', { name: 'Publish' }).click(); + }); + + cy.findByRole('heading', { name: 'Whoops, something went wrong:' }); + // We should still be on the form page + cy.findByRole('form', { name: /^Edit post$/i }); + cy.findByRole('button', { name: 'Publish' }); + cy.findByRole('button', { name: 'Save draft' }); + }); + + it('Shows an error message when saving draft with incorrect markdown', () => { + cy.findByRole('form', { name: /^Edit post$/i }).within(() => { + cy.findByLabelText('Post Title').clear().type('Test title'); + + cy.findByLabelText('Post Content').clear().type('{% tag %}', { + parseSpecialCharSequences: false, + }); + cy.findByRole('button', { name: 'Save draft' }).click(); + }); + + cy.findByRole('heading', { name: 'Whoops, something went wrong:' }); + // We should still be on the form page + cy.findByRole('form', { name: /^Edit post$/i }); + cy.findByRole('button', { name: 'Publish' }); + cy.findByRole('button', { name: 'Save draft' }); + }); + + it('Shows an error message when publishing and network request fails', () => { + cy.intercept('POST', '/articles', { statusCode: 500 }); + cy.findByRole('form', { name: /^Edit post$/i }).within(() => { + cy.findByLabelText('Post Title').clear().type('Test title'); + cy.findByLabelText('Post Content').clear().type('something'); + cy.findByRole('button', { name: 'Publish' }).click(); + }); + cy.findByRole('heading', { name: 'Whoops, something went wrong:' }); + // We should still be on the form page + cy.findByRole('form', { name: /^Edit post$/i }); + cy.findByRole('button', { name: 'Publish' }); + cy.findByRole('button', { name: 'Save draft' }); + }); + + it('Shows an error message when saving draft and network request fails', () => { + cy.intercept('POST', '/articles', { statusCode: 500 }); + cy.findByRole('form', { name: /^Edit post$/i }).within(() => { + cy.findByLabelText('Post Title').clear().type('Test title'); + cy.findByLabelText('Post Content').clear().type('something'); + cy.findByRole('button', { name: 'Save draft' }).click(); + }); + cy.findByRole('heading', { name: 'Whoops, something went wrong:' }); + // We should still be on the form page + cy.findByRole('form', { name: /^Edit post$/i }); + cy.findByRole('button', { name: 'Publish' }); + cy.findByRole('button', { name: 'Save draft' }); + }); + + it('Maintains draft status when editing a draft fails', () => { + cy.findByRole('form', { name: /^Edit post$/i }).within(() => { + cy.findByLabelText('Post Title').clear().type('Test title'); + cy.findByLabelText('Post Content').clear().type('something'); + cy.findByRole('button', { name: 'Save draft' }).click(); + }); + + // Check we are on the draft post page, and choose to edit + cy.findByText(/Unpublished Post/); + cy.findByRole('link', { name: 'Click to edit' }).click(); + + cy.findByLabelText('Post Content').clear().type('something else'); + cy.intercept('PUT', '/articles/*', { statusCode: 500 }); + cy.findByRole('button', { name: 'Save draft' }).click(); + + // We should still be on the form page and see the draft publish/save options + cy.findByRole('form', { name: /^Edit post$/i }); + cy.findByRole('button', { name: 'Publish' }); + cy.findByRole('button', { name: 'Save draft' }); + + // Check post is still draft in dashboard + cy.visitAndWaitForUserSideEffects('/dashboard'); + cy.findByRole('heading', { name: 'Dashboard' }); + cy.findByRole('link', { name: 'Draft' }); + }); + + it('Maintains draft status when publishing a draft fails', () => { + cy.findByRole('form', { name: /^Edit post$/i }).within(() => { + cy.findByLabelText('Post Title').clear().type('Test title'); + cy.findByLabelText('Post Content').clear().type('something'); + cy.findByRole('button', { name: 'Save draft' }).click(); + }); + + // Check we are on the draft post page, and choose to edit and publish + cy.findByText(/Unpublished Post/); + cy.findByRole('link', { name: 'Click to edit' }).click(); + + cy.findByLabelText('Post Content').clear().type('something else'); + cy.intercept('PUT', '/articles/*', { statusCode: 500 }); + cy.findByRole('button', { name: 'Publish' }).click(); + + // We should still be on the form page and see the draft publish/save options + cy.findByRole('form', { name: /^Edit post$/i }); + cy.findByRole('button', { name: 'Publish' }); + cy.findByRole('button', { name: 'Save draft' }); + + // Check post is still draft in dashboard + cy.visitAndWaitForUserSideEffects('/dashboard'); + cy.findByRole('heading', { name: 'Dashboard' }); + cy.findByRole('link', { name: 'Draft' }); + }); + + it('Maintains published status when editing a published post fails', () => { + cy.findByRole('form', { name: /^Edit post$/i }).within(() => { + cy.findByLabelText('Post Title').clear().type('Test title'); + cy.findByLabelText('Post Content').clear().type('something'); + cy.findByRole('button', { name: 'Publish' }).click(); + }); + + // Wait for published post page, and choose to edit + cy.findByRole('heading', { name: 'Test title' }); + cy.findByRole('heading', { name: 'Discussion (0)' }); + cy.findByRole('link', { name: 'Edit' }).click(); + + cy.findByLabelText('Post Content').clear().type('something else'); + cy.intercept('PUT', '/articles/*', { statusCode: 500 }); + cy.findByRole('button', { name: 'Save changes' }).click(); + + // We should still be on the form page and see the draft publish/save options + cy.findByRole('form', { name: /^Edit post$/i }); + cy.findByRole('button', { name: 'Save changes' }); + cy.findByRole('button', { name: 'Publish' }).should('not.exist'); + cy.findByRole('button', { name: 'Save draft' }).should('not.exist'); + + // Check post is still published in dashboard + cy.visitAndWaitForUserSideEffects('/dashboard'); + cy.findByRole('heading', { name: 'Dashboard' }); + cy.findByRole('link', { name: 'Draft' }).should('not.exist'); + }); + }); +});