diff --git a/client/src/app/admin/projects/projects-edit.tpl.html b/client/src/app/admin/projects/projects-edit.tpl.html index 782b4b92..10833210 100644 --- a/client/src/app/admin/projects/projects-edit.tpl.html +++ b/client/src/app/admin/projects/projects-edit.tpl.html @@ -30,13 +30,13 @@ {{usersLookup[userId].getFullName()}} - + - + diff --git a/client/src/app/admin/projects/projects-list.tpl.html b/client/src/app/admin/projects/projects-list.tpl.html index be15fa34..25d43098 100644 --- a/client/src/app/admin/projects/projects-list.tpl.html +++ b/client/src/app/admin/projects/projects-list.tpl.html @@ -13,5 +13,5 @@
- +
diff --git a/client/src/app/admin/users/admin-users-edit.js b/client/src/app/admin/users/admin-users-edit.js index 7145e078..dc7d1530 100644 --- a/client/src/app/admin/users/admin-users-edit.js +++ b/client/src/app/admin/users/admin-users-edit.js @@ -58,27 +58,17 @@ angular.module('admin-users-edit',['services.crud', 'services.i18nNotifications' restrict: 'A', require: 'ngModel', link: function(scope, elm, attrs, ctrl) { - - function validateEqual(myValue, otherValue) { - if (myValue === otherValue) { - ctrl.$setValidity('equal', true); - return myValue; - } else { - ctrl.$setValidity('equal', false); - return undefined; - } + function validateEqual(myValue) { + var valid = (myValue === scope.$eval(attrs.validateEquals)); + ctrl.$setValidity('equal', valid); + return valid ? myValue : undefined; } - scope.$watch(attrs.validateEquals, function(otherModelValue) { - ctrl.$setValidity('equal', ctrl.$viewValue === otherModelValue); - }); - - ctrl.$parsers.push(function(viewValue) { - return validateEqual(viewValue, scope.$eval(attrs.validateEquals)); - }); + ctrl.$parsers.push(validateEqual); + ctrl.$formatters.push(validateEqual); - ctrl.$formatters.push(function(modelValue) { - return validateEqual(modelValue, scope.$eval(attrs.validateEquals)); + scope.$watch(attrs.validateEquals, function() { + ctrl.$setViewValue(ctrl.$viewValue); }); } }; diff --git a/client/src/app/admin/users/users-list.tpl.html b/client/src/app/admin/users/users-list.tpl.html index 7f188243..186c52d5 100644 --- a/client/src/app/admin/users/users-list.tpl.html +++ b/client/src/app/admin/users/users-list.tpl.html @@ -15,5 +15,5 @@
- +
diff --git a/client/src/app/app.js b/client/src/app/app.js index 74e4d361..b13df3bf 100644 --- a/client/src/app/app.js +++ b/client/src/app/app.js @@ -8,6 +8,7 @@ angular.module('app', [ 'services.i18nNotifications', 'services.httpRequestTracker', 'directives.crud', + 'directives.button', 'templates']); angular.module('app').constant('MONGOLAB_CONFIG', { diff --git a/client/src/app/login/form.tpl.html b/client/src/app/login/form.tpl.html index 20110dee..1e4d05fe 100644 --- a/client/src/app/login/form.tpl.html +++ b/client/src/app/login/form.tpl.html @@ -14,8 +14,8 @@

Sign in

diff --git a/client/src/app/login/toolbar.tpl.html b/client/src/app/login/toolbar.tpl.html index 63483ed1..1cca8561 100644 --- a/client/src/app/login/toolbar.tpl.html +++ b/client/src/app/login/toolbar.tpl.html @@ -5,12 +5,12 @@
  • \ No newline at end of file diff --git a/client/src/app/projects/productbacklog/productbacklog-list.tpl.html b/client/src/app/projects/productbacklog/productbacklog-list.tpl.html index 53e53c8f..b6335e63 100644 --- a/client/src/app/projects/productbacklog/productbacklog-list.tpl.html +++ b/client/src/app/projects/productbacklog/productbacklog-list.tpl.html @@ -20,5 +20,5 @@
    - +
    \ No newline at end of file diff --git a/client/src/app/projects/sprints/sprints-edit.tpl.html b/client/src/app/projects/sprints/sprints-edit.tpl.html index c1cba063..2a1e9bb6 100644 --- a/client/src/app/projects/sprints/sprints-edit.tpl.html +++ b/client/src/app/projects/sprints/sprints-edit.tpl.html @@ -16,63 +16,59 @@

    Sprint

    - -
    -

    Sprint backlog

    -
    -
    -
    - - - - - - - - - - - - - - - - - - - - - - - -
    NameEstimationTools
    {{productBacklogLookup[sprintBacklogItem].name}}{{productBacklogLookup[sprintBacklogItem].estimation}} - -
    Estimation in total{{estimationInTotal()}}-
    -
    -
    - - - - - - - - - - - - - - - - -
    NameEstimationTools
    {{productBacklogItem.name}}{{productBacklogItem.estimation}}
    +
    +

    Sprint backlog

    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + +
    NameEstimationTools
    {{productBacklogLookup[sprintBacklogItem].name}}{{productBacklogLookup[sprintBacklogItem].estimation}} + +
    Estimation in total{{estimationInTotal()}}-
    +
    +
    + + + + + + + + + + + + + + + + +
    NameEstimationTools
    {{productBacklogItem.name}}{{productBacklogItem.estimation}}Add to sprint
    +
    -
    -
    -
    - - - -
    +
    + +
    \ No newline at end of file diff --git a/client/src/app/projects/sprints/sprints-list.tpl.html b/client/src/app/projects/sprints/sprints-list.tpl.html index 6d25cc0e..321bc9d2 100644 --- a/client/src/app/projects/sprints/sprints-list.tpl.html +++ b/client/src/app/projects/sprints/sprints-list.tpl.html @@ -15,7 +15,7 @@ {{sprint.end}} ACTIVE - + @@ -24,5 +24,5 @@
    - +
    \ No newline at end of file diff --git a/client/src/app/projects/sprints/tasks/tasks-list.tpl.html b/client/src/app/projects/sprints/tasks/tasks-list.tpl.html index 6102cfa3..d16e9bc0 100644 --- a/client/src/app/projects/sprints/tasks/tasks-list.tpl.html +++ b/client/src/app/projects/sprints/tasks/tasks-list.tpl.html @@ -20,5 +20,5 @@
    - +
    \ No newline at end of file diff --git a/client/src/common/directives/button.js b/client/src/common/directives/button.js new file mode 100644 index 00000000..a97e47a6 --- /dev/null +++ b/client/src/common/directives/button.js @@ -0,0 +1,22 @@ +angular.module('directives.button', []) + +.directive('button', function() { + return { + restrict: 'E', + compile: function(element) { + element.addClass('btn'); + } + }; +}) + +.directive('primaryButton', function() { + return { + restrict: 'E', + template: '', + transclude: true, + replace: true, + compile: function(element) { + element.addClass('btn-primary'); + } + }; +}); \ No newline at end of file diff --git a/client/src/common/directives/crud/crudButtons.js b/client/src/common/directives/crud/crudButtons.js index 0aebf891..4544b260 100644 --- a/client/src/common/directives/crud/crudButtons.js +++ b/client/src/common/directives/crud/crudButtons.js @@ -6,9 +6,9 @@ angular.module('directives.crud.buttons', []) replace:true, template: '
    ' + - ' ' + - ' '+ - ' '+ + ' Save' + + ' '+ + ' '+ '
    ' }; }); \ No newline at end of file diff --git a/client/test/unit/app/admin/users/admin-usersSpec.js b/client/test/unit/app/admin/users/admin-usersSpec.js index e050aea5..53043541 100644 --- a/client/test/unit/app/admin/users/admin-usersSpec.js +++ b/client/test/unit/app/admin/users/admin-usersSpec.js @@ -54,102 +54,120 @@ describe('admin users', function () { }); describe('validateEquals directive', function() { - var $scope, form; - - function setTestValue(value) { - $scope.model.testValue = value; - $scope.$digest(); - } - function setCompareTo(value) { - $scope.model.compareTo = value; - $scope.$digest(); - } + var $scope, modelCtrl, modelValue; beforeEach(inject(function($compile, $rootScope) { $scope = $rootScope; var element = angular.element( - '
    ' + '
    ' + + '' + + '
    ' ); - $scope.model = { - testValue: '', - compareTo: '' - }; $compile(element)($scope); + modelValue = $scope.model = {}; + modelCtrl = $scope.testForm.testInput; $scope.$digest(); - form = $scope.form; })); - describe('model validity', function() { - it('should be valid initially', function() { - expect(form.testInput.$valid).toBe(true); - }); + it('should be valid initially', function() { + expect(modelCtrl.$valid).toBeTruthy(); + }); + + describe('model value changes', function() { it('should be invalid if the model changes', function() { - setTestValue('different'); - expect(form.testInput.$valid).toBe(false); + modelValue.testValue = 'different'; + $scope.$digest(); + expect(modelCtrl.$valid).toBeFalsy(); + expect(modelCtrl.$viewValue).toBe(undefined); }); it('should be invalid if the reference model changes', function() { - setCompareTo('different'); - expect(form.testInput.$valid).toBe(false); + modelValue.compareTo = 'different'; + $scope.$digest(); + expect(modelCtrl.$valid).toBeFalsy(); + expect(modelCtrl.$viewValue).toBe(undefined); + }); + it('should be valid if the modelValue changes to be the same as the reference', function() { + modelValue.compareTo = 'different'; + $scope.$digest(); + expect(modelCtrl.$valid).toBeFalsy(); + + modelValue.testValue = 'different'; + $scope.$digest(); + expect(modelCtrl.$valid).toBeTruthy(); + expect(modelCtrl.$viewValue).toBe('different'); + }); + }); + + describe('input value changes', function() { + it('should be invalid if the input value changes', function() { + modelCtrl.$setViewValue('different'); + expect(modelCtrl.$valid).toBeFalsy(); + expect(modelValue.testValue).toBe(undefined); }); - it('should be valid if the model changes to be the same as the reference', function() { - setCompareTo('different'); - expect(form.testInput.$valid).toBe(false); - setTestValue('different'); - expect(form.testInput.$valid).toBe(true); + it('should be invalid if the input value changes to be the same as the reference', function() { + modelValue.compareTo = 'different'; + $scope.$digest(); + expect(modelCtrl.$valid).toBeFalsy(); + + modelCtrl.$setViewValue('different'); + expect(modelCtrl.$viewValue).toBe('different'); + expect(modelCtrl.$valid).toBeTruthy(); }); }); }); describe('uniqueEmail directive', function() { - var Users, $scope, form; + var $scope, testInput, querySpy, respondWith; - function setTestValue(value) { - $scope.model.testValue = value; - $scope.$digest(); - } - - // Mockup Users resource + // We are best to mock up the whole Users object this way because Users + // relies on so many other services, including the MONGOLAB_CONFIG constant angular.module('test', []).factory('Users', function() { - Users = jasmine.createSpyObj('Users', ['query']); - return Users; + querySpy = jasmine.createSpy('query'); + querySpy.andCallFake(function(query, response) { + // We capture the response so that the tests can call it with their own data + respondWith = response; + }); + return { query: querySpy }; }); - beforeEach(module('test')); + beforeEach(inject(function($compile, $rootScope){ $scope = $rootScope; var element = angular.element( - '
    ' + '
    ' + + '' + + '
    ' ); - $scope.model = { testValue: null}; $compile(element)($scope); + $scope.model = {}; $scope.$digest(); - form = $scope.form; + // Keep a reference to the test input for the tests + testInput = $scope.form.testInput; })); it('should be valid initially', function() { - expect(form.testInput.$valid).toBe(true); + expect(testInput.$valid).toBe(true); }); it('should not call Users.query when the model changes', function() { - setTestValue('different'); - expect(Users.query).not.toHaveBeenCalled(); + $scope.model.testValue = 'different'; + $scope.$digest(); + expect(querySpy).not.toHaveBeenCalled(); }); it('should call Users.query when the view changes', function() { - form.testInput.$setViewValue('different'); - expect(Users.query).toHaveBeenCalled(); + testInput.$setViewValue('different'); + expect(querySpy).toHaveBeenCalled(); }); - it('should set model to invalid if the Users callback contains users', function() { - Users.query.andCallFake(function(query, callback) { - callback(['someUser']); - }); - form.testInput.$setViewValue('different'); - expect(form.testInput.$valid).toBe(false); + it('should set model to invalid if the Users.query response contains users', function() { + testInput.$setViewValue('different'); + respondWith(['someUser']); + expect(testInput.$valid).toBe(false); }); - it('should set model to valid if the Users callback contains no users', function() { - Users.query.andCallFake(function(query, callback) { - callback([]); - }); - form.testInput.$setViewValue('different'); - expect(form.testInput.$valid).toBe(true); + it('should set model to valid if the Users.query response contains no users', function() { + testInput.$setViewValue('different'); + respondWith([]); + expect(testInput.$valid).toBe(true); }); }); }); \ No newline at end of file diff --git a/client/test/unit/common/directives/buttonSpec.js b/client/test/unit/common/directives/buttonSpec.js new file mode 100644 index 00000000..2223468b --- /dev/null +++ b/client/test/unit/common/directives/buttonSpec.js @@ -0,0 +1,46 @@ +describe('button directive', function () { + beforeEach(module('directives.button')); + + it('adds a "btn" class to the button element', function() { + inject(function($compile, $rootScope) { + var element = $compile('')($rootScope); + expect(element.hasClass('btn')).toBe(true); + }); + }); + + it('leaves the contents of the button intact', function() { + inject(function($compile, $rootScope) { + var element = $compile('')($rootScope); + expect(element.text()).toBe('Click Me!'); + }); + }); +}); + +describe('primaryButton directive', function () { + var element; + beforeEach(module('directives.button')); + beforeEach(function() { + inject(function($compile, $rootScope) { + element = $compile('')($rootScope); + }); + }); + + it('replaces the directive element with a button element', function() { + expect(element[0].localName).toBe('button'); + }); + + it('adds a "btn-primary" class to the button element', function() { + expect(element.hasClass('btn')).toBe(true); + }); + + it('adds a "btn" class to button elements (because it is a button!)', function() { + expect(element.hasClass('btn')).toBe(true); + }); + + it('transcludes the contents of the button correctly', function() { + inject(function($compile, $rootScope) { + var element = $compile('Click Me!')($rootScope); + expect(element.text()).toBe('Click Me!'); + }); + }); +});