mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-12-01 05:50:35 +03:00
f198bb8a45
no issue - By default, action buttons had auto-reset off and reset had to be explicitly set - Auto reset for action buttons is on by default now, and any button that should not reset should explicitly switch it off
922 lines
37 KiB
JavaScript
922 lines
37 KiB
JavaScript
import ctrlOrCmd from 'ghost-admin/utils/ctrl-or-cmd';
|
|
import moment from 'moment';
|
|
import windowProxy from 'ghost-admin/utils/window-proxy';
|
|
import {Response} from 'ember-cli-mirage';
|
|
import {afterEach, beforeEach, describe, it} from 'mocha';
|
|
import {authenticateSession, invalidateSession} from 'ember-simple-auth/test-support';
|
|
import {
|
|
blur,
|
|
click,
|
|
currentRouteName,
|
|
currentURL,
|
|
fillIn,
|
|
find,
|
|
findAll,
|
|
focus,
|
|
triggerEvent,
|
|
triggerKeyEvent
|
|
} from '@ember/test-helpers';
|
|
import {expect} from 'chai';
|
|
import {setupApplicationTest} from 'ember-mocha';
|
|
import {setupMirage} from 'ember-cli-mirage/test-support';
|
|
import {visit} from '../helpers/visit';
|
|
|
|
describe('Acceptance: Staff', function () {
|
|
let hooks = setupApplicationTest();
|
|
setupMirage(hooks);
|
|
|
|
it('redirects to signin when not authenticated', async function () {
|
|
await invalidateSession();
|
|
await visit('/staff');
|
|
|
|
expect(currentURL()).to.equal('/signin');
|
|
});
|
|
|
|
it('redirects correctly when authenticated as contributor', async function () {
|
|
let role = this.server.create('role', {name: 'Contributor'});
|
|
this.server.create('user', {roles: [role], slug: 'test-user'});
|
|
|
|
this.server.create('user', {slug: 'no-access'});
|
|
|
|
await authenticateSession();
|
|
await visit('/staff/no-access');
|
|
|
|
expect(currentURL(), 'currentURL').to.equal('/staff/test-user');
|
|
});
|
|
|
|
it('redirects correctly when authenticated as author', async function () {
|
|
let role = this.server.create('role', {name: 'Author'});
|
|
this.server.create('user', {roles: [role], slug: 'test-user'});
|
|
|
|
this.server.create('user', {slug: 'no-access'});
|
|
|
|
await authenticateSession();
|
|
await visit('/staff/no-access');
|
|
|
|
expect(currentURL(), 'currentURL').to.equal('/staff/test-user');
|
|
});
|
|
|
|
it('redirects correctly when authenticated as editor', async function () {
|
|
let role = this.server.create('role', {name: 'Editor'});
|
|
this.server.create('user', {roles: [role], slug: 'test-user'});
|
|
|
|
this.server.create('user', {slug: 'no-access'});
|
|
|
|
await authenticateSession();
|
|
await visit('/staff/no-access');
|
|
|
|
expect(currentURL(), 'currentURL').to.equal('/staff');
|
|
});
|
|
|
|
describe('when logged in as admin', function () {
|
|
let admin, adminRole, suspendedUser;
|
|
|
|
beforeEach(async function () {
|
|
this.server.loadFixtures('roles');
|
|
adminRole = this.server.schema.roles.find(1);
|
|
|
|
admin = this.server.create('user', {email: 'admin@example.com', roles: [adminRole]});
|
|
|
|
// add an expired invite
|
|
this.server.create('invite', {expires: moment.utc().subtract(1, 'day').valueOf(), role: adminRole});
|
|
|
|
// add a suspended user
|
|
suspendedUser = this.server.create('user', {email: 'suspended@example.com', roles: [adminRole], status: 'inactive'});
|
|
|
|
return await authenticateSession();
|
|
});
|
|
|
|
it('it renders and navigates correctly', async function () {
|
|
let user1 = this.server.create('user');
|
|
let user2 = this.server.create('user');
|
|
|
|
await visit('/staff');
|
|
|
|
// doesn't do any redirecting
|
|
expect(currentURL(), 'currentURL').to.equal('/staff');
|
|
|
|
// it has correct page title
|
|
expect(document.title, 'page title').to.equal('Staff - Test Blog');
|
|
|
|
// it shows active users in active section
|
|
expect(
|
|
findAll('[data-test-active-users] [data-test-user-id]').length,
|
|
'number of active users'
|
|
).to.equal(3);
|
|
expect(
|
|
find(`[data-test-active-users] [data-test-user-id="${user1.id}"]`)
|
|
).to.exist;
|
|
expect(
|
|
find(`[data-test-active-users] [data-test-user-id="${user2.id}"]`)
|
|
).to.exist;
|
|
expect(
|
|
find(`[data-test-active-users] [data-test-user-id="${admin.id}"]`)
|
|
).to.exist;
|
|
|
|
// it shows suspended users in suspended section
|
|
expect(
|
|
findAll('[data-test-suspended-users] [data-test-user-id]').length,
|
|
'number of suspended users'
|
|
).to.equal(1);
|
|
expect(
|
|
find(`[data-test-suspended-users] [data-test-user-id="${suspendedUser.id}"]`)
|
|
).to.exist;
|
|
|
|
await click(`[data-test-user-id="${user2.id}"]`);
|
|
|
|
// url is correct
|
|
expect(currentURL(), 'url after clicking user').to.equal(`/staff/${user2.slug}`);
|
|
|
|
// title is correct
|
|
expect(document.title, 'title after clicking user').to.equal('Staff - User - Test Blog');
|
|
|
|
// view title should exist and be linkable and active
|
|
expect(
|
|
find('[data-test-screen-title] a[href="/ghost/staff"]').classList.contains('active'),
|
|
'has linkable url back to staff main page'
|
|
).to.be.true;
|
|
|
|
await click('[data-test-screen-title] a');
|
|
|
|
// url should be /staff again
|
|
expect(currentURL(), 'url after clicking back').to.equal('/staff');
|
|
});
|
|
|
|
it('can manage invites', async function () {
|
|
await visit('/staff');
|
|
|
|
// invite user button exists
|
|
expect(
|
|
find('.view-actions .gh-btn-green'),
|
|
'invite people button'
|
|
).to.exist;
|
|
|
|
// existing users are listed
|
|
expect(
|
|
findAll('[data-test-user-id]').length,
|
|
'initial number of active users'
|
|
).to.equal(2);
|
|
|
|
expect(
|
|
find('[data-test-user-id="1"] [data-test-role-name]').textContent.trim(),
|
|
'active user\'s role label'
|
|
).to.equal('Administrator');
|
|
|
|
// existing invites are shown
|
|
expect(
|
|
findAll('[data-test-invite-id]').length,
|
|
'initial number of invited users'
|
|
).to.equal(1);
|
|
|
|
expect(
|
|
find('[data-test-invite-id="1"] [data-test-invite-description]').textContent,
|
|
'expired invite description'
|
|
).to.match(/expired/);
|
|
|
|
// remove expired invite
|
|
await click('[data-test-invite-id="1"] [data-test-revoke-button]');
|
|
|
|
expect(
|
|
findAll('[data-test-invite-id]').length,
|
|
'initial number of invited users'
|
|
).to.equal(0);
|
|
|
|
// click the invite people button
|
|
await click('.view-actions .gh-btn-green');
|
|
|
|
let roleOptions = findAll('.fullscreen-modal select[name="role"] option');
|
|
|
|
function checkOwnerExists() {
|
|
for (let i in roleOptions) {
|
|
if (roleOptions[i].tagName === 'option' && roleOptions[i].text === 'Owner') {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function checkSelectedIsAuthor() {
|
|
for (let i in roleOptions) {
|
|
if (roleOptions[i].selected) {
|
|
return roleOptions[i].text === 'Author';
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// modal is displayed
|
|
expect(
|
|
find('.fullscreen-modal h1').textContent.trim(),
|
|
'correct modal is displayed'
|
|
).to.equal('Invite a New User');
|
|
|
|
// number of roles is correct
|
|
expect(
|
|
findAll('.fullscreen-modal select[name="role"] option').length,
|
|
'number of selectable roles'
|
|
).to.equal(3);
|
|
|
|
expect(checkOwnerExists(), 'owner role isn\'t available').to.be.false;
|
|
expect(checkSelectedIsAuthor(), 'author role is selected initially').to.be.true;
|
|
|
|
// submit valid invite form
|
|
await fillIn('.fullscreen-modal input[name="email"]', 'invite1@example.com');
|
|
await click('.fullscreen-modal .gh-btn-green');
|
|
|
|
// modal closes
|
|
expect(
|
|
findAll('[data-test-modal]').length,
|
|
'number of modals after sending invite'
|
|
).to.equal(0);
|
|
|
|
// invite is displayed, has correct e-mail + role
|
|
expect(
|
|
findAll('[data-test-invite-id]').length,
|
|
'number of invites after first invite'
|
|
).to.equal(1);
|
|
|
|
expect(
|
|
find('[data-test-invite-id="2"] [data-test-email]').textContent.trim(),
|
|
'displayed email of first invite'
|
|
).to.equal('invite1@example.com');
|
|
|
|
expect(
|
|
find('[data-test-invite-id="2"] [data-test-role-name]').textContent.trim(),
|
|
'displayed role of first invite'
|
|
).to.equal('Author');
|
|
|
|
expect(
|
|
find('[data-test-invite-id="2"] [data-test-invite-description]').textContent,
|
|
'new invite description'
|
|
).to.match(/expires/);
|
|
|
|
// number of users is unchanged
|
|
expect(
|
|
findAll('[data-test-user-id]').length,
|
|
'number of active users after first invite'
|
|
).to.equal(2);
|
|
|
|
// submit new invite with different role
|
|
await click('.view-actions .gh-btn-green');
|
|
await fillIn('.fullscreen-modal input[name="email"]', 'invite2@example.com');
|
|
await fillIn('.fullscreen-modal select[name="role"]', '2');
|
|
await click('.fullscreen-modal .gh-btn-green');
|
|
|
|
// number of invites increases
|
|
expect(
|
|
findAll('[data-test-invite-id]').length,
|
|
'number of invites after second invite'
|
|
).to.equal(2);
|
|
|
|
// invite has correct e-mail + role
|
|
expect(
|
|
find('[data-test-invite-id="3"] [data-test-email]').textContent.trim(),
|
|
'displayed email of second invite'
|
|
).to.equal('invite2@example.com');
|
|
|
|
expect(
|
|
find('[data-test-invite-id="3"] [data-test-role-name]').textContent.trim(),
|
|
'displayed role of second invite'
|
|
).to.equal('Editor');
|
|
|
|
// submit invite form with existing user
|
|
await click('.view-actions .gh-btn-green');
|
|
await fillIn('.fullscreen-modal input[name="email"]', 'admin@example.com');
|
|
await click('.fullscreen-modal .gh-btn-green');
|
|
|
|
// validation message is displayed
|
|
expect(
|
|
find('.fullscreen-modal .error .response').textContent.trim(),
|
|
'inviting existing user error'
|
|
).to.equal('A user with that email address already exists.');
|
|
|
|
// submit invite form with existing invite
|
|
await fillIn('.fullscreen-modal input[name="email"]', 'invite1@example.com');
|
|
await click('.fullscreen-modal .gh-btn-green');
|
|
|
|
// validation message is displayed
|
|
expect(
|
|
find('.fullscreen-modal .error .response').textContent.trim(),
|
|
'inviting invited user error'
|
|
).to.equal('A user with that email address was already invited.');
|
|
|
|
// submit invite form with an invalid email
|
|
await fillIn('.fullscreen-modal input[name="email"]', 'test');
|
|
await click('.fullscreen-modal .gh-btn-green');
|
|
|
|
// validation message is displayed
|
|
expect(
|
|
find('.fullscreen-modal .error .response').textContent.trim(),
|
|
'inviting invalid email error'
|
|
).to.equal('Invalid Email.');
|
|
|
|
await click('.fullscreen-modal a.close');
|
|
// revoke latest invite
|
|
await click('[data-test-invite-id="3"] [data-test-revoke-button]');
|
|
|
|
// number of invites decreases
|
|
expect(
|
|
findAll('[data-test-invite-id]').length,
|
|
'number of invites after revoke'
|
|
).to.equal(1);
|
|
|
|
// notification is displayed
|
|
expect(
|
|
find('.gh-notification:last-of-type').textContent.trim(),
|
|
'notifications contain revoke'
|
|
).to.match(/Invitation revoked\s+invite2@example\.com/);
|
|
|
|
// correct invite is removed
|
|
expect(
|
|
find('[data-test-invite-id] [data-test-email]').textContent.trim(),
|
|
'displayed email of remaining invite'
|
|
).to.equal('invite1@example.com');
|
|
|
|
// add another invite to test ordering on resend
|
|
await click('.view-actions .gh-btn-green');
|
|
await fillIn('.fullscreen-modal input[name="email"]', 'invite3@example.com');
|
|
await click('.fullscreen-modal .gh-btn-green');
|
|
|
|
// new invite should be last in the list
|
|
expect(
|
|
find('[data-test-invite-id]:last-of-type [data-test-email]').textContent.trim(),
|
|
'last invite email in list'
|
|
).to.equal('invite3@example.com');
|
|
|
|
// resend first invite
|
|
await click('[data-test-invite-id="2"] [data-test-resend-button]');
|
|
|
|
// notification is displayed
|
|
expect(
|
|
find('.gh-notification:last-of-type').textContent.trim(),
|
|
'notifications contain resend'
|
|
).to.match(/Invitation resent! \(invite1@example\.com\)/);
|
|
|
|
// first invite is still at the top
|
|
expect(
|
|
find('[data-test-invite-id]:first-of-type [data-test-email]').textContent.trim(),
|
|
'first invite email in list'
|
|
).to.equal('invite1@example.com');
|
|
|
|
// regression test: can revoke a resent invite
|
|
await click('[data-test-invite-id]:first-of-type [data-test-resend-button]');
|
|
await click('[data-test-invite-id]:first-of-type [data-test-revoke-button]');
|
|
|
|
// number of invites decreases
|
|
expect(
|
|
findAll('[data-test-invite-id]').length,
|
|
'number of invites after resend/revoke'
|
|
).to.equal(1);
|
|
|
|
// notification is displayed
|
|
expect(
|
|
find('.gh-notification:last-of-type').textContent.trim(),
|
|
'notifications contain revoke after resend/revoke'
|
|
).to.match(/Invitation revoked\s+invite1@example\.com/);
|
|
});
|
|
|
|
it('can manage suspended users', async function () {
|
|
await visit('/staff');
|
|
await click(`[data-test-user-id="${suspendedUser.id}"]`);
|
|
|
|
expect(find('[data-test-suspended-badge]')).to.exist;
|
|
|
|
await click('[data-test-user-actions]');
|
|
await click('[data-test-unsuspend-button]');
|
|
await click('[data-test-modal-confirm]');
|
|
|
|
// NOTE: there seems to be a timing issue with this test - pausing
|
|
// here confirms that the badge is removed but the andThen is firing
|
|
// before the page is updated
|
|
// andThen(() => {
|
|
// expect('[data-test-suspended-badge]').to.not.exist;
|
|
// });
|
|
|
|
await click('[data-test-staff-link]');
|
|
// suspendedUser is now in active list
|
|
expect(
|
|
find(`[data-test-active-users] [data-test-user-id="${suspendedUser.id}"]`)
|
|
).to.exist;
|
|
|
|
// no suspended users
|
|
expect(
|
|
findAll('[data-test-suspended-users] [data-test-user-id]').length
|
|
).to.equal(0);
|
|
|
|
await click(`[data-test-user-id="${suspendedUser.id}"]`);
|
|
|
|
await click('[data-test-user-actions]');
|
|
await click('[data-test-suspend-button]');
|
|
await click('[data-test-modal-confirm]');
|
|
expect(find('[data-test-suspended-badge]')).to.exist;
|
|
});
|
|
|
|
it('can delete users', async function () {
|
|
let user1 = this.server.create('user');
|
|
let user2 = this.server.create('user');
|
|
let post = this.server.create('post', {authors: [user2]});
|
|
|
|
// we don't have a full many-to-many relationship in mirage so we
|
|
// need to add the inverse manually
|
|
user2.posts = [post];
|
|
user2.save();
|
|
|
|
await visit('/staff');
|
|
await click(`[data-test-user-id="${user1.id}"]`);
|
|
|
|
// user deletion displays modal
|
|
await click('button.delete');
|
|
expect(
|
|
findAll('[data-test-modal="delete-user"]').length,
|
|
'user deletion modal displayed after button click'
|
|
).to.equal(1);
|
|
|
|
// user has no posts so no warning about post deletion
|
|
expect(
|
|
findAll('[data-test-text="user-post-count"]').length,
|
|
'deleting user with no posts has no post count'
|
|
).to.equal(0);
|
|
|
|
// cancelling user deletion closes modal
|
|
await click('[data-test-button="cancel-delete-user"]');
|
|
expect(
|
|
findAll('[data-test-modal]').length === 0,
|
|
'delete user modal is closed when cancelling'
|
|
).to.be.true;
|
|
|
|
// deleting a user with posts
|
|
await visit('/staff');
|
|
await click(`[data-test-user-id="${user2.id}"]`);
|
|
|
|
await click('button.delete');
|
|
// user has posts so should warn about post deletion
|
|
expect(
|
|
find('[data-test-text="user-post-count"]').textContent,
|
|
'deleting user with posts has post count'
|
|
).to.have.string('1 post');
|
|
|
|
await click('[data-test-button="confirm-delete-user"]');
|
|
// redirected to staff page
|
|
expect(currentURL()).to.equal('/staff');
|
|
|
|
// deleted user is not in list
|
|
expect(
|
|
findAll(`[data-test-user-id="${user2.id}"]`).length,
|
|
'deleted user is not in user list after deletion'
|
|
).to.equal(0);
|
|
});
|
|
|
|
describe('existing user', function () {
|
|
let user, newLocation, originalReplaceState;
|
|
|
|
beforeEach(function () {
|
|
user = this.server.create('user', {
|
|
slug: 'test-1',
|
|
name: 'Test User',
|
|
facebook: 'test',
|
|
twitter: '@test'
|
|
});
|
|
|
|
originalReplaceState = windowProxy.replaceState;
|
|
windowProxy.replaceState = function (params, title, url) {
|
|
newLocation = url;
|
|
};
|
|
newLocation = undefined;
|
|
});
|
|
|
|
afterEach(function () {
|
|
windowProxy.replaceState = originalReplaceState;
|
|
});
|
|
|
|
it('input fields reset and validate correctly', async function () {
|
|
// test user name
|
|
await visit('/staff/test-1');
|
|
|
|
expect(currentURL(), 'currentURL').to.equal('/staff/test-1');
|
|
expect(find('[data-test-name-input]').value, 'current user name').to.equal('Test User');
|
|
|
|
expect(find('[data-test-save-button]').textContent.trim(), 'save button text').to.equal('Save');
|
|
|
|
// test empty user name
|
|
await fillIn('[data-test-name-input]', '');
|
|
await blur('[data-test-name-input]');
|
|
|
|
expect(find('.user-details-bottom .first-form-group').classList.contains('error'), 'username input is in error state with blank input').to.be.true;
|
|
|
|
// test too long user name
|
|
await fillIn('[data-test-name-input]', new Array(195).join('a'));
|
|
await blur('[data-test-name-input]');
|
|
|
|
expect(find('.user-details-bottom .first-form-group').classList.contains('error'), 'username input is in error state with too long input').to.be.true;
|
|
|
|
// reset name field
|
|
await fillIn('[data-test-name-input]', 'Test User');
|
|
|
|
expect(find('[data-test-slug-input]').value, 'slug value is default').to.equal('test-1');
|
|
|
|
await fillIn('[data-test-slug-input]', '');
|
|
await blur('[data-test-slug-input]');
|
|
|
|
expect(find('[data-test-slug-input]').value, 'slug value is reset to original upon empty string').to.equal('test-1');
|
|
|
|
// Save changes
|
|
await click('[data-test-save-button]');
|
|
|
|
// Since we reset save status so there's no on-screen indication
|
|
// that we've had a save, check the request was fired instead
|
|
let [lastRequest] = this.server.pretender.handledRequests.slice(-1);
|
|
let params = JSON.parse(lastRequest.requestBody);
|
|
|
|
expect(params.users[0].name).to.equal('Test User');
|
|
|
|
// CMD-S shortcut works
|
|
await fillIn('[data-test-slug-input]', 'Test User');
|
|
await triggerEvent('.gh-app', 'keydown', {
|
|
keyCode: 83, // s
|
|
metaKey: ctrlOrCmd === 'command',
|
|
ctrlKey: ctrlOrCmd === 'ctrl'
|
|
});
|
|
|
|
// Since we reset save status so there's no on-screen indication
|
|
// that we've had a save, check the request was fired instead
|
|
[lastRequest] = this.server.pretender.handledRequests.slice(-1);
|
|
params = JSON.parse(lastRequest.requestBody);
|
|
|
|
expect(params.users[0].name).to.equal('Test User');
|
|
|
|
// check that the history state has been updated
|
|
expect(newLocation).to.equal('Test User');
|
|
|
|
await fillIn('[data-test-slug-input]', 'white space');
|
|
await blur('[data-test-slug-input]');
|
|
|
|
expect(find('[data-test-slug-input]').value, 'slug value is correctly dasherized').to.equal('white-space');
|
|
|
|
await fillIn('[data-test-email-input]', 'thisisnotanemail');
|
|
await blur('[data-test-email-input]');
|
|
|
|
expect(find('.user-details-bottom .form-group:nth-of-type(3)').classList.contains('error'), 'email input should be in error state with invalid email').to.be.true;
|
|
|
|
await fillIn('[data-test-email-input]', 'test@example.com');
|
|
await fillIn('[data-test-location-input]', new Array(160).join('a'));
|
|
await blur('[data-test-location-input]');
|
|
|
|
expect(
|
|
find('[data-test-location-input]').closest('.form-group'),
|
|
'location input should be in error state'
|
|
).to.have.class('error');
|
|
|
|
await fillIn('[data-test-location-input]', '');
|
|
await fillIn('[data-test-website-input]', 'thisisntawebsite');
|
|
await blur('[data-test-website-input]');
|
|
|
|
expect(
|
|
find('[data-test-website-input]').closest('.form-group'),
|
|
'website input should be in error state'
|
|
).to.have.class('error');
|
|
|
|
let testSocialInput = async function (type, input, expectedValue, expectedError = '') {
|
|
await fillIn(`[data-test-${type}-input]`, input);
|
|
await blur(`[data-test-${type}-input]`);
|
|
|
|
expect(
|
|
find(`[data-test-${type}-input]`).value,
|
|
`${type} value for ${input}`
|
|
).to.equal(expectedValue);
|
|
|
|
expect(
|
|
find(`[data-test-error="user-${type}"]`).textContent.trim(),
|
|
`${type} validation response for ${input}`
|
|
).to.equal(expectedError);
|
|
|
|
expect(
|
|
find(`[data-test-error="user-${type}"]`).closest('.form-group').classList.contains('error'),
|
|
`${type} input should be in error state with '${input}'`
|
|
).to.equal(!!expectedError);
|
|
};
|
|
|
|
let testFacebookValidation = async (...args) => testSocialInput('facebook', ...args);
|
|
let testTwitterValidation = async (...args) => testSocialInput('twitter', ...args);
|
|
|
|
// Testing Facebook input
|
|
|
|
// displays initial value
|
|
expect(find('[data-test-facebook-input]').value, 'initial facebook value')
|
|
.to.equal('https://www.facebook.com/test');
|
|
|
|
await focus('[data-test-facebook-input]');
|
|
await blur('[data-test-facebook-input]');
|
|
|
|
// regression test: we still have a value after the input is
|
|
// focused and then blurred without any changes
|
|
expect(find('[data-test-facebook-input]').value, 'facebook value after blur with no change')
|
|
.to.equal('https://www.facebook.com/test');
|
|
|
|
await testFacebookValidation(
|
|
'facebook.com/username',
|
|
'https://www.facebook.com/username');
|
|
|
|
await testFacebookValidation(
|
|
'testuser',
|
|
'https://www.facebook.com/testuser');
|
|
|
|
await testFacebookValidation(
|
|
'ab99',
|
|
'https://www.facebook.com/ab99');
|
|
|
|
await testFacebookValidation(
|
|
'page/ab99',
|
|
'https://www.facebook.com/page/ab99');
|
|
|
|
await testFacebookValidation(
|
|
'page/*(&*(%%))',
|
|
'https://www.facebook.com/page/*(&*(%%))');
|
|
|
|
await testFacebookValidation(
|
|
'facebook.com/pages/some-facebook-page/857469375913?ref=ts',
|
|
'https://www.facebook.com/pages/some-facebook-page/857469375913?ref=ts');
|
|
|
|
await testFacebookValidation(
|
|
'https://www.facebook.com/groups/savethecrowninn',
|
|
'https://www.facebook.com/groups/savethecrowninn');
|
|
|
|
await testFacebookValidation(
|
|
'http://github.com/username',
|
|
'http://github.com/username',
|
|
'The URL must be in a format like https://www.facebook.com/yourPage');
|
|
|
|
await testFacebookValidation(
|
|
'http://github.com/pages/username',
|
|
'http://github.com/pages/username',
|
|
'The URL must be in a format like https://www.facebook.com/yourPage');
|
|
|
|
// Testing Twitter input
|
|
|
|
// loads fixtures and performs transform
|
|
expect(find('[data-test-twitter-input]').value, 'initial twitter value')
|
|
.to.equal('https://twitter.com/test');
|
|
|
|
await focus('[data-test-twitter-input]');
|
|
await blur('[data-test-twitter-input]');
|
|
|
|
// regression test: we still have a value after the input is
|
|
// focused and then blurred without any changes
|
|
expect(find('[data-test-twitter-input]').value, 'twitter value after blur with no change')
|
|
.to.equal('https://twitter.com/test');
|
|
|
|
await testTwitterValidation(
|
|
'twitter.com/username',
|
|
'https://twitter.com/username');
|
|
|
|
await testTwitterValidation(
|
|
'testuser',
|
|
'https://twitter.com/testuser');
|
|
|
|
await testTwitterValidation(
|
|
'http://github.com/username',
|
|
'https://twitter.com/username');
|
|
|
|
await testTwitterValidation(
|
|
'*(&*(%%))',
|
|
'*(&*(%%))',
|
|
'The URL must be in a format like https://twitter.com/yourUsername');
|
|
|
|
await testTwitterValidation(
|
|
'thisusernamehasmorethan15characters',
|
|
'thisusernamehasmorethan15characters',
|
|
'Your Username is not a valid Twitter Username');
|
|
|
|
// Testing bio input
|
|
|
|
await fillIn('[data-test-website-input]', '');
|
|
await fillIn('[data-test-bio-input]', new Array(210).join('a'));
|
|
await blur('[data-test-bio-input]');
|
|
|
|
expect(
|
|
find('[data-test-bio-input]').closest('.form-group'),
|
|
'bio input should be in error state'
|
|
).to.have.class('error');
|
|
|
|
// password reset ------
|
|
|
|
// button triggers validation
|
|
await click('[data-test-save-pw-button]');
|
|
|
|
expect(
|
|
find('[data-test-new-pass-input]').closest('.form-group'),
|
|
'new password has error class when blank'
|
|
).to.have.class('error');
|
|
|
|
expect(
|
|
find('[data-test-error="user-new-pass"]').textContent,
|
|
'new password error when blank'
|
|
).to.have.string('can\'t be blank');
|
|
|
|
// validates too short password (< 10 characters)
|
|
await fillIn('[data-test-new-pass-input]', 'notlong');
|
|
await fillIn('[data-test-ne2-pass-input]', 'notlong');
|
|
|
|
// enter key triggers action
|
|
await triggerKeyEvent('[data-test-new-pass-input]', 'keyup', 13);
|
|
|
|
expect(
|
|
find('[data-test-new-pass-input]').closest('.form-group'),
|
|
'new password has error class when password too short'
|
|
).to.have.class('error');
|
|
|
|
expect(
|
|
find('[data-test-error="user-new-pass"]').textContent,
|
|
'new password error when it\'s too short'
|
|
).to.have.string('at least 10 characters long');
|
|
|
|
// validates unsafe password
|
|
await fillIn('#user-password-new', 'ghostisawesome');
|
|
await fillIn('[data-test-ne2-pass-input]', 'ghostisawesome');
|
|
|
|
// enter key triggers action
|
|
await triggerKeyEvent('#user-password-new', 'keyup', 13);
|
|
|
|
expect(
|
|
find('#user-password-new').closest('.form-group'),
|
|
'new password has error class when password is insecure'
|
|
).to.have.class('error');
|
|
|
|
expect(
|
|
find('[data-test-error="user-new-pass"]').textContent,
|
|
'new password error when it\'s insecure'
|
|
).to.match(/you cannot use an insecure password/);
|
|
|
|
// typing in inputs clears validation
|
|
await fillIn('[data-test-new-pass-input]', 'thisissupersafe');
|
|
await triggerEvent('[data-test-new-pass-input]', 'input');
|
|
|
|
expect(
|
|
find('[data-test-new-pass-input]').closest('.form-group'),
|
|
'password validation is visible after typing'
|
|
).to.not.have.class('error');
|
|
|
|
// enter key triggers action
|
|
await triggerKeyEvent('[data-test-new-pass-input]', 'keyup', 13);
|
|
|
|
expect(
|
|
find('[data-test-ne2-pass-input]').closest('.form-group'),
|
|
'confirm password has error class when it doesn\'t match'
|
|
).to.have.class('error');
|
|
|
|
expect(
|
|
find('[data-test-error="user-ne2-pass"]').textContent,
|
|
'confirm password error when it doesn\'t match'
|
|
).to.have.string('do not match');
|
|
|
|
// submits with correct details
|
|
await fillIn('[data-test-ne2-pass-input]', 'thisissupersafe');
|
|
await click('[data-test-save-pw-button]');
|
|
|
|
// hits the endpoint
|
|
let [newRequest] = this.server.pretender.handledRequests.slice(-1);
|
|
params = JSON.parse(newRequest.requestBody);
|
|
|
|
expect(newRequest.url, 'password request URL')
|
|
.to.match(/\/users\/password/);
|
|
|
|
// eslint-disable-next-line camelcase
|
|
expect(params.password[0].user_id).to.equal(user.id.toString());
|
|
expect(params.password[0].newPassword).to.equal('thisissupersafe');
|
|
expect(params.password[0].ne2Password).to.equal('thisissupersafe');
|
|
|
|
// clears the fields
|
|
expect(
|
|
find('[data-test-new-pass-input]').value,
|
|
'password field after submit'
|
|
).to.be.empty;
|
|
|
|
expect(
|
|
find('[data-test-ne2-pass-input]').value,
|
|
'password verification field after submit'
|
|
).to.be.empty;
|
|
|
|
// displays a notification
|
|
expect(
|
|
findAll('.gh-notifications .gh-notification').length,
|
|
'password saved notification is displayed'
|
|
).to.equal(1);
|
|
});
|
|
|
|
it('warns when leaving without saving', async function () {
|
|
await visit('/staff/test-1');
|
|
|
|
expect(currentURL(), 'currentURL').to.equal('/staff/test-1');
|
|
|
|
await fillIn('[data-test-slug-input]', 'another slug');
|
|
await blur('[data-test-slug-input]');
|
|
|
|
expect(find('[data-test-slug-input]').value).to.be.equal('another-slug');
|
|
|
|
await fillIn('[data-test-facebook-input]', 'testuser');
|
|
await blur('[data-test-facebook-input]');
|
|
|
|
expect(find('[data-test-facebook-input]').value).to.be.equal('https://www.facebook.com/testuser');
|
|
|
|
await visit('/settings/staff');
|
|
|
|
expect(findAll('[data-test-modal]').length, 'modal exists').to.equal(1);
|
|
|
|
// Leave without saving
|
|
await click('.fullscreen-modal [data-test-leave-button]');
|
|
|
|
expect(currentURL(), 'currentURL').to.equal('/settings/staff');
|
|
|
|
await visit('/staff/test-1');
|
|
|
|
expect(currentURL(), 'currentURL').to.equal('/staff/test-1');
|
|
|
|
// settings were not saved
|
|
expect(find('[data-test-slug-input]').value).to.be.equal('test-1');
|
|
expect(find('[data-test-facebook-input]').value).to.be.equal('https://www.facebook.com/test');
|
|
});
|
|
});
|
|
|
|
describe('own user', function () {
|
|
it('requires current password when changing password', async function () {
|
|
await visit(`/staff/${admin.slug}`);
|
|
|
|
// test the "old password" field is validated
|
|
await click('[data-test-save-pw-button]');
|
|
|
|
// old password has error
|
|
expect(
|
|
find('[data-test-old-pass-input]').closest('.form-group'),
|
|
'old password has error class when blank'
|
|
).to.have.class('error');
|
|
|
|
expect(
|
|
find('[data-test-error="user-old-pass"]').textContent,
|
|
'old password error when blank'
|
|
).to.have.string('is required');
|
|
|
|
// new password has error
|
|
expect(
|
|
find('[data-test-new-pass-input]').closest('.form-group'),
|
|
'new password has error class when blank'
|
|
).to.have.class('error');
|
|
|
|
expect(
|
|
find('[data-test-error="user-new-pass"]').textContent,
|
|
'new password error when blank'
|
|
).to.have.string('can\'t be blank');
|
|
|
|
// validation is cleared when typing
|
|
await fillIn('[data-test-old-pass-input]', 'password');
|
|
await triggerEvent('[data-test-old-pass-input]', 'input');
|
|
|
|
expect(
|
|
find('[data-test-old-pass-input]').closest('.form-group'),
|
|
'old password validation is in error state after typing'
|
|
).to.not.have.class('error');
|
|
});
|
|
});
|
|
|
|
it('redirects to 404 when user does not exist', async function () {
|
|
this.server.get('/users/slug/unknown/', function () {
|
|
return new Response(404, {'Content-Type': 'application/json'}, {errors: [{message: 'User not found.', type: 'NotFoundError'}]});
|
|
});
|
|
|
|
await visit('/staff/unknown');
|
|
|
|
expect(currentRouteName()).to.equal('error404');
|
|
expect(currentURL()).to.equal('/staff/unknown');
|
|
});
|
|
});
|
|
|
|
describe('when logged in as author', function () {
|
|
let adminRole, authorRole;
|
|
|
|
beforeEach(async function () {
|
|
adminRole = this.server.create('role', {name: 'Administrator'});
|
|
authorRole = this.server.create('role', {name: 'Author'});
|
|
this.server.create('user', {roles: [authorRole]});
|
|
|
|
this.server.get('/invites/', function () {
|
|
return new Response(403, {}, {
|
|
errors: [{
|
|
type: 'NoPermissionError',
|
|
message: 'You do not have permission to perform this action'
|
|
}]
|
|
});
|
|
});
|
|
|
|
return await authenticateSession();
|
|
});
|
|
|
|
it('can access the staff page', async function () {
|
|
this.server.create('user', {roles: [adminRole]});
|
|
this.server.create('invite', {role: authorRole});
|
|
|
|
await visit('/staff');
|
|
|
|
expect(currentRouteName()).to.equal('staff.index');
|
|
expect(findAll('.gh-alert').length).to.equal(0);
|
|
});
|
|
});
|
|
});
|