import moment from 'moment'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import {Response} from 'ember-cli-mirage'; import {authenticateSession, invalidateSession} from 'ember-simple-auth/test-support'; import {beforeEach, describe, it} from 'mocha'; import {blur, click, currentURL, fillIn, find, findAll} from '@ember/test-helpers'; import {expect} from 'chai'; import {setupApplicationTest} from 'ember-mocha'; import {visit} from '../helpers/visit'; describe('Acceptance: Setup', function () { let hooks = setupApplicationTest(); setupMirage(hooks); it('redirects if already authenticated', async function () { let role = this.server.create('role', {name: 'Author'}); this.server.create('user', {roles: [role], slug: 'test-user'}); await authenticateSession(); await visit('/setup/one'); expect(currentURL()).to.equal('/'); await visit('/setup/two'); expect(currentURL()).to.equal('/'); await visit('/setup/three'); expect(currentURL()).to.equal('/'); }); it('redirects to signin if already set up', async function () { // mimick an already setup blog this.server.get('/authentication/setup/', function () { return { setup: [ {status: true} ] }; }); await invalidateSession(); await visit('/setup'); expect(currentURL()).to.equal('/signin'); }); describe('with a new blog', function () { beforeEach(function () { // mimick a new blog this.server.get('/authentication/setup/', function () { return { setup: [ {status: false} ] }; }); }); it('has a successful happy path', async function () { await invalidateSession(); this.server.loadFixtures('roles'); await visit('/setup'); // it redirects to step one expect(currentURL(), 'url after accessing /setup') .to.equal('/setup/one'); // it highlights first step let stepIcons = findAll('.gh-flow-nav .step'); expect(stepIcons.length, 'sanity check: three steps').to.equal(3); expect(stepIcons[0], 'first step').to.have.class('active'); expect(stepIcons[1], 'second step').to.not.have.class('active'); expect(stepIcons[2], 'third step').to.not.have.class('active'); // it displays download count (count increments for each ajax call // and polling is disabled in testing so our count should be "1" expect(find('.gh-flow-content em').textContent.trim()).to.equal('1'); await click('.gh-btn-green'); // it transitions to step two expect(currentURL(), 'url after clicking "Create your account"') .to.equal('/setup/two'); // email field is focused by default // NOTE: $('x').is(':focus') doesn't work in phantomjs CLI runner // https://github.com/ariya/phantomjs/issues/10427 expect(findAll('[data-test-blog-title-input]')[0] === document.activeElement, 'blog title has focus') .to.be.true; await click('.gh-btn-green'); // it marks fields as invalid expect(findAll('.form-group.error').length, 'number of invalid fields') .to.equal(4); // it displays error messages expect(findAll('.error .response').length, 'number of in-line validation messages') .to.equal(4); // it displays main error expect(findAll('.main-error').length, 'main error is displayed') .to.equal(1); // enter valid details and submit await fillIn('[data-test-email-input]', 'test@example.com'); await fillIn('[data-test-name-input]', 'Test User'); await fillIn('[data-test-password-input]', 'thisissupersafe'); await fillIn('[data-test-blog-title-input]', 'Blog Title'); await click('.gh-btn-green'); // it transitions to step 3 expect(currentURL(), 'url after submitting step two') .to.equal('/setup/three'); // submit button is "disabled" expect(find('button[type="submit"]').classList.contains('gh-btn-green'), 'invite button with no emails is white') .to.be.false; // fill in a valid email await fillIn('[name="users"]', 'new-user@example.com'); // submit button is "enabled" expect(find('button[type="submit"]').classList.contains('gh-btn-green'), 'invite button is green with valid email address') .to.be.true; // submit the invite form await click('button[type="submit"]'); // it redirects to the home / "content" screen expect(currentURL(), 'url after submitting invites') .to.equal('/'); // it displays success alert expect(findAll('.gh-alert-green').length, 'number of success alerts') .to.equal(1); }); it('handles validation errors in step 2', async function () { let postCount = 0; await invalidateSession(); this.server.loadFixtures('roles'); this.server.post('/authentication/setup', function () { postCount += 1; // validation error if (postCount === 1) { return new Response(422, {}, { errors: [ { errorType: 'ValidationError', message: 'Server response message' } ] }); } // server error if (postCount === 2) { return new Response(500, {}, null); } }); await visit('/setup/two'); await click('.gh-btn-green'); // non-server validation expect(find('.main-error').textContent.trim(), 'error text') .to.not.be.empty; await fillIn('[data-test-email-input]', 'test@example.com'); await fillIn('[data-test-name-input]', 'Test User'); await fillIn('[data-test-password-input]', 'thisissupersafe'); await fillIn('[data-test-blog-title-input]', 'Blog Title'); // first post - simulated validation error await click('.gh-btn-green'); expect(find('.main-error').textContent.trim(), 'error text') .to.equal('Server response message'); // second post - simulated server error await click('.gh-btn-green'); expect(find('.main-error').textContent.trim(), 'error text') .to.be.empty; expect(findAll('.gh-alert-red').length, 'number of alerts') .to.equal(1); }); it('handles invalid origin error on step 2', async function () { // mimick the API response for an invalid origin this.server.post('/session', function () { return new Response(401, {}, { errors: [ { errorType: 'UnauthorizedError', message: 'Access Denied from url: unknown.com. Please use the url configured in config.js.' } ] }); }); await invalidateSession(); this.server.loadFixtures('roles'); await visit('/setup/two'); await fillIn('[data-test-email-input]', 'test@example.com'); await fillIn('[data-test-name-input]', 'Test User'); await fillIn('[data-test-password-input]', 'thisissupersafe'); await fillIn('[data-test-blog-title-input]', 'Blog Title'); await click('.gh-btn-green'); // button should not be spinning expect(findAll('.gh-btn-green .spinner').length, 'button has spinner') .to.equal(0); // we should show an error message expect(find('.main-error').textContent, 'error text') .to.have.string('Access Denied from url: unknown.com. Please use the url configured in config.js.'); }); it('handles validation errors in step 3', async function () { let input = '[name="users"]'; let postCount = 0; let button, formGroup; await invalidateSession(); this.server.loadFixtures('roles'); this.server.post('/invites/', function ({invites}) { let attrs = this.normalizedRequestAttrs(); postCount += 1; // invalid if (postCount === 1) { return new Response(422, {}, { errors: [ { errorType: 'ValidationError', message: 'Dummy validation error' } ] }); } // TODO: duplicated from mirage/config/invites - extract method? attrs.token = `${invites.all().models.length}-token`; attrs.expires = moment.utc().add(1, 'day').valueOf(); attrs.createdAt = moment.utc().format(); attrs.createdBy = 1; attrs.updatedAt = moment.utc().format(); attrs.updatedBy = 1; attrs.status = 'sent'; return invites.create(attrs); }); // complete step 2 so we can access step 3 await visit('/setup/two'); await fillIn('[data-test-email-input]', 'test@example.com'); await fillIn('[data-test-name-input]', 'Test User'); await fillIn('[data-test-password-input]', 'thisissupersafe'); await fillIn('[data-test-blog-title-input]', 'Blog Title'); await click('.gh-btn-green'); // default field/button state formGroup = find('.gh-flow-invite .form-group'); button = find('.gh-flow-invite button[type="submit"]'); expect(formGroup, 'default field has error class') .to.not.have.class('error'); expect(button.textContent, 'default button text') .to.have.string('Invite some users'); expect(button, 'default button is disabled') .to.have.class('gh-btn-minor'); // no users submitted state await click('.gh-flow-invite button[type="submit"]'); expect(formGroup, 'no users submitted field has error class') .to.have.class('error'); expect(button.textContent, 'no users submitted button text') .to.have.string('No users to invite'); expect(button, 'no users submitted button is disabled') .to.have.class('gh-btn-minor'); // single invalid email await fillIn(input, 'invalid email'); await blur(input); expect(formGroup, 'invalid field has error class') .to.have.class('error'); expect(button.textContent, 'single invalid button text') .to.have.string('1 invalid email address'); expect(button, 'invalid email button is disabled') .to.have.class('gh-btn-minor'); // multiple invalid emails await fillIn(input, 'invalid email\nanother invalid address'); await blur(input); expect(button.textContent, 'multiple invalid button text') .to.have.string('2 invalid email addresses'); // single valid email await fillIn(input, 'invited@example.com'); await blur(input); expect(formGroup, 'valid field has error class') .to.not.have.class('error'); expect(button.textContent, 'single valid button text') .to.have.string('Invite 1 user'); expect(button, 'valid email button is enabled') .to.have.class('gh-btn-green'); // multiple valid emails await fillIn(input, 'invited1@example.com\ninvited2@example.com'); await blur(input); expect(button.textContent, 'multiple valid button text') .to.have.string('Invite 2 users'); // submit invitations with simulated failure on 1 invite await click('.gh-btn-green'); // it redirects to the home / "content" screen expect(currentURL(), 'url after submitting invites') .to.equal('/'); // it displays success alert expect(findAll('.gh-alert-green').length, 'number of success alerts') .to.equal(1); // it displays failure alert expect(findAll('.gh-alert-red').length, 'number of failure alerts') .to.equal(1); }); }); });