diff --git a/awx/ui/static/js/shared/multi-select-list/multi-select-list.controller.js b/awx/ui/static/js/shared/multi-select-list/multi-select-list.controller.js index d55bd2e2e9..0af01b9eb8 100644 --- a/awx/ui/static/js/shared/multi-select-list/multi-select-list.controller.js +++ b/awx/ui/static/js/shared/multi-select-list/multi-select-list.controller.js @@ -140,7 +140,7 @@ export default ['$scope', * Disables extended selection. * Triggers {@link multiSelectList.selectionChanged `multiSelectList.selectionChanged`} */ - this.deselectAllExtended = function(extendedLength) { + this.deselectAllExtended = function() { $scope.selection.isExtended = false; rebuildSelections(); }; @@ -154,7 +154,7 @@ export default ['$scope', * Enables extended selection. * Triggers {@link multiSelectList.selectionChanged `multiSelectList.selectionChanged`} */ - this.selectAllExtended = function(extendedLength) { + this.selectAllExtended = function() { $scope.selection.isExtended = true; rebuildSelections(); }; diff --git a/awx/ui/static/js/shared/multi-select-list/select-all.directive.js b/awx/ui/static/js/shared/multi-select-list/select-all.directive.js index bbeee0e5bd..57e25eae80 100644 --- a/awx/ui/static/js/shared/multi-select-list/select-all.directive.js +++ b/awx/ui/static/js/shared/multi-select-list/select-all.directive.js @@ -146,7 +146,7 @@ export default scope.selectExtendedLabel = scope.extendedLabel() || 'Select all ' + scope.extendedItemsLength + ' items'; scope.deselectExtendedLabel = scope.deselectExtendedLabel || 'Deselect extra items'; - scope.doSelectAll = function(e) { + scope.doSelectAll = function() { if (scope.isSelected) { controller.selectAll(); diff --git a/awx/ui/tests/karma.conf.js b/awx/ui/tests/karma.conf.js index 62321c9ba0..a6f42aa4a6 100644 --- a/awx/ui/tests/karma.conf.js +++ b/awx/ui/tests/karma.conf.js @@ -18,7 +18,8 @@ module.exports = function(config) { [ 'mocha', 'chai', 'sinon-chai', - 'chai-as-promised' + 'chai-as-promised', + 'chai-things' ], preprocessors: diff --git a/awx/ui/tests/unit/describe-module.js b/awx/ui/tests/unit/describe-module.js index 47b7845a74..b230edb136 100644 --- a/awx/ui/tests/unit/describe-module.js +++ b/awx/ui/tests/unit/describe-module.js @@ -131,6 +131,7 @@ function TestDirective(name, deps) { afterCompile: function(fn) { var self = this; + var $outerScope; // Make sure compile step gets setup first if (!this._compileRegistered) { @@ -140,7 +141,7 @@ function TestDirective(name, deps) { // Then pre-apply the function with the outer scope self.withScope(function($scope) { // `this` refers to mocha test suite - fn = fn.bind(this, $scope); + $outerScope = $scope; }); // Finally, have it called by the isolate scope @@ -150,7 +151,7 @@ function TestDirective(name, deps) { // self.withIsolateScope(function($scope) { // `this` refers to mocha test suite - fn.apply(this, [$scope]); + fn.apply(this, [$outerScope, $scope]); }); }, @@ -164,7 +165,11 @@ function TestDirective(name, deps) { } beforeEach("compile directive element", - inject(['$compile', '$httpBackend', function($compile, $httpBackend) { + inject(['$compile', '$httpBackend', '$rootScope', function($compile, $httpBackend, $rootScope) { + + if (!self.$scope) { + self.$scope = $rootScope.$new(); + } self.$element = $compile(self.element)(self.$scope); $(self.$element).appendTo('body'); @@ -200,6 +205,11 @@ function TestDirective(name, deps) { .whenGET(url) .respond(template); }])); + }, + _ensureCompiled: function() { + if (typeof this.$element === 'undefined') { + throw "Can only call withController after registerPostHooks on directive test"; + } } }; } diff --git a/awx/ui/tests/unit/multi-select-list/multi-select-list.controller-test.js b/awx/ui/tests/unit/multi-select-list/multi-select-list.controller-test.js new file mode 100644 index 0000000000..e69de29bb2 diff --git a/awx/ui/tests/unit/multi-select-list/multi-select-list.directive-test.js b/awx/ui/tests/unit/multi-select-list/multi-select-list.directive-test.js new file mode 100644 index 0000000000..ed4f872c2b --- /dev/null +++ b/awx/ui/tests/unit/multi-select-list/multi-select-list.directive-test.js @@ -0,0 +1,197 @@ +import {describeModule} from 'tests/unit/describe-module'; +import mod from 'tower/shared/multi-select-list/main'; + +describeModule(mod.name) + .testDirective('multiSelectList', function(test) { + + var $scope; + var controller; + + test.use('
'); + + test.afterCompile(function(outerScope, scope) { + $scope = scope; + }); + + test.withController(function(_controller) { + controller = _controller; + }); + + it('works as an attribute on elements', function() { + inject(['$compile', function($compile) { + var node = $compile('')($scope); + var classes = Array.prototype.slice.apply(node[0].classList) + expect(classes).to.contain('ng-scope'); + }]); + }); + + context('controller init', function() { + + it('initializes items and selection', function() { + expect($scope.items).to.be.empty; + expect($scope.selection.selectedItems).to.be.empty; + expect($scope.selection.deselectedItems).to.be.empty; + expect($scope.selection.isExtended).to.be.false; + }); + + it('wraps items when they are registered', function() { + var item = { name: 'blah' }; + var wrapped = controller.registerItem(item); + + expect(wrapped.hasOwnProperty('isSelected')).to.be.true; + expect(wrapped.hasOwnProperty('value')).to.be.true; + + expect(wrapped.isSelected).to.be.false; + expect(wrapped.value).to.eql(item); + + }); + + }); + + context('single select/deselect', function() { + + it('marks item as selected/not selected', function() { + var item = controller.registerItem({ name: 'blah' }); + controller.selectItem(item); + + expect(item.isSelected).to.be.true; + + controller.deselectItem(item); + expect(item.isSelected).to.be.false; + }); + + context('selectionChanged event', function() { + + it('triggers on select/deselect', function() { + var item = controller.registerItem({ name: 'blah' }); + var spy = sinon.spy(); + + $scope.$on('multiSelectList.selectionChanged', spy); + + controller.selectItem(item); + controller.deselectItem(item); + + expect(spy).to.have.been.calledTwice; + }); + + it('is called with the current selection', function() { + var item = controller.registerItem({ name: 'blah' }); + var spy = sinon.spy(); + + $scope.$on('multiSelectList.selectionChanged', spy); + + controller.selectItem(item); + + expect(spy).to.have.been.calledWith(sinon.match.object, + { selectedItems: + [ item.value + ], + deselectedItems: [], + isExtended: false + }); + }); + + it('is called with deselections', function() { + var item = controller.registerItem({ name: 'blah' }); + controller.selectItem(item); + + var spy = sinon.spy(); + + + $scope.$on('multiSelectList.selectionChanged', spy); + controller.deselectItem(item); + + expect(spy).to.have.been.calledWith(sinon.match.object, + { selectedItems: [], + deselectedItems: + [ item.value + ], + isExtended: false + }); + }); + + }); + + }); + + context('select/deselect all items', function() { + + it('marks all items as selected/deselected', function() { + var item1 = controller.registerItem({ name: 'blah' }); + var item2 = controller.registerItem({ name: 'diddy' }); + var item3 = controller.registerItem({ name: 'doo' }); + + controller.selectAll(); + + expect([item1, item2, item3]).to.all.have.property('isSelected', true); + + controller.deselectAll(); + + expect([item1, item2, item3]).to.all.have.property('isSelected', false); + }); + + context('selectionChanged event', function() { + + it('triggers with selections set to all the items', function() { + var item1 = controller.registerItem({ name: 'blah' }); + var item2 = controller.registerItem({ name: 'diddy' }); + var item3 = controller.registerItem({ name: 'doo' }); + var spy = sinon.spy(); + + $scope.$on('multiSelectList.selectionChanged', spy); + + controller.selectAll(); + + expect(spy).to.have.been.calledWith( + sinon.match.object, + { selectedItems: _.pluck([item1, item2, item3], "value"), + deselectedItems: [], + isExtended: false + }); + + controller.deselectAll(); + + expect(spy).to.have.been.calledWith( + sinon.match.object, + { selectedItems: [], + deselectedItems: _.pluck([item1, item2, item3], "value"), + isExtended: false + }); + + }); + + }); + + + it('tracks extended selection state', function() { + var spy = sinon.spy(); + var item1 = controller.registerItem({ name: 'blah' }); + var item2 = controller.registerItem({ name: 'diddy' }); + var item3 = controller.registerItem({ name: 'doo' }); + var allItems = _.pluck([item1, item2, item3], 'value'); + + controller.selectAll(); + controller.selectAllExtended(); + + expect($scope.selection).to.have.property('isExtended', true); + + controller.deselectAllExtended(); + + expect($scope.selection).to.have.property('isExtended', false); + expect($scope.selection) + .to.have.property('selectedItems') + .that.is.an('array') + .deep.equals(allItems); + }); + + + it('toggles extended state on deselectAll', function() { + controller.selectAllExtended(); + + controller.deselectAll(); + + expect($scope.selection).to.have.property('isExtended', false); + }); + }); + }); + diff --git a/awx/ui/tests/unit/multi-select-list/select-all.directive-test.js b/awx/ui/tests/unit/multi-select-list/select-all.directive-test.js new file mode 100644 index 0000000000..31f06c25e3 --- /dev/null +++ b/awx/ui/tests/unit/multi-select-list/select-all.directive-test.js @@ -0,0 +1,94 @@ +import {describeModule} from 'tests/unit/describe-module'; + +var mockController = { + selectAll: sinon.spy(), + deselectAll: sinon.spy(), + selectAllExtended: sinon.spy(), + deselectAllExtended: sinon.spy() +}; + +describeModule('multiSelectList') + .testDirective('selectAll', function(directive) { + + var $scope; + + directive.use('