0b0a122888
Closes #16493
3501 lines
108 KiB
JavaScript
3501 lines
108 KiB
JavaScript
'use strict';
|
|
|
|
describe('ngOptions', function() {
|
|
|
|
var scope, formElement, element, $compile, linkLog, childListMutationObserver, ngModelCtrl;
|
|
|
|
function compile(html) {
|
|
formElement = jqLite('<form name="form">' + html + '</form>');
|
|
element = formElement.find('select');
|
|
$compile(formElement)(scope);
|
|
ngModelCtrl = element.controller('ngModel');
|
|
scope.$apply();
|
|
}
|
|
|
|
function setSelectValue(selectElement, optionIndex) {
|
|
var option = selectElement.find('option').eq(optionIndex);
|
|
selectElement.val(option.val());
|
|
browserTrigger(element, 'change');
|
|
}
|
|
|
|
|
|
beforeEach(function() {
|
|
jasmine.addMatchers({
|
|
toEqualSelectValue: function() {
|
|
return {
|
|
compare: function(_actual_, value, multiple) {
|
|
var errors = [];
|
|
var actual = _actual_.val();
|
|
|
|
if (multiple) {
|
|
value = value.map(function(val) { return hashKey(val); });
|
|
actual = actual || [];
|
|
} else {
|
|
value = hashKey(value);
|
|
}
|
|
|
|
if (!equals(actual, value)) {
|
|
errors.push('Expected select value "' + actual + '" to equal "' + value + '"');
|
|
}
|
|
var message = function() {
|
|
return errors.join('\n');
|
|
};
|
|
|
|
return { pass: errors.length === 0, message: message };
|
|
}
|
|
};
|
|
},
|
|
toEqualOption: function() {
|
|
return {
|
|
compare: function(actual, value, text, label) {
|
|
var errors = [];
|
|
var hash = hashKey(value);
|
|
if (actual.attr('value') !== hash) {
|
|
errors.push('Expected option value "' + actual.attr('value') + '" to equal "' + hash + '"');
|
|
}
|
|
if (text && actual.text() !== text) {
|
|
errors.push('Expected option text "' + actual.text() + '" to equal "' + text + '"');
|
|
}
|
|
if (label && actual.attr('label') !== label) {
|
|
errors.push('Expected option label "' + actual.attr('label') + '" to equal "' + label + '"');
|
|
}
|
|
|
|
var message = function() {
|
|
return errors.join('\n');
|
|
};
|
|
|
|
return { pass: errors.length === 0, message: message };
|
|
}
|
|
};
|
|
},
|
|
toEqualTrackedOption: function() {
|
|
return {
|
|
compare: function(actual, value, text, label) {
|
|
var errors = [];
|
|
if (actual.attr('value') !== '' + value) {
|
|
errors.push('Expected option value "' + actual.attr('value') + '" to equal "' + value + '"');
|
|
}
|
|
if (text && actual.text() !== text) {
|
|
errors.push('Expected option text "' + actual.text() + '" to equal "' + text + '"');
|
|
}
|
|
if (label && actual.attr('label') !== label) {
|
|
errors.push('Expected option label "' + actual.attr('label') + '" to equal "' + label + '"');
|
|
}
|
|
|
|
var message = function() {
|
|
return errors.join('\n');
|
|
};
|
|
|
|
return { pass: errors.length === 0, message: message };
|
|
}
|
|
};
|
|
},
|
|
toEqualUnknownOption: function() {
|
|
return {
|
|
compare: function(actual) {
|
|
var errors = [];
|
|
if (actual.attr('value') !== '?') {
|
|
errors.push('Expected option value "' + actual.attr('value') + '" to equal "?"');
|
|
}
|
|
|
|
var message = function() {
|
|
return errors.join('\n');
|
|
};
|
|
|
|
return { pass: errors.length === 0, message: message };
|
|
}
|
|
};
|
|
},
|
|
toEqualUnknownValue: function() {
|
|
return {
|
|
compare: function(actual, value) {
|
|
var errors = [];
|
|
if (actual !== '?') {
|
|
errors.push('Expected select value "' + actual + '" to equal "?"');
|
|
}
|
|
|
|
var message = function() {
|
|
return errors.join('\n');
|
|
};
|
|
|
|
return { pass: errors.length === 0, message: message };
|
|
}
|
|
};
|
|
}
|
|
});
|
|
});
|
|
|
|
beforeEach(module(function($compileProvider, $provide) {
|
|
linkLog = [];
|
|
|
|
$compileProvider
|
|
.directive('customSelect', function() {
|
|
return {
|
|
restrict: 'E',
|
|
replace: true,
|
|
scope: {
|
|
ngModel: '=',
|
|
options: '='
|
|
},
|
|
templateUrl: 'select_template.html',
|
|
link: function(scope, $element, attributes) {
|
|
scope.selectable_options = scope.options;
|
|
}
|
|
};
|
|
})
|
|
|
|
.directive('oCompileContents', function() {
|
|
return {
|
|
link: function(scope, element) {
|
|
linkLog.push('linkCompileContents');
|
|
$compile(element.contents())(scope);
|
|
}
|
|
};
|
|
})
|
|
|
|
.directive('observeChildList', function() {
|
|
return {
|
|
link: function(scope, element) {
|
|
var config = { childList: true };
|
|
|
|
childListMutationObserver = new window.MutationObserver(noop);
|
|
childListMutationObserver.observe(element[0], config);
|
|
}
|
|
};
|
|
});
|
|
|
|
$provide.decorator('ngOptionsDirective', function($delegate) {
|
|
|
|
var origPreLink = $delegate[0].link.pre;
|
|
var origPostLink = $delegate[0].link.post;
|
|
|
|
$delegate[0].compile = function() {
|
|
return {
|
|
pre: origPreLink,
|
|
post: function() {
|
|
linkLog.push('linkNgOptions');
|
|
origPostLink.apply(this, arguments);
|
|
}
|
|
};
|
|
};
|
|
|
|
return $delegate;
|
|
});
|
|
}));
|
|
|
|
beforeEach(inject(function($rootScope, _$compile_) {
|
|
scope = $rootScope.$new(); //create a child scope because the root scope can't be $destroy-ed
|
|
$compile = _$compile_;
|
|
formElement = element = null;
|
|
}));
|
|
|
|
|
|
afterEach(function() {
|
|
scope.$destroy(); //disables unknown option work during destruction
|
|
dealoc(formElement);
|
|
ngModelCtrl = null;
|
|
});
|
|
|
|
function createSelect(attrs, blank, unknown) {
|
|
var html = '<select';
|
|
forEach(attrs, function(value, key) {
|
|
if (isBoolean(value)) {
|
|
if (value) html += ' ' + key;
|
|
} else {
|
|
html += ' ' + key + '="' + value + '"';
|
|
}
|
|
});
|
|
html += '>' +
|
|
(blank ? (isString(blank) ? blank : '<option value="">blank</option>') : '') +
|
|
(unknown ? (isString(unknown) ? unknown : '<option value="?">unknown</option>') : '') +
|
|
'</select>';
|
|
|
|
compile(html);
|
|
}
|
|
|
|
function createSingleSelect(blank, unknown) {
|
|
createSelect({
|
|
'ng-model':'selected',
|
|
'ng-options':'value.name for value in values'
|
|
}, blank, unknown);
|
|
}
|
|
|
|
function createMultiSelect(blank, unknown) {
|
|
createSelect({
|
|
'ng-model':'selected',
|
|
'multiple':true,
|
|
'ng-options':'value.name for value in values'
|
|
}, blank, unknown);
|
|
}
|
|
|
|
|
|
it('should throw when not formated "? for ? in ?"', function() {
|
|
expect(function() {
|
|
compile('<select ng-model="selected" ng-options="i dont parse"></select>');
|
|
}).toThrowMinErr('ngOptions', 'iexp', /Expected expression in form of/);
|
|
});
|
|
|
|
|
|
it('should have a dependency on ngModel', function() {
|
|
expect(function() {
|
|
compile('<select ng-options="item in items"></select>');
|
|
}).toThrow();
|
|
});
|
|
|
|
|
|
it('should render a list', function() {
|
|
createSingleSelect();
|
|
|
|
scope.$apply(function() {
|
|
scope.values = [{name: 'A'}, {name: 'B'}, {name: 'C'}];
|
|
scope.selected = scope.values[1];
|
|
});
|
|
|
|
var options = element.find('option');
|
|
expect(options.length).toEqual(3);
|
|
expect(options.eq(0)).toEqualOption(scope.values[0], 'A');
|
|
expect(options.eq(1)).toEqualOption(scope.values[1], 'B');
|
|
expect(options.eq(2)).toEqualOption(scope.values[2], 'C');
|
|
expect(options[1].selected).toEqual(true);
|
|
});
|
|
|
|
|
|
it('should not include properties with non-numeric keys in array-like collections when using array syntax', function() {
|
|
createSelect({
|
|
'ng-model':'selected',
|
|
'ng-options':'value for value in values'
|
|
});
|
|
|
|
scope.$apply(function() {
|
|
scope.values = { 0: 'X', 1: 'Y', 2: 'Z', 'a': 'A', length: 3};
|
|
scope.selected = scope.values[1];
|
|
});
|
|
|
|
var options = element.find('option');
|
|
expect(options.length).toEqual(3);
|
|
expect(options.eq(0)).toEqualOption('X');
|
|
expect(options.eq(1)).toEqualOption('Y');
|
|
expect(options.eq(2)).toEqualOption('Z');
|
|
|
|
});
|
|
|
|
|
|
it('should include properties with non-numeric keys in array-like collections when using object syntax', function() {
|
|
createSelect({
|
|
'ng-model':'selected',
|
|
'ng-options':'value for (key, value) in values'
|
|
});
|
|
|
|
scope.$apply(function() {
|
|
scope.values = { 0: 'X', 1: 'Y', 2: 'Z', 'a': 'A', length: 3};
|
|
scope.selected = scope.values[1];
|
|
});
|
|
|
|
var options = element.find('option');
|
|
expect(options.length).toEqual(5);
|
|
expect(options.eq(0)).toEqualOption('X');
|
|
expect(options.eq(1)).toEqualOption('Y');
|
|
expect(options.eq(2)).toEqualOption('Z');
|
|
expect(options.eq(3)).toEqualOption('A');
|
|
expect(options.eq(4)).toEqualOption(3);
|
|
});
|
|
|
|
|
|
it('should render an object', function() {
|
|
createSelect({
|
|
'ng-model': 'selected',
|
|
'ng-options': 'value as key for (key, value) in object'
|
|
});
|
|
|
|
scope.$apply(function() {
|
|
scope.object = {'red': 'FF0000', 'green': '00FF00', 'blue': '0000FF'};
|
|
scope.selected = scope.object.green;
|
|
});
|
|
|
|
var options = element.find('option');
|
|
expect(options.length).toEqual(3);
|
|
expect(options.eq(0)).toEqualOption('FF0000', 'red');
|
|
expect(options.eq(1)).toEqualOption('00FF00', 'green');
|
|
expect(options.eq(2)).toEqualOption('0000FF', 'blue');
|
|
expect(options[1].selected).toEqual(true);
|
|
|
|
scope.$apply('object.azur = "8888FF"');
|
|
|
|
options = element.find('option');
|
|
expect(options[1].selected).toEqual(true);
|
|
|
|
scope.$apply('selected = object.azur');
|
|
|
|
options = element.find('option');
|
|
expect(options[3].selected).toEqual(true);
|
|
|
|
});
|
|
|
|
it('should set the "selected" attribute and property on selected options', function() {
|
|
scope.values = [{
|
|
id: 'FF0000',
|
|
display: 'red'
|
|
}, {
|
|
id: '0000FF',
|
|
display: 'blue'
|
|
}];
|
|
scope.selected = 'FF0000';
|
|
|
|
createSelect({
|
|
'ng-model': 'selected',
|
|
'ng-options': 'option.id as option.display for option in values'
|
|
});
|
|
scope.$digest();
|
|
|
|
var options = element.find('option');
|
|
expect(options.length).toEqual(2);
|
|
expect(options.eq(0)).toEqualOption('FF0000', 'red');
|
|
expect(options.eq(1)).toEqualOption('0000FF', 'blue');
|
|
|
|
expect(options.eq(0)[0].getAttribute('selected')).toBe('selected');
|
|
expect(options.eq(0).attr('selected')).toBe('selected');
|
|
expect(options.eq(0)[0].selected).toBe(true);
|
|
expect(options.eq(0).prop('selected')).toBe(true);
|
|
|
|
scope.selected = '0000FF';
|
|
scope.$digest();
|
|
|
|
expect(options.eq(1)[0].getAttribute('selected')).toBe('selected');
|
|
expect(options.eq(1).attr('selected')).toBe('selected');
|
|
expect(options.eq(1)[0].selected).toBe(true);
|
|
expect(options.eq(1).prop('selected')).toBe(true);
|
|
});
|
|
|
|
it('should render zero as a valid display value', function() {
|
|
createSingleSelect();
|
|
|
|
scope.$apply(function() {
|
|
scope.values = [{name: 0}, {name: 1}, {name: 2}];
|
|
scope.selected = scope.values[0];
|
|
});
|
|
|
|
var options = element.find('option');
|
|
expect(options.length).toEqual(3);
|
|
expect(options.eq(0)).toEqualOption(scope.values[0], '0');
|
|
expect(options.eq(1)).toEqualOption(scope.values[1], '1');
|
|
expect(options.eq(2)).toEqualOption(scope.values[2], '2');
|
|
});
|
|
|
|
|
|
it('should not be set when an option is selected and options are set asynchronously',
|
|
inject(function($timeout) {
|
|
compile('<select ng-model="model" ng-options="opt.id as opt.label for opt in options">' +
|
|
'</select>');
|
|
|
|
scope.$apply(function() {
|
|
scope.model = 0;
|
|
});
|
|
|
|
$timeout(function() {
|
|
scope.options = [
|
|
{id: 0, label: 'x'},
|
|
{id: 1, label: 'y'}
|
|
];
|
|
}, 0);
|
|
|
|
$timeout.flush();
|
|
|
|
var options = element.find('option');
|
|
|
|
expect(options.length).toEqual(2);
|
|
expect(options.eq(0)).toEqualOption(0, 'x');
|
|
expect(options.eq(1)).toEqualOption(1, 'y');
|
|
})
|
|
);
|
|
|
|
|
|
it('should grow list', function() {
|
|
createSingleSelect();
|
|
|
|
scope.$apply(function() {
|
|
scope.values = [];
|
|
});
|
|
|
|
expect(element.find('option').length).toEqual(1); // because we add special unknown option
|
|
expect(element.find('option').eq(0)).toEqualUnknownOption();
|
|
|
|
scope.$apply(function() {
|
|
scope.values.push({name:'A'});
|
|
scope.selected = scope.values[0];
|
|
});
|
|
|
|
expect(element.find('option').length).toEqual(1);
|
|
expect(element.find('option')).toEqualOption(scope.values[0], 'A');
|
|
|
|
scope.$apply(function() {
|
|
scope.values.push({name:'B'});
|
|
});
|
|
|
|
expect(element.find('option').length).toEqual(2);
|
|
expect(element.find('option').eq(0)).toEqualOption(scope.values[0], 'A');
|
|
expect(element.find('option').eq(1)).toEqualOption(scope.values[1], 'B');
|
|
});
|
|
|
|
|
|
it('should shrink list', function() {
|
|
createSingleSelect();
|
|
|
|
scope.$apply(function() {
|
|
scope.values = [{name:'A'}, {name:'B'}, {name:'C'}];
|
|
scope.selected = scope.values[0];
|
|
});
|
|
|
|
expect(element.find('option').length).toEqual(3);
|
|
|
|
scope.$apply(function() {
|
|
scope.values.pop();
|
|
});
|
|
|
|
expect(element.find('option').length).toEqual(2);
|
|
expect(element.find('option').eq(0)).toEqualOption(scope.values[0], 'A');
|
|
expect(element.find('option').eq(1)).toEqualOption(scope.values[1], 'B');
|
|
|
|
scope.$apply(function() {
|
|
scope.values.pop();
|
|
});
|
|
|
|
expect(element.find('option').length).toEqual(1);
|
|
expect(element.find('option')).toEqualOption(scope.values[0], 'A');
|
|
|
|
scope.$apply(function() {
|
|
scope.values.pop();
|
|
scope.selected = null;
|
|
});
|
|
|
|
expect(element.find('option').length).toEqual(1); // we add back the special empty option
|
|
});
|
|
|
|
|
|
it('should shrink and then grow list', function() {
|
|
createSingleSelect();
|
|
|
|
scope.$apply(function() {
|
|
scope.values = [{name:'A'}, {name:'B'}, {name:'C'}];
|
|
scope.selected = scope.values[0];
|
|
});
|
|
|
|
expect(element.find('option').length).toEqual(3);
|
|
|
|
scope.$apply(function() {
|
|
scope.values = [{name: '1'}, {name: '2'}];
|
|
scope.selected = scope.values[0];
|
|
});
|
|
|
|
expect(element.find('option').length).toEqual(2);
|
|
|
|
scope.$apply(function() {
|
|
scope.values = [{name: 'A'}, {name: 'B'}, {name: 'C'}];
|
|
scope.selected = scope.values[0];
|
|
});
|
|
|
|
expect(element.find('option').length).toEqual(3);
|
|
});
|
|
|
|
|
|
it('should update list', function() {
|
|
createSingleSelect();
|
|
|
|
scope.$apply(function() {
|
|
scope.values = [{name: 'A'}, {name: 'B'}, {name: 'C'}];
|
|
scope.selected = scope.values[0];
|
|
});
|
|
|
|
scope.$apply(function() {
|
|
scope.values = [{name: 'B'}, {name: 'C'}, {name: 'D'}];
|
|
scope.selected = scope.values[0];
|
|
});
|
|
|
|
var options = element.find('option');
|
|
expect(options.length).toEqual(3);
|
|
expect(options.eq(0)).toEqualOption(scope.values[0], 'B');
|
|
expect(options.eq(1)).toEqualOption(scope.values[1], 'C');
|
|
expect(options.eq(2)).toEqualOption(scope.values[2], 'D');
|
|
});
|
|
|
|
|
|
it('should preserve pre-existing empty option', function() {
|
|
createSingleSelect(true);
|
|
|
|
scope.$apply(function() {
|
|
scope.values = [];
|
|
});
|
|
expect(element.find('option').length).toEqual(1);
|
|
|
|
scope.$apply(function() {
|
|
scope.values = [{name: 'A'}];
|
|
scope.selected = scope.values[0];
|
|
});
|
|
|
|
expect(element.find('option').length).toEqual(2);
|
|
expect(jqLite(element.find('option')[0]).text()).toEqual('blank');
|
|
expect(jqLite(element.find('option')[1]).text()).toEqual('A');
|
|
|
|
scope.$apply(function() {
|
|
scope.values = [];
|
|
scope.selected = null;
|
|
});
|
|
|
|
expect(element.find('option').length).toEqual(1);
|
|
expect(jqLite(element.find('option')[0]).text()).toEqual('blank');
|
|
});
|
|
|
|
|
|
it('should ignore $ and $$ properties', function() {
|
|
createSelect({
|
|
'ng-options': 'key as value for (key, value) in object',
|
|
'ng-model': 'selected'
|
|
});
|
|
|
|
scope.$apply(function() {
|
|
scope.object = {'regularProperty': 'visible', '$$private': 'invisible', '$property': 'invisible'};
|
|
scope.selected = 'regularProperty';
|
|
});
|
|
|
|
var options = element.find('option');
|
|
expect(options.length).toEqual(1);
|
|
expect(options.eq(0)).toEqualOption('regularProperty', 'visible');
|
|
});
|
|
|
|
|
|
it('should not watch non-numeric array properties', function() {
|
|
createSelect({
|
|
'ng-options': 'value as createLabel(value) for value in array',
|
|
'ng-model': 'selected'
|
|
});
|
|
scope.createLabel = jasmine.createSpy('createLabel').and.callFake(function(value) { return value; });
|
|
scope.array = ['a', 'b', 'c'];
|
|
scope.array.$$private = 'do not watch';
|
|
scope.array.$property = 'do not watch';
|
|
scope.array.other = 'do not watch';
|
|
scope.array.fn = function() {};
|
|
scope.selected = 'b';
|
|
scope.$digest();
|
|
|
|
expect(scope.createLabel).toHaveBeenCalledWith('a');
|
|
expect(scope.createLabel).toHaveBeenCalledWith('b');
|
|
expect(scope.createLabel).toHaveBeenCalledWith('c');
|
|
expect(scope.createLabel).not.toHaveBeenCalledWith('do not watch');
|
|
expect(scope.createLabel).not.toHaveBeenCalledWith(jasmine.any(Function));
|
|
});
|
|
|
|
|
|
it('should not watch object properties that start with $ or $$', function() {
|
|
createSelect({
|
|
'ng-options': 'key as createLabel(key) for (key, value) in object',
|
|
'ng-model': 'selected'
|
|
});
|
|
scope.createLabel = jasmine.createSpy('createLabel').and.callFake(function(value) { return value; });
|
|
scope.object = {'regularProperty': 'visible', '$$private': 'invisible', '$property': 'invisible'};
|
|
scope.selected = 'regularProperty';
|
|
scope.$digest();
|
|
|
|
expect(scope.createLabel).toHaveBeenCalledWith('regularProperty');
|
|
expect(scope.createLabel).not.toHaveBeenCalledWith('$$private');
|
|
expect(scope.createLabel).not.toHaveBeenCalledWith('$property');
|
|
});
|
|
|
|
it('should allow expressions over multiple lines', function() {
|
|
scope.isNotFoo = function(item) {
|
|
return item.name !== 'Foo';
|
|
};
|
|
|
|
createSelect({
|
|
'ng-options': 'key.id\n' +
|
|
'for key in values\n' +
|
|
'| filter:isNotFoo',
|
|
'ng-model': 'selected'
|
|
});
|
|
|
|
scope.$apply(function() {
|
|
scope.values = [{'id': 1, 'name': 'Foo'},
|
|
{'id': 2, 'name': 'Bar'},
|
|
{'id': 3, 'name': 'Baz'}];
|
|
scope.selected = scope.values[0];
|
|
});
|
|
|
|
var options = element.find('option');
|
|
expect(options.length).toEqual(3);
|
|
expect(options.eq(1)).toEqualOption(scope.values[1], '2');
|
|
expect(options.eq(2)).toEqualOption(scope.values[2], '3');
|
|
});
|
|
|
|
|
|
it('should not update selected property of an option element on digest with no change event',
|
|
function() {
|
|
// ng-options="value.name for value in values"
|
|
// ng-model="selected"
|
|
createSingleSelect();
|
|
|
|
scope.$apply(function() {
|
|
scope.values = [{name: 'A'}, {name: 'B'}, {name: 'C'}];
|
|
scope.selected = scope.values[0];
|
|
});
|
|
|
|
var options = element.find('option');
|
|
|
|
expect(scope.selected).toEqual(jasmine.objectContaining({ name: 'A' }));
|
|
expect(options.eq(0).prop('selected')).toBe(true);
|
|
expect(options.eq(1).prop('selected')).toBe(false);
|
|
|
|
var optionToSelect = options.eq(1);
|
|
|
|
expect(optionToSelect.text()).toBe('B');
|
|
|
|
optionToSelect.prop('selected', true);
|
|
scope.$digest();
|
|
|
|
expect(optionToSelect.prop('selected')).toBe(true);
|
|
expect(scope.selected).toBe(scope.values[0]);
|
|
});
|
|
|
|
|
|
// bug fix #9621
|
|
it('should update the label property', function() {
|
|
// ng-options="value.name for value in values"
|
|
// ng-model="selected"
|
|
createSingleSelect();
|
|
|
|
scope.$apply(function() {
|
|
scope.values = [{name: 'A'}, {name: 'B'}, {name: 'C'}];
|
|
scope.selected = scope.values[0];
|
|
});
|
|
|
|
var options = element.find('option');
|
|
expect(options.eq(0).prop('label')).toEqual('A');
|
|
expect(options.eq(1).prop('label')).toEqual('B');
|
|
expect(options.eq(2).prop('label')).toEqual('C');
|
|
});
|
|
|
|
|
|
it('should update the label if only the property has changed', function() {
|
|
// ng-options="value.name for value in values"
|
|
// ng-model="selected"
|
|
createSingleSelect();
|
|
|
|
scope.$apply(function() {
|
|
scope.values = [{name: 'A'}, {name: 'B'}, {name: 'C'}];
|
|
scope.selected = scope.values[0];
|
|
});
|
|
|
|
var options = element.find('option');
|
|
expect(options.eq(0).prop('label')).toEqual('A');
|
|
expect(options.eq(1).prop('label')).toEqual('B');
|
|
expect(options.eq(2).prop('label')).toEqual('C');
|
|
|
|
|
|
scope.$apply('values[0].name = "X"');
|
|
|
|
options = element.find('option');
|
|
expect(options.eq(0).prop('label')).toEqual('X');
|
|
|
|
});
|
|
|
|
|
|
// bug fix #9714
|
|
it('should select the matching option when the options are updated', function() {
|
|
|
|
// first set up a select with no options
|
|
scope.selected = '';
|
|
createSelect({
|
|
'ng-options': 'val.id as val.label for val in values',
|
|
'ng-model': 'selected'
|
|
});
|
|
var options = element.find('option');
|
|
// we expect the selected option to be the "unknown" option
|
|
expect(options.eq(0)).toEqualUnknownOption('');
|
|
expect(options.eq(0).prop('selected')).toEqual(true);
|
|
|
|
// now add some real options - one of which matches the selected value
|
|
scope.$apply('values = [{id:"",label:"A"},{id:"1",label:"B"},{id:"2",label:"C"}]');
|
|
|
|
// we expect the selected option to be the one that matches the correct item
|
|
// and for the unknown option to have been removed
|
|
options = element.find('option');
|
|
expect(element).toEqualSelectValue('');
|
|
expect(options.eq(0)).toEqualOption('','A');
|
|
});
|
|
|
|
|
|
|
|
it('should be possible to use one-time binding on the expression', function() {
|
|
createSelect({
|
|
'ng-model': 'someModel',
|
|
'ng-options': 'o as o for o in ::arr'
|
|
});
|
|
|
|
var options;
|
|
|
|
// Initially the options list is just the unknown option
|
|
options = element.find('option');
|
|
expect(options.length).toEqual(1);
|
|
|
|
// Now initialize the scope and the options should be updated
|
|
scope.$apply(function() {
|
|
scope.arr = ['a','b','c'];
|
|
});
|
|
options = element.find('option');
|
|
expect(options.length).toEqual(4);
|
|
expect(options.eq(0)).toEqualUnknownOption();
|
|
expect(options.eq(1)).toEqualOption('a');
|
|
expect(options.eq(2)).toEqualOption('b');
|
|
expect(options.eq(3)).toEqualOption('c');
|
|
|
|
// Change the scope but the options should not change
|
|
scope.arr = ['w', 'x', 'y', 'z'];
|
|
scope.$digest();
|
|
options = element.find('option');
|
|
expect(options.length).toEqual(4);
|
|
expect(options.eq(0)).toEqualUnknownOption();
|
|
expect(options.eq(1)).toEqualOption('a');
|
|
expect(options.eq(2)).toEqualOption('b');
|
|
expect(options.eq(3)).toEqualOption('c');
|
|
});
|
|
|
|
|
|
it('should remove the "selected" attribute from the previous option when the model changes', function() {
|
|
scope.values = [{id: 10, label: 'ten'}, {id:20, label: 'twenty'}];
|
|
|
|
createSelect({
|
|
'ng-model': 'selected',
|
|
'ng-options': 'item.label for item in values'
|
|
}, true);
|
|
|
|
var options = element.find('option');
|
|
expect(options[0]).toBeMarkedAsSelected();
|
|
expect(options[1]).not.toBeMarkedAsSelected();
|
|
expect(options[2]).not.toBeMarkedAsSelected();
|
|
|
|
scope.selected = scope.values[0];
|
|
scope.$digest();
|
|
|
|
expect(options[0]).not.toBeMarkedAsSelected();
|
|
expect(options[1]).toBeMarkedAsSelected();
|
|
expect(options[2]).not.toBeMarkedAsSelected();
|
|
|
|
scope.selected = scope.values[1];
|
|
scope.$digest();
|
|
|
|
expect(options[0]).not.toBeMarkedAsSelected();
|
|
expect(options[1]).not.toBeMarkedAsSelected();
|
|
expect(options[2]).toBeMarkedAsSelected();
|
|
|
|
// This will select the empty option
|
|
scope.selected = null;
|
|
scope.$digest();
|
|
|
|
expect(options[0]).toBeMarkedAsSelected();
|
|
expect(options[1]).not.toBeMarkedAsSelected();
|
|
expect(options[2]).not.toBeMarkedAsSelected();
|
|
|
|
// This will add and select the unknown option
|
|
scope.selected = 'unmatched value';
|
|
scope.$digest();
|
|
options = element.find('option');
|
|
|
|
expect(options[0]).toBeMarkedAsSelected();
|
|
expect(options[1]).not.toBeMarkedAsSelected();
|
|
expect(options[2]).not.toBeMarkedAsSelected();
|
|
expect(options[3]).not.toBeMarkedAsSelected();
|
|
|
|
// Back to matched value
|
|
scope.selected = scope.values[1];
|
|
scope.$digest();
|
|
options = element.find('option');
|
|
|
|
expect(options[0]).not.toBeMarkedAsSelected();
|
|
expect(options[1]).not.toBeMarkedAsSelected();
|
|
expect(options[2]).toBeMarkedAsSelected();
|
|
});
|
|
|
|
|
|
if (window.MutationObserver) {
|
|
//IE9 and IE10 do not support MutationObserver
|
|
//Since the feature is only needed for a test, it's okay to skip these browsers
|
|
it('should render the initial options only one time', function() {
|
|
scope.value = 'black';
|
|
scope.values = ['black', 'white', 'red'];
|
|
// observe-child-list adds a MutationObserver that we will read out after ngOptions
|
|
// has been compiled
|
|
createSelect({
|
|
'ng-model':'value',
|
|
'ng-options':'value.name for value in values',
|
|
'observe-child-list': ''
|
|
});
|
|
|
|
var optionEls = element[0].querySelectorAll('option');
|
|
var records = childListMutationObserver.takeRecords();
|
|
|
|
expect(records.length).toBe(1);
|
|
expect(records[0].addedNodes).toEqual(optionEls);
|
|
});
|
|
}
|
|
|
|
describe('disableWhen expression', function() {
|
|
|
|
describe('on single select', function() {
|
|
|
|
it('should disable options', function() {
|
|
|
|
scope.selected = '';
|
|
scope.options = [
|
|
{ name: 'white', value: '#FFFFFF' },
|
|
{ name: 'one', value: 1, unavailable: true },
|
|
{ name: 'notTrue', value: false },
|
|
{ name: 'thirty', value: 30, unavailable: false }
|
|
];
|
|
createSelect({
|
|
'ng-options': 'o.value as o.name disable when o.unavailable for o in options',
|
|
'ng-model': 'selected'
|
|
});
|
|
var options = element.find('option');
|
|
|
|
expect(options.length).toEqual(5);
|
|
expect(options.eq(1).prop('disabled')).toEqual(false);
|
|
expect(options.eq(2).prop('disabled')).toEqual(true);
|
|
expect(options.eq(3).prop('disabled')).toEqual(false);
|
|
expect(options.eq(4).prop('disabled')).toEqual(false);
|
|
});
|
|
|
|
|
|
it('should select disabled options when model changes', function() {
|
|
scope.options = [
|
|
{ name: 'white', value: '#FFFFFF' },
|
|
{ name: 'one', value: 1, unavailable: true },
|
|
{ name: 'notTrue', value: false },
|
|
{ name: 'thirty', value: 30, unavailable: false }
|
|
];
|
|
createSelect({
|
|
'ng-options': 'o.value as o.name disable when o.unavailable for o in options',
|
|
'ng-model': 'selected'
|
|
});
|
|
|
|
// Initially the model is set to an enabled option
|
|
scope.$apply('selected = 30');
|
|
var options = element.find('option');
|
|
expect(options.eq(3).prop('selected')).toEqual(true);
|
|
|
|
// Now set the model to a disabled option
|
|
scope.$apply('selected = 1');
|
|
options = element.find('option');
|
|
|
|
// jQuery returns null for val() when the option is disabled, see
|
|
// https://bugs.jquery.com/ticket/13097
|
|
expect(element[0].value).toBe('number:1');
|
|
expect(options.length).toEqual(4);
|
|
expect(options.eq(0).prop('selected')).toEqual(false);
|
|
expect(options.eq(1).prop('selected')).toEqual(true);
|
|
expect(options.eq(2).prop('selected')).toEqual(false);
|
|
expect(options.eq(3).prop('selected')).toEqual(false);
|
|
});
|
|
|
|
|
|
it('should select options in model when they become enabled', function() {
|
|
scope.options = [
|
|
{ name: 'white', value: '#FFFFFF' },
|
|
{ name: 'one', value: 1, unavailable: true },
|
|
{ name: 'notTrue', value: false },
|
|
{ name: 'thirty', value: 30, unavailable: false }
|
|
];
|
|
createSelect({
|
|
'ng-options': 'o.value as o.name disable when o.unavailable for o in options',
|
|
'ng-model': 'selected'
|
|
});
|
|
|
|
// Set the model to a disabled option
|
|
scope.$apply('selected = 1');
|
|
var options = element.find('option');
|
|
|
|
// jQuery returns null for val() when the option is disabled, see
|
|
// https://bugs.jquery.com/ticket/13097
|
|
expect(element[0].value).toBe('number:1');
|
|
expect(options.length).toEqual(4);
|
|
expect(options.eq(0).prop('selected')).toEqual(false);
|
|
expect(options.eq(1).prop('selected')).toEqual(true);
|
|
expect(options.eq(2).prop('selected')).toEqual(false);
|
|
expect(options.eq(3).prop('selected')).toEqual(false);
|
|
|
|
// Now enable that option
|
|
scope.$apply(function() {
|
|
scope.options[1].unavailable = false;
|
|
});
|
|
|
|
expect(element).toEqualSelectValue(1);
|
|
options = element.find('option');
|
|
expect(options.length).toEqual(4);
|
|
expect(options.eq(1).prop('selected')).toEqual(true);
|
|
expect(options.eq(3).prop('selected')).toEqual(false);
|
|
});
|
|
});
|
|
|
|
|
|
describe('on multi select', function() {
|
|
|
|
it('should disable options', function() {
|
|
|
|
scope.selected = [];
|
|
scope.options = [
|
|
{ name: 'a', value: 0 },
|
|
{ name: 'b', value: 1, unavailable: true },
|
|
{ name: 'c', value: 2 },
|
|
{ name: 'd', value: 3, unavailable: false }
|
|
];
|
|
createSelect({
|
|
'ng-options': 'o.value as o.name disable when o.unavailable for o in options',
|
|
'multiple': true,
|
|
'ng-model': 'selected'
|
|
});
|
|
var options = element.find('option');
|
|
|
|
expect(options.eq(0).prop('disabled')).toEqual(false);
|
|
expect(options.eq(1).prop('disabled')).toEqual(true);
|
|
expect(options.eq(2).prop('disabled')).toEqual(false);
|
|
expect(options.eq(3).prop('disabled')).toEqual(false);
|
|
});
|
|
|
|
|
|
it('should select disabled options when model changes', function() {
|
|
scope.options = [
|
|
{ name: 'a', value: 0 },
|
|
{ name: 'b', value: 1, unavailable: true },
|
|
{ name: 'c', value: 2 },
|
|
{ name: 'd', value: 3, unavailable: false }
|
|
];
|
|
createSelect({
|
|
'ng-options': 'o.value as o.name disable when o.unavailable for o in options',
|
|
'multiple': true,
|
|
'ng-model': 'selected'
|
|
});
|
|
|
|
// Initially the model is set to an enabled option
|
|
scope.$apply('selected = [3]');
|
|
var options = element.find('option');
|
|
expect(options.eq(0).prop('selected')).toEqual(false);
|
|
expect(options.eq(1).prop('selected')).toEqual(false);
|
|
expect(options.eq(2).prop('selected')).toEqual(false);
|
|
expect(options.eq(3).prop('selected')).toEqual(true);
|
|
|
|
// Now add a disabled option
|
|
scope.$apply('selected = [1,3]');
|
|
options = element.find('option');
|
|
expect(options.eq(0).prop('selected')).toEqual(false);
|
|
expect(options.eq(1).prop('selected')).toEqual(true);
|
|
expect(options.eq(2).prop('selected')).toEqual(false);
|
|
expect(options.eq(3).prop('selected')).toEqual(true);
|
|
|
|
// Now only select the disabled option
|
|
scope.$apply('selected = [1]');
|
|
expect(options.eq(0).prop('selected')).toEqual(false);
|
|
expect(options.eq(1).prop('selected')).toEqual(true);
|
|
expect(options.eq(2).prop('selected')).toEqual(false);
|
|
expect(options.eq(3).prop('selected')).toEqual(false);
|
|
});
|
|
|
|
|
|
it('should select options in model when they become enabled', function() {
|
|
scope.options = [
|
|
{ name: 'a', value: 0 },
|
|
{ name: 'b', value: 1, unavailable: true },
|
|
{ name: 'c', value: 2 },
|
|
{ name: 'd', value: 3, unavailable: false }
|
|
];
|
|
createSelect({
|
|
'ng-options': 'o.value as o.name disable when o.unavailable for o in options',
|
|
'multiple': true,
|
|
'ng-model': 'selected'
|
|
});
|
|
|
|
// Set the model to a disabled option
|
|
scope.$apply('selected = [1]');
|
|
var options = element.find('option');
|
|
|
|
expect(options.eq(0).prop('selected')).toEqual(false);
|
|
expect(options.eq(1).prop('selected')).toEqual(true);
|
|
expect(options.eq(2).prop('selected')).toEqual(false);
|
|
expect(options.eq(3).prop('selected')).toEqual(false);
|
|
|
|
// Now enable that option
|
|
scope.$apply(function() {
|
|
scope.options[1].unavailable = false;
|
|
});
|
|
|
|
expect(element).toEqualSelectValue([1], true);
|
|
options = element.find('option');
|
|
expect(options.eq(0).prop('selected')).toEqual(false);
|
|
expect(options.eq(1).prop('selected')).toEqual(true);
|
|
expect(options.eq(2).prop('selected')).toEqual(false);
|
|
expect(options.eq(3).prop('selected')).toEqual(false);
|
|
});
|
|
});
|
|
});
|
|
|
|
|
|
describe('selectAs expression', function() {
|
|
beforeEach(function() {
|
|
scope.arr = [{id: 10, label: 'ten'}, {id:20, label: 'twenty'}];
|
|
scope.obj = {'10': {score: 10, label: 'ten'}, '20': {score: 20, label: 'twenty'}};
|
|
});
|
|
|
|
it('should support single select with array source', function() {
|
|
createSelect({
|
|
'ng-model': 'selected',
|
|
'ng-options': 'item.id as item.label for item in arr'
|
|
});
|
|
|
|
scope.$apply(function() {
|
|
scope.selected = 10;
|
|
});
|
|
expect(element).toEqualSelectValue(10);
|
|
|
|
setSelectValue(element, 1);
|
|
expect(scope.selected).toBe(20);
|
|
});
|
|
|
|
|
|
it('should support multi select with array source', function() {
|
|
createSelect({
|
|
'ng-model': 'selected',
|
|
'multiple': true,
|
|
'ng-options': 'item.id as item.label for item in arr'
|
|
});
|
|
|
|
scope.$apply(function() {
|
|
scope.selected = [10,20];
|
|
});
|
|
expect(element).toEqualSelectValue([10,20], true);
|
|
expect(scope.selected).toEqual([10,20]);
|
|
|
|
element.children()[0].selected = false;
|
|
browserTrigger(element, 'change');
|
|
expect(scope.selected).toEqual([20]);
|
|
expect(element).toEqualSelectValue([20], true);
|
|
});
|
|
|
|
|
|
it('should re-render if an item in an array source is added/removed', function() {
|
|
createSelect({
|
|
'ng-model': 'selected',
|
|
'multiple': true,
|
|
'ng-options': 'item.id as item.label for item in arr'
|
|
});
|
|
|
|
scope.$apply(function() {
|
|
scope.selected = [10];
|
|
});
|
|
expect(element).toEqualSelectValue([10], true);
|
|
|
|
scope.$apply(function() {
|
|
scope.selected.push(20);
|
|
});
|
|
expect(element).toEqualSelectValue([10, 20], true);
|
|
|
|
|
|
scope.$apply(function() {
|
|
scope.selected.shift();
|
|
});
|
|
expect(element).toEqualSelectValue([20], true);
|
|
});
|
|
|
|
|
|
it('should handle a options containing circular references', function() {
|
|
scope.arr[0].ref = scope.arr[0];
|
|
scope.selected = [scope.arr[0]];
|
|
createSelect({
|
|
'ng-model': 'selected',
|
|
'multiple': true,
|
|
'ng-options': 'item as item.label for item in arr'
|
|
});
|
|
expect(element).toEqualSelectValue([scope.arr[0]], true);
|
|
|
|
scope.$apply(function() {
|
|
scope.selected.push(scope.arr[1]);
|
|
});
|
|
expect(element).toEqualSelectValue([scope.arr[0], scope.arr[1]], true);
|
|
|
|
|
|
scope.$apply(function() {
|
|
scope.selected.pop();
|
|
});
|
|
expect(element).toEqualSelectValue([scope.arr[0]], true);
|
|
});
|
|
|
|
|
|
it('should support single select with object source', function() {
|
|
createSelect({
|
|
'ng-model': 'selected',
|
|
'ng-options': 'val.score as val.label for (key, val) in obj'
|
|
});
|
|
|
|
scope.$apply(function() {
|
|
scope.selected = 10;
|
|
});
|
|
expect(element).toEqualSelectValue(10);
|
|
|
|
setSelectValue(element, 1);
|
|
expect(scope.selected).toBe(20);
|
|
});
|
|
|
|
|
|
it('should support multi select with object source', function() {
|
|
createSelect({
|
|
'ng-model': 'selected',
|
|
'multiple': true,
|
|
'ng-options': 'val.score as val.label for (key, val) in obj'
|
|
});
|
|
|
|
scope.$apply(function() {
|
|
scope.selected = [10,20];
|
|
});
|
|
expect(element).toEqualSelectValue([10,20], true);
|
|
|
|
element.children()[0].selected = false;
|
|
browserTrigger(element, 'change');
|
|
expect(scope.selected).toEqual([20]);
|
|
expect(element).toEqualSelectValue([20], true);
|
|
});
|
|
});
|
|
|
|
|
|
describe('trackBy expression', function() {
|
|
beforeEach(function() {
|
|
scope.arr = [{id: 10, label: 'ten'}, {id:20, label: 'twenty'}];
|
|
scope.obj = {'1': {score: 10, label: 'ten'}, '2': {score: 20, label: 'twenty'}};
|
|
});
|
|
|
|
|
|
it('should set the result of track by expression to element value', function() {
|
|
createSelect({
|
|
'ng-model': 'selected',
|
|
'ng-options': 'item.label for item in arr track by item.id'
|
|
});
|
|
|
|
expect(element.val()).toEqualUnknownValue();
|
|
|
|
scope.$apply(function() {
|
|
scope.selected = scope.arr[0];
|
|
});
|
|
expect(element.val()).toBe('10');
|
|
|
|
scope.$apply(function() {
|
|
scope.arr[0] = {id: 10, label: 'new ten'};
|
|
});
|
|
expect(element.val()).toBe('10');
|
|
|
|
element.children()[1].selected = 'selected';
|
|
browserTrigger(element, 'change');
|
|
expect(scope.selected).toEqual(scope.arr[1]);
|
|
});
|
|
|
|
|
|
it('should use the tracked expression as option value', function() {
|
|
createSelect({
|
|
'ng-model': 'selected',
|
|
'ng-options': 'item.label for item in arr track by item.id'
|
|
});
|
|
|
|
var options = element.find('option');
|
|
expect(options.length).toEqual(3);
|
|
expect(options.eq(0)).toEqualUnknownOption();
|
|
expect(options.eq(1)).toEqualTrackedOption(10, 'ten');
|
|
expect(options.eq(2)).toEqualTrackedOption(20, 'twenty');
|
|
});
|
|
|
|
|
|
it('should update the selected option even if only the tracked property on the selected object changes (single)', function() {
|
|
createSelect({
|
|
'ng-model': 'selected',
|
|
'ng-options': 'item.label for item in arr track by item.id'
|
|
});
|
|
|
|
scope.$apply(function() {
|
|
scope.selected = {id: 10, label: 'ten'};
|
|
});
|
|
|
|
expect(element.val()).toEqual('10');
|
|
|
|
// Update the properties on the selected object, rather than replacing the whole object
|
|
scope.$apply(function() {
|
|
scope.selected.id = 20;
|
|
scope.selected.label = 'new twenty';
|
|
});
|
|
|
|
// The value of the select should change since the id property changed
|
|
expect(element.val()).toEqual('20');
|
|
|
|
// But the label of the selected option does not change
|
|
var option = element.find('option').eq(1);
|
|
expect(option.prop('selected')).toEqual(true);
|
|
expect(option.text()).toEqual('twenty'); // not 'new twenty'
|
|
});
|
|
|
|
|
|
it('should update the selected options even if only the tracked properties on the objects in the ' +
|
|
'selected collection change (multi)', function() {
|
|
createSelect({
|
|
'ng-model': 'selected',
|
|
'multiple': true,
|
|
'ng-options': 'item.label for item in arr track by item.id'
|
|
});
|
|
|
|
scope.$apply(function() {
|
|
scope.selected = [{id: 10, label: 'ten'}];
|
|
});
|
|
|
|
expect(element.val()).toEqual(['10']);
|
|
|
|
// Update the tracked property on the object in the selected array, rather than replacing the whole object
|
|
scope.$apply(function() {
|
|
scope.selected[0].id = 20;
|
|
});
|
|
|
|
// The value of the select should change since the id property changed
|
|
expect(element.val()).toEqual(['20']);
|
|
|
|
// But the label of the selected option does not change
|
|
var option = element.find('option').eq(1);
|
|
expect(option.prop('selected')).toEqual(true);
|
|
expect(option.text()).toEqual('twenty'); // not 'new twenty'
|
|
});
|
|
|
|
|
|
it('should prevent changes to the selected object from modifying the options objects (single)', function() {
|
|
|
|
createSelect({
|
|
'ng-model': 'selected',
|
|
'ng-options': 'item.label for item in arr track by item.id'
|
|
});
|
|
|
|
element.val('10');
|
|
browserTrigger(element, 'change');
|
|
|
|
expect(scope.selected).toEqual(scope.arr[0]);
|
|
|
|
scope.$apply(function() {
|
|
scope.selected.id = 20;
|
|
});
|
|
|
|
expect(scope.selected).not.toEqual(scope.arr[0]);
|
|
expect(element.val()).toEqual('20');
|
|
expect(scope.arr).toEqual([{id: 10, label: 'ten'}, {id:20, label: 'twenty'}]);
|
|
});
|
|
|
|
|
|
it('should preserve value even when reference has changed (single&array)', function() {
|
|
createSelect({
|
|
'ng-model': 'selected',
|
|
'ng-options': 'item.label for item in arr track by item.id'
|
|
});
|
|
|
|
scope.$apply(function() {
|
|
scope.selected = scope.arr[0];
|
|
});
|
|
expect(element.val()).toBe('10');
|
|
|
|
scope.$apply(function() {
|
|
scope.arr[0] = {id: 10, label: 'new ten'};
|
|
});
|
|
expect(element.val()).toBe('10');
|
|
|
|
element.children()[1].selected = 1;
|
|
browserTrigger(element, 'change');
|
|
expect(scope.selected).toEqual(scope.arr[1]);
|
|
});
|
|
|
|
|
|
it('should preserve value even when reference has changed (multi&array)', function() {
|
|
createSelect({
|
|
'ng-model': 'selected',
|
|
'multiple': true,
|
|
'ng-options': 'item.label for item in arr track by item.id'
|
|
});
|
|
|
|
scope.$apply(function() {
|
|
scope.selected = scope.arr;
|
|
});
|
|
expect(element.val()).toEqual(['10','20']);
|
|
|
|
scope.$apply(function() {
|
|
scope.arr[0] = {id: 10, label: 'new ten'};
|
|
});
|
|
expect(element.val()).toEqual(['10','20']);
|
|
|
|
element.children()[0].selected = false;
|
|
browserTrigger(element, 'change');
|
|
expect(scope.selected).toEqual([scope.arr[1]]);
|
|
});
|
|
|
|
|
|
it('should preserve value even when reference has changed (single&object)', function() {
|
|
createSelect({
|
|
'ng-model': 'selected',
|
|
'ng-options': 'val.label for (key, val) in obj track by val.score'
|
|
});
|
|
|
|
scope.$apply(function() {
|
|
scope.selected = scope.obj['1'];
|
|
});
|
|
expect(element.val()).toBe('10');
|
|
|
|
scope.$apply(function() {
|
|
scope.obj['1'] = {score: 10, label: 'ten'};
|
|
});
|
|
expect(element.val()).toBe('10');
|
|
|
|
setSelectValue(element, 1);
|
|
expect(scope.selected).toEqual(scope.obj['2']);
|
|
});
|
|
|
|
|
|
it('should preserve value even when reference has changed (multi&object)', function() {
|
|
createSelect({
|
|
'ng-model': 'selected',
|
|
'multiple': true,
|
|
'ng-options': 'val.label for (key, val) in obj track by val.score'
|
|
});
|
|
|
|
scope.$apply(function() {
|
|
scope.selected = [scope.obj['1']];
|
|
});
|
|
expect(element.val()).toEqual(['10']);
|
|
|
|
scope.$apply(function() {
|
|
scope.obj['1'] = {score: 10, label: 'ten'};
|
|
});
|
|
expect(element.val()).toEqual(['10']);
|
|
|
|
element.children()[1].selected = 'selected';
|
|
browserTrigger(element, 'change');
|
|
expect(scope.selected).toEqual([scope.obj['1'], scope.obj['2']]);
|
|
});
|
|
|
|
it('should prevent infinite digest if track by expression is stable', function() {
|
|
scope.makeOptions = function() {
|
|
var options = [];
|
|
for (var i = 0; i < 5; i++) {
|
|
options.push({ label: 'Value = ' + i, value: i });
|
|
}
|
|
return options;
|
|
};
|
|
scope.selected = { label: 'Value = 1', value: 1 };
|
|
expect(function() {
|
|
createSelect({
|
|
'ng-model': 'selected',
|
|
'ng-options': 'item.label for item in makeOptions() track by item.value'
|
|
});
|
|
}).not.toThrow();
|
|
});
|
|
|
|
it('should re-render if the tracked property of the model is changed when using trackBy', function() {
|
|
|
|
createSelect({
|
|
'ng-model': 'selected',
|
|
'ng-options': 'item for item in arr track by item.id'
|
|
});
|
|
|
|
scope.$apply(function() {
|
|
scope.selected = {id: 10, label: 'ten'};
|
|
});
|
|
|
|
spyOn(element.controller('ngModel'), '$render');
|
|
|
|
scope.$apply(function() {
|
|
scope.arr[0].id = 20;
|
|
});
|
|
|
|
// update render due to equality watch
|
|
expect(element.controller('ngModel').$render).toHaveBeenCalled();
|
|
|
|
});
|
|
|
|
it('should not set view value again if the tracked property of the model has not changed when using trackBy', function() {
|
|
|
|
createSelect({
|
|
'ng-model': 'selected',
|
|
'ng-options': 'item for item in arr track by item.id'
|
|
});
|
|
|
|
scope.$apply(function() {
|
|
scope.selected = {id: 10, label: 'ten'};
|
|
});
|
|
|
|
spyOn(element.controller('ngModel'), '$setViewValue');
|
|
|
|
scope.$apply(function() {
|
|
scope.arr[0] = {id: 10, label: 'ten'};
|
|
});
|
|
|
|
expect(element.controller('ngModel').$setViewValue).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('should not re-render if a property of the model is changed when not using trackBy', function() {
|
|
|
|
createSelect({
|
|
'ng-model': 'selected',
|
|
'ng-options': 'item for item in arr'
|
|
});
|
|
|
|
scope.$apply(function() {
|
|
scope.selected = scope.arr[0];
|
|
});
|
|
|
|
spyOn(element.controller('ngModel'), '$render');
|
|
|
|
scope.$apply(function() {
|
|
scope.selected.label = 'changed';
|
|
});
|
|
|
|
// no render update as no equality watch
|
|
expect(element.controller('ngModel').$render).not.toHaveBeenCalled();
|
|
});
|
|
|
|
|
|
it('should handle options containing circular references (single)', function() {
|
|
scope.arr[0].ref = scope.arr[0];
|
|
createSelect({
|
|
'ng-model': 'selected',
|
|
'ng-options': 'item for item in arr track by item.id'
|
|
});
|
|
|
|
expect(function() {
|
|
scope.$apply(function() {
|
|
scope.selected = scope.arr[0];
|
|
});
|
|
}).not.toThrow();
|
|
});
|
|
|
|
|
|
it('should handle options containing circular references (multiple)', function() {
|
|
scope.arr[0].ref = scope.arr[0];
|
|
createSelect({
|
|
'ng-model': 'selected',
|
|
'multiple': true,
|
|
'ng-options': 'item for item in arr track by item.id'
|
|
});
|
|
|
|
expect(function() {
|
|
scope.$apply(function() {
|
|
scope.selected = [scope.arr[0]];
|
|
});
|
|
|
|
scope.$apply(function() {
|
|
scope.selected.push(scope.arr[1]);
|
|
});
|
|
}).not.toThrow();
|
|
});
|
|
|
|
it('should remove the "selected" attribute when the model changes', function() {
|
|
createSelect({
|
|
'ng-model': 'selected',
|
|
'ng-options': 'item.label for item in arr track by item.id'
|
|
});
|
|
|
|
var options = element.find('option');
|
|
browserTrigger(options[2], 'click');
|
|
|
|
expect(scope.selected).toEqual(scope.arr[1]);
|
|
|
|
scope.selected = {};
|
|
scope.$digest();
|
|
|
|
expect(options[0]).toBeMarkedAsSelected();
|
|
expect(options[2]).not.toBeMarkedAsSelected();
|
|
expect(options[2]).not.toBeMarkedAsSelected();
|
|
});
|
|
|
|
});
|
|
|
|
|
|
/**
|
|
* This behavior is broken and should probably be cleaned up later as track by and select as
|
|
* aren't compatible.
|
|
*/
|
|
describe('selectAs+trackBy expression', function() {
|
|
beforeEach(function() {
|
|
scope.arr = [{subItem: {label: 'ten', id: 10}}, {subItem: {label: 'twenty', id: 20}}];
|
|
scope.obj = {'10': {subItem: {id: 10, label: 'ten'}}, '20': {subItem: {id: 20, label: 'twenty'}}};
|
|
});
|
|
|
|
|
|
it('It should use the "value" variable to represent items in the array as well as for the ' +
|
|
'selected values in track by expression (single&array)', function() {
|
|
createSelect({
|
|
'ng-model': 'selected',
|
|
'ng-options': 'item.subItem as item.subItem.label for item in arr track by (item.id || item.subItem.id)'
|
|
});
|
|
|
|
// First test model -> view
|
|
|
|
scope.$apply(function() {
|
|
scope.selected = scope.arr[0].subItem;
|
|
});
|
|
expect(element.val()).toEqual('10');
|
|
|
|
scope.$apply(function() {
|
|
scope.selected = scope.arr[1].subItem;
|
|
});
|
|
expect(element.val()).toEqual('20');
|
|
|
|
// Now test view -> model
|
|
|
|
element.val('10');
|
|
browserTrigger(element, 'change');
|
|
expect(scope.selected).toEqual(scope.arr[0].subItem);
|
|
|
|
// Now reload the array
|
|
scope.$apply(function() {
|
|
scope.arr = [{
|
|
subItem: {label: 'new ten', id: 10}
|
|
},{
|
|
subItem: {label: 'new twenty', id: 20}
|
|
}];
|
|
});
|
|
expect(element.val()).toBe('10');
|
|
expect(scope.selected.id).toBe(10);
|
|
});
|
|
|
|
|
|
it('It should use the "value" variable to represent items in the array as well as for the ' +
|
|
'selected values in track by expression (multiple&array)', function() {
|
|
createSelect({
|
|
'ng-model': 'selected',
|
|
'multiple': true,
|
|
'ng-options': 'item.subItem as item.subItem.label for item in arr track by (item.id || item.subItem.id)'
|
|
});
|
|
|
|
// First test model -> view
|
|
|
|
scope.$apply(function() {
|
|
scope.selected = [scope.arr[0].subItem];
|
|
});
|
|
expect(element.val()).toEqual(['10']);
|
|
|
|
scope.$apply(function() {
|
|
scope.selected = [scope.arr[1].subItem];
|
|
});
|
|
expect(element.val()).toEqual(['20']);
|
|
|
|
// Now test view -> model
|
|
|
|
element.find('option')[0].selected = true;
|
|
element.find('option')[1].selected = false;
|
|
browserTrigger(element, 'change');
|
|
expect(scope.selected).toEqual([scope.arr[0].subItem]);
|
|
|
|
// Now reload the array
|
|
scope.$apply(function() {
|
|
scope.arr = [{
|
|
subItem: {label: 'new ten', id: 10}
|
|
},{
|
|
subItem: {label: 'new twenty', id: 20}
|
|
}];
|
|
});
|
|
expect(element.val()).toEqual(['10']);
|
|
expect(scope.selected[0].id).toEqual(10);
|
|
expect(scope.selected.length).toBe(1);
|
|
});
|
|
|
|
|
|
it('It should use the "value" variable to represent items in the array as well as for the ' +
|
|
'selected values in track by expression (multiple&object)', function() {
|
|
createSelect({
|
|
'ng-model': 'selected',
|
|
'multiple': true,
|
|
'ng-options': 'val.subItem as val.subItem.label for (key, val) in obj track by (val.id || val.subItem.id)'
|
|
});
|
|
|
|
// First test model -> view
|
|
|
|
scope.$apply(function() {
|
|
scope.selected = [scope.obj['10'].subItem];
|
|
});
|
|
expect(element.val()).toEqual(['10']);
|
|
|
|
|
|
scope.$apply(function() {
|
|
scope.selected = [scope.obj['10'].subItem];
|
|
});
|
|
expect(element.val()).toEqual(['10']);
|
|
|
|
// Now test view -> model
|
|
|
|
element.find('option')[0].selected = true;
|
|
element.find('option')[1].selected = false;
|
|
browserTrigger(element, 'change');
|
|
expect(scope.selected).toEqual([scope.obj['10'].subItem]);
|
|
|
|
// Now reload the object
|
|
scope.$apply(function() {
|
|
scope.obj = {
|
|
'10': {
|
|
subItem: {label: 'new ten', id: 10}
|
|
},
|
|
'20': {
|
|
subItem: {label: 'new twenty', id: 20}
|
|
}
|
|
};
|
|
});
|
|
expect(element.val()).toEqual(['10']);
|
|
expect(scope.selected[0].id).toBe(10);
|
|
expect(scope.selected.length).toBe(1);
|
|
});
|
|
|
|
|
|
it('It should use the "value" variable to represent items in the array as well as for the ' +
|
|
'selected values in track by expression (single&object)', function() {
|
|
createSelect({
|
|
'ng-model': 'selected',
|
|
'ng-options': 'val.subItem as val.subItem.label for (key, val) in obj track by (val.id || val.subItem.id)'
|
|
});
|
|
|
|
// First test model -> view
|
|
|
|
scope.$apply(function() {
|
|
scope.selected = scope.obj['10'].subItem;
|
|
});
|
|
expect(element.val()).toEqual('10');
|
|
|
|
|
|
scope.$apply(function() {
|
|
scope.selected = scope.obj['10'].subItem;
|
|
});
|
|
expect(element.val()).toEqual('10');
|
|
|
|
// Now test view -> model
|
|
|
|
element.find('option')[0].selected = true;
|
|
browserTrigger(element, 'change');
|
|
expect(scope.selected).toEqual(scope.obj['10'].subItem);
|
|
|
|
// Now reload the object
|
|
scope.$apply(function() {
|
|
scope.obj = {
|
|
'10': {
|
|
subItem: {label: 'new ten', id: 10}
|
|
},
|
|
'20': {
|
|
subItem: {label: 'new twenty', id: 20}
|
|
}
|
|
};
|
|
});
|
|
expect(element.val()).toEqual('10');
|
|
expect(scope.selected.id).toBe(10);
|
|
});
|
|
});
|
|
|
|
|
|
describe('binding', function() {
|
|
|
|
it('should bind to scope value', function() {
|
|
createSingleSelect();
|
|
|
|
scope.$apply(function() {
|
|
scope.values = [{name: 'A'}, {name: 'B'}];
|
|
scope.selected = scope.values[0];
|
|
});
|
|
|
|
expect(element).toEqualSelectValue(scope.selected);
|
|
|
|
scope.$apply(function() {
|
|
scope.selected = scope.values[1];
|
|
});
|
|
|
|
expect(element).toEqualSelectValue(scope.selected);
|
|
});
|
|
|
|
|
|
it('should bind to scope value and group', function() {
|
|
createSelect({
|
|
'ng-model': 'selected',
|
|
'ng-options': 'item.name group by item.group for item in values'
|
|
});
|
|
|
|
scope.$apply(function() {
|
|
scope.values = [{name: 'A'},
|
|
{name: 'B', group: 0},
|
|
{name: 'C', group: 'first'},
|
|
{name: 'D', group: 'second'},
|
|
{name: 'E', group: 0},
|
|
{name: 'F', group: 'first'},
|
|
{name: 'G', group: 'second'}];
|
|
scope.selected = scope.values[3];
|
|
});
|
|
|
|
expect(element).toEqualSelectValue(scope.selected);
|
|
|
|
var optgroups = element.find('optgroup');
|
|
expect(optgroups.length).toBe(3);
|
|
|
|
var zero = optgroups.eq(0);
|
|
var b = zero.find('option').eq(0);
|
|
var e = zero.find('option').eq(1);
|
|
expect(zero.attr('label')).toEqual('0');
|
|
expect(b.text()).toEqual('B');
|
|
expect(e.text()).toEqual('E');
|
|
|
|
var first = optgroups.eq(1);
|
|
var c = first.find('option').eq(0);
|
|
var f = first.find('option').eq(1);
|
|
expect(first.attr('label')).toEqual('first');
|
|
expect(c.text()).toEqual('C');
|
|
expect(f.text()).toEqual('F');
|
|
|
|
var second = optgroups.eq(2);
|
|
var d = second.find('option').eq(0);
|
|
var g = second.find('option').eq(1);
|
|
expect(second.attr('label')).toEqual('second');
|
|
expect(d.text()).toEqual('D');
|
|
expect(g.text()).toEqual('G');
|
|
|
|
scope.$apply(function() {
|
|
scope.selected = scope.values[0];
|
|
});
|
|
|
|
expect(element).toEqualSelectValue(scope.selected);
|
|
});
|
|
|
|
|
|
it('should group when the options are available on compile time', function() {
|
|
scope.values = [{name: 'C', group: 'first'},
|
|
{name: 'D', group: 'second'},
|
|
{name: 'F', group: 'first'},
|
|
{name: 'G', group: 'second'}];
|
|
scope.selected = scope.values[3];
|
|
|
|
createSelect({
|
|
'ng-model': 'selected',
|
|
'ng-options': 'item as item.name group by item.group for item in values'
|
|
});
|
|
|
|
expect(element).toEqualSelectValue(scope.selected);
|
|
|
|
var optgroups = element.find('optgroup');
|
|
expect(optgroups.length).toBe(2);
|
|
|
|
var first = optgroups.eq(0);
|
|
var c = first.find('option').eq(0);
|
|
var f = first.find('option').eq(1);
|
|
expect(first.attr('label')).toEqual('first');
|
|
expect(c.text()).toEqual('C');
|
|
expect(f.text()).toEqual('F');
|
|
|
|
var second = optgroups.eq(1);
|
|
var d = second.find('option').eq(0);
|
|
var g = second.find('option').eq(1);
|
|
expect(second.attr('label')).toEqual('second');
|
|
expect(d.text()).toEqual('D');
|
|
expect(g.text()).toEqual('G');
|
|
|
|
scope.$apply(function() {
|
|
scope.selected = scope.values[0];
|
|
});
|
|
|
|
expect(element).toEqualSelectValue(scope.selected);
|
|
});
|
|
|
|
|
|
it('should group when the options are updated', function() {
|
|
var optgroups, one, two, three, alpha, beta, gamma, delta, epsilon;
|
|
|
|
createSelect({
|
|
'ng-model': 'selected',
|
|
'ng-options': 'i.name group by i.cls for i in list'
|
|
});
|
|
|
|
scope.list = [
|
|
{cls: 'one', name: 'Alpha'},
|
|
{cls: 'one', name: 'Beta'},
|
|
{cls: 'two', name: 'Gamma'}
|
|
];
|
|
scope.$digest();
|
|
|
|
optgroups = element.find('optgroup');
|
|
expect(optgroups.length).toBe(2);
|
|
|
|
one = optgroups.eq(0);
|
|
expect(one.children('option').length).toBe(2);
|
|
|
|
alpha = one.find('option').eq(0);
|
|
beta = one.find('option').eq(1);
|
|
expect(one.attr('label')).toEqual('one');
|
|
expect(alpha.text()).toEqual('Alpha');
|
|
expect(beta.text()).toEqual('Beta');
|
|
|
|
two = optgroups.eq(1);
|
|
expect(two.children('option').length).toBe(1);
|
|
|
|
gamma = two.find('option').eq(0);
|
|
expect(two.attr('label')).toEqual('two');
|
|
expect(gamma.text()).toEqual('Gamma');
|
|
|
|
// Remove item from first group, add item to second group, add new group
|
|
scope.list.shift();
|
|
scope.list.push(
|
|
{cls: 'two', name: 'Delta'},
|
|
{cls: 'three', name: 'Epsilon'}
|
|
);
|
|
scope.$digest();
|
|
|
|
optgroups = element.find('optgroup');
|
|
expect(optgroups.length).toBe(3);
|
|
|
|
// Group with removed item
|
|
one = optgroups.eq(0);
|
|
expect(one.children('option').length).toBe(1);
|
|
|
|
beta = one.find('option').eq(0);
|
|
expect(one.attr('label')).toEqual('one');
|
|
expect(beta.text()).toEqual('Beta');
|
|
|
|
// Group with new item
|
|
two = optgroups.eq(1);
|
|
expect(two.children('option').length).toBe(2);
|
|
|
|
gamma = two.find('option').eq(0);
|
|
expect(two.attr('label')).toEqual('two');
|
|
expect(gamma.text()).toEqual('Gamma');
|
|
delta = two.find('option').eq(1);
|
|
expect(two.attr('label')).toEqual('two');
|
|
expect(delta.text()).toEqual('Delta');
|
|
|
|
// New group
|
|
three = optgroups.eq(2);
|
|
expect(three.children('option').length).toBe(1);
|
|
|
|
epsilon = three.find('option').eq(0);
|
|
expect(three.attr('label')).toEqual('three');
|
|
expect(epsilon.text()).toEqual('Epsilon');
|
|
});
|
|
|
|
it('should place non-grouped items in the list where they appear', function() {
|
|
createSelect({
|
|
'ng-model': 'selected',
|
|
'ng-options': 'item.name group by item.group for item in values'
|
|
});
|
|
|
|
scope.$apply(function() {
|
|
scope.values = [{name: 'A'},
|
|
{name: 'B', group: 'first'},
|
|
{name: 'C', group: 'second'},
|
|
{name: 'D'},
|
|
{name: 'E', group: 'first'},
|
|
{name: 'F'},
|
|
{name: 'G'},
|
|
{name: 'H', group: 'second'}];
|
|
scope.selected = scope.values[0];
|
|
});
|
|
|
|
var children = element.children();
|
|
expect(children.length).toEqual(6);
|
|
|
|
expect(nodeName_(children[0])).toEqual('option');
|
|
expect(nodeName_(children[1])).toEqual('optgroup');
|
|
expect(nodeName_(children[2])).toEqual('optgroup');
|
|
expect(nodeName_(children[3])).toEqual('option');
|
|
expect(nodeName_(children[4])).toEqual('option');
|
|
expect(nodeName_(children[5])).toEqual('option');
|
|
});
|
|
|
|
|
|
it('should group if the group has a falsy value (except undefined)', function() {
|
|
createSelect({
|
|
'ng-model': 'selected',
|
|
'ng-options': 'item.name group by item.group for item in values'
|
|
});
|
|
|
|
scope.$apply(function() {
|
|
scope.values = [{name: 'A'},
|
|
{name: 'B', group: ''},
|
|
{name: 'C', group: null},
|
|
{name: 'D', group: false},
|
|
{name: 'E', group: 0}];
|
|
scope.selected = scope.values[0];
|
|
});
|
|
|
|
var optgroups = element.find('optgroup');
|
|
var options = element.find('option');
|
|
|
|
expect(optgroups.length).toEqual(4);
|
|
expect(options.length).toEqual(5);
|
|
|
|
expect(optgroups[0].label).toBe('');
|
|
expect(optgroups[1].label).toBe('null');
|
|
expect(optgroups[2].label).toBe('false');
|
|
expect(optgroups[3].label).toBe('0');
|
|
|
|
expect(options[0].textContent).toBe('A');
|
|
expect(options[0].parentNode).toBe(element[0]);
|
|
|
|
expect(options[1].textContent).toBe('B');
|
|
expect(options[1].parentNode).toBe(optgroups[0]);
|
|
|
|
expect(options[2].textContent).toBe('C');
|
|
expect(options[2].parentNode).toBe(optgroups[1]);
|
|
|
|
expect(options[3].textContent).toBe('D');
|
|
expect(options[3].parentNode).toBe(optgroups[2]);
|
|
|
|
expect(options[4].textContent).toBe('E');
|
|
expect(options[4].parentNode).toBe(optgroups[3]);
|
|
});
|
|
|
|
|
|
it('should not duplicate a group with a falsy value when the options are updated', function() {
|
|
|
|
scope.$apply(function() {
|
|
scope.values = [{value: 'A', group: ''},
|
|
{value: 'B', group: 'First'}];
|
|
scope.selected = scope.values[0];
|
|
});
|
|
|
|
createSelect({
|
|
'ng-model': 'selected',
|
|
'ng-options': 'item.value group by item.group for item in values'
|
|
});
|
|
|
|
scope.$apply(function() {
|
|
scope.values.push({value: 'C', group: false});
|
|
});
|
|
|
|
var optgroups = element.find('optgroup');
|
|
var options = element.find('option');
|
|
|
|
expect(optgroups.length).toEqual(3);
|
|
expect(options.length).toEqual(3);
|
|
|
|
expect(optgroups[0].label).toBe('');
|
|
expect(optgroups[1].label).toBe('First');
|
|
expect(optgroups[2].label).toBe('false');
|
|
|
|
expect(options[0].textContent).toBe('A');
|
|
expect(options[0].parentNode).toBe(optgroups[0]);
|
|
|
|
expect(options[1].textContent).toBe('B');
|
|
expect(options[1].parentNode).toBe(optgroups[1]);
|
|
|
|
expect(options[2].textContent).toBe('C');
|
|
expect(options[2].parentNode).toBe(optgroups[2]);
|
|
});
|
|
|
|
|
|
it('should bind to scope value and track/identify objects', function() {
|
|
createSelect({
|
|
'ng-model': 'selected',
|
|
'ng-options': 'item.name for item in values track by item.id'
|
|
});
|
|
|
|
scope.$apply(function() {
|
|
scope.values = [{id: 1, name: 'first'},
|
|
{id: 2, name: 'second'},
|
|
{id: 3, name: 'third'},
|
|
{id: 4, name: 'forth'}];
|
|
scope.selected = scope.values[1];
|
|
});
|
|
|
|
expect(element.val()).toEqual('2');
|
|
|
|
var first = jqLite(element.find('option')[0]);
|
|
expect(first.text()).toEqual('first');
|
|
expect(first.attr('value')).toEqual('1');
|
|
var forth = jqLite(element.find('option')[3]);
|
|
expect(forth.text()).toEqual('forth');
|
|
expect(forth.attr('value')).toEqual('4');
|
|
|
|
scope.$apply(function() {
|
|
scope.selected = scope.values[3];
|
|
});
|
|
|
|
expect(element.val()).toEqual('4');
|
|
});
|
|
|
|
|
|
it('should bind to scope value through expression', function() {
|
|
createSelect({
|
|
'ng-model': 'selected',
|
|
'ng-options': 'item.id as item.name for item in values'
|
|
});
|
|
|
|
scope.$apply(function() {
|
|
scope.values = [{id: 10, name: 'A'}, {id: 20, name: 'B'}];
|
|
scope.selected = scope.values[0].id;
|
|
});
|
|
|
|
expect(element).toEqualSelectValue(scope.selected);
|
|
|
|
scope.$apply(function() {
|
|
scope.selected = scope.values[1].id;
|
|
});
|
|
|
|
expect(element).toEqualSelectValue(scope.selected);
|
|
});
|
|
|
|
|
|
it('should update options in the DOM', function() {
|
|
compile(
|
|
'<select ng-model="selected" ng-options="item.id as item.name for item in values"></select>'
|
|
);
|
|
|
|
scope.$apply(function() {
|
|
scope.values = [{id: 10, name: 'A'}, {id: 20, name: 'B'}];
|
|
scope.selected = scope.values[0].id;
|
|
});
|
|
|
|
scope.$apply(function() {
|
|
scope.values[0].name = 'C';
|
|
});
|
|
|
|
var options = element.find('option');
|
|
expect(options.length).toEqual(2);
|
|
expect(options.eq(0)).toEqualOption(10, 'C');
|
|
expect(options.eq(1)).toEqualOption(20, 'B');
|
|
});
|
|
|
|
|
|
it('should update options in the DOM from object source', function() {
|
|
compile(
|
|
'<select ng-model="selected" ng-options="val.id as val.name for (key, val) in values"></select>'
|
|
);
|
|
|
|
scope.$apply(function() {
|
|
scope.values = {a: {id: 10, name: 'A'}, b: {id: 20, name: 'B'}};
|
|
scope.selected = scope.values.a.id;
|
|
});
|
|
|
|
scope.$apply(function() {
|
|
scope.values.a.name = 'C';
|
|
});
|
|
|
|
var options = element.find('option');
|
|
expect(options.length).toEqual(2);
|
|
expect(options.eq(0)).toEqualOption(10, 'C');
|
|
expect(options.eq(1)).toEqualOption(20, 'B');
|
|
});
|
|
|
|
|
|
it('should bind to object key', function() {
|
|
createSelect({
|
|
'ng-model': 'selected',
|
|
'ng-options': 'key as value for (key, value) in object'
|
|
});
|
|
|
|
scope.$apply(function() {
|
|
scope.object = {red: 'FF0000', green: '00FF00', blue: '0000FF'};
|
|
scope.selected = 'green';
|
|
});
|
|
|
|
expect(element).toEqualSelectValue(scope.selected);
|
|
|
|
scope.$apply(function() {
|
|
scope.selected = 'blue';
|
|
});
|
|
|
|
expect(element).toEqualSelectValue(scope.selected);
|
|
});
|
|
|
|
|
|
it('should bind to object value', function() {
|
|
createSelect({
|
|
'ng-model': 'selected',
|
|
'ng-options': 'value as key for (key, value) in object'
|
|
});
|
|
|
|
scope.$apply(function() {
|
|
scope.object = {red: 'FF0000', green: '00FF00', blue:'0000FF'};
|
|
scope.selected = '00FF00';
|
|
});
|
|
|
|
expect(element).toEqualSelectValue(scope.selected);
|
|
|
|
scope.$apply(function() {
|
|
scope.selected = '0000FF';
|
|
});
|
|
|
|
expect(element).toEqualSelectValue(scope.selected);
|
|
});
|
|
|
|
it('should bind to object disabled', function() {
|
|
scope.selected = 30;
|
|
scope.options = [
|
|
{ name: 'white', value: '#FFFFFF' },
|
|
{ name: 'one', value: 1, unavailable: true },
|
|
{ name: 'notTrue', value: false },
|
|
{ name: 'thirty', value: 30, unavailable: false }
|
|
];
|
|
createSelect({
|
|
'ng-options': 'o.value as o.name disable when o.unavailable for o in options',
|
|
'ng-model': 'selected'
|
|
});
|
|
|
|
var options = element.find('option');
|
|
|
|
expect(scope.options[1].unavailable).toEqual(true);
|
|
expect(options.eq(1).prop('disabled')).toEqual(true);
|
|
|
|
scope.$apply(function() {
|
|
scope.options[1].unavailable = false;
|
|
});
|
|
|
|
options = element.find('option');
|
|
|
|
expect(scope.options[1].unavailable).toEqual(false);
|
|
expect(options.eq(1).prop('disabled')).toEqual(false);
|
|
});
|
|
|
|
it('should insert the unknown option if bound to null', function() {
|
|
createSingleSelect();
|
|
|
|
scope.$apply(function() {
|
|
scope.values = [{name: 'A'}];
|
|
scope.selected = null;
|
|
});
|
|
|
|
expect(element.find('option').length).toEqual(2);
|
|
expect(element.val()).toEqual('?');
|
|
expect(jqLite(element.find('option')[0]).val()).toEqual('?');
|
|
|
|
scope.$apply(function() {
|
|
scope.selected = scope.values[0];
|
|
});
|
|
|
|
expect(element).toEqualSelectValue(scope.selected);
|
|
expect(element.find('option').length).toEqual(1);
|
|
});
|
|
|
|
it('should select the provided empty option if bound to null', function() {
|
|
createSingleSelect(true);
|
|
|
|
scope.$apply(function() {
|
|
scope.values = [{name: 'A'}];
|
|
scope.selected = null;
|
|
});
|
|
|
|
expect(element.find('option').length).toEqual(2);
|
|
expect(element.val()).toEqual('');
|
|
expect(jqLite(element.find('option')[0]).val()).toEqual('');
|
|
|
|
scope.$apply(function() {
|
|
scope.selected = scope.values[0];
|
|
});
|
|
|
|
expect(element).toEqualSelectValue(scope.selected);
|
|
expect(jqLite(element.find('option')[0]).val()).toEqual('');
|
|
expect(element.find('option').length).toEqual(2);
|
|
});
|
|
|
|
|
|
it('should reuse blank option if bound to null', function() {
|
|
createSingleSelect(true);
|
|
|
|
scope.$apply(function() {
|
|
scope.values = [{name: 'A'}];
|
|
scope.selected = null;
|
|
});
|
|
|
|
expect(element.find('option').length).toEqual(2);
|
|
expect(element.val()).toEqual('');
|
|
expect(jqLite(element.find('option')[0]).val()).toEqual('');
|
|
|
|
scope.$apply(function() {
|
|
scope.selected = scope.values[0];
|
|
});
|
|
|
|
expect(element).toEqualSelectValue(scope.selected);
|
|
expect(element.find('option').length).toEqual(2);
|
|
});
|
|
|
|
|
|
it('should not insert a blank option if one of the options maps to null', function() {
|
|
createSelect({
|
|
'ng-model': 'myColor',
|
|
'ng-options': 'color.shade as color.name for color in colors'
|
|
});
|
|
|
|
scope.$apply(function() {
|
|
scope.colors = [
|
|
{name:'nothing', shade:null},
|
|
{name:'red', shade:'dark'}
|
|
];
|
|
scope.myColor = null;
|
|
});
|
|
|
|
expect(element.find('option').length).toEqual(2);
|
|
expect(element.find('option').eq(0)).toEqualOption(null);
|
|
expect(element.val()).not.toEqualUnknownValue(null);
|
|
expect(element.find('option').eq(0)).not.toEqualUnknownOption(null);
|
|
});
|
|
|
|
|
|
it('should insert a unknown option if bound to something not in the list', function() {
|
|
createSingleSelect();
|
|
|
|
scope.$apply(function() {
|
|
scope.values = [{name: 'A'}];
|
|
scope.selected = {};
|
|
});
|
|
|
|
expect(element.find('option').length).toEqual(2);
|
|
expect(element.val()).toEqualUnknownValue(scope.selected);
|
|
expect(element.find('option').eq(0)).toEqualUnknownOption(scope.selected);
|
|
|
|
scope.$apply(function() {
|
|
scope.selected = scope.values[0];
|
|
});
|
|
|
|
expect(element).toEqualSelectValue(scope.selected);
|
|
expect(element.find('option').length).toEqual(1);
|
|
});
|
|
|
|
|
|
it('should insert and select temporary unknown option when no options-model match, empty ' +
|
|
'option is present and model is defined', function() {
|
|
scope.selected = 'C';
|
|
scope.values = [{name: 'A'}, {name: 'B'}];
|
|
createSingleSelect(true);
|
|
|
|
expect(element).toEqualSelect(['?'], '', 'object:3', 'object:4');
|
|
|
|
scope.$apply('selected = values[1]');
|
|
|
|
expect(element).toEqualSelect('', 'object:3', ['object:4']);
|
|
});
|
|
|
|
|
|
it('should select correct input if previously selected option was "?"', function() {
|
|
createSingleSelect();
|
|
|
|
scope.$apply(function() {
|
|
scope.values = [{name: 'A'}, {name: 'B'}];
|
|
scope.selected = {};
|
|
});
|
|
|
|
expect(element.find('option').length).toEqual(3);
|
|
expect(element.val()).toEqualUnknownValue();
|
|
expect(element.find('option').eq(0)).toEqualUnknownOption();
|
|
|
|
browserTrigger(element.find('option').eq(1));
|
|
expect(element.find('option').length).toEqual(2);
|
|
expect(element).toEqualSelectValue(scope.selected);
|
|
expect(element.find('option').eq(0).prop('selected')).toBeTruthy();
|
|
});
|
|
|
|
|
|
it('should remove unknown option when empty option exists and model is undefined', function() {
|
|
scope.selected = 'C';
|
|
scope.values = [{name: 'A'}, {name: 'B'}];
|
|
createSingleSelect(true);
|
|
|
|
expect(element).toEqualSelect(['?'], '', 'object:3', 'object:4');
|
|
|
|
scope.selected = undefined;
|
|
scope.$digest();
|
|
|
|
expect(element).toEqualSelect([''], 'object:3', 'object:4');
|
|
});
|
|
|
|
it('should use exact same values as values in scope with one-time bindings', function() {
|
|
scope.values = [{name: 'A'}, {name: 'B'}];
|
|
scope.selected = scope.values[0];
|
|
createSelect({
|
|
'ng-model': 'selected',
|
|
'ng-options': 'value.name for value in ::values'
|
|
});
|
|
|
|
browserTrigger(element.find('option').eq(1));
|
|
|
|
expect(scope.selected).toBe(scope.values[1]);
|
|
});
|
|
|
|
|
|
it('should ensure that at least one option element has the "selected" attribute', function() {
|
|
createSelect({
|
|
'ng-model': 'selected',
|
|
'ng-options': 'item.id as item.name for item in values'
|
|
});
|
|
|
|
scope.$apply(function() {
|
|
scope.values = [{id: 10, name: 'A'}, {id: 20, name: 'B'}];
|
|
});
|
|
expect(element.val()).toEqualUnknownValue();
|
|
expect(element.find('option').eq(0).attr('selected')).toEqual('selected');
|
|
|
|
scope.$apply(function() {
|
|
scope.selected = 10;
|
|
});
|
|
// Here the ? option should disappear and the first real option should have selected attribute
|
|
expect(element).toEqualSelectValue(scope.selected);
|
|
expect(element.find('option').eq(0).attr('selected')).toEqual('selected');
|
|
|
|
// Here the selected value is changed and we change the selected attribute
|
|
scope.$apply(function() {
|
|
scope.selected = 20;
|
|
});
|
|
expect(element).toEqualSelectValue(scope.selected);
|
|
expect(element.find('option').eq(1).attr('selected')).toEqual('selected');
|
|
|
|
scope.$apply(function() {
|
|
scope.values.push({id: 30, name: 'C'});
|
|
});
|
|
expect(element).toEqualSelectValue(scope.selected);
|
|
expect(element.find('option').eq(1).attr('selected')).toEqual('selected');
|
|
|
|
// Here the ? option should reappear and have selected attribute
|
|
scope.$apply(function() {
|
|
scope.selected = undefined;
|
|
});
|
|
expect(element.val()).toEqualUnknownValue();
|
|
expect(element.find('option').eq(0).attr('selected')).toEqual('selected');
|
|
});
|
|
|
|
|
|
it('should select the correct option for selectAs and falsy values', function() {
|
|
scope.values = [{value: 0, label: 'zero'}, {value: 1, label: 'one'}];
|
|
scope.selected = '';
|
|
createSelect({
|
|
'ng-model': 'selected',
|
|
'ng-options': 'option.value as option.label for option in values'
|
|
});
|
|
|
|
var option = element.find('option').eq(0);
|
|
expect(option).toEqualUnknownOption();
|
|
});
|
|
|
|
|
|
it('should update the model if the selected option is removed', function() {
|
|
scope.values = [{value: 0, label: 'zero'}, {value: 1, label: 'one'}];
|
|
scope.selected = 1;
|
|
createSelect({
|
|
'ng-model': 'selected',
|
|
'ng-options': 'option.value as option.label for option in values'
|
|
});
|
|
expect(element).toEqualSelectValue(1);
|
|
|
|
// Check after initial option update
|
|
scope.$apply(function() {
|
|
scope.values.pop();
|
|
});
|
|
|
|
expect(element.val()).toEqual('?');
|
|
expect(scope.selected).toEqual(null);
|
|
|
|
// Check after model change
|
|
scope.$apply(function() {
|
|
scope.selected = 0;
|
|
});
|
|
|
|
expect(element).toEqualSelectValue(0);
|
|
|
|
scope.$apply(function() {
|
|
scope.values.pop();
|
|
});
|
|
|
|
expect(element.val()).toEqual('?');
|
|
expect(scope.selected).toEqual(null);
|
|
});
|
|
|
|
|
|
it('should update the model if all the selected (multiple) options are removed', function() {
|
|
scope.values = [{value: 0, label: 'zero'}, {value: 1, label: 'one'}, {value: 2, label: 'two'}];
|
|
scope.selected = [1, 2];
|
|
createSelect({
|
|
'ng-model': 'selected',
|
|
'multiple': true,
|
|
'ng-options': 'option.value as option.label for option in values'
|
|
});
|
|
|
|
expect(element).toEqualSelectValue([1, 2], true);
|
|
|
|
// Check after initial option update
|
|
scope.$apply(function() {
|
|
scope.values.pop();
|
|
});
|
|
|
|
expect(element).toEqualSelectValue([1], true);
|
|
expect(scope.selected).toEqual([1]);
|
|
|
|
scope.$apply(function() {
|
|
scope.values.pop();
|
|
});
|
|
|
|
expect(element).toEqualSelectValue([], true);
|
|
expect(scope.selected).toEqual([]);
|
|
|
|
// Check after model change
|
|
scope.$apply(function() {
|
|
scope.selected = [0];
|
|
});
|
|
|
|
expect(element).toEqualSelectValue([0], true);
|
|
|
|
scope.$apply(function() {
|
|
scope.values.pop();
|
|
});
|
|
|
|
expect(element).toEqualSelectValue([], true);
|
|
expect(scope.selected).toEqual([]);
|
|
});
|
|
|
|
});
|
|
|
|
|
|
describe('empty option', function() {
|
|
|
|
it('should be compiled as template, be watched and updated', function() {
|
|
var option;
|
|
createSingleSelect('<option value="">blank is {{blankVal}}</option>');
|
|
|
|
scope.$apply(function() {
|
|
scope.blankVal = 'so blank';
|
|
scope.values = [{name: 'A'}];
|
|
});
|
|
|
|
// check blank option is first and is compiled
|
|
expect(element.find('option').length).toBe(2);
|
|
option = element.find('option').eq(0);
|
|
expect(option.val()).toBe('');
|
|
expect(option.text()).toBe('blank is so blank');
|
|
|
|
scope.$apply(function() {
|
|
scope.blankVal = 'not so blank';
|
|
});
|
|
|
|
// check blank option is first and is compiled
|
|
expect(element.find('option').length).toBe(2);
|
|
option = element.find('option').eq(0);
|
|
expect(option.val()).toBe('');
|
|
expect(option.text()).toBe('blank is not so blank');
|
|
});
|
|
|
|
|
|
it('should support binding via ngBindTemplate directive', function() {
|
|
var option;
|
|
createSingleSelect('<option value="" ng-bind-template="blank is {{blankVal}}"></option>');
|
|
|
|
scope.$apply(function() {
|
|
scope.blankVal = 'so blank';
|
|
scope.values = [{name: 'A'}];
|
|
});
|
|
|
|
// check blank option is first and is compiled
|
|
expect(element.find('option').length).toBe(2);
|
|
option = element.find('option').eq(0);
|
|
expect(option.val()).toBe('');
|
|
expect(option.text()).toBe('blank is so blank');
|
|
});
|
|
|
|
|
|
it('should support binding via ngBind attribute', function() {
|
|
var option;
|
|
createSingleSelect('<option value="" ng-bind="blankVal"></option>');
|
|
|
|
scope.$apply(function() {
|
|
scope.blankVal = 'is blank';
|
|
scope.values = [{name: 'A'}];
|
|
});
|
|
|
|
// check blank option is first and is compiled
|
|
expect(element.find('option').length).toBe(2);
|
|
option = element.find('option').eq(0);
|
|
expect(option.val()).toBe('');
|
|
expect(option.text()).toBe('is blank');
|
|
});
|
|
|
|
it('should be ignored when it has no value attribute', function() {
|
|
// The option value is set to the textContent if there's no value attribute,
|
|
// so in that case it doesn't count as a blank option
|
|
createSingleSelect('<option>--select--</option>');
|
|
scope.$apply(function() {
|
|
scope.values = [{name: 'A'}, {name: 'B'}, {name: 'C'}];
|
|
});
|
|
|
|
var options = element.find('option');
|
|
|
|
expect(options.eq(0)).toEqualUnknownOption();
|
|
expect(options.eq(1)).toEqualOption(scope.values[0], 'A');
|
|
expect(options.eq(2)).toEqualOption(scope.values[1], 'B');
|
|
expect(options.eq(3)).toEqualOption(scope.values[2], 'C');
|
|
});
|
|
|
|
|
|
it('should be rendered with the attributes preserved', function() {
|
|
var option;
|
|
createSingleSelect('<option value="" class="coyote" id="road-runner" ' +
|
|
'custom-attr="custom-attr">{{blankVal}}</option>');
|
|
|
|
scope.$apply(function() {
|
|
scope.blankVal = 'is blank';
|
|
});
|
|
|
|
// check blank option is first and is compiled
|
|
option = element.find('option').eq(0);
|
|
expect(option.hasClass('coyote')).toBeTruthy();
|
|
expect(option.attr('id')).toBe('road-runner');
|
|
expect(option.attr('custom-attr')).toBe('custom-attr');
|
|
});
|
|
|
|
it('should be selected, if it is available and no other option is selected', function() {
|
|
// selectedIndex is used here because jqLite incorrectly reports element.val()
|
|
scope.$apply(function() {
|
|
scope.values = [{name: 'A'}];
|
|
});
|
|
createSingleSelect(true);
|
|
// ensure the first option (the blank option) is selected
|
|
expect(element[0].selectedIndex).toEqual(0);
|
|
scope.$digest();
|
|
// ensure the option has not changed following the digest
|
|
expect(element[0].selectedIndex).toEqual(0);
|
|
});
|
|
|
|
|
|
it('should be selectable if select is multiple', function() {
|
|
createMultiSelect(true);
|
|
|
|
// select the empty option
|
|
setSelectValue(element, 0);
|
|
|
|
// ensure selection and correct binding
|
|
expect(element[0].selectedIndex).toEqual(0);
|
|
expect(scope.selected).toEqual([]);
|
|
});
|
|
|
|
|
|
it('should be possible to use ngIf in the blank option', function() {
|
|
var option;
|
|
createSingleSelect('<option ng-if="isBlank" value="">blank</option>');
|
|
|
|
scope.$apply(function() {
|
|
scope.values = [{name: 'A'}];
|
|
scope.isBlank = true;
|
|
});
|
|
|
|
expect(element).toEqualSelect([''], 'object:4');
|
|
|
|
scope.$apply('isBlank = false');
|
|
|
|
expect(element).toEqualSelect(['?'], 'object:4');
|
|
|
|
scope.$apply('isBlank = true');
|
|
|
|
expect(element).toEqualSelect([''], 'object:4');
|
|
});
|
|
|
|
|
|
it('should be possible to use ngIf in the blank option when values are available upon linking',
|
|
function() {
|
|
var options;
|
|
|
|
scope.values = [{name: 'A'}];
|
|
createSingleSelect('<option ng-if="isBlank" value="">blank</option>');
|
|
|
|
scope.$apply('isBlank = true');
|
|
|
|
options = element.find('option');
|
|
expect(options.length).toBe(2);
|
|
expect(options.eq(0).val()).toBe('');
|
|
expect(options.eq(0).text()).toBe('blank');
|
|
|
|
scope.$apply('isBlank = false');
|
|
|
|
expect(element).toEqualSelect(['?'], 'object:3');
|
|
}
|
|
);
|
|
|
|
|
|
it('should select the correct option after linking when the ngIf expression is initially falsy', function() {
|
|
scope.values = [
|
|
{name:'black'},
|
|
{name:'white'},
|
|
{name:'red'}
|
|
];
|
|
scope.selected = scope.values[2];
|
|
|
|
expect(function() {
|
|
createSingleSelect('<option ng-if="isBlank" value="">blank</option>');
|
|
scope.$apply();
|
|
}).not.toThrow();
|
|
|
|
expect(element.find('option')[2]).toBeMarkedAsSelected();
|
|
expect(linkLog).toEqual(['linkNgOptions']);
|
|
});
|
|
|
|
|
|
it('should add / remove the "selected" attribute on empty option which has an initially falsy ngIf expression', function() {
|
|
scope.values = [
|
|
{name:'black'},
|
|
{name:'white'},
|
|
{name:'red'}
|
|
];
|
|
scope.selected = scope.values[2];
|
|
|
|
createSingleSelect('<option ng-if="isBlank" value="">blank</option>');
|
|
scope.$apply();
|
|
|
|
expect(element.find('option')[2]).toBeMarkedAsSelected();
|
|
|
|
scope.$apply('isBlank = true');
|
|
expect(element.find('option')[0].value).toBe('');
|
|
expect(element.find('option')[0]).not.toBeMarkedAsSelected();
|
|
|
|
scope.$apply('selected = null');
|
|
expect(element.find('option')[0].value).toBe('');
|
|
expect(element.find('option')[0]).toBeMarkedAsSelected();
|
|
|
|
scope.selected = scope.values[1];
|
|
scope.$apply();
|
|
expect(element.find('option')[0].value).toBe('');
|
|
expect(element.find('option')[0]).not.toBeMarkedAsSelected();
|
|
expect(element.find('option')[2]).toBeMarkedAsSelected();
|
|
});
|
|
|
|
|
|
it('should add / remove the "selected" attribute on empty option which has an initially truthy ngIf expression when no option is selected', function() {
|
|
scope.values = [
|
|
{name:'black'},
|
|
{name:'white'},
|
|
{name:'red'}
|
|
];
|
|
scope.isBlank = true;
|
|
|
|
createSingleSelect('<option ng-if="isBlank" value="">blank</option>');
|
|
scope.$apply();
|
|
|
|
expect(element.find('option')[0].value).toBe('');
|
|
expect(element.find('option')[0]).toBeMarkedAsSelected();
|
|
|
|
scope.selected = scope.values[2];
|
|
scope.$apply();
|
|
expect(element.find('option')[0]).not.toBeMarkedAsSelected();
|
|
expect(element.find('option')[3]).toBeMarkedAsSelected();
|
|
});
|
|
|
|
|
|
it('should add the "selected" attribute on empty option which has an initially falsy ngIf expression when no option is selected', function() {
|
|
scope.values = [
|
|
{name:'black'},
|
|
{name:'white'},
|
|
{name:'red'}
|
|
];
|
|
|
|
createSingleSelect('<option ng-if="isBlank" value="">blank</option>');
|
|
scope.$apply();
|
|
|
|
expect(element.find('option')[0]).not.toBeMarkedAsSelected();
|
|
|
|
scope.isBlank = true;
|
|
scope.$apply();
|
|
|
|
expect(element.find('option')[0].value).toBe('');
|
|
expect(element.find('option')[0]).toBeMarkedAsSelected();
|
|
expect(element.find('option')[1]).not.toBeMarkedAsSelected();
|
|
});
|
|
|
|
|
|
it('should not throw when a directive compiles the blank option before ngOptions is linked', function() {
|
|
expect(function() {
|
|
createSelect({
|
|
'o-compile-contents': '',
|
|
'name': 'select',
|
|
'ng-model': 'value',
|
|
'ng-options': 'item for item in items'
|
|
}, true);
|
|
}).not.toThrow();
|
|
|
|
expect(linkLog).toEqual(['linkCompileContents', 'linkNgOptions']);
|
|
});
|
|
|
|
|
|
it('should not throw with a directive that replaces', inject(function($templateCache, $httpBackend) {
|
|
$templateCache.put('select_template.html', '<select ng-options="option as option for option in selectable_options"> <option value="">This is a test</option> </select>');
|
|
|
|
scope.options = ['a', 'b', 'c', 'd'];
|
|
|
|
expect(function() {
|
|
element = $compile('<custom-select ng-model="value" options="options"></custom-select>')(scope);
|
|
scope.$digest();
|
|
}).not.toThrow();
|
|
|
|
dealoc(element);
|
|
}));
|
|
|
|
});
|
|
|
|
|
|
describe('on change', function() {
|
|
|
|
it('should update model on change', function() {
|
|
createSingleSelect();
|
|
|
|
scope.$apply(function() {
|
|
scope.values = [{name: 'A'}, {name: 'B'}];
|
|
scope.selected = scope.values[0];
|
|
});
|
|
|
|
expect(element).toEqualSelectValue(scope.selected);
|
|
|
|
setSelectValue(element, 1);
|
|
expect(scope.selected).toEqual(scope.values[1]);
|
|
});
|
|
|
|
|
|
it('should update model on change through expression', function() {
|
|
createSelect({
|
|
'ng-model': 'selected',
|
|
'ng-options': 'item.id as item.name for item in values'
|
|
});
|
|
|
|
scope.$apply(function() {
|
|
scope.values = [{id: 10, name: 'A'}, {id: 20, name: 'B'}];
|
|
scope.selected = scope.values[0].id;
|
|
});
|
|
|
|
expect(element).toEqualSelectValue(scope.selected);
|
|
|
|
setSelectValue(element, 1);
|
|
expect(scope.selected).toEqual(scope.values[1].id);
|
|
});
|
|
|
|
|
|
it('should update model to null on change', function() {
|
|
createSingleSelect(true);
|
|
|
|
scope.$apply(function() {
|
|
scope.values = [{name: 'A'}, {name: 'B'}];
|
|
scope.selected = scope.values[0];
|
|
});
|
|
|
|
element.val('');
|
|
browserTrigger(element, 'change');
|
|
expect(scope.selected).toEqual(null);
|
|
});
|
|
|
|
|
|
// Regression https://github.com/angular/angular.js/issues/7855
|
|
it('should update the model with ng-change', function() {
|
|
createSelect({
|
|
'ng-change':'change()',
|
|
'ng-model':'selected',
|
|
'ng-options':'value for value in values'
|
|
});
|
|
|
|
scope.$apply(function() {
|
|
scope.values = ['A', 'B'];
|
|
scope.selected = 'A';
|
|
});
|
|
|
|
scope.change = function() {
|
|
scope.selected = 'A';
|
|
};
|
|
|
|
element.find('option')[1].selected = true;
|
|
|
|
browserTrigger(element, 'change');
|
|
expect(element.find('option')[0].selected).toBeTruthy();
|
|
expect(scope.selected).toEqual('A');
|
|
});
|
|
});
|
|
|
|
describe('disabled blank', function() {
|
|
it('should select disabled blank by default', function() {
|
|
var html = '<select ng-model="someModel" ng-options="c for c in choices">' +
|
|
'<option value="" disabled>Choose One</option>' +
|
|
'</select>';
|
|
scope.$apply(function() {
|
|
scope.choices = ['A', 'B', 'C'];
|
|
});
|
|
|
|
compile(html);
|
|
|
|
var options = element.find('option');
|
|
var optionToSelect = options.eq(0);
|
|
expect(optionToSelect.text()).toBe('Choose One');
|
|
expect(optionToSelect.prop('selected')).toBe(true);
|
|
expect(element[0].value).toBe('');
|
|
|
|
dealoc(element);
|
|
});
|
|
|
|
|
|
it('should select disabled blank by default when select is required', function() {
|
|
var html = '<select ng-model="someModel" ng-options="c for c in choices" required>' +
|
|
'<option value="" disabled>Choose One</option>' +
|
|
'</select>';
|
|
scope.$apply(function() {
|
|
scope.choices = ['A', 'B', 'C'];
|
|
});
|
|
|
|
compile(html);
|
|
|
|
var options = element.find('option');
|
|
var optionToSelect = options.eq(0);
|
|
expect(optionToSelect.text()).toBe('Choose One');
|
|
expect(optionToSelect.prop('selected')).toBe(true);
|
|
expect(element[0].value).toBe('');
|
|
|
|
dealoc(element);
|
|
});
|
|
});
|
|
|
|
describe('select-many', function() {
|
|
|
|
it('should read multiple selection', function() {
|
|
createMultiSelect();
|
|
|
|
scope.$apply(function() {
|
|
scope.values = [{name: 'A'}, {name: 'B'}];
|
|
scope.selected = [];
|
|
});
|
|
|
|
expect(element.find('option').length).toEqual(2);
|
|
expect(element.find('option')[0].selected).toBeFalsy();
|
|
expect(element.find('option')[1].selected).toBeFalsy();
|
|
|
|
scope.$apply(function() {
|
|
scope.selected.push(scope.values[1]);
|
|
});
|
|
|
|
expect(element.find('option').length).toEqual(2);
|
|
expect(element.find('option')[0].selected).toBeFalsy();
|
|
expect(element.find('option')[1].selected).toBeTruthy();
|
|
|
|
scope.$apply(function() {
|
|
scope.selected.push(scope.values[0]);
|
|
});
|
|
|
|
expect(element.find('option').length).toEqual(2);
|
|
expect(element.find('option')[0].selected).toBeTruthy();
|
|
expect(element.find('option')[1].selected).toBeTruthy();
|
|
});
|
|
|
|
|
|
it('should update model on change', function() {
|
|
createMultiSelect();
|
|
|
|
scope.$apply(function() {
|
|
scope.values = [{name: 'A'}, {name: 'B'}];
|
|
scope.selected = [];
|
|
});
|
|
|
|
element.find('option')[0].selected = true;
|
|
|
|
browserTrigger(element, 'change');
|
|
expect(scope.selected).toEqual([scope.values[0]]);
|
|
});
|
|
|
|
|
|
it('should select from object', function() {
|
|
createSelect({
|
|
'ng-model':'selected',
|
|
'multiple':true,
|
|
'ng-options':'key as value for (key,value) in values'
|
|
});
|
|
scope.values = {'0':'A', '1':'B'};
|
|
|
|
scope.selected = ['1'];
|
|
scope.$digest();
|
|
expect(element.find('option')[1].selected).toBe(true);
|
|
|
|
element.find('option')[0].selected = true;
|
|
browserTrigger(element, 'change');
|
|
expect(scope.selected).toEqual(['0', '1']);
|
|
|
|
element.find('option')[1].selected = false;
|
|
browserTrigger(element, 'change');
|
|
expect(scope.selected).toEqual(['0']);
|
|
});
|
|
|
|
|
|
it('should deselect all options when model is emptied', function() {
|
|
createMultiSelect();
|
|
scope.$apply(function() {
|
|
scope.values = [{name: 'A'}, {name: 'B'}];
|
|
scope.selected = [scope.values[0]];
|
|
});
|
|
expect(element.find('option')[0].selected).toEqual(true);
|
|
|
|
scope.$apply(function() {
|
|
scope.selected.pop();
|
|
});
|
|
|
|
expect(element.find('option')[0].selected).toEqual(false);
|
|
});
|
|
|
|
|
|
// Support: Safari 9+
|
|
// This test relies defining a getter/setter `selected` property on either `<option>` elements
|
|
// or their prototype. Some browsers (including Safari 9) are very flakey when the
|
|
// getter/setter is not defined on the prototype (probably due to some bug). On Safari 9, the
|
|
// getter/setter that is already defined on the `<option>` element's prototype is not
|
|
// configurable, so we can't overwrite it with our spy.
|
|
if (!/\b(9|\d{2})(?:\.\d+)+[\s\S]*safari/i.test(window.navigator.userAgent)) {
|
|
it('should not re-set the `selected` property if it already has the correct value', function() {
|
|
scope.values = [{name: 'A'}, {name: 'B'}];
|
|
createMultiSelect();
|
|
|
|
var options = element.find('option');
|
|
var optionsSetSelected = [];
|
|
var _selected = [];
|
|
|
|
// Set up spies
|
|
var optionProto = Object.getPrototypeOf(options[0]);
|
|
var originalSelectedDescriptor = isFunction(Object.getOwnPropertyDescriptor) &&
|
|
Object.getOwnPropertyDescriptor(optionProto, 'selected');
|
|
var addSpiesOnProto = originalSelectedDescriptor && originalSelectedDescriptor.configurable;
|
|
|
|
forEach(options, function(option, i) {
|
|
var setSelected = function(value) { _selected[i] = value; };
|
|
optionsSetSelected[i] = jasmine.createSpy('optionSetSelected' + i).and.callFake(setSelected);
|
|
setSelected(option.selected);
|
|
});
|
|
|
|
if (!addSpiesOnProto) {
|
|
forEach(options, function(option, i) {
|
|
Object.defineProperty(option, 'selected', {
|
|
get: function() { return _selected[i]; },
|
|
set: optionsSetSelected[i]
|
|
});
|
|
});
|
|
} else {
|
|
// Support: Firefox 54+
|
|
// We cannot use the above (simpler) method on all browsers because of Firefox 54+, which
|
|
// is very flaky when the getter/setter property is defined on the element itself and not
|
|
// the prototype. (Possibly the result of some (buggy?) optimization.)
|
|
var getSelected = function(index) { return _selected[index]; };
|
|
var setSelected = function(index, value) { optionsSetSelected[index](value); };
|
|
var getSelectedOriginal = function(option) {
|
|
return originalSelectedDescriptor.get.call(option);
|
|
};
|
|
var setSelectedOriginal = function(option, value) {
|
|
originalSelectedDescriptor.set.call(option, value);
|
|
};
|
|
var getIndexAndCall = function(option, foundFn, notFoundFn, value) {
|
|
for (var i = 0, ii = options.length; i < ii; ++i) {
|
|
if (options[i] === option) return foundFn(i, value);
|
|
}
|
|
return notFoundFn(option, value);
|
|
};
|
|
|
|
Object.defineProperty(optionProto, 'selected', {
|
|
get: function() {
|
|
return getIndexAndCall(this, getSelected, getSelectedOriginal);
|
|
},
|
|
set: function(value) {
|
|
return getIndexAndCall(this, setSelected, setSelectedOriginal, value);
|
|
}
|
|
});
|
|
}
|
|
|
|
// Select `optionA`
|
|
scope.$apply('selected = [values[0]]');
|
|
|
|
expect(optionsSetSelected[0]).toHaveBeenCalledOnceWith(true);
|
|
expect(optionsSetSelected[1]).not.toHaveBeenCalled();
|
|
expect(options[0].selected).toBe(true);
|
|
expect(options[1].selected).toBe(false);
|
|
optionsSetSelected[0].calls.reset();
|
|
optionsSetSelected[1].calls.reset();
|
|
|
|
// Select `optionB` (`optionA` remains selected)
|
|
scope.$apply('selected.push(values[1])');
|
|
|
|
expect(optionsSetSelected[0]).not.toHaveBeenCalled();
|
|
expect(optionsSetSelected[1]).toHaveBeenCalledOnceWith(true);
|
|
expect(options[0].selected).toBe(true);
|
|
expect(options[1].selected).toBe(true);
|
|
optionsSetSelected[0].calls.reset();
|
|
optionsSetSelected[1].calls.reset();
|
|
|
|
// Unselect `optionA` (`optionB` remains selected)
|
|
scope.$apply('selected.shift()');
|
|
|
|
expect(optionsSetSelected[0]).toHaveBeenCalledOnceWith(false);
|
|
expect(optionsSetSelected[1]).not.toHaveBeenCalled();
|
|
expect(options[0].selected).toBe(false);
|
|
expect(options[1].selected).toBe(true);
|
|
optionsSetSelected[0].calls.reset();
|
|
optionsSetSelected[1].calls.reset();
|
|
|
|
// Reselect `optionA` (`optionB` remains selected)
|
|
scope.$apply('selected.push(values[0])');
|
|
|
|
expect(optionsSetSelected[0]).toHaveBeenCalledOnceWith(true);
|
|
expect(optionsSetSelected[1]).not.toHaveBeenCalled();
|
|
expect(options[0].selected).toBe(true);
|
|
expect(options[1].selected).toBe(true);
|
|
optionsSetSelected[0].calls.reset();
|
|
optionsSetSelected[1].calls.reset();
|
|
|
|
// Unselect `optionB` (`optionA` remains selected)
|
|
scope.$apply('selected.shift()');
|
|
|
|
expect(optionsSetSelected[0]).not.toHaveBeenCalled();
|
|
expect(optionsSetSelected[1]).toHaveBeenCalledOnceWith(false);
|
|
expect(options[0].selected).toBe(true);
|
|
expect(options[1].selected).toBe(false);
|
|
optionsSetSelected[0].calls.reset();
|
|
optionsSetSelected[1].calls.reset();
|
|
|
|
// Unselect `optionA`
|
|
scope.$apply('selected.length = 0');
|
|
|
|
expect(optionsSetSelected[0]).toHaveBeenCalledOnceWith(false);
|
|
expect(optionsSetSelected[1]).not.toHaveBeenCalled();
|
|
expect(options[0].selected).toBe(false);
|
|
expect(options[1].selected).toBe(false);
|
|
optionsSetSelected[0].calls.reset();
|
|
optionsSetSelected[1].calls.reset();
|
|
|
|
// Support: Firefox 54+
|
|
// Restore `originalSelectedDescriptor`
|
|
if (addSpiesOnProto) {
|
|
Object.defineProperty(optionProto, 'selected', originalSelectedDescriptor);
|
|
}
|
|
});
|
|
}
|
|
|
|
if (window.MutationObserver) {
|
|
//IE9 and IE10 do not support MutationObserver
|
|
//Since the feature is only needed for a test, it's okay to skip these browsers
|
|
it('should render the initial options only one time', function() {
|
|
scope.value = ['black'];
|
|
scope.values = ['black', 'white', 'red'];
|
|
// observe-child-list adds a MutationObserver that we will read out after ngOptions
|
|
// has been compiled
|
|
createSelect({
|
|
'ng-model':'selected',
|
|
'ng-options':'value.name for value in values',
|
|
'multiple': 'true',
|
|
'observe-child-list': ''
|
|
});
|
|
|
|
var optionEls = element[0].querySelectorAll('option');
|
|
var records = childListMutationObserver.takeRecords();
|
|
|
|
expect(records.length).toBe(1);
|
|
expect(records[0].addedNodes).toEqual(optionEls);
|
|
});
|
|
}
|
|
|
|
});
|
|
|
|
|
|
describe('required state', function() {
|
|
|
|
it('should set the error if the empty option is selected', function() {
|
|
createSelect({
|
|
'ng-model': 'selection',
|
|
'ng-options': 'item for item in values',
|
|
'required': ''
|
|
}, true);
|
|
|
|
scope.$apply(function() {
|
|
scope.values = ['a', 'b'];
|
|
scope.selection = scope.values[0];
|
|
});
|
|
expect(element).toBeValid();
|
|
expect(ngModelCtrl.$error.required).toBeFalsy();
|
|
|
|
var options = element.find('option');
|
|
|
|
// view -> model
|
|
browserTrigger(options[0], 'click');
|
|
expect(element).toBeInvalid();
|
|
expect(ngModelCtrl.$error.required).toBeTruthy();
|
|
|
|
browserTrigger(options[1], 'click');
|
|
expect(element).toBeValid();
|
|
expect(ngModelCtrl.$error.required).toBeFalsy();
|
|
|
|
// model -> view
|
|
scope.$apply('selection = null');
|
|
expect(options[0]).toBeMarkedAsSelected();
|
|
expect(element).toBeInvalid();
|
|
expect(ngModelCtrl.$error.required).toBeTruthy();
|
|
});
|
|
|
|
|
|
it('should validate with empty option and bound ngRequired', function() {
|
|
createSelect({
|
|
'ng-model': 'value',
|
|
'ng-options': 'item.name for item in values',
|
|
'ng-required': 'required'
|
|
}, true);
|
|
|
|
scope.$apply(function() {
|
|
scope.values = [{name: 'A', id: 1}, {name: 'B', id: 2}];
|
|
scope.required = false;
|
|
});
|
|
|
|
var options = element.find('option');
|
|
|
|
browserTrigger(options[0], 'click');
|
|
expect(element).toBeValid();
|
|
|
|
scope.$apply('required = true');
|
|
expect(element).toBeInvalid();
|
|
|
|
scope.$apply('value = values[0]');
|
|
expect(element).toBeValid();
|
|
|
|
browserTrigger(options[0], 'click');
|
|
expect(element).toBeInvalid();
|
|
|
|
scope.$apply('required = false');
|
|
expect(element).toBeValid();
|
|
});
|
|
|
|
|
|
it('should treat an empty array as invalid when `multiple` attribute used', function() {
|
|
createSelect({
|
|
'ng-model': 'value',
|
|
'ng-options': 'item.name for item in values',
|
|
'ng-required': 'required',
|
|
'multiple': ''
|
|
}, true);
|
|
|
|
scope.$apply(function() {
|
|
scope.value = [];
|
|
scope.values = [{name: 'A', id: 1}, {name: 'B', id: 2}];
|
|
scope.required = true;
|
|
});
|
|
expect(element).toBeInvalid();
|
|
|
|
scope.$apply(function() {
|
|
// ngModelWatch does not set objectEquality flag
|
|
// array must be replaced in order to trigger $formatters
|
|
scope.value = [scope.values[0]];
|
|
});
|
|
expect(element).toBeValid();
|
|
});
|
|
|
|
|
|
it('should NOT set the error if the empty option is present but required attribute is not',
|
|
function() {
|
|
scope.$apply(function() {
|
|
scope.values = ['a', 'b'];
|
|
});
|
|
|
|
createSingleSelect();
|
|
|
|
expect(element).toBeValid();
|
|
expect(element).toBePristine();
|
|
expect(ngModelCtrl.$error.required).toBeFalsy();
|
|
}
|
|
);
|
|
|
|
|
|
it('should NOT set the error if the unknown option is selected', function() {
|
|
createSelect({
|
|
'ng-model': 'selection',
|
|
'ng-options': 'item for item in values',
|
|
'required': ''
|
|
});
|
|
|
|
scope.$apply(function() {
|
|
scope.values = ['a', 'b'];
|
|
scope.selection = 'a';
|
|
});
|
|
|
|
expect(element).toBeValid();
|
|
expect(ngModelCtrl.$error.required).toBeFalsy();
|
|
|
|
scope.$apply('selection = "c"');
|
|
expect(element).toEqualSelect(['?'], 'string:a', 'string:b');
|
|
expect(element).toBeValid();
|
|
expect(ngModelCtrl.$error.required).toBeFalsy();
|
|
});
|
|
|
|
|
|
it('should allow falsy values as values', function() {
|
|
createSelect({
|
|
'ng-model': 'value',
|
|
'ng-options': 'item.value as item.name for item in values',
|
|
'ng-required': 'required'
|
|
}, true);
|
|
|
|
scope.$apply(function() {
|
|
scope.values = [{name: 'True', value: true}, {name: 'False', value: false}];
|
|
scope.required = false;
|
|
});
|
|
|
|
setSelectValue(element, 2);
|
|
expect(element).toBeValid();
|
|
expect(scope.value).toBe(false);
|
|
|
|
scope.$apply('required = true');
|
|
expect(element).toBeValid();
|
|
expect(scope.value).toBe(false);
|
|
});
|
|
|
|
|
|
it('should validate after option list was updated', function() {
|
|
createSelect({
|
|
'ng-model': 'selection',
|
|
'ng-options': 'item for item in values',
|
|
'required': ''
|
|
}, true);
|
|
|
|
scope.$apply(function() {
|
|
scope.values = ['A', 'B'];
|
|
scope.selection = scope.values[0];
|
|
});
|
|
|
|
expect(element).toEqualSelect('', ['string:A'], 'string:B');
|
|
expect(element).toBeValid();
|
|
expect(ngModelCtrl.$error.required).toBeFalsy();
|
|
|
|
scope.$apply(function() {
|
|
scope.values = ['C', 'D'];
|
|
});
|
|
|
|
expect(element).toEqualSelect([''], 'string:C', 'string:D');
|
|
expect(element).toBeInvalid();
|
|
expect(ngModelCtrl.$error.required).toBeTruthy();
|
|
// ngModel sets undefined for invalid values
|
|
expect(scope.selection).toBeUndefined();
|
|
});
|
|
});
|
|
|
|
describe('required and empty option', function() {
|
|
|
|
it('should select the empty option after compilation', function() {
|
|
createSelect({
|
|
'name': 'select',
|
|
'ng-model': 'value',
|
|
'ng-options': 'item for item in [\'first\', \'second\', \'third\']',
|
|
'required': 'required'
|
|
}, true);
|
|
|
|
expect(element.val()).toBe('');
|
|
var emptyOption = element.find('option').eq(0);
|
|
expect(emptyOption.prop('selected')).toBe(true);
|
|
expect(emptyOption.val()).toBe('');
|
|
});
|
|
});
|
|
|
|
describe('ngModelCtrl', function() {
|
|
it('should prefix the model value with the word "the" using $parsers', function() {
|
|
createSelect({
|
|
'name': 'select',
|
|
'ng-model': 'value',
|
|
'ng-options': 'item for item in [\'first\', \'second\', \'third\', \'fourth\']'
|
|
});
|
|
|
|
scope.form.select.$parsers.push(function(value) {
|
|
return 'the ' + value;
|
|
});
|
|
|
|
setSelectValue(element, 3);
|
|
expect(scope.value).toBe('the third');
|
|
expect(element).toEqualSelectValue('third');
|
|
});
|
|
|
|
it('should prefix the view value with the word "the" using $formatters', function() {
|
|
createSelect({
|
|
'name': 'select',
|
|
'ng-model': 'value',
|
|
'ng-options': 'item for item in [\'the first\', \'the second\', \'the third\', \'the fourth\']'
|
|
});
|
|
|
|
scope.form.select.$formatters.push(function(value) {
|
|
return 'the ' + value;
|
|
});
|
|
|
|
scope.$apply(function() {
|
|
scope.value = 'third';
|
|
});
|
|
expect(element).toEqualSelectValue('the third');
|
|
});
|
|
|
|
it('should fail validation when $validators fail', function() {
|
|
createSelect({
|
|
'name': 'select',
|
|
'ng-model': 'value',
|
|
'ng-options': 'item for item in [\'first\', \'second\', \'third\', \'fourth\']'
|
|
});
|
|
|
|
scope.form.select.$validators.fail = function() {
|
|
return false;
|
|
};
|
|
|
|
setSelectValue(element, 3);
|
|
expect(element).toBeInvalid();
|
|
expect(scope.value).toBeUndefined();
|
|
expect(element).toEqualSelectValue('third');
|
|
});
|
|
|
|
it('should pass validation when $validators pass', function() {
|
|
createSelect({
|
|
'name': 'select',
|
|
'ng-model': 'value',
|
|
'ng-options': 'item for item in [\'first\', \'second\', \'third\', \'fourth\']'
|
|
});
|
|
|
|
scope.form.select.$validators.pass = function() {
|
|
return true;
|
|
};
|
|
|
|
setSelectValue(element, 3);
|
|
expect(element).toBeValid();
|
|
expect(scope.value).toBe('third');
|
|
expect(element).toEqualSelectValue('third');
|
|
});
|
|
|
|
it('should fail validation when $asyncValidators fail', inject(function($q, $rootScope) {
|
|
var defer;
|
|
createSelect({
|
|
'name': 'select',
|
|
'ng-model': 'value',
|
|
'ng-options': 'item for item in [\'first\', \'second\', \'third\', \'fourth\']'
|
|
});
|
|
|
|
scope.form.select.$asyncValidators.async = function() {
|
|
defer = $q.defer();
|
|
return defer.promise;
|
|
};
|
|
|
|
setSelectValue(element, 3);
|
|
expect(scope.form.select.$pending).toBeDefined();
|
|
expect(scope.value).toBeUndefined();
|
|
expect(element).toEqualSelectValue('third');
|
|
|
|
defer.reject();
|
|
$rootScope.$digest();
|
|
expect(scope.form.select.$pending).toBeUndefined();
|
|
expect(scope.value).toBeUndefined();
|
|
expect(element).toEqualSelectValue('third');
|
|
}));
|
|
|
|
it('should pass validation when $asyncValidators pass', inject(function($q, $rootScope) {
|
|
var defer;
|
|
createSelect({
|
|
'name': 'select',
|
|
'ng-model': 'value',
|
|
'ng-options': 'item for item in [\'first\', \'second\', \'third\', \'fourth\']'
|
|
});
|
|
|
|
scope.form.select.$asyncValidators.async = function() {
|
|
defer = $q.defer();
|
|
return defer.promise;
|
|
};
|
|
|
|
setSelectValue(element, 3);
|
|
expect(scope.form.select.$pending).toBeDefined();
|
|
expect(scope.value).toBeUndefined();
|
|
expect(element).toEqualSelectValue('third');
|
|
|
|
defer.resolve();
|
|
$rootScope.$digest();
|
|
expect(scope.form.select.$pending).toBeUndefined();
|
|
expect(scope.value).toBe('third');
|
|
expect(element).toEqualSelectValue('third');
|
|
}));
|
|
|
|
it('should not set $dirty with select-multiple after compilation', function() {
|
|
scope.values = ['a', 'b'];
|
|
scope.selected = ['b'];
|
|
|
|
createSelect({
|
|
'ng-model':'selected',
|
|
'multiple':true,
|
|
'ng-options':'value for value in values',
|
|
'name': 'select'
|
|
});
|
|
|
|
expect(element.find('option')[1].selected).toBe(true);
|
|
expect(scope.form.select.$pristine).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('selectCtrl api', function() {
|
|
|
|
it('should reflect the status of empty and unknown option', function() {
|
|
createSingleSelect('<option ng-if="isBlank" value="">blank</option>');
|
|
|
|
var selectCtrl = element.controller('select');
|
|
|
|
scope.$apply(function() {
|
|
scope.values = [{name: 'A'}, {name: 'B'}];
|
|
scope.isBlank = true;
|
|
});
|
|
|
|
expect(element).toEqualSelect([''], 'object:4', 'object:5');
|
|
expect(selectCtrl.$hasEmptyOption()).toBe(true);
|
|
expect(selectCtrl.$isEmptyOptionSelected()).toBe(true);
|
|
expect(selectCtrl.$isUnknownOptionSelected()).toBe(false);
|
|
|
|
// empty -> selection
|
|
scope.$apply(function() {
|
|
scope.selected = scope.values[0];
|
|
});
|
|
|
|
expect(element).toEqualSelect('', ['object:4'], 'object:5');
|
|
expect(selectCtrl.$hasEmptyOption()).toBe(true);
|
|
expect(selectCtrl.$isEmptyOptionSelected()).toBe(false);
|
|
expect(selectCtrl.$isUnknownOptionSelected()).toBe(false);
|
|
|
|
// remove empty
|
|
scope.$apply('isBlank = false');
|
|
|
|
expect(element).toEqualSelect(['object:4'], 'object:5');
|
|
expect(selectCtrl.$hasEmptyOption()).toBe(false);
|
|
expect(selectCtrl.$isEmptyOptionSelected()).toBe(false);
|
|
expect(selectCtrl.$isUnknownOptionSelected()).toBe(false);
|
|
|
|
// selection -> unknown
|
|
scope.$apply('selected = "unmatched"');
|
|
|
|
expect(element).toEqualSelect(['?'], 'object:4', 'object:5');
|
|
expect(selectCtrl.$hasEmptyOption()).toBe(false);
|
|
expect(selectCtrl.$isEmptyOptionSelected()).toBe(false);
|
|
expect(selectCtrl.$isUnknownOptionSelected()).toBe(true);
|
|
|
|
// add empty
|
|
scope.$apply('isBlank = true');
|
|
|
|
expect(element).toEqualSelect(['?'], '', 'object:4', 'object:5');
|
|
expect(selectCtrl.$hasEmptyOption()).toBe(true);
|
|
expect(selectCtrl.$isEmptyOptionSelected()).toBe(false);
|
|
expect(selectCtrl.$isUnknownOptionSelected()).toBe(true);
|
|
|
|
// unknown -> empty
|
|
scope.$apply(function() {
|
|
scope.selected = null;
|
|
});
|
|
|
|
expect(element).toEqualSelect([''], 'object:4', 'object:5');
|
|
expect(selectCtrl.$hasEmptyOption()).toBe(true);
|
|
expect(selectCtrl.$isEmptyOptionSelected()).toBe(true);
|
|
expect(selectCtrl.$isUnknownOptionSelected()).toBe(false);
|
|
|
|
// empty -> unknown
|
|
scope.$apply('selected = "unmatched"');
|
|
|
|
expect(element).toEqualSelect(['?'], '', 'object:4', 'object:5');
|
|
expect(selectCtrl.$hasEmptyOption()).toBe(true);
|
|
expect(selectCtrl.$isEmptyOptionSelected()).toBe(false);
|
|
expect(selectCtrl.$isUnknownOptionSelected()).toBe(true);
|
|
|
|
// unknown -> selection
|
|
scope.$apply(function() {
|
|
scope.selected = scope.values[1];
|
|
});
|
|
|
|
expect(element).toEqualSelect('', 'object:4', ['object:5']);
|
|
expect(selectCtrl.$hasEmptyOption()).toBe(true);
|
|
expect(selectCtrl.$isEmptyOptionSelected()).toBe(false);
|
|
expect(selectCtrl.$isUnknownOptionSelected()).toBe(false);
|
|
|
|
// selection -> empty
|
|
scope.$apply('selected = null');
|
|
|
|
expect(element).toEqualSelect([''], 'object:4', 'object:5');
|
|
expect(selectCtrl.$hasEmptyOption()).toBe(true);
|
|
expect(selectCtrl.$isEmptyOptionSelected()).toBe(true);
|
|
expect(selectCtrl.$isUnknownOptionSelected()).toBe(false);
|
|
});
|
|
});
|
|
|
|
});
|