diff --git a/templates/web/login/layout/base-layout.html.twig b/templates/web/login/layout/base-layout.html.twig
index 41e45d3e14..fc72e143dd 100644
--- a/templates/web/login/layout/base-layout.html.twig
+++ b/templates/web/login/layout/base-layout.html.twig
@@ -71,6 +71,7 @@
+
{% endblock header_javascript %}
{% endblock header %}
diff --git a/www/skins/login/js/angular/app/app.js b/www/skins/login/js/angular/app/app.js
new file mode 100644
index 0000000000..f6b4824241
--- /dev/null
+++ b/www/skins/login/js/angular/app/app.js
@@ -0,0 +1,144 @@
+// controllers
+function LoginFormController($scope) {
+ $scope.$watch('loginForm', function() {
+ $scope.loginForm.email.errors = {'filled' : true, 'valid' : true};
+ $scope.loginForm.password.errors = {'filled' : true, 'valid' : true};
+ });
+
+ $scope.submit = function() {
+ $scope.$broadcast('event:force-model-update');
+
+ if (true === $scope.loginForm.$valid) {
+ $scope.loginForm.email.errors = {'filled' : true, 'valid' : true};
+ $scope.loginForm.password.errors = {'filled' : true,'valid' : true};
+ // submit
+ return true;
+ }
+
+ $scope.loginForm.email.errors.valid = $scope.loginForm.email.$valid;
+ $scope.loginForm.email.errors.filled = !$scope.loginForm.email.$error.required;
+
+ $scope.loginForm.password.errors.filled = !$scope.loginForm.password.$error.required;
+
+ return false;
+ };
+
+ $scope.getInputClass = function(name) {
+ return _.every($scope.loginForm[name].errors, function(value) {
+ return value === true;
+ }) ? '' : 'input-table-error';
+ };
+}
+
+function forgottenPasswordFormCtrl($scope) {
+ $scope.$watch('forgottenPasswordForm', function() {
+ $scope.forgottenPasswordForm.email.errors = {'filled' : true, 'valid' : true};
+ });
+
+ $scope.submit = function() {
+ $scope.$broadcast('event:force-model-update');
+
+ if (true === $scope.forgottenPasswordForm.$valid) {
+ $scope.forgottenPasswordForm.email.errors = {'filled' : true, 'valid' : true};
+ // submit
+ return true;
+ }
+
+ $scope.forgottenPasswordForm.email.errors.valid = $scope.forgottenPasswordForm.email.$valid;
+ $scope.forgottenPasswordForm.email.errors.filled = !$scope.forgottenPasswordForm.email.$error.required;
+
+ return false;
+ };
+
+ $scope.getInputClass = function(name) {
+ return _.every($scope.forgottenPasswordForm[name].errors, function(value) {
+ return value === true;
+ }) ? '' : 'input-table-error';
+ };
+}
+
+function passwordChangeFormCtrl($scope) {
+ $scope.$watch('passwordChangeForm', function() {
+ $scope.passwordChangeForm.password.errors = {'filled' : true, 'valid' : true};
+ $scope.passwordChangeForm.passwordConfirm.errors = {'filled' : true, 'valid' : true};
+ });
+
+ $scope.submit = function() {
+ $scope.$broadcast('event:force-model-update');
+
+ if (true === $scope.passwordChangeForm.$valid) {
+ $scope.passwordChangeForm.password.errors = {'filled' : true, 'valid' : true};
+ $scope.passwordChangeForm.passwordConfirm.errors = {'filled' : true, 'valid' : true};
+ // submit
+ return true;
+ }
+
+ $scope.passwordChangeForm.password.errors.filled = !$scope.passwordChangeForm.password.$error.required;
+ $scope.passwordChangeForm.passwordConfirm.errors.filled = !$scope.passwordChangeForm.passwordConfirm.$error.required;
+
+ return false;
+ };
+
+ $scope.getInputClass = function(name) {
+ return _.every($scope.passwordChangeForm[name].errors, function(value) {
+ return value === true;
+ }) ? '' : 'input-table-error';
+ };
+}
+
+// bootstrap angular
+angular.element(document).ready(function() {
+ angular.bootstrap(document, ['phraseanetAuthentication']);
+});
+
+
+// angular app
+angular.module('phraseanetAuthentication', ['ui'])
+// force model update for autofill inputs. Yuck.
+.directive('forceModelUpdate', function($compile) {
+ return {
+ restrict: 'A',
+ require: 'ngModel',
+ link: function(scope, element, attrs, ctrl) {
+ scope.$on('event:force-model-update', function() {
+ ctrl.$setViewValue(element.val());
+ });
+ }
+ }
+}).directive('alert', function () {
+ return {
+ restrict:'EA',
+ template: [
+ '
',
+ '
',
+ ' | ',
+ ' | ',
+ '× | ',
+ '
',
+ '
'
+ ].join(''),
+ transclude:true,
+ replace:true,
+ scope:{
+ type: '@'
+ },
+ compile: function (element, attrs, transclude) {
+ return function (scope, element, attr) {
+ if (true === 'type' in attrs) {
+ switch (attrs.type) {
+ case 'error' :
+ scope.icon = 'icon-warning-sign';
+ break;
+ case 'success' :
+ scope.icon = 'icon-ok-sign';
+ break;
+ case 'info' :
+ scope.icon = 'icon-info-sign';
+ break;
+ }
+ }
+ }
+ }
+ };
+});
+
diff --git a/www/skins/login/js/angular/config/testacular.conf.js b/www/skins/login/js/angular/config/testacular.conf.js
new file mode 100644
index 0000000000..fd8f6c0362
--- /dev/null
+++ b/www/skins/login/js/angular/config/testacular.conf.js
@@ -0,0 +1,16 @@
+basePath = '../../../../../';
+
+files = [
+ JASMINE,
+ JASMINE_ADAPTER,
+ 'assets/angular/angular.js',
+ 'assets/angular-ui/build/angular-ui.js',
+ 'assets/underscore/underscore.js',
+ 'assets/angular-mocks/angular-mocks.js',
+ 'skins/login/js/angular/app/*.js',
+ 'skins/login/js/angular/tests/unit/*.js'
+];
+
+autoWatch = true;
+
+browsers = ['firefox'];
diff --git a/www/skins/login/js/angular/scripts/test.bat b/www/skins/login/js/angular/scripts/test.bat
new file mode 100644
index 0000000000..000242f53e
--- /dev/null
+++ b/www/skins/login/js/angular/scripts/test.bat
@@ -0,0 +1,11 @@
+@echo off
+
+REM Windows script for running unit tests
+REM You have to run server and capture some browser first
+REM
+REM Requirements:
+REM - NodeJS (http://nodejs.org/)
+REM - Testacular (npm install -g testacular)
+
+set BASE_DIR=%~dp0
+testacular start "%BASE_DIR%\..\config\testacular.conf.js" %*
diff --git a/www/skins/login/js/angular/scripts/test.sh b/www/skins/login/js/angular/scripts/test.sh
new file mode 100644
index 0000000000..4c37cde250
--- /dev/null
+++ b/www/skins/login/js/angular/scripts/test.sh
@@ -0,0 +1,9 @@
+#!/bin/bash
+
+BASE_DIR=`dirname $0`
+
+echo ""
+echo "Starting Testacular Server (http://vojtajina.github.com/testacular)"
+echo "-------------------------------------------------------------------"
+
+testacular start $BASE_DIR/../config/testacular.conf.js $*
diff --git a/www/skins/login/js/angular/scripts/watchr.rb b/www/skins/login/js/angular/scripts/watchr.rb
new file mode 100644
index 0000000000..89ef656d27
--- /dev/null
+++ b/www/skins/login/js/angular/scripts/watchr.rb
@@ -0,0 +1,19 @@
+#!/usr/bin/env watchr
+
+# config file for watchr http://github.com/mynyml/watchr
+# install: gem install watchr
+# run: watch watchr.rb
+# note: make sure that you have jstd server running (server.sh) and a browser captured
+
+log_file = File.expand_path(File.dirname(__FILE__) + '/../logs/jstd.log')
+
+`cd ..`
+`touch #{log_file}`
+
+puts "String watchr... log file: #{log_file}"
+
+watch( '(app/js|test/unit)' ) do
+ `echo "\n\ntest run started @ \`date\`" > #{log_file}`
+ `scripts/test.sh &> #{log_file}`
+end
+
diff --git a/www/skins/login/js/angular/tests/unit/controllersSpec.js b/www/skins/login/js/angular/tests/unit/controllersSpec.js
new file mode 100644
index 0000000000..e255d86eb1
--- /dev/null
+++ b/www/skins/login/js/angular/tests/unit/controllersSpec.js
@@ -0,0 +1,107 @@
+'use strict';
+
+describe('LoginFormController', function(){
+ var scope;
+ beforeEach(inject(function($rootScope) {
+ scope = $rootScope.$new();
+
+ scope.loginForm = {
+ email : {
+ errors:{},
+ $valid : true,
+ $error : {
+ required : false
+ }
+ },
+ password : {
+ errors:{},
+ $valid : true,
+ $error : {
+ required : false
+ }
+ }
+ };
+ }));
+
+ it('should create model errors with 2 validation for each input', inject(function($controller) {
+ $controller(LoginFormController, {
+ $scope: scope
+ });
+
+ scope.$digest();
+
+ expect(Object.keys(scope.loginForm.email.errors).length).toEqual(2);
+ expect(Object.keys(scope.loginForm.password.errors).length).toEqual(2);
+ }));
+
+ it('should return input-table-error class when input is not valid', inject(function($controller) {
+ scope.loginForm.email.errors.valid = false;
+ scope.loginForm.password.errors.valid = true;
+
+ $controller(LoginFormController, {
+ $scope: scope
+ });
+
+ expect(scope.getInputClass('password')).toBe('');
+ expect(scope.getInputClass('email')).toEqual('input-table-error');
+ }));
+
+ it('The valid email input validation should be equal to input form validation if form is submited and not valid', inject(function($controller) {
+ scope.loginForm.$valid = false;
+
+ $controller(LoginFormController, {
+ $scope: scope
+ });
+
+ scope.$digest();
+
+ expect(scope.loginForm.email.errors.valid).toBe(scope.loginForm.email.$valid);
+ }));
+
+ it('The filled password and email input validation should be true if input is not required and form is submited and not valid', inject(function($controller) {
+ scope.loginForm.$valid = false;
+
+ $controller(LoginFormController, {
+ $scope: scope
+ });
+
+ scope.$digest();
+
+ expect(scope.loginForm.email.errors.filled).not.toBe(scope.loginForm.email.$error.required);
+ expect(scope.loginForm.password.errors.filled).not.toBe(scope.loginForm.password.$error.required);
+ }));
+
+ it('Should reset input validation errors when form is submited and valid', inject(function($controller) {
+ $controller(LoginFormController, {
+ $scope: scope
+ });
+
+ scope.$digest();
+
+ var startEmailState = _.clone(scope.loginForm.email.errors);
+ var startPasswordState = _.clone(scope.loginForm.password.errors);
+
+ scope.loginForm.$valid = true;
+
+ scope.loginForm.email.errors.valid = false;
+ scope.loginForm.password.errors.valid = false;
+
+ scope.submit();
+
+ expect(scope.loginForm.email.errors).toEqual(startEmailState);
+ expect(scope.loginForm.password.errors).toEqual(startPasswordState);
+ }));
+
+ it('should not submit form if it is not valid', inject(function($controller) {
+ scope.loginForm.$valid = false;
+
+ $controller(LoginFormController, {
+ $scope: scope
+ });
+
+ scope.$digest();
+
+ expect(scope.submit()).toBe(false);
+ }));
+});
+
diff --git a/www/skins/login/js/main.js b/www/skins/login/js/main.js
index 63ef38d292..abbd1196e0 100644
--- a/www/skins/login/js/main.js
+++ b/www/skins/login/js/main.js
@@ -6,146 +6,3 @@ $(document).ready(function() {
return false;
});
});
-
-// angular app
-angular.module('phraseanetAuthentication', ['ui'])
-// force model update for autofill inputs. Yuck.
-.directive('forceModelUpdate', function($compile) {
- return {
- restrict: 'A',
- require: 'ngModel',
- link: function(scope, element, attrs, ctrl) {
- scope.$on('event:force-model-update', function() {
- ctrl.$setViewValue(element.val());
- });
- }
- }
-}).directive('alert', function () {
- return {
- restrict:'EA',
- template: [
- '',
- '
',
- ' | ',
- ' | ',
- '× | ',
- '
',
- '
'
- ].join(''),
- transclude:true,
- replace:true,
- scope:{
- type: '@'
- },
- compile: function (element, attrs, transclude) {
- return function (scope, element, attr) {
- if (true === 'type' in attrs) {
- switch (attrs.type) {
- case 'error' :
- scope.icon = 'icon-warning-sign';
- break;
- case 'success' :
- scope.icon = 'icon-ok-sign';
- break;
- case 'info' :
- scope.icon = 'icon-info-sign';
- break;
- }
- }
- }
- }
- };
-});
-
-// controllers
-function LoginFormController($scope) {
- $scope.$watch('loginForm', function() {
- $scope.loginForm.email.errors = {'filled' : true, 'valid' : true};
- $scope.loginForm.password.errors = {'filled' : true, 'valid' : true};
- });
-
- $scope.submit = function() {
- $scope.$broadcast('event:force-model-update');
-
- if (true === $scope.loginForm.$valid) {
- $scope.loginForm.email.errors = {'filled' : true, 'valid' : true};
- $scope.loginForm.password.errors = {'filled' : true,'valid' : true};
- // submit
- return;
- }
-
- $scope.loginForm.email.errors.valid = $scope.loginForm.email.$valid;
- $scope.loginForm.email.errors.filled = !$scope.loginForm.email.$error.required;
-
- $scope.loginForm.password.errors.filled = !$scope.loginForm.password.$error.required;
-
- return;
- };
-
- $scope.getInputClass = function(name) {
- return _.every($scope.loginForm[name].errors, function(value) {
- return value === true;
- }) ? '' : 'input-table-error';
- };
-}
-
-function forgottenPasswordFormCtrl($scope) {
- $scope.$watch('forgottenPasswordForm', function() {
- $scope.forgottenPasswordForm.email.errors = {'filled' : true, 'valid' : true};
- });
-
- $scope.submit = function() {
- $scope.$broadcast('event:force-model-update');
-
- if (true === $scope.forgottenPasswordForm.$valid) {
- $scope.forgottenPasswordForm.email.errors = {'filled' : true, 'valid' : true};
- // submit
- return;
- }
-
- $scope.forgottenPasswordForm.email.errors.valid = $scope.forgottenPasswordForm.email.$valid;
- $scope.forgottenPasswordForm.email.errors.filled = !$scope.forgottenPasswordForm.email.$error.required;
-
- return;
- };
-
- $scope.getInputClass = function(name) {
- return _.every($scope.forgottenPasswordForm[name].errors, function(value) {
- return value === true;
- }) ? '' : 'input-table-error';
- };
-}
-
-function passwordChangeFormCtrl($scope) {
- $scope.$watch('passwordChangeForm', function() {
- $scope.passwordChangeForm.password.errors = {'filled' : true, 'valid' : true};
- $scope.passwordChangeForm.passwordConfirm.errors = {'filled' : true, 'valid' : true};
- });
-
- $scope.submit = function() {
- $scope.$broadcast('event:force-model-update');
-
- if (true === $scope.passwordChangeForm.$valid) {
- $scope.passwordChangeForm.password.errors = {'filled' : true, 'valid' : true};
- $scope.passwordChangeForm.passwordConfirm.errors = {'filled' : true, 'valid' : true};
- // submit
- return;
- }
-
- $scope.passwordChangeForm.password.errors.filled = !$scope.passwordChangeForm.password.$error.required;
- $scope.passwordChangeForm.passwordConfirm.errors.filled = !$scope.passwordChangeForm.passwordConfirm.$error.required;
-
- return;
- };
-
- $scope.getInputClass = function(name) {
- return _.every($scope.passwordChangeForm[name].errors, function(value) {
- return value === true;
- }) ? '' : 'input-table-error';
- };
-}
-
-// bootstrap angular
-angular.element(document).ready(function() {
- angular.bootstrap(document, ['phraseanetAuthentication']);
-});