mirror of
https://github.com/Hopiu/angular.js.git
synced 2026-03-16 23:30:23 +00:00
parent
89e001b18a
commit
af285dd370
9 changed files with 498 additions and 159 deletions
|
|
@ -3,15 +3,19 @@
|
|||
|
||||
### New Features
|
||||
- Added prepend() to jqLite
|
||||
- Added ng:options directive (http://docs.angularjs.org/#!angular.directive.ng:options)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
- Number filter would return incorrect value when fractional part had leading zeros.
|
||||
|
||||
|
||||
### Breaking changes
|
||||
- $service now has $service.invoke for method injection ($service(self, fn) no longer works)
|
||||
- injection name inference no longer supports method curry and linking functions. Both must be
|
||||
explicitly specified using $inject property.
|
||||
- Dynamic Iteration (ng:repeater) on <option> elements is no longer supported. Use ng:options
|
||||
|
||||
|
||||
|
||||
<a name="0.9.16"><a/>
|
||||
|
|
|
|||
|
|
@ -52,8 +52,9 @@ if ('i' !== 'I'.toLowerCase()) {
|
|||
function fromCharCode(code) { return String.fromCharCode(code); }
|
||||
|
||||
|
||||
var $$element = '$element',
|
||||
$$update = '$update',
|
||||
var _undefined = undefined,
|
||||
_null = null,
|
||||
$$element = '$element',
|
||||
$$scope = '$scope',
|
||||
$$validate = '$validate',
|
||||
$angular = 'angular',
|
||||
|
|
|
|||
|
|
@ -443,10 +443,8 @@ var REMOVE_ATTRIBUTES = {
|
|||
angularDirective("ng:bind-attr", function(expression){
|
||||
return function(element){
|
||||
var lastValue = {};
|
||||
var updateFn = element.data($$update) || noop;
|
||||
this.$onEval(function(){
|
||||
var values = this.$eval(expression),
|
||||
dirty = noop;
|
||||
var values = this.$eval(expression);
|
||||
for(var key in values) {
|
||||
var value = compileBindTemplate(values[key]).call(this, element),
|
||||
specialName = REMOVE_ATTRIBUTES[lowercase(key)];
|
||||
|
|
@ -464,10 +462,8 @@ angularDirective("ng:bind-attr", function(expression){
|
|||
} else {
|
||||
element.attr(key, value);
|
||||
}
|
||||
dirty = updateFn;
|
||||
}
|
||||
}
|
||||
dirty();
|
||||
}, element);
|
||||
};
|
||||
});
|
||||
|
|
|
|||
|
|
@ -204,6 +204,7 @@ angularFormatter.trim = formatter(
|
|||
* @workInProgress
|
||||
* @ngdoc formatter
|
||||
* @name angular.formatter.index
|
||||
* @deprecated
|
||||
* @description
|
||||
* Index formatter is meant to be used with `select` input widget. It is useful when one needs
|
||||
* to select from a set of objects. To create pull-down one can iterate over the array of object
|
||||
|
|
@ -250,6 +251,7 @@ angularFormatter.trim = formatter(
|
|||
</doc:scenario>
|
||||
</doc:example>
|
||||
*/
|
||||
//TODO: delete me since this is replaced by ng:options
|
||||
angularFormatter.index = formatter(
|
||||
function(object, array){
|
||||
return '' + indexOf(array || [], object);
|
||||
|
|
|
|||
|
|
@ -239,6 +239,8 @@ function parser(text, json){
|
|||
pipeFunction =
|
||||
function (){ throwError("is not valid json", {text:text, index:0}); };
|
||||
}
|
||||
//TODO: Shouldn't all of the public methods have assertAllConsumed?
|
||||
//TODO: I think these should be public as part of the parser api instead of scope.$eval().
|
||||
return {
|
||||
assignable: assertConsumed(assignable),
|
||||
primary: assertConsumed(primary),
|
||||
|
|
@ -659,4 +661,3 @@ function parser(text, json){
|
|||
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
288
src/widgets.js
288
src/widgets.js
|
|
@ -166,18 +166,19 @@
|
|||
|
||||
function modelAccessor(scope, element) {
|
||||
var expr = element.attr('name');
|
||||
var assign;
|
||||
var exprFn, assignFn;
|
||||
if (expr) {
|
||||
assign = parser(expr).assignable().assign;
|
||||
if (!assign) throw new Error("Expression '" + expr + "' is not assignable.");
|
||||
exprFn = parser(expr).assignable();
|
||||
assignFn = exprFn.assign;
|
||||
if (!assignFn) throw new Error("Expression '" + expr + "' is not assignable.");
|
||||
return {
|
||||
get: function() {
|
||||
return scope.$eval(expr);
|
||||
return exprFn(scope);
|
||||
},
|
||||
set: function(value) {
|
||||
if (value !== undefined) {
|
||||
return scope.$tryEval(function(){
|
||||
assign(scope, value);
|
||||
assignFn(scope, value);
|
||||
}, element);
|
||||
}
|
||||
}
|
||||
|
|
@ -561,64 +562,241 @@ function inputWidgetSelector(element){
|
|||
angularWidget('input', inputWidgetSelector);
|
||||
angularWidget('textarea', inputWidgetSelector);
|
||||
angularWidget('button', inputWidgetSelector);
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc directive
|
||||
* @name angular.directive.ng:options
|
||||
*
|
||||
* @description
|
||||
* Dynamically generate a list of `<option>` elements for a `<select>` element using the array
|
||||
* obtained by evaluating the `ng:options` expression.
|
||||
*
|
||||
* When an item in the select menu is select, the array element represented by the selected option
|
||||
* will be bound to the model identified by the `name` attribute of the parent select element.
|
||||
*
|
||||
* Optionally, a single hard-coded `<option>` element, with the value set to an empty string, can
|
||||
* be nested into the `<select>` element. This element will then represent `null` or "not selected"
|
||||
* option. See example below for demonstration.
|
||||
*
|
||||
* Note: `ng:options` provides iterator facility for `<option>` element which must be used instead
|
||||
* of {@link angular.widget.@ng:repeat ng:repeat}. `ng:repeat` 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.widget.@ng:repeat ng:repeat} unrolls after the select binds causing
|
||||
* incorect rendering on most browsers.
|
||||
* * binding to a value not in list confuses most browsers.
|
||||
*
|
||||
* @element select
|
||||
* @param {comprehension_expression} comprehension _expresion_ `for` _item_ `in` _array_.
|
||||
*
|
||||
* * _array_: an expression which evaluates to an array of objects to bind.
|
||||
* * _item_: local variable which will reffer to the item in the _array_ during the itteration
|
||||
* * _expression_: The result of this expression will is `option` label. The
|
||||
* `expression` most likely reffers to the _item_ varibale.
|
||||
*
|
||||
* @example
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
<script>
|
||||
function MyCntrl(){
|
||||
this.colors = [
|
||||
{name:'black'},
|
||||
{name:'white'},
|
||||
{name:'red'},
|
||||
{name:'blue'},
|
||||
{name:'green'}
|
||||
];
|
||||
this.color = this.colors[2]; // red
|
||||
}
|
||||
</script>
|
||||
<div ng:controller="MyCntrl">
|
||||
<ul>
|
||||
<li ng:repeat="color in colors">
|
||||
Name: <input name="color.name"/> [<a href ng:click="colors.$remove(color)">X</a>]
|
||||
</li>
|
||||
<li>
|
||||
[<a href ng:click="colors.push({})">add</a>]
|
||||
</li>
|
||||
</ul>
|
||||
<hr/>
|
||||
Color (null not allowed):
|
||||
<select name="color" ng:options="c.name for c in colors"></select><br/>
|
||||
|
||||
Color (null allowed):
|
||||
<select name="color" ng:options="c.name for c in colors">
|
||||
<option value="">-- chose color --</option>
|
||||
</select><br/>
|
||||
|
||||
Select <a href ng:click="color={name:'not in list'}">bogus</a>. <br/>
|
||||
<hr/>
|
||||
Currently selected: {{ {selected_color:color} }}
|
||||
<div style="border:solid 1px black;"
|
||||
ng:style="{'background-color':color.name}">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('should check ng:options', function(){
|
||||
expect(binding('color')).toMatch('red');
|
||||
select('color').option('0');
|
||||
expect(binding('color')).toMatch('black');
|
||||
select('color').option('');
|
||||
expect(binding('color')).toMatch('null');
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
*/
|
||||
|
||||
var NG_OPTIONS_REGEXP = /^(.*)\s+for\s+([\$\w][\$\w\d]*)\s+in\s+(.*)$/;
|
||||
angularWidget('select', function(element){
|
||||
this.descend(true);
|
||||
return inputWidgetSelector.call(this, element);
|
||||
});
|
||||
|
||||
|
||||
/*
|
||||
* Consider this:
|
||||
* <select name="selection">
|
||||
* <option ng:repeat="x in [1,2]">{{x}}</option>
|
||||
* </select>
|
||||
*
|
||||
* The issue is that the select gets evaluated before option is unrolled.
|
||||
* This means that the selection is undefined, but the browser
|
||||
* default behavior is to show the top selection in the list.
|
||||
* To fix that we register a $update function on the select element
|
||||
* and the option creation then calls the $update function when it is
|
||||
* unrolled. The $update function then calls this update function, which
|
||||
* then tries to determine if the model is unassigned, and if so it tries to
|
||||
* chose one of the options from the list.
|
||||
*/
|
||||
angularWidget('option', function(){
|
||||
this.descend(true);
|
||||
this.directives(true);
|
||||
return function(option) {
|
||||
var select = option.parent();
|
||||
var isMultiple = select[0].type == 'select-multiple';
|
||||
var scope = select.scope();
|
||||
var model = modelAccessor(scope, select);
|
||||
var isMultiselect = element.attr('multiple');
|
||||
var expression = element.attr('ng:options');
|
||||
var match;
|
||||
if (!expression) {
|
||||
return inputWidgetSelector.call(this, element);
|
||||
}
|
||||
if (! (match = expression.match(NG_OPTIONS_REGEXP))) {
|
||||
throw Error(
|
||||
"Expected ng:options in form of '_expresion_ for _item_ in _collection_' but got '" +
|
||||
expression + "'.");
|
||||
}
|
||||
var displayFn = expressionCompile(match[1]).fnSelf;
|
||||
var itemName = match[2];
|
||||
var collectionFn = expressionCompile(match[3]).fnSelf;
|
||||
// we can't just jqLite('<option>') since jqLite is not smart enough
|
||||
// to create it in <select> and IE barfs otherwise.
|
||||
var option = jqLite(document.createElement('option'));
|
||||
return function(select){
|
||||
var scope = this;
|
||||
var optionElements = [];
|
||||
var optionTexts = [];
|
||||
var lastSelectValue = isMultiselect ? {} : false;
|
||||
var nullOption = option.clone().val('');
|
||||
var missingOption = option.clone().val('?');
|
||||
var model = modelAccessor(scope, element);
|
||||
|
||||
//if parent select doesn't have a name, don't bother doing anything any more
|
||||
if (!model) return;
|
||||
// find existing special options
|
||||
forEach(select.children(), function(option){
|
||||
if (option.value == '') nullOption = false;
|
||||
});
|
||||
|
||||
var formattedModel = modelFormattedAccessor(scope, select);
|
||||
var view = isMultiple
|
||||
? optionsAccessor(scope, select)
|
||||
: valueAccessor(scope, select);
|
||||
var lastValue = option.attr($value);
|
||||
var wasSelected = option.attr('ng-' + $selected);
|
||||
option.data($$update, isMultiple
|
||||
? function(){
|
||||
view.set(model.get());
|
||||
select.bind('change', function(){
|
||||
var collection = collectionFn(scope) || [];
|
||||
var value = select.val();
|
||||
var index, length;
|
||||
if (isMultiselect) {
|
||||
value = [];
|
||||
for (index = 0, length = optionElements.length; index < length; index++) {
|
||||
if (optionElements[index][0].selected) {
|
||||
value.push(collection[index]);
|
||||
}
|
||||
}
|
||||
: function(){
|
||||
var currentValue = option.attr($value);
|
||||
var isSelected = option.attr('ng-' + $selected);
|
||||
var modelValue = model.get();
|
||||
if (wasSelected != isSelected || lastValue != currentValue) {
|
||||
wasSelected = isSelected;
|
||||
lastValue = currentValue;
|
||||
if (isSelected || !modelValue == null || modelValue == undefined )
|
||||
formattedModel.set(currentValue);
|
||||
if (currentValue == modelValue) {
|
||||
view.set(lastValue);
|
||||
} else {
|
||||
if (value == '?') {
|
||||
value = undefined;
|
||||
} else {
|
||||
value = (value == '' ? null : collection[value]);
|
||||
}
|
||||
}
|
||||
if (!isUndefined(value)) model.set(value);
|
||||
scope.$tryEval(function(){
|
||||
scope.$root.$eval();
|
||||
});
|
||||
});
|
||||
|
||||
scope.$onEval(function(){
|
||||
var scope = this;
|
||||
var collection = collectionFn(scope) || [];
|
||||
var value;
|
||||
var length;
|
||||
var fragment;
|
||||
var index;
|
||||
var optionText;
|
||||
var optionElement;
|
||||
var optionScope = scope.$new();
|
||||
var modelValue = model.get();
|
||||
var currentItem;
|
||||
var selectValue = '';
|
||||
var isMulti = isMultiselect;
|
||||
|
||||
if (isMulti) {
|
||||
selectValue = new HashMap();
|
||||
if (modelValue && isNumber(length = modelValue.length)) {
|
||||
for (index = 0; index < length; index++) {
|
||||
selectValue.put(modelValue[index], true);
|
||||
}
|
||||
}
|
||||
}
|
||||
try {
|
||||
for (index = 0, length = collection.length; index < length; index++) {
|
||||
currentItem = optionScope[itemName] = collection[index];
|
||||
optionText = displayFn(optionScope);
|
||||
if (optionTexts.length > index) {
|
||||
// reuse
|
||||
optionElement = optionElements[index];
|
||||
if (optionText != optionTexts[index]) {
|
||||
(optionElement).text(optionTexts[index] = optionText);
|
||||
}
|
||||
} else {
|
||||
// grow
|
||||
if (!fragment) {
|
||||
fragment = document.createDocumentFragment();
|
||||
}
|
||||
optionTexts.push(optionText);
|
||||
optionElements.push(optionElement = option.clone());
|
||||
optionElement.attr('value', index).text(optionText);
|
||||
fragment.appendChild(optionElement[0]);
|
||||
}
|
||||
if (isMulti) {
|
||||
if (lastSelectValue[index] != (value = selectValue.remove(currentItem))) {
|
||||
optionElement[0].selected = !!(lastSelectValue[index] = value);
|
||||
}
|
||||
} else {
|
||||
if (modelValue == currentItem) {
|
||||
selectValue = index;
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
if (fragment) select.append(jqLite(fragment));
|
||||
// shrink children
|
||||
while(optionElements.length > index) {
|
||||
optionElements.pop().remove();
|
||||
delete lastSelectValue[optionElements.length];
|
||||
}
|
||||
|
||||
if (!isMulti) {
|
||||
if (selectValue === '' && modelValue) {
|
||||
// We could not find a match
|
||||
selectValue = '?';
|
||||
}
|
||||
|
||||
// update the selected item
|
||||
if (lastSelectValue !== selectValue) {
|
||||
if (nullOption) {
|
||||
if (lastSelectValue == '') nullOption.remove();
|
||||
if (selectValue === '') select.prepend(nullOption);
|
||||
}
|
||||
|
||||
if (missingOption) {
|
||||
if (lastSelectValue == '?') missingOption.remove();
|
||||
if (selectValue === '?') select.prepend(missingOption);
|
||||
}
|
||||
|
||||
select.val(lastSelectValue = selectValue);
|
||||
}
|
||||
}
|
||||
|
||||
} finally {
|
||||
optionScope = null;
|
||||
}
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
|
|
@ -932,7 +1110,7 @@ angularWidget('@ng:repeat', function(expression, element){
|
|||
var match = expression.match(/^\s*(.+)\s+in\s+(.*)\s*$/),
|
||||
lhs, rhs, valueIdent, keyIdent;
|
||||
if (! match) {
|
||||
throw Error("Expected ng:repeat in form of 'item in collection' but got '" +
|
||||
throw Error("Expected ng:repeat in form of '_item_ in _collection_' but got '" +
|
||||
expression + "'.");
|
||||
}
|
||||
lhs = match[1];
|
||||
|
|
|
|||
|
|
@ -463,12 +463,6 @@ describe('Binder', function(){
|
|||
assertEquals('123{{a}}{{b}}{{c}}', scope.$element.text());
|
||||
});
|
||||
|
||||
it('OptionShouldUpdateParentToGetProperBinding', function(){
|
||||
var scope = this.compile('<select name="s"><option ng:repeat="i in [0,1]" value="{{i}}" ng:bind="i"></option></select>');
|
||||
scope.$set('s', 1);
|
||||
scope.$eval();
|
||||
assertEquals(1, scope.$element[0].selectedIndex);
|
||||
});
|
||||
|
||||
it('RepeaterShouldBindInputsDefaults', function () {
|
||||
var scope = this.compile('<div><input value="123" name="item.name" ng:repeat="item in items"></div>');
|
||||
|
|
|
|||
|
|
@ -212,7 +212,7 @@ function sortedHtml(element, showNgClass) {
|
|||
attr.value !='auto' &&
|
||||
attr.value !='false' &&
|
||||
attr.value !='inherit' &&
|
||||
attr.value !='0' &&
|
||||
(attr.value !='0' || attr.name =='value') &&
|
||||
attr.name !='loop' &&
|
||||
attr.name !='complete' &&
|
||||
attr.name !='maxLength' &&
|
||||
|
|
|
|||
|
|
@ -367,93 +367,6 @@ describe("widget", function(){
|
|||
expect(element[0].childNodes[0].selected).toEqual(true);
|
||||
});
|
||||
|
||||
it('should honor the value field in option', function(){
|
||||
compile(
|
||||
'<select name="selection" ng:format="number">' +
|
||||
'<option value="{{$index}}" ng:repeat="name in [\'A\', \'B\', \'C\']">{{name}}</option>' +
|
||||
'</select>');
|
||||
// childNodes[0] is repeater comment
|
||||
expect(scope.selection).toEqual(0);
|
||||
|
||||
browserTrigger(element[0].childNodes[2], 'change');
|
||||
expect(scope.selection).toEqual(1);
|
||||
|
||||
scope.selection = 2;
|
||||
scope.$eval();
|
||||
expect(element[0].childNodes[3].selected).toEqual(true);
|
||||
});
|
||||
|
||||
it('should unroll select options before eval', function(){
|
||||
compile(
|
||||
'<select name="selection" ng:required>' +
|
||||
'<option value="{{$index}}" ng:repeat="opt in options">{{opt}}</option>' +
|
||||
'</select>',
|
||||
jqLite(document.body));
|
||||
scope.selection = 1;
|
||||
scope.options = ['one', 'two'];
|
||||
scope.$eval();
|
||||
expect(element[0].value).toEqual('1');
|
||||
expect(element.hasClass(NG_VALIDATION_ERROR)).toEqual(false);
|
||||
});
|
||||
|
||||
it('should update select when value changes', function(){
|
||||
compile(
|
||||
'<select name="selection">' +
|
||||
'<option value="...">...</option>' +
|
||||
'<option value="{{value}}">B</option>' +
|
||||
'</select>');
|
||||
scope.selection = 'B';
|
||||
scope.$eval();
|
||||
expect(element[0].childNodes[1].selected).toEqual(false);
|
||||
scope.value = 'B';
|
||||
scope.$eval();
|
||||
expect(element[0].childNodes[1].selected).toEqual(true);
|
||||
});
|
||||
|
||||
it('should select default option on repeater', function(){
|
||||
compile(
|
||||
'<select name="selection">' +
|
||||
'<option ng:repeat="no in [1,2]">{{no}}</option>' +
|
||||
'</select>');
|
||||
expect(scope.selection).toEqual('1');
|
||||
});
|
||||
|
||||
it('should select selected option on repeater', function(){
|
||||
compile(
|
||||
'<select name="selection">' +
|
||||
'<option ng:repeat="no in [1,2]">{{no}}</option>' +
|
||||
'<option selected>ABC</option>' +
|
||||
'</select>');
|
||||
expect(scope.selection).toEqual('ABC');
|
||||
});
|
||||
|
||||
it('should select dynamically selected option on repeater', function(){
|
||||
compile(
|
||||
'<select name="selection">' +
|
||||
'<option ng:repeat="no in [1,2]" ng:bind-attr="{selected:\'{{no==2}}\'}">{{no}}</option>' +
|
||||
'</select>');
|
||||
expect(scope.selection).toEqual('2');
|
||||
});
|
||||
|
||||
it('should allow binding to objects through JSON', function(){
|
||||
compile(
|
||||
'<select name="selection" ng:format="json">' +
|
||||
'<option ng:repeat="obj in objs" value="{{obj}}">{{obj.name}}</option>' +
|
||||
'</select>');
|
||||
scope.objs = [{name:'A'}, {name:'B'}];
|
||||
scope.$eval();
|
||||
expect(scope.selection).toEqual({name:'A'});
|
||||
});
|
||||
|
||||
it('should allow binding to objects through index', function(){
|
||||
compile(
|
||||
'<select name="selection" ng:format="index:objs">' +
|
||||
'<option ng:repeat="obj in objs" value="{{$index}}">{{obj.name}}</option>' +
|
||||
'</select>');
|
||||
scope.objs = [{name:'A'}, {name:'B'}];
|
||||
scope.$eval();
|
||||
expect(scope.selection).toBe(scope.objs[0]);
|
||||
});
|
||||
|
||||
it('should compile children of a select without a name, but not create a model for it',
|
||||
function() {
|
||||
|
|
@ -695,6 +608,256 @@ describe("widget", function(){
|
|||
});
|
||||
});
|
||||
|
||||
describe('ng:options', function(){
|
||||
var select, scope;
|
||||
|
||||
function createSelect(multiple, blank, unknown){
|
||||
select = jqLite(
|
||||
'<select name="selected" ' + (multiple ? ' multiple' : '') +
|
||||
' ng:options="value.name for value in values">' +
|
||||
(blank ? '<option value="">blank</option>' : '') +
|
||||
(unknown ? '<option value="?">unknown</option>' : '') +
|
||||
'</select>');
|
||||
scope = compile(select);
|
||||
};
|
||||
|
||||
function createSingleSelect(blank, unknown){
|
||||
createSelect(false, blank, unknown);
|
||||
};
|
||||
|
||||
function createMultiSelect(blank, unknown){
|
||||
createSelect(true, blank, unknown);
|
||||
};
|
||||
|
||||
afterEach(function(){
|
||||
dealoc(select);
|
||||
dealoc(scope);
|
||||
});
|
||||
|
||||
it('should throw when not formated "? for ? in ?"', function(){
|
||||
expect(function(){
|
||||
compile('<select name="selected" ng:options="i dont parse"></select>');
|
||||
}).toThrow("Expected ng:options in form of '_expresion_ for _item_ in _collection_' but got 'i dont parse'.");
|
||||
|
||||
$logMock.error.logs.shift();
|
||||
});
|
||||
|
||||
it('should render a list', function(){
|
||||
createSingleSelect();
|
||||
scope.values = [{name:'A'}, {name:'B'}, {name:'C'}];
|
||||
scope.selected = scope.values[0];
|
||||
scope.$eval();
|
||||
var options = select.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 grow list', function(){
|
||||
createSingleSelect();
|
||||
scope.values = [];
|
||||
scope.$eval();
|
||||
expect(select.find('option').length).toEqual(1); // because we add special empty option
|
||||
expect(sortedHtml(select.find('option')[0])).toEqual('<option></option>');
|
||||
|
||||
scope.values.push({name:'A'});
|
||||
scope.selected = scope.values[0];
|
||||
scope.$eval();
|
||||
expect(select.find('option').length).toEqual(1);
|
||||
expect(sortedHtml(select.find('option')[0])).toEqual('<option value="0">A</option>');
|
||||
|
||||
scope.values.push({name:'B'});
|
||||
scope.$eval();
|
||||
expect(select.find('option').length).toEqual(2);
|
||||
expect(sortedHtml(select.find('option')[0])).toEqual('<option value="0">A</option>');
|
||||
expect(sortedHtml(select.find('option')[1])).toEqual('<option value="1">B</option>');
|
||||
});
|
||||
|
||||
it('should shrink list', function(){
|
||||
createSingleSelect();
|
||||
scope.values = [{name:'A'}, {name:'B'}, {name:'C'}];
|
||||
scope.selected = scope.values[0];
|
||||
scope.$eval();
|
||||
expect(select.find('option').length).toEqual(3);
|
||||
|
||||
scope.values.pop();
|
||||
scope.$eval();
|
||||
expect(select.find('option').length).toEqual(2);
|
||||
expect(sortedHtml(select.find('option')[0])).toEqual('<option value="0">A</option>');
|
||||
expect(sortedHtml(select.find('option')[1])).toEqual('<option value="1">B</option>');
|
||||
|
||||
scope.values.pop();
|
||||
scope.$eval();
|
||||
expect(select.find('option').length).toEqual(1);
|
||||
expect(sortedHtml(select.find('option')[0])).toEqual('<option value="0">A</option>');
|
||||
|
||||
scope.values.pop();
|
||||
scope.selected = null;
|
||||
scope.$eval();
|
||||
expect(select.find('option').length).toEqual(1); // we add back the special empty option
|
||||
});
|
||||
|
||||
it('should update list', function(){
|
||||
createSingleSelect();
|
||||
scope.values = [{name:'A'}, {name:'B'}, {name:'C'}];
|
||||
scope.selected = scope.values[0];
|
||||
scope.$eval();
|
||||
|
||||
scope.values = [{name:'B'}, {name:'C'}, {name:'D'}];
|
||||
scope.selected = scope.values[0];
|
||||
scope.$eval();
|
||||
var options = select.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.$eval();
|
||||
expect(select.find('option').length).toEqual(1);
|
||||
|
||||
scope.values = [{name:'A'}];
|
||||
scope.selected = scope.values[0];
|
||||
scope.$eval();
|
||||
expect(select.find('option').length).toEqual(2);
|
||||
expect(jqLite(select.find('option')[0]).text()).toEqual('blank');
|
||||
expect(jqLite(select.find('option')[1]).text()).toEqual('A');
|
||||
|
||||
scope.values = [];
|
||||
scope.selected = null;
|
||||
scope.$eval();
|
||||
expect(select.find('option').length).toEqual(1);
|
||||
expect(jqLite(select.find('option')[0]).text()).toEqual('blank');
|
||||
});
|
||||
|
||||
describe('binding', function(){
|
||||
it('should bind to scope value', function(){
|
||||
createSingleSelect();
|
||||
scope.values = [{name:'A'}, {name:'B'}];
|
||||
scope.selected = scope.values[0];
|
||||
scope.$eval();
|
||||
expect(select.val()).toEqual('0');
|
||||
|
||||
scope.selected = scope.values[1];
|
||||
scope.$eval();
|
||||
expect(select.val()).toEqual('1');
|
||||
});
|
||||
|
||||
it('should insert a blank option if bound to null', function(){
|
||||
createSingleSelect();
|
||||
scope.values = [{name:'A'}];
|
||||
scope.selected = null;
|
||||
scope.$eval();
|
||||
expect(select.find('option').length).toEqual(2);
|
||||
expect(select.val()).toEqual('');
|
||||
expect(jqLite(select.find('option')[0]).val()).toEqual('');
|
||||
|
||||
scope.selected = scope.values[0];
|
||||
scope.$eval();
|
||||
expect(select.val()).toEqual('0');
|
||||
expect(select.find('option').length).toEqual(1);
|
||||
});
|
||||
|
||||
it('should reuse blank option if bound to null', function(){
|
||||
createSingleSelect(true);
|
||||
scope.values = [{name:'A'}];
|
||||
scope.selected = null;
|
||||
scope.$eval();
|
||||
expect(select.find('option').length).toEqual(2);
|
||||
expect(select.val()).toEqual('');
|
||||
expect(jqLite(select.find('option')[0]).val()).toEqual('');
|
||||
|
||||
scope.selected = scope.values[0];
|
||||
scope.$eval();
|
||||
expect(select.val()).toEqual('0');
|
||||
expect(select.find('option').length).toEqual(2);
|
||||
});
|
||||
|
||||
it('should insert a unknown option if bound to not in list', function(){
|
||||
createSingleSelect();
|
||||
scope.values = [{name:'A'}];
|
||||
scope.selected = {};
|
||||
scope.$eval();
|
||||
expect(select.find('option').length).toEqual(2);
|
||||
expect(select.val()).toEqual('?');
|
||||
expect(jqLite(select.find('option')[0]).val()).toEqual('?');
|
||||
|
||||
scope.selected = scope.values[0];
|
||||
scope.$eval();
|
||||
expect(select.val()).toEqual('0');
|
||||
expect(select.find('option').length).toEqual(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('on change', function(){
|
||||
it('should update model on change', function(){
|
||||
createSingleSelect();
|
||||
scope.values = [{name:'A'}, {name:'B'}];
|
||||
scope.selected = scope.values[0];
|
||||
scope.$eval();
|
||||
expect(select.val()).toEqual('0');
|
||||
|
||||
select.val('1');
|
||||
browserTrigger(select, 'change');
|
||||
expect(scope.selected).toEqual(scope.values[1]);
|
||||
});
|
||||
|
||||
it('should update model to null on change', function(){
|
||||
createSingleSelect(true);
|
||||
scope.values = [{name:'A'}, {name:'B'}];
|
||||
scope.selected = scope.values[0];
|
||||
select.val('0');
|
||||
scope.$eval();
|
||||
|
||||
select.val('');
|
||||
browserTrigger(select, 'change');
|
||||
expect(scope.selected).toEqual(null);
|
||||
});
|
||||
});
|
||||
|
||||
describe('select-many', function(){
|
||||
it('should read multiple selection', function(){
|
||||
createMultiSelect();
|
||||
scope.values = [{name:'A'}, {name:'B'}];
|
||||
|
||||
scope.selected = [];
|
||||
scope.$eval();
|
||||
expect(select.find('option').length).toEqual(2);
|
||||
expect(jqLite(select.find('option')[0]).attr('selected')).toEqual(false);
|
||||
expect(jqLite(select.find('option')[1]).attr('selected')).toEqual(false);
|
||||
|
||||
scope.selected.push(scope.values[1]);
|
||||
scope.$eval();
|
||||
expect(select.find('option').length).toEqual(2);
|
||||
expect(select.find('option')[0].selected).toEqual(false);
|
||||
expect(select.find('option')[1].selected).toEqual(true);
|
||||
|
||||
scope.selected.push(scope.values[0]);
|
||||
scope.$eval();
|
||||
expect(select.find('option').length).toEqual(2);
|
||||
expect(select.find('option')[0].selected).toEqual(true);
|
||||
expect(select.find('option')[1].selected).toEqual(true);
|
||||
});
|
||||
|
||||
it('should update model on change', function(){
|
||||
createMultiSelect();
|
||||
scope.values = [{name:'A'}, {name:'B'}];
|
||||
|
||||
scope.selected = [];
|
||||
scope.$eval();
|
||||
select.find('option')[0].selected = true;
|
||||
|
||||
browserTrigger(select, 'change');
|
||||
expect(scope.selected).toEqual([scope.values[0]]);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
describe('@ng:repeat', function() {
|
||||
|
||||
|
|
@ -739,10 +902,10 @@ describe("widget", function(){
|
|||
var scope = compile('<ul><li ng:repeat="i dont parse"></li></ul>');
|
||||
|
||||
expect(scope.$service('$log').error.logs.shift()[0]).
|
||||
toEqualError("Expected ng:repeat in form of 'item in collection' but got 'i dont parse'.");
|
||||
toEqualError("Expected ng:repeat in form of '_item_ in _collection_' but got 'i dont parse'.");
|
||||
|
||||
expect(scope.$element.attr('ng-exception')).
|
||||
toMatch(/Expected ng:repeat in form of 'item in collection' but got 'i dont parse'/);
|
||||
toMatch(/Expected ng:repeat in form of '_item_ in _collection_' but got 'i dont parse'/);
|
||||
expect(scope.$element).toHaveClass('ng-exception');
|
||||
|
||||
dealoc(scope);
|
||||
|
|
|
|||
Loading…
Reference in a new issue