import ctrlOrCmd from 'ghost-admin/utils/ctrl-or-cmd'; import moment from 'moment'; import sinon from 'sinon'; import {Response} from 'miragejs'; import {authenticateSession, invalidateSession} from 'ember-simple-auth/test-support'; 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 {expect} from 'chai'; import {selectChoose} from 'ember-power-select/test-support'; import {setupApplicationTest} from 'ember-mocha'; import {setupMirage} from 'ember-cli-mirage/test-support'; import {visit} from '../helpers/visit'; // TODO: update ember-power-datepicker to expose modern test helpers // https://github.com/cibernox/ember-power-datepicker/issues/30 describe('Acceptance: Editor', function () { let hooks = setupApplicationTest(); setupMirage(hooks); it('redirects to signin when not authenticated', async function () { let author = this.server.create('user'); // necesary for post-author association this.server.create('post', {authors: [author]}); await invalidateSession(); await visit('/editor/post/1'); expect(currentURL(), 'currentURL').to.equal('/signin'); }); it('does not redirect to staff page when authenticated as contributor', async function () { let role = this.server.create('role', {name: 'Contributor'}); let author = this.server.create('user', {roles: [role], slug: 'test-user'}); this.server.create('post', {authors: [author]}); await authenticateSession(); await visit('/editor/post/1'); expect(currentURL(), 'currentURL').to.equal('/editor/post/1'); }); it('does not redirect to staff page when authenticated as author', async function () { let role = this.server.create('role', {name: 'Author'}); let author = this.server.create('user', {roles: [role], slug: 'test-user'}); this.server.create('post', {authors: [author]}); await authenticateSession(); await visit('/editor/post/1'); expect(currentURL(), 'currentURL').to.equal('/editor/post/1'); }); it('does not redirect to staff page when authenticated as editor', async function () { let role = this.server.create('role', {name: 'Editor'}); let author = this.server.create('user', {roles: [role], slug: 'test-user'}); this.server.create('post', {authors: [author]}); await authenticateSession(); await visit('/editor/post/1'); expect(currentURL(), 'currentURL').to.equal('/editor/post/1'); }); it('displays 404 when post does not exist', async function () { let role = this.server.create('role', {name: 'Editor'}); this.server.create('user', {roles: [role], slug: 'test-user'}); await authenticateSession(); await visit('/editor/post/1'); expect(currentRouteName()).to.equal('error404'); expect(currentURL()).to.equal('/editor/post/1'); }); it('when logged in as a contributor, renders a save button instead of a publish menu & hides tags input', async function () { let role = this.server.create('role', {name: 'Contributor'}); let author = this.server.create('user', {roles: [role]}); this.server.createList('post', 2, {authors: [author]}); this.server.loadFixtures('settings'); await authenticateSession(); // post id 1 is a draft, checking for draft behaviour now await visit('/editor/post/1'); expect(currentURL(), 'currentURL').to.equal('/editor/post/1'); // Expect publish menu to not exist expect( find('[data-test-publishmenu-trigger]'), 'publish menu trigger' ).to.not.exist; // Open post settings menu await click('[data-test-psm-trigger]'); // Check to make sure that tags input doesn't exist expect( find('[data-test-token-input]'), 'tags input' ).to.not.exist; // post id 2 is published, we should be redirected to index await visit('/editor/post/2'); expect(currentURL(), 'currentURL').to.equal('/posts'); }); describe('when logged in', function () { let author; beforeEach(async function () { let role = this.server.create('role', {name: 'Administrator'}); author = this.server.create('user', {roles: [role]}); this.server.loadFixtures('settings'); return await authenticateSession(); }); 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 () { this.server.put('/posts/:id/', function () { return new Response(422, {}, { errors: [{ type: 'ValidationError', message: 'Error test' }] }); }); let post = this.server.create('post', 1, {authors: [author], status: 'draft'}); let plusTenMin = moment().utc().add(10, 'minutes'); await visit(`/editor/post/${post.id}`); await click('[data-test-publishmenu-trigger]'); await click('[data-test-publishmenu-scheduled-option]'); await datepickerSelect('[data-test-publishmenu-draft] [data-test-date-time-picker-datepicker]', plusTenMin.toDate()); await fillIn('[data-test-publishmenu-draft] [data-test-date-time-picker-time-input]', plusTenMin.format('HH:mm')); await blur('[data-test-publishmenu-draft] [data-test-date-time-picker-time-input]'); await click('[data-test-publishmenu-save]'); expect( findAll('.gh-alert').length, 'number of alerts after failed schedule' ).to.equal(1); expect( find('.gh-alert').textContent, 'alert text after failed schedule' ).to.match(/Error test/); }); it.skip('handles title validation errors correctly', async function () { this.server.create('post', {authors: [author]}); // post id 1 is a draft, checking for draft behaviour now await visit('/editor/post/1'); expect(currentURL(), 'currentURL') .to.equal('/editor/post/1'); await fillIn('[data-test-editor-title-input]', Array(260).join('a')); await click('[data-test-publishmenu-trigger]'); await click('[data-test-publishmenu-save]'); expect( findAll('.gh-alert').length, 'number of alerts after invalid title' ).to.equal(1); expect( find('.gh-alert').textContent, 'alert text after invalid title' ).to.match(/Title cannot be longer than 255 characters/); }); // NOTE: these tests are specific to the mobiledoc editor // it('inserts a placeholder if the title is blank', async function () { // this.server.createList('post', 1); // // // post id 1 is a draft, checking for draft behaviour now // await visit('/editor/post/1'); // // expect(currentURL(), 'currentURL') // .to.equal('/editor/post/1'); // // await titleRendered(); // // let title = find('#koenig-title-input div'); // expect(title.data('placeholder')).to.equal('Your Post Title'); // expect(title.hasClass('no-content')).to.be.false; // // await replaceTitleHTML(''); // expect(title.hasClass('no-content')).to.be.true; // // await replaceTitleHTML('test'); // expect(title.hasClass('no-content')).to.be.false; // }); // // it('removes HTML from the title.', async function () { // this.server.createList('post', 1); // // // post id 1 is a draft, checking for draft behaviour now // await visit('/editor/post/1'); // // expect(currentURL(), 'currentURL') // .to.equal('/editor/post/1'); // // await titleRendered(); // // let title = find('#koenig-title-input div'); // await replaceTitleHTML('