Rafał Pitoń 10 лет назад
Родитель
Сommit
4e43fd2f31
35 измененных файлов с 561 добавлено и 269 удалено
  1. 0 2
      misago/emberapp/app/app.js
  2. 102 0
      misago/emberapp/app/controllers/login-modal.js
  3. 4 4
      misago/emberapp/app/initializers/misago-settings.js
  4. 30 21
      misago/emberapp/app/routes/application.js
  5. 9 3
      misago/emberapp/app/templates/guest-nav.hbs
  6. 1 1
      misago/emberapp/app/templates/index.hbs
  7. 29 5
      misago/emberapp/app/templates/login-modal.hbs
  8. 7 0
      misago/emberapp/app/utils/csrf.js
  9. 63 0
      misago/emberapp/app/utils/rpc.js
  10. 7 0
      misago/emberapp/app/utils/strings.js
  11. 4 4
      misago/emberapp/bower.json
  12. 4 4
      misago/emberapp/package.json
  13. 0 23
      misago/emberapp/tests/.jshintrc
  14. 16 15
      misago/emberapp/tests/acceptance/error-handling-test.js
  15. 8 7
      misago/emberapp/tests/acceptance/index-test.js
  16. 8 7
      misago/emberapp/tests/acceptance/privacy-policy-test.js
  17. 8 7
      misago/emberapp/tests/acceptance/terms-of-service-test.js
  18. 31 31
      misago/emberapp/tests/unit/controllers/flash-message-test.js
  19. 17 17
      misago/emberapp/tests/unit/controllers/footer-test.js
  20. 0 15
      misago/emberapp/tests/unit/controllers/guest-nav-test.js
  21. 3 7
      misago/emberapp/tests/unit/controllers/site-nav-test.js
  22. 3 7
      misago/emberapp/tests/unit/controllers/user-nav-test.js
  23. 6 5
      misago/emberapp/tests/unit/initializers/misago-settings-test.js
  24. 4 3
      misago/emberapp/tests/unit/initializers/trailing-slash-test.js
  25. 3 6
      misago/emberapp/tests/unit/models/legal-page-test.js
  26. 11 11
      misago/emberapp/tests/unit/routes/application-test.js
  27. 6 6
      misago/emberapp/tests/unit/routes/index-test.js
  28. 3 6
      misago/emberapp/tests/unit/routes/not-found-test.js
  29. 11 11
      misago/emberapp/tests/unit/routes/privacy-policy-test.js
  30. 11 11
      misago/emberapp/tests/unit/routes/terms-of-service-test.js
  31. 26 0
      misago/emberapp/tests/unit/utils/csrf-test.js
  32. 31 30
      misago/emberapp/tests/unit/utils/preloadstore-test.js
  33. 75 0
      misago/emberapp/tests/unit/utils/rpc-test.js
  34. 16 0
      misago/emberapp/tests/unit/utils/strings-test.js
  35. 4 0
      misago/emberapp/vendor/testutils/misago-preload-data.js

+ 0 - 2
misago/emberapp/app/app.js

@@ -1,7 +1,6 @@
 import Ember from 'ember';
 import Resolver from 'ember/resolver';
 import loadInitializers from 'ember/load-initializers';
-import registerGettextHelpers from 'django-ember-gettext/lib/main';
 import config from './config/environment';
 
 Ember.MODEL_FACTORY_INJECTIONS = true;
@@ -13,7 +12,6 @@ var App = Ember.Application.extend({
   Resolver: Resolver
 });
 
-registerGettextHelpers();
 loadInitializers(App, config.modulePrefix);
 
 export default App;

+ 102 - 0
misago/emberapp/app/controllers/login-modal.js

@@ -0,0 +1,102 @@
+import Ember from 'ember';
+import MisagoPreloadStore from 'misago/utils/preloadstore';
+import rpc from 'misago/utils/rpc';
+
+export default Ember.Controller.extend({
+  modal: null,
+
+  message: '',
+
+  username: '',
+  password: '',
+
+  isLoading: false,
+  showActivation: false,
+
+  scheduleSetup: function() {
+    Ember.run.scheduleOnce('afterRender', this, this.setup);
+  }.on('init'),
+
+  setup: function() {
+    this.modal = Ember.$('#loginModal').modal({show: false});
+
+    this.modal.on('shown.bs.modal', function () {
+      Ember.$('#loginModal').focus();
+    });
+
+    var self = this;
+    this.modal.on('hidden.bs.modal', function() {
+      self.reset();
+    });
+  },
+
+  reset: function() {
+    this.set('message', '');
+
+    this.set('username', '');
+    this.set('password', '');
+
+    this.set('isLoading', false);
+    this.set('showActivation', false);
+  },
+
+  isValid: function(credentials) {
+    this.set('isLoading', true);
+
+    if (!credentials.username || !credentials.password) {
+      this.send('flashWarning', gettext("Fill out both fields."));
+      return false;
+    }
+
+    return true;
+  },
+
+  authenticate: function(credentials) {
+    var self = this;
+    rpc(MisagoPreloadStore.get('authApiUrl'), credentials).then(function() {
+      self.logIn(credentials);
+    }, function(rejection) {
+      self.authError(rejection);
+    }).finally(function() {
+      self.set('isLoading', false);
+    });
+  },
+
+  logIn: function(credentials) {
+    this.send('flashSuccess', gettext("You are authenticated!"));
+    console.log(credentials);
+  },
+
+  authError: function(rejection) {
+    if (rejection.code === 'inactive_admin') {
+      this.send('flashInfo', rejection.detail);
+    } else if (rejection.code === 'inactive_user') {
+      this.send('flashInfo', rejection.detail);
+      this.set('showActivation', true);
+    } else if (rejection.code === 'banned') {
+    } else {
+      this.send('flashError', rejection.detail);
+    }
+  },
+
+  actions: {
+    open: function() {
+      Ember.$('#loginModal').modal('show');
+    },
+
+    signIn: function() {
+      if (!this.get('isLoading')) {
+        var credentials = {
+          username: Ember.$.trim(this.get('username')),
+          password: Ember.$.trim(this.get('password'))
+        };
+
+        if (this.isValid(credentials)) {
+          this.authenticate(credentials);
+        } else {
+          this.set('isLoading', false);
+        }
+      }
+    }
+  }
+});

+ 4 - 4
misago/emberapp/app/initializers/misago-settings.js

@@ -1,15 +1,15 @@
 import MisagoPreloadStore from 'misago/utils/preloadstore';
 
 export function initialize(container, application) {
+  application.register('misago:settings', MisagoPreloadStore.get('misagoSettings'), { instantiate: false });
+  application.inject('route', 'settings', 'misago:settings');
+  application.inject('controller', 'settings', 'misago:settings');
+
   application.register('misago:static-url', MisagoPreloadStore.get('staticUrl'), { instantiate: false });
   application.inject('controller', 'staticUrl', 'misago:static-url');
 
   application.register('misago:media-url', MisagoPreloadStore.get('mediaUrl'), { instantiate: false });
   application.inject('controller', 'mediaUrl', 'misago:media-url');
-
-  application.register('misago:settings', MisagoPreloadStore.get('misagoSettings'), { instantiate: false });
-  application.inject('route', 'settings', 'misago:settings');
-  application.inject('controller', 'settings', 'misago:settings');
 }
 
 export default {

+ 30 - 21
misago/emberapp/app/routes/application.js

@@ -2,6 +2,29 @@ import Ember from 'ember';
 
 export default Ember.Route.extend({
   actions: {
+    setTitle: function(title) {
+      if (typeof title === 'string') {
+        title = {title: title};
+      }
+
+      var complete_title = title.title;
+
+      if (typeof title.page !== 'undefined') {
+        complete_title += ' (' + interpolate(gettext('page %(page)s'), {page:title.page}, true) + ')';
+      }
+
+      if (typeof title.parent !== 'undefined') {
+        complete_title += ' | ' + title.parent;
+      }
+
+      complete_title += ' | ' + this.get('settings.forum_name');
+
+      document.title = complete_title;
+      return false;
+    },
+
+    // Error handler
+
     error: function(error){
       if (error.status === 0) {
         this.send('setTitle', gettext('Connection lost'));
@@ -28,27 +51,6 @@ export default Ember.Route.extend({
       return true;
     },
 
-    setTitle: function(title) {
-      if (typeof title === 'string') {
-        title = {title: title};
-      }
-
-      var complete_title = title.title;
-
-      if (typeof title.page !== 'undefined') {
-        complete_title += ' (' + interpolate(gettext('page %(page)s'), {page:title.page}, true) + ')';
-      }
-
-      if (typeof title.parent !== 'undefined') {
-        complete_title += ' | ' + title.parent;
-      }
-
-      complete_title += ' | ' + this.get('settings.forum_name');
-
-      document.title = complete_title;
-      return false;
-    },
-
     // Flashes
 
     flashInfo: function(message) {
@@ -69,6 +71,13 @@ export default Ember.Route.extend({
     flashError: function(message) {
       this.controllerFor("flashMessage").send('setFlash', 'error', message);
       return false;
+    },
+
+    // Login Modal
+
+    openLoginModal: function() {
+      this.controllerFor("loginModal").send('open');
+      return false;
     }
   }
 });

+ 9 - 3
misago/emberapp/app/templates/guest-nav.hbs

@@ -1,3 +1,9 @@
-<button type="button" class="btn btn-default navbar-btn navbar-right" {{action "openLoginModal"}}>
-  Sign in
-</button>
+<div class="guest-nav navbar-right">
+  <button type="button" class="btn btn-default navbar-btn btn-sm" {{action "openLoginModal"}}>
+    {{gettext "Sign in"}}
+  </button>
+
+  <button type="button" class="btn btn-danger navbar-btn btn-sm">
+    {{gettext "Join now"}}
+  </button>
+</div>

+ 1 - 1
misago/emberapp/app/templates/index.hbs

@@ -7,7 +7,7 @@
 
   <hr>
 
-  <h2>Test Flash Message</h2>
+  <h2>{{gettext "Test %(msg)s Message" msg=(gettext "Flash")}}</h2>
 
   <p>
     {{input type="text" value=newFlash}}

+ 29 - 5
misago/emberapp/app/templates/login-modal.hbs

@@ -1,16 +1,40 @@
 <div class="modal fade" id="loginModal" tabindex="-1" role="dialog" aria-labelledby="loginModalLabel" aria-hidden="true">
-  <div class="modal-dialog">
+  <div class="modal-dialog modal-sm modal-sign-in">
     <div class="modal-content">
       <div class="modal-header">
-        <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
         <h4 class="modal-title" id="loginModalLabel">{{gettext "Sign in"}}</h4>
       </div>
       <div class="modal-body">
-        ...
+
+        <div class="form-group">
+          <div class="control-input">
+            {{input type="text" value=username class="form-control" placeholder=(gettext "Username or e-mail")}}
+          </div>
+        </div>
+
+        <div class="form-group">
+          <div class="control-input">
+            {{input type="password" value=password class="form-control" placeholder=(gettext "Password")}}
+          </div>
+        </div>
+
       </div>
       <div class="modal-footer">
-        <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
-        <button type="button" class="btn btn-primary">Save changes</button>
+        {{#if showActivation}}
+        <button class="btn btn-info btn-success">{{gettext "Activate account"}}</button>
+        {{else}}
+          {{#if isLoading}}
+          <button type="button" class="btn btn-block btn-primary" disabled="disabled">
+            <span class="fa fa-cog fa-spin"></span>
+            {{gettext "Singing in..."}}
+          </button>
+          {{else}}
+          <button type="button" class="btn btn-block btn-primary" {{action "signIn"}}>{{gettext "Sign in"}}</button>
+          {{/if}}
+        {{/if}}
+
+        <button class="btn btn-block btn-default">{{gettext "Forgot password?"}}</button>
+
       </div>
     </div>
   </div>

+ 7 - 0
misago/emberapp/app/utils/csrf.js

@@ -0,0 +1,7 @@
+import Ember from 'ember';
+import MisagoPreloadStore from 'misago/utils/preloadstore';
+
+export default function getCsrfToken() {
+  var regex = new RegExp(MisagoPreloadStore.get('csrfCookieName') + '\=([^;]*)');
+  return Ember.get(document.cookie.match(regex), "1");
+}

+ 63 - 0
misago/emberapp/app/utils/rpc.js

@@ -0,0 +1,63 @@
+import Ember from 'ember';
+import getCsrfToken from 'misago/utils/csrf';
+import {startsWith, endsWith} from 'misago/utils/strings';
+import ENV from '../config/environment';
+
+export function buildUrl(procedure, config) {
+  if (typeof config === 'undefined') {
+    config = ENV.APP;
+  }
+
+  var finalUrl = config.API_HOST;
+
+  if (!startsWith(procedure, '/' + config.API_NAMESPACE + '/')) {
+    finalUrl += '/' + config.API_NAMESPACE + '/';
+  }
+
+  finalUrl += procedure;
+
+  if (config.API_ADD_TRAILING_SLASHES && !endsWith(procedure, '/')) {
+    finalUrl += '/';
+  }
+
+  return finalUrl;
+}
+
+export function ajax(url, data) {
+  return new Ember.RSVP.Promise(function(resolve, reject){
+    function success(json) {
+      Ember.run(null, resolve, json);
+    }
+
+    function error(jqXHR) {
+      if (jqXHR.status === 200) {
+        if (typeof jqXHR.responseJSON === 'undefined') {
+          Ember.run(null, resolve, {});
+        } else {
+          Ember.run(null, resolve, jqXHR.responseJSON);
+        }
+      } else {
+        Ember.run(null, reject, jqXHR.responseJSON);
+      }
+    }
+
+    Ember.$.ajax(url, {
+      type: 'POST',
+      accepts: 'application/json',
+      contentType: 'application/json;charset=UTF-8',
+      data: JSON.stringify(data || {}),
+      dataType: 'json',
+
+      headers: {
+        'X-CSRFToken': getCsrfToken()
+      },
+
+      error: error,
+      success: success
+    });
+  }, 'RPC: ' + url);
+}
+
+export function rpc(procedure, data, config) {
+  return ajax(buildUrl(procedure, config), data);
+}

+ 7 - 0
misago/emberapp/app/utils/strings.js

@@ -0,0 +1,7 @@
+export function startsWith(string, beginning) {
+  return string.indexOf(beginning) === 0;
+}
+
+export function endsWith(string, tail) {
+  return string.indexOf(tail, string.length - tail.length) !== -1;
+}

+ 4 - 4
misago/emberapp/bower.json

@@ -3,14 +3,14 @@
   "dependencies": {
     "jquery": "^1.11.1",
     "ember": "1.10.0",
-    "ember-data": "1.0.0-beta.14",
+    "ember-data": "1.0.0-beta.14.1",
     "ember-resolver": "~0.1.11",
     "loader.js": "ember-cli/loader.js#1.0.1",
     "ember-cli-shims": "ember-cli/ember-cli-shims#0.0.3",
-    "ember-cli-test-loader": "ember-cli/ember-cli-test-loader#0.1.0",
+    "ember-cli-test-loader": "ember-cli/ember-cli-test-loader#0.1.1",
     "ember-load-initializers": "ember-cli/ember-load-initializers#0.0.2",
-    "ember-qunit": "0.1.8",
-    "ember-qunit-notifications": "0.0.5",
+    "ember-qunit": "0.2.8",
+    "ember-qunit-notifications": "0.0.7",
     "qunit": "~1.17.1"
   }
 }

+ 4 - 4
misago/emberapp/package.json

@@ -21,7 +21,7 @@
   "devDependencies": {
     "broccoli-asset-rev": "^2.0.0",
     "django-ember-gettext": "^0.9.9",
-    "ember-cli": "0.1.12",
+    "ember-cli": "0.1.15",
     "ember-cli-6to5": "^3.0.0",
     "ember-cli-app-version": "0.3.1",
     "ember-cli-content-security-policy": "0.3.0",
@@ -30,11 +30,11 @@
     "ember-cli-ic-ajax": "0.1.1",
     "ember-cli-inject-live-reload": "^1.3.0",
     "ember-cli-less": "1.1.0",
-    "ember-cli-qunit": "0.3.1",
+    "ember-cli-qunit": "0.3.7",
     "ember-cli-uglify": "1.0.1",
-    "ember-data": "1.0.0-beta.14",
+    "ember-data": "1.0.0-beta.14.1",
     "ember-django-adapter": "0.5.3",
-    "ember-export-application-global": "^1.0.0",
+    "ember-export-application-global": "^1.0.2",
     "express": "^4.8.5",
     "glob": "^4.0.5"
   }

+ 0 - 23
misago/emberapp/tests/.jshintrc

@@ -6,29 +6,8 @@
     "setTimeout",
     "$",
     "-Promise",
-    "QUnit",
     "define",
     "console",
-    "equal",
-    "notEqual",
-    "notStrictEqual",
-    "test",
-    "asyncTest",
-    "testBoth",
-    "testWithDefault",
-    "raises",
-    "throws",
-    "deepEqual",
-    "start",
-    "stop",
-    "ok",
-    "strictEqual",
-    "module",
-    "moduleFor",
-    "moduleForComponent",
-    "moduleForModel",
-    "process",
-    "expect",
     "visit",
     "exists",
     "fillIn",
@@ -39,8 +18,6 @@
     "findWithAssert",
     "wait",
     "DS",
-    "isolatedContainer",
-    "startApp",
     "andThen",
     "currentURL",
     "currentPath",

+ 16 - 15
misago/emberapp/tests/acceptance/error-handling-test.js

@@ -1,19 +1,20 @@
 import Ember from 'ember';
+import { module, test } from 'qunit';
 import startApp from '../helpers/start-app';
 
 var application;
 
 module('Acceptance: Application Error Handler', {
-  setup: function() {
+  beforeEach: function() {
     application = startApp();
   },
-  teardown: function() {
+  afterEach: function() {
     Ember.run(application, 'destroy');
     Ember.$.mockjax.clear();
   }
 });
 
-test('some unhandled error occured', function() {
+test('some unhandled error occured', function(assert) {
   Ember.$.mockjax({
     url: "/api/legal-pages/privacy-policy/",
     status: 500,
@@ -25,11 +26,11 @@ test('some unhandled error occured', function() {
   visit('/privacy-policy');
 
   andThen(function() {
-    equal(currentPath(), 'error');
+    assert.equal(currentPath(), 'error');
   });
 });
 
-test('app went away', function() {
+test('app went away', function(assert) {
   Ember.$.mockjax({
     url: "/api/legal-pages/privacy-policy/",
     status: 0,
@@ -41,11 +42,11 @@ test('app went away', function() {
   visit('/privacy-policy');
 
   andThen(function() {
-    equal(currentPath(), 'error-0');
+    assert.equal(currentPath(), 'error-0');
   });
 });
 
-test('not found', function() {
+test('not found', function(assert) {
   Ember.$.mockjax({
     url: "/api/legal-pages/privacy-policy/",
     status: 404,
@@ -57,11 +58,11 @@ test('not found', function() {
   visit('/privacy-policy');
 
   andThen(function() {
-    equal(currentPath(), 'error-404');
+    assert.equal(currentPath(), 'error-404');
   });
 });
 
-test('permission denied', function() {
+test('permission denied', function(assert) {
   Ember.$.mockjax({
     url: "/api/legal-pages/privacy-policy/",
     status: 403,
@@ -73,11 +74,11 @@ test('permission denied', function() {
   visit('/privacy-policy');
 
   andThen(function() {
-    equal(currentPath(), 'error-403');
+    assert.equal(currentPath(), 'error-403');
   });
 });
 
-test('permission denied with reason', function() {
+test('permission denied with reason', function(assert) {
   Ember.$.mockjax({
     url: "/api/legal-pages/privacy-policy/",
     status: 403,
@@ -89,16 +90,16 @@ test('permission denied with reason', function() {
   visit('/privacy-policy');
 
   andThen(function() {
-    equal(currentPath(), 'error-403');
+    assert.equal(currentPath(), 'error-403');
     var $e = find('.lead');
-    equal(Ember.$.trim($e.text()), 'Lorem ipsum dolor met.');
+    assert.equal(Ember.$.trim($e.text()), 'Lorem ipsum dolor met.');
   });
 });
 
-test('not found route', function() {
+test('not found route', function(assert) {
   visit('/this-url-really-doesnt-exist');
 
   andThen(function() {
-    equal(currentPath(), 'error-404');
+    assert.equal(currentPath(), 'error-404');
   });
 });

+ 8 - 7
misago/emberapp/tests/acceptance/index-test.js

@@ -1,32 +1,33 @@
 import Ember from 'ember';
+import { module, test } from 'qunit';
 import startApp from '../helpers/start-app';
 
 var application;
 
 module('Acceptance: Index', {
-  setup: function() {
+  beforeEach: function() {
     application = startApp();
   },
-  teardown: function() {
+  afterEach: function() {
     Ember.run(application, 'destroy');
   }
 });
 
-test('visiting /', function() {
+test('visiting /', function(assert) {
   visit('/');
 
   andThen(function() {
-    equal(currentPath(), 'index');
+    assert.equal(currentPath(), 'index');
   });
 });
 
-test('visiting / with custom title', function() {
+test('visiting / with custom title', function(assert) {
   var newTitle = "Misago Support Forums";
   window.MisagoData.misagoSettings['forum_index_title'] = newTitle;
   visit('/');
 
   andThen(function() {
-    equal(currentPath(), 'index');
-    equal(document.title, newTitle);
+    assert.equal(currentPath(), 'index');
+    assert.equal(document.title, newTitle);
   });
 });

+ 8 - 7
misago/emberapp/tests/acceptance/privacy-policy-test.js

@@ -1,19 +1,20 @@
 import Ember from 'ember';
+import { module, test } from 'qunit';
 import startApp from '../helpers/start-app';
 
 var application;
 
 module('Acceptance: PrivacyPolicy', {
-  setup: function() {
+  beforeEach: function() {
     application = startApp();
   },
-  teardown: function() {
+  afterEach: function() {
     Ember.run(application, 'destroy');
     Ember.$.mockjax.clear();
   }
 });
 
-test('visiting unset /privacy-policy', function() {
+test('visiting unset /privacy-policy', function(assert) {
   Ember.$.mockjax({
     url: "/api/legal-pages/privacy-policy/",
     status: 404,
@@ -23,11 +24,11 @@ test('visiting unset /privacy-policy', function() {
   visit('/privacy-policy');
 
   andThen(function() {
-    equal(currentPath(), 'error-404');
+    assert.equal(currentPath(), 'error-404');
   });
 });
 
-test('visiting set /privacy-policy', function() {
+test('visiting set /privacy-policy', function(assert) {
   Ember.$.mockjax({
     url: "/api/legal-pages/privacy-policy/",
     status: 200,
@@ -42,8 +43,8 @@ test('visiting set /privacy-policy', function() {
   visit('/privacy-policy');
 
   andThen(function() {
-    equal(currentPath(), 'privacy-policy');
+    assert.equal(currentPath(), 'privacy-policy');
     var $e = find('article');
-    equal(Ember.$.trim($e.html()), '<p>Top kek</p>');
+    assert.equal(Ember.$.trim($e.html()), '<p>Top kek</p>');
   });
 });

+ 8 - 7
misago/emberapp/tests/acceptance/terms-of-service-test.js

@@ -1,19 +1,20 @@
 import Ember from 'ember';
+import { module, test } from 'qunit';
 import startApp from '../helpers/start-app';
 
 var application;
 
 module('Acceptance: TermsOfService', {
-  setup: function() {
+  beforeEach: function() {
     application = startApp();
   },
-  teardown: function() {
+  afterEach: function() {
     Ember.run(application, 'destroy');
     Ember.$.mockjax.clear();
   }
 });
 
-test('visiting unset /terms-of-service', function() {
+test('visiting unset /terms-of-service', function(assert) {
   Ember.$.mockjax({
     url: "/api/legal-pages/terms-of-service/",
     status: 404,
@@ -23,11 +24,11 @@ test('visiting unset /terms-of-service', function() {
   visit('/terms-of-service');
 
   andThen(function() {
-    equal(currentPath(), 'error-404');
+    assert.equal(currentPath(), 'error-404');
   });
 });
 
-test('visiting set /terms-of-service', function() {
+test('visiting set /terms-of-service', function(assert) {
   Ember.$.mockjax({
     url: "/api/legal-pages/terms-of-service/",
     status: 200,
@@ -42,8 +43,8 @@ test('visiting set /terms-of-service', function() {
   visit('/terms-of-service');
 
   andThen(function() {
-    equal(currentPath(), 'terms-of-service');
+    assert.equal(currentPath(), 'terms-of-service');
     var $e = find('article');
-    equal(Ember.$.trim($e.html()), '<p>Top kek</p>');
+    assert.equal(Ember.$.trim($e.html()), '<p>Top kek</p>');
   });
 });

+ 31 - 31
misago/emberapp/tests/unit/controllers/flash-message-test.js

@@ -5,75 +5,75 @@ import {
 
 moduleFor('controller:flash-message', 'FlashMessageController');
 
-test('it exists', function() {
+test('it exists', function(assert) {
   var controller = this.subject();
-  ok(controller);
+  assert.ok(controller);
 });
 
-test('isInfo', function() {
+test('isInfo', function(assert) {
   var controller = this.subject();
 
   controller.set('type', 'info');
 
-  ok(controller.get('isInfo'));
-  ok(!controller.get('isSuccess'));
-  ok(!controller.get('isWarning'));
-  ok(!controller.get('isError'));
+  assert.ok(controller.get('isInfo'));
+  assert.ok(!controller.get('isSuccess'));
+  assert.ok(!controller.get('isWarning'));
+  assert.ok(!controller.get('isError'));
 });
 
-test('isSuccess', function() {
+test('isSuccess', function(assert) {
   var controller = this.subject();
 
   controller.set('type', 'success');
 
-  ok(!controller.get('isInfo'));
-  ok(controller.get('isSuccess'));
-  ok(!controller.get('isWarning'));
-  ok(!controller.get('isError'));
+  assert.ok(!controller.get('isInfo'));
+  assert.ok(controller.get('isSuccess'));
+  assert.ok(!controller.get('isWarning'));
+  assert.ok(!controller.get('isError'));
 });
 
-test('isWarning', function() {
+test('isWarning', function(assert) {
   var controller = this.subject();
 
   controller.set('type', 'warning');
 
-  ok(!controller.get('isInfo'));
-  ok(!controller.get('isSuccess'));
-  ok(controller.get('isWarning'));
-  ok(!controller.get('isError'));
+  assert.ok(!controller.get('isInfo'));
+  assert.ok(!controller.get('isSuccess'));
+  assert.ok(controller.get('isWarning'));
+  assert.ok(!controller.get('isError'));
 });
 
-test('isError', function() {
+test('isError', function(assert) {
   var controller = this.subject();
 
   controller.set('type', 'error');
 
-  ok(!controller.get('isInfo'));
-  ok(!controller.get('isSuccess'));
-  ok(!controller.get('isWarning'));
-  ok(controller.get('isError'));
+  assert.ok(!controller.get('isInfo'));
+  assert.ok(!controller.get('isSuccess'));
+  assert.ok(!controller.get('isWarning'));
+  assert.ok(controller.get('isError'));
 });
 
-test('setFlash', function() {
+test('setFlash', function(assert) {
   var controller = this.subject();
 
   var testMessage = "I'm test flash!";
 
   controller.send('setFlash', 'success', testMessage);
 
-  ok(controller.get('isVisible'));
-  ok(controller.get('isSuccess'));
-  equal(controller.get('message'), testMessage);
+  assert.ok(controller.get('isVisible'));
+  assert.ok(controller.get('isSuccess'));
+  assert.equal(controller.get('message'), testMessage);
 });
 
-test('showFlash', function() {
+test('showFlash', function(assert) {
   var controller = this.subject();
 
   var testMessage = "I'm test flash!";
 
-  controller.send('showFlash', 'success', testMessage);
+  controller.showFlash('success', testMessage);
 
-  ok(controller.get('isVisible'));
-  ok(controller.get('isSuccess'));
-  equal(controller.get('message'), testMessage);
+  assert.ok(controller.get('isVisible'));
+  assert.ok(controller.get('isSuccess'));
+  assert.equal(controller.get('message'), testMessage);
 });

+ 17 - 17
misago/emberapp/tests/unit/controllers/footer-test.js

@@ -5,52 +5,52 @@ import {
 
 moduleFor('controller:footer', 'FooterController');
 
-test('it exists', function() {
+test('it exists', function(assert) {
   var controller = this.subject();
-  ok(controller);
+  assert.ok(controller);
 });
 
-test('showTermsLink', function() {
+test('showTermsLink', function(assert) {
   var controller = this.subject();
 
   // ToS isn't defined and there isn't link to remote ToS page, don't show link
   controller.set('settings', {'terms_of_service': null, 'terms_of_service_link': ''});
-  ok(!controller.get('showTermsLink'));
+  assert.ok(!controller.get('showTermsLink'));
 
   // ToS is defined but there isn't link to remote ToS page, show link
   controller.set('settings', {'terms_of_service': true, 'terms_of_service_link': ''});
-  ok(controller.get('showTermsLink'));
+  assert.ok(controller.get('showTermsLink'));
 
   // ToS isn't defined but there is link to remote ToS page, show link
   controller.set('settings', {'terms_of_service': null, 'terms_of_service_link': 'http://somewhere.com'});
-  ok(controller.get('showTermsLink'));
+  assert.ok(controller.get('showTermsLink'));
 
   // ToS is defined and there is link to remote ToS page, show link
   controller.set('settings', {'terms_of_service': true, 'terms_of_service_link': 'http://somewhere.com'});
-  ok(controller.get('showTermsLink'));
+  assert.ok(controller.get('showTermsLink'));
 });
 
-test('showPrivacyLink', function() {
+test('showPrivacyLink', function(assert) {
   var controller = this.subject();
 
   // PrivPolicy isn't defined and there isn't link to remote PrivPolicy page, don't show link
   controller.set('settings', {'privacy_policy': null, 'privacy_policy_link': ''});
-  ok(!controller.get('showPrivacyLink'));
+  assert.ok(!controller.get('showPrivacyLink'));
 
   // PrivPolicy is defined but there isn't link to remote PrivPolicy page, show link
   controller.set('settings', {'privacy_policy': true, 'privacy_policy_link': ''});
-  ok(controller.get('showPrivacyLink'));
+  assert.ok(controller.get('showPrivacyLink'));
 
   // PrivPolicy isn't defined but there is link to remote PrivPolicy page, show link
   controller.set('settings', {'privacy_policy': null, 'privacy_policy_link': 'http://somewhere.com'});
-  ok(controller.get('showPrivacyLink'));
+  assert.ok(controller.get('showPrivacyLink'));
 
   // PrivPolicy is defined and there is link to remote PrivPolicy page, show link
   controller.set('settings', {'privacy_policy': true, 'privacy_policy_link': 'http://somewhere.com'});
-  ok(controller.get('showPrivacyLink'));
+  assert.ok(controller.get('showPrivacyLink'));
 });
 
-test('showNav', function() {
+test('showNav', function(assert) {
   var controller = this.subject();
 
   // no Privacy Policy or ToS, don't show footer nav
@@ -58,26 +58,26 @@ test('showNav', function() {
     'terms_of_service': null, 'terms_of_service_link': '',
     'privacy_policy': null, 'privacy_policy_link': ''
   });
-  ok(!controller.get('showNav'));
+  assert.ok(!controller.get('showNav'));
 
   // Privacy Policy but no ToS, don't show footer nav
   controller.set('settings', {
     'terms_of_service': null, 'terms_of_service_link': '',
     'privacy_policy': true, 'privacy_policy_link': ''
   });
-  ok(controller.get('showNav'));
+  assert.ok(controller.get('showNav'));
 
   // no Privacy Policy but ToS, don't show footer nav
   controller.set('settings', {
     'terms_of_service': null, 'terms_of_service_link': 'http://somewhere.com',
     'privacy_policy': null, 'privacy_policy_link': ''
   });
-  ok(controller.get('showNav'));
+  assert.ok(controller.get('showNav'));
 
   // Privacy Policy and ToS, don't show footer nav
   controller.set('settings', {
     'terms_of_service': null, 'terms_of_service_link': 'http://somewhere.com',
     'privacy_policy': null, 'privacy_policy_link': 'http://somewhere.com'
   });
-  ok(controller.get('showNav'));
+  assert.ok(controller.get('showNav'));
 });

+ 0 - 15
misago/emberapp/tests/unit/controllers/guest-nav-test.js

@@ -1,15 +0,0 @@
-import {
-  moduleFor,
-  test
-} from 'ember-qunit';
-
-moduleFor('controller:guest-nav', 'GuestNavController', {
-  // Specify the other units that are required for this test.
-  // needs: ['controller:foo']
-});
-
-// Replace this with your real tests.
-test('it exists', function() {
-  var controller = this.subject();
-  ok(controller);
-});

+ 3 - 7
misago/emberapp/tests/unit/controllers/site-nav-test.js

@@ -3,13 +3,9 @@ import {
   test
 } from 'ember-qunit';
 
-moduleFor('controller:site-nav', 'SiteNavController', {
-  // Specify the other units that are required for this test.
-  // needs: ['controller:foo']
-});
+moduleFor('controller:site-nav', 'SiteNavController');
 
-// Replace this with your real tests.
-test('it exists', function() {
+test('it exists', function(assert) {
   var controller = this.subject();
-  ok(controller);
+  assert.ok(controller);
 });

+ 3 - 7
misago/emberapp/tests/unit/controllers/user-nav-test.js

@@ -3,13 +3,9 @@ import {
   test
 } from 'ember-qunit';
 
-moduleFor('controller:user-nav', 'UserNavController', {
-  // Specify the other units that are required for this test.
-  // needs: ['controller:foo']
-});
+moduleFor('controller:user-nav', 'UserNavController');
 
-// Replace this with your real tests.
-test('it exists', function() {
+test('it exists', function(assert) {
   var controller = this.subject();
-  ok(controller);
+  assert.ok(controller);
 });

+ 6 - 5
misago/emberapp/tests/unit/initializers/misago-settings-test.js

@@ -1,11 +1,12 @@
 import Ember from 'ember';
 import { initialize } from 'misago/initializers/misago-settings';
 import MisagoPreloadStore from 'misago/utils/preloadstore';
+import { module, test } from 'qunit';
 
 var container, application;
 
 module('SettingsInitializer', {
-  setup: function() {
+  beforeEach: function() {
     Ember.run(function() {
       application = Ember.Application.create();
       container = application.__container__;
@@ -14,11 +15,11 @@ module('SettingsInitializer', {
   }
 });
 
-test('registers preloaded configuration in Ember', function() {
+test('registers preloaded configuration in Ember', function(assert) {
   initialize(container, application);
 
-  equal(container.lookup('misago:static-url'), MisagoPreloadStore.get('staticUrl'));
-  equal(container.lookup('misago:media-url'), MisagoPreloadStore.get('mediaUrl'));
-  equal(container.lookup('misago:settings'), MisagoPreloadStore.get('misagoSettings'));
+  assert.equal(container.lookup('misago:static-url'), MisagoPreloadStore.get('staticUrl'));
+  assert.equal(container.lookup('misago:media-url'), MisagoPreloadStore.get('mediaUrl'));
+  assert.equal(container.lookup('misago:settings'), MisagoPreloadStore.get('misagoSettings'));
 });
 

+ 4 - 3
misago/emberapp/tests/unit/initializers/trailing-slash-test.js

@@ -1,10 +1,11 @@
 import Ember from 'ember';
 import { initialize } from 'misago/initializers/trailing-slash';
+import { module, test } from 'qunit';
 
 var container, application;
 
 module('TrailingSlashInitializer', {
-  setup: function() {
+  beforeEach: function() {
     Ember.run(function() {
       application = Ember.Application.create();
       container = application.__container__;
@@ -13,9 +14,9 @@ module('TrailingSlashInitializer', {
   }
 });
 
-test('it exists', function() {
+test('it exists', function(assert) {
   initialize(container, application);
 
-  ok(container.has('location:trailing-history'));
+  assert.ok(container.has('location:trailing-history'));
 });
 

+ 3 - 6
misago/emberapp/tests/unit/models/legal-page-test.js

@@ -3,12 +3,9 @@ import {
   test
 } from 'ember-qunit';
 
-moduleForModel('legal-page', 'LegalPage', {
-  // Specify the other units that are required for this test.
-  needs: []
-});
+moduleForModel('legal-page', 'LegalPage');
 
-test('it exists', function() {
+test('it exists', function(assert) {
   var model = this.subject();
-  ok(!!model);
+  assert.ok(!!model);
 });

+ 11 - 11
misago/emberapp/tests/unit/routes/application-test.js

@@ -6,46 +6,46 @@ import {
 var document_title = document.title;
 
 moduleFor('route:application', 'ApplicationRoute', {
-  teardown: function() {
+  afterEach: function() {
     document.title = document_title;
   }
 });
 
-test('it exists', function() {
+test('it exists', function(assert) {
   var route = this.subject();
-  ok(route);
+  assert.ok(route);
 });
 
-test('error', function() {
+test('error', function(assert) {
   var route = this.subject();
   route.set('settings', {'forum_name': 'Test Forum'});
 
   // generic error
   route.send('error', {status: 123});
-  equal(document.title, 'Error | Test Forum');
+  assert.equal(document.title, 'Error | Test Forum');
 });
 
-test('setTitle', function() {
+test('setTitle', function(assert) {
   var route = this.subject();
   route.set('settings', {'forum_name': 'Test Forum'});
 
   // string argument
   route.send('setTitle', 'Welcome!');
-  equal(document.title, 'Welcome! | Test Forum');
+  assert.equal(document.title, 'Welcome! | Test Forum');
 
   // object argument
   route.send('setTitle', {title: 'Thread'});
-  equal(document.title, 'Thread | Test Forum');
+  assert.equal(document.title, 'Thread | Test Forum');
 
   // object argument with parent
   route.send('setTitle', {title: 'Test Thread', parent: 'Support'});
-  equal(document.title, 'Test Thread | Support | Test Forum');
+  assert.equal(document.title, 'Test Thread | Support | Test Forum');
 
   // object argument with page
   route.send('setTitle', {title: 'Test Thread', page: 12});
-  equal(document.title, 'Test Thread (page 12) | Test Forum');
+  assert.equal(document.title, 'Test Thread (page 12) | Test Forum');
 
   // object argument with page and parent
   route.send('setTitle', {title: 'Test Thread', page: 12, parent: 'Support'});
-  equal(document.title, 'Test Thread (page 12) | Support | Test Forum');
+  assert.equal(document.title, 'Test Thread (page 12) | Support | Test Forum');
 });

+ 6 - 6
misago/emberapp/tests/unit/routes/index-test.js

@@ -6,17 +6,17 @@ import {
 var document_title = document.title;
 
 moduleFor('route:index', 'IndexRoute', {
-  teardown: function() {
+  afterEach: function() {
     document.title = document_title;
   }
 });
 
-test('it exists', function() {
+test('it exists', function(assert) {
   var route = this.subject();
-  ok(route);
+  assert.ok(route);
 });
 
-test('sets title correctly', function() {
+test('sets title correctly', function(assert) {
   var route = this.subject();
 
   route.set('settings', {
@@ -25,7 +25,7 @@ test('sets title correctly', function() {
   });
 
   route.send('didTransition');
-  equal(document.title, 'Forum Name');
+  assert.equal(document.title, 'Forum Name');
 
   route.set('settings', {
     'forum_index_title': 'Welcome to Forum!',
@@ -33,5 +33,5 @@ test('sets title correctly', function() {
   });
 
   route.send('didTransition');
-  equal(document.title, 'Welcome to Forum!');
+  assert.equal(document.title, 'Welcome to Forum!');
 });

+ 3 - 6
misago/emberapp/tests/unit/routes/not-found-test.js

@@ -3,12 +3,9 @@ import {
   test
 } from 'ember-qunit';
 
-moduleFor('route:not-found', 'NotFoundRoute', {
-  // Specify the other units that are required for this test.
-  // needs: ['controller:foo']
-});
+moduleFor('route:not-found', 'NotFoundRoute');
 
-test('it exists', function() {
+test('it exists', function(assert) {
   var route = this.subject();
-  ok(route);
+  assert.ok(route);
 });

+ 11 - 11
misago/emberapp/tests/unit/routes/privacy-policy-test.js

@@ -6,37 +6,37 @@ import {
 var document_title = document.title;
 
 moduleFor('route:privacy-policy', 'PrivacyPolicyRoute', {
-  teardown: function() {
+  afterEach: function() {
     document.title = document_title;
   }
 });
 
-test('it exists', function() {
+test('it exists', function(assert) {
   var route = this.subject();
-  ok(route);
+  assert.ok(route);
 });
 
-test('setting', function() {
+test('setting', function(assert) {
   var route = this.subject();
-  equal(route.get('setting'), 'privacy_policy');
+  assert.equal(route.get('setting'), 'privacy_policy');
 });
 
-test('title', function() {
+test('title', function(assert) {
   var route = this.subject();
 
-  equal(route.get('title'), route.get('defaultTitle'));
+  assert.equal(route.get('title'), route.get('defaultTitle'));
 
   var testTitle = "Lorem Ipsum Dolor Met";
   route.set("settings", {'privacy_policy_title': testTitle});
-  equal(route.get('title'), testTitle);
+  assert.equal(route.get('title'), testTitle);
 });
 
-test('link', function() {
+test('link', function(assert) {
   var route = this.subject();
 
-  ok(!route.get('link'));
+  assert.ok(!route.get('link'));
 
   var testLink = "http://somewhere.com";
   route.set("settings", {'privacy_policy_link': testLink});
-  equal(route.get('link'), testLink);
+  assert.equal(route.get('link'), testLink);
 });

+ 11 - 11
misago/emberapp/tests/unit/routes/terms-of-service-test.js

@@ -6,37 +6,37 @@ import {
 var document_title = document.title;
 
 moduleFor('route:terms-of-service', 'TermsOfServiceRoute', {
-  teardown: function() {
+  afterEach: function() {
     document.title = document_title;
   }
 });
 
-test('it exists', function() {
+test('it exists', function(assert) {
   var route = this.subject();
-  ok(route);
+  assert.ok(route);
 });
 
-test('setting', function() {
+test('setting', function(assert) {
   var route = this.subject();
-  equal(route.get('setting'), 'terms_of_service');
+  assert.equal(route.get('setting'), 'terms_of_service');
 });
 
-test('title', function() {
+test('title', function(assert) {
   var route = this.subject();
 
-  equal(route.get('title'), route.get('defaultTitle'));
+  assert.equal(route.get('title'), route.get('defaultTitle'));
 
   var testTitle = "Lorem Ipsum Dolor Met";
   route.set("settings", {'terms_of_service_title': testTitle});
-  equal(route.get('title'), testTitle);
+  assert.equal(route.get('title'), testTitle);
 });
 
-test('link', function() {
+test('link', function(assert) {
   var route = this.subject();
 
-  ok(!route.get('link'));
+  assert.ok(!route.get('link'));
 
   var testLink = "http://somewhere.com";
   route.set("settings", {'terms_of_service_link': testLink});
-  equal(route.get('link'), testLink);
+  assert.equal(route.get('link'), testLink);
 });

+ 26 - 0
misago/emberapp/tests/unit/utils/csrf-test.js

@@ -0,0 +1,26 @@
+import getCsrfToken from '../../../utils/csrf';
+import MisagoPreloadStore from '../../../utils/preloadstore';
+import { module, test } from 'qunit';
+
+var cookieName = MisagoPreloadStore.get('csrfCookieName');
+
+module('csrf', {
+  afterEach: function() {
+    MisagoPreloadStore.set('csrfCookieName', cookieName);
+  }
+});
+
+test('getCsrfToken function returns csrf token', function(assert) {
+  var cookieName = 'validcsrfcookie';
+  var token = 'v4l1dc5rft0k3n';
+
+  MisagoPreloadStore.set('csrfCookieName', cookieName);
+
+  document.cookie = cookieName + '=' + token + ';';
+  assert.equal(getCsrfToken(), token);
+});
+
+test('getCsrfToken function returns undefined for non-existing cookie', function(assert) {
+  MisagoPreloadStore.set('csrfCookieName', 'n0n3x15t1ng');
+  assert.equal(getCsrfToken(), undefined);
+});

+ 31 - 30
misago/emberapp/tests/unit/utils/preloadstore-test.js

@@ -1,66 +1,67 @@
-import PreloadStore from 'misago/utils/preloadstore';
+import PreloadStore from '../../../utils/preloadstore';
+import { module, test } from 'qunit';
 
 module('PreloadStore');
 
-test('has(key) method returns true for existing keys', function() {
-  ok(PreloadStore.has('staticUrl'));
-  ok(PreloadStore.has('mediaUrl'));
-  ok(PreloadStore.has('misagoSettings'));
+test('has(key) method returns true for existing keys', function(assert) {
+  assert.ok(PreloadStore.has('staticUrl'));
+  assert.ok(PreloadStore.has('mediaUrl'));
+  assert.ok(PreloadStore.has('misagoSettings'));
 });
 
-test('has(key) method returns false for undefined keys', function() {
-  equal(PreloadStore.has('notExisting'), false);
+test('has(key) method returns false for undefined keys', function(assert) {
+  assert.equal(PreloadStore.has('notExisting'), false);
 });
 
-test('get(key) method returns value for defined key', function() {
-  equal(PreloadStore.get('misagoSettings'), window.MisagoData.misagoSettings);
-  equal(PreloadStore.get('mediaUrl'), window.MisagoData.mediaUrl);
+test('get(key) method returns value for defined key', function(assert) {
+  assert.equal(PreloadStore.get('misagoSettings'), window.MisagoData.misagoSettings);
+  assert.equal(PreloadStore.get('mediaUrl'), window.MisagoData.mediaUrl);
 });
 
-test('get(key) method returns undefined for undefined key', function() {
-  equal(PreloadStore.get('undefinedKey'), undefined);
+test('get(key) method returns undefined for undefined key', function(assert) {
+  assert.equal(PreloadStore.get('undefinedKey'), undefined);
 });
 
-test('get(key, default) method returns default value for undefined key', function() {
+test('get(key, default) method returns default value for undefined key', function(assert) {
   var key = 'undefinedKey';
   var defaultValue = 'Default value';
 
-  equal(PreloadStore.get(key, defaultValue), defaultValue);
-  ok(!PreloadStore.has(key));
+  assert.equal(PreloadStore.get(key, defaultValue), defaultValue);
+  assert.ok(!PreloadStore.has(key));
 });
 
-test('get(key, default) method returns value for defined key', function() {
+test('get(key, default) method returns value for defined key', function(assert) {
   var key = 'mediaUrl';
 
-  equal(PreloadStore.get(key, 'Default Value'), window.MisagoData.mediaUrl);
-  ok(PreloadStore.has(key));
+  assert.equal(PreloadStore.get(key, 'Default Value'), window.MisagoData.mediaUrl);
+  assert.ok(PreloadStore.has(key));
 });
 
-test('set(key, value) method sets new value', function() {
+test('set(key, value) method sets new value', function(assert) {
   var key = 'testKey';
-
   var value = 'Lo Bob!';
-  equal(PreloadStore.set(key, value), value);
-  equal(PreloadStore.get(key), value);
-  ok(PreloadStore.has(key));
+
+  assert.equal(PreloadStore.set(key, value), value);
+  assert.equal(PreloadStore.get(key), value);
+  assert.ok(PreloadStore.has(key));
 });
 
-test('pop(key, default) method returns default undefined for key', function() {
+test('pop(key, default) method returns default undefined for key', function(assert) {
   var key = 'undefinedKey';
   var defaultValue = 'Default value';
 
-  equal(PreloadStore.get(key, defaultValue), defaultValue);
-  ok(!PreloadStore.has(key));
+  assert.equal(PreloadStore.get(key, defaultValue), defaultValue);
+  assert.ok(!PreloadStore.has(key));
 });
 
-test('pop(key, default) method returns and deletes value for key', function() {
+test('pop(key, default) method returns and deletes value for key', function(assert) {
   var key = 'undefinedKey';
   var realValue = 'valid value!';
   var defaultValue = 'Default value';
 
   PreloadStore.set(key, realValue);
 
-  equal(PreloadStore.pop(key, defaultValue), realValue);
-  equal(PreloadStore.pop(key, defaultValue), defaultValue);
-  ok(!PreloadStore.has(key));
+  assert.equal(PreloadStore.pop(key, defaultValue), realValue);
+  assert.equal(PreloadStore.pop(key, defaultValue), defaultValue);
+  assert.ok(!PreloadStore.has(key));
 });

+ 75 - 0
misago/emberapp/tests/unit/utils/rpc-test.js

@@ -0,0 +1,75 @@
+import Ember from 'ember';
+import {buildUrl, ajax, rpc} from '../../../utils/rpc';
+import { module, test } from 'qunit';
+
+module('RPC', {
+  afterEach: function() {
+    Ember.$.mockjax.clear();
+  }
+});
+
+test('buildUrl builds valid urls', function(assert) {
+  assert.equal(buildUrl('some/procedure', {
+    API_HOST: '',
+    API_NAMESPACE: 'api/v2',
+    API_ADD_TRAILING_SLASHES: true
+  }), '/api/v2/some/procedure/');
+
+  assert.equal(buildUrl('some/procedure', {
+    API_HOST: '',
+    API_NAMESPACE: 'api/v2',
+    API_ADD_TRAILING_SLASHES: false
+  }), '/api/v2/some/procedure');
+
+  assert.equal(buildUrl('some/procedure', {
+    API_HOST: 'https://api.testsite.com',
+    API_NAMESPACE: 'api/v2',
+    API_ADD_TRAILING_SLASHES: false
+  }), 'https://api.testsite.com/api/v2/some/procedure');
+});
+
+var conf = {
+  API_HOST: '',
+  API_NAMESPACE: 'api',
+  API_ADD_TRAILING_SLASHES: true
+};
+
+test('successfull rpc call passes', function(assert) {
+  var done = assert.async();
+
+  Ember.$.mockjax({
+    url: "/api/some-rpc/",
+    status: 200,
+    responseText: {
+      'detail': 'it works'
+    }
+  });
+
+  rpc('some-rpc', {}, conf).then(function(json) {
+    assert.equal(json.detail, 'it works');
+  }, function() {
+    assert.fail("rpc call should pass");
+  }).finally(function() {
+    done();
+  });
+});
+
+test('invalid rpc call fails', function(assert) {
+  var done = assert.async();
+
+  Ember.$.mockjax({
+    url: "/api/some-rpc/",
+    status: 400,
+    responseText: {
+      'detail': 'nope'
+    }
+  });
+
+  rpc('some-rpc', {}, conf).then(function() {
+    assert.fail("rpc call should fail");
+  }, function(json) {
+    assert.equal(json.detail, 'nope');
+  }).finally(function() {
+    done();
+  });
+});

+ 16 - 0
misago/emberapp/tests/unit/utils/strings-test.js

@@ -0,0 +1,16 @@
+import { startsWith, endsWith } from '../../../utils/strings';
+import { module, test } from 'qunit';
+
+module('strings');
+
+test('startsWith works', function(assert) {
+  assert.ok(startsWith("Boberson", "Bob"));
+  assert.ok(!startsWith("Boberson", "bob"));
+  assert.ok(!startsWith("Bob", "Boberson"));
+});
+
+test('endsWith works', function(assert) {
+  assert.ok(endsWith("Boberson", "son"));
+  assert.ok(!endsWith("Boberson", "Son"));
+  assert.ok(!endsWith("Bob", "Boberson"));
+});

+ 4 - 0
misago/emberapp/vendor/testutils/misago-preload-data.js

@@ -18,9 +18,13 @@ window.MisagoData = {
     "privacy_policy": true
   },
 
+  "csrfCookieName": 'csrftokencookie',
+
   "mediaUrl": "/media/",
   "staticUrl": "/static/",
 
+  "authApiUrl": "/api/auth/",
+
   "loginUrl": "/login/",
   "loginRedirectUrl": "/",