@@ -522,7 +522,12 @@ describe('Acceptance: Staff', function () {
                 // Save changes
                 await click('[data-test-save-button]');
 
-                expect(find('[data-test-save-button]').textContent.trim(), 'save button text').to.equal('Saved');
+                // 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');
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]');

                expect(find('[data-test-save-button]').textContent.trim(), 'save button text').to.equal('Saved');

                // 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'
                });

                // we've already saved in this test so there's no on-screen indication
                // that we've had another 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');

                // 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);
        });
    });
});

@kevinansfield Not sure if this is the best way to update tests for the saved status since we turned on the auto-reset for all buttons, so checking button text as "Saved" is not gonna work here anymore. Copied this pattern from just below where we already checked the save status with request, lmk tho if this is not ideal and should be changed.