From 3f0e46c7b025dd4e48f49117c5acf5a0fb43e7d0 Mon Sep 17 00:00:00 2001 From: Kevin Ansfield Date: Mon, 30 May 2022 19:08:07 +0100 Subject: [PATCH] Added initial tests for new publish flow refs https://github.com/TryGhost/Team/issues/1586 - added test helper for enabling/disabling members and helpers for disabling mailgun and newsletters - added `loginAsRole` helper that alleviates duplication of user+role creation and also handles log-out before log-in so it's easier to mix different role tests within a block that has a default role setup in `beforeEach()` - cleaned up editor tests that were skipped due to using the old publish flow - added `Publish flow` acceptance test suite with an initial batch of tests --- .../components/editor/modals/publish-flow.hbs | 15 +- .../editor/modals/publish-flow/complete.hbs | 10 +- .../editor/modals/publish-flow/confirm.hbs | 9 +- .../editor/modals/publish-flow/options.hbs | 24 +- .../components/editor/modals/update-flow.hbs | 10 +- .../editor/publish-options/publish-type.hbs | 7 +- ghost/admin/tests/acceptance/editor-test.js | 483 ++++-------------- .../acceptance/editor/publish-flow-test.js | 218 ++++++++ ghost/admin/tests/helpers/login-as-role.js | 10 + ghost/admin/tests/helpers/mailgun.js | 18 +- ghost/admin/tests/helpers/members.js | 11 + ghost/admin/tests/helpers/newsletters.js | 10 +- 12 files changed, 398 insertions(+), 427 deletions(-) create mode 100644 ghost/admin/tests/acceptance/editor/publish-flow-test.js create mode 100644 ghost/admin/tests/helpers/login-as-role.js create mode 100644 ghost/admin/tests/helpers/members.js diff --git a/ghost/admin/app/components/editor/modals/publish-flow.hbs b/ghost/admin/app/components/editor/modals/publish-flow.hbs index b388b12c9f..85017eccef 100644 --- a/ghost/admin/app/components/editor/modals/publish-flow.hbs +++ b/ghost/admin/app/components/editor/modals/publish-flow.hbs @@ -1,6 +1,6 @@ -
-
- @@ -10,10 +10,17 @@ type="button" class="gh-btn gh-btn-editor gh-editor-preview-trigger" {{on "click" @data.togglePreviewPublish}} + data-test-button="publish-flow-preview" > Preview -
diff --git a/ghost/admin/app/components/editor/modals/publish-flow/complete.hbs b/ghost/admin/app/components/editor/modals/publish-flow/complete.hbs index f62acd8e2b..9181b79ad4 100644 --- a/ghost/admin/app/components/editor/modals/publish-flow/complete.hbs +++ b/ghost/admin/app/components/editor/modals/publish-flow/complete.hbs @@ -1,5 +1,5 @@ {{#let @publishOptions.post as |post|}} -
+
{{#if post.isScheduled}} All set! @@ -27,7 +27,7 @@ {{#if post.emailOnly}} Your email has been sent. {{else if (and post.isPost @postCount)}} - That’s {{@postCount}} posts published, keep going! + That’s {{gh-pluralize @postCount "post"}} published, keep going! {{else}} Your {{post.displayName}} has been published. {{/if}} @@ -35,7 +35,7 @@
{{#if post.emailOnly}} -
+

Your post {{if post.isScheduled "will be" "was"}} @@ -79,7 +79,7 @@ {{/if}}

{{else}} - + @@ -90,6 +90,7 @@ type="button" class="gh-revert-to-draft" {{on "click" (fn @close (hash afterTask="revertToDraftTask"))}} + data-test-button="revert-to-draft" > Unschedule and revert to draft → @@ -100,6 +101,7 @@ type="button" class="gh-back-to-editor" {{on "click" @close}} + data-test-button="back-to-editor" > Back to editor diff --git a/ghost/admin/app/components/editor/modals/publish-flow/confirm.hbs b/ghost/admin/app/components/editor/modals/publish-flow/confirm.hbs index 2d41ef045c..4006d594d6 100644 --- a/ghost/admin/app/components/editor/modals/publish-flow/confirm.hbs +++ b/ghost/admin/app/components/editor/modals/publish-flow/confirm.hbs @@ -1,8 +1,8 @@ -
+
Ready, set, publish.
Share it with the world.
-

+

{{#if @publishOptions.isScheduled}} {{#let (moment-site-tz @publishOptions.scheduledAtUTC) as |scheduledAt|}} On @@ -53,7 +53,7 @@

{{#if this.errorMessage}} -

+

{{this.errorMessage}}

{{/if}} @@ -67,6 +67,7 @@ @class="gh-btn gh-btn-large" @idleClass="gh-btn-pulse" @runningClass="gh-btn-green gh-btn-icon" + data-test-button="confirm-publish" /> - +
\ No newline at end of file diff --git a/ghost/admin/app/components/editor/modals/publish-flow/options.hbs b/ghost/admin/app/components/editor/modals/publish-flow/options.hbs index d08b321a27..95bb472c36 100644 --- a/ghost/admin/app/components/editor/modals/publish-flow/options.hbs +++ b/ghost/admin/app/components/editor/modals/publish-flow/options.hbs @@ -1,16 +1,16 @@ -
+
Ready, set, publish.
Share it with the world.
-
+
{{#if @publishOptions.emailUnavailable}} -
+
{{svg-jar "send-email"}}
Publish on site
{{else}} - +
diff --git a/ghost/admin/app/components/editor/modals/update-flow.hbs b/ghost/admin/app/components/editor/modals/update-flow.hbs index df28c42ea1..8f29e51f0b 100644 --- a/ghost/admin/app/components/editor/modals/update-flow.hbs +++ b/ghost/admin/app/components/editor/modals/update-flow.hbs @@ -1,5 +1,5 @@ -
-
+
+
@@ -13,7 +13,7 @@ {{#let @data.publishOptions.post as |post|}}
-
+
{{#if (and post.isSent (not post.isPublished))}} This {{post.displayName}} was {{post.status}} by email @@ -23,7 +23,7 @@ {{/if}}
-
+

Your {{post.displayName}} @@ -86,6 +86,7 @@ type="button" class="gh-revert-to-draft" {{on "click" (fn @close (hash afterTask="revertToDraftTask"))}} + data-test-button="revert-to-draft" > Unschedule and revert to draft → @@ -95,6 +96,7 @@ type="button" class="gh-revert-to-draft" {{on "click" (fn @close (hash afterTask="revertToDraftTask"))}} + data-test-button="revert-to-draft" > Unpublish and revert to private draft → diff --git a/ghost/admin/app/components/editor/publish-options/publish-type.hbs b/ghost/admin/app/components/editor/publish-options/publish-type.hbs index 11942e9d89..5537366a13 100644 --- a/ghost/admin/app/components/editor/publish-options/publish-type.hbs +++ b/ghost/admin/app/components/editor/publish-options/publish-type.hbs @@ -10,6 +10,7 @@ checked={{eq option.value @publishOptions.selectedPublishTypeOption.value}} disabled={{option.disabled}} {{on "change" this.onChange}} + data-test-publish-type={{option.value}} > @@ -17,16 +18,16 @@ {{#if @publishOptions.emailDisabledError}} -

+

{{@publishOptions.emailDisabledError}}

{{else if (eq @publishOptions.totalMemberCount 0)}} -

+

Add members to start sending newsletters!

{{else if (not @publishOptions.mailgunIsConfigured)}} -

+

Set up Mailgun to start sending newsletters!

{{/if}} \ No newline at end of file diff --git a/ghost/admin/tests/acceptance/editor-test.js b/ghost/admin/tests/acceptance/editor-test.js index ccb90bd9da..501b776981 100644 --- a/ghost/admin/tests/acceptance/editor-test.js +++ b/ghost/admin/tests/acceptance/editor-test.js @@ -6,9 +6,6 @@ import {authenticateSession, invalidateSession} from 'ember-simple-auth/test-sup import {beforeEach, describe, it} from 'mocha'; import {blur, click, currentRouteName, currentURL, fillIn, find, findAll, triggerEvent} from '@ember/test-helpers'; import {datepickerSelect} from 'ember-power-datepicker/test-support'; -import {enableMailgun} from '../helpers/mailgun'; -import {enableNewsletters} from '../helpers/newsletters'; -import {enableStripe} from '../helpers/stripe'; import {expect} from 'chai'; import {selectChoose} from 'ember-power-select/test-support'; import {setupApplicationTest} from 'ember-mocha'; @@ -120,319 +117,101 @@ describe('Acceptance: Editor', function () { return await authenticateSession(); }); - it.skip('renders the editor correctly, PSM Publish Date and Save Button', async function () { - let [post1] = this.server.createList('post', 2, {authors: [author]}); - let futureTime = moment().tz('Etc/UTC').add(10, 'minutes'); - - // post id 1 is a draft, checking for draft behaviour now - await visit('/editor/post/1'); - - expect(currentURL(), 'currentURL') - .to.equal('/editor/post/1'); - - // open post settings menu - await click('[data-test-psm-trigger]'); - - // should error, if the publish time is in the wrong format - await fillIn('[data-test-date-time-picker-time-input]', 'foo'); - await blur('[data-test-date-time-picker-time-input]'); - - expect(find('[data-test-date-time-picker-error]').textContent.trim(), 'inline error response for invalid time') - .to.equal('Must be in format: "15:00"'); - - // should error, if the publish time is in the future - // NOTE: date must be selected first, changing the time first will save - // with the new time - await fillIn('[data-test-date-time-picker-datepicker] input', moment.tz('Etc/UTC').add(1, 'day').format('YYYY-MM-DD')); - await blur('[data-test-date-time-picker-datepicker] input'); - await fillIn('[data-test-date-time-picker-time-input]', futureTime.format('HH:mm')); - await blur('[data-test-date-time-picker-time-input]'); - - expect(find('[data-test-date-time-picker-error]').textContent.trim(), 'inline error response for future time') - .to.equal('Must be in the past'); - - // closing the PSM will reset the invalid date/time - await click('[data-test-psm-trigger]'); - await click('[data-test-psm-trigger]'); - - expect( - find('[data-test-date-time-picker-error]'), - 'date picker error after closing PSM' - ).to.not.exist; - - expect( - find('[data-test-date-time-picker-date-input]').value, - 'PSM date value after closing with invalid date' - ).to.equal(moment(post1.publishedAt).tz('Etc/UTC').format('YYYY-MM-DD')); - - expect( - find('[data-test-date-time-picker-time-input]').value, - 'PSM time value after closing with invalid date' - ).to.equal(moment(post1.publishedAt).tz('Etc/UTC').format('HH:mm')); - - // saves the post with the new date - let validTime = moment('2017-04-09 12:00').tz('Etc/UTC'); - await fillIn('[data-test-date-time-picker-time-input]', validTime.format('HH:mm')); - await blur('[data-test-date-time-picker-time-input]'); - await datepickerSelect('[data-test-date-time-picker-datepicker]', validTime.toDate()); - - // hide psm - await click('[data-test-psm-trigger]'); - - // checking the flow of the saving button for a draft - expect( - find('[data-test-publishmenu-trigger]').textContent.trim(), - 'draft publish button text' - ).to.equal('Publish'); - - expect( - find('[data-test-editor-post-status]').textContent.trim(), - 'draft status text' - ).to.match(/Draft\s+- Saved/); - - // click on publish now - await click('[data-test-publishmenu-trigger]'); - - expect( - find('[data-test-publishmenu-draft]'), - 'draft publish menu is shown' - ).to.exist; - - await click('[data-test-publishmenu-scheduled-option]'); - - expect( - find('[data-test-publishmenu-save]').textContent.trim(), - 'draft post schedule button text' - ).to.equal('Schedule'); - - await click('[data-test-publishmenu-published-option]'); - - expect( - find('[data-test-publishmenu-save]').textContent.trim(), - 'draft post publish button text' - ).to.equal('Publish'); - - // Publish the post and re-open publish menu - await click('[data-test-publishmenu-save]'); - - expect( - find('[data-test-publishmenu-save]').textContent.trim(), - 'publish menu save button updated after draft is published' - ).to.equal('Update'); - - expect( - find('[data-test-publishmenu-published]'), - 'publish menu is shown after draft published' - ).to.exist; - - expect( - find('[data-test-editor-post-status]').textContent.trim(), - 'post status updated after draft published' - ).to.equal('Published'); - - await click('[data-test-publishmenu-cancel]'); - await click('[data-test-publishmenu-trigger]'); - await click('[data-test-publishmenu-unpublished-option]'); - - expect( - find('[data-test-publishmenu-save]').textContent.trim(), - 'published post unpublish button text' - ).to.equal('Unpublish'); - - // post id 2 is a published post, checking for published post behaviour now - await visit('/editor/post/2'); - expect(currentURL(), 'currentURL').to.equal('/editor/post/2'); - - await click('[data-test-psm-trigger]'); - expect(find('[data-test-date-time-picker-date-input]').value).to.equal('2015-12-19'); - expect(find('[data-test-date-time-picker-time-input]').value).to.equal('16:25'); - - // saves the post with a new date - await datepickerSelect('[data-test-date-time-picker-datepicker]', moment('2016-05-10 10:00').toDate()); - await fillIn('[data-test-date-time-picker-time-input]', '10:00'); - await blur('[data-test-date-time-picker-time-input]'); - - await click('[data-test-psm-trigger]'); - - // saving - await click('[data-test-publishmenu-trigger]'); - - expect( - find('[data-test-publishmenu-save]').textContent.trim(), - 'published button text' - ).to.equal('Update'); - - await click('[data-test-publishmenu-save]'); - - expect( - find('[data-test-publishmenu-save]').textContent.trim(), - 'publish menu save button updated after published post is updated' - ).to.equal('Update'); - - // go to settings to change the timezone - await visit('/settings/general'); - await click('[data-test-toggle-timezone]'); - - expect(currentURL(), 'currentURL for settings') - .to.equal('/settings/general'); - expect(find('#timezone option:checked').textContent.trim(), 'default timezone') - .to.equal('(GMT) UTC'); - - // select a new timezone - find('#timezone option[value="Pacific/Kwajalein"]').selected = true; - - await triggerEvent('#timezone', 'change'); - // save the settings - await click('[data-test-button="save"]'); - - expect(find('#timezone option:checked').textContent.trim(), 'new timezone after saving') - .to.equal('(GMT +12:00) International Date Line West'); - - // and now go back to the editor - await visit('/editor/post/2'); - - expect(currentURL(), 'currentURL in editor') - .to.equal('/editor/post/2'); - - await click('[data-test-psm-trigger]'); - expect( - find('[data-test-date-time-picker-date-input]').value, - 'date after timezone change' - ).to.equal('2016-05-10'); - - expect( - find('[data-test-date-time-picker-time-input]').value, - 'time after timezone change' - ).to.equal('22:00'); - - // unpublish - await click('[data-test-publishmenu-trigger]'); - await click('[data-test-publishmenu-unpublished-option]'); - - expect( - find('[data-test-publishmenu-save]').textContent.trim(), - 'published post unpublish button text' - ).to.equal('Unpublish'); - - await click('[data-test-publishmenu-save]'); - - expect( - find('[data-test-publishmenu-save]').textContent.trim(), - 'publish menu save button updated after published post is unpublished' - ).to.equal('Publish'); - - expect( - find('[data-test-publishmenu-draft]'), - 'draft menu is shown after unpublished' - ).to.exist; - - expect( - find('[data-test-editor-post-status]').textContent.trim(), - 'post status updated after unpublished' - ).to.match(/Draft\s+- Saved/); - - // schedule post - await click('[data-test-publishmenu-cancel]'); - await click('[data-test-publishmenu-trigger]'); - await click('[data-test-publishmenu-scheduled-option]'); - - expect( - find('[data-test-publishmenu-save]').textContent.trim(), - 'draft post, schedule button text' - ).to.equal('Schedule'); - - // get time in current timezone and select the current date - // will result in the default +5mins schedule time - let newFutureTime = moment.tz('Pacific/Kwajalein'); - await datepickerSelect('[data-test-publishmenu-draft] [data-test-date-time-picker-datepicker]', new Date(newFutureTime.format().replace(/\+.*$/, ''))); - await click('[data-test-publishmenu-save]'); - - expect( - find('[data-test-publishmenu-save]').textContent.trim(), - 'publish menu save button updated after draft is scheduled' - ).to.equal('Reschedule'); - - await click('[data-test-publishmenu-cancel]'); - - expect( - find('[data-test-publishmenu-scheduled]'), - 'publish menu is not shown after closed' - ).to.not.exist; - - // expect countdown to show warning that post is scheduled to be published - await triggerEvent('[data-test-editor-post-status]', 'mouseover'); - expect(find('[data-test-schedule-countdown]').textContent.trim(), 'notification countdown') - .to.match(/to be published\s+in (4|5) minutes/); - - expect( - find('[data-test-publishmenu-trigger]').textContent.trim(), - 'scheduled publish button text' - ).to.equal('Scheduled'); - - expect( - find('[data-test-editor-post-status]').textContent.trim(), - 'scheduled post status' - ).to.match(/to be published\s+in (4|5) minutes/); - - // Re-schedule - await click('[data-test-publishmenu-trigger]'); - await click('[data-test-publishmenu-scheduled-option]'); - expect( - find('[data-test-publishmenu-save]').textContent.trim(), - 'scheduled post button reschedule text' - ).to.equal('Reschedule'); - - await click('[data-test-publishmenu-save]'); - - expect( - find('[data-test-publishmenu-save]').textContent.trim(), - 'publish menu save button text for a rescheduled post' - ).to.equal('Reschedule'); - - await click('[data-test-publishmenu-cancel]'); - - expect( - find('[data-test-publishmenu-scheduled]'), - 'publish menu is not shown after closed' - ).to.not.exist; - - expect( - find('[data-test-editor-post-status]').textContent.trim(), - 'scheduled status text' - ).to.match(/to be published\s+in (4|5) minutes/); - - // unschedule - await click('[data-test-publishmenu-trigger]'); - await click('[data-test-publishmenu-draft-option]'); - - expect( - find('[data-test-publishmenu-save]').textContent.trim(), - 'publish menu save button updated after scheduled post is unscheduled' - ).to.equal('Unschedule'); - - await click('[data-test-publishmenu-save]'); - - expect( - find('[data-test-publishmenu-save]').textContent.trim(), - 'publish menu save button updated after scheduled post is unscheduled' - ).to.equal('Publish'); - - await click('[data-test-publishmenu-cancel]'); - - expect( - find('[data-test-publishmenu-trigger]').textContent.trim(), - 'publish button text after unschedule' - ).to.equal('Publish'); - - expect( - find('[data-test-editor-post-status]').textContent.trim(), - 'status text after unschedule' - ).to.match(/Draft\s+- Saved/); - - expect( - find('[data-test-schedule-countdown]'), - 'scheduled countdown after unschedule' - ).to.not.exist; + describe('post settings menu', function () { + it('can set publish date', async function () { + let [post1] = this.server.createList('post', 2, {authors: [author]}); + let futureTime = moment().tz('Etc/UTC').add(10, 'minutes'); + + // sanity check + expect( + moment(post1.publishedAt).tz('Etc/UTC').format('YYYY-MM-DD HH:mm:ss'), + 'initial publishedAt sanity check') + .to.equal('2015-12-19 16:25:07'); + + // post id 1 is a draft, checking for draft behaviour now + await visit('/editor/post/1'); + + // open post settings menu + await click('[data-test-psm-trigger]'); + + // should error, if the publish time is in the wrong format + await fillIn('[data-test-date-time-picker-time-input]', 'foo'); + await blur('[data-test-date-time-picker-time-input]'); + + expect(find('[data-test-date-time-picker-error]').textContent.trim(), 'inline error response for invalid time') + .to.equal('Must be in format: "15:00"'); + + // should error, if the publish time is in the future + // NOTE: date must be selected first, changing the time first will save + // with the new time + await fillIn('[data-test-date-time-picker-datepicker] input', moment.tz('Etc/UTC').add(1, 'day').format('YYYY-MM-DD')); + await blur('[data-test-date-time-picker-datepicker] input'); + await fillIn('[data-test-date-time-picker-time-input]', futureTime.format('HH:mm')); + await blur('[data-test-date-time-picker-time-input]'); + + expect(find('[data-test-date-time-picker-error]').textContent.trim(), 'inline error response for future time') + .to.equal('Must be in the past'); + + // closing the PSM will reset the invalid date/time + await click('[data-test-psm-trigger]'); + await click('[data-test-psm-trigger]'); + + expect( + find('[data-test-date-time-picker-error]'), + 'date picker error after closing PSM' + ).to.not.exist; + + expect( + find('[data-test-date-time-picker-date-input]').value, + 'PSM date value after closing with invalid date' + ).to.equal(moment(post1.publishedAt).tz('Etc/UTC').format('YYYY-MM-DD')); + + expect( + find('[data-test-date-time-picker-time-input]').value, + 'PSM time value after closing with invalid date' + ).to.equal(moment(post1.publishedAt).tz('Etc/UTC').format('HH:mm')); + + // saves the post with the new date + let validTime = moment('2017-04-09 12:00'); + await fillIn('[data-test-date-time-picker-time-input]', validTime.format('HH:mm')); + await blur('[data-test-date-time-picker-time-input]'); + await datepickerSelect('[data-test-date-time-picker-datepicker]', validTime.toDate()); + + expect(moment(post1.publishedAt).tz('Etc/UTC').format('YYYY-MM-DD HH:mm:ss')).to.equal('2017-04-09 12:00:00'); + + // go to settings to change the timezone + await visit('/settings/general'); + await click('[data-test-toggle-timezone]'); + + expect(currentURL(), 'currentURL for settings') + .to.equal('/settings/general'); + expect(find('#timezone option:checked').textContent.trim(), 'default timezone') + .to.equal('(GMT) UTC'); + + // select a new timezone + find('#timezone option[value="Pacific/Kwajalein"]').selected = true; + + await triggerEvent('#timezone', 'change'); + // save the settings + await click('[data-test-button="save"]'); + + expect(find('#timezone option:checked').textContent.trim(), 'new timezone after saving') + .to.equal('(GMT +12:00) International Date Line West'); + + // and now go back to the editor + await visit('/editor/post/1'); + + await click('[data-test-psm-trigger]'); + expect( + find('[data-test-date-time-picker-date-input]').value, + 'date after timezone change' + ).to.equal('2017-04-10'); + + expect( + find('[data-test-date-time-picker-time-input]').value, + 'time after timezone change' + ).to.equal('00:00'); + }); }); it.skip('handles validation errors when scheduling', async function () { @@ -836,76 +615,4 @@ describe('Acceptance: Editor', function () { expect(body.posts[0].title).to.equal('CMD-S Test'); }); }); - - describe('regressions', function () { - let user; - - beforeEach(async function () { - const role = this.server.create('role', {name: 'Administrator'}); - user = this.server.create('user', {roles: [role]}); - this.server.loadFixtures('settings'); - return await authenticateSession(); - }); - - // BUG: opening the publish menu and selecting a scheduled time then - // closing prevents scheduling with a "Must be in the past" error - // when re-opening the menu - // https://github.com/TryGhost/Team/issues/1399 - it.skip('can close publish menu after selecting schedule then re-open, schedule, and publish without error', async function () { - const post = this.server.create('post', {status: 'draft', authors: [user]}); - - await visit(`/editor/post/${post.id}`); - await click('[data-test-publishmenu-trigger]'); - await click('[data-test-publishmenu-scheduled-option]'); - await click('[data-test-publishmenu-cancel]'); - await click('[data-test-publishmenu-trigger]'); - await click('[data-test-publishmenu-scheduled-option]'); - await click('[data-test-publishmenu-save]'); - - expect(post.attrs.status).to.equal('scheduled'); - }); - - // BUG: re-scheduling a send-only post unexpectedly switched to publish+send - // https://github.com/TryGhost/Ghost/issues/14354 - it.skip('can re-schedule an email-only post', async function () { - // Enable newsletters (extra confirmation step) - enableMailgun(this.server); - enableNewsletters(this.server, true); - - // Enable stripe to also show paid members breakdown - enableStripe(this.server); - - const newsletter = this.server.create('newsletter', {status: 'active', name: 'test newsletter', slug: 'test-newsletter'}); - this.server.createList('member', 4, {status: 'free', newsletters: [newsletter]}); - this.server.createList('member', 2, {status: 'paid', newsletters: [newsletter]}); - - const post = this.server.create('post', {status: 'draft', authors: [user]}); - - const scheduledTime = moment().add(2, 'hours'); - - await visit(`/editor/post/${post.id}`); - await click('[data-test-publishmenu-trigger]'); - await selectChoose('[data-test-distribution-action-select]', 'send'); - await click('[data-test-publishmenu-scheduled-option]'); - await datepickerSelect('[data-test-publishmenu-draft] [data-test-date-time-picker-datepicker]', new Date(scheduledTime.format().replace(/\+.*$/, ''))); - - // Expect 4 free and 2 paid recipients here - expect(find('[data-test-email-count="free-members"]')).to.contain.text('4'); - expect(find('[data-test-email-count="paid-members"]')).to.contain.text('2'); - - await click('[data-test-publishmenu-save]'); - await click('[data-test-button="confirm-schedule"]'); - - expect(post.attrs.emailOnly).to.be.true; - - await click('[data-test-publishmenu-trigger]'); - - expect(find('[data-test-publishmenu-header]')).to.contain.text('Will be sent'); - - await datepickerSelect('[data-test-publishmenu-scheduled] [data-test-date-time-picker-datepicker]', new Date(scheduledTime.add(1, 'day').format().replace(/\+.*$/, ''))); - await click('[data-test-publishmenu-save]'); - - expect(post.attrs.emailOnly).to.be.true; - }); - }); }); diff --git a/ghost/admin/tests/acceptance/editor/publish-flow-test.js b/ghost/admin/tests/acceptance/editor/publish-flow-test.js new file mode 100644 index 0000000000..b844ba8a76 --- /dev/null +++ b/ghost/admin/tests/acceptance/editor/publish-flow-test.js @@ -0,0 +1,218 @@ +import loginAsRole from '../../helpers/login-as-role'; +import moment from 'moment'; +import {blur, click, fillIn, find} from '@ember/test-helpers'; +import {disableMailgun, enableMailgun} from '../../helpers/mailgun'; +import {disableMembers, enableMembers} from '../../helpers/members'; +import {disableNewsletters, enableNewsletters} from '../../helpers/newsletters'; +import {expect} from 'chai'; +import {setupApplicationTest} from 'ember-mocha'; +import {setupMirage} from 'ember-cli-mirage/test-support'; +import {visit} from '../../helpers/visit'; + +describe('Acceptance: Publish flow', function () { + let hooks = setupApplicationTest(); + setupMirage(hooks); + + beforeEach(function () { + this.server.loadFixtures(); + }); + + it('has minimal features for contributors', async function () { + await loginAsRole('Contributor', this.server); + + const post = this.server.create('post', {status: 'draft'}); + await visit(`/editor/post/${post.id}`); + + expect(find('[data-test-button="publish-flow"]'), 'publish button').to.not.exist; + + expect(find('[data-test-button="contributor-preview"]'), 'contributor preview button').to.exist; + expect(find('[data-test-button="contributor-save"]'), 'contributor save button').to.exist; + + await fillIn('[data-test-editor-title-input]', 'Contributor save test'); + await click('[data-test-button="contributor-save"]'); + + expect(post.title, 'post title after save').to.equal('Contributor save test'); + }); + + it('triggers post validation before opening', async function () { + await loginAsRole('Administrator', this.server); + + const post = this.server.create('post', {status: 'draft'}); + await visit(`/editor/post/${post.id}`); + + await fillIn('[data-test-editor-title-input]', Array(260).join('a')); + await blur('[data-test-editor-title-input]'); + + await click('[data-test-button="publish-flow"]'); + + expect(find('.gh-alert'), 'validation shown in alert').to.exist; + expect(find('[data-test-modal="publish-flow"]'), 'publish flow modal').to.not.exist; + }); + + it('handles timezones correctly when scheduling'); + + // email unavailable state occurs when + // 1. members signup access is set to "none" + // 2. default newsletter recipients is set to "disabled" + async function testEmailUnavailableFlow() { + await loginAsRole('Administrator', this.server); + + const post = this.server.create('post', {status: 'draft'}); + await visit(`/editor/post/${post.id}`); + + await fillIn('[data-test-editor-title-input]', 'Members disabled publish test'); + await blur('[data-test-editor-title-input]'); + + await click('[data-test-button="publish-flow"]'); + + expect(find('[data-test-modal="publish-flow"]'), 'publish flow modal').to.exist; + expect(find('[data-test-publish-flow="options"]'), 'options step').to.exist; + + // members/newsletters disabled = + // - fixed "Publish on site" publish type + // - no email options shown + // - standard publish time options + expect(find('[data-test-setting="publish-type"]'), 'publish type setting').to.exist; + expect( + find('[data-test-setting="publish-type"] [data-test-setting-title]'), 'publish type title' + ).to.contain.trimmed.text('Publish on site'); + expect(find('[data-test-setting="publish-type"] [data-test-setting-title]')).to.not.match('button'); + + expect(find('[data-test-setting="email-recipients"]')).to.not.exist; + + expect(find('[data-test-setting="publish-time"]'), 'publish time setting').to.exist; + expect( + find('[data-test-setting="publish-time"] [data-test-setting-title]'), 'publish time title' + ).to.contain.trimmed.text('Right now'); + expect(find('[data-test-setting="publish-time"] [data-test-setting-title]')).to.match('button'); + + await click('[data-test-button="continue"]'); + + expect(find('[data-test-publish-flow="confirm"]'), 'confirm step').to.exist; + + expect(find('[data-test-text="confirm-details"]'), 'confirmation text') + .to.have.rendered.text('Your post will be published on your site.'); + + expect(find('[data-test-button="confirm-publish"]'), 'publish button text') + .to.have.rendered.text('Publish post, right now'); + + await click('[data-test-button="confirm-publish"]'); + + expect(post.status, 'post status after publish').to.equal('published'); + + expect(find('[data-test-publish-flow="complete"]'), 'complete step').to.exist; + expect(find('[data-test-complete-title]'), 'complete title').to.have.rendered.text('Boom. It’s out there. That’s 1 post published, keep going!'); + expect(find('[data-test-complete-bookmark]'), 'bookmark card').to.exist; + + // "revert to draft" only shown for scheduled posts + expect(find('[data-test-button="revert-to-draft"]'), 'revert-to-draft button').to.not.exist; + + // publish/preview buttons are hidden on complete step + expect(find('[data-test-button="publish-flow-preview"]'), 'preview button on complete step').to.not.exist; + expect(find('[data-test-button="publish-flow-publish"]'), 'publish button on complete step').to.not.exist; + + await click('[data-test-button="back-to-editor"]'); + + expect(find('[data-test-button="publish-flow"]'), 'publish button after publishing').to.not.exist; + expect(find('[data-test-button="update-flow"]'), 'update button after publishing').to.exist; + + await click('[data-test-button="update-flow"]'); + + expect(find('[data-test-modal="update-flow"]'), 'update flow modal').to.exist; + expect(find('[data-test-update-flow-title]')).to.have.rendered.text('This post has been published'); + expect(find('[data-test-update-flow-confirmation]')).to.contain.rendered.text('Your post was published on your site'); + const savedPublishAt = moment(post.publishedAt).utc(); + expect(find('[data-test-update-flow-confirmation]')).to.contain.rendered.text(`on ${savedPublishAt.format('D MMM YYYY')} at ${savedPublishAt.format('HH:mm')}`); + expect(find('[data-test-button="revert-to-draft"]')).to.exist; + expect(find('[data-test-button="revert-to-draft"]')).to.contain.rendered.text('Unpublish and revert to private draft'); + + await click('[data-test-button="revert-to-draft"]'); + + expect(post.status).to.equal('draft'); + + expect(find('[data-test-modal="update-flow"]')).to.not.exist; + expect(find('[data-test-button="publish-flow"]')).to.exist; + } + + it('can publish with members disabled', async function () { + await disableMembers(this.server); + await testEmailUnavailableFlow.apply(this); + }); + + it('can publish with newsletters disabled', async function () { + await enableMembers(this.server); + await disableNewsletters(this.server); + await testEmailUnavailableFlow.apply(this); + }); + + describe('members enabled', function () { + beforeEach(async function () { + enableMembers(this.server); + enableMailgun(this.server); + enableNewsletters(this.server); + + // at least one member is required for publish+send to be available + this.server.create('member'); + + await loginAsRole('Administrator', this.server); + }); + + it('can publish+send with single newsletter'); + + it('can publish+send with multiple newsletters'); + + it('can schedule publish+send'); + it('can send'); + it('can schedule send'); + it('can publish'); + it('can schedule publish'); + + it('handles Mailgun not being set up', async function () { + disableMailgun(this.server); + + const post = this.server.create('post', {status: 'draft'}); + await visit(`/editor/post/${post.id}`); + await click('[data-test-button="publish-flow"]'); + + expect( + find('[data-test-setting="publish-type"] [data-test-setting-title]'), 'publish type title' + ).to.have.trimmed.text('Publish'); + + await click('[data-test-setting="publish-type"] [data-test-setting-title]'); + + // mailgun not set up notice is shown + expect(find('[data-test-publish-type-error]'), 'publish type error').to.exist; + expect(find('[data-test-publish-type-error="no-mailgun"]'), 'publish type error text').to.exist; + + // email-related options are disabled + expect(find('[data-test-publish-type="publish+send"]')).to.have.attribute('disabled'); + expect(find('[data-test-publish-type="send"]')).to.have.attribute('disabled'); + }); + + it('handles no members present', async function () { + this.server.db.members.remove(); + + const post = this.server.create('post', {status: 'draft'}); + await visit(`/editor/post/${post.id}`); + await click('[data-test-button="publish-flow"]'); + + expect( + find('[data-test-setting="publish-type"] [data-test-setting-title]'), 'publish type title' + ).to.have.trimmed.text('Publish'); + + await click('[data-test-setting="publish-type"] [data-test-setting-title]'); + + // no-members notice is shown + expect(find('[data-test-publish-type-error]'), 'publish type error').to.exist; + expect(find('[data-test-publish-type-error="no-members"]'), 'publish type error text').to.exist; + + // email-related options are disabled + expect(find('[data-test-publish-type="publish+send"]')).to.have.attribute('disabled'); + expect(find('[data-test-publish-type="send"]')).to.have.attribute('disabled'); + }); + + it('handles member limits'); + it('handles server error when confirming'); + it('handles email sending error'); + }); +}); diff --git a/ghost/admin/tests/helpers/login-as-role.js b/ghost/admin/tests/helpers/login-as-role.js new file mode 100644 index 0000000000..3b3c654657 --- /dev/null +++ b/ghost/admin/tests/helpers/login-as-role.js @@ -0,0 +1,10 @@ +import {authenticateSession, invalidateSession} from 'ember-simple-auth/test-support'; + +export default async function loginRole(roleName, server) { + const role = server.create('role', {name: roleName}); + const user = server.create('user', {roles: [role], slug: 'test-user'}); + await invalidateSession(); + await authenticateSession(); + + return user; +} diff --git a/ghost/admin/tests/helpers/mailgun.js b/ghost/admin/tests/helpers/mailgun.js index ab537eff7f..2094c39420 100644 --- a/ghost/admin/tests/helpers/mailgun.js +++ b/ghost/admin/tests/helpers/mailgun.js @@ -1,13 +1,17 @@ -export function enableMailgun(server) { +export function enableMailgun(server, enabled = true) { server.db.settings.find({key: 'mailgun_api_key'}) - ? server.db.settings.update({key: 'mailgun_api_key'}, {value: 'MAILGUN_API_KEY'}) - : server.create('setting', {key: 'mailgun_api_key', value: 'MAILGUN_API_KEY', group: 'email'}); + ? server.db.settings.update({key: 'mailgun_api_key'}, {value: (enabled ? 'MAILGUN_API_KEY' : null)}) + : server.create('setting', {key: 'mailgun_api_key', value: (enabled ? 'MAILGUN_API_KEY' : null), group: 'email'}); server.db.settings.find({key: 'mailgun_domain'}) - ? server.db.settings.update({key: 'mailgun_domain'}, {value: 'MAILGUN_DOMAIN'}) - : server.create('setting', {key: 'mailgun_domain', value: 'MAILGUN_DOMAIN', group: 'email'}); + ? server.db.settings.update({key: 'mailgun_domain'}, {value: (enabled ? 'MAILGUN_DOMAIN' : null)}) + : server.create('setting', {key: 'mailgun_domain', value: (enabled ? 'MAILGUN_DOMAIN' : null), group: 'email'}); server.db.settings.find({key: 'mailgun_base_url'}) - ? server.db.settings.update({key: 'mailgun_base_url'}, {value: 'MAILGUN_BASE_URL'}) - : server.create('setting', {key: 'mailgun_base_url', value: 'MAILGUN_BASE_URL', group: 'email'}); + ? server.db.settings.update({key: 'mailgun_base_url'}, {value: (enabled ? 'MAILGUN_BASE_URL' : null)}) + : server.create('setting', {key: 'mailgun_base_url', value: (enabled ? 'MAILGUN_BASE_URL' : null), group: 'email'}); +} + +export function disableMailgun(server) { + enableMailgun(server, false); } diff --git a/ghost/admin/tests/helpers/members.js b/ghost/admin/tests/helpers/members.js new file mode 100644 index 0000000000..872db5da82 --- /dev/null +++ b/ghost/admin/tests/helpers/members.js @@ -0,0 +1,11 @@ +export function enableMembers(server) { + server.db.settings.find({key: 'members_signup_access'}) + ? server.db.settings.update({key: 'members_signup_access'}, {value: 'all'}) + : server.create('setting', {key: 'members_signup_access', value: 'all', group: 'members'}); +} + +export function disableMembers(server) { + server.db.settings.find({key: 'members_signup_access'}) + ? server.db.settings.update({key: 'members_signup_access'}, {value: 'none'}) + : server.create('setting', {key: 'members_signup_access', value: 'none', group: 'members'}); +} diff --git a/ghost/admin/tests/helpers/newsletters.js b/ghost/admin/tests/helpers/newsletters.js index 20d2802e23..a8d1a4e09c 100644 --- a/ghost/admin/tests/helpers/newsletters.js +++ b/ghost/admin/tests/helpers/newsletters.js @@ -1,5 +1,9 @@ -export function enableNewsletters(server, enabled) { +export function enableNewsletters(server, enabled = true) { server.db.settings.find({key: 'editor_default_email_recipients'}) - ? server.db.settings.update({key: 'editor_default_email_recipients'}, {value: enabled ? 'visibility' : 'disabled'}) - : server.create('setting', {key: 'editor_default_email_recipients', value: enabled ? 'visibility' : 'disabled', group: 'editor'}); + ? server.db.settings.update({key: 'editor_default_email_recipients'}, {value: (enabled ? 'visibility' : 'disabled')}) + : server.create('setting', {key: 'editor_default_email_recipients', value: (enabled ? 'visibility' : 'disabled'), group: 'editor'}); +} + +export function disableNewsletters(server) { + enableNewsletters(server, false); }