ui-select
Version:
1,407 lines (1,142 loc) • 134 kB
JavaScript
'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