diff --git a/modules/articles/client/articles.client.module.js b/modules/articles/client/articles.client.module.js
index 4f2b96b347..85ff0c3fb1 100644
--- a/modules/articles/client/articles.client.module.js
+++ b/modules/articles/client/articles.client.module.js
@@ -1,7 +1,7 @@
(function (app) {
'use strict';
- app.registerModule('articles');
+ app.registerModule('articles', ['core']);// The core module is required for special route handling; see /core/client/config/core.client.routes
app.registerModule('articles.services');
app.registerModule('articles.routes', ['ui.router', 'articles.services']);
})(ApplicationConfiguration);
diff --git a/modules/articles/tests/client/articles.client.routes.tests.js b/modules/articles/tests/client/articles.client.routes.tests.js
index 32608c2af2..c850a2541e 100644
--- a/modules/articles/tests/client/articles.client.routes.tests.js
+++ b/modules/articles/tests/client/articles.client.routes.tests.js
@@ -38,6 +38,25 @@
});
});
+ describe('List Route', function () {
+ var liststate;
+ beforeEach(inject(function ($state) {
+ liststate = $state.get('articles.list');
+ }));
+
+ it('Should have the correct URL', function () {
+ expect(liststate.url).toEqual('');
+ });
+
+ it('Should not be abstract', function () {
+ expect(liststate.abstract).toBe(undefined);
+ });
+
+ it('Should have template', function () {
+ expect(liststate.templateUrl).toBe('modules/articles/client/views/list-articles.client.view.html');
+ });
+ });
+
describe('View Route', function () {
var viewstate,
ArticlesController,
@@ -190,6 +209,21 @@
});
});
+ describe('Handle Trailing Slash', function () {
+ beforeEach(inject(function ($state, $rootScope) {
+ $state.go('articles.list');
+ $rootScope.$digest();
+ }));
+
+ it('Should remove trailing slash', inject(function ($state, $location, $rootScope) {
+ $location.path('articles/');
+ $rootScope.$digest();
+
+ expect($location.path()).toBe('/articles');
+ expect($state.current.templateUrl).toBe('modules/articles/client/views/list-articles.client.view.html');
+ }));
+ });
+
});
});
})();
diff --git a/modules/chat/client/chat.client.module.js b/modules/chat/client/chat.client.module.js
index 5e7669f891..e9a138d8fe 100644
--- a/modules/chat/client/chat.client.module.js
+++ b/modules/chat/client/chat.client.module.js
@@ -1,6 +1,6 @@
(function (app) {
'use strict';
- app.registerModule('chat');
+ app.registerModule('chat', ['core']);
app.registerModule('chat.routes', ['ui.router']);
})(ApplicationConfiguration);
diff --git a/modules/chat/tests/client/chat.client.routes.tests.js b/modules/chat/tests/client/chat.client.routes.tests.js
new file mode 100644
index 0000000000..4e72f4f960
--- /dev/null
+++ b/modules/chat/tests/client/chat.client.routes.tests.js
@@ -0,0 +1,63 @@
+(function () {
+ 'use strict';
+
+ describe('Chat Route Tests', function () {
+ // Initialize global variables
+ var $scope,
+ Authentication;
+
+ //We can start by loading the main application module
+ beforeEach(module(ApplicationConfiguration.applicationModuleName));
+
+ // The injector ignores leading and trailing underscores here (i.e. _$httpBackend_).
+ // This allows us to inject a service but then attach it to a variable
+ // with the same name as the service.
+ beforeEach(inject(function ($rootScope, _Authentication_) {
+ // Set a new global scope
+ $scope = $rootScope.$new();
+ Authentication = _Authentication_;
+ }));
+
+ describe('Route Config', function () {
+ describe('Main Route', function () {
+ var mainstate;
+ beforeEach(inject(function ($state) {
+ mainstate = $state.get('chat');
+ }));
+
+ it('Should have the correct URL', function () {
+ expect(mainstate.url).toEqual('/chat');
+ });
+
+ it('Should not be abstract', function () {
+ expect(mainstate.abstract).toBe(undefined);
+ });
+
+ it('Should have templateUrl', function () {
+ expect(mainstate.templateUrl).toBe('modules/chat/client/views/chat.client.view.html');
+ });
+ });
+
+ describe('Handle Trailing Slash', function () {
+ beforeEach(inject(function ($state, $rootScope, _Authentication_) {
+ Authentication.user = {
+ name: 'user',
+ roles: ['user']
+ };
+
+ $state.go('chat');
+ $rootScope.$digest();
+ }));
+
+ it('Should remove trailing slash', inject(function ($state, $location, $rootScope) {
+ $location.path('chat/');
+ $rootScope.$digest();
+
+ expect($location.path()).toBe('/chat');
+ expect($state.current.templateUrl).toBe('modules/chat/client/views/chat.client.view.html');
+ }));
+ });
+
+ });
+ });
+})();
diff --git a/modules/core/client/config/core.client.routes.js b/modules/core/client/config/core.client.routes.js
index e67e3335d8..49714cb628 100644
--- a/modules/core/client/config/core.client.routes.js
+++ b/modules/core/client/config/core.client.routes.js
@@ -4,6 +4,17 @@
angular.module('core').config(['$stateProvider', '$urlRouterProvider',
function ($stateProvider, $urlRouterProvider) {
+ $urlRouterProvider.rule(function ($injector, $location) {
+ var path = $location.path();
+ var hasTrailingSlash = path.length > 1 && path[path.length - 1] === '/';
+
+ if (hasTrailingSlash) {
+ //if last character is a slash, return the same url without the slash
+ var newPath = path.substr(0, path.length - 1);
+ $location.replace().path(newPath);
+ }
+ });
+
// Redirect to 404 when route not found
$urlRouterProvider.otherwise(function ($injector, $location) {
$injector.get('$state').transitionTo('not-found', null, {
diff --git a/modules/users/tests/client/users-admin.client.routes.tests.js b/modules/users/tests/client/users-admin.client.routes.tests.js
new file mode 100644
index 0000000000..0a3f5ce8c2
--- /dev/null
+++ b/modules/users/tests/client/users-admin.client.routes.tests.js
@@ -0,0 +1,101 @@
+(function () {
+ 'use strict';
+
+ describe('Users Admin Route Tests', function () {
+ // Initialize global variables
+ var $scope,
+ Authentication;
+
+ //We can start by loading the main application module
+ beforeEach(module(ApplicationConfiguration.applicationModuleName));
+
+ // The injector ignores leading and trailing underscores here (i.e. _$httpBackend_).
+ // This allows us to inject a service but then attach it to a variable
+ // with the same name as the service.
+ beforeEach(inject(function ($rootScope, _Authentication_) {
+ // Set a new global scope
+ $scope = $rootScope.$new();
+ Authentication = _Authentication_;
+ }));
+
+ describe('Route Config', function () {
+ describe('Main Route', function () {
+ var mainstate;
+ beforeEach(inject(function ($state) {
+ mainstate = $state.get('admin.users');
+ }));
+
+ it('Should have the correct URL', function () {
+ expect(mainstate.url).toEqual('/users');
+ });
+
+ it('Should not be abstract', function () {
+ expect(mainstate.abstract).toBe(undefined);
+ });
+
+ it('Should have templateUrl', function () {
+ expect(mainstate.templateUrl).toBe('modules/users/client/views/admin/list-users.client.view.html');
+ });
+ });
+
+ describe('View Route', function () {
+ var viewstate;
+ beforeEach(inject(function ($state) {
+ viewstate = $state.get('admin.user');
+ }));
+
+ it('Should have the correct URL', function () {
+ expect(viewstate.url).toEqual('/users/:userId');
+ });
+
+ it('Should not be abstract', function () {
+ expect(viewstate.abstract).toBe(undefined);
+ });
+
+ it('Should have templateUrl', function () {
+ expect(viewstate.templateUrl).toBe('modules/users/client/views/admin/view-user.client.view.html');
+ });
+ });
+
+ describe('Edit Route', function () {
+ var editstate;
+ beforeEach(inject(function ($state) {
+ editstate = $state.get('admin.user-edit');
+ }));
+
+ it('Should have the correct URL', function () {
+ expect(editstate.url).toEqual('/users/:userId/edit');
+ });
+
+ it('Should not be abstract', function () {
+ expect(editstate.abstract).toBe(undefined);
+ });
+
+ it('Should have templateUrl', function () {
+ expect(editstate.templateUrl).toBe('modules/users/client/views/admin/edit-user.client.view.html');
+ });
+ });
+
+ describe('Handle Trailing Slash', function () {
+ beforeEach(inject(function ($state, $rootScope, _Authentication_) {
+ Authentication.user = {
+ name: 'user',
+ roles: ['admin']
+ };
+
+ $state.go('admin.users');
+ $rootScope.$digest();
+ }));
+
+ it('Should remove trailing slash', inject(function ($state, $location, $rootScope) {
+ $location.path('admin/users/');
+ $rootScope.$digest();
+
+ expect($location.path()).toBe('/admin/users');
+ expect($state.current.templateUrl).toBe('modules/users/client/views/admin/list-users.client.view.html');
+ }));
+ });
+
+ });
+ });
+})();
diff --git a/modules/users/tests/client/users.client.routes.tests.js b/modules/users/tests/client/users.client.routes.tests.js
new file mode 100644
index 0000000000..0861de02e3
--- /dev/null
+++ b/modules/users/tests/client/users.client.routes.tests.js
@@ -0,0 +1,319 @@
+(function () {
+ 'use strict';
+
+ describe('Users Route Tests', function () {
+ // Initialize global variables
+ var $scope,
+ Authentication;
+
+ //We can start by loading the main application module
+ beforeEach(module(ApplicationConfiguration.applicationModuleName));
+
+ // The injector ignores leading and trailing underscores here (i.e. _$httpBackend_).
+ // This allows us to inject a service but then attach it to a variable
+ // with the same name as the service.
+ beforeEach(inject(function ($rootScope, _Authentication_) {
+ // Set a new global scope
+ $scope = $rootScope.$new();
+ Authentication = _Authentication_;
+ }));
+
+ describe('Settings Route Config', function () {
+ describe('Main Route', function () {
+ var mainstate;
+ beforeEach(inject(function ($state) {
+ mainstate = $state.get('settings');
+ }));
+
+ it('Should have the correct URL', function () {
+ expect(mainstate.url).toEqual('/settings');
+ });
+
+ it('Should be abstract', function () {
+ expect(mainstate.abstract).toBe(true);
+ });
+
+ it('Should have templateUrl', function () {
+ expect(mainstate.templateUrl).toBe('modules/users/client/views/settings/settings.client.view.html');
+ });
+ });
+
+ describe('Profile Route', function () {
+ var profilestate;
+ beforeEach(inject(function ($state) {
+ profilestate = $state.get('settings.profile');
+ }));
+
+ it('Should have the correct URL', function () {
+ expect(profilestate.url).toEqual('/profile');
+ });
+
+ it('Should not be abstract', function () {
+ expect(profilestate.abstract).toBe(undefined);
+ });
+
+ it('Should have templateUrl', function () {
+ expect(profilestate.templateUrl).toBe('modules/users/client/views/settings/edit-profile.client.view.html');
+ });
+ });
+
+ describe('Password Route', function () {
+ var passwordstate;
+ beforeEach(inject(function ($state) {
+ passwordstate = $state.get('settings.password');
+ }));
+
+ it('Should have the correct URL', function () {
+ expect(passwordstate.url).toEqual('/password');
+ });
+
+ it('Should not be abstract', function () {
+ expect(passwordstate.abstract).toBe(undefined);
+ });
+
+ it('Should have templateUrl', function () {
+ expect(passwordstate.templateUrl).toBe('modules/users/client/views/settings/change-password.client.view.html');
+ });
+ });
+
+ describe('Accounts Route', function () {
+ var accountsstate;
+ beforeEach(inject(function ($state) {
+ accountsstate = $state.get('settings.accounts');
+ }));
+
+ it('Should have the correct URL', function () {
+ expect(accountsstate.url).toEqual('/accounts');
+ });
+
+ it('Should not be abstract', function () {
+ expect(accountsstate.abstract).toBe(undefined);
+ });
+
+ it('Should have templateUrl', function () {
+ expect(accountsstate.templateUrl).toBe('modules/users/client/views/settings/manage-social-accounts.client.view.html');
+ });
+ });
+
+ describe('Picture Route', function () {
+ var picturestate;
+ beforeEach(inject(function ($state) {
+ picturestate = $state.get('settings.picture');
+ }));
+
+ it('Should have the correct URL', function () {
+ expect(picturestate.url).toEqual('/picture');
+ });
+
+ it('Should not be abstract', function () {
+ expect(picturestate.abstract).toBe(undefined);
+ });
+
+ it('Should have templateUrl', function () {
+ expect(picturestate.templateUrl).toBe('modules/users/client/views/settings/change-profile-picture.client.view.html');
+ });
+ });
+
+ describe('Handle Trailing Slash', function () {
+ beforeEach(inject(function ($state, $rootScope, _Authentication_) {
+ Authentication.user = {
+ name: 'user',
+ roles: ['user']
+ };
+
+ $state.go('settings.profile');
+ $rootScope.$digest();
+ }));
+
+ it('Should remove trailing slash', inject(function ($state, $location, $rootScope) {
+ $location.path('settings/profile/');
+ $rootScope.$digest();
+
+ expect($location.path()).toBe('/settings/profile');
+ expect($state.current.templateUrl).toBe('modules/users/client/views/settings/edit-profile.client.view.html');
+ }));
+ });
+
+ });
+
+ describe('Authentication Route Config', function () {
+ describe('Main Route', function () {
+ var mainstate;
+ beforeEach(inject(function ($state) {
+ mainstate = $state.get('authentication');
+ }));
+
+ it('Should have the correct URL', function () {
+ expect(mainstate.url).toEqual('/authentication');
+ });
+
+ it('Should be abstract', function () {
+ expect(mainstate.abstract).toBe(true);
+ });
+
+ it('Should have templateUrl', function () {
+ expect(mainstate.templateUrl).toBe('modules/users/client/views/authentication/authentication.client.view.html');
+ });
+ });
+
+ describe('Signup Route', function () {
+ var signupstate;
+ beforeEach(inject(function ($state) {
+ signupstate = $state.get('authentication.signup');
+ }));
+
+ it('Should have the correct URL', function () {
+ expect(signupstate.url).toEqual('/signup');
+ });
+
+ it('Should not be abstract', function () {
+ expect(signupstate.abstract).toBe(undefined);
+ });
+
+ it('Should have templateUrl', function () {
+ expect(signupstate.templateUrl).toBe('modules/users/client/views/authentication/signup.client.view.html');
+ });
+ });
+
+ describe('Signin Route', function () {
+ var signinstate;
+ beforeEach(inject(function ($state) {
+ signinstate = $state.get('authentication.signin');
+ }));
+
+ it('Should have the correct URL', function () {
+ expect(signinstate.url).toEqual('/signin?err');
+ });
+
+ it('Should not be abstract', function () {
+ expect(signinstate.abstract).toBe(undefined);
+ });
+
+ it('Should have templateUrl', function () {
+ expect(signinstate.templateUrl).toBe('modules/users/client/views/authentication/signin.client.view.html');
+ });
+ });
+
+ });
+
+ describe('Password Route Config', function () {
+ describe('Main Route', function () {
+ var mainstate;
+ beforeEach(inject(function ($state) {
+ mainstate = $state.get('password');
+ }));
+
+ it('Should have the correct URL', function () {
+ expect(mainstate.url).toEqual('/password');
+ });
+
+ it('Should be abstract', function () {
+ expect(mainstate.abstract).toBe(true);
+ });
+
+ it('Should have template', function () {
+ expect(mainstate.template).toBe('');
+ });
+ });
+
+ describe('Forgot Route', function () {
+ var forgotstate;
+ beforeEach(inject(function ($state) {
+ forgotstate = $state.get('password.forgot');
+ }));
+
+ it('Should have the correct URL', function () {
+ expect(forgotstate.url).toEqual('/forgot');
+ });
+
+ it('Should not be abstract', function () {
+ expect(forgotstate.abstract).toBe(undefined);
+ });
+
+ it('Should have templateUrl', function () {
+ expect(forgotstate.templateUrl).toBe('modules/users/client/views/password/forgot-password.client.view.html');
+ });
+ });
+
+ });
+
+ describe('Password Reset Route Config', function () {
+ describe('Main Route', function () {
+ var mainstate;
+ beforeEach(inject(function ($state) {
+ mainstate = $state.get('password.reset');
+ }));
+
+ it('Should have the correct URL', function () {
+ expect(mainstate.url).toEqual('/reset');
+ });
+
+ it('Should be abstract', function () {
+ expect(mainstate.abstract).toBe(true);
+ });
+
+ it('Should have template', function () {
+ expect(mainstate.template).toBe('');
+ });
+ });
+
+ describe('Invalid Route', function () {
+ var invalidstate;
+ beforeEach(inject(function ($state) {
+ invalidstate = $state.get('password.reset.invalid');
+ }));
+
+ it('Should have the correct URL', function () {
+ expect(invalidstate.url).toEqual('/invalid');
+ });
+
+ it('Should not be abstract', function () {
+ expect(invalidstate.abstract).toBe(undefined);
+ });
+
+ it('Should have templateUrl', function () {
+ expect(invalidstate.templateUrl).toBe('modules/users/client/views/password/reset-password-invalid.client.view.html');
+ });
+ });
+
+ describe('Success Route', function () {
+ var successstate;
+ beforeEach(inject(function ($state) {
+ successstate = $state.get('password.reset.success');
+ }));
+
+ it('Should have the correct URL', function () {
+ expect(successstate.url).toEqual('/success');
+ });
+
+ it('Should not be abstract', function () {
+ expect(successstate.abstract).toBe(undefined);
+ });
+
+ it('Should have templateUrl', function () {
+ expect(successstate.templateUrl).toBe('modules/users/client/views/password/reset-password-success.client.view.html');
+ });
+ });
+
+ describe('Form Route', function () {
+ var formstate;
+ beforeEach(inject(function ($state) {
+ formstate = $state.get('password.reset.form');
+ }));
+
+ it('Should have the correct URL', function () {
+ expect(formstate.url).toEqual('/:token');
+ });
+
+ it('Should not be abstract', function () {
+ expect(formstate.abstract).toBe(undefined);
+ });
+
+ it('Should have templateUrl', function () {
+ expect(formstate.templateUrl).toBe('modules/users/client/views/password/reset-password.client.view.html');
+ });
+ });
+
+ });
+ });
+})();