aboutsummaryrefslogtreecommitdiffstats
path: root/test/BinderSpec.js
diff options
context:
space:
mode:
authorMisko Hevery2011-03-23 09:33:29 -0700
committerVojta Jina2011-08-02 01:00:03 +0200
commit8f0dcbab804180828d6859b1340c86cf161209fb (patch)
treed13d47d47a1889cb7c96a87cecacd2e25307d51c /test/BinderSpec.js
parent1f4b417184ce53af15474de065400f8a686430c5 (diff)
downloadangular.js-8f0dcbab804180828d6859b1340c86cf161209fb.tar.bz2
feat(scope): new and improved scope implementation
- Speed improvements (about 4x on flush phase) - Memory improvements (uses no function closures) - Break $eval into $apply, $dispatch, $flush - Introduced $watch and $observe Breaks angular.equals() use === instead of == Breaks angular.scope() does not take parent as first argument Breaks scope.$watch() takes scope as first argument Breaks scope.$set(), scope.$get are removed Breaks scope.$config is removed Breaks $route.onChange callback has not "this" bounded
Diffstat (limited to 'test/BinderSpec.js')
-rw-r--r--test/BinderSpec.js274
1 files changed, 134 insertions, 140 deletions
diff --git a/test/BinderSpec.js b/test/BinderSpec.js
index 241bb98d..a84fe68a 100644
--- a/test/BinderSpec.js
+++ b/test/BinderSpec.js
@@ -1,11 +1,10 @@
'use strict';
describe('Binder', function(){
-
beforeEach(function(){
var self = this;
- this.compile = function(html, parent) {
+ this.compile = function(html, parent, logErrors) {
if (self.element) dealoc(self.element);
var element;
if (parent) {
@@ -15,7 +14,8 @@ describe('Binder', function(){
element = jqLite(html);
}
self.element = element;
- return angular.compile(element)();
+ return angular.compile(element)(angular.scope(null,
+ logErrors ? {'$exceptionHandler': $exceptionHandlerMockFactory()} : null));
};
this.compileToHtml = function (content) {
return sortedHtml(this.compile(content).$element);
@@ -31,20 +31,20 @@ describe('Binder', function(){
it('text-field should default to value attribute', function(){
var scope = this.compile('<input type="text" name="model.price" value="abc">');
- scope.$eval();
+ scope.$apply();
assertEquals('abc', scope.model.price);
});
it('ChangingTextareaUpdatesModel', function(){
var scope = this.compile('<textarea name="model.note">abc</textarea>');
- scope.$eval();
+ scope.$apply();
assertEquals(scope.model.note, 'abc');
});
it('ChangingRadioUpdatesModel', function(){
var scope = this.compile('<div><input type="radio" name="model.price" value="A" checked>' +
'<input type="radio" name="model.price" value="B"></div>');
- scope.$eval();
+ scope.$apply();
assertEquals(scope.model.price, 'A');
});
@@ -55,7 +55,8 @@ describe('Binder', function(){
it('BindUpdate', function(){
var scope = this.compile('<div ng:eval="a=123"/>');
- assertEquals(123, scope.$get('a'));
+ scope.$flush();
+ assertEquals(123, scope.a);
});
it('ChangingSelectNonSelectedUpdatesModel', function(){
@@ -69,7 +70,7 @@ describe('Binder', function(){
'<option value="B" selected>Extra padding</option>' +
'<option value="C">Expedite</option>' +
'</select>');
- assertJsonEquals(["A", "B"], scope.$get('Invoice').options);
+ assertJsonEquals(["A", "B"], scope.Invoice.options);
});
it('ChangingSelectSelectedUpdatesModel', function(){
@@ -79,19 +80,19 @@ describe('Binder', function(){
it('ExecuteInitialization', function(){
var scope = this.compile('<div ng:init="a=123">');
- assertEquals(scope.$get('a'), 123);
+ assertEquals(scope.a, 123);
});
it('ExecuteInitializationStatements', function(){
var scope = this.compile('<div ng:init="a=123;b=345">');
- assertEquals(scope.$get('a'), 123);
- assertEquals(scope.$get('b'), 345);
+ assertEquals(scope.a, 123);
+ assertEquals(scope.b, 345);
});
it('ApplyTextBindings', function(){
var scope = this.compile('<div ng:bind="model.a">x</div>');
- scope.$set('model', {a:123});
- scope.$eval();
+ scope.model = {a:123};
+ scope.$apply();
assertEquals('123', scope.$element.text());
});
@@ -145,7 +146,7 @@ describe('Binder', function(){
it('AttributesAreEvaluated', function(){
var scope = this.compile('<a ng:bind-attr=\'{"a":"a", "b":"a+b={{a+b}}"}\'></a>');
scope.$eval('a=1;b=2');
- scope.$eval();
+ scope.$apply();
var a = scope.$element;
assertEquals(a.attr('a'), 'a');
assertEquals(a.attr('b'), 'a+b=3');
@@ -154,9 +155,10 @@ describe('Binder', function(){
it('InputTypeButtonActionExecutesInScope', function(){
var savedCalled = false;
var scope = this.compile('<input type="button" ng:click="person.save()" value="Apply">');
- scope.$set("person.save", function(){
+ scope.person = {};
+ scope.person.save = function(){
savedCalled = true;
- });
+ };
browserTrigger(scope.$element, 'click');
assertTrue(savedCalled);
});
@@ -164,9 +166,9 @@ describe('Binder', function(){
it('InputTypeButtonActionExecutesInScope2', function(){
var log = "";
var scope = this.compile('<input type="image" ng:click="action()">');
- scope.$set("action", function(){
+ scope.action = function(){
log += 'click;';
- });
+ };
expect(log).toEqual('');
browserTrigger(scope.$element, 'click');
expect(log).toEqual('click;');
@@ -175,9 +177,10 @@ describe('Binder', function(){
it('ButtonElementActionExecutesInScope', function(){
var savedCalled = false;
var scope = this.compile('<button ng:click="person.save()">Apply</button>');
- scope.$set("person.save", function(){
+ scope.person = {};
+ scope.person.save = function(){
savedCalled = true;
- });
+ };
browserTrigger(scope.$element, 'click');
assertTrue(savedCalled);
});
@@ -186,9 +189,9 @@ describe('Binder', function(){
var scope = this.compile('<ul><LI ng:repeat="item in model.items" ng:bind="item.a"/></ul>');
var form = scope.$element;
var items = [{a:"A"}, {a:"B"}];
- scope.$set('model', {items:items});
+ scope.model = {items:items};
- scope.$eval();
+ scope.$apply();
assertEquals('<ul>' +
'<#comment></#comment>' +
'<li ng:bind="item.a" ng:repeat-index="0">A</li>' +
@@ -196,7 +199,7 @@ describe('Binder', function(){
'</ul>', sortedHtml(form));
items.unshift({a:'C'});
- scope.$eval();
+ scope.$apply();
assertEquals('<ul>' +
'<#comment></#comment>' +
'<li ng:bind="item.a" ng:repeat-index="0">C</li>' +
@@ -205,7 +208,7 @@ describe('Binder', function(){
'</ul>', sortedHtml(form));
items.shift();
- scope.$eval();
+ scope.$apply();
assertEquals('<ul>' +
'<#comment></#comment>' +
'<li ng:bind="item.a" ng:repeat-index="0">A</li>' +
@@ -214,13 +217,13 @@ describe('Binder', function(){
items.shift();
items.shift();
- scope.$eval();
+ scope.$apply();
});
it('RepeaterContentDoesNotBind', function(){
var scope = this.compile('<ul><LI ng:repeat="item in model.items"><span ng:bind="item.a"></span></li></ul>');
- scope.$set('model', {items:[{a:"A"}]});
- scope.$eval();
+ scope.model = {items:[{a:"A"}]};
+ scope.$apply();
assertEquals('<ul>' +
'<#comment></#comment>' +
'<li ng:repeat-index="0"><span ng:bind="item.a">A</span></li>' +
@@ -234,59 +237,62 @@ describe('Binder', function(){
it('RepeaterAdd', function(){
var scope = this.compile('<div><input type="text" name="item.x" ng:repeat="item in items"></div>');
- scope.$set('items', [{x:'a'}, {x:'b'}]);
- scope.$eval();
+ scope.items = [{x:'a'}, {x:'b'}];
+ scope.$apply();
var first = childNode(scope.$element, 1);
var second = childNode(scope.$element, 2);
- assertEquals('a', first.val());
- assertEquals('b', second.val());
+ expect(first.val()).toEqual('a');
+ expect(second.val()).toEqual('b');
+ return
first.val('ABC');
browserTrigger(first, 'keydown');
scope.$service('$browser').defer.flush();
- assertEquals(scope.items[0].x, 'ABC');
+ expect(scope.items[0].x).toEqual('ABC');
});
it('ItShouldRemoveExtraChildrenWhenIteratingOverHash', function(){
var scope = this.compile('<div><div ng:repeat="i in items">{{i}}</div></div>');
var items = {};
- scope.$set("items", items);
+ scope.items = items;
- scope.$eval();
+ scope.$apply();
expect(scope.$element[0].childNodes.length - 1).toEqual(0);
items.name = "misko";
- scope.$eval();
+ scope.$apply();
expect(scope.$element[0].childNodes.length - 1).toEqual(1);
delete items.name;
- scope.$eval();
+ scope.$apply();
expect(scope.$element[0].childNodes.length - 1).toEqual(0);
});
it('IfTextBindingThrowsErrorDecorateTheSpan', function(){
- var scope = this.compile('<div>{{error.throw()}}</div>');
+ var scope = this.compile('<div>{{error.throw()}}</div>', null, true);
var doc = scope.$element;
- var errorLogs = scope.$service('$log').error.logs;
+ var errorLogs = scope.$service('$exceptionHandler').errors;
- scope.$set('error.throw', function(){throw "ErrorMsg1";});
- scope.$eval();
+ scope.error = {
+ 'throw': function(){throw "ErrorMsg1";}
+ };
+ scope.$apply();
var span = childNode(doc, 0);
assertTrue(span.hasClass('ng-exception'));
assertTrue(!!span.text().match(/ErrorMsg1/));
assertTrue(!!span.attr('ng-exception').match(/ErrorMsg1/));
assertEquals(['ErrorMsg1'], errorLogs.shift());
- scope.$set('error.throw', function(){throw "MyError";});
- scope.$eval();
+ scope.error['throw'] = function(){throw "MyError";};
+ scope.$apply();
span = childNode(doc, 0);
assertTrue(span.hasClass('ng-exception'));
assertTrue(span.text(), span.text().match('MyError') !== null);
assertEquals('MyError', span.attr('ng-exception'));
assertEquals(['MyError'], errorLogs.shift());
- scope.$set('error.throw', function(){return "ok";});
- scope.$eval();
+ scope.error['throw'] = function(){return "ok";};
+ scope.$apply();
assertFalse(span.hasClass('ng-exception'));
assertEquals('ok', span.text());
assertEquals(null, span.attr('ng-exception'));
@@ -294,23 +300,21 @@ describe('Binder', function(){
});
it('IfAttrBindingThrowsErrorDecorateTheAttribute', function(){
- var scope = this.compile('<div attr="before {{error.throw()}} after"></div>');
+ var scope = this.compile('<div attr="before {{error.throw()}} after"></div>', null, true);
var doc = scope.$element;
- var errorLogs = scope.$service('$log').error.logs;
+ var errorLogs = scope.$service('$exceptionHandler').errors;
+ var count = 0;
- scope.$set('error.throw', function(){throw "ErrorMsg";});
- scope.$eval();
- assertTrue('ng-exception', doc.hasClass('ng-exception'));
- assertEquals('"ErrorMsg"', doc.attr('ng-exception'));
- assertEquals('before "ErrorMsg" after', doc.attr('attr'));
- assertEquals(['ErrorMsg'], errorLogs.shift());
-
- scope.$set('error.throw', function(){ return 'X';});
- scope.$eval();
- assertFalse('!ng-exception', doc.hasClass('ng-exception'));
- assertEquals('before X after', doc.attr('attr'));
- assertEquals(null, doc.attr('ng-exception'));
- assertEquals(0, errorLogs.length);
+ scope.error = {
+ 'throw': function(){throw new Error("ErrorMsg" + (++count));}
+ };
+ scope.$apply();
+ expect(errorLogs.length).toMatch(1);
+ expect(errorLogs.shift()).toMatch(/ErrorMsg1/);
+
+ scope.error['throw'] = function(){ return 'X';};
+ scope.$apply();
+ expect(errorLogs.length).toMatch(0);
});
it('NestedRepeater', function(){
@@ -318,8 +322,8 @@ describe('Binder', function(){
'<ul name="{{i}}" ng:repeat="i in m.item"></ul>' +
'</div></div>');
- scope.$set('model', [{name:'a', item:['a1', 'a2']}, {name:'b', item:['b1', 'b2']}]);
- scope.$eval();
+ scope.model = [{name:'a', item:['a1', 'a2']}, {name:'b', item:['b1', 'b2']}];
+ scope.$apply();
assertEquals('<div>'+
'<#comment></#comment>'+
@@ -338,13 +342,13 @@ describe('Binder', function(){
it('HideBindingExpression', function(){
var scope = this.compile('<div ng:hide="hidden == 3"/>');
- scope.$set('hidden', 3);
- scope.$eval();
+ scope.hidden = 3;
+ scope.$apply();
assertHidden(scope.$element);
- scope.$set('hidden', 2);
- scope.$eval();
+ scope.hidden = 2;
+ scope.$apply();
assertVisible(scope.$element);
});
@@ -352,18 +356,18 @@ describe('Binder', function(){
it('HideBinding', function(){
var scope = this.compile('<div ng:hide="hidden"/>');
- scope.$set('hidden', 'true');
- scope.$eval();
+ scope.hidden = 'true';
+ scope.$apply();
assertHidden(scope.$element);
- scope.$set('hidden', 'false');
- scope.$eval();
+ scope.hidden = 'false';
+ scope.$apply();
assertVisible(scope.$element);
- scope.$set('hidden', '');
- scope.$eval();
+ scope.hidden = '';
+ scope.$apply();
assertVisible(scope.$element);
});
@@ -371,25 +375,25 @@ describe('Binder', function(){
it('ShowBinding', function(){
var scope = this.compile('<div ng:show="show"/>');
- scope.$set('show', 'true');
- scope.$eval();
+ scope.show = 'true';
+ scope.$apply();
assertVisible(scope.$element);
- scope.$set('show', 'false');
- scope.$eval();
+ scope.show = 'false';
+ scope.$apply();
assertHidden(scope.$element);
- scope.$set('show', '');
- scope.$eval();
+ scope.show = '';
+ scope.$apply();
assertHidden(scope.$element);
});
it('BindClassUndefined', function(){
var scope = this.compile('<div ng:class="undefined"/>');
- scope.$eval();
+ scope.$apply();
assertEquals(
'<div class="undefined" ng:class="undefined"></div>',
@@ -397,22 +401,22 @@ describe('Binder', function(){
});
it('BindClass', function(){
- var scope = this.compile('<div ng:class="class"/>');
+ var scope = this.compile('<div ng:class="clazz"/>');
- scope.$set('class', 'testClass');
- scope.$eval();
+ scope.clazz = 'testClass';
+ scope.$apply();
- assertEquals('<div class="testClass" ng:class="class"></div>', sortedHtml(scope.$element));
+ assertEquals('<div class="testClass" ng:class="clazz"></div>', sortedHtml(scope.$element));
- scope.$set('class', ['a', 'b']);
- scope.$eval();
+ scope.clazz = ['a', 'b'];
+ scope.$apply();
- assertEquals('<div class="a b" ng:class="class"></div>', sortedHtml(scope.$element));
+ assertEquals('<div class="a b" ng:class="clazz"></div>', sortedHtml(scope.$element));
});
it('BindClassEvenOdd', function(){
var scope = this.compile('<div><div ng:repeat="i in [0,1]" ng:class-even="\'e\'" ng:class-odd="\'o\'"></div></div>');
- scope.$eval();
+ scope.$apply();
var d1 = jqLite(scope.$element[0].childNodes[1]);
var d2 = jqLite(scope.$element[0].childNodes[2]);
expect(d1.hasClass('o')).toBeTruthy();
@@ -428,31 +432,22 @@ describe('Binder', function(){
var scope = this.compile('<div ng:style="style"/>');
scope.$eval('style={height: "10px"}');
- scope.$eval();
+ scope.$apply();
assertEquals("10px", scope.$element.css('height'));
scope.$eval('style={}');
- scope.$eval();
+ scope.$apply();
});
it('ActionOnAHrefThrowsError', function(){
- var scope = this.compile('<a ng:click="action()">Add Phone</a>');
+ var scope = this.compile('<a ng:click="action()">Add Phone</a>', null, true);
scope.action = function(){
throw new Error('MyError');
};
var input = scope.$element;
browserTrigger(input, 'click');
- var error = input.attr('ng-exception');
- assertTrue(!!error.match(/MyError/));
- assertTrue("should have an error class", input.hasClass('ng-exception'));
- assertTrue(!!scope.$service('$log').error.logs.shift()[0].message.match(/MyError/));
-
- // TODO: I think that exception should never get cleared so this portion of test makes no sense
- //c.scope.action = noop;
- //browserTrigger(input, 'click');
- //dump(input.attr('ng-error'));
- //assertFalse('error class should be cleared', input.hasClass('ng-exception'));
+ expect(scope.$service('$exceptionHandler').errors[0]).toMatch(/MyError/);
});
it('ShoulIgnoreVbNonBindable', function(){
@@ -460,16 +455,15 @@ describe('Binder', function(){
"<div ng:non-bindable>{{a}}</div>" +
"<div ng:non-bindable=''>{{b}}</div>" +
"<div ng:non-bindable='true'>{{c}}</div></div>");
- scope.$set('a', 123);
- scope.$eval();
+ scope.a = 123;
+ scope.$apply();
assertEquals('123{{a}}{{b}}{{c}}', scope.$element.text());
});
-
it('RepeaterShouldBindInputsDefaults', function () {
var scope = this.compile('<div><input value="123" name="item.name" ng:repeat="item in items"></div>');
- scope.$set('items', [{}, {name:'misko'}]);
- scope.$eval();
+ scope.items = [{}, {name:'misko'}];
+ scope.$apply();
assertEquals("123", scope.$eval('items[0].name'));
assertEquals("misko", scope.$eval('items[1].name'));
@@ -477,8 +471,8 @@ describe('Binder', function(){
it('ShouldTemplateBindPreElements', function () {
var scope = this.compile('<pre>Hello {{name}}!</pre>');
- scope.$set("name", "World");
- scope.$eval();
+ scope.name = "World";
+ scope.$apply();
assertEquals('<pre ng:bind-template="Hello {{name}}!">Hello World!</pre>', sortedHtml(scope.$element));
});
@@ -486,9 +480,9 @@ describe('Binder', function(){
it('FillInOptionValueWhenMissing', function(){
var scope = this.compile(
'<select name="foo"><option selected="true">{{a}}</option><option value="">{{b}}</option><option>C</option></select>');
- scope.$set('a', 'A');
- scope.$set('b', 'B');
- scope.$eval();
+ scope.a = 'A';
+ scope.b = 'B';
+ scope.$apply();
var optionA = childNode(scope.$element, 0);
var optionB = childNode(scope.$element, 1);
var optionC = childNode(scope.$element, 2);
@@ -508,39 +502,39 @@ describe('Binder', function(){
'<input ng:repeat="item in items" name="item.name" ng:required/></div>',
jqLite(document.body));
var items = [{}, {}];
- scope.$set("items", items);
- scope.$eval();
+ scope.items = items;
+ scope.$apply();
assertEquals(3, scope.$service('$invalidWidgets').length);
- scope.$set('name', '');
- scope.$eval();
+ scope.name = '';
+ scope.$apply();
assertEquals(3, scope.$service('$invalidWidgets').length);
- scope.$set('name', ' ');
- scope.$eval();
+ scope.name = ' ';
+ scope.$apply();
assertEquals(3, scope.$service('$invalidWidgets').length);
- scope.$set('name', 'abc');
- scope.$eval();
+ scope.name = 'abc';
+ scope.$apply();
assertEquals(2, scope.$service('$invalidWidgets').length);
items[0].name = 'abc';
- scope.$eval();
+ scope.$apply();
assertEquals(1, scope.$service('$invalidWidgets').length);
items[1].name = 'abc';
- scope.$eval();
+ scope.$apply();
assertEquals(0, scope.$service('$invalidWidgets').length);
});
it('ValidateOnlyVisibleItems', function(){
var scope = this.compile('<div><input name="name" ng:required><input ng:show="show" name="name" ng:required></div>', jqLite(document.body));
- scope.$set("show", true);
- scope.$eval();
+ scope.show = true;
+ scope.$apply();
assertEquals(2, scope.$service('$invalidWidgets').length);
- scope.$set("show", false);
- scope.$eval();
+ scope.show = false;
+ scope.$apply();
assertEquals(1, scope.$service('$invalidWidgets').visible());
});
@@ -549,7 +543,7 @@ describe('Binder', function(){
'<input name="a0" ng:bind-attr="{disabled:\'{{true}}\'}"><input name="a1" ng:bind-attr="{disabled:\'{{false}}\'}">' +
'<input name="b0" ng:bind-attr="{disabled:\'{{1}}\'}"><input name="b1" ng:bind-attr="{disabled:\'{{0}}\'}">' +
'<input name="c0" ng:bind-attr="{disabled:\'{{[0]}}\'}"><input name="c1" ng:bind-attr="{disabled:\'{{[]}}\'}"></div>');
- scope.$eval();
+ scope.$apply();
function assertChild(index, disabled) {
var child = childNode(scope.$element, index);
assertEquals(sortedHtml(child), disabled, !!child.attr('disabled'));
@@ -566,7 +560,7 @@ describe('Binder', function(){
it('ItShouldDisplayErrorWhenActionIsSyntacticlyIncorrect', function(){
var scope = this.compile('<div>' +
'<input type="button" ng:click="greeting=\'ABC\'"/>' +
- '<input type="button" ng:click=":garbage:"/></div>');
+ '<input type="button" ng:click=":garbage:"/></div>', null, true);
var first = jqLite(scope.$element[0].childNodes[0]);
var second = jqLite(scope.$element[0].childNodes[1]);
var errorLogs = scope.$service('$log').error.logs;
@@ -576,8 +570,8 @@ describe('Binder', function(){
expect(errorLogs).toEqual([]);
browserTrigger(second, 'click');
- assertTrue(second.hasClass("ng-exception"));
- expect(errorLogs.shift()[0]).toMatchError(/Syntax Error: Token ':' not a primary expression/);
+ expect(scope.$service('$exceptionHandler').errors[0]).
+ toMatchError(/Syntax Error: Token ':' not a primary expression/);
});
it('ItShouldSelectTheCorrectRadioBox', function(){
@@ -602,7 +596,7 @@ describe('Binder', function(){
it('ItShouldRepeatOnHashes', function(){
var scope = this.compile('<ul><li ng:repeat="(k,v) in {a:0,b:1}" ng:bind=\"k + v\"></li></ul>');
- scope.$eval();
+ scope.$apply();
assertEquals('<ul>' +
'<#comment></#comment>' +
'<li ng:bind=\"k + v\" ng:repeat-index="0">a0</li>' +
@@ -613,11 +607,11 @@ describe('Binder', function(){
it('ItShouldFireChangeListenersBeforeUpdate', function(){
var scope = this.compile('<div ng:bind="name"></div>');
- scope.$set("name", "");
+ scope.name = "";
scope.$watch("watched", "name=123");
- scope.$set("watched", "change");
- scope.$eval();
- assertEquals(123, scope.$get("name"));
+ scope.watched = "change";
+ scope.$apply();
+ assertEquals(123, scope.name);
assertEquals(
'<div ng:bind="name">123</div>',
sortedHtml(scope.$element));
@@ -625,26 +619,26 @@ describe('Binder', function(){
it('ItShouldHandleMultilineBindings', function(){
var scope = this.compile('<div>{{\n 1 \n + \n 2 \n}}</div>');
- scope.$eval();
+ scope.$apply();
assertEquals("3", scope.$element.text());
});
it('ItBindHiddenInputFields', function(){
var scope = this.compile('<input type="hidden" name="myName" value="abc" />');
- scope.$eval();
- assertEquals("abc", scope.$get("myName"));
+ scope.$apply();
+ assertEquals("abc", scope.myName);
});
it('ItShouldUseFormaterForText', function(){
var scope = this.compile('<input name="a" ng:format="list" value="a,b">');
- scope.$eval();
- assertEquals(['a','b'], scope.$get('a'));
+ scope.$apply();
+ assertEquals(['a','b'], scope.a);
var input = scope.$element;
input[0].value = ' x,,yz';
browserTrigger(input, 'change');
- assertEquals(['x','yz'], scope.$get('a'));
- scope.$set('a', [1 ,2, 3]);
- scope.$eval();
+ assertEquals(['x','yz'], scope.a);
+ scope.a = [1 ,2, 3];
+ scope.$apply();
assertEquals('1, 2, 3', input[0].value);
});