Browse Source

ember tests passing, configurable users pagination, pagination in ember.js

Rafał Pitoń 10 years ago
parent
commit
6699c6150e

+ 6 - 0
docs/developers/settings.rst

@@ -377,6 +377,12 @@ MISAGO_THREAD_TYPES
 List of clasess defining thread types.
 
 
+MISAGO_USERS_PER_PAGE
+---------------------
+
+Controls pagination of users lists.
+
+
 password_complexity
 -------------------
 

+ 4 - 0
misago/conf/defaults.py

@@ -324,6 +324,10 @@ MISAGO_ONLINE_LIST_SIZE = 50
 MISAGO_ONLINE_LIST_CACHE = 40
 
 
+# Controls number of users displayed on single page
+MISAGO_USERS_PER_PAGE = 12
+
+
 # Controls amount of data used for new threads/replies lists
 # Only unread threads younger than number of days specified in this setting
 # will be considered fresh for "new threads" list

+ 5 - 1
misago/emberapp/app/components/navbar-dropdown.js

@@ -10,5 +10,9 @@ export default Ember.Component.extend({
     Ember.$(document).on('click.misagoNavbarDropdown', function() {
       self.get('navbar-dropdown').hide();
     });
-  }.on('didInsertElement')
+  }.on('didInsertElement'),
+
+  removeClickDelegation: function() {
+    Ember.$(document).off('click.misagoNavbarDropdown');
+  }.on('willDestroyElement')
 });

+ 1 - 1
misago/emberapp/app/components/user-card.js

@@ -8,7 +8,7 @@ export default Ember.Component.extend({
     if (this.get('user.rank.css_class').length) {
       return 'user-card-' + this.get('user.rank.css_class');
     } else {
-      return ''
+      return '';
     }
   }.property('user.rank.css_class')
 });

+ 1 - 0
misago/emberapp/app/mixins/model-pagination.js

@@ -15,6 +15,7 @@ export default Ember.Mixin.create({
         this.transitionTo(routePath.join('.'));
       }
     } else {
+      console.log('invalid page!');
       this.throw404(); // not a valid page number
     }
   },

+ 0 - 1
misago/emberapp/app/routes/users/rank/page.js

@@ -7,7 +7,6 @@ export default IndexRoute.extend({
     var page = this.cleanPage(params.page, transition);
     if (page) {
       this.set('page', page);
-
       return this.store.find('user', {
         'list': 'rank',
         'rank': this.modelFor('users.rank').get('slug'),

+ 1 - 1
misago/emberapp/app/styles/misago/dropdowns.less

@@ -55,7 +55,7 @@
 }
 
 
-// Upscale dropdown on small display
+// Upscale dropdown on small displays
 @media (max-width: @screen-sm-max) {
   .btn-group {
     position: static;

+ 55 - 36
misago/emberapp/app/styles/misago/pagination.less

@@ -5,60 +5,79 @@
 
 // Full-width pager
 .pager-aligned {
-  overflow: auto;
+  .buttons {
+    overflow: auto;
 
-  .btn {
-    .opacity(0.8);
+    .btn {
+      .opacity(0.8);
 
-    .material-icons {
-      font-size: @font-size-base + 4px;
+      .material-icons {
+        font-size: @font-size-base + 4px;
+      }
     }
-  }
-
-  .btn-first-page, .btn-last-page {
-    .opacity(1);
-  }
-
-  .btn-previous-page {
-    float: left;
-    margin-right: 8px;
-  }
 
-  .btn-next-page {
-    float: right;
-    margin-left: 8px;
-  }
-
-  .btn-first-page {
-    .material-icons:first-child {
-      margin-right: @font-size-base * -1.25;
+    .btn-first-page, .btn-last-page {
+      .opacity(1);
     }
-  }
 
-  .btn-last-page {
-    .material-icons:last-child {
-      margin-left: @font-size-base * -1.25;
+    .btn-previous-page {
+      float: left;
+      margin-right: 8px;
     }
-  }
 
-  // Upscale buttons on small displays
-  @media (max-width: @screen-sm-max) {
-    .btn {
-      .material-icons {
-        font-size: @font-size-base * 2;
-      }
+    .btn-next-page {
+      float: right;
+      margin-left: 8px;
     }
 
     .btn-first-page {
       .material-icons:first-child {
-        margin-right: @font-size-base * -1.75;
+        margin-right: @font-size-base * -1.25;
       }
     }
 
     .btn-last-page {
       .material-icons:last-child {
-        margin-left: @font-size-base * -1.75;
+        margin-left: @font-size-base * -1.25;
       }
     }
   }
+
+  .help-text {
+    margin-top: @line-height-computed * -1 - 8px;
+
+    text-align: center;
+  }
+
+
+  @media (max-width: @screen-sm-max) {
+    // Upscale buttons on small displays
+    .buttons {
+      .btn {
+        .material-icons {
+          font-size: @font-size-base * 2;
+        }
+      }
+
+      .btn-first-page {
+        .material-icons:first-child {
+          margin-right: @font-size-base * -1.75;
+        }
+      }
+
+      .btn-last-page {
+        .material-icons:last-child {
+          margin-left: @font-size-base * -1.75;
+        }
+      }
+    }
+
+    // Don't negative margin middle label
+    .help-text {
+      margin-top: @line-height-computed * .75;
+
+      color: @text-muted;
+      font-size: @font-size-small;
+    }
+  }
 }

+ 33 - 26
misago/emberapp/app/templates/components/pagination-aligned.hbs

@@ -1,29 +1,36 @@
-{{#if meta.first}}
-  {{#link-to (join-strings path ".index") model class="btn btn-default btn-previous-page btn-first-page"}}
-    <i class="material-icons">navigate_before</i>
-    <i class="material-icons">navigate_before</i>
-    <span class="hidden-xs hidden-sm">{{gettext "Start"}}</span>
-  {{/link-to}}
-{{/if}}
+<div class="buttons">
+  {{#if meta.first}}
+    {{#link-to (join-strings path ".index") model class="btn btn-default btn-previous-page btn-first-page"}}
+      <i class="material-icons">navigate_before</i>
+      <i class="material-icons">navigate_before</i>
+      <span class="hidden-xs hidden-sm">{{gettext "Start"}}</span>
+    {{/link-to}}
+  {{/if}}
 
-{{#if meta.previous}}
-  {{#link-to (join-strings path ".page") model meta.previous class="btn btn-default btn-previous-page"}}
-    <i class="material-icons">navigate_before</i>
-    <span class="hidden-xs hidden-sm">{{gettext "Previous"}}</span>
-  {{/link-to}}
-{{/if}}
+  {{#if meta.previous}}
+    {{#link-to (join-strings path ".page") model meta.previous class="btn btn-default btn-previous-page"}}
+      <i class="material-icons">navigate_before</i>
+      <span class="hidden-xs hidden-sm">{{gettext "Previous"}}</span>
+    {{/link-to}}
+  {{/if}}
 
-{{#if meta.next}}
-  {{#link-to (join-strings path ".page") model meta.last class="btn btn-default btn-next-page btn-last-page"}}
-    <span class="hidden-xs hidden-sm">{{gettext "End"}}</span>
-    <i class="material-icons">navigate_next</i>
-    <i class="material-icons">navigate_next</i>
-  {{/link-to}}
-{{/if}}
+  {{#if meta.last}}
+    {{#link-to (join-strings path ".page") model meta.last class="btn btn-default btn-next-page btn-last-page"}}
+      <span class="hidden-xs hidden-sm">{{gettext "End"}}</span>
+      <i class="material-icons">navigate_next</i>
+      <i class="material-icons">navigate_next</i>
+    {{/link-to}}
+  {{/if}}
 
-{{#if meta.last}}
-  {{#link-to (join-strings path ".page") model meta.next class="btn btn-default btn-next-page"}}
-    <span class="hidden-xs hidden-sm">{{gettext "Next"}}</span>
-    <i class="material-icons">navigate_next</i>
-  {{/link-to}}
-{{/if}}
+  {{#if meta.next}}
+    {{#link-to (join-strings path ".page") model meta.next class="btn btn-default btn-next-page"}}
+      <span class="hidden-xs hidden-sm">{{gettext "Next"}}</span>
+      <i class="material-icons">navigate_next</i>
+    {{/link-to}}
+  {{/if}}
+</div>
+{{#if template}}
+  <div class="help-text">
+    {{yield}}
+  </div>
+{{/if}}

+ 1 - 1
misago/emberapp/app/templates/components/user-card.hbs

@@ -4,7 +4,7 @@
     <div class="avatar-overlay-inner">
 
       {{#link-to 'user' user.url_name class="user-avatar-link"}}
-        {{user-avatar user=user size=150}}
+        {{user-avatar user=user size=400}}
       {{/link-to}}
 
       <h4>

+ 7 - 2
misago/emberapp/app/templates/users/rank.hbs

@@ -17,6 +17,11 @@
     {{/each}}
   </div>
 
-  {{pagination-aligned path='users.rank' model=rank meta=meta}}
-
+  {{#pagination-aligned path='users.rank' model=rank meta=meta}}
+    {{#if meta.count}}
+      {{ngettext "There is %(members)s member with this rank." "There are %(members)s members with this rank." meta.count members=meta.count}}
+    {{else}}
+      {{gettext "There are no members with this rank."}}
+    {{/if}}
+  {{/pagination-aligned}}
 </div>

+ 1 - 1
misago/emberapp/tests/acceptance/change-username-test.js

@@ -213,7 +213,7 @@ test('/options/change-username changes username', function(assert) {
     assert.equal(currentPath(), 'options.username');
     assert.equal(getToastMessage(), 'Your username has been changed.');
 
-    var listedUsername = Ember.$.trim(find('.last-username-changes .item-name').text());
+    var listedUsername = Ember.$.trim(find('.last-username-changes .first-row .item-name').text());
     assert.equal(listedUsername, 'NewName');
 
     assert.ok(find('.last-username-changes').text().indexOf('BobBoberson') !== -1);

+ 5 - 5
misago/emberapp/tests/acceptance/error-toast-test.js

@@ -27,7 +27,7 @@ test('some unhandled error occured', function(assert) {
 
   visit('/');
 
-  click('.navbar-guest-nav button.btn-default');
+  click('.navbar-guest-nav button.btn-sign-in');
   fillIn('#appModal .form-group:first-child input', 'SomeFake');
   fillIn('#appModal .form-group:last-child input', 'pass1234');
   click('#appModal .btn-primary');
@@ -47,7 +47,7 @@ test('app went away', function(assert) {
 
   visit('/');
 
-  click('.navbar-guest-nav button.btn-default');
+  click('.navbar-guest-nav button.btn-sign-in');
   fillIn('#appModal .form-group:first-child input', 'SomeFake');
   fillIn('#appModal .form-group:last-child input', 'pass1234');
   click('#appModal .btn-primary');
@@ -70,7 +70,7 @@ test('not found', function(assert) {
 
   visit('/');
 
-  click('.navbar-guest-nav button.btn-default');
+  click('.navbar-guest-nav button.btn-sign-in');
   fillIn('#appModal .form-group:first-child input', 'SomeFake');
   fillIn('#appModal .form-group:last-child input', 'pass1234');
   click('#appModal .btn-primary');
@@ -93,7 +93,7 @@ test('permission denied', function(assert) {
 
   visit('/');
 
-  click('.navbar-guest-nav button.btn-default');
+  click('.navbar-guest-nav button.btn-sign-in');
   fillIn('#appModal .form-group:first-child input', 'SomeFake');
   fillIn('#appModal .form-group:last-child input', 'pass1234');
   click('#appModal .btn-primary');
@@ -116,7 +116,7 @@ test('permission denied with reason', function(assert) {
 
   visit('/');
 
-  click('.navbar-guest-nav button.btn-default');
+  click('.navbar-guest-nav button.btn-sign-in');
   fillIn('#appModal .form-group:first-child input', 'SomeFake');
   fillIn('#appModal .form-group:last-child input', 'pass1234');
   click('#appModal .btn-primary');

+ 7 - 7
misago/emberapp/tests/acceptance/login-test.js

@@ -27,7 +27,7 @@ test('login with empty credentials', function(assert) {
   assert.expect(1);
 
   visit('/');
-  click('.navbar-guest-nav button.btn-default');
+  click('.navbar-guest-nav button.btn-sign-in');
   click('#appModal .btn-primary');
 
   andThen(function() {
@@ -45,7 +45,7 @@ test('backend errored', function(assert) {
 
   visit('/');
 
-  click('.navbar-guest-nav .btn-default');
+  click('.navbar-guest-nav .btn-sign-in');
   fillIn('#appModal .form-group:first-child input', 'SomeFake');
   fillIn('#appModal .form-group:last-child input', 'pass1234');
   click('#appModal .btn-primary');
@@ -70,7 +70,7 @@ test('login with invalid credentials', function(assert) {
 
   visit('/');
 
-  click('.navbar-guest-nav .btn-default');
+  click('.navbar-guest-nav .btn-sign-in');
   fillIn('#appModal .form-group:first-child input', 'SomeFake');
   fillIn('#appModal .form-group:last-child input', 'pass1234');
   click('#appModal .btn-primary');
@@ -95,7 +95,7 @@ test('login to user-activated account', function(assert) {
 
   visit('/');
 
-  click('.navbar-guest-nav .btn-default');
+  click('.navbar-guest-nav .btn-sign-in');
   fillIn('#appModal .form-group:first-child input', 'SomeFake');
   fillIn('#appModal .form-group:last-child input', 'pass1234');
   click('#appModal .btn-primary');
@@ -120,7 +120,7 @@ test('login to admin-activated account', function(assert) {
 
   visit('/');
 
-  click('.navbar-guest-nav .btn-default');
+  click('.navbar-guest-nav .btn-sign-in');
   fillIn('#appModal .form-group:first-child input', 'SomeFake');
   fillIn('#appModal .form-group:last-child input', 'pass1234');
   click('#appModal .btn-primary');
@@ -150,7 +150,7 @@ test('login to banned account', function(assert) {
 
   visit('/');
 
-  click('.navbar-guest-nav .btn-default');
+  click('.navbar-guest-nav .btn-sign-in');
   fillIn('#appModal .form-group:first-child input', 'SomeFake');
   fillIn('#appModal .form-group:last-child input', 'pass1234');
   click('#appModal .btn-primary');
@@ -179,7 +179,7 @@ test('login successfully', function(assert) {
 
   visit('/');
 
-  click('.navbar-guest-nav .btn-default');
+  click('.navbar-guest-nav .btn-sign-in');
   fillIn('#appModal .form-group:first-child input', 'SomeFake');
   fillIn('#appModal .form-group:last-child input', 'pass1234');
   click('#appModal .btn-primary');

+ 319 - 12
misago/emberapp/tests/acceptance/model-pagination-test.js

@@ -2,6 +2,7 @@ import Ember from 'ember';
 import { module, test } from 'qunit';
 import startApp from '../helpers/start-app';
 import createUser from '../helpers/create-user';
+import { updateObjProps, paginatedJSON, rankJSON, userJSON } from '../helpers/api-mocks';
 
 var application, container, auth;
 
@@ -25,31 +26,337 @@ test('pagination redirects from explicit first page', function(assert) {
     'user': user
   });
 
+  var rank = rankJSON(3, {
+    'name': 'Test Rank',
+    'slug': 'test-rank',
+    'is_tab': true
+  });
+
+  Ember.$.mockjax({
+    url: '/api/ranks/',
+    status: 200,
+    responseText: [rank]
+  });
+
+  var users = [];
+  for (var i = 1; i <= 20; i++) {
+    users.push(userJSON(i, { 'rank': rank }));
+  }
+
+  var resultsJSON = paginatedJSON(users, 20, 2, 10, 5);
+  resultsJSON.users = 10;
+
+  Ember.$.mockjax({
+    url: '/api/users/',
+    data: {
+      'list': 'rank',
+      'rank': 'test-rank'
+    },
+    status: 200,
+    responseText: resultsJSON
+  });
+
+  assert.expect(1);
+
+  visit('/users/test-rank/1/');
+
+  andThen(function() {
+    assert.equal(currentPath(), 'users.rank.index');
+  });
+});
+
+test('pagination displays next page', function(assert) {
+  var user = createUser();
+  auth.setProperties({
+    'isAuthenticated': true,
+    'user': user
+  });
+
+  var rank = rankJSON(3, {
+    'name': 'Test Rank',
+    'slug': 'test-rank',
+    'is_tab': true
+  });
+
   Ember.$.mockjax({
     url: '/api/ranks/',
     status: 200,
-    responseText: [{
-      'id': 3,
-      'name': 'Test Rank',
-      'slug': 'test-rank',
-      'description': '',
-      'css_class': '',
-      'is_tab': true
-    }]
+    responseText: [rank]
   });
 
+  var users = [];
+  for (var i = 1; i <= 20; i++) {
+    users.push(userJSON(i, { 'rank': rank }));
+  }
+
+  var resultsJSON = paginatedJSON(users, 20, 2, 10, 5);
+  resultsJSON.users = 10;
+
   Ember.$.mockjax({
-    url: '/api/users/?list=rank&rank=test-rank',
+    url: '/api/users/',
+    data: {
+      'list': 'rank',
+      'rank': 'test-rank',
+      'page': 2,
+    },
     status: 200,
-    responseText: []
+    responseText: resultsJSON
   });
 
   assert.expect(1);
 
-  visit('/users/');
+  visit('/users/test-rank/2/');
+
+  andThen(function() {
+    assert.equal(currentPath(), 'users.rank.page');
+  });
+});
+
+test('pagination go to next page', function(assert) {
+  var user = createUser();
+  auth.setProperties({
+    'isAuthenticated': true,
+    'user': user
+  });
+
+  var rank = rankJSON(3, {
+    'name': 'Test Rank',
+    'slug': 'test-rank',
+    'is_tab': true
+  });
+
+  Ember.$.mockjax({
+    url: '/api/ranks/',
+    status: 200,
+    responseText: [rank]
+  });
+
+  var users = [];
+  for (var i = 1; i <= 20; i++) {
+    users.push(userJSON(i, { 'rank': rank }));
+  }
+
+  var resultsJSON = paginatedJSON(users, 20, 2, 6, 2);
+  resultsJSON.users = 10;
+
+  Ember.$.mockjax({
+    url: '/api/users/',
+    data: {
+      'list': 'rank',
+      'rank': 'test-rank',
+      'page': 2
+    },
+    status: 200,
+    responseText: resultsJSON
+  });
+
+  resultsJSON = paginatedJSON(users, 20, 1, 6, 2);
+  resultsJSON.users = 10;
+
+  Ember.$.mockjax({
+    url: '/api/users/',
+    data: {
+      'list': 'rank',
+      'rank': 'test-rank'
+    },
+    status: 200,
+    responseText: resultsJSON
+  });
+
+  assert.expect(2);
+
+  visit('/users/test-rank/');
+  click('.pager-aligned .btn-next-page:last');
+
+  andThen(function() {
+    assert.equal(currentURL(), '/users/test-rank/2');
+    assert.equal(currentPath(), 'users.rank.page');
+  });
+});
+
+test('pagination go to last page', function(assert) {
+  var user = createUser();
+  auth.setProperties({
+    'isAuthenticated': true,
+    'user': user
+  });
+
+  var rank = rankJSON(3, {
+    'name': 'Test Rank',
+    'slug': 'test-rank',
+    'is_tab': true
+  });
+
+  Ember.$.mockjax({
+    url: '/api/ranks/',
+    status: 200,
+    responseText: [rank]
+  });
+
+  var users = [];
+  for (var i = 1; i <= 20; i++) {
+    users.push(userJSON(i, { 'rank': rank }));
+  }
+
+  var resultsJSON = paginatedJSON(users, 20, 3, 6, 2);
+  resultsJSON.users = 10;
+
+  Ember.$.mockjax({
+    url: '/api/users/',
+    data: {
+      'list': 'rank',
+      'rank': 'test-rank',
+      'page': 3
+    },
+    status: 200,
+    responseText: resultsJSON
+  });
+
+  resultsJSON = paginatedJSON(users, 20, 1, 6, 2);
+  resultsJSON.users = 10;
+
+  Ember.$.mockjax({
+    url: '/api/users/',
+    data: {
+      'list': 'rank',
+      'rank': 'test-rank'
+    },
+    status: 200,
+    responseText: resultsJSON
+  });
+
+  assert.expect(2);
+
+  visit('/users/test-rank/');
+  click('.pager-aligned .btn-last-page');
+
+  andThen(function() {
+    assert.equal(currentURL(), '/users/test-rank/3');
+    assert.equal(currentPath(), 'users.rank.page');
+  });
+});
+
+test('pagination go to previous page', function(assert) {
+  var user = createUser();
+  auth.setProperties({
+    'isAuthenticated': true,
+    'user': user
+  });
+
+  var rank = rankJSON(3, {
+    'name': 'Test Rank',
+    'slug': 'test-rank',
+    'is_tab': true
+  });
+
+  Ember.$.mockjax({
+    url: '/api/ranks/',
+    status: 200,
+    responseText: [rank]
+  });
+
+  var users = [];
+  for (var i = 1; i <= 20; i++) {
+    users.push(userJSON(i, { 'rank': rank }));
+  }
+
+  var resultsJSON = paginatedJSON(users, 20, 2, 6, 2);
+  resultsJSON.users = 10;
+
+  Ember.$.mockjax({
+    url: '/api/users/',
+    data: {
+      'list': 'rank',
+      'rank': 'test-rank',
+      'page': 2
+    },
+    status: 200,
+    responseText: resultsJSON
+  });
+
+  resultsJSON = paginatedJSON(users, 20, 3, 6, 2);
+  resultsJSON.users = 10;
+
+  Ember.$.mockjax({
+    url: '/api/users/',
+    data: {
+      'list': 'rank',
+      'rank': 'test-rank',
+      'page': 3
+    },
+    status: 200,
+    responseText: resultsJSON
+  });
+
+  assert.expect(2);
+
+  visit('/users/test-rank/3/');
+  click('.pager-aligned .btn-previous-page:last');
+
+  andThen(function() {
+    assert.equal(currentURL(), '/users/test-rank/2');
+    assert.equal(currentPath(), 'users.rank.page');
+  });
+});
+
+test('pagination go to first page', function(assert) {
+  var user = createUser();
+  auth.setProperties({
+    'isAuthenticated': true,
+    'user': user
+  });
+
+  var rank = rankJSON(3, {
+    'name': 'Test Rank',
+    'slug': 'test-rank',
+    'is_tab': true
+  });
+
+  Ember.$.mockjax({
+    url: '/api/ranks/',
+    status: 200,
+    responseText: [rank]
+  });
+
+  var users = [];
+  for (var i = 1; i <= 20; i++) {
+    users.push(userJSON(i, { 'rank': rank }));
+  }
+
+  var resultsJSON = paginatedJSON(users, 20, 3, 6, 2);
+  resultsJSON.users = 10;
+
+  Ember.$.mockjax({
+    url: '/api/users/',
+    data: {
+      'list': 'rank',
+      'rank': 'test-rank',
+      'page': 3
+    },
+    status: 200,
+    responseText: resultsJSON
+  });
+
+  resultsJSON = paginatedJSON(users, 20, 1, 6, 2);
+  resultsJSON.users = 10;
+
+  Ember.$.mockjax({
+    url: '/api/users/',
+    data: {
+      'list': 'rank',
+      'rank': 'test-rank'
+    },
+    status: 200,
+    responseText: resultsJSON
+  });
+
+  assert.expect(2);
+
+  visit('/users/test-rank/3/');
+  click('.pager-aligned .btn-first-page');
 
   andThen(function() {
-    console.log(find('.nav-tabs').text())
+    assert.equal(currentURL(), '/users/test-rank');
     assert.equal(currentPath(), 'users.rank.index');
   });
 });

+ 8 - 8
misago/emberapp/tests/acceptance/registration-test.js

@@ -24,7 +24,7 @@ test('registration is closed', function(assert) {
   assert.expect(1);
 
   visit('/');
-  click('.navbar-guest-nav button.btn-success');
+  click('.navbar-guest-nav button.btn-join');
 
   andThen(function() {
     assert.equal(Ember.$.trim(find('.modal-register-closed .lead').text()), 'New registrations are currently not being accepted.');
@@ -35,7 +35,7 @@ test('register with empty credentials', function(assert) {
   assert.expect(1);
 
   visit('/');
-  click('.navbar-guest-nav button.btn-success');
+  click('.navbar-guest-nav button.btn-join');
   click('#appModal .btn-primary');
 
   andThen(function() {
@@ -47,7 +47,7 @@ test('register with invalid credentials', function(assert) {
   assert.expect(1);
 
   visit('/');
-  click('.navbar-guest-nav button.btn-success');
+  click('.navbar-guest-nav button.btn-join');
   fillIn('#appModal #id_username', 'a');
   fillIn('#appModal #id_password', 'b');
   fillIn('#appModal #id_email', 'c');
@@ -67,7 +67,7 @@ test('register with rejected credentials', function(assert) {
   });
 
   visit('/');
-  click('.navbar-guest-nav button.btn-success');
+  click('.navbar-guest-nav button.btn-join');
   fillIn('#appModal #id_username', 'Lorem');
   fillIn('#appModal #id_password', 'ipsum123');
   fillIn('#appModal #id_email', 'lorem@ipsum.com');
@@ -97,7 +97,7 @@ test('register banned', function(assert) {
   });
 
   visit('/');
-  click('.navbar-guest-nav button.btn-success');
+  click('.navbar-guest-nav button.btn-join');
   fillIn('#appModal #id_username', 'Lorem');
   fillIn('#appModal #id_password', 'ipsum123');
   fillIn('#appModal #id_email', 'lorem@ipsum.com');
@@ -128,7 +128,7 @@ test('register active user', function(assert) {
   });
 
   visit('/');
-  click('.navbar-guest-nav button.btn-success');
+  click('.navbar-guest-nav button.btn-join');
   fillIn('#appModal #id_username', 'Lorem');
   fillIn('#appModal #id_password', 'ipsum123');
   fillIn('#appModal #id_email', 'lorem@ipsum.com');
@@ -154,7 +154,7 @@ test('register admin-activated user', function(assert) {
   });
 
   visit('/');
-  click('.navbar-guest-nav button.btn-success');
+  click('.navbar-guest-nav button.btn-join');
   fillIn('#appModal #id_username', 'Lorem');
   fillIn('#appModal #id_password', 'ipsum123');
   fillIn('#appModal #id_email', 'lorem@ipsum.com');
@@ -180,7 +180,7 @@ test('register self-activated user', function(assert) {
   });
 
   visit('/');
-  click('.navbar-guest-nav button.btn-success');
+  click('.navbar-guest-nav button.btn-join');
   fillIn('#appModal #id_username', 'Lorem');
   fillIn('#appModal #id_password', 'ipsum123');
   fillIn('#appModal #id_email', 'lorem@ipsum.com');

+ 1 - 1
misago/users/api/userendpoints/list.py

@@ -76,7 +76,7 @@ def rank(request, queryset):
         return
 
     rank = get_object_or_404(Rank.objects.filter(is_tab=True), slug=rank_slug)
-    queryset = queryset.filter(rank=rank)
+    queryset = queryset.filter(rank=rank).order_by('slug')
 
     return {'queryset': queryset, 'paginate': True}
 

+ 2 - 1
misago/users/api/users.py

@@ -1,3 +1,4 @@
+from django.conf import settings
 from django.contrib.auth import get_user_model
 from django.core.exceptions import PermissionDenied
 from django.shortcuts import get_object_or_404
@@ -50,7 +51,7 @@ class UserViewSet(viewsets.GenericViewSet):
     parser_classes=(JSONParser, MultiPartParser)
     serializer_class = UserSerializer
     queryset = get_user_model().objects
-    pagination_class = ApiPaginator(16, 4)
+    pagination_class = ApiPaginator(settings.MISAGO_USERS_PER_PAGE, 4)
 
     def get_queryset(self):
         relations = ('rank', 'online_tracker', 'ban_cache')