angular.js/test/ng/directive/selectSpec.js
Peter Bacon Darwin 7a586e5c19 fix(*): protect calls to hasOwnProperty in public API
Objects received from outside AngularJS may have had their `hasOwnProperty`
method overridden with something else. In cases where we can do this without
incurring a performance penalty we call directly on Object.prototype.hasOwnProperty
to ensure that we use the correct method.

Also, we have some internal hash objects, where the keys for the map are provided
from outside AngularJS. In such cases we either prevent `hasOwnProperty` from
being used as a key or provide some other way of preventing our objects from
having their `hasOwnProperty` overridden.

BREAKING CHANGE: Inputs with name equal to "hasOwnProperty" are not allowed inside
form or ngForm directives.

Before, inputs whose name was "hasOwnProperty" were quietly ignored and not added
to the scope.  Now a badname exception is thrown.

Using "hasOwnProperty" for an input name would be very unusual and bad practice.
Either do not include such an input in a `form` or `ngForm` directive or change
the name of the input.

Closes #3331
2013-10-07 09:01:13 -07:00

1261 lines
38 KiB
JavaScript

'use strict';
describe('select', function() {
var scope, formElement, element, $compile;
function compile(html) {
formElement = jqLite('<form name="form">' + html + '</form>');
element = formElement.find('select');
$compile(formElement)(scope);
scope.$apply();
}
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){
var actualValues = [],
expectedValues = [].slice.call(arguments);
forEach(this.actual.find('option'), function(option) {
actualValues.push(option.selected ? [option.value] : option.value);
});
this.message = function() {
return 'Expected ' + toJson(actualValues) + ' to equal ' + toJson(expectedValues) + '.';
};
return equals(expectedValues, actualValues);
}
});
});
describe('select-one', function() {
it('should compile children of a select without a ngModel, but not create a model for it',
function() {
compile('<select>' +
'<option selected="true">{{a}}</option>' +
'<option value="">{{b}}</option>' +
'<option>C</option>' +
'</select>');
scope.$apply(function() {
scope.a = 'foo';
scope.b = 'bar';
});
expect(element.text()).toBe('foobarC');
});
it('should not interfere with selection via selected attr if ngModel directive is not present',
function() {
compile('<select>' +
'<option>not me</option>' +
'<option selected>me!</option>' +
'<option>nah</option>' +
'</select>');
expect(element).toEqualSelect('not me', ['me!'], 'nah');
});
it('should require', function() {
compile(
'<select name="select" ng-model="selection" required ng-change="change()">' +
'<option value=""></option>' +
'<option value="c">C</option>' +
'</select>');
scope.change = function() {
scope.log += 'change;';
};
scope.$apply(function() {
scope.log = '';
scope.selection = 'c';
});
expect(scope.form.select.$error.required).toBeFalsy();
expect(element).toBeValid();
expect(element).toBePristine();
scope.$apply(function() {
scope.selection = '';
});
expect(scope.form.select.$error.required).toBeTruthy();
expect(element).toBeInvalid();
expect(element).toBePristine();
expect(scope.log).toEqual('');
element[0].value = 'c';
browserTrigger(element, 'change');
expect(element).toBeValid();
expect(element).toBeDirty();
expect(scope.log).toEqual('change;');
});
it('should not be invalid if no require', function() {
compile(
'<select name="select" ng-model="selection">' +
'<option value=""></option>' +
'<option value="c">C</option>' +
'</select>');
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');
});
});
});
});
describe('select-multiple', function() {
it('should support type="select-multiple"', function() {
compile(
'<select ng-model="selection" multiple>' +
'<option>A</option>' +
'<option>B</option>' +
'</select>');
scope.$apply(function() {
scope.selection = ['A'];
});
expect(element).toEqualSelect(['A'], 'B');
scope.$apply(function() {
scope.selection.push('B');
});
expect(element).toEqualSelect(['A'], ['B']);
});
it('should work with optgroups', function() {
compile('<select ng-model="selection" multiple>' +
'<optgroup label="group1">' +
'<option>A</option>' +
'<option>B</option>' +
'</optgroup>' +
'</select>');
expect(element).toEqualSelect('A', 'B');
expect(scope.selection).toBeUndefined();
scope.$apply(function() {
scope.selection = ['A'];
});
expect(element).toEqualSelect(['A'], 'B');
scope.$apply(function() {
scope.selection.push('B');
});
expect(element).toEqualSelect(['A'], ['B']);
});
it('should require', function() {
compile(
'<select name="select" ng-model="selection" multiple required>' +
'<option>A</option>' +
'<option>B</option>' +
'</select>');
scope.$apply(function() {
scope.selection = [];
});
expect(scope.form.select.$error.required).toBeTruthy();
expect(element).toBeInvalid();
expect(element).toBePristine();
scope.$apply(function() {
scope.selection = ['A'];
});
expect(element).toBeValid();
expect(element).toBePristine();
element[0].value = 'B';
browserTrigger(element, 'change');
expect(element).toBeValid();
expect(element).toBeDirty();
});
});
describe('ngOptions', function() {
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 render a list', function() {
createSingleSelect();
scope.$apply(function() {
scope.values = [{name: 'A'}, {name: 'B'}, {name: 'C'}];
scope.selected = scope.values[0];
});
var options = element.find('option');
expect(options.length).toEqual(3);
expect(sortedHtml(options[0])).toEqual('<option value="0">A</option>');
expect(sortedHtml(options[1])).toEqual('<option value="1">B</option>');
expect(sortedHtml(options[2])).toEqual('<option value="2">C</option>');
});
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(sortedHtml(options[0])).toEqual('<option value="0">0</option>');
expect(sortedHtml(options[1])).toEqual('<option value="1">1</option>');
expect(sortedHtml(options[2])).toEqual('<option value="2">2</option>');
});
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.red;
});
var options = element.find('option');
expect(options.length).toEqual(3);
expect(sortedHtml(options[0])).toEqual('<option value="blue">blue</option>');
expect(sortedHtml(options[1])).toEqual('<option value="green">green</option>');
expect(sortedHtml(options[2])).toEqual('<option value="red">red</option>');
expect(options[2].selected).toEqual(true);
scope.$apply(function() {
scope.object.azur = '8888FF';
});
options = element.find('option');
expect(options[3].selected).toEqual(true);
});
it('should grow list', function() {
createSingleSelect();
scope.$apply(function() {
scope.values = [];
});
expect(element.find('option').length).toEqual(1); // because we add special empty option
expect(sortedHtml(element.find('option')[0])).toEqual('<option value="?"></option>');
scope.$apply(function() {
scope.values.push({name:'A'});
scope.selected = scope.values[0];
});
expect(element.find('option').length).toEqual(1);
expect(sortedHtml(element.find('option')[0])).toEqual('<option value="0">A</option>');
scope.$apply(function() {
scope.values.push({name:'B'});
});
expect(element.find('option').length).toEqual(2);
expect(sortedHtml(element.find('option')[0])).toEqual('<option value="0">A</option>');
expect(sortedHtml(element.find('option')[1])).toEqual('<option value="1">B</option>');
});
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(sortedHtml(element.find('option')[0])).toEqual('<option value="0">A</option>');
expect(sortedHtml(element.find('option')[1])).toEqual('<option value="1">B</option>');
scope.$apply(function() {
scope.values.pop();
});
expect(element.find('option').length).toEqual(1);
expect(sortedHtml(element.find('option')[0])).toEqual('<option value="0">A</option>');
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(sortedHtml(options[0])).toEqual('<option value="0">B</option>');
expect(sortedHtml(options[1])).toEqual('<option value="1">C</option>');
expect(sortedHtml(options[2])).toEqual('<option value="2">D</option>');
});
it('should preserve existing options', 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(sortedHtml(options[0])).toEqual('<option value="regularProperty">visible</option>');
});
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.val()).toEqual('0');
scope.$apply(function() {
scope.selected = scope.values[1];
});
expect(element.val()).toEqual('1');
});
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: 'first'},
{name: 'C', group: 'second'},
{name: 'D', group: 'first'},
{name: 'E', group: 'second'}];
scope.selected = scope.values[3];
});
expect(element.val()).toEqual('3');
var first = jqLite(element.find('optgroup')[0]);
var b = jqLite(first.find('option')[0]);
var d = jqLite(first.find('option')[1]);
expect(first.attr('label')).toEqual('first');
expect(b.text()).toEqual('B');
expect(d.text()).toEqual('D');
var second = jqLite(element.find('optgroup')[1]);
var c = jqLite(second.find('option')[0]);
var e = jqLite(second.find('option')[1]);
expect(second.attr('label')).toEqual('second');
expect(c.text()).toEqual('C');
expect(e.text()).toEqual('E');
scope.$apply(function() {
scope.selected = scope.values[0];
});
expect(element.val()).toEqual('0');
});
it('should bind to scope value and track/identify objects', function() {
createSelect({
'ng-model': 'selected',
'ng-options': 'item as 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 = {id: 2};
});
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 experession', 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.val()).toEqual('0');
scope.$apply(function() {
scope.selected = scope.values[1].id;
});
expect(element.val()).toEqual('1');
});
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.val()).toEqual('green');
scope.$apply(function() {
scope.selected = 'blue';
});
expect(element.val()).toEqual('blue');
});
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.val()).toEqual('green');
scope.$apply(function() {
scope.selected = '0000FF';
});
expect(element.val()).toEqual('blue');
});
it('should insert a blank 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.val()).toEqual('0');
expect(element.find('option').length).toEqual(1);
});
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.val()).toEqual('0');
expect(element.find('option').length).toEqual(2);
});
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()).toEqual('?');
expect(jqLite(element.find('option')[0]).val()).toEqual('?');
scope.$apply(function() {
scope.selected = scope.values[0];
});
expect(element.val()).toEqual('0');
expect(element.find('option').length).toEqual(1);
});
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()).toEqual('?');
expect(element.find('option').eq(0).val()).toEqual('?');
browserTrigger(element.find('option').eq(1));
expect(element.val()).toEqual('0');
expect(element.find('option').eq(0).prop('selected')).toBeTruthy();
expect(element.find('option').length).toEqual(2);
});
});
describe('blank 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 biding 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 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);
});
});
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.val()).toEqual('0');
element.val('1');
browserTrigger(element, 'change');
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.val()).toEqual('0');
element.val('1');
browserTrigger(element, 'change');
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('0');
});
element.val('');
browserTrigger(element, 'change');
expect(scope.selected).toEqual(null);
});
});
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);
})
});
describe('ngRequired', function() {
it('should allow bindings on 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;
});
element.val('');
browserTrigger(element, 'change');
expect(element).toBeValid();
scope.$apply(function() {
scope.required = true;
});
expect(element).toBeInvalid();
scope.$apply(function() {
scope.value = scope.values[0];
});
expect(element).toBeValid();
element.val('');
browserTrigger(element, 'change');
expect(element).toBeInvalid();
scope.$apply(function() {
scope.required = false;
});
expect(element).toBeValid();
});
});
});
describe('option', function() {
it('should populate value attribute on OPTION', function() {
compile('<select ng-model="x"><option selected>abc</option></select>');
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(['? undefined:undefined ?'], 'abc');
});
it('should set value even if self closing HTML', function() {
scope.x = 'hello'
compile('<select ng-model="x"><option>hello</select>');
expect(element).toEqualSelect(['hello']);
});
it('should not blow up when option directive is found inside of a datalist',
inject(function($compile, $rootScope) {
var element = $compile('<div>' +
'<datalist><option>some val</option></datalist>' +
'<span>{{foo}}</span>' +
'</div>')($rootScope);
$rootScope.foo = 'success';
$rootScope.$digest();
expect(element.find('span').text()).toBe('success');
dealoc(element);
}));
it('should throw an exception if an option value interpolates to "hasOwnProperty"', function() {
scope.hasOwnPropertyOption = "hasOwnProperty";
expect(function() {
compile('<select ng-model="x">'+
'<option>{{hasOwnPropertyOption}}</option>'+
'</select>');
}).toThrowMinErr('ng','badname', 'hasOwnProperty is not a valid "option value" name');
});
});
});