mirror of
https://github.com/Hopiu/angular.js.git
synced 2026-03-17 07:40:22 +00:00
fix(select): properly handle empty & unknown options without ngOptions
Previously only when ngOptions was used, we correctly handled situations when model was set to an unknown value. With this change, we'll add/remove extra unknown option or reuse an existing empty option (option with value set to "") when model is undefined.
This commit is contained in:
parent
c65c34ebfe
commit
904b69c745
2 changed files with 425 additions and 74 deletions
|
|
@ -22,16 +22,10 @@
|
|||
* be nested into the `<select>` element. This element will then represent `null` or "not selected"
|
||||
* option. See example below for demonstration.
|
||||
*
|
||||
* Note: `ngOptions` provides iterator facility for `<option>` element which must be used instead
|
||||
* of {@link angular.module.ng.$compileProvider.directive.ngRepeat ngRepeat}. `ngRepeat` is not
|
||||
* suitable for use with `<option>` element because of the following reasons:
|
||||
*
|
||||
* * value attribute of the option element that we need to bind to requires a string, but the
|
||||
* source of data for the iteration might be in a form of array containing objects instead of
|
||||
* strings
|
||||
* * {@link angular.module.ng.$compileProvider.directive.ngRepeat ngRepeat} unrolls after the
|
||||
* select binds causing incorect rendering on most browsers.
|
||||
* * binding to a value not in list confuses most browsers.
|
||||
* Note: `ngOptions` provides iterator facility for `<option>` element which should be used instead
|
||||
* of {@link angular.module.ng.$compileProvider.directive.ngRepeat ngRepeat} when you want the
|
||||
* `select` model to be bound to a non-string value. This is because an option element can currently
|
||||
* be bound to string values only.
|
||||
*
|
||||
* @param {string} name assignable expression to data-bind to.
|
||||
* @param {string=} required The control is considered valid only if value is entered.
|
||||
|
|
@ -92,11 +86,11 @@
|
|||
<select ng-model="color" ng-options="c.name for c in colors"></select><br>
|
||||
|
||||
Color (null allowed):
|
||||
<div class="nullable">
|
||||
<span class="nullable">
|
||||
<select ng-model="color" ng-options="c.name for c in colors">
|
||||
<option value="">-- chose color --</option>
|
||||
</select>
|
||||
</div><br/>
|
||||
</span><br/>
|
||||
|
||||
Color grouped by shade:
|
||||
<select ng-model="color" ng-options="c.name group by c.shade for c in colors">
|
||||
|
|
@ -126,49 +120,136 @@
|
|||
var ngOptionsDirective = valueFn({ terminal: true });
|
||||
var selectDirective = ['$compile', '$parse', function($compile, $parse) {
|
||||
//00001111100000000000222200000000000000000000003333000000000000044444444444444444000000000555555555555555550000000666666666666666660000000000000007777
|
||||
var NG_OPTIONS_REGEXP = /^\s*(.*?)(?:\s+as\s+(.*?))?(?:\s+group\s+by\s+(.*))?\s+for\s+(?:([\$\w][\$\w\d]*)|(?:\(\s*([\$\w][\$\w\d]*)\s*,\s*([\$\w][\$\w\d]*)\s*\)))\s+in\s+(.*)$/;
|
||||
var NG_OPTIONS_REGEXP = /^\s*(.*?)(?:\s+as\s+(.*?))?(?:\s+group\s+by\s+(.*))?\s+for\s+(?:([\$\w][\$\w\d]*)|(?:\(\s*([\$\w][\$\w\d]*)\s*,\s*([\$\w][\$\w\d]*)\s*\)))\s+in\s+(.*)$/,
|
||||
nullModelCtrl = {$setViewValue: noop};
|
||||
|
||||
return {
|
||||
restrict: 'E',
|
||||
require: '?ngModel',
|
||||
link: function(scope, element, attr, ctrl) {
|
||||
if (!ctrl) return;
|
||||
require: ['select', '?ngModel'],
|
||||
controller: ['$element', '$scope', function($element, $scope) {
|
||||
var self = this,
|
||||
optionsMap = {},
|
||||
ngModelCtrl = nullModelCtrl,
|
||||
nullOption,
|
||||
unknownOption;
|
||||
|
||||
var multiple = attr.multiple,
|
||||
optionsExp = attr.ngOptions;
|
||||
self.init = function(ngModelCtrl_, nullOption_, unknownOption_) {
|
||||
ngModelCtrl = ngModelCtrl_;
|
||||
nullOption = nullOption_;
|
||||
unknownOption = unknownOption_;
|
||||
}
|
||||
|
||||
|
||||
self.addOption = function(value) {
|
||||
optionsMap[value] = true;
|
||||
|
||||
if (ngModelCtrl.$viewValue == value) {
|
||||
$element.val(value);
|
||||
if (unknownOption.parent()) unknownOption.remove();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
self.removeOption = function(value) {
|
||||
if (this.hasOption(value)) {
|
||||
delete optionsMap[value];
|
||||
if (ngModelCtrl.$viewValue == value) {
|
||||
this.renderUnknownOption(value);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
self.renderUnknownOption = function(val) {
|
||||
var unknownVal = '? ' + hashKey(val) + ' ?';
|
||||
unknownOption.val(unknownVal);
|
||||
$element.prepend(unknownOption);
|
||||
$element.val(unknownVal);
|
||||
unknownOption.prop('selected', true); // needed for IE
|
||||
}
|
||||
|
||||
|
||||
self.hasOption = function(value) {
|
||||
return optionsMap.hasOwnProperty(value);
|
||||
}
|
||||
|
||||
$scope.$on('$destroy', function() {
|
||||
// disable unknown option so that we don't do work when the whole select is being destroyed
|
||||
self.renderUnknownOption = noop;
|
||||
});
|
||||
}],
|
||||
|
||||
link: function(scope, element, attr, ctrls) {
|
||||
// if ngModel is not defined, we don't need to do anything
|
||||
if (!ctrls[1]) return;
|
||||
|
||||
var selectCtrl = ctrls[0],
|
||||
ngModelCtrl = ctrls[1],
|
||||
multiple = attr.multiple,
|
||||
optionsExp = attr.ngOptions,
|
||||
nullOption = false, // if false, user will not be able to select it (used by ngOptions)
|
||||
emptyOption,
|
||||
// we can't just jqLite('<option>') since jqLite is not smart enough
|
||||
// to create it in <select> and IE barfs otherwise.
|
||||
optionTemplate = jqLite(document.createElement('option')),
|
||||
optGroupTemplate =jqLite(document.createElement('optgroup')),
|
||||
unknownOption = optionTemplate.clone();
|
||||
|
||||
// find "null" option
|
||||
for(var i = 0, children = element.children(), ii = children.length; i < ii; i++) {
|
||||
if (children[i].value == '') {
|
||||
emptyOption = nullOption = children.eq(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
selectCtrl.init(ngModelCtrl, nullOption, unknownOption);
|
||||
|
||||
// required validator
|
||||
if (multiple && (attr.required || attr.ngRequired)) {
|
||||
var requiredValidator = function(value) {
|
||||
ctrl.$setValidity('required', !attr.required || (value && value.length));
|
||||
ngModelCtrl.$setValidity('required', !attr.required || (value && value.length));
|
||||
return value;
|
||||
};
|
||||
|
||||
ctrl.$parsers.push(requiredValidator);
|
||||
ctrl.$formatters.unshift(requiredValidator);
|
||||
ngModelCtrl.$parsers.push(requiredValidator);
|
||||
ngModelCtrl.$formatters.unshift(requiredValidator);
|
||||
|
||||
attr.$observe('required', function() {
|
||||
requiredValidator(ctrl.$viewValue);
|
||||
requiredValidator(ngModelCtrl.$viewValue);
|
||||
});
|
||||
}
|
||||
|
||||
if (optionsExp) Options(scope, element, ctrl);
|
||||
else if (multiple) Multiple(scope, element, ctrl);
|
||||
else Single(scope, element, ctrl);
|
||||
if (optionsExp) Options(scope, element, ngModelCtrl);
|
||||
else if (multiple) Multiple(scope, element, ngModelCtrl);
|
||||
else Single(scope, element, ngModelCtrl, selectCtrl);
|
||||
|
||||
|
||||
////////////////////////////
|
||||
|
||||
|
||||
|
||||
function Single(scope, selectElement, ctrl) {
|
||||
ctrl.$render = function() {
|
||||
selectElement.val(ctrl.$viewValue);
|
||||
function Single(scope, selectElement, ngModelCtrl, selectCtrl) {
|
||||
ngModelCtrl.$render = function() {
|
||||
var viewValue = ngModelCtrl.$viewValue;
|
||||
|
||||
if (selectCtrl.hasOption(viewValue)) {
|
||||
if (unknownOption.parent()) unknownOption.remove();
|
||||
selectElement.val(viewValue);
|
||||
if (viewValue === '') emptyOption.prop('selected', true); // to make IE9 happy
|
||||
} else {
|
||||
if (isUndefined(viewValue) && emptyOption) {
|
||||
selectElement.val('');
|
||||
} else {
|
||||
selectCtrl.renderUnknownOption(viewValue);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
selectElement.bind('change', function() {
|
||||
scope.$apply(function() {
|
||||
ctrl.$setViewValue(selectElement.val());
|
||||
if (unknownOption.parent()) unknownOption.remove();
|
||||
ngModelCtrl.$setViewValue(selectElement.val());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
@ -219,26 +300,26 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) {
|
|||
groupByFn = $parse(match[3] || ''),
|
||||
valueFn = $parse(match[2] ? match[1] : valueName),
|
||||
valuesFn = $parse(match[7]),
|
||||
// we can't just jqLite('<option>') since jqLite is not smart enough
|
||||
// to create it in <select> and IE barfs otherwise.
|
||||
optionTemplate = jqLite(document.createElement('option')),
|
||||
optGroupTemplate = jqLite(document.createElement('optgroup')),
|
||||
nullOption = false, // if false then user will not be able to select it
|
||||
// This is an array of array of existing option groups in DOM. We try to reuse these if possible
|
||||
// optionGroupsCache[0] is the options with no option group
|
||||
// optionGroupsCache[?][0] is the parent: either the SELECT or OPTGROUP element
|
||||
optionGroupsCache = [[{element: selectElement, label:''}]];
|
||||
|
||||
// find existing special options
|
||||
forEach(selectElement.children(), function(option) {
|
||||
if (option.value == '') {
|
||||
// developer declared null option, so user should be able to select it
|
||||
nullOption = jqLite(option).remove();
|
||||
// compile the element since there might be bindings in it
|
||||
$compile(nullOption)(scope);
|
||||
}
|
||||
});
|
||||
selectElement.html(''); // clear contents
|
||||
if (nullOption) {
|
||||
// compile the element since there might be bindings in it
|
||||
$compile(nullOption)(scope);
|
||||
|
||||
// remove the class, which is added automatically because we recompile the element and it
|
||||
// becomes the compilation root
|
||||
nullOption.removeClass('ng-scope');
|
||||
|
||||
// we need to remove it before calling selectElement.html('') because otherwise IE will
|
||||
// remove the label from the element. wtf?
|
||||
nullOption.remove();
|
||||
}
|
||||
|
||||
// clear contents, we'll add what's needed based on the model
|
||||
selectElement.html('');
|
||||
|
||||
selectElement.bind('change', function() {
|
||||
scope.$apply(function() {
|
||||
|
|
@ -250,8 +331,8 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) {
|
|||
if (multiple) {
|
||||
value = [];
|
||||
for (groupIndex = 0, groupLength = optionGroupsCache.length;
|
||||
groupIndex < groupLength;
|
||||
groupIndex++) {
|
||||
groupIndex < groupLength;
|
||||
groupIndex++) {
|
||||
// list of options for that group. (first item has the parent)
|
||||
optionGroup = optionGroupsCache[groupIndex];
|
||||
|
||||
|
|
@ -365,7 +446,7 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) {
|
|||
}
|
||||
}
|
||||
|
||||
lastElement = null; // start at the begining
|
||||
lastElement = null; // start at the beginning
|
||||
for(index = 0, length = optionGroup.length; index < length; index++) {
|
||||
option = optionGroup[index];
|
||||
if ((existingOption = existingOptions[index+1])) {
|
||||
|
|
@ -431,19 +512,34 @@ var optionDirective = ['$interpolate', function($interpolate) {
|
|||
return {
|
||||
restrict: 'E',
|
||||
priority: 100,
|
||||
require: '^select',
|
||||
compile: function(element, attr) {
|
||||
if (isUndefined(attr.value)) {
|
||||
var interpolateFn = $interpolate(element.text(), true);
|
||||
if (interpolateFn) {
|
||||
return function (scope, element, attr) {
|
||||
scope.$watch(interpolateFn, function(value) {
|
||||
attr.$set('value', value);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if (!interpolateFn) {
|
||||
attr.$set('value', element.text());
|
||||
}
|
||||
}
|
||||
|
||||
// For some reason Opera defaults to true and if not overridden this messes up the repeater.
|
||||
// We don't want the view to drive the initialization of the model anyway.
|
||||
element.prop('selected', false);
|
||||
|
||||
return function (scope, element, attr, selectCtrl) {
|
||||
if (interpolateFn) {
|
||||
scope.$watch(interpolateFn, function(newVal, oldVal) {
|
||||
attr.$set('value', newVal);
|
||||
if (newVal !== oldVal) selectCtrl.removeOption(oldVal);
|
||||
selectCtrl.addOption(newVal);
|
||||
});
|
||||
} else {
|
||||
selectCtrl.addOption(attr.value);
|
||||
}
|
||||
|
||||
element.bind('$destroy', function() {
|
||||
selectCtrl.removeOption(attr.value);
|
||||
});
|
||||
};
|
||||
}
|
||||
}
|
||||
}];
|
||||
|
|
|
|||
|
|
@ -10,14 +10,19 @@ describe('select', function() {
|
|||
scope.$apply();
|
||||
}
|
||||
|
||||
|
||||
beforeEach(inject(function($injector, $rootScope) {
|
||||
scope = $rootScope;
|
||||
$compile = $injector.get('$compile');
|
||||
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);
|
||||
});
|
||||
|
||||
|
||||
beforeEach(function() {
|
||||
this.addMatchers({
|
||||
toEqualSelect: function(expected){
|
||||
|
|
@ -38,11 +43,6 @@ describe('select', function() {
|
|||
});
|
||||
|
||||
|
||||
afterEach(function() {
|
||||
dealoc(formElement);
|
||||
});
|
||||
|
||||
|
||||
describe('select-one', function() {
|
||||
|
||||
it('should compile children of a select without a ngModel, but not create a model for it',
|
||||
|
|
@ -108,6 +108,267 @@ describe('select', function() {
|
|||
expect(element).toBeValid();
|
||||
expect(element).toBePristine();
|
||||
});
|
||||
|
||||
|
||||
it('should work with repeated value options', function() {
|
||||
scope.robots = ['c3p0', 'r2d2'];
|
||||
scope.robot = 'r2d2';
|
||||
compile('<select ng-model="robot">' +
|
||||
'<option ng-repeat="r in robots">{{r}}</option>' +
|
||||
'</select>');
|
||||
expect(element).toEqualSelect('c3p0', ['r2d2']);
|
||||
|
||||
browserTrigger(element.find('option').eq(0));
|
||||
expect(element).toEqualSelect(['c3p0'], 'r2d2');
|
||||
expect(scope.robot).toBe('c3p0');
|
||||
|
||||
scope.$apply(function() {
|
||||
scope.robots.unshift('wallee');
|
||||
});
|
||||
expect(element).toEqualSelect('wallee', ['c3p0'], 'r2d2');
|
||||
expect(scope.robot).toBe('c3p0');
|
||||
|
||||
scope.$apply(function() {
|
||||
scope.robots = ['c3p0+', 'r2d2+'];
|
||||
scope.robot = 'r2d2+';
|
||||
});
|
||||
expect(element).toEqualSelect('c3p0+', ['r2d2+']);
|
||||
expect(scope.robot).toBe('r2d2+');
|
||||
});
|
||||
|
||||
|
||||
describe('empty option', function() {
|
||||
|
||||
it('should select the empty option when model is undefined', function() {
|
||||
compile('<select ng-model="robot">' +
|
||||
'<option value="">--select--</option>' +
|
||||
'<option value="x">robot x</option>' +
|
||||
'<option value="y">robot y</option>' +
|
||||
'</select>');
|
||||
|
||||
expect(element).toEqualSelect([''], 'x', 'y');
|
||||
});
|
||||
|
||||
|
||||
it('should support defining an empty option anywhere in the option list', function() {
|
||||
compile('<select ng-model="robot">' +
|
||||
'<option value="x">robot x</option>' +
|
||||
'<option value="">--select--</option>' +
|
||||
'<option value="y">robot y</option>' +
|
||||
'</select>');
|
||||
|
||||
expect(element).toEqualSelect('x', [''], 'y');
|
||||
});
|
||||
|
||||
|
||||
it('should set the model to empty string when empty option is selected', function() {
|
||||
scope.robot = 'x';
|
||||
compile('<select ng-model="robot">' +
|
||||
'<option value="">--select--</option>' +
|
||||
'<option value="x">robot x</option>' +
|
||||
'<option value="y">robot y</option>' +
|
||||
'</select>');
|
||||
expect(element).toEqualSelect('', ['x'], 'y');
|
||||
|
||||
browserTrigger(element.find('option').eq(0));
|
||||
expect(element).toEqualSelect([''], 'x', 'y');
|
||||
expect(scope.robot).toBe('');
|
||||
});
|
||||
|
||||
|
||||
describe('interactions with repeated options', function() {
|
||||
|
||||
it('should select empty option when model is undefined', function() {
|
||||
scope.robots = ['c3p0', 'r2d2'];
|
||||
compile('<select ng-model="robot">' +
|
||||
'<option value="">--select--</option>' +
|
||||
'<option ng-repeat="r in robots">{{r}}</option>' +
|
||||
'</select>');
|
||||
expect(element).toEqualSelect([''], 'c3p0', 'r2d2');
|
||||
});
|
||||
|
||||
|
||||
it('should set model to empty string when selected', function() {
|
||||
scope.robots = ['c3p0', 'r2d2'];
|
||||
compile('<select ng-model="robot">' +
|
||||
'<option value="">--select--</option>' +
|
||||
'<option ng-repeat="r in robots">{{r}}</option>' +
|
||||
'</select>');
|
||||
|
||||
browserTrigger(element.find('option').eq(1));
|
||||
expect(element).toEqualSelect('', ['c3p0'], 'r2d2');
|
||||
expect(scope.robot).toBe('c3p0');
|
||||
|
||||
browserTrigger(element.find('option').eq(0));
|
||||
expect(element).toEqualSelect([''], 'c3p0', 'r2d2');
|
||||
expect(scope.robot).toBe('');
|
||||
});
|
||||
|
||||
|
||||
it('should not break if both the select and repeater models change at once', function() {
|
||||
scope.robots = ['c3p0', 'r2d2'];
|
||||
scope.robot = 'c3p0'
|
||||
compile('<select ng-model="robot">' +
|
||||
'<option value="">--select--</option>' +
|
||||
'<option ng-repeat="r in robots">{{r}}</option>' +
|
||||
'</select>');
|
||||
expect(element).toEqualSelect('', ['c3p0'], 'r2d2');
|
||||
|
||||
scope.$apply(function() {
|
||||
scope.robots = ['wallee'];
|
||||
scope.robot = '';
|
||||
});
|
||||
|
||||
expect(element).toEqualSelect([''], 'wallee');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('unknown option', function() {
|
||||
|
||||
it("should insert&select temporary unknown option when no options-model match", function() {
|
||||
compile('<select ng-model="robot">' +
|
||||
'<option>c3p0</option>' +
|
||||
'<option>r2d2</option>' +
|
||||
'</select>');
|
||||
|
||||
expect(element).toEqualSelect(['? undefined:undefined ?'], 'c3p0', 'r2d2');
|
||||
|
||||
scope.$apply(function() {
|
||||
scope.robot = 'r2d2';
|
||||
});
|
||||
expect(element).toEqualSelect('c3p0', ['r2d2']);
|
||||
|
||||
|
||||
scope.$apply(function() {
|
||||
scope.robot = "wallee";
|
||||
});
|
||||
expect(element).toEqualSelect(['? string:wallee ?'], 'c3p0', 'r2d2');
|
||||
});
|
||||
|
||||
|
||||
it("should NOT insert temporary unknown option when model is undefined and empty options " +
|
||||
"is present", function() {
|
||||
compile('<select ng-model="robot">' +
|
||||
'<option value="">--select--</option>' +
|
||||
'<option>c3p0</option>' +
|
||||
'<option>r2d2</option>' +
|
||||
'</select>');
|
||||
|
||||
expect(element).toEqualSelect([''], 'c3p0', 'r2d2');
|
||||
expect(scope.robot).toBeUndefined();
|
||||
|
||||
scope.$apply(function() {
|
||||
scope.robot = null;
|
||||
});
|
||||
expect(element).toEqualSelect(['? object:null ?'], '', 'c3p0', 'r2d2');
|
||||
|
||||
scope.$apply(function() {
|
||||
scope.robot = 'r2d2';
|
||||
});
|
||||
expect(element).toEqualSelect('', 'c3p0', ['r2d2']);
|
||||
|
||||
scope.$apply(function() {
|
||||
delete scope.robot;
|
||||
});
|
||||
expect(element).toEqualSelect([''], 'c3p0', 'r2d2');
|
||||
});
|
||||
|
||||
|
||||
it("should insert&select temporary unknown option when no options-model match, empty " +
|
||||
"option is present and model is defined", function() {
|
||||
scope.robot = 'wallee';
|
||||
compile('<select ng-model="robot">' +
|
||||
'<option value="">--select--</option>' +
|
||||
'<option>c3p0</option>' +
|
||||
'<option>r2d2</option>' +
|
||||
'</select>');
|
||||
|
||||
expect(element).toEqualSelect(['? string:wallee ?'], '', 'c3p0', 'r2d2');
|
||||
|
||||
scope.$apply(function() {
|
||||
scope.robot = 'r2d2';
|
||||
});
|
||||
expect(element).toEqualSelect('', 'c3p0', ['r2d2']);
|
||||
});
|
||||
|
||||
|
||||
describe('interactions with repeated options', function() {
|
||||
|
||||
it('should work with repeated options', function() {
|
||||
compile('<select ng-model="robot">' +
|
||||
'<option ng-repeat="r in robots">{{r}}</option>' +
|
||||
'</select>');
|
||||
expect(element).toEqualSelect(['? undefined:undefined ?']);
|
||||
expect(scope.robot).toBeUndefined();
|
||||
|
||||
scope.$apply(function() {
|
||||
scope.robot = 'r2d2';
|
||||
});
|
||||
expect(element).toEqualSelect(['? string:r2d2 ?']);
|
||||
expect(scope.robot).toBe('r2d2');
|
||||
|
||||
scope.$apply(function() {
|
||||
scope.robots = ['c3p0', 'r2d2'];
|
||||
});
|
||||
expect(element).toEqualSelect('c3p0', ['r2d2']);
|
||||
expect(scope.robot).toBe('r2d2');
|
||||
});
|
||||
|
||||
|
||||
it('should work with empty option and repeated options', function() {
|
||||
compile('<select ng-model="robot">' +
|
||||
'<option value="">--select--</option>' +
|
||||
'<option ng-repeat="r in robots">{{r}}</option>' +
|
||||
'</select>');
|
||||
expect(element).toEqualSelect(['']);
|
||||
expect(scope.robot).toBeUndefined();
|
||||
|
||||
scope.$apply(function() {
|
||||
scope.robot = 'r2d2';
|
||||
});
|
||||
expect(element).toEqualSelect(['? string:r2d2 ?'], '');
|
||||
expect(scope.robot).toBe('r2d2');
|
||||
|
||||
scope.$apply(function() {
|
||||
scope.robots = ['c3p0', 'r2d2'];
|
||||
});
|
||||
expect(element).toEqualSelect('', 'c3p0', ['r2d2']);
|
||||
expect(scope.robot).toBe('r2d2');
|
||||
});
|
||||
|
||||
|
||||
it('should insert unknown element when repeater shrinks and selected option is unavailable',
|
||||
function() {
|
||||
scope.robots = ['c3p0', 'r2d2'];
|
||||
scope.robot = 'r2d2';
|
||||
compile('<select ng-model="robot">' +
|
||||
'<option ng-repeat="r in robots">{{r}}</option>' +
|
||||
'</select>');
|
||||
expect(element).toEqualSelect('c3p0', ['r2d2']);
|
||||
expect(scope.robot).toBe('r2d2');
|
||||
|
||||
scope.$apply(function() {
|
||||
scope.robots.pop();
|
||||
});
|
||||
expect(element).toEqualSelect(['? string:r2d2 ?'], 'c3p0');
|
||||
expect(scope.robot).toBe('r2d2');
|
||||
|
||||
scope.$apply(function() {
|
||||
scope.robots.unshift('r2d2');
|
||||
});
|
||||
expect(element).toEqualSelect(['r2d2'], 'c3p0');
|
||||
expect(scope.robot).toBe('r2d2');
|
||||
|
||||
scope.$apply(function() {
|
||||
delete scope.robots;
|
||||
});
|
||||
expect(element).toEqualSelect(['? string:r2d2 ?']);
|
||||
expect(scope.robot).toBe('r2d2');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
|
@ -840,24 +1101,18 @@ describe('select', function() {
|
|||
|
||||
it('should populate value attribute on OPTION', function() {
|
||||
compile('<select ng-model="x"><option selected>abc</option></select>');
|
||||
expect(element).toEqualSelect('abc');
|
||||
expect(element).toEqualSelect(['? undefined:undefined ?'], 'abc');
|
||||
});
|
||||
|
||||
it('should ignore value if already exists', function() {
|
||||
compile('<select ng-model="x"><option value="abc">xyz</option></select>');
|
||||
expect(element).toEqualSelect('abc');
|
||||
});
|
||||
|
||||
it('should set value even if newlines present', function() {
|
||||
compile('<select ng-model="x"><option attr="\ntext\n" \n>\nabc\n</option></select>');
|
||||
expect(element).toEqualSelect('\nabc\n');
|
||||
expect(element).toEqualSelect(['? undefined:undefined ?'], 'abc');
|
||||
});
|
||||
|
||||
it('should set value even if self closing HTML', function() {
|
||||
// IE removes the \n from option, which makes this test pointless
|
||||
if (msie) return;
|
||||
compile('<select ng-model="x"><option>\n</option></select>');
|
||||
expect(element).toEqualSelect('\n');
|
||||
scope.x = 'hello'
|
||||
compile('<select ng-model="x"><option>hello</select>');
|
||||
expect(element).toEqualSelect(['hello']);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue