1
0
Fork 0
mirror of https://github.com/TryGhost/Ghost-Admin.git synced 2023-12-14 02:33:04 +01:00
Ghost-Admin/tests/acceptance/editor-test.js
Kevin Ansfield e4c0aaf7b9 "400 Version Mismatch" error handling
refs https://github.com/TryGhost/Ghost/issues/6949

Handle version mismatch errors by:
- displaying an alert asking the user to copy any data and refresh
- disabling navigation so that unsaved data is not accidentally lost

Detailed changes:
- add `error` action to application route for global route-based error handling
- remove 404-handler mixin, move logic into app route error handler
- update `.catch` in validation-engine so that promises are rejected with the
  original error objects
- add `VersionMismatchError` and `isVersionMismatchError` to ajax service
- add `upgrade-status` service
  - has a method to trigger the alert and toggle the "upgrade required" mode
  - is injected into all routes by default so that it can be checked before
    transitioning
- add `Route` override
  - updates the `willTransition` hook to check the `upgrade-status` service
    and abort the transition if we're in "upgrade required" mode
- update notifications `showAPIError` method to handle version mismatch errors
- update any areas where we were catching ajax errors manually so that the
  version mismatch error handling is obeyed
- fix redirect tests in editor acceptance test
- fix mirage's handling of 404s for unknown posts in get post requests
- adjust alert z-index to to appear above modal backgrounds
2016-07-08 14:56:26 +01:00

443 lines
20 KiB
JavaScript

