diff --git a/core/client/app/controllers/modals/delete-user.js b/core/client/app/controllers/modals/delete-user.js index 9dc2a1c6a4..e446b62954 100644 --- a/core/client/app/controllers/modals/delete-user.js +++ b/core/client/app/controllers/modals/delete-user.js @@ -30,7 +30,7 @@ export default Ember.Controller.extend({ user.destroyRecord().then(function () { self.store.unloadAll('post'); - self.transitionToRoute('settings.users'); + self.transitionToRoute('team'); self.get('notifications').showSuccess('The user has been deleted.', {delayed: true}); }, function () { self.get('notifications').showError('The user could not be deleted. Please try again.'); diff --git a/core/client/app/controllers/settings/users/index.js b/core/client/app/controllers/team/index.js similarity index 86% rename from core/client/app/controllers/settings/users/index.js rename to core/client/app/controllers/team/index.js index a2893fd581..ae26adbd55 100644 --- a/core/client/app/controllers/settings/users/index.js +++ b/core/client/app/controllers/team/index.js @@ -1,7 +1,7 @@ import Ember from 'ember'; import PaginationControllerMixin from 'ghost/mixins/pagination-controller'; -var UsersIndexController = Ember.ArrayController.extend(PaginationControllerMixin, { +var TeamIndexController = Ember.ArrayController.extend(PaginationControllerMixin, { init: function () { // let the PaginationControllerMixin know what type of model we will be paginating // this is necessary because we do not have access to the model inside the Controller::init method @@ -21,4 +21,4 @@ var UsersIndexController = Ember.ArrayController.extend(PaginationControllerMixi }) }); -export default UsersIndexController; +export default TeamIndexController; diff --git a/core/client/app/controllers/settings/users/user.js b/core/client/app/controllers/team/user.js similarity index 100% rename from core/client/app/controllers/settings/users/user.js rename to core/client/app/controllers/team/user.js diff --git a/core/client/app/mixins/current-user-settings.js b/core/client/app/mixins/current-user-settings.js index bdd3f5fb86..30e567a659 100644 --- a/core/client/app/mixins/current-user-settings.js +++ b/core/client/app/mixins/current-user-settings.js @@ -5,7 +5,7 @@ var CurrentUserSettings = Ember.Mixin.create({ return function (user) { if (user.get('isAuthor')) { - return self.transitionTo('settings.users.user', user); + return self.transitionTo('team.user', user); } return user; @@ -17,7 +17,7 @@ var CurrentUserSettings = Ember.Mixin.create({ return function (user) { if (user.get('isEditor')) { - return self.transitionTo('settings.users'); + return self.transitionTo('team'); } return user; diff --git a/core/client/app/router.js b/core/client/app/router.js index 552fedff98..d5253a5771 100644 --- a/core/client/app/router.js +++ b/core/client/app/router.js @@ -40,18 +40,16 @@ Router.map(function () { this.route('edit', {path: ':post_id'}); }); - this.route('settings.general', {path: '/settings/general'}); - this.route('settings.users', {path: '/settings/users'}, function () { + this.route('team', {path: '/team'}, function () { this.route('user', {path: ':slug'}); }); + + this.route('settings.general', {path: '/settings/general'}); this.route('settings.tags', {path: '/settings/tags'}); this.route('settings.labs', {path: '/settings/labs'}); this.route('settings.code-injection', {path: '/settings/code-injection'}); this.route('settings.navigation', {path: '/settings/navigation'}); - // Redirect legacy content to posts - this.route('content'); - this.route('error404', {path: '/*path'}); }); diff --git a/core/client/app/routes/content.js b/core/client/app/routes/content.js deleted file mode 100644 index 69593da6de..0000000000 --- a/core/client/app/routes/content.js +++ /dev/null @@ -1,8 +0,0 @@ -import Ember from 'ember'; -var ContentRoute = Ember.Route.extend({ - beforeModel: function () { - this.transitionTo('posts'); - } -}); - -export default ContentRoute; diff --git a/core/client/app/routes/debug.js b/core/client/app/routes/debug.js deleted file mode 100644 index 01ba719c12..0000000000 --- a/core/client/app/routes/debug.js +++ /dev/null @@ -1,8 +0,0 @@ -import Ember from 'ember'; -var DebugRoute = Ember.Route.extend({ - beforeModel: function () { - this.transitionTo('settings.labs'); - } -}); - -export default DebugRoute; diff --git a/core/client/app/routes/settings/users/index.js b/core/client/app/routes/team/index.js similarity index 88% rename from core/client/app/routes/settings/users/index.js rename to core/client/app/routes/team/index.js index b5f2436d22..77c021e0ab 100644 --- a/core/client/app/routes/settings/users/index.js +++ b/core/client/app/routes/team/index.js @@ -4,7 +4,7 @@ import PaginationRouteMixin from 'ghost/mixins/pagination-route'; import styleBody from 'ghost/mixins/style-body'; var paginationSettings, - UsersIndexRoute; + TeamIndexRoute; paginationSettings = { page: 1, @@ -12,10 +12,10 @@ paginationSettings = { status: 'active' }; -UsersIndexRoute = AuthenticatedRoute.extend(styleBody, CurrentUserSettings, PaginationRouteMixin, { +TeamIndexRoute = AuthenticatedRoute.extend(styleBody, CurrentUserSettings, PaginationRouteMixin, { titleToken: 'Team', - classNames: ['settings-view-users'], + classNames: ['view-users'], setupController: function (controller, model) { this._super(controller, model); @@ -55,4 +55,4 @@ UsersIndexRoute = AuthenticatedRoute.extend(styleBody, CurrentUserSettings, Pagi } }); -export default UsersIndexRoute; +export default TeamIndexRoute; diff --git a/core/client/app/routes/settings/users/user.js b/core/client/app/routes/team/user.js similarity index 77% rename from core/client/app/routes/settings/users/user.js rename to core/client/app/routes/team/user.js index 72c2cd2ecd..6ec6760814 100644 --- a/core/client/app/routes/settings/users/user.js +++ b/core/client/app/routes/team/user.js @@ -2,10 +2,10 @@ import AuthenticatedRoute from 'ghost/routes/authenticated'; import CurrentUserSettings from 'ghost/mixins/current-user-settings'; import styleBody from 'ghost/mixins/style-body'; -var SettingsUserRoute = AuthenticatedRoute.extend(styleBody, CurrentUserSettings, { +var TeamUserRoute = AuthenticatedRoute.extend(styleBody, CurrentUserSettings, { titleToken: 'Team - User', - classNames: ['settings-view-user'], + classNames: ['team-view-user'], model: function (params) { var self = this; @@ -17,7 +17,7 @@ var SettingsUserRoute = AuthenticatedRoute.extend(styleBody, CurrentUserSettings var user = result.findBy('slug', params.slug); if (!user) { - return self.transitionTo('error404', 'settings/users/' + params.slug); + return self.transitionTo('error404', 'team/' + params.slug); } return user; @@ -31,15 +31,15 @@ var SettingsUserRoute = AuthenticatedRoute.extend(styleBody, CurrentUserSettings isAuthor = currentUser.get('isAuthor'), isEditor = currentUser.get('isEditor'); if (isAuthor && !isOwnProfile) { - self.transitionTo('settings.users.user', currentUser); + self.transitionTo('team.user', currentUser); } else if (isEditor && !isOwnProfile && !user.get('isAuthor')) { - self.transitionTo('settings.users'); + self.transitionTo('team'); } }); }, deactivate: function () { - var model = this.modelFor('settings.users.user'); + var model = this.modelFor('team.user'); // we want to revert any unsaved changes on exit if (model && model.get('isDirty')) { @@ -56,4 +56,4 @@ var SettingsUserRoute = AuthenticatedRoute.extend(styleBody, CurrentUserSettings } }); -export default SettingsUserRoute; +export default TeamUserRoute; diff --git a/core/client/app/templates/components/gh-nav-menu.hbs b/core/client/app/templates/components/gh-nav-menu.hbs index 298bad9f2f..f1539adc0e 100644 --- a/core/client/app/templates/components/gh-nav-menu.hbs +++ b/core/client/app/templates/components/gh-nav-menu.hbs @@ -10,7 +10,7 @@ {{/gh-dropdown}} @@ -24,7 +24,7 @@
  • {{#link-to "editor.new" classNames="gh-nav-main-editor"}}New Post{{/link-to}}
  • {{#link-to "posts" classNames="gh-nav-main-content"}}Content{{/link-to}}
  • {{!
  • My Posts
  • }} -
  • {{#link-to "settings.users" classNames="gh-nav-main-users"}}Team{{/link-to}}
  • +
  • {{#link-to "team" classNames="gh-nav-main-users"}}Team{{/link-to}}
  • {{!
  • Ideas
  • }} {{#if (gh-user-can session.user 'admin')}} diff --git a/core/client/app/templates/settings/users/index.hbs b/core/client/app/templates/team/index.hbs similarity index 91% rename from core/client/app/templates/settings/users/index.hbs rename to core/client/app/templates/team/index.hbs index 4dc9988654..d669b51ff4 100644 --- a/core/client/app/templates/settings/users/index.hbs +++ b/core/client/app/templates/team/index.hbs @@ -11,7 +11,7 @@

    Invited users

    - {{#each invitedUsers itemController="settings/users/user" as |user|}} + {{#each invitedUsers itemController="team/user" as |user|}}
    ic @@ -37,8 +37,8 @@

    Active users

    - {{#each activeUsers itemController="settings/users/user" as |user|}} - {{#link-to 'settings.users.user' user.model class="user-list-item" }} + {{#each activeUsers itemController="team/user" as |user|}} + {{#link-to 'team.user' user.model class="user-list-item" }} diff --git a/core/client/app/templates/settings/users/user.hbs b/core/client/app/templates/team/user.hbs similarity index 98% rename from core/client/app/templates/settings/users/user.hbs rename to core/client/app/templates/team/user.hbs index 06a3d878e8..7409c924fa 100644 --- a/core/client/app/templates/settings/users/user.hbs +++ b/core/client/app/templates/team/user.hbs @@ -1,6 +1,6 @@
    {{#gh-view-title openMobileMenu="openMobileMenu"}} - {{#link-to "settings.users"}}Team{{/link-to}} {{user.name}} + {{#link-to "team"}}Team{{/link-to}} {{user.name}} {{/gh-view-title}}
    {{#if view.userActionsAreVisible}} diff --git a/core/client/app/views/settings/users/index.js b/core/client/app/views/settings/users/index.js deleted file mode 100644 index 8e91210980..0000000000 --- a/core/client/app/views/settings/users/index.js +++ /dev/null @@ -1,5 +0,0 @@ -import BaseView from 'ghost/views/settings/content-base'; - -var SettingsUserIndexView = BaseView.extend(); - -export default SettingsUserIndexView; diff --git a/core/client/app/views/team/index.js b/core/client/app/views/team/index.js new file mode 100644 index 0000000000..aa60f7fc73 --- /dev/null +++ b/core/client/app/views/team/index.js @@ -0,0 +1,7 @@ +import Ember from 'ember'; +var TeamUserIndexView = Ember.View.extend({ + tagName: 'section', + classNames: ['gh-view'] +}); + +export default TeamUserIndexView; diff --git a/core/client/app/views/settings/users/user.js b/core/client/app/views/team/user.js similarity index 89% rename from core/client/app/views/settings/users/user.js rename to core/client/app/views/team/user.js index 87c5eaaeef..e0b8b616bf 100644 --- a/core/client/app/views/settings/users/user.js +++ b/core/client/app/views/team/user.js @@ -1,7 +1,8 @@ import Ember from 'ember'; -import BaseView from 'ghost/views/settings/content-base'; -var SettingsUserView = BaseView.extend({ +var TeamUserView = Ember.View.extend({ + tagName: 'section', + classNames: ['gh-view'], currentUser: Ember.computed.alias('controller.session.user'), isNotOwnProfile: Ember.computed('controller.user.id', 'currentUser.id', function () { @@ -28,4 +29,4 @@ var SettingsUserView = BaseView.extend({ }); -export default SettingsUserView; +export default TeamUserView; diff --git a/core/test/functional/base.js b/core/test/functional/base.js index fdf930f23c..bfb77efb5b 100644 --- a/core/test/functional/base.js +++ b/core/test/functional/base.js @@ -66,7 +66,7 @@ screens = { selector: '.gh-nav-main-content.active' }, content: { - url: 'ghost/content/', + url: 'ghost/', linkSelector: '.gh-nav-main-content', selector: '.gh-nav-main-content.active' }, @@ -89,13 +89,13 @@ screens = { url: 'ghost/settings/general', selector: '.gh-nav-settings-general.active' }, - 'settings.users': { - url: 'ghost/settings/users', + team: { + url: 'ghost/team', linkSelector: '.gh-nav-main-users', selector: '.gh-nav-main-users.active' }, - 'settings.users.user': { - url: 'ghost/settings/users/test', + 'team.user': { + url: 'ghost/team/test', linkSelector: '.user-menu-profile', selector: '.user-profile' }, diff --git a/core/test/functional/client/app_test.js b/core/test/functional/client/app_test.js index e9a68dca8e..56c6a67852 100644 --- a/core/test/functional/client/app_test.js +++ b/core/test/functional/client/app_test.js @@ -39,7 +39,7 @@ CasperTest.begin('Admin navigation bar is correct', 65, function suite(test) { // Users test.assertExists('.gh-nav-main-users', 'Users nav item exists'); test.assertSelectorHasText('.gh-nav-main-users', 'Team', 'Users nav item has correct text'); - test.assertEquals(usersHref, '/ghost/settings/users/', 'Users href is correct'); + test.assertEquals(usersHref, '/ghost/team/', 'Users href is correct'); test.assertDoesntExist('.gh-nav-main-users.active', 'Users nav item is not marked active'); // Settings - General @@ -139,7 +139,7 @@ CasperTest.begin('Admin navigation bar is correct', 65, function suite(test) { test.assertExists('.dropdown-item.user-menu-profile', 'Profile menu item exists'); test.assertSelectorHasText('.dropdown-item.user-menu-profile', 'Your Profile', 'Profile menu item has correct text'); - test.assertEquals(profileHref, '/ghost/settings/users/' + newUser.slug + '/', 'Profile href is correct'); + test.assertEquals(profileHref, '/ghost/team/' + newUser.slug + '/', 'Profile href is correct'); test.assertExists('.user-menu-signout', 'Sign Out menu item exists'); test.assertSelectorHasText('.user-menu-signout', 'Sign Out', 'Signout menu item has correct text'); diff --git a/core/test/functional/client/settings_test.js b/core/test/functional/client/settings_test.js index 6540fc3915..d57e5b106e 100644 --- a/core/test/functional/client/settings_test.js +++ b/core/test/functional/client/settings_test.js @@ -5,10 +5,9 @@ // These classes relate to elements which only appear when a given tab is loaded. // These are used to check that a switch to a tab is complete, or that we are on the right tab. -var generalTabDetector = '.gh-nav-settings-general.active', - usersTabDetector = '.gh-nav-main-users'; +var generalTabDetector = '.gh-nav-settings-general.active'; -CasperTest.begin('Settings screen is correct', 9, function suite(test) { +CasperTest.begin('Settings screen is correct', 5, function suite(test) { casper.thenOpenAndWaitForPageLoad('settings.general', function testTitleAndUrl() { test.assertTitle('Settings - General - Test Blog', 'Ghost admin has incorrect title'); test.assertUrlMatch(/ghost\/settings\/general\/$/, 'Landed on the correct URL'); @@ -19,24 +18,6 @@ CasperTest.begin('Settings screen is correct', 9, function suite(test) { test.assertExists(generalTabDetector, 'Form is present'); test.assertSelectorHasText('.view-title', 'General', 'Title is "General"'); }); - - // TODO move users tests to a new file and make sure settings nav tests are refactored into app_test.js - - casper.then(function testSwitchingTabs() { - casper.thenClick('.gh-nav-main-users'); - casper.waitForSelector(usersTabDetector, function then() { - // assert that the right menu item is active - test.assertExists('.gh-nav-main-users.active', 'Users link is active'); - test.assertDoesntExist('.gh-nav-settings-general.active', 'General link is not active'); - }, casper.failOnTimeout(test, 'waitForSelector `usersTabDetector` timed out')); - - casper.thenClick('.gh-nav-settings-general'); - casper.waitForSelector(generalTabDetector, function then() { - // assert that the right menu item is active - test.assertExists('.gh-nav-settings-general.active', 'General link is active'); - test.assertDoesntExist('.gh-nav-main-users.active', 'User link is not active'); - }, casper.failOnTimeout(test, 'waitForSelector `generalTabDetector` timed out')); - }); }); // ## General settings tests @@ -153,307 +134,3 @@ CasperTest.begin('General settings validation is correct', 6, function suite(tes test.assertField('general[postsPerPage]', '5', 'posts per page is set correctly'); }); }); -// -CasperTest.begin('Users screen is correct', 9, function suite(test) { - casper.thenOpenAndWaitForPageLoad('settings.general'); - casper.thenTransitionAndWaitForScreenLoad('settings.users', function canTransition() { - test.assert(true, 'Can transition to users screen from settings.general'); - test.assertUrlMatch(/ghost\/settings\/users\/$/, 'settings.users transitions to correct url'); - }); - casper.then(function usersScreenHasContent() { - test.assertSelectorHasText('.settings-users .user-list .user-list-title', 'Active users', 'active users text is correct'); - test.assertExists('.settings-users .user-list .user-list-item', 'Has an active user'); - test.assertSelectorHasText('.settings-users .user-list-item .name', 'Test User', 'test user text is correct'); - test.assertExists('.settings-users .user-list-item .role-label.owner', 'First user has owner role displayed'); - - test.assertExists('.view-actions .btn-green', 'Add user button is on page.'); - }); - casper.thenClick('.view-actions .btn-green'); - casper.waitForOpaque('.invite-new-user .modal-content', function then() { - test.assertEval(function testOwnerRoleNotAnOption() { - var options = document.querySelectorAll('.invite-new-user select#new-user-role option'), - i = 0; - for (; i < options.length; i = i + 1) { - if (options[i].text === 'Owner') { - return false; - } - } - return true; - }, '"Owner" is not a role option for new users'); - }); - // role options get loaded asynchronously; give them a chance to come in - casper.waitForSelector('.invite-new-user select#new-user-role option', function then() { - test.assertEval(function authorIsSelectedByDefault() { - var options = document.querySelectorAll('.invite-new-user select#new-user-role option'), - i = 0; - for (; i < options.length; i = i + 1) { - if (options[i].selected) { - return options[i].text === 'Author'; - } - } - return false; - }, 'The "Author" role is selected by default when adding a new user'); - }); -}); -// ### User settings tests -CasperTest.begin('Can save settings', 7, function suite(test) { - casper.thenOpenAndWaitForPageLoad('settings.users.user', function testTitleAndUrl() { - test.assertTitle('Team - User - Test Blog', 'Ghost Admin title is correct'); - test.assertUrlMatch(/ghost\/settings\/users\/test\/$/, 'settings.users.user has correct URL'); - }); - - function handleUserRequest(requestData) { - // make sure we only get requests from the user pane - if (requestData.url.indexOf('settings/') !== -1) { - test.fail('Saving the user pane triggered another settings pane to save'); - } - } - - function handleSettingsRequest(requestData) { - // make sure we only get requests from the user pane - if (requestData.url.indexOf('users/') !== -1) { - test.fail('Saving a settings pane triggered the user pane to save'); - } - } - - casper.then(function listenForRequests() { - casper.on('resource.requested', handleUserRequest); - }); - - casper.thenClick('.btn-blue'); - casper.waitFor(function successNotification() { - return this.evaluate(function () { - return document.querySelectorAll('.gh-notification').length > 0; - }); - }, function doneWaiting() { - test.pass('Waited for notification'); - }, casper.failOnTimeout(test, 'Saving the user pane did not result in a notification')); - - casper.then(function checkUserWasSaved() { - casper.removeListener('resource.requested', handleUserRequest); - }); - - casper.waitForSelector('.notification-success', function onSuccess() { - test.assert(true, 'Got success notification'); - }, casper.failOnTimeout(test, 'No success notification :(')); - - casper.thenClick('.gh-nav-settings-general').then(function testTransitionToGeneral() { - casper.waitForSelector(generalTabDetector, function then() { - casper.on('resource.requested', handleSettingsRequest); - test.assertEval(function testGeneralIsActive() { - return document.querySelector('.gh-nav-settings-general').classList.contains('active'); - }, 'general tab is marked active'); - }, - casper.failOnTimeout(test, 'waitForSelector `usersTabDetector` timed out')); - }); - - casper.thenClick('.btn-blue').waitFor(function successNotification() { - return this.evaluate(function () { - return document.querySelectorAll('.gh-notification').length > 0; - }); - }, function doneWaiting() { - test.pass('Waited for notification'); - }, casper.failOnTimeout(test, 'Saving the general pane did not result in a notification')); - - casper.then(function checkSettingsWereSaved() { - casper.removeListener('resource.requested', handleSettingsRequest); - }); - - casper.waitForSelector('.notification-success', function onSuccess() { - test.assert(true, 'Got success notification'); - }, casper.failOnTimeout(test, 'No success notification :(')); - - CasperTest.beforeDone(function () { - casper.removeListener('resource.requested', handleUserRequest); - casper.removeListener('resource.requested', handleSettingsRequest); - }); -}); - -CasperTest.begin('User settings screen resets all whitespace slug to original value', 3, function suite(test) { - var slug; - - casper.thenOpenAndWaitForPageLoad('settings.users.user', function testTitleAndUrl() { - test.assertTitle('Team - User - Test Blog', 'Ghost admin has incorrect title'); - test.assertUrlMatch(/ghost\/settings\/users\/test\/$/, 'Ghost doesn\'t require login this time'); - }); - - casper.then(function getSlugValue() { - slug = this.evaluate(function () { - return document.querySelector('#user-slug').value; - }); - }); - - casper.then(function changeSlugInput() { - casper.fillSelectors('.user-profile', { - '#user-slug': ' ' - }, false); - }); - - casper.thenClick('body'); - - casper.then(function checkSlugInputValue() { - casper.wait(250); - test.assertField('user', slug, 'user slug is correct'); - }); -}); - -CasperTest.begin('User settings screen change slug handles duplicate slug', 4, function suite(test) { - var slug; - - casper.thenOpenAndWaitForPageLoad('settings.users.user', function testTitleAndUrl() { - test.assertTitle('Team - User - Test Blog', 'Ghost admin has incorrect title'); - test.assertUrlMatch(/ghost\/settings\/users\/test\/$/, 'Ghost doesn\'t require login this time'); - }); - - casper.then(function getSlugValue() { - slug = this.evaluate(function () { - return document.querySelector('#user-slug').value; - }); - }); - - casper.then(function changeSlug() { - casper.fillSelectors('.user-profile', { - '#user-slug': slug + '!' - }, false); - }); - - casper.thenClick('body'); - - casper.waitForResource(/\/slugs\/user\//, function testGoodResponse(resource) { - test.assert(resource.status < 400, 'resource.status < 400'); - }); - - casper.then(function checkSlugInputValue() { - test.assertField('user', slug, 'user slug is correct'); - }); -}); - -CasperTest.begin('User settings screen validates email', 6, function suite(test) { - var email; - - casper.thenOpenAndWaitForPageLoad('settings.users.user', function testTitleAndUrl() { - test.assertTitle('Team - User - Test Blog', 'Ghost admin has incorrect title'); - test.assertUrlMatch(/ghost\/settings\/users\/test\/$/, 'Ghost doesn\'t require login this time'); - }); - - casper.then(function getEmail() { - email = this.evaluate(function () { - return document.querySelector('#user-email').value; - }); - }); - - casper.then(function setEmailToInvalid() { - var brokenEmail = email.replace('.', '-'); - - casper.fillSelectors('.user-profile', { - '#user-email': brokenEmail - }, false); - }); - - casper.thenClick('.btn-blue'); - - casper.waitForResource('/users/'); - - casper.waitForSelector('.notification-error', function onSuccess() { - test.assert(true, 'Got error notification'); - test.assertSelectorDoesntHaveText('.notification-error', '[object Object]', 'notification text is not broken'); - }, casper.failOnTimeout(test, 'No error notification :(')); - - casper.then(function resetEmailToValid() { - casper.fillSelectors('.user-profile', { - '#user-email': email - }, false); - }); - - casper.thenClick('.view-actions .btn-blue'); - - casper.waitForResource(/users/); - - casper.waitForSelector('.notification-success', function onSuccess() { - test.assert(true, 'Got success notification'); - test.assertSelectorDoesntHaveText('.notification-success', '[object Object]', 'notification text is not broken'); - }, casper.failOnTimeout(test, 'No success notification :(')); -}); - -// TODO: user needs to be loaded whenever it is edited (multi user) -CasperTest.begin('User settings screen shows remaining characters for Bio properly', 4, function suite(test) { - casper.thenOpenAndWaitForPageLoad('settings.users.user', function testTitleAndUrl() { - test.assertTitle('Team - User - Test Blog', 'Ghost admin has incorrect title'); - test.assertUrlMatch(/ghost\/settings\/users\/test\/$/, 'Ghost doesn\'t require login this time'); - }); - - function getRemainingBioCharacterCount() { - return casper.getHTML('.word-count'); - } - - casper.then(function checkCharacterCount() { - test.assert(getRemainingBioCharacterCount() === '200', 'Bio remaining characters is 200'); - }); - - casper.then(function setBioToValid() { - casper.fillSelectors('.user-profile', { - '#user-bio': 'asdf\n' // 5 characters - }, false); - }); - - casper.then(function checkCharacterCount() { - test.assert(getRemainingBioCharacterCount() === '195', 'Bio remaining characters is 195'); - }); -}); - -CasperTest.begin('Ensure user bio field length validation', 3, function suite(test) { - casper.thenOpenAndWaitForPageLoad('settings.users.user', function testTitleAndUrl() { - test.assertTitle('Team - User - Test Blog', 'Ghost admin has incorrect title'); - test.assertUrlMatch(/ghost\/settings\/users\/test\/$/, 'Ghost doesn\'t require login this time'); - }); - - casper.then(function setBioToInvalid() { - this.fillSelectors('form.user-profile', { - '#user-bio': new Array(202).join('a') - }); - }); - - casper.thenClick('.view-actions .btn-blue'); - - casper.waitForSelectorTextChange('.notification-error', function onSuccess() { - test.assertSelectorHasText('.notification-error', 'is too long', '.notification-error text is correct'); - }, casper.failOnTimeout(test, 'Bio field length error did not appear', 2000)); -}); - -CasperTest.begin('Ensure user url field validation', 3, function suite(test) { - casper.thenOpenAndWaitForPageLoad('settings.users.user', function testTitleAndUrl() { - test.assertTitle('Team - User - Test Blog', 'Ghost admin has incorrect title'); - test.assertUrlMatch(/ghost\/settings\/users\/test\/$/, 'Ghost doesn\'t require login this time'); - }); - - casper.then(function setWebsiteToInvalid() { - this.fillSelectors('form.user-profile', { - '#user-website': 'notaurl' - }); - }); - - casper.thenClick('.view-actions .btn-blue'); - - casper.waitForSelectorTextChange('.notification-error', function onSuccess() { - test.assertSelectorHasText('.notification-error', 'not a valid url', '.notification-error text is correct'); - }, casper.failOnTimeout(test, 'Url validation error did not appear', 2000)); -}); - -CasperTest.begin('Ensure user location field length validation', 3, function suite(test) { - casper.thenOpenAndWaitForPageLoad('settings.users.user', function testTitleAndUrl() { - test.assertTitle('Team - User - Test Blog', 'Ghost admin has incorrect title'); - test.assertUrlMatch(/ghost\/settings\/users\/test\/$/, 'Ghost doesn\'t require login this time'); - }); - - casper.then(function setLocationToInvalid() { - this.fillSelectors('form.user-profile', { - '#user-location': new Array(1002).join('a') - }); - }); - - casper.thenClick('.view-actions .btn-blue'); - - casper.waitForSelectorTextChange('.notification-error', function onSuccess() { - test.assertSelectorHasText('.notification-error', 'is too long', '.notification-error text is correct'); - }, casper.failOnTimeout(test, 'Location field length error did not appear', 2000)); -}); diff --git a/core/test/functional/client/team_test.js b/core/test/functional/client/team_test.js new file mode 100644 index 0000000000..dd4c679d47 --- /dev/null +++ b/core/test/functional/client/team_test.js @@ -0,0 +1,332 @@ +// # Settings Test + +/*globals CasperTest, casper */ + +// These classes relate to elements which only appear when a given tab is loaded. +// These are used to check that a switch to a tab is complete, or that we are on the right tab. +var generalTabDetector = '.gh-nav-settings-general.active', + usersTabDetector = '.gh-nav-main-users'; + +CasperTest.begin('Team tab is correct', 4, function suite(test) { + // TODO make sure settings nav tests are refactored into app_test.js + + casper.then(function testSwitchingTabs() { + casper.thenClick('.gh-nav-main-users'); + casper.waitForSelector(usersTabDetector, function then() { + // assert that the right menu item is active + test.assertExists('.gh-nav-main-users.active', 'Users link is active'); + test.assertDoesntExist('.gh-nav-settings-general.active', 'General link is not active'); + }, casper.failOnTimeout(test, 'waitForSelector `usersTabDetector` timed out')); + + casper.thenClick('.gh-nav-settings-general'); + casper.waitForSelector(generalTabDetector, function then() { + // assert that the right menu item is active + test.assertExists('.gh-nav-settings-general.active', 'General link is active'); + test.assertDoesntExist('.gh-nav-main-users.active', 'User link is not active'); + }, casper.failOnTimeout(test, 'waitForSelector `generalTabDetector` timed out')); + }); +}); + +CasperTest.begin('Users screen is correct', 9, function suite(test) { + casper.thenOpenAndWaitForPageLoad('settings.general'); + casper.thenTransitionAndWaitForScreenLoad('team', function canTransition() { + test.assert(true, 'Can transition to users screen from settings.general'); + test.assertUrlMatch(/ghost\/team\/$/, 'team transitions to correct url'); + }); + casper.then(function usersScreenHasContent() { + test.assertSelectorHasText('.settings-users .user-list .user-list-title', 'Active users', 'active users text is correct'); + test.assertExists('.settings-users .user-list .user-list-item', 'Has an active user'); + test.assertSelectorHasText('.settings-users .user-list-item .name', 'Test User', 'test user text is correct'); + test.assertExists('.settings-users .user-list-item .role-label.owner', 'First user has owner role displayed'); + + test.assertExists('.view-actions .btn-green', 'Add user button is on page.'); + }); + casper.thenClick('.view-actions .btn-green'); + casper.waitForOpaque('.invite-new-user .modal-content', function then() { + test.assertEval(function testOwnerRoleNotAnOption() { + var options = document.querySelectorAll('.invite-new-user select#new-user-role option'), + i = 0; + for (; i < options.length; i = i + 1) { + if (options[i].text === 'Owner') { + return false; + } + } + return true; + }, '"Owner" is not a role option for new users'); + }); + // role options get loaded asynchronously; give them a chance to come in + casper.waitForSelector('.invite-new-user select#new-user-role option', function then() { + test.assertEval(function authorIsSelectedByDefault() { + var options = document.querySelectorAll('.invite-new-user select#new-user-role option'), + i = 0; + for (; i < options.length; i = i + 1) { + if (options[i].selected) { + return options[i].text === 'Author'; + } + } + return false; + }, 'The "Author" role is selected by default when adding a new user'); + }); +}); +// ### User settings tests +CasperTest.begin('Can save settings', 7, function suite(test) { + casper.thenOpenAndWaitForPageLoad('team.user', function testTitleAndUrl() { + test.assertTitle('Team - User - Test Blog', 'Ghost Admin title is correct'); + test.assertUrlMatch(/ghost\/team\/test\/$/, 'team.user has correct URL'); + }); + + function handleUserRequest(requestData) { + // make sure we only get requests from the user pane + if (requestData.url.indexOf('settings/') !== -1) { + test.fail('Saving the user pane triggered another settings pane to save'); + } + } + + function handleSettingsRequest(requestData) { + // make sure we only get requests from the user pane + if (requestData.url.indexOf('team/') !== -1) { + test.fail('Saving a settings pane triggered the user pane to save'); + } + } + + casper.then(function listenForRequests() { + casper.on('resource.requested', handleUserRequest); + }); + + casper.thenClick('.btn-blue'); + casper.waitFor(function successNotification() { + return this.evaluate(function () { + return document.querySelectorAll('.gh-notification').length > 0; + }); + }, function doneWaiting() { + test.pass('Waited for notification'); + }, casper.failOnTimeout(test, 'Saving the user pane did not result in a notification')); + + casper.then(function checkUserWasSaved() { + casper.removeListener('resource.requested', handleUserRequest); + }); + + casper.waitForSelector('.notification-success', function onSuccess() { + test.assert(true, 'Got success notification'); + }, casper.failOnTimeout(test, 'No success notification :(')); + + casper.thenClick('.gh-nav-settings-general').then(function testTransitionToGeneral() { + casper.waitForSelector(generalTabDetector, function then() { + casper.on('resource.requested', handleSettingsRequest); + test.assertEval(function testGeneralIsActive() { + return document.querySelector('.gh-nav-settings-general').classList.contains('active'); + }, 'general tab is marked active'); + }, + casper.failOnTimeout(test, 'waitForSelector `usersTabDetector` timed out')); + }); + + casper.thenClick('.btn-blue').waitFor(function successNotification() { + return this.evaluate(function () { + return document.querySelectorAll('.gh-notification').length > 0; + }); + }, function doneWaiting() { + test.pass('Waited for notification'); + }, casper.failOnTimeout(test, 'Saving the general pane did not result in a notification')); + + casper.then(function checkSettingsWereSaved() { + casper.removeListener('resource.requested', handleSettingsRequest); + }); + + casper.waitForSelector('.notification-success', function onSuccess() { + test.assert(true, 'Got success notification'); + }, casper.failOnTimeout(test, 'No success notification :(')); + + CasperTest.beforeDone(function () { + casper.removeListener('resource.requested', handleUserRequest); + casper.removeListener('resource.requested', handleSettingsRequest); + }); +}); + +CasperTest.begin('User settings screen resets all whitespace slug to original value', 3, function suite(test) { + var slug; + + casper.thenOpenAndWaitForPageLoad('team.user', function testTitleAndUrl() { + test.assertTitle('Team - User - Test Blog', 'Ghost admin has incorrect title'); + test.assertUrlMatch(/ghost\/team\/test\/$/, 'Ghost doesn\'t require login this time'); + }); + + casper.then(function getSlugValue() { + slug = this.evaluate(function () { + return document.querySelector('#user-slug').value; + }); + }); + + casper.then(function changeSlugInput() { + casper.fillSelectors('.user-profile', { + '#user-slug': ' ' + }, false); + }); + + casper.thenClick('body'); + + casper.then(function checkSlugInputValue() { + casper.wait(250); + test.assertField('user', slug, 'user slug is correct'); + }); +}); + +CasperTest.begin('User settings screen change slug handles duplicate slug', 4, function suite(test) { + var slug; + + casper.thenOpenAndWaitForPageLoad('team.user', function testTitleAndUrl() { + test.assertTitle('Team - User - Test Blog', 'Ghost admin has incorrect title'); + test.assertUrlMatch(/ghost\/team\/test\/$/, 'Ghost doesn\'t require login this time'); + }); + + casper.then(function getSlugValue() { + slug = this.evaluate(function () { + return document.querySelector('#user-slug').value; + }); + }); + + casper.then(function changeSlug() { + casper.fillSelectors('.user-profile', { + '#user-slug': slug + '!' + }, false); + }); + + casper.thenClick('body'); + + casper.waitForResource(/\/slugs\/user\//, function testGoodResponse(resource) { + test.assert(resource.status < 400, 'resource.status < 400'); + }); + + casper.then(function checkSlugInputValue() { + test.assertField('user', slug, 'user slug is correct'); + }); +}); + +CasperTest.begin('User settings screen validates email', 6, function suite(test) { + var email; + + casper.thenOpenAndWaitForPageLoad('team.user', function testTitleAndUrl() { + test.assertTitle('Team - User - Test Blog', 'Ghost admin has incorrect title'); + test.assertUrlMatch(/ghost\/team\/test\/$/, 'Ghost doesn\'t require login this time'); + }); + + casper.then(function getEmail() { + email = this.evaluate(function () { + return document.querySelector('#user-email').value; + }); + }); + + casper.then(function setEmailToInvalid() { + var brokenEmail = email.replace('.', '-'); + + casper.fillSelectors('.user-profile', { + '#user-email': brokenEmail + }, false); + }); + + casper.thenClick('.btn-blue'); + + casper.waitForResource('/team/'); + + casper.waitForSelector('.notification-error', function onSuccess() { + test.assert(true, 'Got error notification'); + test.assertSelectorDoesntHaveText('.notification-error', '[object Object]', 'notification text is not broken'); + }, casper.failOnTimeout(test, 'No error notification :(')); + + casper.then(function resetEmailToValid() { + casper.fillSelectors('.user-profile', { + '#user-email': email + }, false); + }); + + casper.thenClick('.view-actions .btn-blue'); + + casper.waitForResource(/users/); + + casper.waitForSelector('.notification-success', function onSuccess() { + test.assert(true, 'Got success notification'); + test.assertSelectorDoesntHaveText('.notification-success', '[object Object]', 'notification text is not broken'); + }, casper.failOnTimeout(test, 'No success notification :(')); +}); + +// TODO: user needs to be loaded whenever it is edited (multi user) +CasperTest.begin('User settings screen shows remaining characters for Bio properly', 4, function suite(test) { + casper.thenOpenAndWaitForPageLoad('team.user', function testTitleAndUrl() { + test.assertTitle('Team - User - Test Blog', 'Ghost admin has incorrect title'); + test.assertUrlMatch(/ghost\/team\/test\/$/, 'Ghost doesn\'t require login this time'); + }); + + function getRemainingBioCharacterCount() { + return casper.getHTML('.word-count'); + } + + casper.then(function checkCharacterCount() { + test.assert(getRemainingBioCharacterCount() === '200', 'Bio remaining characters is 200'); + }); + + casper.then(function setBioToValid() { + casper.fillSelectors('.user-profile', { + '#user-bio': 'asdf\n' // 5 characters + }, false); + }); + + casper.then(function checkCharacterCount() { + test.assert(getRemainingBioCharacterCount() === '195', 'Bio remaining characters is 195'); + }); +}); + +CasperTest.begin('Ensure user bio field length validation', 3, function suite(test) { + casper.thenOpenAndWaitForPageLoad('team.user', function testTitleAndUrl() { + test.assertTitle('Team - User - Test Blog', 'Ghost admin has incorrect title'); + test.assertUrlMatch(/ghost\/team\/test\/$/, 'Ghost doesn\'t require login this time'); + }); + + casper.then(function setBioToInvalid() { + this.fillSelectors('form.user-profile', { + '#user-bio': new Array(202).join('a') + }); + }); + + casper.thenClick('.view-actions .btn-blue'); + + casper.waitForSelectorTextChange('.notification-error', function onSuccess() { + test.assertSelectorHasText('.notification-error', 'is too long', '.notification-error text is correct'); + }, casper.failOnTimeout(test, 'Bio field length error did not appear', 2000)); +}); + +CasperTest.begin('Ensure user url field validation', 3, function suite(test) { + casper.thenOpenAndWaitForPageLoad('team.user', function testTitleAndUrl() { + test.assertTitle('Team - User - Test Blog', 'Ghost admin has incorrect title'); + test.assertUrlMatch(/ghost\/team\/test\/$/, 'Ghost doesn\'t require login this time'); + }); + + casper.then(function setWebsiteToInvalid() { + this.fillSelectors('form.user-profile', { + '#user-website': 'notaurl' + }); + }); + + casper.thenClick('.view-actions .btn-blue'); + + casper.waitForSelectorTextChange('.notification-error', function onSuccess() { + test.assertSelectorHasText('.notification-error', 'not a valid url', '.notification-error text is correct'); + }, casper.failOnTimeout(test, 'Url validation error did not appear', 2000)); +}); + +CasperTest.begin('Ensure user location field length validation', 3, function suite(test) { + casper.thenOpenAndWaitForPageLoad('team.user', function testTitleAndUrl() { + test.assertTitle('Team - User - Test Blog', 'Ghost admin has incorrect title'); + test.assertUrlMatch(/ghost\/team\/test\/$/, 'Ghost doesn\'t require login this time'); + }); + + casper.then(function setLocationToInvalid() { + this.fillSelectors('form.user-profile', { + '#user-location': new Array(1002).join('a') + }); + }); + + casper.thenClick('.view-actions .btn-blue'); + + casper.waitForSelectorTextChange('.notification-error', function onSuccess() { + test.assertSelectorHasText('.notification-error', 'is too long', '.notification-error text is correct'); + }, casper.failOnTimeout(test, 'Location field length error did not appear', 2000)); +});