UNPKG

ui-select

Version:
1,407 lines (1,142 loc) 134 kB
'use strict'; describe('ui-select tests', function () { var scope, $rootScope, $compile, $timeout, $injector, $q, uisRepeatParser; var Key = { Enter: 13, Tab: 9, Up: 38, Down: 40, Left: 37, Right: 39, Backspace: 8, Delete: 46, Escape: 27 }; function isNil(value) { return angular.isUndefined(value) || value === null; } //create a directive that wraps ui-select angular.module('wrapperDirective', ['ui.select']); angular.module('wrapperDirective').directive('wrapperUiSelect', function () { return { restrict: 'EA', template: '<ui-select> \ <ui-select-match placeholder="Pick one...">{{$select.selected.name}}</ui-select-match> \ <ui-select-choices repeat="person in people | filter: $select.search"> \ <div ng-bind-html="person.name | highlight: $select.search"></div> \ </ui-select-choices> \ </ui-select>', require: 'ngModel', scope: true, link: function (scope, element, attrs, ctrl) { } }; }); /* Create a directive that can be applied to the ui-select instance to test * the effects of Angular's validation process on the control. * * Does not currently work with single property binding. Looks at the * selected object or objects for a "valid" property. If all selected objects * have a "valid" property that is truthy, the validator passes. */ angular.module('testValidator', []); angular.module('testValidator').directive('testValidator', function () { return { restrict: 'A', require: 'ngModel', link: function (scope, element, attrs, ngModel) { ngModel.$validators.testValidator = function(modelValue, viewValue) { if (isNil(modelValue)) { return true; } else if (angular.isArray(modelValue)) { var allValid = true, idx = modelValue.length; while (idx-- > 0 && allValid) { allValid = allValid && modelValue[idx].valid; } return allValid; } else { return !!modelValue.valid; } }; } } }); beforeEach(module('ngSanitize', 'ui.select', 'wrapperDirective', 'testValidator')); beforeEach(function () { module(function ($provide) { $provide.factory('uisOffset', function () { return function (el) { return {top: 100, left: 200, width: 300, height: 400}; }; }); }); }); beforeEach(inject(function (_$rootScope_, _$compile_, _$timeout_, _$injector_, _$q_, _uisRepeatParser_) { $rootScope = _$rootScope_; scope = $rootScope.$new(); $compile = _$compile_; $timeout = _$timeout_; $injector = _$injector_; $q = _$q_; uisRepeatParser = _uisRepeatParser_; scope.selection = {}; scope.getGroupLabel = function (person) { return person.age % 2 ? 'even' : 'odd'; }; scope.filterInvertOrder = function (groups) { return groups.sort(function (groupA, groupB) { return groupA.name.toLocaleLowerCase() < groupB.name.toLocaleLowerCase(); }); }; scope.people = [ {name: 'Adam', email: 'adam@email.com', group: 'Foo', age: 12}, {name: 'Amalie', email: 'amalie@email.com', group: 'Foo', age: 12}, {name: 'Estefanía', email: 'estefanía@email.com', group: 'Foo', age: 21}, {name: 'Adrian', email: 'adrian@email.com', group: 'Foo', age: 21}, {name: 'Wladimir', email: 'wladimir@email.com', group: 'Foo', age: 30}, {name: 'Samantha', email: 'samantha@email.com', group: 'bar', age: 30}, {name: 'Nicole', email: 'nicole@email.com', group: 'bar', age: 43}, {name: 'Natasha', email: 'natasha@email.com', group: 'Baz', age: 54} ]; scope.peopleObj = { '1': {name: 'Adam', email: 'adam@email.com', age: 12, country: 'United States'}, '2': {name: 'Amalie', email: 'amalie@email.com', age: 12, country: 'Argentina'}, '3': {name: 'Estefanía', email: 'estefania@email.com', age: 21, country: 'Argentina'}, '4': {name: 'Adrian', email: 'adrian@email.com', age: 21, country: 'Ecuador'}, '5': {name: 'Wladimir', email: 'wladimir@email.com', age: 30, country: 'Ecuador'}, '6': {name: 'Samantha', email: 'samantha@email.com', age: 30, country: 'United States'}, '7': {name: 'Nicole', email: 'nicole@email.com', age: 43, country: 'Colombia'}, '8': {name: 'Natasha', email: 'natasha@email.com', age: 54, country: 'Ecuador'}, '9': {name: 'Michael', email: 'michael@email.com', age: 15, country: 'Colombia'}, '10': {name: 'Nicolás', email: 'nicolas@email.com', age: 43, country: 'Colombia'} }; scope.someObject = {}; scope.someObject.people = [ {name: 'Adam', email: 'adam@email.com', group: 'Foo', age: 12}, {name: 'Amalie', email: 'amalie@email.com', group: 'Foo', age: 12}, {name: 'Estefanía', email: 'estefanía@email.com', group: 'Foo', age: 21}, {name: 'Adrian', email: 'adrian@email.com', group: 'Foo', age: 21}, {name: 'Wladimir', email: 'wladimir@email.com', group: 'Foo', age: 30}, {name: 'Samantha', email: 'samantha@email.com', group: 'bar', age: 30}, {name: 'Nicole', email: 'nicole@email.com', group: 'bar', age: 43}, {name: 'Natasha', email: 'natasha@email.com', group: 'Baz', age: 54} ]; })); // DSL (domain-specific language) function compileTemplate(template) { var el = $compile(angular.element(template))(scope); scope.$digest(); return el; } function createUiSelect(attrs) { var attrsHtml = '', matchAttrsHtml = '', choicesAttrsHtml = '' if (attrs !== undefined) { if (attrs.disabled !== undefined) { attrsHtml += ' ng-disabled="' + attrs.disabled + '"'; } if (attrs.required !== undefined) { attrsHtml += ' ng-required="' + attrs.required + '"'; } if (attrs.theme !== undefined) { attrsHtml += ' theme="' + attrs.theme + '"'; } if (attrs.tabindex !== undefined) { attrsHtml += ' tabindex="' + attrs.tabindex + '"'; } if (attrs.tagging !== undefined) { attrsHtml += ' tagging="' + attrs.tagging + '"'; } if (attrs.taggingTokens !== undefined) { attrsHtml += ' tagging-tokens="' + attrs.taggingTokens + '"'; } if (attrs.title !== undefined) { attrsHtml += ' title="' + attrs.title + '"'; } if (attrs.appendToBody !== undefined) { attrsHtml += ' append-to-body="' + attrs.appendToBody + '"'; } if (attrs.allowClear !== undefined) { matchAttrsHtml += ' allow-clear="' + attrs.allowClear + '"'; } if (attrs.inputId !== undefined) { attrsHtml += ' input-id="' + attrs.inputId + '"'; } if (attrs.ngClass !== undefined) { attrsHtml += ' ng-class="' + attrs.ngClass + '"'; } if (attrs.resetSearchInput !== undefined) { attrsHtml += ' reset-search-input="' + attrs.resetSearchInput + '"'; } if (attrs.closeOnSelect !== undefined) { attrsHtml += ' close-on-select="' + attrs.closeOnSelect + '"'; } if (attrs.spinnerEnabled !== undefined) { attrsHtml += ' spinner-enabled="' + attrs.spinnerEnabled + '"'; } if (attrs.spinnerClass !== undefined) { attrsHtml += ' spinner-class="' + attrs.spinnerClass + '"'; } if (attrs.refresh !== undefined) { choicesAttrsHtml += ' refresh="' + attrs.refresh + '"'; } if (attrs.refreshDelay !== undefined) { choicesAttrsHtml += ' refresh-delay="' + attrs.refreshDelay + '"'; } if (attrs.backspaceReset !== undefined) { attrsHtml += ' backspace-reset="' + attrs.backspaceReset + '"'; } if (attrs.uiDisableChoice !== undefined) { choicesAttrsHtml += ' ui-disable-choice="' + attrs.uiDisableChoice + '"'; } if (attrs.removeSelected !== undefined) { attrsHtml += ' remove-selected="' + attrs.removeSelected + '"'; } } return compileTemplate( '<ui-select ng-model="selection.selected"' + attrsHtml + '> \ <ui-select-match placeholder="Pick one..."' + matchAttrsHtml + '>{{$select.selected.name}}</ui-select-match> \ <ui-select-choices repeat="person in people | filter: $select.search"' + choicesAttrsHtml + '"> \ <div ng-bind-html="person.name | highlight: $select.search"></div> \ <div ng-bind-html="person.email | highlight: $select.search"></div> \ </ui-select-choices> \ </ui-select>' ); } function getMatchLabel(el) { return $(el).find('.ui-select-match > span:first > span[ng-transclude]:not(.ng-hide)').text(); } function clickItem(el, text) { if (!isDropdownOpened(el)) { openDropdown(el); } $(el).find('.ui-select-choices-row div:contains("' + text + '")').click(); scope.$digest(); } function clickMatch(el) { $(el).find('.ui-select-match > span:first').click(); scope.$digest(); } function isDropdownOpened(el) { // Does not work with jQuery 2.*, have to use jQuery 1.11.* // This will be fixed in AngularJS 1.3 // See issue with unit-testing directive using karma https://github.com/angular/angular.js/issues/4640#issuecomment-35002427 return el.scope().$select.open && el.hasClass('open'); } function triggerKeydown(element, keyCode) { var e = jQuery.Event("keydown"); e.which = keyCode; e.keyCode = keyCode; element.trigger(e); } function triggerPaste(element, text, isClipboardEvent) { var e = jQuery.Event("paste"); if (isClipboardEvent) { e.clipboardData = { getData: function () { return text; } }; } else { e.originalEvent = { clipboardData: { getData: function () { return text; } } }; } element.trigger(e); } function setSearchText(el, text) { el.scope().$select.search = text; scope.$digest(); $timeout.flush(); } function openDropdown(el) { var $select = el.scope().$select; $select.open = true; scope.$digest(); } function closeDropdown(el) { var $select = el.scope().$select; $select.open = false; scope.$digest(); } function showChoicesForSearch(el, search) { setSearchText(el, search); el.scope().$select.searchInput.trigger('keyup'); scope.$digest(); } it('should initialize selected choices with an array if choices source is undefined', function(){ var el = createUiSelect(), ctrl = el.scope().$select; ctrl.setItemsFn(); // setPlainItems expect(ctrl.items).toEqual([]); }); // Tests //uisRepeatParser it('should parse simple repeat syntax', function () { var locals = {}; locals.people = [{name: 'Wladimir'}, {name: 'Samantha'}]; locals.person = locals.people[0]; var parserResult = uisRepeatParser.parse('person in people'); expect(parserResult.itemName).toBe('person'); expect(parserResult.modelMapper(locals)).toBe(locals.person); expect(parserResult.source(locals)).toBe(locals.people); var ngExp = parserResult.repeatExpression(false); expect(ngExp).toBe('person in $select.items'); var ngExpGrouped = parserResult.repeatExpression(true); expect(ngExpGrouped).toBe('person in $group.items'); }); it('should parse simple repeat syntax', function () { var locals = {}; locals.people = [{name: 'Wladimir'}, {name: 'Samantha'}]; locals.person = locals.people[0]; var parserResult = uisRepeatParser.parse('person.name as person in people'); expect(parserResult.itemName).toBe('person'); expect(parserResult.modelMapper(locals)).toBe(locals.person.name); expect(parserResult.source(locals)).toBe(locals.people); }); it('should parse simple property binding repeat syntax', function () { var locals = {}; locals.people = [{name: 'Wladimir'}, {name: 'Samantha'}]; locals.person = locals.people[0]; var parserResult = uisRepeatParser.parse('person.name as person in people'); expect(parserResult.itemName).toBe('person'); expect(parserResult.modelMapper(locals)).toBe(locals.person.name); expect(parserResult.source(locals)).toBe(locals.people); }); it('should parse simple property binding repeat syntax with a basic filter', function () { var locals = {}; locals.people = [{name: 'Wladimir'}, {name: 'Samantha'}]; locals.person = locals.people[1]; var parserResult = uisRepeatParser.parse('person.name as person in people | filter: { name: \'Samantha\' }'); expect(parserResult.itemName).toBe('person'); expect(parserResult.modelMapper(locals)).toBe(locals.person.name); expect(parserResult.source(locals)).toEqual([locals.person]); }); it('should parse simple property binding repeat syntax with track by', function () { var locals = {}; locals.people = [{name: 'Wladimir'}, {name: 'Samantha'}]; locals.person = locals.people[0]; var parserResult = uisRepeatParser.parse('person.name as person in people track by person.name'); expect(parserResult.itemName).toBe('person'); expect(parserResult.modelMapper(locals)).toBe(locals.person.name); expect(parserResult.source(locals)).toBe(locals.people); }); it('should parse (key, value) repeat syntax', function () { var locals = {}; locals.people = {'WC': {name: 'Wladimir'}, 'SH': {name: 'Samantha'}}; locals.person = locals.people[0]; var parserResult = uisRepeatParser.parse('(key,person) in people'); expect(parserResult.itemName).toBe('person'); expect(parserResult.keyName).toBe('key'); expect(parserResult.modelMapper(locals)).toBe(locals.person); expect(parserResult.source(locals)).toBe(locals.people); var ngExp = parserResult.repeatExpression(false); expect(ngExp).toBe('person in $select.items'); var ngExpGrouped = parserResult.repeatExpression(true); expect(ngExpGrouped).toBe('person in $group.items'); }); it('should parse simple property binding with (key, value) repeat syntax', function () { var locals = {}; locals.people = {'WC': {name: 'Wladimir'}, 'SH': {name: 'Samantha'}}; locals.person = locals.people['WC']; var parserResult = uisRepeatParser.parse('person.name as (key, person) in people'); expect(parserResult.itemName).toBe('person'); expect(parserResult.keyName).toBe('key'); expect(parserResult.modelMapper(locals)).toBe(locals.person.name); expect(parserResult.source(locals)).toBe(locals.people); }); it('should should accept a "collection expresion" only if its not (key, value) repeat syntax', function () { var locals = {}; locals.people = {'WC': {name: 'Wladimir'}, 'SH': {name: 'Samantha'}}; locals.person = locals.people['WC']; var parserResult = uisRepeatParser.parse('person.name as person in (peopleNothing || people)'); expect(parserResult.itemName).toBe('person'); expect(parserResult.modelMapper(locals)).toBe(locals.person.name); // expect(parserResult.source(locals)).toBe(locals.people); }); it('should should throw if "collection expresion" used and (key, value) repeat syntax', function () { var locals = {}; locals.people = {'WC': {name: 'Wladimir'}, 'SH': {name: 'Samantha'}}; locals.person = locals.people['WC']; function errorFunctionWrapper() { uisRepeatParser.parse('person.name as (key,person) in (people | someFilter)'); } expect(errorFunctionWrapper).toThrow(); }); it('should not leak memory', function () { var cacheLenght = Object.keys(angular.element.cache).length; createUiSelect().remove(); scope.$destroy(); expect(Object.keys(angular.element.cache).length).toBe(cacheLenght); }); it('should compile child directives', function () { var el = createUiSelect(); var searchEl = $(el).find('.ui-select-search'); expect(searchEl.length).toEqual(1); var matchEl = $(el).find('.ui-select-match'); expect(matchEl.length).toEqual(1); var choicesContentEl = $(el).find('.ui-select-choices-content'); expect(choicesContentEl.length).toEqual(1); var choicesContainerEl = $(el).find('.ui-select-choices'); expect(choicesContainerEl.length).toEqual(1); openDropdown(el); var choicesEls = $(el).find('.ui-select-choices-row'); expect(choicesEls.length).toEqual(8); }); it('should correctly render initial state', function () { scope.selection.selected = scope.people[0]; var el = createUiSelect(); expect(getMatchLabel(el)).toEqual('Adam'); }); it('should merge both ng-class attributes defined on ui-select and its templates', function () { var el = createUiSelect({ ngClass: "{class: expression}" }); expect($(el).attr('ng-class')).toEqual("{class: expression, open: $select.open}"); }); it('should correctly render initial state with track by feature', function () { var el = compileTemplate( '<ui-select ng-model="selection.selected"> \ <ui-select-match placeholder="Pick one...">{{$select.selected.name}}</ui-select-match> \ <ui-select-choices repeat="person in people | filter: $select.search track by person.name"> \ <div ng-bind-html="person.name | highlight: $select.search"></div> \ <div ng-bind-html="person.email | highlight: $select.search"></div> \ </ui-select-choices> \ </ui-select>' ); scope.selection.selected = { name: 'Samantha', email: 'something different than array source', group: 'bar', age: 30 }; scope.$digest(); expect(getMatchLabel(el)).toEqual('Samantha'); }); it('should correctly render initial state with track by $index', function () { var el = compileTemplate( '<ui-select ng-model="selection.selected"> \ <ui-select-match placeholder="Pick one...">{{$select.selected.name}}</ui-select-match> \ <ui-select-choices repeat="person in people track by $index"> \ {{person.email}} \ </ui-select-choices> \ </ui-select>' ); openDropdown(el); var generatedId = el.scope().$select.generatedId; expect($(el).find('[id="ui-select-choices-row-' + generatedId + '-0"]').length).toEqual(1); }); it('should utilize wrapper directive ng-model', function () { var el = compileTemplate('<wrapper-ui-select ng-model="selection.selected"/>'); scope.selection.selected = { name: 'Samantha', email: 'something different than array source', group: 'bar', age: 30 }; scope.$digest(); expect($(el).find('.ui-select-container > .ui-select-match > span:first > span[ng-transclude]:not(.ng-hide)') .text()).toEqual('Samantha'); }); it('should display the choices when activated', function () { var el = createUiSelect(); expect(isDropdownOpened(el)).toEqual(false); clickMatch(el); expect(isDropdownOpened(el)).toEqual(true); }); it('should select an item', function () { var el = createUiSelect(); clickItem(el, 'Samantha'); expect(getMatchLabel(el)).toEqual('Samantha'); }); it('should select an item (controller)', function () { var el = createUiSelect(); el.scope().$select.select(scope.people[1]); scope.$digest(); expect(getMatchLabel(el)).toEqual('Amalie'); }); it('should not select a non existing item', function () { var el = createUiSelect(); clickItem(el, "I don't exist"); expect(getMatchLabel(el)).toEqual(''); }); it('should close the choices when an item is selected', function () { var el = createUiSelect(); clickMatch(el); expect(isDropdownOpened(el)).toEqual(true); clickItem(el, 'Samantha'); expect(isDropdownOpened(el)).toEqual(false); }); it('should open/close dropdown when clicking caret icon', function () { var el = createUiSelect({theme: 'select2'}); var searchInput = el.find('.ui-select-search'); var $select = el.scope().$select; expect($select.open).toEqual(false); el.find(".ui-select-toggle").click(); expect($select.open).toEqual(true); el.find(".ui-select-toggle").click(); expect($select.open).toEqual(false); }); it('should clear selection', function () { scope.selection.selected = scope.people[0]; var el = createUiSelect({theme: 'select2', allowClear: 'true'}); var $select = el.scope().$select; // allowClear should be true. expect($select.allowClear).toEqual(true); // Trigger clear. el.find('.select2-search-choice-close').click(); expect(scope.selection.selected).toEqual(null); // If there is no selection the X icon should be gone. expect(el.find('.select2-search-choice-close').length).toEqual(0); }); it('should toggle allow-clear directive', function () { scope.selection.selected = scope.people[0]; scope.isClearAllowed = false; var el = createUiSelect({theme: 'select2', allowClear: '{{isClearAllowed}}'}); var $select = el.scope().$select; expect($select.allowClear).toEqual(false); expect(el.find('.select2-search-choice-close').length).toEqual(0); // Turn clear on scope.isClearAllowed = true; scope.$digest(); expect($select.allowClear).toEqual(true); expect(el.find('.select2-search-choice-close').length).toEqual(1); }); it('should clear selection (with object as source)', function() { var el = compileTemplate( '<ui-select ng-model="selection.selected" theme="select2"> \ <ui-select-match placeholder="Pick one..." allow-clear="true">{{$select.selected.value.name}}</ui-select-match> \ <ui-select-choices repeat="person.value.name as (key,person) in peopleObj | filter: $select.search"> \ <div ng-bind-html="person.value.name | highlight: $select.search"></div> \ <div ng-bind-html="person.value.email | highlight: $select.search"></div> \ </ui-select-choices> \ </ui-select>' ); var $select = el.scope().$select; clickItem(el, 'Samantha'); expect(scope.selection.selected).toEqual('Samantha'); // allowClear should be true. expect($select.allowClear).toEqual(true); // Trigger clear. el.find('.select2-search-choice-close').click(); expect(scope.selection.selected).toEqual(null); // If there is no selection the X icon should be gone. expect(el.find('.select2-search-choice-close').length).toEqual(0); }); it('should pass tabindex to focusser', function () { var el = createUiSelect({tabindex: 5}); expect($(el).find('.ui-select-focusser').attr('tabindex')).toEqual('5'); expect($(el).attr('tabindex')).toEqual(undefined); }); it('should pass tabindex to focusser when tabindex is an expression', function () { scope.tabValue = 22; var el = createUiSelect({tabindex: '{{tabValue + 10}}'}); expect($(el).find('.ui-select-focusser').attr('tabindex')).toEqual('32'); expect($(el).attr('tabindex')).toEqual(undefined); }); it('should not give focusser a tabindex when ui-select does not have one', function () { var el = createUiSelect(); expect($(el).find('.ui-select-focusser').attr('tabindex')).toEqual(undefined); expect($(el).attr('tabindex')).toEqual(undefined); }); it('should be disabled if the attribute says so', function () { var el1 = createUiSelect({disabled: true}); expect(el1.scope().$select.disabled).toEqual(true); clickMatch(el1); expect(isDropdownOpened(el1)).toEqual(false); var el2 = createUiSelect({disabled: false}); expect(el2.scope().$select.disabled).toEqual(false); clickMatch(el2); expect(isDropdownOpened(el2)).toEqual(true); var el3 = createUiSelect(); expect(el3.scope().$select.disabled).toBeFalsy(); clickMatch(el3); expect(isDropdownOpened(el3)).toEqual(true); }); it('should allow decline tags when tagging function returns null', function () { scope.taggingFunc = function (name) { return null; }; var el = createUiSelect({tagging: 'taggingFunc'}); clickMatch(el); showChoicesForSearch(el, 'idontexist'); $(el).scope().$select.activeIndex = 0; $(el).scope().$select.select('idontexist'); expect($(el).scope().$select.selected).not.toBeDefined(); }); it('should allow tagging if the attribute says so', function () { var el = createUiSelect({tagging: true}); clickMatch(el); $(el).scope().$select.select("I don't exist"); expect($(el).scope().$select.selected).toEqual("I don't exist"); }); it('should format new items using the tagging function when the attribute is a function', function () { scope.taggingFunc = function (name) { return { name: name, email: name + '@email.com', group: 'Foo', age: 12 }; }; var el = createUiSelect({tagging: 'taggingFunc'}); clickMatch(el); $(el).scope().$select.search = 'idontexist'; $(el).scope().$select.activeIndex = 0; $(el).scope().$select.select('idontexist'); expect($(el).scope().$select.selected).toEqual({ name: 'idontexist', email: 'idontexist@email.com', group: 'Foo', age: 12 }); }); // See when an item that evaluates to false (such as "false" or "no") is selected, the placeholder is shown https://github.com/angular-ui/ui-select/pull/32 it('should not display the placeholder when item evaluates to false', function () { scope.items = ['false']; var el = compileTemplate( '<ui-select ng-model="selection.selected"> \ <ui-select-match>{{$select.selected}}</ui-select-match> \ <ui-select-choices repeat="item in items | filter: $select.search"> \ <div ng-bind-html="item | highlight: $select.search"></div> \ </ui-select-choices> \ </ui-select>' ); expect(el.scope().$select.selected).toEqual(undefined); clickItem(el, 'false'); expect(el.scope().$select.selected).toEqual('false'); expect(getMatchLabel(el)).toEqual('false'); }); it('should close an opened select when another one is opened', function () { var el1 = createUiSelect(); var el2 = createUiSelect(); el1.appendTo(document.body); el2.appendTo(document.body); expect(isDropdownOpened(el1)).toEqual(false); expect(isDropdownOpened(el2)).toEqual(false); clickMatch(el1); expect(isDropdownOpened(el1)).toEqual(true); expect(isDropdownOpened(el2)).toEqual(false); clickMatch(el2); expect(isDropdownOpened(el1)).toEqual(false); expect(isDropdownOpened(el2)).toEqual(true); el1.remove(); el2.remove(); }); it('should bind model correctly (with object as source)', function () { var el = compileTemplate( '<ui-select ng-model="selection.selected"> \ <ui-select-match placeholder="Pick one...">{{$select.selected.value.name}}</ui-select-match> \ <ui-select-choices repeat="person.value as (key,person) in peopleObj | filter: $select.search"> \ <div ng-bind-html="person.value.name | highlight: $select.search"></div> \ <div ng-bind-html="person.value.email | highlight: $select.search"></div> \ </ui-select-choices> \ </ui-select>' ); // scope.selection.selected = 'Samantha'; clickItem(el, 'Samantha'); scope.$digest(); expect(getMatchLabel(el)).toEqual('Samantha'); expect(scope.selection.selected).toBe(scope.peopleObj[6]); }); it('should bind model correctly (with object as source) using a single property', function () { var el = compileTemplate( '<ui-select ng-model="selection.selected"> \ <ui-select-match placeholder="Pick one...">{{$select.selected.value.name}}</ui-select-match> \ <ui-select-choices repeat="person.value.name as (key,person) in peopleObj | filter: $select.search"> \ <div ng-bind-html="person.value.name | highlight: $select.search"></div> \ <div ng-bind-html="person.value.email | highlight: $select.search"></div> \ </ui-select-choices> \ </ui-select>' ); // scope.selection.selected = 'Samantha'; clickItem(el, 'Samantha'); scope.$digest(); expect(getMatchLabel(el)).toEqual('Samantha'); expect(scope.selection.selected).toBe('Samantha'); }); it('should update choices when original source changes (with object as source)', function () { var el = compileTemplate( '<ui-select ng-model="selection.selected"> \ <ui-select-match placeholder="Pick one...">{{$select.selected.value.name}}</ui-select-match> \ <ui-select-choices repeat="person.value.name as (key,person) in peopleObj | filter: $select.search"> \ <div ng-bind-html="person.value.name | highlight: $select.search"></div> \ <div ng-bind-html="person.value.email | highlight: $select.search"></div> \ </ui-select-choices> \ </ui-select>' ); scope.$digest(); openDropdown(el); var choicesEls = $(el).find('.ui-select-choices-row'); expect(choicesEls.length).toEqual(10); scope.peopleObj['11'] = {name: 'Camila', email: 'camila@email.com', age: 1, country: 'Ecuador'}; scope.$digest(); choicesEls = $(el).find('.ui-select-choices-row'); expect(choicesEls.length).toEqual(11); }); it('should bind model correctly (with object as source) using the key of collection', function () { var el = compileTemplate( '<ui-select ng-model="selection.selected"> \ <ui-select-match placeholder="Pick one...">{{$select.selected.value.name}}</ui-select-match> \ <ui-select-choices repeat="person.key as (key,person) in peopleObj | filter: $select.search"> \ <div ng-bind-html="person.value.name | highlight: $select.search"></div> \ <div ng-bind-html="person.value.email | highlight: $select.search"></div> \ </ui-select-choices> \ </ui-select>' ); // scope.selection.selected = 'Samantha'; clickItem(el, 'Samantha'); scope.$digest(); expect(getMatchLabel(el)).toEqual('Samantha'); expect(scope.selection.selected).toBe('6'); }); it('should correctly render initial state (with object as source) differentiating between falsy values', function () { scope.items = [{ label: '-- None Selected --', value: '' }, { label: 'Yes', value: true }, { label: 'No', value: false }]; var el = compileTemplate( '<ui-select ng-model="selection.selected"> \ <ui-select-match>{{ $select.selected.label }}</ui-select-match> \ <ui-select-choices repeat="item.value as item in items track by item.value">{{ item.label }}</ui-select-choices> \ </ui-select>' ); scope.selection.selected = ''; scope.$digest(); expect(getMatchLabel(el)).toEqual('-- None Selected --'); }); describe('backspace reset option', function () { it('should undefined model when pressing BACKSPACE key if backspaceReset=true', function () { var el = createUiSelect(); var focusserInput = el.find('.ui-select-focusser'); clickItem(el, 'Samantha'); triggerKeydown(focusserInput, Key.Backspace); expect(scope.selection.selected).toBeUndefined(); }); it('should NOT reset model when pressing BACKSPACE key if backspaceReset=false', function () { var el = createUiSelect({backspaceReset: false}); var focusserInput = el.find('.ui-select-focusser'); clickItem(el, 'Samantha'); triggerKeydown(focusserInput, Key.Backspace); expect(scope.selection.selected).toBe(scope.people[5]); }); }); describe('disabled options', function () { function createUiSelect(attrs) { var attrsDisabled = ''; if (attrs !== undefined) { if (attrs.disabled !== undefined) { attrsDisabled = ' ui-disable-choice="' + attrs.disabled + '"'; } else { attrsDisabled = ''; } } return compileTemplate( '<ui-select ng-model="selection.selected"> \ <ui-select-match placeholder="Pick one...">{{$select.selected.name}}</ui-select-match> \ <ui-select-choices repeat="person in people | filter: $select.search"' + attrsDisabled + '> \ <div ng-bind-html="person.name | highlight: $select.search"></div> \ <div ng-bind-html="person.email | highlight: $select.search"></div> \ </ui-select-choices> \ </ui-select>' ); } function disablePerson(opts) { opts = opts || {}; var key = opts.key || 'people', disableAttr = opts.disableAttr || 'disabled', disableBool = opts.disableBool === undefined ? true : opts.disableBool, matchAttr = opts.match || 'name', matchVal = opts.matchVal || 'Wladimir'; scope['_' + key] = angular.copy(scope[key]); scope[key].map(function (model) { if (model[matchAttr] == matchVal) { model[disableAttr] = disableBool; } return model; }); } function resetScope(opts) { opts = opts || {}; var key = opts.key || 'people'; scope[key] = angular.copy(scope['_' + key]); } describe('without disabling expression', function () { beforeEach(function () { disablePerson(); this.el = createUiSelect(); }); it('should not allow disabled options to be selected', function () { clickItem(this.el, 'Wladimir'); expect(getMatchLabel(this.el)).toEqual('Wladimir'); }); it('should set a disabled class on the option', function () { var option = $(this.el).find('.ui-select-choices-row div:contains("Wladimir")'); var container = option.closest('.ui-select-choices-row'); expect(container.hasClass('disabled')).toBeFalsy(); }); }); describe('disable on truthy property', function () { beforeEach(function () { disablePerson({ disableAttr: 'inactive', disableBool: true }); this.el = createUiSelect({ disabled: 'person.inactive' }); }); it('should allow the user to define the selected option', function () { expect($(this.el).find('.ui-select-choices').attr('ui-disable-choice')).toBe('person.inactive'); }); it('should not allow disabled options to be selected', function () { clickItem(this.el, 'Wladimir'); expect(getMatchLabel(this.el)).not.toEqual('Wladimir'); }); it('should set a disabled class on the option', function () { openDropdown(this.el); var option = $(this.el).find('.ui-select-choices-row div:contains("Wladimir")'); var container = option.closest('.ui-select-choices-row'); expect(container.hasClass('disabled')).toBeTruthy(); }); }); describe('disable on inverse property check', function () { beforeEach(function () { disablePerson({ disableAttr: 'active', disableBool: false }); this.el = createUiSelect({ disabled: '!person.active' }); }); it('should allow the user to define the selected option', function () { expect($(this.el).find('.ui-select-choices').attr('ui-disable-choice')).toBe('!person.active'); }); it('should not allow disabled options to be selected', function () { clickItem(this.el, 'Wladimir'); expect(getMatchLabel(this.el)).not.toEqual('Wladimir'); }); it('should set a disabled class on the option', function () { openDropdown(this.el); var option = $(this.el).find('.ui-select-choices-row div:contains("Wladimir")'); var container = option.closest('.ui-select-choices-row'); expect(container.hasClass('disabled')).toBeTruthy(); }); }); describe('disable on expression', function () { beforeEach(function () { disablePerson({ disableAttr: 'status', disableBool: 'inactive' }); this.el = createUiSelect({ disabled: "person.status == 'inactive'" }); }); it('should allow the user to define the selected option', function () { expect($(this.el).find('.ui-select-choices').attr('ui-disable-choice')).toBe("person.status == 'inactive'"); }); it('should not allow disabled options to be selected', function () { clickItem(this.el, 'Wladimir'); expect(getMatchLabel(this.el)).not.toEqual('Wladimir'); }); it('should set a disabled class on the option', function () { openDropdown(this.el); var option = $(this.el).find('.ui-select-choices-row div:contains("Wladimir")'); var container = option.closest('.ui-select-choices-row'); expect(container.hasClass('disabled')).toBeTruthy(); }); }); afterEach(function () { resetScope(); }); }); describe('choices group', function () { function getGroupLabel(item) { return item.parent('.ui-select-choices-group').find('.ui-select-choices-group-label'); } function createUiSelect() { return compileTemplate( '<ui-select ng-model="selection.selected"> \ <ui-select-match placeholder="Pick one...">{{$select.selected.name}}</ui-select-match> \ <ui-select-choices group-by="\'group\'" repeat="person in people | filter: $select.search"> \ <div ng-bind-html="person.name | highlight: $select.search"></div> \ <div ng-bind-html="person.email | highlight: $select.search"></div> \ </ui-select-choices> \ </ui-select>' ); } it('should create items group', function () { var el = createUiSelect(); expect(el.find('.ui-select-choices-group').length).toBe(3); }); it('should show label before each group', function () { var el = createUiSelect(); expect(el.find('.ui-select-choices-group .ui-select-choices-group-label').map(function () { return this.textContent; }).toArray()).toEqual(['Foo', 'bar', 'Baz']); }); it('should hide empty groups', function () { var el = createUiSelect(); el.scope().$select.search = 'd'; scope.$digest(); expect(el.find('.ui-select-choices-group .ui-select-choices-group-label').map(function () { return this.textContent; }).toArray()).toEqual(['Foo']); }); it('should change activeItem through groups', function () { var el = createUiSelect(); el.scope().$select.search = 't'; scope.$digest(); openDropdown(el); var choices = el.find('.ui-select-choices-row'); expect(choices.eq(0)).toHaveClass('active'); expect(getGroupLabel(choices.eq(0)).text()).toBe('Foo'); triggerKeydown(el.find('input'), 40 /*Down*/); scope.$digest(); expect(choices.eq(1)).toHaveClass('active'); expect(getGroupLabel(choices.eq(1)).text()).toBe('bar'); }); }); describe('choices group by function', function () { function createUiSelect() { return compileTemplate( '<ui-select ng-model="selection.selected"> \ <ui-select-match placeholder="Pick one...">{{$select.selected.name}}</ui-select-match> \ <ui-select-choices group-by="getGroupLabel" repeat="person in people | filter: $select.search"> \ <div ng-bind-html="person.name | highlight: $select.search"></div> \ </ui-select-choices> \ </ui-select>' ); } it("should extract group value through function", function () { var el = createUiSelect(); expect(el.find('.ui-select-choices-group .ui-select-choices-group-label').map(function () { return this.textContent; }).toArray()).toEqual(['odd', 'even']); }); }); describe('choices group filter function', function () { function createUiSelect() { return compileTemplate('\ <ui-select ng-model="selection.selected"> \ <ui-select-match placeholder="Pick one...">{{$select.selected.name}}</ui-select-match> \ <ui-select-choices group-by="\'group\'" group-filter="filterInvertOrder" repeat="person in people | filter: $select.search"> \ <div ng-bind-html="person.name | highlight: $select.search"></div> \ </ui-select-choices> \ </ui-select>' ); } it("should sort groups using filter", function () { var el = createUiSelect(); expect(el.find('.ui-select-choices-group .ui-select-choices-group-label').map(function () { return this.textContent; }).toArray()).toEqual(["Foo", "Baz", "bar"]); }); }); describe('choices group filter array', function () { function createUiSelect() { return compileTemplate('\ <ui-select ng-model="selection.selected"> \ <ui-select-match placeholder="Pick one...">{{$select.selected.name}}</ui-select-match> \ <ui-select-choices group-by="\'group\'" group-filter="[\'Foo\']" \ repeat="person in people | filter: $select.search"> \ <div ng-bind-html="person.name | highlight: $select.search"></div> \ </ui-select-choices> \ </ui-select>' ); } it("should sort groups using filter", function () { var el = createUiSelect(); expect(el.find('.ui-select-choices-group .ui-select-choices-group-label').map(function () { return this.textContent; }).toArray()).toEqual(["Foo"]); }); }); it('should format the model correctly using alias', function () { var el = compileTemplate( '<ui-select ng-model="selection.selected"> \ <ui-select-match placeholder="Pick one...">{{$select.selected.name}}</ui-select-match> \ <ui-select-choices repeat="person as person in people | filter: $select.search"> \ <div ng-bind-html="person.name | highlight: $select.search"></div> \ <div ng-bind-html="person.email | highlight: $select.search"></div> \ </ui-select-choices> \ </ui-select>' ); clickItem(el, 'Samantha'); expect(scope.selection.selected).toBe(scope.people[5]); }); it('should parse the model correctly using alias', function () { var el = compileTemplate( '<ui-select ng-model="selection.selected"> \ <ui-select-match placeholder="Pick one...">{{$select.selected.name}}</ui-select-match> \ <ui-select-choices repeat="person as person in people | filter: $select.search"> \ <div ng-bind-html="person.name | highlight: $select.search"></div> \ <div ng-bind-html="person.email | highlight: $select.search"></div> \ </ui-select-choices> \ </ui-select>' ); scope.selection.selected = scope.people[5]; scope.$digest(); expect(getMatchLabel(el)).toEqual('Samantha'); }); it('should format the model correctly using property of alias', function () { var el = compileTemplate( '<ui-select ng-model="selection.selected"> \ <ui-select-match placeholder="Pick one...">{{$select.selected.name}}</ui-select-match> \ <ui-select-choices repeat="person.name as person in people | filter: $select.search"> \ <div ng-bind-html="person.name | highlight: $select.search"></div> \ <div ng-bind-html="person.email | highlight: $select.search"></div> \ </ui-select-choices> \ </ui-select>' ); clickItem(el, 'Samantha'); expect(scope.selection.selected).toBe('Samantha'); }); it('should parse the model correctly using property of alias', function () { var el = compileTemplate( '<ui-select ng-model="selection.selected"> \ <ui-select-match placeholder="Pick one...">{{$select.selected.name}}</ui-select-match> \ <ui-select-choices repeat="person.name as person in people | filter: $select.search"> \ <div ng-bind-html="person.name | highlight: $select.search"></div> \ <div ng-bind-html="person.email | highlight: $select.search"></div> \ </ui-select-choices> \ </ui-select>' ); scope.selection.selected = 'Samantha'; scope.$digest(); expect(getMatchLabel(el)).toEqual('Samantha'); }); it('should parse the model correctly using property of alias with async choices data', function () { var el = compileTemplate( '<ui-select ng-model="selection.selected"> \ <ui-select-match placeholder="Pick one...">{{$select.selected.name}}</ui-select-match> \ <ui-select-choices repeat="person.name as person in peopleAsync | filter: $select.search"> \ <div ng-bind-html="person.name | highlight: $select.search"></div> \ <div ng-bind-html="person.email | highlight: $select.search"></div> \ </ui-select-choices> \ </ui-select>' ); $timeout(function () { scope.peopleAsync = scope.people; }); scope.selection.selected = 'Samantha'; scope.$digest(); expect(getMatchLabel(el)).toEqual(''); $timeout.flush(); //After choices populated (async), it should show match correctly expect(getMatchLabel(el)).toEqual('Samantha'); }); //TODO Is this really something we should expect? it('should parse the model correctly using property of alias but passed whole object', function () { var el = compileTemplate( '<ui-select ng-model="selection.selected"> \ <ui-select-match placeholder="Pick one...">{{$select.selected.name}}</ui-select-match> \ <ui-select-choices repeat="person.name as person in people | filter: $select.search"> \ <div ng-bind-html="person.name | highlight: $select.search"></div> \ <div ng-bind-html="person.email | highlight: $select.search"></div> \ </ui-select-choices> \ </ui-select>' ); scope.selection.selected = scope.people[5]; scope.$digest(); expect(getMatchLabel(el)).toEqual('Samantha'); }); it('should format the model correctly without alias', function () { var el = createUiSelect(); clickItem(el, 'Samantha'); expect(scope.selection.selected).toBe(scope.people[5]); }); it('should parse the model correctly without alias', function () { var el = createUiSelect(); scope.selection.selected = scope.people[5]; scope.$digest(); expect(getMatchLabel(el)).toEqual('Samantha'); }); it('should display choices correctly with child array', function () { var el = compileTemplate( '<ui-select ng-model="selection.selected"> \ <ui-select-match placeholder="Pick one...">{{$select.selected.name}}</ui-select-match> \ <ui-select-choices repeat="person in someObject.people | filter: $select.search"> \ <div ng-bind-html="person.name | highlight: $select.search"></div> \ <div ng-bind-html="person.email | highlight: $select.search"></div> \ </ui-select-choices> \ </ui-select>' ); scope.selection.selected = scope.people[5]; scope.$digest(); expect(getMatchLabel(el)).toEqual('Samantha'); }); it('should format the model correctly using property of alias and when using child array for choices', function () { var el = compileTemplate( '<ui-select ng-model="selection.selected"> \ <ui-select-match placeholder="Pick one...">{{$select.selected.name}}</ui-select-match> \ <ui-select-choices repeat="person.name as person in someObject.people | filter: $select.search"> \ <div ng-bind-html="person.name | highlight: $select.search"></div> \ <div ng-bind-html="person.email | highlight: $select.search"></div> \ </ui-select-choices> \ </ui-select>' ); clickItem(el, 'Samantha'); expect(scope.selection.selected).toBe('Samantha'); }); it('should invoke select callback on select', function () { scope.onSelectFn = function ($item, $model, $label) { scope.$item = $item; scope.$model = $model; }; var el = compileTemplate( '<ui-select on-select="onSelectFn($item, $model)" ng-model="selection.selected"> \ <ui-select-match placeholder="Pick one...">{{$select.selected.name}}</ui-select-match> \ <ui-select-choices repeat="person.name as person in people | filter: $select.search"> \ <div ng-bind-html="person.name | highlight: $select.search"></div> \ <div ng-bind-html="person.email | highlight: $select.search"></div> \ </ui-select-choices> \ </ui-select>' ); expect(scope.$item).toBeFalsy(); expect(scope.$model).toBeFalsy(); clickItem(el, 'Samantha'); $timeout.flush(); expect(scope.selection.selected).toBe('Samantha'); expect(scope.$item).toEqual(scope.people[5]); expect(scope.$model).toEqual('Samantha'); }); it('should set $item & $model correctly when invoking callback on select and no single prop. binding', function () { scope.onSelectFn = function ($item, $model, $label) { scope.$item = $item; scope.$model = $model; }; var el = compileTemplate( '<ui-select on-select="onSelectFn($item, $model)" ng-model="selection.selected"> \ <ui-select-match placeholder="Pick one...">{{$select.selected.name}}</ui-select-match> \ <ui-select-choices repeat="person in people | filter: $select.search"> \ <div ng-bind-html="person.name | highlight: $select.search"></div> \ <div ng-bind-html="person.email | highlight: $select.search"></div> \ </ui-select-choices> \ </ui-select>' ); expect(scope.$item).toBeFalsy(); expect(scope.$model).toBeFalsy(); clickItem(el, 'Samantha'); expect(scope.$item).toEqual(scope.$model); }); it('should invoke remove callback on remove', function () { scope.onRemoveFn = function ($item, $model, $label) { scope.$item = $item; scope.$model = $model; }; var el = compileTemplate( '<ui-select multiple on-remove="onRemoveFn($item, $model)" ng-model="selection.selected"> \ <ui-select-match placeholder