/* jshint expr:true */
import {
describe,
it,
beforeEach,
afterEach
} from 'mocha';
import { expect } from 'chai';
import startApp from '../helpers/start-app';
import destroyApp from '../helpers/destroy-app';
import { invalidateSession, authenticateSession } from 'ghost-admin/tests/helpers/ember-simple-auth';
import Mirage from 'ember-cli-mirage';
import sinon from 'sinon';
describe('Acceptance: Editor', function() {
let application;
beforeEach(function() {
application = startApp();
});
afterEach(function() {
destroyApp(application);
});
it('redirects to signin when not authenticated', function () {
server.create('post');
invalidateSession(application);
visit('/editor/1');
andThen(function() {
expect(currentURL(), 'currentURL').to.equal('/signin');
});
});
it('does not redirect to team page when authenticated as author', function () {
let role = server.create('role', {name: 'Author'});
let user = server.create('user', {roles: [role], slug: 'test-user'});
server.create('post');
authenticateSession(application);
visit('/editor/1');
andThen(() => {
expect(currentURL(), 'currentURL').to.equal('/editor/1');
});
});
it('does not redirect to team page when authenticated as editor', function () {
let role = server.create('role', {name: 'Editor'});
let user = server.create('user', {roles: [role], slug: 'test-user'});
server.create('post');
authenticateSession(application);
visit('/editor/1');
andThen(() => {
expect(currentURL(), 'currentURL').to.equal('/editor/1');
});
});
it('displays 404 when post does not exist', function () {
let role = server.create('role', {name: 'Editor'});
let user = server.create('user', {roles: [role], slug: 'test-user'});
authenticateSession(application);
visit('/editor/1');
andThen(() => {
expect(currentPath()).to.equal('error404');
expect(currentURL()).to.equal('/editor/1');
});
});
describe('when logged in', function () {
beforeEach(function () {
let role = server.create('role', {name: 'Administrator'});
let user = server.create('user', {roles: [role]});
server.loadFixtures();
return authenticateSession(application);
});
it('renders the editor correctly, PSM Publish Date and Save Button', function () {
let posts = server.createList('post', 2);
let plusTenMinPacific = moment().tz('Pacific/Kwajalein').add(10, 'minutes').format('DD MMM YY @ HH:mm').toString();
let plusTwoMinPacific = moment().tz('Pacific/Kwajalein').add(2, 'minutes').format('DD MMM YY @ HH:mm').toString();
// post id 1 is a draft, checking for draft behaviour now
visit('/editor/1');
andThen(() => {
expect(currentURL(), 'currentURL')
.to.equal('/editor/1');
});
// should error, if the date input is in a wrong format
fillIn('input[name="post-setting-date"]', 'testdate');
triggerEvent('input[name="post-setting-date"]', 'blur');
andThen(() => {
expect(find('.ember-view.response').text().trim(), 'inline error response for invalid date')
.to.equal('Published Date must be a valid date with format: DD MMM YY @ HH:mm (e.g. 6 Dec 14 @ 15:00)');
});
// saves the post with the new date
fillIn('input[name="post-setting-date"]', '10 May 16 @ 10:00');
// return pauseTest();
triggerEvent('input[name="post-setting-date"]', 'blur');
// saving
click('.view-header .btn.btn-sm.js-publish-button');
andThen(() => {
expect(find('input[name="post-setting-date"]').val(), 'date after saving')
.to.equal('10 May 16 @ 10:00');
});
// should not do anything if the input date is not different
fillIn('input[name="post-setting-date"]', '10 May 16 @ 10:00');
triggerEvent('input[name="post-setting-date"]', 'blur');
andThen(() => {
expect(find('input[name="post-setting-date"]').val(), 'date didn\'t change')
.to.equal('10 May 16 @ 10:00');
});
// checking the flow of the saving button for a draft
andThen(() => {
expect(find('.view-header .btn.btn-sm.js-publish-button').hasClass('btn-red'), 'no red button expected')
.to.be.false;
expect(find('.view-header .btn.btn-sm.js-publish-button').text().trim(), 'text in save button')
.to.equal('Save Draft');
expect(find('.post-save-draft').hasClass('active'), 'highlights the default active button state for a draft')
.to.be.true;
});
// click on publish now
click('.post-save-publish a');
andThen(() => {
expect(find('.post-save-publish').hasClass('active'), 'highlights the selected active button state')
.to.be.true;
expect(find('.view-header .btn.btn-sm.js-publish-button').hasClass('btn-red'), 'red button to change from draft to published')
.to.be.true;
expect(find('.view-header .btn.btn-sm.js-publish-button').text().trim(), 'text in save button after click on \'publish now\'')
.to.equal('Publish Now');
});
// Publish the post
click('.view-header .btn.btn-sm.js-publish-button');
andThen(() => {
expect(find('.view-header .btn.btn-sm.js-publish-button').text().trim(), 'text in save button after publishing')
.to.equal('Update Post');
expect(find('.post-save-publish').hasClass('active'), 'highlights the default active button state for a published post')
.to.be.true;
expect(find('.view-header .btn.btn-sm.js-publish-button').hasClass('btn-red'), 'no red button expected')
.to.be.false;
});
// post id 2 is a published post, checking for published post behaviour now
visit('/editor/2');
andThen(() => {
expect(currentURL(), 'currentURL').to.equal('/editor/2');
expect(find('input[name="post-setting-date"]').val()).to.equal('19 Dec 15 @ 16:25');
});
// should reset the date if the input field is blank
fillIn('input[name="post-setting-date"]', '');
triggerEvent('input[name="post-setting-date"]', 'blur');
andThen(() => {
expect(find('input[name="post-setting-date"]').val(), 'empty date input')
.to.equal('');
});
// saving
click('.view-header .btn.btn-sm.js-publish-button');
andThen(() => {
expect(find('input[name="post-setting-date"]').val(), 'date value restored')
.to.equal('19 Dec 15 @ 16:25');
});
// saves the post with a new date
fillIn('input[name="post-setting-date"]', '10 May 16 @ 10:00');
triggerEvent('input[name="post-setting-date"]', 'blur');
// saving
click('.view-header .btn.btn-sm.js-publish-button');
andThen(() => {
expect(find('input[name="post-setting-date"]').val(), 'new date after saving')
.to.equal('10 May 16 @ 10:00');
});
// go to settings to change the timezone
visit('/settings/general');
andThen(() => {
expect(currentURL(), 'currentURL for settings')
.to.equal('/settings/general');
expect(find('#activeTimezone option:selected').text().trim(), 'default timezone')
.to.equal('(GMT) UTC');
// select a new timezone
find('#activeTimezone option[value="Pacific/Kwajalein"]').prop('selected', true);
});
triggerEvent('#activeTimezone select', 'change');
// save the settings
click('.view-header .btn.btn-blue');
andThen(() => {
expect(find('#activeTimezone option:selected').text().trim(), 'new timezone after saving')
.to.equal('(GMT +12:00) International Date Line West');
});
// and now go back to the editor
visit('/editor/2');
andThen(() => {
expect(currentURL(), 'currentURL in editor')
.to.equal('/editor/2');
expect(find('input[name="post-setting-date"]').val(), 'date with blog timezone')
.to.equal('10 May 16 @ 22:00');
});
// should not do anything if the input date is not different
fillIn('input[name="post-setting-date"]', '10 May 16 @ 22:00');
triggerEvent('input[name="post-setting-date"]', 'blur');
andThen(() => {
expect(find('input[name="post-setting-date"]').val(), 'date didn\'t change')
.to.equal('10 May 16 @ 22:00');
});
// click on unpublish
click('.post-save-draft a');
andThen(() => {
expect(find('.post-save-draft').hasClass('active'), 'highlights the active button state for a draft')
.to.be.true;
expect(find('.view-header .btn.btn-sm.js-publish-button').hasClass('btn-red'), 'red button to change from published to draft')
.to.be.true;
expect(find('.view-header .btn.btn-sm.js-publish-button').text().trim(), 'text in save button for post to unpublish')
.to.equal('Unpublish');
});
// Unpublish the post
click('.view-header .btn.btn-sm.js-publish-button');
andThen(() => {
expect(find('.view-header .btn.btn-sm.js-publish-button').text().trim(), 'text in save button for draft')
.to.equal('Save Draft');
expect(find('.post-save-draft').hasClass('active'), 'highlights the default active button state for a draft')
.to.be.true;
expect(find('.view-header .btn.btn-sm.js-publish-button').hasClass('btn-red'), 'no red button expected')
.to.be.false;
});
// Set the publish date 2 minute to the future to find an error message
fillIn('input[name="post-setting-date"]', plusTwoMinPacific);
triggerEvent('input[name="post-setting-date"]', 'blur');
andThen(() => {
andThen(() => {
expect(find('.ember-view.response').text().trim(), 'inline error response for invalid date in future')
.to.equal('Must be at least 2 minutes from now.');
});
});
// Set the publish date into the future (best to have it 10 minutes from now in the future)
fillIn('input[name="post-setting-date"]', plusTenMinPacific);
triggerEvent('input[name="post-setting-date"]', 'blur');
andThen(() => {
expect(find('label[for="post-setting-date"]').text().trim(), 'label changes to \'Scheduled Date\'')
.to.equal('Scheduled Date');
});
// click on 'Schedule Post'
click('.post-save-schedule a');
// button should show 'schedule post'
andThen(() => {
expect(find('.post-save-schedule').hasClass('active'), 'highlights the active button state for a draft')
.to.be.true;
expect(find('.view-header .btn.btn-sm.js-publish-button').hasClass('btn-red'), 'red button to change from published to draft')
.to.be.true;
expect(find('.view-header .btn.btn-sm.js-publish-button').text().trim(), 'text in save button for post to schedule')
.to.equal('Schedule Post');
});
// click on schedule post and save
click('.view-header .btn.btn-sm.js-publish-button');
andThen(() => {
// Dropdown menu should be 'Update Post' and 'Unschedule'
expect(find('.view-header .btn.btn-sm.js-publish-button').text().trim(), 'text in save button for scheduled post')
.to.equal('Update Post');
expect(find('.post-save-schedule').hasClass('active'), 'highlights the default active button state for a scheduled post')
.to.be.true;
expect(find('.post-save-draft').text().trim(), 'not active option should say \'Unschedule\'')
.to.equal('Unschedule');
expect(find('.view-header .btn.btn-sm.js-publish-button').hasClass('btn-red'), 'no red button expected')
.to.be.false;
// expect countdown to show warning, that post will be published in x minutes
expect(find('.gh-notification.gh-notification-schedule').text().trim(), 'notification countdown')
.to.contain('Post will be published in');
});
// click on 'Unschedule'
click('.post-save-draft a');
andThen(() => {
expect(find('.view-header .btn.btn-sm.js-publish-button').text().trim(), 'text in save button to unscheduled post')
.to.equal('Unschedule');
expect(find('.post-save-draft').hasClass('active'), 'highlights the default active button state for a scheduled post')
.to.be.true;
expect(find('.view-header .btn.btn-sm.js-publish-button').hasClass('btn-red'), 'red button expected due to status change')
.to.be.true;
});
// click on unschedule post and save
click('.view-header .btn.btn-sm.js-publish-button');
andThen(() => {
expect(find('.view-header .btn.btn-sm.js-publish-button').text().trim(), 'text in save button for a draft')
.to.equal('Save Draft');
expect(find('.post-save-draft').hasClass('active'), 'highlights the default active button state for a draft post')
.to.be.true;
expect(find('.view-header .btn.btn-sm.js-publish-button').hasClass('btn-red'), 'red button expected due to status change')
.to.be.false;
// expect no countdown notification after unscheduling
expect(find('.gh-notification.gh-notification-schedule').text().trim(), 'notification countdown')
.to.equal('');
});
});
it('renders first countdown notification before scheduled time', function () {
/* jscs:disable requireCamelCaseOrUpperCaseIdentifiers */
let clock = sinon.useFakeTimers(moment().valueOf());
let post = server.create('post', {published_at: moment.utc().add(4, 'minutes'), status: 'scheduled'});
let compareDate = moment().tz('Etc/UTC').add(4, 'minutes').format('DD MMM YY @ HH:mm').toString();
let settings = server.create('setting', {activeTimezone: 'Europe/Dublin'});
visit('/editor/1');
andThen(() => {
expect(currentURL(), 'currentURL')
.to.equal('/editor/1');
expect(find('input[name="post-setting-date"]').val(), 'scheduled date')
.to.equal(compareDate);
// Dropdown menu should be 'Update Post' and 'Unschedule'
expect(find('.view-header .btn.btn-sm.js-publish-button').text().trim(), 'text in save button for scheduled post')
.to.equal('Update Post');
expect(find('.post-save-schedule').hasClass('active'), 'highlights the default active button state for a scheduled post')
.to.be.true;
expect(find('.post-save-draft').text().trim(), 'not active option should say \'Unschedule\'')
.to.equal('Unschedule');
expect(find('.view-header .btn.btn-sm.js-publish-button').hasClass('btn-red'), 'no red button expected')
.to.be.false;
// expect countdown to show warning, that post will be published in x minutes
expect(find('.gh-notification.gh-notification-schedule').text().trim(), 'notification countdown')
.to.contain('Post will be published in');
});
clock.restore();
});
it('only shows option to unschedule post 2 minutes before scheduled time', function () {
/* jscs:disable requireCamelCaseOrUpperCaseIdentifiers */
let clock = sinon.useFakeTimers(moment().valueOf());
let post = server.create('post', {published_at: moment.utc().add(2, 'minutes'), status: 'scheduled'});
let compareDate = moment().tz('Europe/Dublin').add(2, 'minutes').format('DD MMM YY @ HH:mm').toString();
let settings = server.create('setting', {activeTimezone: 'Europe/Dublin'});
visit('/editor/1');
andThen(() => {
// Save button should say 'Unschedule'
expect(find('.view-header .btn.btn-sm.js-publish-button').text().trim(), 'text in save button for scheduled post in status freeze mode')
.to.equal('Unschedule');
// expect countdown to show warning, that post will be published in x minutes
expect(find('.gh-notification.gh-notification-schedule').text().trim(), 'notification countdown')
.to.contain('Post will be published in');
// no dropdown menu
expect(find('.btn.btn-sm.dropdown-toggle').hasClass('active'), 'no dropdown menu')
.to.be.false;
});
clock.restore();
});
it('lets user unschedule the post shortly before scheduled date', function () {
/* jscs:disable requireCamelCaseOrUpperCaseIdentifiers */
let clock = sinon.useFakeTimers(moment().valueOf());
let post = server.create('post', {published_at: moment.utc().add(1, 'minute'), status: 'scheduled'});
let compareDate = moment().tz('Europe/Dublin').add(1, 'minute').format('DD MMM YY @ HH:mm').toString();
let settings = server.create('setting', {activeTimezone: 'Europe/Dublin'});
visit('/editor/1');
// change some text
fillIn('.markdown-editor', 'Let\'s make some markdown changes');
andThen(() => {
// Save button should say 'Unschedule'
expect(find('.view-header .btn.btn-sm.js-publish-button').text().trim(), 'text in save button for scheduled post in status freeze mode')
.to.equal('Unschedule');
// expect countdown to show warning, that post will be published in x minutes
expect(find('.gh-notification.gh-notification-schedule').text().trim(), 'notification countdown')
.to.contain('Post will be published in');
// no dropdown menu
expect(find('.btn.btn-sm.dropdown-toggle').hasClass('active'), 'no dropdown menu')
.to.be.false;
});
// click on Unschedule
click('.view-header .btn.btn-sm.js-publish-button');
andThen(() => {
expect(find('.markdown-editor').val(), 'changed text in markdown editor')
.to.equal('Let\'s make some markdown changes');
expect(find('.view-header .btn.btn-sm.js-publish-button').text().trim(), 'text in save button for a draft')
.to.equal('Save Draft');
expect(find('.post-save-draft').hasClass('active'), 'highlights the default active button state for a draft post')
.to.be.true;
expect(find('.view-header .btn.btn-sm.js-publish-button').hasClass('btn-red'), 'red button expected due to status change')
.to.be.false;
// expect no countdown notification after unscheduling
expect(find('.gh-notification.gh-notification-schedule').text().trim(), 'notification countdown')
.to.equal('');
});
clock.restore();
});
});
});