diff --git a/core/client/controllers/posts.js b/core/client/controllers/posts.js index d85cdce8e3..b5ba0de310 100644 --- a/core/client/controllers/posts.js +++ b/core/client/controllers/posts.js @@ -1,6 +1,6 @@ -import { getRequestErrorMessage } from 'ghost/utils/ajax'; +import PaginationControllerMixin from 'ghost/mixins/pagination-controller'; -var PostsController = Ember.ArrayController.extend({ +var PostsController = Ember.ArrayController.extend(PaginationControllerMixin, { // this will cause the list to re-sort when any of these properties change on any of the models sortProperties: ['status', 'published_at', 'updated_at'], @@ -66,64 +66,11 @@ var PostsController = Ember.ArrayController.extend({ return statusResult; }, - // set from PostsRoute - paginationSettings: null, - - // holds the next page to load during infinite scroll - nextPage: null, - - // indicates whether we're currently loading the next page - isLoading: null, - init: function () { - this._super(); + //let the PaginationControllerMixin know what type of model we will be paginating + //this is necesariy because we do not have access to the model inside the Controller::init method + this._super({'modelType': 'post'}); - var metadata = this.store.metadataFor('post'); - this.set('nextPage', metadata.pagination.next); - }, - - /** - * Takes an ajax response, concatenates any error messages, then generates an error notification. - * @param {jqXHR} response The jQuery ajax reponse object. - * @return - */ - reportLoadError: function (response) { - var message = 'A problem was encountered while loading more posts'; - - if (response) { - // Get message from response - message += ': ' + getRequestErrorMessage(response, true); - } else { - message += '.'; - } - - this.notifications.showError(message); - }, - - actions: { - /** - * Loads the next paginated page of posts into the ember-data store. Will cause the posts list UI to update. - * @return - */ - loadNextPage: function () { - var self = this, - store = this.get('store'), - nextPage = this.get('nextPage'), - paginationSettings = this.get('paginationSettings'); - - if (nextPage) { - this.set('isLoading', true); - this.set('paginationSettings.page', nextPage); - store.find('post', paginationSettings).then(function () { - var metadata = store.metadataFor('post'); - - self.set('nextPage', metadata.pagination.next); - self.set('isLoading', false); - }, function (response) { - self.reportLoadError(response); - }); - } - } } }); diff --git a/core/client/controllers/settings/users/index.js b/core/client/controllers/settings/users/index.js index 0bb053defb..2bd4903678 100644 --- a/core/client/controllers/settings/users/index.js +++ b/core/client/controllers/settings/users/index.js @@ -1,4 +1,12 @@ -var UsersIndexController = Ember.ArrayController.extend({ +import PaginationControllerMixin from 'ghost/mixins/pagination-controller'; + +var UsersIndexController = Ember.ArrayController.extend(PaginationControllerMixin, { + init: function () { + //let the PaginationControllerMixin know what type of model we will be paginating + //this is necesariy because we do not have access to the model inside the Controller::init method + this._super({'modelType': 'user'}); + }, + users: Ember.computed.alias('model'), activeUsers: Ember.computed.filterBy('users', 'active', true).property('users'), diff --git a/core/client/mixins/pagination-controller.js b/core/client/mixins/pagination-controller.js new file mode 100644 index 0000000000..5cbd86dcdb --- /dev/null +++ b/core/client/mixins/pagination-controller.js @@ -0,0 +1,76 @@ +import { getRequestErrorMessage } from 'ghost/utils/ajax'; + +var PaginationControllerMixin = Ember.Mixin.create({ + + // set from PaginationRouteMixin + paginationSettings: null, + + // holds the next page to load during infinite scroll + nextPage: null, + + // indicates whether we're currently loading the next page + isLoading: null, + + /** + * + * @param options: { + * modelType: name of the model that will be paginated + * } + */ + init: function (options) { + this._super(); + + var metadata = this.store.metadataFor(options.modelType); + this.set('nextPage', metadata.pagination.next); + }, + + + /** + * Takes an ajax response, concatenates any error messages, then generates an error notification. + * @param {jqXHR} response The jQuery ajax reponse object. + * @return + */ + reportLoadError: function (response) { + var message = 'A problem was encountered while loading more records'; + + if (response) { + // Get message from response + message += ': ' + getRequestErrorMessage(response, true); + } else { + message += '.'; + } + + this.notifications.showError(message); + }, + + actions: { + /** + * Loads the next paginated page of posts into the ember-data store. Will cause the posts list UI to update. + * @return + */ + loadNextPage: function () { + + var self = this, + store = this.get('store'), + recordType = this.get('model').get('type'), + nextPage = this.get('nextPage'), + paginationSettings = this.get('paginationSettings'); + + if (nextPage) { + this.set('isLoading', true); + this.set('paginationSettings.page', nextPage); + store.find(recordType, paginationSettings).then(function () { + var metadata = store.metadataFor(recordType); + + self.set('nextPage', metadata.pagination.next); + self.set('isLoading', false); + }, function (response) { + self.reportLoadError(response); + }); + } + } + } + +}); + +export default PaginationControllerMixin; diff --git a/core/client/mixins/pagination-route.js b/core/client/mixins/pagination-route.js new file mode 100644 index 0000000000..874e7a7cc7 --- /dev/null +++ b/core/client/mixins/pagination-route.js @@ -0,0 +1,23 @@ +var defaultPaginationSettings = { + page: 1, + limit: 15 +}; + +var PaginationRoute = Ember.Mixin.create({ + + /** + * Sets up pagination details + * @param {settings}: object that specifies additional pagination details + */ + setupPagination: function (settings) { + + settings = settings || {}; + settings = _.defaults(settings, defaultPaginationSettings); + + this.set('paginationSettings', settings); + this.controller.set('paginationSettings', settings); + } + +}); + +export default PaginationRoute; diff --git a/core/client/mixins/pagination-view-infinite-scroll.js b/core/client/mixins/pagination-view-infinite-scroll.js new file mode 100644 index 0000000000..1c1e8104c4 --- /dev/null +++ b/core/client/mixins/pagination-view-infinite-scroll.js @@ -0,0 +1,39 @@ +var PaginationViewInfiniteScrollMixin = Ember.Mixin.create({ + + /** + * Determines if we are past a scroll point where we need to fetch the next page + * @param event The scroll event + */ + checkScroll: function (event) { + var element = event.target, + triggerPoint = 100, + controller = this.get('controller'), + isLoading = controller.get('isLoading'); + + // If we haven't passed our threshold or we are already fetching content, exit + if (isLoading || (element.scrollTop + element.clientHeight + triggerPoint <= element.scrollHeight)) { + return; + } + + controller.send('loadNextPage'); + }, + + /** + * Bind to the scroll event once the element is in the DOM + */ + didInsertElement: function () { + var el = this.$(); + + el.on('scroll', Ember.run.bind(this, this.checkScroll)); + }, + + /** + * Unbind from the scroll event when the element is no longer in the DOM + */ + willDestroyElement: function () { + var el = this.$(); + el.off('scroll'); + } +}); + +export default PaginationViewInfiniteScrollMixin; diff --git a/core/client/routes/posts.js b/core/client/routes/posts.js index 179ea9a85c..16c5276981 100644 --- a/core/client/routes/posts.js +++ b/core/client/routes/posts.js @@ -1,6 +1,7 @@ import styleBody from 'ghost/mixins/style-body'; import ShortcutsRoute from 'ghost/mixins/shortcuts-route'; import loadingIndicator from 'ghost/mixins/loading-indicator'; +import PaginationRouteMixin from 'ghost/mixins/pagination-route'; var paginationSettings = { status: 'all', @@ -9,7 +10,7 @@ var paginationSettings = { page: 1 }; -var PostsRoute = Ember.Route.extend(Ember.SimpleAuth.AuthenticatedRouteMixin, ShortcutsRoute, styleBody, loadingIndicator, { +var PostsRoute = Ember.Route.extend(Ember.SimpleAuth.AuthenticatedRouteMixin, ShortcutsRoute, styleBody, loadingIndicator, PaginationRouteMixin, { classNames: ['manage'], model: function () { @@ -22,7 +23,7 @@ var PostsRoute = Ember.Route.extend(Ember.SimpleAuth.AuthenticatedRouteMixin, Sh setupController: function (controller, model) { this._super(controller, model); - controller.set('paginationSettings', paginationSettings); + this.setupPagination(paginationSettings); }, shortcuts: { diff --git a/core/client/routes/settings/users/index.js b/core/client/routes/settings/users/index.js index 445375e51b..608bf1f77e 100644 --- a/core/client/routes/settings/users/index.js +++ b/core/client/routes/settings/users/index.js @@ -1,7 +1,34 @@ -var UsersIndexRoute = Ember.Route.extend(Ember.SimpleAuth.AuthenticatedRouteMixin, { +import PaginationRouteMixin from 'ghost/mixins/pagination-route'; + +var activeUsersPaginationSettings = { + include: 'roles', + page: 1, + limit: 20 +}; + +var invitedUsersPaginationSettings = { + include: 'roles', + limit: 'all', + status: 'invited' +}; + +var UsersIndexRoute = Ember.Route.extend(Ember.SimpleAuth.AuthenticatedRouteMixin, PaginationRouteMixin, { + setupController: function (controller, model) { + this._super(controller, model.active); + this.setupPagination(activeUsersPaginationSettings); + }, model: function () { - return this.store.find('user'); + // using `.filter` allows the template to auto-update when new models are pulled in from the server. + // we just need to 'return true' to allow all models by default. + return Ember.RSVP.hash({ + inactive: this.store.filter('user', invitedUsersPaginationSettings, function () { + return true; + }), + active: this.store.filter('user', activeUsersPaginationSettings, function () { + return true; + }) + }); } }); diff --git a/core/client/templates/settings/users/index.hbs b/core/client/templates/settings/users/index.hbs index 66e62ef95d..9ce6e5d993 100644 --- a/core/client/templates/settings/users/index.hbs +++ b/core/client/templates/settings/users/index.hbs @@ -6,7 +6,7 @@ -
+{{#view "settings/users/users-list-view" tagName="section" class="content settings-users" }} {{#if invitedUsers}}
@@ -62,4 +62,4 @@ {{/each}}
-
+{{/view}} diff --git a/core/client/views/content-list-content-view.js b/core/client/views/content-list-content-view.js index 0ed10f257e..688de853d3 100644 --- a/core/client/views/content-list-content-view.js +++ b/core/client/views/content-list-content-view.js @@ -1,34 +1,17 @@ import setScrollClassName from 'ghost/utils/set-scroll-classname'; +import PaginationViewMixin from 'ghost/mixins/pagination-view-infinite-scroll'; -var PostsListView = Ember.View.extend({ + +var PostsListView = Ember.View.extend(PaginationViewMixin, { classNames: ['content-list-content'], - checkScroll: function (event) { - var element = event.target, - triggerPoint = 100, - controller = this.get('controller'), - isLoading = controller.get('isLoading'); - - // If we haven't passed our threshold, exit - if (isLoading || (element.scrollTop + element.clientHeight + triggerPoint <= element.scrollHeight)) { - return; - } - - controller.send('loadNextPage'); - }, - didInsertElement: function () { + this._super(); var el = this.$(); - el.on('scroll', Ember.run.bind(this, this.checkScroll)); el.on('scroll', Ember.run.bind(el, setScrollClassName, { target: el.closest('.content-list'), offset: 10 })); - }, - - willDestroyElement: function () { - var el = this.$(); - el.off('scroll'); } }); diff --git a/core/client/views/settings/users/users-list-view.js b/core/client/views/settings/users/users-list-view.js new file mode 100644 index 0000000000..f7b9aa45f0 --- /dev/null +++ b/core/client/views/settings/users/users-list-view.js @@ -0,0 +1,8 @@ +//import setScrollClassName from 'ghost/utils/set-scroll-classname'; +import PaginationViewMixin from 'ghost/mixins/pagination-view-infinite-scroll'; + +var UsersListView = Ember.View.extend(PaginationViewMixin, { + classNames: ['settings-users'] +}); + +export default UsersListView; diff --git a/core/server/api/users.js b/core/server/api/users.js index c82b719c42..afd82380c3 100644 --- a/core/server/api/users.js +++ b/core/server/api/users.js @@ -42,9 +42,7 @@ users = { if (options.include) { options.include = prepareInclude(options.include); } - return dataProvider.User.findAll(options).then(function (result) { - return { users: result.toJSON() }; - }); + return dataProvider.User.findPage(options); }, function () { return when.reject(new errors.NoPermissionError('You do not have permission to browse users.')); }); diff --git a/core/server/models/user.js b/core/server/models/user.js index 3840aaee13..aef2418034 100644 --- a/core/server/models/user.js +++ b/core/server/models/user.js @@ -109,7 +109,8 @@ User = ghostBookshelf.Model.extend({ findOne: ['withRelated'], findAll: ['withRelated'], setup: ['id'], - edit: ['withRelated', 'id'] + edit: ['withRelated', 'id'], + findPage: ['page', 'limit', 'status'] }; if (validOptions[methodName]) { @@ -131,6 +132,144 @@ User = ghostBookshelf.Model.extend({ return ghostBookshelf.Model.findAll.call(this, options); }, + /** + * #### findPage + * Find results by page - returns an object containing the + * information about the request (page, limit), along with the + * info needed for pagination (pages, total). + * + * **response:** + * + * { + * users: [ + * {...}, {...}, {...} + * ], + * meta: { + * page: __, + * limit: __, + * pages: __, + * total: __ + * } + * } + * + * @params {Object} options + */ + findPage: function (options) { + options = options || {}; + + var userCollection = Users.forge(), + userQuery; + + if (options.limit && options.limit !== 'all') { + options.limit = parseInt(options.limit) || 15; + } + + options = this.filterOptions(options, 'findPage'); + + // Set default settings for options + options = _.extend({ + page: 1, // pagination page + limit: 15, + status: 'active', + where: {} + }, options); + + //TODO: there are multiple statuses that make a user "active" or "invited" - we a way to translate/map them: + //TODO (cont'd from above): * valid "active" statuses: active, warn-1, warn-2, warn-3, warn-4, locked + //TODO (cont'd from above): * valid "invited" statuses" invited, invited-pending + + // the status provided. + if (options.status) { + // make sure that status is valid + //TODO: need a better way of getting a list of statuses other than hard-coding them... + options.status = _.indexOf(['active', 'warn-1', 'warn-2', 'warn-3', 'locked', 'invited'], options.status) !== -1 ? options.status : 'active'; + options.where.status = options.status; + } + + // If there are where conditionals specified, add those + // to the query. + if (options.where) { + userCollection.query('where', options.where); + } + + // Add related objects + options.withRelated = _.union([ 'roles' ], options.include); + + //only include a limit-query if a numeric limit is provided + if (_.isNumber(options.limit)) { + userCollection + .query('limit', options.limit) + .query('offset', options.limit * (options.page - 1)); + } + + userQuery = userCollection + .query('orderBy', 'last_login', 'DESC') + .query('orderBy', 'name', 'ASC') + .query('orderBy', 'created_at', 'DESC') + .fetch(_.omit(options, 'page', 'limit')); + + + return when(userQuery) + + // Fetch pagination information + .then(function () { + var qb, + tableName = _.result(userCollection, 'tableName'), + idAttribute = _.result(userCollection, 'idAttribute'); + + // After we're done, we need to figure out what + // the limits are for the pagination values. + qb = ghostBookshelf.knex(tableName); + + if (options.where) { + qb.where(options.where); + } + + return qb.count(tableName + '.' + idAttribute + ' as aggregate'); + }) + + // Format response of data + .then(function (resp) { + var totalUsers = parseInt(resp[0].aggregate, 10), + calcPages = Math.ceil(totalUsers / options.limit), + pagination = {}, + meta = {}, + data = {}; + + pagination.page = parseInt(options.page, 10); + pagination.limit = options.limit; + pagination.pages = calcPages === 0 ? 1 : calcPages; + pagination.total = totalUsers; + pagination.next = null; + pagination.prev = null; + + // Pass include to each model so that toJSON works correctly + if (options.include) { + _.each(userCollection.models, function (item) { + item.include = options.include; + }); + } + + data.users = userCollection.toJSON(); + data.meta = meta; + meta.pagination = pagination; + + if (pagination.pages > 1) { + if (pagination.page === 1) { + pagination.next = pagination.page + 1; + } else if (pagination.page === pagination.pages) { + pagination.prev = pagination.page - 1; + } else { + pagination.next = pagination.page + 1; + pagination.prev = pagination.page - 1; + } + } + + return data; + }) + .catch(errors.logAndThrowError); + }, + /** * ### Find One * @extends ghostBookshelf.Model.findOne to include roles diff --git a/core/test/functional/routes/api/users_test.js b/core/test/functional/routes/api/users_test.js index 922fd5ba7e..6ac35f50fd 100644 --- a/core/test/functional/routes/api/users_test.js +++ b/core/test/functional/routes/api/users_test.js @@ -117,7 +117,7 @@ describe('User API', function () { should.not.exist(res.headers['x-cache-invalidate']); var jsonResponse = res.body; jsonResponse.users.should.exist; - testUtils.API.checkResponse(jsonResponse, 'users'); + should.not.exist(jsonResponse.meta); jsonResponse.users.should.have.length(1); testUtils.API.checkResponse(jsonResponse.users[0], 'user', ['roles']); @@ -138,7 +138,7 @@ describe('User API', function () { should.not.exist(res.headers['x-cache-invalidate']); var jsonResponse = res.body; jsonResponse.users.should.exist; - testUtils.API.checkResponse(jsonResponse, 'users'); + should.not.exist(jsonResponse.meta); jsonResponse.users.should.have.length(1); testUtils.API.checkResponse(jsonResponse.users[0], 'user', ['roles']); @@ -159,7 +159,7 @@ describe('User API', function () { should.not.exist(res.headers['x-cache-invalidate']); var jsonResponse = res.body; jsonResponse.users.should.exist; - testUtils.API.checkResponse(jsonResponse, 'users'); + should.not.exist(jsonResponse.meta); jsonResponse.users.should.have.length(1); testUtils.API.checkResponse(jsonResponse.users[0], 'user', ['roles']); @@ -180,7 +180,7 @@ describe('User API', function () { should.not.exist(res.headers['x-cache-invalidate']); var jsonResponse = res.body; jsonResponse.users.should.exist; - testUtils.API.checkResponse(jsonResponse, 'users'); + should.not.exist(jsonResponse.meta); jsonResponse.users.should.have.length(1); testUtils.API.checkResponse(jsonResponse.users[0], 'user', ['roles']); @@ -201,7 +201,7 @@ describe('User API', function () { should.not.exist(res.headers['x-cache-invalidate']); var jsonResponse = res.body; jsonResponse.users.should.exist; - testUtils.API.checkResponse(jsonResponse, 'users'); + should.not.exist(jsonResponse.meta); jsonResponse.users.should.have.length(1); testUtils.API.checkResponse(jsonResponse.users[0], 'user', ['roles']); @@ -223,7 +223,7 @@ describe('User API', function () { should.not.exist(res.headers['x-cache-invalidate']); var jsonResponse = res.body; jsonResponse.users.should.exist; - testUtils.API.checkResponse(jsonResponse, 'users'); + should.not.exist(jsonResponse.meta); jsonResponse.users.should.have.length(1); testUtils.API.checkResponse(jsonResponse.users[0], 'user', ['roles']); @@ -247,7 +247,7 @@ describe('User API', function () { should.not.exist(res.headers['x-cache-invalidate']); var jsonResponse = res.body; jsonResponse.users.should.exist; - testUtils.API.checkResponse(jsonResponse, 'users'); + should.not.exist(jsonResponse.meta); jsonResponse.users.should.have.length(1); testUtils.API.checkResponse(jsonResponse.users[0], 'user', ['roles']); @@ -366,4 +366,4 @@ describe('User API', function () { }); }); }); -}); \ No newline at end of file +}); diff --git a/core/test/integration/api/api_authentication_spec.js b/core/test/integration/api/api_authentication_spec.js index b98192de2d..da8447d176 100644 --- a/core/test/integration/api/api_authentication_spec.js +++ b/core/test/integration/api/api_authentication_spec.js @@ -58,8 +58,8 @@ describe('Authentication API', function () { AuthAPI.setup({ setup: [setupData] }).then(function (result) { should.exist(result); should.exist(result.users); + should.not.exist(result.meta); result.users.should.have.length(1); - testUtils.API.checkResponse(result, 'users'); testUtils.API.checkResponse(result.users[0], 'user', ['roles']); var newUser = result.users[0]; diff --git a/core/test/integration/api/api_users_spec.js b/core/test/integration/api/api_users_spec.js index 311ac35d14..6406ef8df4 100644 --- a/core/test/integration/api/api_users_spec.js +++ b/core/test/integration/api/api_users_spec.js @@ -1,19 +1,53 @@ /*globals describe, before, beforeEach, afterEach, it */ -/*jshint expr:true*/ -var testUtils = require('../../utils'), - should = require('should'), +var testUtils = require('../../utils'), + should = require('should'), + + permissions = require('../../../server/permissions'), + UserModel = require('../../../server/models').User; // Stuff we are testing - permissions = require('../../../server/permissions'), - UserModel = require('../../../server/models').User, - UsersAPI = require('../../../server/api/users'); + UsersAPI = require('../../../server/api/users'); + AuthAPI = require('../../../server/api/authentication'); describe('Users API', function () { - // Keep the DB clean - before(testUtils.teardown); - afterEach(testUtils.teardown); - should.exist(UsersAPI); + before(function (done) { + testUtils.clearData().then(function () { + done(); + }).catch(done); + }); + + afterEach(function (done) { + testUtils.clearData().then(function () { + done(); + }).catch(done); + }); + + describe('No User', function () { + beforeEach(function (done) { + testUtils.initData().then(function () { + return permissions.init(); + }).then(function () { + done(); + }).catch(done); + }); + + it('can add with internal user', function (done) { + AuthAPI.setup({ setup: [{ + 'name': 'Hello World', + 'email': 'hello@world.com', + 'password': 'password' + }]}).then(function (results) { + should.exist(results); + should.not.exist(results.meta); + should.exist(results.users); + results.users.should.have.length(1); + testUtils.API.checkResponse(results.users[0], 'user', ['roles']); + results.users[0].name.should.equal('Hello World'); + done(); + }).catch(done); + }); + }); describe('With Users', function () { beforeEach(function (done) { @@ -95,7 +129,8 @@ describe('Users API', function () { it('admin can read', function (done) { UsersAPI.read({id: 1, context: {user: 1}}).then(function (results) { should.exist(results); - testUtils.API.checkResponse(results, 'users'); + should.not.exist(results.meta); + should.exist(results.users); results.users[0].id.should.eql(1); testUtils.API.checkResponse(results.users[0], 'user', ['roles']); @@ -108,7 +143,8 @@ describe('Users API', function () { it('editor can read', function (done) { UsersAPI.read({id: 1, context: {user: 2}}).then(function (results) { should.exist(results); - testUtils.API.checkResponse(results, 'users'); + should.not.exist(results.meta); + should.exist(results.users); results.users[0].id.should.eql(1); testUtils.API.checkResponse(results.users[0], 'user', ['roles']); done(); @@ -118,7 +154,8 @@ describe('Users API', function () { it('author can read', function (done) { UsersAPI.read({id: 1, context: {user: 3}}).then(function (results) { should.exist(results); - testUtils.API.checkResponse(results, 'users'); + should.not.exist(results.meta); + should.exist(results.users); results.users[0].id.should.eql(1); testUtils.API.checkResponse(results.users[0], 'user', ['roles']); done(); @@ -128,7 +165,8 @@ describe('Users API', function () { it('no-auth can read', function (done) { UsersAPI.read({id: 1}).then(function (results) { should.exist(results); - testUtils.API.checkResponse(results, 'users'); + should.not.exist(results.meta); + should.exist(results.users); results.users[0].id.should.eql(1); testUtils.API.checkResponse(results.users[0], 'user', ['roles']); done(); @@ -138,7 +176,8 @@ describe('Users API', function () { it('admin can edit', function (done) { UsersAPI.edit({users: [{name: 'Joe Blogger'}]}, {id: 1, context: {user: 1}}).then(function (response) { should.exist(response); - testUtils.API.checkResponse(response, 'users'); + should.not.exist(response.meta); + should.exist(response.users); response.users.should.have.length(1); testUtils.API.checkResponse(response.users[0], 'user', ['roles']); response.users[0].name.should.equal('Joe Blogger'); @@ -150,7 +189,8 @@ describe('Users API', function () { it('editor can edit', function (done) { UsersAPI.edit({users: [{name: 'Joe Blogger'}]}, {id: 1, context: {user: 2}}).then(function (response) { should.exist(response); - testUtils.API.checkResponse(response, 'users'); + should.not.exist(response.meta); + should.exist(response.users); response.users.should.have.length(1); testUtils.API.checkResponse(response.users[0], 'user', ['roles']); response.users[0].name.should.eql('Joe Blogger'); @@ -170,7 +210,8 @@ describe('Users API', function () { return UsersAPI.edit({users: [{name: 'Timothy Bogendath'}]}, {id: 3, context: {user: 3}}) .then(function (response) { should.exist(response); - testUtils.API.checkResponse(response, 'users'); + should.not.exist(response.meta); + should.exist(response.users); response.users.should.have.length(1); testUtils.API.checkResponse(response.users[0], 'user', ['roles']); response.users[0].name.should.eql('Timothy Bogendath'); @@ -179,4 +220,4 @@ describe('Users API', function () { }); }); }); -}); \ No newline at end of file +}); diff --git a/core/test/utils/api.js b/core/test/utils/api.js index f913810ce3..0b5df91b5c 100644 --- a/core/test/utils/api.js +++ b/core/test/utils/api.js @@ -6,7 +6,7 @@ var url = require('url'), schema = 'http://', expectedProperties = { posts: ['posts', 'meta'], - users: ['users'], + users: ['users', 'meta'], pagination: ['page', 'limit', 'pages', 'total', 'next', 'prev'], post: ['id', 'uuid', 'title', 'slug', 'markdown', 'html', 'meta_title', 'meta_description', 'featured', 'image', 'status', 'language', 'created_at', 'created_by', 'updated_at',