mirror of
https://github.com/Hopiu/angular.js.git
synced 2026-03-16 23:30:23 +00:00
feat(ng:repeat) collection items and DOM elements affinity / stability
This commit is contained in:
parent
e134a8335f
commit
75f11f1fc4
7 changed files with 384 additions and 227 deletions
|
|
@ -52,7 +52,10 @@
|
|||
- If Angular is being used with jQuery older than 1.6, some features might not work properly. Please
|
||||
upgrade to jQuery version 1.6.4.
|
||||
|
||||
|
||||
## Breaking Changes
|
||||
- ng:repeat no longer has ng:repeat-index property. This is because the elements now have
|
||||
affinity to the underlying collection, and moving items around in the collection would move
|
||||
ng:repeat-index property rendering it meaningless.
|
||||
|
||||
|
||||
<a name="0.10.1"><a/>
|
||||
|
|
@ -88,7 +91,7 @@
|
|||
- $location.hashPath -> $location.path()
|
||||
- $location.hashSearch -> $location.search()
|
||||
- $location.search -> no equivalent, use $window.location.search (this is so that we can work in
|
||||
hashBang and html5 mode at the same time, check out the docs)
|
||||
hashBang and html5 mode at the same time, check out the docs)
|
||||
- $location.update() / $location.updateHash() -> use $location.url()
|
||||
- n/a -> $location.replace() - new api for replacing history record instead of creating a new one
|
||||
|
||||
|
|
|
|||
58
src/apis.js
58
src/apis.js
|
|
@ -840,20 +840,22 @@ var angularFunction = {
|
|||
* Hash of a:
|
||||
* string is string
|
||||
* number is number as string
|
||||
* object is either call $hashKey function on object or assign unique hashKey id.
|
||||
* object is either result of calling $$hashKey function on the object or uniquely generated id,
|
||||
* that is also assigned to the $$hashKey property of the object.
|
||||
*
|
||||
* @param obj
|
||||
* @returns {String} hash string such that the same input will have the same hash string
|
||||
* @returns {String} hash string such that the same input will have the same hash string.
|
||||
* The resulting string key is in 'type:hashKey' format.
|
||||
*/
|
||||
function hashKey(obj) {
|
||||
var objType = typeof obj;
|
||||
var key = obj;
|
||||
if (objType == 'object') {
|
||||
if (typeof (key = obj.$hashKey) == 'function') {
|
||||
if (typeof (key = obj.$$hashKey) == 'function') {
|
||||
// must invoke on object to keep the right this
|
||||
key = obj.$hashKey();
|
||||
key = obj.$$hashKey();
|
||||
} else if (key === undefined) {
|
||||
key = obj.$hashKey = nextUid();
|
||||
key = obj.$$hashKey = nextUid();
|
||||
}
|
||||
}
|
||||
return objType + ':' + key;
|
||||
|
|
@ -868,13 +870,9 @@ HashMap.prototype = {
|
|||
* Store key value pair
|
||||
* @param key key to store can be any type
|
||||
* @param value value to store can be any type
|
||||
* @returns old value if any
|
||||
*/
|
||||
put: function(key, value) {
|
||||
var _key = hashKey(key);
|
||||
var oldValue = this[_key];
|
||||
this[_key] = value;
|
||||
return oldValue;
|
||||
this[hashKey(key)] = value;
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
@ -888,16 +886,48 @@ HashMap.prototype = {
|
|||
/**
|
||||
* Remove the key/value pair
|
||||
* @param key
|
||||
* @returns value associated with key before it was removed
|
||||
*/
|
||||
remove: function(key) {
|
||||
var _key = hashKey(key);
|
||||
var value = this[_key];
|
||||
delete this[_key];
|
||||
var value = this[key = hashKey(key)];
|
||||
delete this[key];
|
||||
return value;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* A map where multiple values can be added to the same key such that the form a queue.
|
||||
* @returns {HashQueueMap}
|
||||
*/
|
||||
function HashQueueMap(){}
|
||||
HashQueueMap.prototype = {
|
||||
/**
|
||||
* Same as array push, but using an array as the value for the hash
|
||||
*/
|
||||
push: function(key, value) {
|
||||
var array = this[key = hashKey(key)];
|
||||
if (!array) {
|
||||
this[key] = [value];
|
||||
} else {
|
||||
array.push(value);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Same as array shift, but using an array as the value for the hash
|
||||
*/
|
||||
shift: function(key) {
|
||||
var array = this[key = hashKey(key)];
|
||||
if (array) {
|
||||
if (array.length == 1) {
|
||||
delete this[key];
|
||||
return array[0];
|
||||
} else {
|
||||
return array.shift();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function defineApi(dst, chain){
|
||||
angular[dst] = angular[dst] || {};
|
||||
forEach(chain, function(parent){
|
||||
|
|
|
|||
112
src/widgets.js
112
src/widgets.js
|
|
@ -1182,10 +1182,9 @@ angularWidget('a', function() {
|
|||
* @name angular.widget.@ng:repeat
|
||||
*
|
||||
* @description
|
||||
* The `ng:repeat` widget instantiates a template once per item from a collection. The collection is
|
||||
* enumerated with the `ng:repeat-index` attribute, starting from 0. Each template instance gets
|
||||
* its own scope, where the given loop variable is set to the current collection item, and `$index`
|
||||
* is set to the item index or key.
|
||||
* The `ng:repeat` widget instantiates a template once per item from a collection. Each template
|
||||
* instance gets its own scope, where the given loop variable is set to the current collection item,
|
||||
* and `$index` is set to the item index or key.
|
||||
*
|
||||
* Special properties are exposed on the local scope of each template instance, including:
|
||||
*
|
||||
|
|
@ -1256,68 +1255,89 @@ angularWidget('@ng:repeat', function(expression, element){
|
|||
valueIdent = match[3] || match[1];
|
||||
keyIdent = match[2];
|
||||
|
||||
var childScopes = [];
|
||||
var childElements = [iterStartElement];
|
||||
var parentScope = this;
|
||||
// Store a list of elements from previous run. This is a hash where key is the item from the
|
||||
// iterator, and the value is an array of objects with following properties.
|
||||
// - scope: bound scope
|
||||
// - element: previous element.
|
||||
// - index: position
|
||||
// We need an array of these objects since the same object can be returned from the iterator.
|
||||
// We expect this to be a rare case.
|
||||
var lastOrder = new HashQueueMap();
|
||||
this.$watch(function(scope){
|
||||
var index = 0,
|
||||
childCount = childScopes.length,
|
||||
collection = scope.$eval(rhs),
|
||||
collectionLength = size(collection, true),
|
||||
fragment = document.createDocumentFragment(),
|
||||
addFragmentTo = (childCount < collectionLength) ? childElements[childCount] : null,
|
||||
childScope,
|
||||
key;
|
||||
// Same as lastOrder but it has the current state. It will become the
|
||||
// lastOrder on the next iteration.
|
||||
nextOrder = new HashQueueMap(),
|
||||
key, value, // key/value of iteration
|
||||
array, last, // last object information {scope, element, index}
|
||||
cursor = iterStartElement; // current position of the node
|
||||
|
||||
for (key in collection) {
|
||||
if (collection.hasOwnProperty(key)) {
|
||||
if (index < childCount) {
|
||||
// reuse existing child
|
||||
childScope = childScopes[index];
|
||||
childScope[valueIdent] = collection[key];
|
||||
if (keyIdent) childScope[keyIdent] = key;
|
||||
childScope.$position = index == 0
|
||||
? 'first'
|
||||
: (index == collectionLength - 1 ? 'last' : 'middle');
|
||||
childScope.$eval();
|
||||
last = lastOrder.shift(value = collection[key]);
|
||||
if (last) {
|
||||
// if we have already seen this object, then we need to reuse the
|
||||
// associated scope/element
|
||||
childScope = last.scope;
|
||||
nextOrder.push(value, last);
|
||||
|
||||
if (index === last.index) {
|
||||
// do nothing
|
||||
cursor = last.element;
|
||||
} else {
|
||||
// existing item which got moved
|
||||
last.index = index;
|
||||
// This may be a noop, if the element is next, but I don't know of a good way to
|
||||
// figure this out, since it would require extra DOM access, so let's just hope that
|
||||
// the browsers realizes that it is noop, and treats it as such.
|
||||
cursor.after(last.element);
|
||||
cursor = last.element;
|
||||
}
|
||||
} else {
|
||||
// grow children
|
||||
// new item which we don't know about
|
||||
childScope = parentScope.$new();
|
||||
childScope[valueIdent] = collection[key];
|
||||
if (keyIdent) childScope[keyIdent] = key;
|
||||
childScope.$index = index;
|
||||
childScope.$position = index == 0
|
||||
? 'first'
|
||||
: (index == collectionLength - 1 ? 'last' : 'middle');
|
||||
childScopes.push(childScope);
|
||||
}
|
||||
|
||||
childScope[valueIdent] = collection[key];
|
||||
if (keyIdent) childScope[keyIdent] = key;
|
||||
childScope.$index = index;
|
||||
childScope.$position = index == 0
|
||||
? 'first'
|
||||
: (index == collectionLength - 1 ? 'last' : 'middle');
|
||||
|
||||
if (!last) {
|
||||
linker(childScope, function(clone){
|
||||
clone.attr('ng:repeat-index', index);
|
||||
fragment.appendChild(clone[0]);
|
||||
// TODO(misko): Temporary hack - maybe think about it - removed after we add fragment after $digest()
|
||||
// This causes double $digest for children
|
||||
// The first flush will couse a lot of DOM access (initial)
|
||||
// Second flush shuld be noop since nothing has change hence no DOM access.
|
||||
childScope.$digest();
|
||||
childElements[index + 1] = clone;
|
||||
cursor.after(clone);
|
||||
last = {
|
||||
scope: childScope,
|
||||
element: (cursor = clone),
|
||||
index: index
|
||||
};
|
||||
nextOrder.push(value, last);
|
||||
});
|
||||
}
|
||||
|
||||
index ++;
|
||||
}
|
||||
}
|
||||
|
||||
//attach new nodes buffered in doc fragment
|
||||
if (addFragmentTo) {
|
||||
// TODO(misko): For performance reasons, we should do the addition after all other widgets
|
||||
// have run. For this should happend after $digest() is done!
|
||||
addFragmentTo.after(jqLite(fragment));
|
||||
//shrink children
|
||||
for (key in lastOrder) {
|
||||
if (lastOrder.hasOwnProperty(key)) {
|
||||
array = lastOrder[key];
|
||||
while(array.length) {
|
||||
value = array.pop();
|
||||
value.element.remove();
|
||||
value.scope.$destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// shrink children
|
||||
while(childScopes.length > index) {
|
||||
// can not use $destroy(true) since there may be multiple iterators on same parent.
|
||||
childScopes.pop().$destroy();
|
||||
childElements.pop().remove();
|
||||
}
|
||||
lastOrder = nextOrder;
|
||||
});
|
||||
};
|
||||
});
|
||||
|
|
|
|||
105
test/ApiSpecs.js
105
test/ApiSpecs.js
|
|
@ -1,26 +1,39 @@
|
|||
'use strict';
|
||||
|
||||
describe('api', function(){
|
||||
describe('api', function() {
|
||||
|
||||
describe('HashMap', function(){
|
||||
it('should do basic crud', function(){
|
||||
describe('HashMap', function() {
|
||||
it('should do basic crud', function() {
|
||||
var map = new HashMap();
|
||||
var key = {};
|
||||
var value1 = {};
|
||||
var value2 = {};
|
||||
expect(map.put(key, value1)).toEqual(undefined);
|
||||
expect(map.put(key, value2)).toEqual(value1);
|
||||
expect(map.get(key)).toEqual(value2);
|
||||
expect(map.get({})).toEqual(undefined);
|
||||
expect(map.remove(key)).toEqual(value2);
|
||||
expect(map.get(key)).toEqual(undefined);
|
||||
map.put(key, value1);
|
||||
map.put(key, value2);
|
||||
expect(map.get(key)).toBe(value2);
|
||||
expect(map.get({})).toBe(undefined);
|
||||
expect(map.remove(key)).toBe(value2);
|
||||
expect(map.get(key)).toBe(undefined);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('Object', function(){
|
||||
describe('HashQueueMap', function() {
|
||||
it('should do basic crud with collections', function() {
|
||||
var map = new HashQueueMap();
|
||||
map.push('key', 'a');
|
||||
map.push('key', 'b');
|
||||
expect(map[hashKey('key')]).toEqual(['a', 'b']);
|
||||
expect(map.shift('key')).toEqual('a');
|
||||
expect(map.shift('key')).toEqual('b');
|
||||
expect(map.shift('key')).toEqual(undefined);
|
||||
expect(map[hashKey('key')]).toEqual(undefined);
|
||||
});
|
||||
});
|
||||
|
||||
it('should return type of', function(){
|
||||
|
||||
describe('Object', function() {
|
||||
it('should return type of', function() {
|
||||
assertEquals("undefined", angular.Object.typeOf(undefined));
|
||||
assertEquals("null", angular.Object.typeOf(null));
|
||||
assertEquals("object", angular.Collection.typeOf({}));
|
||||
|
|
@ -28,46 +41,45 @@ describe('api', function(){
|
|||
assertEquals("string", angular.Object.typeOf(""));
|
||||
assertEquals("date", angular.Object.typeOf(new Date()));
|
||||
assertEquals("element", angular.Object.typeOf(document.body));
|
||||
assertEquals('function', angular.Object.typeOf(function(){}));
|
||||
assertEquals('function', angular.Object.typeOf(function() {}));
|
||||
});
|
||||
|
||||
it('should extend object', function(){
|
||||
it('should extend object', function() {
|
||||
assertEquals({a:1, b:2}, angular.Object.extend({a:1}, {b:2}));
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
it('should return size', function(){
|
||||
it('should return size', function() {
|
||||
assertEquals(0, angular.Collection.size({}));
|
||||
assertEquals(1, angular.Collection.size({a:"b"}));
|
||||
assertEquals(0, angular.Object.size({}));
|
||||
assertEquals(1, angular.Array.size([0]));
|
||||
});
|
||||
|
||||
describe('Array', function(){
|
||||
|
||||
describe('sum', function(){
|
||||
describe('Array', function() {
|
||||
|
||||
it('should sum', function(){
|
||||
describe('sum', function() {
|
||||
it('should sum', function() {
|
||||
assertEquals(3, angular.Array.sum([{a:"1"}, {a:"2"}], 'a'));
|
||||
});
|
||||
|
||||
it('should sum containing NaN', function(){
|
||||
it('should sum containing NaN', function() {
|
||||
assertEquals(1, angular.Array.sum([{a:1}, {a:Number.NaN}], 'a'));
|
||||
assertEquals(1, angular.Array.sum([{a:1}, {a:Number.NaN}], function($){return $.a;}));
|
||||
assertEquals(1, angular.Array.sum([{a:1}, {a:Number.NaN}], function($) {return $.a;}));
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
it('should find indexOf', function(){
|
||||
|
||||
it('should find indexOf', function() {
|
||||
assertEquals(angular.Array.indexOf(['a'], 'a'), 0);
|
||||
assertEquals(angular.Array.indexOf(['a', 'b'], 'a'), 0);
|
||||
assertEquals(angular.Array.indexOf(['b', 'a'], 'a'), 1);
|
||||
assertEquals(angular.Array.indexOf(['b', 'b'],'x'), -1);
|
||||
});
|
||||
|
||||
it('should remove item from array', function(){
|
||||
it('should remove item from array', function() {
|
||||
var items = ['a', 'b', 'c'];
|
||||
assertEquals(angular.Array.remove(items, 'q'), 'q');
|
||||
assertEquals(items.length, 3);
|
||||
|
|
@ -85,8 +97,8 @@ describe('api', function(){
|
|||
assertEquals(items.length, 0);
|
||||
});
|
||||
|
||||
describe('filter', function(){
|
||||
|
||||
describe('filter', function() {
|
||||
it('should filter by string', function() {
|
||||
var items = ["MIsKO", {name:"shyam"}, ["adam"], 1234];
|
||||
assertEquals(4, angular.Array.filter(items, "").length);
|
||||
|
|
@ -113,7 +125,7 @@ describe('api', function(){
|
|||
assertEquals(0, angular.Array.filter(items, "misko").length);
|
||||
});
|
||||
|
||||
it('should filter on specific property', function(){
|
||||
it('should filter on specific property', function() {
|
||||
var items = [{ignore:"a", name:"a"}, {ignore:"a", name:"abc"}];
|
||||
assertEquals(2, angular.Array.filter(items, {}).length);
|
||||
|
||||
|
|
@ -123,12 +135,12 @@ describe('api', function(){
|
|||
assertEquals("abc", angular.Array.filter(items, {name:'b'})[0].name);
|
||||
});
|
||||
|
||||
it('should take function as predicate', function(){
|
||||
it('should take function as predicate', function() {
|
||||
var items = [{name:"a"}, {name:"abc", done:true}];
|
||||
assertEquals(1, angular.Array.filter(items, function(i){return i.done;}).length);
|
||||
assertEquals(1, angular.Array.filter(items, function(i) {return i.done;}).length);
|
||||
});
|
||||
|
||||
it('should take object as perdicate', function(){
|
||||
it('should take object as perdicate', function() {
|
||||
var items = [{first:"misko", last:"hevery"},
|
||||
{first:"adam", last:"abrons"}];
|
||||
|
||||
|
|
@ -139,7 +151,7 @@ describe('api', function(){
|
|||
assertEquals(items[0], angular.Array.filter(items, {first:'misko', last:'hevery'})[0]);
|
||||
});
|
||||
|
||||
it('should support negation operator', function(){
|
||||
it('should support negation operator', function() {
|
||||
var items = ["misko", "adam"];
|
||||
|
||||
assertEquals(1, angular.Array.filter(items, '!isk').length);
|
||||
|
|
@ -198,12 +210,12 @@ describe('api', function(){
|
|||
});
|
||||
|
||||
|
||||
it('add', function(){
|
||||
it('add', function() {
|
||||
var add = angular.Array.add;
|
||||
assertJsonEquals([{}, "a"], add(add([]),"a"));
|
||||
});
|
||||
|
||||
it('count', function(){
|
||||
it('count', function() {
|
||||
var array = [{name:'a'},{name:'b'},{name:''}];
|
||||
var obj = {};
|
||||
|
||||
|
|
@ -212,24 +224,25 @@ describe('api', function(){
|
|||
assertEquals(1, angular.Array.count(array, 'name=="a"'));
|
||||
});
|
||||
|
||||
describe('orderBy', function(){
|
||||
|
||||
describe('orderBy', function() {
|
||||
var orderBy;
|
||||
beforeEach(function(){
|
||||
beforeEach(function() {
|
||||
orderBy = angular.Array.orderBy;
|
||||
});
|
||||
|
||||
it('should return same array if predicate is falsy', function(){
|
||||
it('should return same array if predicate is falsy', function() {
|
||||
var array = [1, 2, 3];
|
||||
expect(orderBy(array)).toBe(array);
|
||||
});
|
||||
|
||||
it('shouldSortArrayInReverse', function(){
|
||||
it('shouldSortArrayInReverse', function() {
|
||||
assertJsonEquals([{a:15},{a:2}], angular.Array.orderBy([{a:15},{a:2}], 'a', true));
|
||||
assertJsonEquals([{a:15},{a:2}], angular.Array.orderBy([{a:15},{a:2}], 'a', "T"));
|
||||
assertJsonEquals([{a:15},{a:2}], angular.Array.orderBy([{a:15},{a:2}], 'a', "reverse"));
|
||||
});
|
||||
|
||||
it('should sort array by predicate', function(){
|
||||
it('should sort array by predicate', function() {
|
||||
assertJsonEquals([{a:2, b:1},{a:15, b:1}],
|
||||
angular.Array.orderBy([{a:15, b:1},{a:2, b:1}], ['a', 'b']));
|
||||
assertJsonEquals([{a:2, b:1},{a:15, b:1}],
|
||||
|
|
@ -238,11 +251,11 @@ describe('api', function(){
|
|||
angular.Array.orderBy([{a:15, b:1},{a:2, b:1}], ['+b', '-a']));
|
||||
});
|
||||
|
||||
it('should use function', function(){
|
||||
it('should use function', function() {
|
||||
expect(
|
||||
orderBy(
|
||||
[{a:15, b:1},{a:2, b:1}],
|
||||
function(value){ return value.a; })).
|
||||
function(value) { return value.a; })).
|
||||
toEqual([{a:2, b:1},{a:15, b:1}]);
|
||||
});
|
||||
|
||||
|
|
@ -250,9 +263,9 @@ describe('api', function(){
|
|||
|
||||
});
|
||||
|
||||
describe('string', function(){
|
||||
|
||||
it('should quote', function(){
|
||||
describe('string', function() {
|
||||
it('should quote', function() {
|
||||
assertEquals(angular.String.quote('a'), '"a"');
|
||||
assertEquals(angular.String.quote('\\'), '"\\\\"');
|
||||
assertEquals(angular.String.quote("'a'"), '"\'a\'"');
|
||||
|
|
@ -260,22 +273,22 @@ describe('api', function(){
|
|||
assertEquals(angular.String.quote('\n\f\r\t'), '"\\n\\f\\r\\t"');
|
||||
});
|
||||
|
||||
it('should quote slashes', function(){
|
||||
it('should quote slashes', function() {
|
||||
assertEquals('"7\\\\\\\"7"', angular.String.quote("7\\\"7"));
|
||||
});
|
||||
|
||||
it('should quote unicode', function(){
|
||||
it('should quote unicode', function() {
|
||||
assertEquals('"abc\\u00a0def"', angular.String.quoteUnicode('abc\u00A0def'));
|
||||
});
|
||||
|
||||
it('should read/write to date', function(){
|
||||
it('should read/write to date', function() {
|
||||
var date = new Date("Sep 10 2003 13:02:03 GMT");
|
||||
assertEquals("date", angular.Object.typeOf(date));
|
||||
assertEquals("2003-09-10T13:02:03.000Z", angular.Date.toString(date));
|
||||
assertEquals(date.getTime(), angular.String.toDate(angular.Date.toString(date)).getTime());
|
||||
});
|
||||
|
||||
it('should convert to date', function(){
|
||||
it('should convert to date', function() {
|
||||
//full ISO8061
|
||||
expect(angular.String.toDate("2003-09-10T13:02:03.000Z")).
|
||||
toEqual(new Date("Sep 10 2003 13:02:03 GMT"));
|
||||
|
|
@ -297,14 +310,12 @@ describe('api', function(){
|
|||
toEqual(new Date("Sep 10 2003 00:00:00 GMT"));
|
||||
});
|
||||
|
||||
it('should parse date', function(){
|
||||
it('should parse date', function() {
|
||||
var date = angular.String.toDate("2003-09-10T13:02:03.000Z");
|
||||
assertEquals("date", angular.Object.typeOf(date));
|
||||
assertEquals("2003-09-10T13:02:03.000Z", angular.Date.toString(date));
|
||||
assertEquals("str", angular.String.toDate("str"));
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -194,25 +194,25 @@ describe('Binder', function(){
|
|||
scope.$apply();
|
||||
assertEquals('<ul>' +
|
||||
'<#comment></#comment>' +
|
||||
'<li ng:bind="item.a" ng:repeat-index="0">A</li>' +
|
||||
'<li ng:bind="item.a" ng:repeat-index="1">B</li>' +
|
||||
'<li ng:bind="item.a">A</li>' +
|
||||
'<li ng:bind="item.a">B</li>' +
|
||||
'</ul>', sortedHtml(form));
|
||||
|
||||
items.unshift({a:'C'});
|
||||
scope.$apply();
|
||||
assertEquals('<ul>' +
|
||||
'<#comment></#comment>' +
|
||||
'<li ng:bind="item.a" ng:repeat-index="0">C</li>' +
|
||||
'<li ng:bind="item.a" ng:repeat-index="1">A</li>' +
|
||||
'<li ng:bind="item.a" ng:repeat-index="2">B</li>' +
|
||||
'<li ng:bind="item.a">C</li>' +
|
||||
'<li ng:bind="item.a">A</li>' +
|
||||
'<li ng:bind="item.a">B</li>' +
|
||||
'</ul>', sortedHtml(form));
|
||||
|
||||
items.shift();
|
||||
scope.$apply();
|
||||
assertEquals('<ul>' +
|
||||
'<#comment></#comment>' +
|
||||
'<li ng:bind="item.a" ng:repeat-index="0">A</li>' +
|
||||
'<li ng:bind="item.a" ng:repeat-index="1">B</li>' +
|
||||
'<li ng:bind="item.a">A</li>' +
|
||||
'<li ng:bind="item.a">B</li>' +
|
||||
'</ul>', sortedHtml(form));
|
||||
|
||||
items.shift();
|
||||
|
|
@ -226,7 +226,7 @@ describe('Binder', function(){
|
|||
scope.$apply();
|
||||
assertEquals('<ul>' +
|
||||
'<#comment></#comment>' +
|
||||
'<li ng:repeat-index="0"><span ng:bind="item.a">A</span></li>' +
|
||||
'<li><span ng:bind="item.a">A</span></li>' +
|
||||
'</ul>', sortedHtml(scope.$element));
|
||||
});
|
||||
|
||||
|
|
@ -329,15 +329,15 @@ describe('Binder', function(){
|
|||
|
||||
assertEquals('<div>'+
|
||||
'<#comment></#comment>'+
|
||||
'<div name="a" ng:bind-attr="{"name":"{{m.name}}"}" ng:repeat-index="0">'+
|
||||
'<div name="a" ng:bind-attr="{"name":"{{m.name}}"}">'+
|
||||
'<#comment></#comment>'+
|
||||
'<ul name="a1" ng:bind-attr="{"name":"{{i}}"}" ng:repeat-index="0"></ul>'+
|
||||
'<ul name="a2" ng:bind-attr="{"name":"{{i}}"}" ng:repeat-index="1"></ul>'+
|
||||
'<ul name="a1" ng:bind-attr="{"name":"{{i}}"}"></ul>'+
|
||||
'<ul name="a2" ng:bind-attr="{"name":"{{i}}"}"></ul>'+
|
||||
'</div>'+
|
||||
'<div name="b" ng:bind-attr="{"name":"{{m.name}}"}" ng:repeat-index="1">'+
|
||||
'<div name="b" ng:bind-attr="{"name":"{{m.name}}"}">'+
|
||||
'<#comment></#comment>'+
|
||||
'<ul name="b1" ng:bind-attr="{"name":"{{i}}"}" ng:repeat-index="0"></ul>'+
|
||||
'<ul name="b2" ng:bind-attr="{"name":"{{i}}"}" ng:repeat-index="1"></ul>'+
|
||||
'<ul name="b1" ng:bind-attr="{"name":"{{i}}"}"></ul>'+
|
||||
'<ul name="b2" ng:bind-attr="{"name":"{{i}}"}"></ul>'+
|
||||
'</div></div>', sortedHtml(scope.$element));
|
||||
});
|
||||
|
||||
|
|
@ -417,8 +417,8 @@ describe('Binder', function(){
|
|||
expect(d2.hasClass('e')).toBeTruthy();
|
||||
assertEquals(
|
||||
'<div><#comment></#comment>' +
|
||||
'<div class="o" ng:class-even="\'e\'" ng:class-odd="\'o\'" ng:repeat-index="0"></div>' +
|
||||
'<div class="e" ng:class-even="\'e\'" ng:class-odd="\'o\'" ng:repeat-index="1"></div></div>',
|
||||
'<div class="o" ng:class-even="\'e\'" ng:class-odd="\'o\'"></div>' +
|
||||
'<div class="e" ng:class-even="\'e\'" ng:class-odd="\'o\'"></div></div>',
|
||||
sortedHtml(scope.$element));
|
||||
});
|
||||
|
||||
|
|
@ -459,8 +459,8 @@ describe('Binder', function(){
|
|||
scope.items = [{}, {name:'misko'}];
|
||||
scope.$apply();
|
||||
|
||||
assertEquals("123", scope.$eval('items[0].name'));
|
||||
assertEquals("misko", scope.$eval('items[1].name'));
|
||||
expect(scope.$eval('items[0].name')).toEqual("123");
|
||||
expect(scope.$eval('items[1].name')).toEqual("misko");
|
||||
});
|
||||
|
||||
it('ShouldTemplateBindPreElements', function () {
|
||||
|
|
@ -593,8 +593,8 @@ describe('Binder', function(){
|
|||
scope.$apply();
|
||||
assertEquals('<ul>' +
|
||||
'<#comment></#comment>' +
|
||||
'<li ng:bind=\"k + v\" ng:repeat-index="0">a0</li>' +
|
||||
'<li ng:bind=\"k + v\" ng:repeat-index="1">b1</li>' +
|
||||
'<li ng:bind=\"k + v\">a0</li>' +
|
||||
'<li ng:bind=\"k + v\">b1</li>' +
|
||||
'</ul>',
|
||||
sortedHtml(scope.$element));
|
||||
});
|
||||
|
|
|
|||
|
|
@ -378,9 +378,9 @@ describe("angular.scenario.dsl", function() {
|
|||
beforeEach(function() {
|
||||
doc.append(
|
||||
'<ul>' +
|
||||
' <li ng:repeat-index="0"><span ng:bind="name" class="ng-binding">misko</span>' +
|
||||
' <li><span ng:bind="name" class="ng-binding">misko</span>' +
|
||||
' <span ng:bind="test && gender" class="ng-binding">male</span></li>' +
|
||||
' <li ng:repeat-index="1"><span ng:bind="name" class="ng-binding">felisa</span>' +
|
||||
' <li><span ng:bind="name" class="ng-binding">felisa</span>' +
|
||||
' <span ng:bind="gender | uppercase" class="ng-binding">female</span></li>' +
|
||||
'</ul>'
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
'use strict';
|
||||
|
||||
describe("widget", function(){
|
||||
describe("widget", function() {
|
||||
var compile, element, scope;
|
||||
|
||||
beforeEach(function() {
|
||||
|
|
@ -19,14 +19,15 @@ describe("widget", function(){
|
|||
};
|
||||
});
|
||||
|
||||
afterEach(function(){
|
||||
afterEach(function() {
|
||||
dealoc(element);
|
||||
});
|
||||
|
||||
describe("input", function(){
|
||||
|
||||
describe("text", function(){
|
||||
it('should input-text auto init and handle keydown/change events', function(){
|
||||
describe("input", function() {
|
||||
|
||||
describe("text", function() {
|
||||
it('should input-text auto init and handle keydown/change events', function() {
|
||||
compile('<input type="Text" name="name" value="Misko" ng:change="count = count + 1" ng:init="count=0"/>');
|
||||
expect(scope.name).toEqual("Misko");
|
||||
expect(scope.count).toEqual(0);
|
||||
|
|
@ -49,7 +50,7 @@ describe("widget", function(){
|
|||
expect(scope.count).toEqual(2);
|
||||
});
|
||||
|
||||
it('should not trigger eval if value does not change', function(){
|
||||
it('should not trigger eval if value does not change', function() {
|
||||
compile('<input type="Text" name="name" value="Misko" ng:change="count = count + 1" ng:init="count=0"/>');
|
||||
expect(scope.name).toEqual("Misko");
|
||||
expect(scope.count).toEqual(0);
|
||||
|
|
@ -58,16 +59,16 @@ describe("widget", function(){
|
|||
expect(scope.count).toEqual(0);
|
||||
});
|
||||
|
||||
it('should allow complex refernce binding', function(){
|
||||
it('should allow complex refernce binding', function() {
|
||||
compile('<div ng:init="obj={abc:{}}">'+
|
||||
'<input type="Text" name="obj[\'abc\'].name" value="Misko""/>'+
|
||||
'</div>');
|
||||
expect(scope.obj['abc'].name).toEqual('Misko');
|
||||
});
|
||||
|
||||
describe("ng:format", function(){
|
||||
|
||||
it("should format text", function(){
|
||||
describe("ng:format", function() {
|
||||
it("should format text", function() {
|
||||
compile('<input type="Text" name="list" value="a,b,c" ng:format="list"/>');
|
||||
expect(scope.list).toEqual(['a', 'b', 'c']);
|
||||
|
||||
|
|
@ -80,13 +81,13 @@ describe("widget", function(){
|
|||
expect(scope.list).toEqual(['1', '2', '3']);
|
||||
});
|
||||
|
||||
it("should come up blank if null", function(){
|
||||
it("should come up blank if null", function() {
|
||||
compile('<input type="text" name="age" ng:format="number" ng:init="age=null"/>');
|
||||
expect(scope.age).toBeNull();
|
||||
expect(scope.$element[0].value).toEqual('');
|
||||
});
|
||||
|
||||
it("should show incorect text while number does not parse", function(){
|
||||
it("should show incorect text while number does not parse", function() {
|
||||
compile('<input type="text" name="age" ng:format="number"/>');
|
||||
scope.age = 123;
|
||||
scope.$digest();
|
||||
|
|
@ -97,14 +98,14 @@ describe("widget", function(){
|
|||
expect(scope.$element).toBeInvalid();
|
||||
});
|
||||
|
||||
it("should clober incorect text if model changes", function(){
|
||||
it("should clober incorect text if model changes", function() {
|
||||
compile('<input type="text" name="age" ng:format="number" value="123X"/>');
|
||||
scope.age = 456;
|
||||
scope.$digest();
|
||||
expect(scope.$element.val()).toEqual('456');
|
||||
});
|
||||
|
||||
it("should not clober text if model changes due to itself", function(){
|
||||
it("should not clober text if model changes due to itself", function() {
|
||||
compile('<input type="text" name="list" ng:format="list" value="a"/>');
|
||||
|
||||
scope.$element.val('a ');
|
||||
|
|
@ -128,23 +129,23 @@ describe("widget", function(){
|
|||
expect(scope.list).toEqual(['a', 'b']);
|
||||
});
|
||||
|
||||
it("should come up blank when no value specifiend", function(){
|
||||
it("should come up blank when no value specifiend", function() {
|
||||
compile('<input type="text" name="age" ng:format="number"/>');
|
||||
scope.$digest();
|
||||
expect(scope.$element.val()).toEqual('');
|
||||
expect(scope.age).toEqual(null);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe("checkbox", function(){
|
||||
it("should format booleans", function(){
|
||||
|
||||
describe("checkbox", function() {
|
||||
it("should format booleans", function() {
|
||||
compile('<input type="checkbox" name="name" ng:init="name=false"/>');
|
||||
expect(scope.name).toEqual(false);
|
||||
expect(scope.$element[0].checked).toEqual(false);
|
||||
});
|
||||
|
||||
it('should support type="checkbox"', function(){
|
||||
it('should support type="checkbox"', function() {
|
||||
compile('<input type="checkBox" name="checkbox" checked ng:change="action = true"/>');
|
||||
expect(scope.checkbox).toEqual(true);
|
||||
browserTrigger(element);
|
||||
|
|
@ -154,9 +155,9 @@ describe("widget", function(){
|
|||
expect(scope.checkbox).toEqual(true);
|
||||
});
|
||||
|
||||
it("should use ng:format", function(){
|
||||
it("should use ng:format", function() {
|
||||
angularFormatter('testFormat', {
|
||||
parse: function(value){
|
||||
parse: function(value) {
|
||||
return value ? "Worked" : "Failed";
|
||||
},
|
||||
|
||||
|
|
@ -181,8 +182,9 @@ describe("widget", function(){
|
|||
});
|
||||
});
|
||||
|
||||
describe("ng:validate", function(){
|
||||
it("should process ng:validate", function(){
|
||||
|
||||
describe("ng:validate", function() {
|
||||
it("should process ng:validate", function() {
|
||||
compile('<input type="text" name="price" value="abc" ng:validate="number"/>',
|
||||
jqLite(document.body));
|
||||
expect(element.hasClass('ng-validation-error')).toBeTruthy();
|
||||
|
|
@ -210,9 +212,9 @@ describe("widget", function(){
|
|||
expect(element.attr('ng-validation-error')).toBeFalsy();
|
||||
});
|
||||
|
||||
it("should not call validator if undefined/empty", function(){
|
||||
it("should not call validator if undefined/empty", function() {
|
||||
var lastValue = "NOT_CALLED";
|
||||
angularValidator.myValidator = function(value){lastValue = value;};
|
||||
angularValidator.myValidator = function(value) {lastValue = value;};
|
||||
compile('<input type="text" name="url" ng:validate="myValidator"/>');
|
||||
expect(lastValue).toEqual("NOT_CALLED");
|
||||
|
||||
|
|
@ -225,19 +227,20 @@ describe("widget", function(){
|
|||
});
|
||||
});
|
||||
|
||||
it("should ignore disabled widgets", function(){
|
||||
|
||||
it("should ignore disabled widgets", function() {
|
||||
compile('<input type="text" name="price" ng:required disabled/>');
|
||||
expect(element.hasClass('ng-validation-error')).toBeFalsy();
|
||||
expect(element.attr('ng-validation-error')).toBeFalsy();
|
||||
});
|
||||
|
||||
it("should ignore readonly widgets", function(){
|
||||
it("should ignore readonly widgets", function() {
|
||||
compile('<input type="text" name="price" ng:required readonly/>');
|
||||
expect(element.hasClass('ng-validation-error')).toBeFalsy();
|
||||
expect(element.attr('ng-validation-error')).toBeFalsy();
|
||||
});
|
||||
|
||||
it("should process ng:required", function(){
|
||||
it("should process ng:required", function() {
|
||||
compile('<input type="text" name="price" ng:required/>', jqLite(document.body));
|
||||
expect(element.hasClass('ng-validation-error')).toBeTruthy();
|
||||
expect(element.attr('ng-validation-error')).toEqual('Required');
|
||||
|
|
@ -296,9 +299,8 @@ describe("widget", function(){
|
|||
});
|
||||
|
||||
|
||||
describe('radio', function(){
|
||||
|
||||
it('should support type="radio"', function(){
|
||||
describe('radio', function() {
|
||||
it('should support type="radio"', function() {
|
||||
compile('<div>' +
|
||||
'<input type="radio" name="chose" value="A" ng:change="clicked = 1"/>' +
|
||||
'<input type="radio" name="chose" value="B" checked ng:change="clicked = 2"/>' +
|
||||
|
|
@ -323,7 +325,7 @@ describe("widget", function(){
|
|||
expect(scope.clicked).toEqual(1);
|
||||
});
|
||||
|
||||
it('should honor model over html checked keyword after', function(){
|
||||
it('should honor model over html checked keyword after', function() {
|
||||
compile('<div ng:init="choose=\'C\'">' +
|
||||
'<input type="radio" name="choose" value="A""/>' +
|
||||
'<input type="radio" name="choose" value="B" checked/>' +
|
||||
|
|
@ -333,7 +335,7 @@ describe("widget", function(){
|
|||
expect(scope.choose).toEqual('C');
|
||||
});
|
||||
|
||||
it('should honor model over html checked keyword before', function(){
|
||||
it('should honor model over html checked keyword before', function() {
|
||||
compile('<div ng:init="choose=\'A\'">' +
|
||||
'<input type="radio" name="choose" value="A""/>' +
|
||||
'<input type="radio" name="choose" value="B" checked/>' +
|
||||
|
|
@ -345,8 +347,9 @@ describe("widget", function(){
|
|||
|
||||
});
|
||||
|
||||
describe('select-one', function(){
|
||||
it('should initialize to selected', function(){
|
||||
|
||||
describe('select-one', function() {
|
||||
it('should initialize to selected', function() {
|
||||
compile(
|
||||
'<select name="selection">' +
|
||||
'<option>A</option>' +
|
||||
|
|
@ -372,11 +375,11 @@ describe("widget", function(){
|
|||
|
||||
expect(scope.$element.text()).toBe('foobarC');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('select-multiple', function(){
|
||||
it('should support type="select-multiple"', function(){
|
||||
|
||||
describe('select-multiple', function() {
|
||||
it('should support type="select-multiple"', function() {
|
||||
compile('<select name="selection" multiple>' +
|
||||
'<option>A</option>' +
|
||||
'<option selected>B</option>' +
|
||||
|
|
@ -386,32 +389,32 @@ describe("widget", function(){
|
|||
scope.$digest();
|
||||
expect(element[0].childNodes[0].selected).toEqual(true);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
it('should ignore text widget which have no name', function(){
|
||||
|
||||
it('should ignore text widget which have no name', function() {
|
||||
compile('<input type="text"/>');
|
||||
expect(scope.$element.attr('ng-exception')).toBeFalsy();
|
||||
expect(scope.$element.hasClass('ng-exception')).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should ignore checkbox widget which have no name', function(){
|
||||
it('should ignore checkbox widget which have no name', function() {
|
||||
compile('<input type="checkbox"/>');
|
||||
expect(scope.$element.attr('ng-exception')).toBeFalsy();
|
||||
expect(scope.$element.hasClass('ng-exception')).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should report error on assignment error', function(){
|
||||
expect(function(){
|
||||
it('should report error on assignment error', function() {
|
||||
expect(function() {
|
||||
compile('<input type="text" name="throw \'\'" value="x"/>');
|
||||
}).toThrow("Syntax Error: Token '''' is an unexpected token at column 7 of the expression [throw ''] starting at [''].");
|
||||
$logMock.error.logs.shift();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('ng:switch', function(){
|
||||
it('should switch on value change', function(){
|
||||
|
||||
describe('ng:switch', function() {
|
||||
it('should switch on value change', function() {
|
||||
compile('<ng:switch on="select">' +
|
||||
'<div ng:switch-when="1">first:{{name}}</div>' +
|
||||
'<div ng:switch-when="2">second:{{name}}</div>' +
|
||||
|
|
@ -435,7 +438,7 @@ describe("widget", function(){
|
|||
expect(element.text()).toEqual('true:misko');
|
||||
});
|
||||
|
||||
it('should switch on switch-when-default', function(){
|
||||
it('should switch on switch-when-default', function() {
|
||||
compile('<ng:switch on="select">' +
|
||||
'<div ng:switch-when="1">one</div>' +
|
||||
'<div ng:switch-default>other</div>' +
|
||||
|
|
@ -447,7 +450,7 @@ describe("widget", function(){
|
|||
expect(element.text()).toEqual('one');
|
||||
});
|
||||
|
||||
it('should call change on switch', function(){
|
||||
it('should call change on switch', function() {
|
||||
var scope = angular.compile('<ng:switch on="url" change="name=\'works\'"><div ng:switch-when="a">{{name}}</div></ng:switch>')();
|
||||
scope.url = 'a';
|
||||
scope.$apply();
|
||||
|
|
@ -457,7 +460,8 @@ describe("widget", function(){
|
|||
});
|
||||
});
|
||||
|
||||
describe('ng:include', function(){
|
||||
|
||||
describe('ng:include', function() {
|
||||
it('should include on external file', function() {
|
||||
var element = jqLite('<ng:include src="url" scope="childScope"></ng:include>');
|
||||
var scope = angular.compile(element)();
|
||||
|
|
@ -488,7 +492,7 @@ describe("widget", function(){
|
|||
dealoc(scope);
|
||||
});
|
||||
|
||||
it('should allow this for scope', function(){
|
||||
it('should allow this for scope', function() {
|
||||
var element = jqLite('<ng:include src="url" scope="this"></ng:include>');
|
||||
var scope = angular.compile(element)();
|
||||
scope.url = 'myUrl';
|
||||
|
|
@ -518,7 +522,7 @@ describe("widget", function(){
|
|||
dealoc(element);
|
||||
});
|
||||
|
||||
it('should destroy old scope', function(){
|
||||
it('should destroy old scope', function() {
|
||||
var element = jqLite('<ng:include src="url"></ng:include>');
|
||||
var scope = angular.compile(element)();
|
||||
|
||||
|
|
@ -536,6 +540,7 @@ describe("widget", function(){
|
|||
});
|
||||
});
|
||||
|
||||
|
||||
describe('a', function() {
|
||||
it('should prevent default action to be executed when href is empty', function() {
|
||||
var orgLocation = document.location.href,
|
||||
|
|
@ -571,12 +576,13 @@ describe("widget", function(){
|
|||
});
|
||||
});
|
||||
|
||||
describe('ng:options', function(){
|
||||
|
||||
describe('ng:options', function() {
|
||||
var select, scope;
|
||||
|
||||
function createSelect(attrs, blank, unknown){
|
||||
function createSelect(attrs, blank, unknown) {
|
||||
var html = '<select';
|
||||
forEach(attrs, function(value, key){
|
||||
forEach(attrs, function(value, key) {
|
||||
if (isBoolean(value)) {
|
||||
if (value) html += ' ' + key;
|
||||
} else {
|
||||
|
|
@ -591,14 +597,14 @@ describe("widget", function(){
|
|||
scope = compile(select);
|
||||
}
|
||||
|
||||
function createSingleSelect(blank, unknown){
|
||||
function createSingleSelect(blank, unknown) {
|
||||
createSelect({
|
||||
'name':'selected',
|
||||
'ng:options':'value.name for value in values'
|
||||
}, blank, unknown);
|
||||
}
|
||||
|
||||
function createMultiSelect(blank, unknown){
|
||||
function createMultiSelect(blank, unknown) {
|
||||
createSelect({
|
||||
'name':'selected',
|
||||
'multiple':true,
|
||||
|
|
@ -606,19 +612,19 @@ describe("widget", function(){
|
|||
}, blank, unknown);
|
||||
}
|
||||
|
||||
afterEach(function(){
|
||||
afterEach(function() {
|
||||
dealoc(select);
|
||||
dealoc(scope);
|
||||
});
|
||||
|
||||
it('should throw when not formated "? for ? in ?"', function(){
|
||||
expect(function(){
|
||||
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 '_select_ (as _label_)? for (_key_,)?_value_ in" +
|
||||
" _collection_' but got 'i dont parse'.");
|
||||
});
|
||||
|
||||
it('should render a list', function(){
|
||||
it('should render a list', function() {
|
||||
createSingleSelect();
|
||||
scope.values = [{name:'A'}, {name:'B'}, {name:'C'}];
|
||||
scope.selected = scope.values[0];
|
||||
|
|
@ -630,7 +636,7 @@ describe("widget", function(){
|
|||
expect(sortedHtml(options[2])).toEqual('<option value="2">C</option>');
|
||||
});
|
||||
|
||||
it('should render an object', function(){
|
||||
it('should render an object', function() {
|
||||
createSelect({
|
||||
'name':'selected',
|
||||
'ng:options': 'value as key for (key, value) in object'
|
||||
|
|
@ -651,7 +657,7 @@ describe("widget", function(){
|
|||
expect(options[3].selected).toEqual(true);
|
||||
});
|
||||
|
||||
it('should grow list', function(){
|
||||
it('should grow list', function() {
|
||||
createSingleSelect();
|
||||
scope.values = [];
|
||||
scope.$digest();
|
||||
|
|
@ -671,7 +677,7 @@ describe("widget", function(){
|
|||
expect(sortedHtml(select.find('option')[1])).toEqual('<option value="1">B</option>');
|
||||
});
|
||||
|
||||
it('should shrink list', function(){
|
||||
it('should shrink list', function() {
|
||||
createSingleSelect();
|
||||
scope.values = [{name:'A'}, {name:'B'}, {name:'C'}];
|
||||
scope.selected = scope.values[0];
|
||||
|
|
@ -695,7 +701,7 @@ describe("widget", function(){
|
|||
expect(select.find('option').length).toEqual(1); // we add back the special empty option
|
||||
});
|
||||
|
||||
it('should shrink and then grow list', function(){
|
||||
it('should shrink and then grow list', function() {
|
||||
createSingleSelect();
|
||||
scope.values = [{name:'A'}, {name:'B'}, {name:'C'}];
|
||||
scope.selected = scope.values[0];
|
||||
|
|
@ -713,7 +719,7 @@ describe("widget", function(){
|
|||
expect(select.find('option').length).toEqual(3);
|
||||
});
|
||||
|
||||
it('should update list', function(){
|
||||
it('should update list', function() {
|
||||
createSingleSelect();
|
||||
scope.values = [{name:'A'}, {name:'B'}, {name:'C'}];
|
||||
scope.selected = scope.values[0];
|
||||
|
|
@ -729,7 +735,7 @@ describe("widget", function(){
|
|||
expect(sortedHtml(options[2])).toEqual('<option value="2">D</option>');
|
||||
});
|
||||
|
||||
it('should preserve existing options', function(){
|
||||
it('should preserve existing options', function() {
|
||||
createSingleSelect(true);
|
||||
|
||||
scope.$digest();
|
||||
|
|
@ -749,8 +755,9 @@ describe("widget", function(){
|
|||
expect(jqLite(select.find('option')[0]).text()).toEqual('blank');
|
||||
});
|
||||
|
||||
describe('binding', function(){
|
||||
it('should bind to scope value', function(){
|
||||
|
||||
describe('binding', function() {
|
||||
it('should bind to scope value', function() {
|
||||
createSingleSelect();
|
||||
scope.values = [{name:'A'}, {name:'B'}];
|
||||
scope.selected = scope.values[0];
|
||||
|
|
@ -762,7 +769,8 @@ describe("widget", function(){
|
|||
expect(select.val()).toEqual('1');
|
||||
});
|
||||
|
||||
it('should bind to scope value and group', function(){
|
||||
|
||||
it('should bind to scope value and group', function() {
|
||||
createSelect({
|
||||
'name':'selected',
|
||||
'ng:options':'item.name group by item.group for item in values'
|
||||
|
|
@ -795,7 +803,7 @@ describe("widget", function(){
|
|||
expect(select.val()).toEqual('0');
|
||||
});
|
||||
|
||||
it('should bind to scope value through experession', function(){
|
||||
it('should bind to scope value through experession', function() {
|
||||
createSelect({'name':'selected', 'ng:options':'item.id as item.name for item in values'});
|
||||
scope.values = [{id:10, name:'A'}, {id:20, name:'B'}];
|
||||
scope.selected = scope.values[0].id;
|
||||
|
|
@ -807,7 +815,7 @@ describe("widget", function(){
|
|||
expect(select.val()).toEqual('1');
|
||||
});
|
||||
|
||||
it('should bind to object key', function(){
|
||||
it('should bind to object key', function() {
|
||||
createSelect({
|
||||
'name':'selected',
|
||||
'ng:options':'key as value for (key, value) in object'
|
||||
|
|
@ -822,7 +830,7 @@ describe("widget", function(){
|
|||
expect(select.val()).toEqual('blue');
|
||||
});
|
||||
|
||||
it('should bind to object value', function(){
|
||||
it('should bind to object value', function() {
|
||||
createSelect({
|
||||
name:'selected',
|
||||
'ng:options':'value as key for (key, value) in object'
|
||||
|
|
@ -837,7 +845,7 @@ describe("widget", function(){
|
|||
expect(select.val()).toEqual('blue');
|
||||
});
|
||||
|
||||
it('should insert a blank option if bound to null', function(){
|
||||
it('should insert a blank option if bound to null', function() {
|
||||
createSingleSelect();
|
||||
scope.values = [{name:'A'}];
|
||||
scope.selected = null;
|
||||
|
|
@ -852,7 +860,7 @@ describe("widget", function(){
|
|||
expect(select.find('option').length).toEqual(1);
|
||||
});
|
||||
|
||||
it('should reuse blank option if bound to null', function(){
|
||||
it('should reuse blank option if bound to null', function() {
|
||||
createSingleSelect(true);
|
||||
scope.values = [{name:'A'}];
|
||||
scope.selected = null;
|
||||
|
|
@ -867,7 +875,7 @@ describe("widget", function(){
|
|||
expect(select.find('option').length).toEqual(2);
|
||||
});
|
||||
|
||||
it('should insert a unknown option if bound to something not in the list', function(){
|
||||
it('should insert a unknown option if bound to something not in the list', function() {
|
||||
createSingleSelect();
|
||||
scope.values = [{name:'A'}];
|
||||
scope.selected = {};
|
||||
|
|
@ -883,8 +891,9 @@ describe("widget", function(){
|
|||
});
|
||||
});
|
||||
|
||||
describe('on change', function(){
|
||||
it('should update model on change', function(){
|
||||
|
||||
describe('on change', function() {
|
||||
it('should update model on change', function() {
|
||||
createSingleSelect();
|
||||
scope.values = [{name:'A'}, {name:'B'}];
|
||||
scope.selected = scope.values[0];
|
||||
|
|
@ -896,7 +905,7 @@ describe("widget", function(){
|
|||
expect(scope.selected).toEqual(scope.values[1]);
|
||||
});
|
||||
|
||||
it('should fire ng:change if present', function(){
|
||||
it('should fire ng:change if present', function() {
|
||||
createSelect({
|
||||
name:'selected',
|
||||
'ng:options':'value for value in values',
|
||||
|
|
@ -924,7 +933,7 @@ describe("widget", function(){
|
|||
expect(scope.selected).toEqual(scope.values[0]);
|
||||
});
|
||||
|
||||
it('should update model on change through expression', function(){
|
||||
it('should update model on change through expression', function() {
|
||||
createSelect({name:'selected', 'ng:options':'item.id as item.name for item in values'});
|
||||
scope.values = [{id:10, name:'A'}, {id:20, name:'B'}];
|
||||
scope.selected = scope.values[0].id;
|
||||
|
|
@ -936,7 +945,7 @@ describe("widget", function(){
|
|||
expect(scope.selected).toEqual(scope.values[1].id);
|
||||
});
|
||||
|
||||
it('should update model to null on change', function(){
|
||||
it('should update model to null on change', function() {
|
||||
createSingleSelect(true);
|
||||
scope.values = [{name:'A'}, {name:'B'}];
|
||||
scope.selected = scope.values[0];
|
||||
|
|
@ -949,8 +958,9 @@ describe("widget", function(){
|
|||
});
|
||||
});
|
||||
|
||||
describe('select-many', function(){
|
||||
it('should read multiple selection', function(){
|
||||
|
||||
describe('select-many', function() {
|
||||
it('should read multiple selection', function() {
|
||||
createMultiSelect();
|
||||
scope.values = [{name:'A'}, {name:'B'}];
|
||||
|
||||
|
|
@ -973,7 +983,7 @@ describe("widget", function(){
|
|||
expect(select.find('option')[1].selected).toEqual(true);
|
||||
});
|
||||
|
||||
it('should update model on change', function(){
|
||||
it('should update model on change', function() {
|
||||
createMultiSelect();
|
||||
scope.values = [{name:'A'}, {name:'B'}];
|
||||
|
||||
|
|
@ -990,8 +1000,7 @@ describe("widget", function(){
|
|||
|
||||
|
||||
describe('@ng:repeat', function() {
|
||||
|
||||
it('should ng:repeat over array', function(){
|
||||
it('should ng:repeat over array', function() {
|
||||
var scope = compile('<ul><li ng:repeat="item in items" ng:init="suffix = \';\'" ng:bind="item + suffix"></li></ul>');
|
||||
|
||||
Array.prototype.extraProperty = "should be ignored";
|
||||
|
|
@ -1015,16 +1024,16 @@ describe("widget", function(){
|
|||
expect(element.text()).toEqual('brad;');
|
||||
});
|
||||
|
||||
it('should ng:repeat over object', function(){
|
||||
it('should ng:repeat over object', function() {
|
||||
var scope = compile('<ul><li ng:repeat="(key, value) in items" ng:bind="key + \':\' + value + \';\' "></li></ul>');
|
||||
scope.items = {misko:'swe', shyam:'set'};
|
||||
scope.$digest();
|
||||
expect(element.text()).toEqual('misko:swe;shyam:set;');
|
||||
});
|
||||
|
||||
it('should not ng:repeat over parent properties', function(){
|
||||
var Class = function(){};
|
||||
Class.prototype.abc = function(){};
|
||||
it('should not ng:repeat over parent properties', function() {
|
||||
var Class = function() {};
|
||||
Class.prototype.abc = function() {};
|
||||
Class.prototype.value = 'abc';
|
||||
|
||||
var scope = compile('<ul><li ng:repeat="(key, value) in items" ng:bind="key + \':\' + value + \';\' "></li></ul>');
|
||||
|
|
@ -1034,8 +1043,8 @@ describe("widget", function(){
|
|||
expect(element.text()).toEqual('name:value;');
|
||||
});
|
||||
|
||||
it('should error on wrong parsing of ng:repeat', function(){
|
||||
expect(function(){
|
||||
it('should error on wrong parsing of ng:repeat', function() {
|
||||
expect(function() {
|
||||
compile('<ul><li ng:repeat="i dont parse"></li></ul>');
|
||||
}).toThrow("Expected ng:repeat in form of '_item_ in _collection_' but got 'i dont parse'.");
|
||||
|
||||
|
|
@ -1076,8 +1085,11 @@ describe("widget", function(){
|
|||
});
|
||||
|
||||
it('should expose iterator position as $position when iterating over objects', function() {
|
||||
var scope = compile('<ul><li ng:repeat="(key, val) in items" ' +
|
||||
'ng:bind="key + \':\' + val + \':\' + $position + \'|\'"></li></ul>');
|
||||
var scope = compile(
|
||||
'<ul>' +
|
||||
'<li ng:repeat="(key, val) in items" ng:bind="key + \':\' + val + \':\' + $position + \'|\'">' +
|
||||
'</li>' +
|
||||
'</ul>');
|
||||
scope.items = {'misko':'m', 'shyam':'s', 'doug':'d', 'frodo':'f'};
|
||||
scope.$digest();
|
||||
expect(element.text()).toEqual('misko:m:first|shyam:s:middle|doug:d:middle|frodo:f:last|');
|
||||
|
|
@ -1087,12 +1099,93 @@ describe("widget", function(){
|
|||
scope.$digest();
|
||||
expect(element.text()).toEqual('misko:m:first|shyam:s:last|');
|
||||
});
|
||||
|
||||
|
||||
describe('stability', function() {
|
||||
var a, b, c, d, scope, lis;
|
||||
|
||||
beforeEach(function() {
|
||||
scope = compile(
|
||||
'<ul>' +
|
||||
'<li ng:repeat="item in items" ng:bind="key + \':\' + val + \':\' + $position + \'|\'">' +
|
||||
'</li>' +
|
||||
'</ul>');
|
||||
a = {};
|
||||
b = {};
|
||||
c = {};
|
||||
d = {};
|
||||
|
||||
scope.items = [a, b, c];
|
||||
scope.$digest();
|
||||
lis = element.find('li');
|
||||
});
|
||||
|
||||
it('should preserve the order of elements', function() {
|
||||
scope.items = [a, c, d];
|
||||
scope.$digest();
|
||||
var newElements = element.find('li');
|
||||
expect(newElements[0]).toEqual(lis[0]);
|
||||
expect(newElements[1]).toEqual(lis[2]);
|
||||
expect(newElements[2]).not.toEqual(lis[1]);
|
||||
});
|
||||
|
||||
it('should support duplicates', function() {
|
||||
scope.items = [a, a, b, c];
|
||||
scope.$digest();
|
||||
var newElements = element.find('li');
|
||||
expect(newElements[0]).toEqual(lis[0]);
|
||||
expect(newElements[1]).not.toEqual(lis[0]);
|
||||
expect(newElements[2]).toEqual(lis[1]);
|
||||
expect(newElements[3]).toEqual(lis[2]);
|
||||
|
||||
lis = newElements;
|
||||
scope.$digest();
|
||||
newElements = element.find('li');
|
||||
expect(newElements[0]).toEqual(lis[0]);
|
||||
expect(newElements[1]).toEqual(lis[1]);
|
||||
expect(newElements[2]).toEqual(lis[2]);
|
||||
expect(newElements[3]).toEqual(lis[3]);
|
||||
|
||||
scope.$digest();
|
||||
newElements = element.find('li');
|
||||
expect(newElements[0]).toEqual(lis[0]);
|
||||
expect(newElements[1]).toEqual(lis[1]);
|
||||
expect(newElements[2]).toEqual(lis[2]);
|
||||
expect(newElements[3]).toEqual(lis[3]);
|
||||
});
|
||||
|
||||
it('should remove last item when one duplicate instance is removed', function() {
|
||||
scope.items = [a, a, a];
|
||||
scope.$digest();
|
||||
lis = element.find('li');
|
||||
|
||||
scope.items = [a, a];
|
||||
scope.$digest();
|
||||
var newElements = element.find('li');
|
||||
expect(newElements.length).toEqual(2);
|
||||
expect(newElements[0]).toEqual(lis[0]);
|
||||
expect(newElements[1]).toEqual(lis[1]);
|
||||
});
|
||||
|
||||
it('should reverse items when the collection is reversed', function() {
|
||||
scope.items = [a, b, c];
|
||||
scope.$digest();
|
||||
lis = element.find('li');
|
||||
|
||||
scope.items = [c, b, a];
|
||||
scope.$digest();
|
||||
var newElements = element.find('li');
|
||||
expect(newElements.length).toEqual(3);
|
||||
expect(newElements[0]).toEqual(lis[2]);
|
||||
expect(newElements[1]).toEqual(lis[1]);
|
||||
expect(newElements[2]).toEqual(lis[0]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('@ng:non-bindable', function() {
|
||||
|
||||
it('should prevent compilation of the owning element and its children', function(){
|
||||
it('should prevent compilation of the owning element and its children', function() {
|
||||
var scope = compile('<div ng:non-bindable><span ng:bind="name"></span></div>');
|
||||
scope.name = 'misko';
|
||||
scope.$digest();
|
||||
|
|
@ -1203,7 +1296,6 @@ describe("widget", function(){
|
|||
dealoc($route.current.scope);
|
||||
});
|
||||
|
||||
|
||||
it('should initialize view template after the view controller was initialized even when ' +
|
||||
'templates were cached', function() {
|
||||
//this is a test for a regression that was introduced by making the ng:view cache sync
|
||||
|
|
@ -1245,6 +1337,8 @@ describe("widget", function(){
|
|||
|
||||
|
||||
describe('ng:pluralize', function() {
|
||||
|
||||
|
||||
describe('deal with pluralized strings without offset', function() {
|
||||
beforeEach(function() {
|
||||
compile('<ng:pluralize count="email"' +
|
||||
|
|
@ -1366,7 +1460,6 @@ describe("widget", function(){
|
|||
expect(element.text()).toBe('Igor, Misko and 2 other people are viewing.');
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue