diff options
| author | Misko Hevery | 2011-03-23 09:33:29 -0700 |
|---|---|---|
| committer | Vojta Jina | 2011-08-02 01:00:03 +0200 |
| commit | 8f0dcbab804180828d6859b1340c86cf161209fb (patch) | |
| tree | d13d47d47a1889cb7c96a87cecacd2e25307d51c /test | |
| parent | 1f4b417184ce53af15474de065400f8a686430c5 (diff) | |
| download | angular.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')
28 files changed, 1020 insertions, 705 deletions
diff --git a/test/AngularSpec.js b/test/AngularSpec.js index 0166503c..abb34f3e 100644 --- a/test/AngularSpec.js +++ b/test/AngularSpec.js @@ -63,7 +63,8 @@ describe('angular', function(){ it('should return true if same object', function(){ var o = {}; expect(equals(o, o)).toEqual(true); - expect(equals(1, '1')).toEqual(true); + expect(equals(o, {})).toEqual(true); + expect(equals(1, '1')).toEqual(false); expect(equals(1, '2')).toEqual(false); }); @@ -550,6 +551,7 @@ describe('angular', function(){ it('should link to existing node and create scope', function(){ template = angular.element('<div>{{greeting = "hello world"}}</div>'); scope = angular.compile(template)(); + scope.$flush(); expect(template.text()).toEqual('hello world'); expect(scope.greeting).toEqual('hello world'); }); @@ -558,6 +560,7 @@ describe('angular', function(){ scope = angular.scope(); template = angular.element('<div>{{greeting = "hello world"}}</div>'); angular.compile(template)(scope); + scope.$flush(); expect(template.text()).toEqual('hello world'); expect(scope).toEqual(scope); }); @@ -572,6 +575,7 @@ describe('angular', function(){ templateFn(scope, function(clone){ templateClone = clone; }); + scope.$flush(); expect(template.text()).toEqual(''); expect(scope.$element.text()).toEqual('hello world'); @@ -582,7 +586,7 @@ describe('angular', function(){ it('should link to cloned node and create scope', function(){ scope = angular.scope(); template = jqLite('<div>{{greeting = "hello world"}}</div>'); - angular.compile(template)(scope, noop); + angular.compile(template)(scope, noop).$flush(); expect(template.text()).toEqual(''); expect(scope.$element.text()).toEqual('hello world'); expect(scope.greeting).toEqual('hello world'); 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); }); diff --git a/test/CompilerSpec.js b/test/CompilerSpec.js index 1bfc6173..90afbaff 100644 --- a/test/CompilerSpec.js +++ b/test/CompilerSpec.js @@ -13,9 +13,9 @@ describe('compiler', function(){ }; }, - watch: function(expression, element){ + observe: function(expression, element){ return function() { - this.$watch(expression, function(val){ + this.$observe(expression, function(scope, val){ if (val) log += ":" + val; }); @@ -33,10 +33,12 @@ describe('compiler', function(){ }; }); + afterEach(function(){ dealoc(scope); }); + it('should not allow compilation of multiple roots', function(){ expect(function(){ compiler.compile('<div>A</div><span></span>'); @@ -46,6 +48,7 @@ describe('compiler', function(){ } }); + it('should recognize a directive', function(){ var e = jqLite('<div directive="expr" ignore="me"></div>'); directives.directive = function(expression, element){ @@ -63,50 +66,56 @@ describe('compiler', function(){ expect(log).toEqual("found:init"); }); + it('should recurse to children', function(){ scope = compile('<div><span hello="misko"/></div>'); expect(log).toEqual("hello misko"); }); - it('should watch scope', function(){ - scope = compile('<span watch="name"/>'); + + it('should observe scope', function(){ + scope = compile('<span observe="name">'); expect(log).toEqual(""); - scope.$eval(); - scope.$set('name', 'misko'); - scope.$eval(); - scope.$eval(); - scope.$set('name', 'adam'); - scope.$eval(); - scope.$eval(); + scope.$flush(); + scope.name = 'misko'; + scope.$flush(); + scope.$flush(); + scope.name = 'adam'; + scope.$flush(); + scope.$flush(); expect(log).toEqual(":misko:adam"); }); + it('should prevent descend', function(){ directives.stop = function(){ this.descend(false); }; scope = compile('<span hello="misko" stop="true"><span hello="adam"/></span>'); expect(log).toEqual("hello misko"); }); + it('should allow creation of templates', function(){ directives.duplicate = function(expr, element){ element.replaceWith(document.createComment("marker")); element.removeAttr("duplicate"); var linker = this.compile(element); return function(marker) { - this.$onEval(function() { + this.$observe(function() { var scope = linker(angular.scope(), noop); marker.after(scope.$element); }); }; }; scope = compile('before<span duplicate="expr">x</span>after'); + scope.$flush(); expect(sortedHtml(scope.$element)).toEqual('<div>before<#comment></#comment><span>x</span>after</div>'); - scope.$eval(); + scope.$flush(); expect(sortedHtml(scope.$element)).toEqual('<div>before<#comment></#comment><span>x</span><span>x</span>after</div>'); - scope.$eval(); + scope.$flush(); expect(sortedHtml(scope.$element)).toEqual('<div>before<#comment></#comment><span>x</span><span>x</span><span>x</span>after</div>'); }); + it('should process markup before directives', function(){ markup.push(function(text, textNode, parentNode) { if (text == 'middle') { @@ -120,6 +129,7 @@ describe('compiler', function(){ expect(log).toEqual("hello middle"); }); + it('should replace widgets', function(){ widgets['NG:BUTTON'] = function(element) { expect(element.hasClass('ng-widget')).toEqual(true); @@ -133,6 +143,7 @@ describe('compiler', function(){ expect(log).toEqual('init'); }); + it('should use the replaced element after calling widget', function(){ widgets['H1'] = function(element) { // HTML elements which are augmented by acting as widgets, should not be marked as so @@ -151,6 +162,7 @@ describe('compiler', function(){ expect(scope.$element.text()).toEqual('3'); }); + it('should allow multiple markups per text element', function(){ markup.push(function(text, textNode, parent){ var index = text.indexOf('---'); @@ -174,6 +186,7 @@ describe('compiler', function(){ expect(sortedHtml(scope.$element)).toEqual('<div>A<hr></hr>B<hr></hr>C<p></p>D</div>'); }); + it('should add class for namespace elements', function(){ scope = compile('<ng:space>abc</ng:space>'); var space = jqLite(scope.$element[0].firstChild); diff --git a/test/ParserSpec.js b/test/ParserSpec.js index 71ac9813..4c3cb64b 100644 --- a/test/ParserSpec.js +++ b/test/ParserSpec.js @@ -204,15 +204,15 @@ describe('parser', function() { scope.$eval("1|nonExistant"); }).toThrow(new Error("Syntax Error: Token 'nonExistant' should be a function at column 3 of the expression [1|nonExistant] starting at [nonExistant].")); - scope.$set('offset', 3); + scope.offset = 3; expect(scope.$eval("'abcd'|upper._case")).toEqual("ABCD"); expect(scope.$eval("'abcd'|substring:1:offset")).toEqual("bc"); expect(scope.$eval("'abcd'|substring:1:3|upper._case")).toEqual("BC"); }); it('should access scope', function() { - scope.$set('a', 123); - scope.$set('b.c', 456); + scope.a = 123; + scope.b = {c: 456}; expect(scope.$eval("a", scope)).toEqual(123); expect(scope.$eval("b.c", scope)).toEqual(456); expect(scope.$eval("x.y.z", scope)).not.toBeDefined(); @@ -224,32 +224,32 @@ describe('parser', function() { it('should evaluate assignments', function() { expect(scope.$eval("a=12")).toEqual(12); - expect(scope.$get("a")).toEqual(12); + expect(scope.a).toEqual(12); scope = createScope(); expect(scope.$eval("x.y.z=123;")).toEqual(123); - expect(scope.$get("x.y.z")).toEqual(123); + expect(scope.x.y.z).toEqual(123); expect(scope.$eval("a=123; b=234")).toEqual(234); - expect(scope.$get("a")).toEqual(123); - expect(scope.$get("b")).toEqual(234); + expect(scope.a).toEqual(123); + expect(scope.b).toEqual(234); }); it('should evaluate function call without arguments', function() { - scope.$set('const', function(a,b){return 123;}); + scope['const'] = function(a,b){return 123;}; expect(scope.$eval("const()")).toEqual(123); }); it('should evaluate function call with arguments', function() { - scope.$set('add', function(a,b) { + scope.add = function(a,b) { return a+b; - }); + }; expect(scope.$eval("add(1,2)")).toEqual(3); }); it('should evaluate multiplication and division', function() { - scope.$set('taxRate', 8); - scope.$set('subTotal', 100); + scope.taxRate = 8; + scope.subTotal = 100; expect(scope.$eval("taxRate / 100 * subTotal")).toEqual(8); expect(scope.$eval("subTotal * taxRate / 100")).toEqual(8); }); @@ -297,7 +297,7 @@ describe('parser', function() { return this.a; }; - scope.$set("obj", new C()); + scope.obj = new C(); expect(scope.$eval("obj.getA()")).toEqual(123); }); @@ -312,29 +312,29 @@ describe('parser', function() { return this.a; }; - scope.$set("obj", new C()); + scope.obj = new C(); expect(scope.$eval("obj.sum(obj.getA())")).toEqual(246); }); it('should evaluate objects on scope context', function() { - scope.$set('a', "abc"); + scope.a = "abc"; expect(scope.$eval("{a:a}").a).toEqual("abc"); }); it('should evaluate field access on function call result', function() { - scope.$set('a', function() { + scope.a = function() { return {name:'misko'}; - }); + }; expect(scope.$eval("a().name")).toEqual("misko"); }); it('should evaluate field access after array access', function () { - scope.$set('items', [{}, {name:'misko'}]); + scope.items = [{}, {name:'misko'}]; expect(scope.$eval('items[1].name')).toEqual("misko"); }); it('should evaluate array assignment', function() { - scope.$set('items', []); + scope.items = []; expect(scope.$eval('items[1] = "abc"')).toEqual("abc"); expect(scope.$eval('items[1]')).toEqual("abc"); @@ -388,7 +388,7 @@ describe('parser', function() { it('should evaluate undefined', function() { expect(scope.$eval("undefined")).not.toBeDefined(); expect(scope.$eval("a=undefined")).not.toBeDefined(); - expect(scope.$get("a")).not.toBeDefined(); + expect(scope.a).not.toBeDefined(); }); it('should allow assignment after array dereference', function(){ diff --git a/test/ResourceSpec.js b/test/ResourceSpec.js index 81519f0f..b4a7d37e 100644 --- a/test/ResourceSpec.js +++ b/test/ResourceSpec.js @@ -4,7 +4,7 @@ describe("resource", function() { var xhr, resource, CreditCard, callback, $xhrErr; beforeEach(function() { - var scope = angular.scope({}, null, {'$xhr.error': $xhrErr = jasmine.createSpy('xhr.error')}); + var scope = angular.scope(angularService, {'$xhr.error': $xhrErr = jasmine.createSpy('xhr.error')}); xhr = scope.$service('$browser').xhr; resource = new ResourceFactory(scope.$service('$xhr')); CreditCard = resource.route('/CreditCard/:id:verb', {id:'@id.key'}, { diff --git a/test/ScenarioSpec.js b/test/ScenarioSpec.js index 17a10ae9..ce8b0dec 100644 --- a/test/ScenarioSpec.js +++ b/test/ScenarioSpec.js @@ -15,41 +15,24 @@ describe("ScenarioSpec: Compilation", function(){ it("should compile dom node and return scope", function(){ var node = jqLite('<div ng:init="a=1">{{b=a+1}}</div>')[0]; scope = angular.compile(node)(); + scope.$flush(); expect(scope.a).toEqual(1); expect(scope.b).toEqual(2); }); it("should compile jQuery node and return scope", function(){ scope = compile(jqLite('<div>{{a=123}}</div>'))(); + scope.$flush(); expect(jqLite(scope.$element).text()).toEqual('123'); }); it("should compile text node and return scope", function(){ scope = angular.compile('<div>{{a=123}}</div>')(); + scope.$flush(); expect(jqLite(scope.$element).text()).toEqual('123'); }); }); - describe('scope', function(){ - it("should have $set, $get, $eval, $updateView methods", function(){ - scope = angular.compile('<div>{{a}}</div>')(); - scope.$eval("$invalidWidgets.push({})"); - expect(scope.$set("a", 2)).toEqual(2); - expect(scope.$get("a")).toEqual(2); - expect(scope.$eval("a=3")).toEqual(3); - scope.$eval(); - expect(jqLite(scope.$element).text()).toEqual('3'); - }); - - it("should have $ objects", function(){ - scope = angular.compile('<div></div>')(angular.scope({$config: {a:"b"}})); - expect(scope.$service('$location')).toBeDefined(); - expect(scope.$get('$eval')).toBeDefined(); - expect(scope.$get('$config')).toBeDefined(); - expect(scope.$get('$config.a')).toEqual("b"); - }); - }); - describe("configuration", function(){ it("should take location object", function(){ var url = "http://server/#?book=moby"; diff --git a/test/ScopeSpec.js b/test/ScopeSpec.js index 9cbd5f48..a2ad57a3 100644 --- a/test/ScopeSpec.js +++ b/test/ScopeSpec.js @@ -1,246 +1,497 @@ 'use strict'; -describe('scope/model', function(){ - - var temp; - - beforeEach(function() { - temp = window.temp = {}; - temp.InjectController = function(exampleService, extra) { - this.localService = exampleService; - this.extra = extra; - this.$root.injectController = this; - }; - temp.InjectController.$inject = ["exampleService"]; +describe('Scope', function(){ + var root, mockHandler; + + beforeEach(function(){ + root = createScope(angular.service, { + $updateView: function(){ + root.$flush(); + }, + '$exceptionHandler': $exceptionHandlerMockFactory() + }); + mockHandler = root.$service('$exceptionHandler'); }); - afterEach(function() { - window.temp = undefined; + + describe('$root', function(){ + it('should point to itself', function(){ + expect(root.$root).toEqual(root); + expect(root.hasOwnProperty('$root')).toBeTruthy(); + }); + + + it('should not have $root on children, but should inherit', function(){ + var child = root.$new(); + expect(child.$root).toEqual(root); + expect(child.hasOwnProperty('$root')).toBeFalsy(); + }); + }); - it('should create a scope with parent', function(){ - var model = createScope({name:'Misko'}); - expect(model.name).toEqual('Misko'); + + describe('$parent', function(){ + it('should point to itself in root', function(){ + expect(root.$root).toEqual(root); + }); + + + it('should point to parent', function(){ + var child = root.$new(); + expect(root.$parent).toEqual(null); + expect(child.$parent).toEqual(root); + expect(child.$new().$parent).toEqual(child); + }); }); - it('should have $get/$set/$parent', function(){ - var parent = {}; - var model = createScope(parent); - model.$set('name', 'adam'); - expect(model.name).toEqual('adam'); - expect(model.$get('name')).toEqual('adam'); - expect(model.$parent).toEqual(model); - expect(model.$root).toEqual(model); + + describe('$id', function(){ + it('should have a unique id', function(){ + expect(root.$id < root.$new().$id).toBeTruthy(); + }); }); - it('should return noop function when LHS is undefined', function(){ - var model = createScope(); - expect(model.$eval('x.$filter()')).toEqual(undefined); + + describe('this', function(){ + it('should have a \'this\'', function(){ + expect(root['this']).toEqual(root); + }); }); - describe('$eval', function(){ - var model; - beforeEach(function(){model = createScope();}); + describe('$new()', function(){ + it('should create a child scope', function(){ + var child = root.$new(); + root.a = 123; + expect(child.a).toEqual(123); + }); - it('should eval function with correct this', function(){ - model.$eval(function(){ - this.name = 'works'; - }); - expect(model.name).toEqual('works'); + + it('should instantiate controller and bind functions', function(){ + function Cntl($browser, name){ + this.$browser = $browser; + this.callCount = 0; + this.name = name; + } + Cntl.$inject = ['$browser']; + + Cntl.prototype = { + myFn: function(){ + expect(this).toEqual(cntl); + this.callCount++; + } + }; + + var cntl = root.$new(Cntl, ['misko']); + + expect(root.$browser).toBeUndefined(); + expect(root.myFn).toBeUndefined(); + + expect(cntl.$browser).toBeDefined(); + expect(cntl.name).toEqual('misko'); + + cntl.myFn(); + cntl.$new().myFn(); + expect(cntl.callCount).toEqual(2); }); + }); - it('should eval expression with correct this', function(){ - model.$eval('name="works"'); - expect(model.name).toEqual('works'); + + describe('$service', function(){ + it('should have it on root', function(){ + expect(root.hasOwnProperty('$service')).toBeTruthy(); }); + }); - it('should not bind regexps', function(){ - model.exp = /abc/; - expect(model.$eval('exp')).toEqual(model.exp); + + describe('$watch/$digest', function(){ + it('should watch and fire on simple property change', function(){ + var spy = jasmine.createSpy(); + root.$watch('name', spy); + expect(spy).not.wasCalled(); + root.$digest(); + expect(spy).not.wasCalled(); + root.name = 'misko'; + root.$digest(); + expect(spy).wasCalledWith(root, 'misko', undefined); }); - it('should do nothing on empty string and not update view', function(){ - var onEval = jasmine.createSpy('onEval'); - model.$onEval(onEval); - model.$eval(''); - expect(onEval).not.toHaveBeenCalled(); + + it('should watch and fire on expression change', function(){ + var spy = jasmine.createSpy(); + root.$watch('name.first', spy); + root.name = {}; + expect(spy).not.wasCalled(); + root.$digest(); + expect(spy).not.wasCalled(); + root.name.first = 'misko'; + root.$digest(); + expect(spy).wasCalled(); }); - it('should ignore none string/function', function(){ - model.$eval(null); - model.$eval({}); - model.$tryEval(null); - model.$tryEval({}); + it('should delegate exceptions', function(){ + root.$watch('a', function(){throw new Error('abc');}); + root.a = 1; + root.$digest(); + expect(mockHandler.errors[0].message).toEqual('abc'); + $logMock.error.logs.length = 0; }); - }); - describe('$watch', function(){ - it('should watch an expression for change', function(){ - var model = createScope(); - model.oldValue = ""; - var nameCount = 0, evalCount = 0; - model.name = 'adam'; - model.$watch('name', function(){ nameCount ++; }); - model.$watch(function(){return model.name;}, function(newValue, oldValue){ - this.newValue = newValue; - this.oldValue = oldValue; - }); - model.$onEval(function(){evalCount ++;}); - model.name = 'misko'; - model.$eval(); - expect(nameCount).toEqual(2); - expect(evalCount).toEqual(1); - expect(model.newValue).toEqual('misko'); - expect(model.oldValue).toEqual('adam'); - }); - - it('should eval with no arguments', function(){ - var model = createScope(); - var count = 0; - model.$onEval(function(){count++;}); - model.$eval(); - expect(count).toEqual(1); - }); - - it('should run listener upon registration by default', function() { - var model = createScope(); - var count = 0, - nameNewVal = 'crazy val 1', - nameOldVal = 'crazy val 2'; - - model.$watch('name', function(newVal, oldVal){ - count ++; - nameNewVal = newVal; - nameOldVal = oldVal; - }); + it('should fire watches in order of addition', function(){ + // this is not an external guarantee, just our own sanity + var log = ''; + root.$watch('a', function(){ log += 'a'; }); + root.$watch('b', function(){ log += 'b'; }); + root.$watch('c', function(){ log += 'c'; }); + root.a = root.b = root.c = 1; + root.$digest(); + expect(log).toEqual('abc'); + }); + + + it('should delegate $digest to children in addition order', function(){ + // this is not an external guarantee, just our own sanity + var log = ''; + var childA = root.$new(); + var childB = root.$new(); + var childC = root.$new(); + childA.$watch('a', function(){ log += 'a'; }); + childB.$watch('b', function(){ log += 'b'; }); + childC.$watch('c', function(){ log += 'c'; }); + childA.a = childB.b = childC.c = 1; + root.$digest(); + expect(log).toEqual('abc'); + }); + - expect(count).toBe(1); - expect(nameNewVal).not.toBeDefined(); - expect(nameOldVal).not.toBeDefined(); + it('should repeat watch cycle while model changes are identified', function(){ + var log = ''; + root.$watch('c', function(self, v){self.d = v; log+='c'; }); + root.$watch('b', function(self, v){self.c = v; log+='b'; }); + root.$watch('a', function(self, v){self.b = v; log+='a'; }); + root.a = 1; + expect(root.$digest()).toEqual(3); + expect(root.b).toEqual(1); + expect(root.c).toEqual(1); + expect(root.d).toEqual(1); + expect(log).toEqual('abc'); }); - it('should not run listener upon registration if flag is passed in', function() { - var model = createScope(); - var count = 0, - nameNewVal = 'crazy val 1', - nameOldVal = 'crazy val 2'; - model.$watch('name', function(newVal, oldVal){ - count ++; - nameNewVal = newVal; - nameOldVal = oldVal; - }, undefined, false); + it('should prevent infinite recursion', function(){ + root.$watch('a', function(self, v){self.b++;}); + root.$watch('b', function(self, v){self.a++;}); + root.a = root.b = 0; - expect(count).toBe(0); - expect(nameNewVal).toBe('crazy val 1'); - expect(nameOldVal).toBe('crazy val 2'); + expect(function(){ + root.$digest(); + }).toThrow('100 $digest() iterations reached. Aborting!'); }); - }); - describe('$bind', function(){ - it('should curry a function with respect to scope', function(){ - var model = createScope(); - model.name = 'misko'; - expect(model.$bind(function(){return this.name;})()).toEqual('misko'); + + it('should not fire upon $watch registration on initial $digest', function(){ + var log = ''; + root.a = 1; + root.$watch('a', function(){ log += 'a'; }); + root.$watch('b', function(){ log += 'b'; }); + expect(log).toEqual(''); + expect(root.$digest()).toEqual(0); + expect(log).toEqual(''); }); - }); - describe('$tryEval', function(){ - it('should report error using the provided error handler and $log.error', function(){ - var scope = createScope(), - errorLogs = scope.$service('$log').error.logs; - scope.$tryEval(function(){throw "myError";}, function(error){ - scope.error = error; - }); - expect(scope.error).toEqual('myError'); - expect(errorLogs.shift()[0]).toBe("myError"); + it('should return the listener to force a initial watch', function(){ + var log = ''; + root.a = 1; + root.$watch('a', function(scope, o1, o2){ log += scope.a + ':' + (o1 == o2 == 1) ; })(); + expect(log).toEqual('1:true'); + expect(root.$digest()).toEqual(0); + expect(log).toEqual('1:true'); }); - it('should report error on visible element', function(){ - var element = jqLite('<div></div>'), - scope = createScope(), - errorLogs = scope.$service('$log').error.logs; - scope.$tryEval(function(){throw "myError";}, element); - expect(element.attr('ng-exception')).toEqual('myError'); - expect(element.hasClass('ng-exception')).toBeTruthy(); - expect(errorLogs.shift()[0]).toBe("myError"); + it('should watch objects', function(){ + var log = ''; + root.a = []; + root.b = {}; + root.$watch('a', function(){ log +='.';}); + root.$watch('b', function(){ log +='!';}); + root.$digest(); + expect(log).toEqual(''); + + root.a.push({}); + root.b.name = ''; + + root.$digest(); + expect(log).toEqual('.!'); }); - it('should report error on $excetionHandler', function(){ - var scope = createScope(null, {$exceptionHandler: $exceptionHandlerMockFactory}, - {$log: $logMock}); - scope.$tryEval(function(){throw "myError";}); - expect(scope.$service('$exceptionHandler').errors.shift()).toEqual("myError"); - expect(scope.$service('$log').error.logs.shift()).toEqual(["myError"]); + + it('should prevent recursion', function(){ + var callCount = 0; + root.$watch('name', function(){ + expect(function(){ + root.$digest(); + }).toThrow('$digest already in progress'); + expect(function(){ + root.$flush(); + }).toThrow('$digest already in progress'); + callCount++; + }); + root.name = 'a'; + root.$digest(); + expect(callCount).toEqual(1); }); }); - // $onEval - describe('$onEval', function(){ - it("should eval using priority", function(){ - var scope = createScope(); - scope.log = ""; - scope.$onEval('log = log + "middle;"'); - scope.$onEval(-1, 'log = log + "first;"'); - scope.$onEval(1, 'log = log + "last;"'); - scope.$eval(); - expect(scope.log).toEqual('first;middle;last;'); + + describe('$observe/$flush', function(){ + it('should register simple property observer and fire on change', function(){ + var spy = jasmine.createSpy(); + root.$observe('name', spy); + expect(spy).not.wasCalled(); + root.$flush(); + expect(spy).wasCalled(); + expect(spy.mostRecentCall.args[0]).toEqual(root); + expect(spy.mostRecentCall.args[1]).toEqual(undefined); + expect(spy.mostRecentCall.args[2].toString()).toEqual(NaN.toString()); + root.name = 'misko'; + root.$flush(); + expect(spy).wasCalledWith(root, 'misko', undefined); }); - it("should have $root and $parent", function(){ - var parent = createScope(); - var scope = createScope(parent); - expect(scope.$root).toEqual(parent); - expect(scope.$parent).toEqual(parent); + + it('should register expression observers and fire them on change', function(){ + var spy = jasmine.createSpy(); + root.$observe('name.first', spy); + root.name = {}; + expect(spy).not.wasCalled(); + root.$flush(); + expect(spy).wasCalled(); + root.name.first = 'misko'; + root.$flush(); + expect(spy).wasCalled(); }); - }); - describe('getterFn', function(){ - it('should get chain', function(){ - expect(getterFn('a.b')(undefined)).toEqual(undefined); - expect(getterFn('a.b')({})).toEqual(undefined); - expect(getterFn('a.b')({a:null})).toEqual(undefined); - expect(getterFn('a.b')({a:{}})).toEqual(undefined); - expect(getterFn('a.b')({a:{b:null}})).toEqual(null); - expect(getterFn('a.b')({a:{b:0}})).toEqual(0); - expect(getterFn('a.b')({a:{b:'abc'}})).toEqual('abc'); + + it('should delegate exceptions', function(){ + root.$observe('a', function(){throw new Error('abc');}); + root.a = 1; + root.$flush(); + expect(mockHandler.errors[0].message).toEqual('abc'); + $logMock.error.logs.shift(); + }); + + + it('should fire observers in order of addition', function(){ + // this is not an external guarantee, just our own sanity + var log = ''; + root.$observe('a', function(){ log += 'a'; }); + root.$observe('b', function(){ log += 'b'; }); + root.$observe('c', function(){ log += 'c'; }); + root.a = root.b = root.c = 1; + root.$flush(); + expect(log).toEqual('abc'); }); - it('should map type method on top of expression', function(){ - expect(getterFn('a.$filter')({a:[]})('')).toEqual([]); + + it('should delegate $flush to children in addition order', function(){ + // this is not an external guarantee, just our own sanity + var log = ''; + var childA = root.$new(); + var childB = root.$new(); + var childC = root.$new(); + childA.$observe('a', function(){ log += 'a'; }); + childB.$observe('b', function(){ log += 'b'; }); + childC.$observe('c', function(){ log += 'c'; }); + childA.a = childB.b = childC.c = 1; + root.$flush(); + expect(log).toEqual('abc'); + }); + + + it('should fire observers once at beggining and on change', function(){ + var log = ''; + root.$observe('c', function(self, v){self.d = v; log += 'c';}); + root.$observe('b', function(self, v){self.c = v; log += 'b';}); + root.$observe('a', function(self, v){self.b = v; log += 'a';}); + root.a = 1; + root.$flush(); + expect(root.b).toEqual(1); + expect(log).toEqual('cba'); + root.$flush(); + expect(root.c).toEqual(1); + expect(log).toEqual('cbab'); + root.$flush(); + expect(root.d).toEqual(1); + expect(log).toEqual('cbabc'); + }); + + + it('should fire on initial observe', function(){ + var log = ''; + root.a = 1; + root.$observe('a', function(){ log += 'a'; }); + root.$observe('b', function(){ log += 'b'; }); + expect(log).toEqual(''); + root.$flush(); + expect(log).toEqual('ab'); + }); + + + it('should observe objects', function(){ + var log = ''; + root.a = []; + root.b = {}; + root.$observe('a', function(){ log +='.';}); + root.$observe('a', function(){ log +='!';}); + root.$flush(); + expect(log).toEqual('.!'); + + root.$flush(); + expect(log).toEqual('.!'); + + root.a.push({}); + root.b.name = ''; + + root.$digest(); + expect(log).toEqual('.!'); }); - it('should bind function this', function(){ - expect(getterFn('a')({a:function($){return this.b + $;}, b:1})(2)).toEqual(3); + it('should prevent recursion', function(){ + var callCount = 0; + root.$observe('name', function(){ + expect(function(){ + root.$digest(); + }).toThrow('$flush already in progress'); + expect(function(){ + root.$flush(); + }).toThrow('$flush already in progress'); + callCount++; + }); + root.name = 'a'; + root.$flush(); + expect(callCount).toEqual(1); }); }); - describe('$new', function(){ - it('should create new child scope and $become controller', function(){ - var parent = createScope(null, angularService, {exampleService: 'Example Service'}); - var child = parent.$new(temp.InjectController, 10); - expect(child.localService).toEqual('Example Service'); - expect(child.extra).toEqual(10); - child.$onEval(function(){ this.run = true; }); - parent.$eval(); - expect(child.run).toEqual(true); + describe('$destroy', function(){ + var first, middle, last, log; + + beforeEach(function(){ + log = ''; + + first = root.$new(); + middle = root.$new(); + last = root.$new(); + + first.$watch(function(){ log += '1';}); + middle.$watch(function(){ log += '2';}); + last.$watch(function(){ log += '3';}); + + log = ''; + }); + + + it('should ignore remove on root', function(){ + root.$destroy(); + root.$digest(); + expect(log).toEqual('123'); + }); + + + it('should remove first', function(){ + first.$destroy(); + root.$digest(); + expect(log).toEqual('23'); + }); + + + it('should remove middle', function(){ + middle.$destroy(); + root.$digest(); + expect(log).toEqual('13'); + }); + + + it('should remove last', function(){ + last.$destroy(); + root.$digest(); + expect(log).toEqual('12'); }); }); - describe('$become', function(){ - it('should inject properties on controller defined in $inject', function(){ - var parent = createScope(null, angularService, {exampleService: 'Example Service'}); - var child = createScope(parent); - child.$become(temp.InjectController, 10); - expect(child.localService).toEqual('Example Service'); - expect(child.extra).toEqual(10); + + describe('$eval', function(){ + it('should eval an expression', function(){ + expect(root.$eval('a=1')).toEqual(1); + expect(root.a).toEqual(1); + + root.$eval(function(self){self.b=2;}); + expect(root.b).toEqual(2); }); }); + + describe('$apply', function(){ + it('should apply expression with full lifecycle', function(){ + var log = ''; + var child = root.$new(); + root.$watch('a', function(scope, a){ log += '1'; }); + root.$observe('a', function(scope, a){ log += '2'; }); + child.$apply('$parent.a=0'); + expect(log).toEqual('12'); + }); + + + it('should catch exceptions', function(){ + var log = ''; + var child = root.$new(); + root.$watch('a', function(scope, a){ log += '1'; }); + root.$observe('a', function(scope, a){ log += '2'; }); + root.a = 0; + child.$apply(function(){ throw new Error('MyError'); }); + expect(log).toEqual('12'); + expect(mockHandler.errors[0].message).toEqual('MyError'); + $logMock.error.logs.shift(); + }); + + + describe('exceptions', function(){ + var $exceptionHandler, $updateView, log; + beforeEach(function(){ + log = ''; + $exceptionHandler = jasmine.createSpy('$exceptionHandler'); + $updateView = jasmine.createSpy('$updateView'); + root.$service = function(name) { + return {$updateView:$updateView, $exceptionHandler:$exceptionHandler}[name]; + }; + root.$watch(function(){ log += '$digest;'; }); + log = ''; + }); + + + it('should execute and return value and update', function(){ + root.name = 'abc'; + expect(root.$apply(function(scope){ + return scope.name; + })).toEqual('abc'); + expect(log).toEqual('$digest;'); + expect($exceptionHandler).not.wasCalled(); + expect($updateView).wasCalled(); + }); + + + it('should catch exception and update', function(){ + var error = new Error('MyError'); + root.$apply(function(){ throw error; }); + expect(log).toEqual('$digest;'); + expect($exceptionHandler).wasCalledWith(error); + expect($updateView).wasCalled(); + }); + }); + }); }); diff --git a/test/ValidatorsSpec.js b/test/ValidatorsSpec.js index 2c2488fc..f44a9a59 100644 --- a/test/ValidatorsSpec.js +++ b/test/ValidatorsSpec.js @@ -1,6 +1,6 @@ 'use strict'; -describe('ValidatorTest', function(){ +describe('Validator', function(){ it('ShouldHaveThisSet', function() { var validator = {}; @@ -11,7 +11,7 @@ describe('ValidatorTest', function(){ }; var scope = compile('<input name="name" ng:validate="myValidator:\'hevery\'"/>')(); scope.name = 'misko'; - scope.$eval(); + scope.$digest(); assertEquals('misko', validator.first); assertEquals('hevery', validator.last); expect(validator._this.$id).toEqual(scope.$id); @@ -118,7 +118,7 @@ describe('ValidatorTest', function(){ value=v; fn=f; }; scope.name = "misko"; - scope.$eval(); + scope.$digest(); expect(value).toEqual('misko'); expect(input.hasClass('ng-input-indicator-wait')).toBeTruthy(); fn("myError"); @@ -158,7 +158,7 @@ describe('ValidatorTest', function(){ scope.asyncFn = jasmine.createSpy(); scope.updateFn = jasmine.createSpy(); scope.name = 'misko'; - scope.$eval(); + scope.$digest(); expect(scope.asyncFn).toHaveBeenCalledWith('misko', scope.asyncFn.mostRecentCall.args[1]); assertTrue(scope.$element.hasClass('ng-input-indicator-wait')); scope.asyncFn.mostRecentCall.args[1]('myError', {id: 1234, data:'data'}); diff --git a/test/directivesSpec.js b/test/directivesSpec.js index 22d3c84b..a05861ae 100644 --- a/test/directivesSpec.js +++ b/test/directivesSpec.js @@ -22,8 +22,9 @@ describe("directive", function(){ it("should ng:eval", function() { var scope = compile('<div ng:init="a=0" ng:eval="a = a + 1"></div>'); + scope.$flush(); expect(scope.a).toEqual(1); - scope.$eval(); + scope.$flush(); expect(scope.a).toEqual(2); }); @@ -32,7 +33,7 @@ describe("directive", function(){ var scope = compile('<div ng:bind="a"></div>'); expect(element.text()).toEqual(''); scope.a = 'misko'; - scope.$eval(); + scope.$flush(); expect(element.hasClass('ng-binding')).toEqual(true); expect(element.text()).toEqual('misko'); }); @@ -40,24 +41,24 @@ describe("directive", function(){ it('should set text to blank if undefined', function() { var scope = compile('<div ng:bind="a"></div>'); scope.a = 'misko'; - scope.$eval(); + scope.$flush(); expect(element.text()).toEqual('misko'); scope.a = undefined; - scope.$eval(); + scope.$flush(); expect(element.text()).toEqual(''); }); it('should set html', function() { var scope = compile('<div ng:bind="html|html"></div>'); scope.html = '<div unknown>hello</div>'; - scope.$eval(); + scope.$flush(); expect(lowercase(element.html())).toEqual('<div>hello</div>'); }); it('should set unsafe html', function() { var scope = compile('<div ng:bind="html|html:\'unsafe\'"></div>'); scope.html = '<div onclick="">hello</div>'; - scope.$eval(); + scope.$flush(); expect(lowercase(element.html())).toEqual('<div onclick="">hello</div>'); }); @@ -66,7 +67,7 @@ describe("directive", function(){ return jqLite('<a>hello</a>'); }; var scope = compile('<div ng:bind="0|myElement"></div>'); - scope.$eval(); + scope.$flush(); expect(lowercase(element.html())).toEqual('<a>hello</a>'); }); @@ -76,12 +77,14 @@ describe("directive", function(){ return 'HELLO'; }; var scope = compile('<div>before<div ng:bind="0|myFilter"></div>after</div>'); + scope.$flush(); expect(sortedHtml(scope.$element)).toEqual('<div>before<div class="filter" ng:bind="0|myFilter">HELLO</div>after</div>'); }); it('should suppress rendering of falsy values', function(){ var scope = compile('<div>{{ null }}{{ undefined }}{{ "" }}-{{ 0 }}{{ false }}</div>'); + scope.$flush(); expect(scope.$element.text()).toEqual('-0false'); }); @@ -90,8 +93,8 @@ describe("directive", function(){ describe('ng:bind-template', function(){ it('should ng:bind-template', function() { var scope = compile('<div ng:bind-template="Hello {{name}}!"></div>'); - scope.$set('name', 'Misko'); - scope.$eval(); + scope.name = 'Misko'; + scope.$flush(); expect(element.hasClass('ng-binding')).toEqual(true); expect(element.text()).toEqual('Hello Misko!'); }); @@ -103,6 +106,7 @@ describe("directive", function(){ return text; }; var scope = compile('<div>before<span ng:bind-template="{{\'HELLO\'|myFilter}}">INNER</span>after</div>'); + scope.$flush(); expect(scope.$element.text()).toEqual("beforeHELLOafter"); expect(innerText).toEqual('INNER'); }); @@ -112,12 +116,14 @@ describe("directive", function(){ describe('ng:bind-attr', function(){ it('should bind attributes', function(){ var scope = compile('<div ng:bind-attr="{src:\'http://localhost/mysrc\', alt:\'myalt\'}"/>'); + scope.$flush(); expect(element.attr('src')).toEqual('http://localhost/mysrc'); expect(element.attr('alt')).toEqual('myalt'); }); it('should not pretty print JSON in attributes', function(){ var scope = compile('<img alt="{{ {a:1} }}"/>'); + scope.$flush(); expect(element.attr('alt')).toEqual('{"a":1}'); }); }); @@ -132,7 +138,7 @@ describe("directive", function(){ scope.disabled = true; scope.readonly = true; scope.checked = true; - scope.$eval(); + scope.$flush(); expect(input.disabled).toEqual(true); expect(input.readOnly).toEqual(true); @@ -142,16 +148,16 @@ describe("directive", function(){ describe('ng:click', function(){ it('should get called on a click', function(){ var scope = compile('<div ng:click="clicked = true"></div>'); - scope.$eval(); - expect(scope.$get('clicked')).toBeFalsy(); + scope.$flush(); + expect(scope.clicked).toBeFalsy(); browserTrigger(element, 'click'); - expect(scope.$get('clicked')).toEqual(true); + expect(scope.clicked).toEqual(true); }); it('should stop event propagation', function() { var scope = compile('<div ng:click="outer = true"><div ng:click="inner = true"></div></div>'); - scope.$eval(); + scope.$flush(); expect(scope.outer).not.toBeDefined(); expect(scope.inner).not.toBeDefined(); @@ -169,7 +175,7 @@ describe("directive", function(){ var scope = compile('<form action="" ng:submit="submitted = true">' + '<input type="submit"/>' + '</form>'); - scope.$eval(); + scope.$flush(); expect(scope.submitted).not.toBeDefined(); browserTrigger(element.children()[0]); @@ -177,23 +183,22 @@ describe("directive", function(){ }); }); - describe('ng:class', function() { it('should add new and remove old classes dynamically', function() { var scope = compile('<div class="existing" ng:class="dynClass"></div>'); scope.dynClass = 'A'; - scope.$eval(); + scope.$flush(); expect(element.hasClass('existing')).toBe(true); expect(element.hasClass('A')).toBe(true); scope.dynClass = 'B'; - scope.$eval(); + scope.$flush(); expect(element.hasClass('existing')).toBe(true); expect(element.hasClass('A')).toBe(false); expect(element.hasClass('B')).toBe(true); delete scope.dynClass; - scope.$eval(); + scope.$flush(); expect(element.hasClass('existing')).toBe(true); expect(element.hasClass('A')).toBe(false); expect(element.hasClass('B')).toBe(false); @@ -201,7 +206,7 @@ describe("directive", function(){ it('should support adding multiple classes', function(){ var scope = compile('<div class="existing" ng:class="[\'A\', \'B\']"></div>'); - scope.$eval(); + scope.$flush(); expect(element.hasClass('existing')).toBeTruthy(); expect(element.hasClass('A')).toBeTruthy(); expect(element.hasClass('B')).toBeTruthy(); @@ -211,7 +216,7 @@ describe("directive", function(){ it('should ng:class odd/even', function(){ var scope = compile('<ul><li ng:repeat="i in [0,1]" class="existing" ng:class-odd="\'odd\'" ng:class-even="\'even\'"></li><ul>'); - scope.$eval(); + scope.$flush(); var e1 = jqLite(element[0].childNodes[1]); var e2 = jqLite(element[0].childNodes[2]); expect(e1.hasClass('existing')).toBeTruthy(); @@ -223,32 +228,32 @@ describe("directive", function(){ describe('ng:style', function(){ it('should set', function(){ var scope = compile('<div ng:style="{height: \'40px\'}"></div>'); - scope.$eval(); + scope.$flush(); expect(element.css('height')).toEqual('40px'); }); it('should silently ignore undefined style', function() { var scope = compile('<div ng:style="myStyle"></div>'); - scope.$eval(); + scope.$flush(); expect(element.hasClass('ng-exception')).toBeFalsy(); }); it('should preserve and remove previous style', function(){ var scope = compile('<div style="height: 10px;" ng:style="myStyle"></div>'); - scope.$eval(); + scope.$flush(); expect(getStyle(element)).toEqual({height: '10px'}); scope.myStyle = {height: '20px', width: '10px'}; - scope.$eval(); + scope.$flush(); expect(getStyle(element)).toEqual({height: '20px', width: '10px'}); scope.myStyle = {}; - scope.$eval(); + scope.$flush(); expect(getStyle(element)).toEqual({height: '10px'}); }); }); it('should silently ignore undefined ng:style', function() { var scope = compile('<div ng:style="myStyle"></div>'); - scope.$eval(); + scope.$flush(); expect(element.hasClass('ng-exception')).toBeFalsy(); }); @@ -258,9 +263,10 @@ describe("directive", function(){ var element = jqLite('<div ng:show="exp"></div>'), scope = compile(element); + scope.$flush(); expect(isCssVisible(element)).toEqual(false); scope.exp = true; - scope.$eval(); + scope.$flush(); expect(isCssVisible(element)).toEqual(true); }); @@ -271,7 +277,7 @@ describe("directive", function(){ expect(isCssVisible(element)).toBe(false); scope.exp = true; - scope.$eval(); + scope.$flush(); expect(isCssVisible(element)).toBe(true); }); }); @@ -283,7 +289,7 @@ describe("directive", function(){ expect(isCssVisible(element)).toBe(true); scope.exp = true; - scope.$eval(); + scope.$flush(); expect(isCssVisible(element)).toBe(false); }); }); @@ -333,11 +339,13 @@ describe("directive", function(){ expect(scope.greeter.greeting).toEqual('hello'); expect(scope.childGreeter.greeting).toEqual('hey'); expect(scope.childGreeter.$parent.greeting).toEqual('hello'); + scope.$flush(); expect(scope.$element.text()).toEqual('hey dude!'); }); }); + //TODO(misko): this needs to be deleted when ng:eval-order is gone it('should eval things according to ng:eval-order', function(){ var scope = compile( '<div ng:init="log=\'\'">' + @@ -348,6 +356,7 @@ describe("directive", function(){ '<span bind-template="{{log = log + \'d\'}}"></span>' + '</span>' + '</div>'); + scope.$flush(); expect(scope.log).toEqual('abcde'); }); diff --git a/test/markupSpec.js b/test/markupSpec.js index ce44d88c..ab8b4c74 100644 --- a/test/markupSpec.js +++ b/test/markupSpec.js @@ -20,24 +20,25 @@ describe("markups", function(){ it('should translate {{}} in text', function(){ compile('<div>hello {{name}}!</div>'); expect(sortedHtml(element)).toEqual('<div>hello <span ng:bind="name"></span>!</div>'); - scope.$set('name', 'Misko'); - scope.$eval(); + scope.name = 'Misko'; + scope.$flush(); expect(sortedHtml(element)).toEqual('<div>hello <span ng:bind="name">Misko</span>!</div>'); }); it('should translate {{}} in terminal nodes', function(){ compile('<select name="x"><option value="">Greet {{name}}!</option></select>'); + scope.$flush(); expect(sortedHtml(element).replace(' selected="true"', '')).toEqual('<select name="x"><option ng:bind-template="Greet {{name}}!">Greet !</option></select>'); - scope.$set('name', 'Misko'); - scope.$eval(); + scope.name = 'Misko'; + scope.$flush(); expect(sortedHtml(element).replace(' selected="true"', '')).toEqual('<select name="x"><option ng:bind-template="Greet {{name}}!">Greet Misko!</option></select>'); }); it('should translate {{}} in attributes', function(){ compile('<div src="http://server/{{path}}.png"/>'); expect(element.attr('ng:bind-attr')).toEqual('{"src":"http://server/{{path}}.png"}'); - scope.$set('path', 'a/b'); - scope.$eval(); + scope.path = 'a/b'; + scope.$flush(); expect(element.attr('src')).toEqual("http://server/a/b.png"); }); @@ -94,57 +95,57 @@ describe("markups", function(){ it('should bind disabled', function() { compile('<button ng:disabled="{{isDisabled}}">Button</button>'); scope.isDisabled = false; - scope.$eval(); + scope.$flush(); expect(element.attr('disabled')).toBeFalsy(); scope.isDisabled = true; - scope.$eval(); + scope.$flush(); expect(element.attr('disabled')).toBeTruthy(); }); it('should bind checked', function() { compile('<input type="checkbox" ng:checked="{{isChecked}}" />'); scope.isChecked = false; - scope.$eval(); + scope.$flush(); expect(element.attr('checked')).toBeFalsy(); scope.isChecked=true; - scope.$eval(); + scope.$flush(); expect(element.attr('checked')).toBeTruthy(); }); it('should bind selected', function() { compile('<select><option value=""></option><option ng:selected="{{isSelected}}">Greetings!</option></select>'); scope.isSelected=false; - scope.$eval(); + scope.$flush(); expect(element.children()[1].selected).toBeFalsy(); scope.isSelected=true; - scope.$eval(); + scope.$flush(); expect(element.children()[1].selected).toBeTruthy(); }); it('should bind readonly', function() { compile('<input type="text" ng:readonly="{{isReadonly}}" />'); scope.isReadonly=false; - scope.$eval(); + scope.$flush(); expect(element.attr('readOnly')).toBeFalsy(); scope.isReadonly=true; - scope.$eval(); + scope.$flush(); expect(element.attr('readOnly')).toBeTruthy(); }); it('should bind multiple', function() { compile('<select ng:multiple="{{isMultiple}}"></select>'); scope.isMultiple=false; - scope.$eval(); + scope.$flush(); expect(element.attr('multiple')).toBeFalsy(); scope.isMultiple='multiple'; - scope.$eval(); + scope.$flush(); expect(element.attr('multiple')).toBeTruthy(); }); it('should bind src', function() { compile('<div ng:src="{{url}}" />'); scope.url = 'http://localhost/'; - scope.$eval(); + scope.$flush(); expect(element.attr('src')).toEqual('http://localhost/'); }); diff --git a/test/mocks.js b/test/mocks.js index 79a24bf1..37e1d31b 100644 --- a/test/mocks.js +++ b/test/mocks.js @@ -55,7 +55,7 @@ angular.service('$log', function() { * this: * * <pre> - * var scope = angular.scope(null, {'$exceptionHandler': $exceptionHandlerMockFactory}); + * var scope = angular.scope(null, {'$exceptionHandler': $exceptionHandlerMockFactory()}); * </pre> * */ diff --git a/test/scenario/SpecRunnerSpec.js b/test/scenario/SpecRunnerSpec.js index 0e1ffac1..92f000ba 100644 --- a/test/scenario/SpecRunnerSpec.js +++ b/test/scenario/SpecRunnerSpec.js @@ -31,14 +31,13 @@ describe('angular.scenario.SpecRunner', function() { $window.setTimeout = function(fn, timeout) { fn(); }; - $root = angular.scope({ - emit: function(eventName) { - log.push(eventName); - }, - on: function(eventName) { - log.push('Listener Added for ' + eventName); - } - }); + $root = angular.scope(); + $root.emit = function(eventName) { + log.push(eventName); + }; + $root.on = function(eventName) { + log.push('Listener Added for ' + eventName); + }; $root.application = new ApplicationMock($window); $root.$window = $window; runner = $root.$new(angular.scenario.SpecRunner); diff --git a/test/scenario/dslSpec.js b/test/scenario/dslSpec.js index a07d411e..5485fe52 100644 --- a/test/scenario/dslSpec.js +++ b/test/scenario/dslSpec.js @@ -10,14 +10,13 @@ describe("angular.scenario.dsl", function() { document: _jQuery("<div></div>"), angular: new angular.scenario.testing.MockAngular() }; - $root = angular.scope({ - emit: function(eventName) { - eventLog.push(eventName); - }, - on: function(eventName) { - eventLog.push('Listener Added for ' + eventName); - } - }); + $root = angular.scope(); + $root.emit = function(eventName) { + eventLog.push(eventName); + }; + $root.on = function(eventName) { + eventLog.push('Listener Added for ' + eventName); + }; $root.futures = []; $root.futureLog = []; $root.$window = $window; diff --git a/test/service/cookieStoreSpec.js b/test/service/cookieStoreSpec.js index b37e9cb0..75be924c 100644 --- a/test/service/cookieStoreSpec.js +++ b/test/service/cookieStoreSpec.js @@ -16,7 +16,7 @@ describe('$cookieStore', function() { it('should serialize objects to json', function() { $cookieStore.put('objectCookie', {id: 123, name: 'blah'}); - scope.$eval(); //force eval in test + scope.$flush(); expect($browser.cookies()).toEqual({'objectCookie': '{"id":123,"name":"blah"}'}); }); @@ -30,12 +30,12 @@ describe('$cookieStore', function() { it('should delete objects from the store when remove is called', function() { $cookieStore.put('gonner', { "I'll":"Be Back"}); - scope.$eval(); //force eval in test + scope.$flush(); //force eval in test $browser.poll(); expect($browser.cookies()).toEqual({'gonner': '{"I\'ll":"Be Back"}'}); $cookieStore.remove('gonner'); - scope.$eval(); + scope.$flush(); expect($browser.cookies()).toEqual({}); }); }); diff --git a/test/service/cookiesSpec.js b/test/service/cookiesSpec.js index 3248fe23..cc667b56 100644 --- a/test/service/cookiesSpec.js +++ b/test/service/cookiesSpec.js @@ -6,7 +6,7 @@ describe('$cookies', function() { beforeEach(function() { $browser = new MockBrowser(); $browser.cookieHash['preexisting'] = 'oldCookie'; - scope = angular.scope(null, angular.service, {$browser: $browser}); + scope = angular.scope(angular.service, {$browser: $browser}); scope.$cookies = scope.$service('$cookies'); }); @@ -38,13 +38,13 @@ describe('$cookies', function() { it('should create or update a cookie when a value is assigned to a property', function() { scope.$cookies.oatmealCookie = 'nom nom'; - scope.$eval(); + scope.$flush(); expect($browser.cookies()). toEqual({'preexisting': 'oldCookie', 'oatmealCookie':'nom nom'}); scope.$cookies.oatmealCookie = 'gone'; - scope.$eval(); + scope.$flush(); expect($browser.cookies()). toEqual({'preexisting': 'oldCookie', 'oatmealCookie': 'gone'}); @@ -56,7 +56,7 @@ describe('$cookies', function() { scope.$cookies.nullVal = null; scope.$cookies.undefVal = undefined; scope.$cookies.preexisting = function(){}; - scope.$eval(); + scope.$flush(); expect($browser.cookies()).toEqual({'preexisting': 'oldCookie'}); expect(scope.$cookies).toEqual({'preexisting': 'oldCookie'}); }); @@ -64,13 +64,13 @@ describe('$cookies', function() { it('should remove a cookie when a $cookies property is deleted', function() { scope.$cookies.oatmealCookie = 'nom nom'; - scope.$eval(); + scope.$flush(); $browser.poll(); expect($browser.cookies()). toEqual({'preexisting': 'oldCookie', 'oatmealCookie':'nom nom'}); delete scope.$cookies.oatmealCookie; - scope.$eval(); + scope.$flush(); expect($browser.cookies()).toEqual({'preexisting': 'oldCookie'}); }); @@ -85,16 +85,16 @@ describe('$cookies', function() { //drop if no previous value scope.$cookies.longCookie = longVal; - scope.$eval(); + scope.$flush(); expect(scope.$cookies).toEqual({'preexisting': 'oldCookie'}); //reset if previous value existed scope.$cookies.longCookie = 'shortVal'; - scope.$eval(); + scope.$flush(); expect(scope.$cookies).toEqual({'preexisting': 'oldCookie', 'longCookie': 'shortVal'}); scope.$cookies.longCookie = longVal; - scope.$eval(); + scope.$flush(); expect(scope.$cookies).toEqual({'preexisting': 'oldCookie', 'longCookie': 'shortVal'}); }); }); diff --git a/test/service/deferSpec.js b/test/service/deferSpec.js index 7e59978c..4f651522 100644 --- a/test/service/deferSpec.js +++ b/test/service/deferSpec.js @@ -4,7 +4,7 @@ describe('$defer', function() { var scope, $browser, $defer, $exceptionHandler; beforeEach(function(){ - scope = angular.scope({}, angular.service, + scope = angular.scope(angular.service, {'$exceptionHandler': jasmine.createSpy('$exceptionHandler')}); $browser = scope.$service('$browser'); $defer = scope.$service('$defer'); @@ -41,32 +41,32 @@ describe('$defer', function() { }); - it('should call eval after each callback is executed', function() { - var evalSpy = this.spyOn(scope, '$eval').andCallThrough(); + it('should call $apply after each callback is executed', function() { + var applySpy = this.spyOn(scope, '$apply').andCallThrough(); $defer(function() {}); - expect(evalSpy).not.toHaveBeenCalled(); + expect(applySpy).not.toHaveBeenCalled(); $browser.defer.flush(); - expect(evalSpy).toHaveBeenCalled(); + expect(applySpy).toHaveBeenCalled(); - evalSpy.reset(); //reset the spy; + applySpy.reset(); //reset the spy; $defer(function() {}); $defer(function() {}); $browser.defer.flush(); - expect(evalSpy.callCount).toBe(2); + expect(applySpy.callCount).toBe(2); }); - it('should call eval even if an exception is thrown in callback', function() { - var evalSpy = this.spyOn(scope, '$eval').andCallThrough(); + it('should call $apply even if an exception is thrown in callback', function() { + var applySpy = this.spyOn(scope, '$apply').andCallThrough(); $defer(function() {throw "Test Error";}); - expect(evalSpy).not.toHaveBeenCalled(); + expect(applySpy).not.toHaveBeenCalled(); $browser.defer.flush(); - expect(evalSpy).toHaveBeenCalled(); + expect(applySpy).toHaveBeenCalled(); }); it('should allow you to specify the delay time', function(){ diff --git a/test/service/exceptionHandlerSpec.js b/test/service/exceptionHandlerSpec.js index c6f13161..74f37cb9 100644 --- a/test/service/exceptionHandlerSpec.js +++ b/test/service/exceptionHandlerSpec.js @@ -14,11 +14,12 @@ describe('$exceptionHandler', function() { it('should log errors', function(){ - var scope = createScope({}, {$exceptionHandler: $exceptionHandlerFactory}, - {$log: $logMock}), + var scope = createScope({$exceptionHandler: $exceptionHandlerFactory}, + {$log: $logMock}), $log = scope.$service('$log'), $exceptionHandler = scope.$service('$exceptionHandler'); + $log.error.rethrow = false; $exceptionHandler('myError'); expect($log.error.logs.shift()).toEqual(['myError']); }); diff --git a/test/service/invalidWidgetsSpec.js b/test/service/invalidWidgetsSpec.js index 027d8d7c..fe7efe38 100644 --- a/test/service/invalidWidgetsSpec.js +++ b/test/service/invalidWidgetsSpec.js @@ -21,21 +21,21 @@ describe('$invalidWidgets', function() { expect($invalidWidgets.length).toEqual(1); scope.price = 123; - scope.$eval(); + scope.$digest(); expect($invalidWidgets.length).toEqual(0); scope.$element.remove(); scope.price = 'abc'; - scope.$eval(); + scope.$digest(); expect($invalidWidgets.length).toEqual(0); jqLite(document.body).append(scope.$element); scope.price = 'abcd'; //force revalidation, maybe this should be done automatically? - scope.$eval(); + scope.$digest(); expect($invalidWidgets.length).toEqual(1); jqLite(document.body).html(''); - scope.$eval(); + scope.$digest(); expect($invalidWidgets.length).toEqual(0); }); }); diff --git a/test/service/locationSpec.js b/test/service/locationSpec.js index f5a8c6b2..73e5e43e 100644 --- a/test/service/locationSpec.js +++ b/test/service/locationSpec.js @@ -46,9 +46,10 @@ describe('$location', function() { $location.update('http://www.angularjs.org/'); $location.update({path: '/a/b'}); expect($location.href).toEqual('http://www.angularjs.org/a/b'); - expect($browser.getUrl()).toEqual(origBrowserUrl); - scope.$eval(); expect($browser.getUrl()).toEqual('http://www.angularjs.org/a/b'); + $location.path = '/c'; + scope.$digest(); + expect($browser.getUrl()).toEqual('http://www.angularjs.org/c'); }); @@ -65,7 +66,7 @@ describe('$location', function() { it('should update hash on hashPath or hashSearch update', function() { $location.update('http://server/#path?a=b'); - scope.$eval(); + scope.$digest(); $location.update({hashPath: '', hashSearch: {}}); expect($location.hash).toEqual(''); @@ -74,10 +75,10 @@ describe('$location', function() { it('should update hashPath and hashSearch on $location.hash change upon eval', function(){ $location.update('http://server/#path?a=b'); - scope.$eval(); + scope.$digest(); $location.hash = ''; - scope.$eval(); + scope.$digest(); expect($location.href).toEqual('http://server/'); expect($location.hashPath).toEqual(''); @@ -88,11 +89,13 @@ describe('$location', function() { it('should update hash on $location.hashPath or $location.hashSearch change upon eval', function() { $location.update('http://server/#path?a=b'); - scope.$eval(); + expect($location.href).toEqual('http://server/#path?a=b'); + expect($location.hashPath).toEqual('path'); + expect($location.hashSearch).toEqual({a:'b'}); + $location.hashPath = ''; $location.hashSearch = {}; - - scope.$eval(); + scope.$digest(); expect($location.href).toEqual('http://server/'); expect($location.hash).toEqual(''); @@ -103,14 +106,14 @@ describe('$location', function() { scope.$location = scope.$service('$location'); //publish to the scope for $watch var log = ''; - scope.$watch('$location.hash', function(){ - log += this.$location.hashPath + ';'; - }); + scope.$watch('$location.hash', function(scope){ + log += scope.$location.hashPath + ';'; + })(); expect(log).toEqual(';'); log = ''; scope.$location.hash = '/abc'; - scope.$eval(); + scope.$digest(); expect(scope.$location.hash).toEqual('/abc'); expect(log).toEqual('/abc;'); }); @@ -120,7 +123,7 @@ describe('$location', function() { it('should update hash with escaped hashPath', function() { $location.hashPath = 'foo=bar'; - scope.$eval(); + scope.$digest(); expect($location.hash).toBe('foo%3Dbar'); }); @@ -133,7 +136,7 @@ describe('$location', function() { $location.host = 'host'; $location.href = 'https://hrefhost:23/hrefpath'; - scope.$eval(); + scope.$digest(); expect($location).toEqualData({href: 'https://hrefhost:23/hrefpath', protocol: 'https', @@ -156,7 +159,7 @@ describe('$location', function() { $location.host = 'host'; $location.path = '/path'; - scope.$eval(); + scope.$digest(); expect($location).toEqualData({href: 'http://host:333/path#hash', protocol: 'http', @@ -237,7 +240,7 @@ describe('$location', function() { expect($location.href).toBe('http://server'); expect($location.hash).toBe(''); - scope.$eval(); + scope.$digest(); expect($location.href).toBe('http://server'); expect($location.hash).toBe(''); diff --git a/test/service/logSpec.js b/test/service/logSpec.js index 80d085b8..499447ad 100644 --- a/test/service/logSpec.js +++ b/test/service/logSpec.js @@ -19,12 +19,12 @@ describe('$log', function() { function warn(){ logger+= 'warn;'; } function info(){ logger+= 'info;'; } function error(){ logger+= 'error;'; } - var scope = createScope({}, {$log: $logFactory}, - {$exceptionHandler: rethrow, - $window: {console: {log: log, - warn: warn, - info: info, - error: error}}}), + var scope = createScope({$log: $logFactory}, + {$exceptionHandler: rethrow, + $window: {console: {log: log, + warn: warn, + info: info, + error: error}}}), $log = scope.$service('$log'); $log.log(); @@ -38,9 +38,9 @@ describe('$log', function() { it('should use console.log() if other not present', function(){ var logger = ""; function log(){ logger+= 'log;'; } - var scope = createScope({}, {$log: $logFactory}, - {$window: {console:{log:log}}, - $exceptionHandler: rethrow}); + var scope = createScope({$log: $logFactory}, + {$window: {console:{log:log}}, + $exceptionHandler: rethrow}); var $log = scope.$service('$log'); $log.log(); $log.warn(); @@ -51,9 +51,9 @@ describe('$log', function() { it('should use noop if no console', function(){ - var scope = createScope({}, {$log: $logFactory}, - {$window: {}, - $exceptionHandler: rethrow}), + var scope = createScope({$log: $logFactory}, + {$window: {}, + $exceptionHandler: rethrow}), $log = scope.$service('$log'); $log.log(); $log.warn(); diff --git a/test/service/routeSpec.js b/test/service/routeSpec.js index fc2c7f9d..6c6c0868 100644 --- a/test/service/routeSpec.js +++ b/test/service/routeSpec.js @@ -18,7 +18,7 @@ describe('$route', function() { $location, $route; function BookChapter() { - this.log = '<init>'; + log += '<init>'; } scope = compile('<div></div>')(); $location = scope.$service('$location'); @@ -28,28 +28,28 @@ describe('$route', function() { $route.onChange(function(){ log += 'onChange();'; }); + $location.update('http://server#/Book/Moby/Chapter/Intro?p=123'); - scope.$eval(); - expect(log).toEqual('onChange();'); + scope.$digest(); + expect(log).toEqual('onChange();<init>'); expect($route.current.params).toEqual({book:'Moby', chapter:'Intro', p:'123'}); - expect($route.current.scope.log).toEqual('<init>'); var lastId = $route.current.scope.$id; log = ''; $location.update('http://server#/Blank?ignore'); - scope.$eval(); + scope.$digest(); expect(log).toEqual('onChange();'); expect($route.current.params).toEqual({ignore:true}); expect($route.current.scope.$id).not.toEqual(lastId); log = ''; $location.update('http://server#/NONE'); - scope.$eval(); + scope.$digest(); expect(log).toEqual('onChange();'); expect($route.current).toEqual(null); $route.when('/NONE', {template:'instant update'}); - scope.$eval(); + scope.$digest(); expect($route.current.template).toEqual('instant update'); }); @@ -75,7 +75,7 @@ describe('$route', function() { expect(onChangeSpy).not.toHaveBeenCalled(); $location.updateHash('/foo'); - scope.$eval(); + scope.$digest(); expect($route.current.template).toEqual('foo.html'); expect($route.current.controller).toBeUndefined(); @@ -98,7 +98,7 @@ describe('$route', function() { expect(onChangeSpy).not.toHaveBeenCalled(); $location.updateHash('/unknownRoute'); - scope.$eval(); + scope.$digest(); expect($route.current.template).toBe('404.html'); expect($route.current.controller).toBe(NotFoundCtrl); @@ -107,7 +107,7 @@ describe('$route', function() { onChangeSpy.reset(); $location.updateHash('/foo'); - scope.$eval(); + scope.$digest(); expect($route.current.template).toEqual('foo.html'); expect($route.current.controller).toBeUndefined(); @@ -115,6 +115,39 @@ describe('$route', function() { expect(onChangeSpy).toHaveBeenCalled(); }); + it('should $destroy old routes', function(){ + var scope = angular.scope(), + $location = scope.$service('$location'), + $route = scope.$service('$route'); + + $route.when('/foo', {template: 'foo.html', controller: function(){ this.name = 'FOO';}}); + $route.when('/bar', {template: 'bar.html', controller: function(){ this.name = 'BAR';}}); + $route.when('/baz', {template: 'baz.html'}); + + expect(scope.$childHead).toEqual(null); + + $location.updateHash('/foo'); + scope.$digest(); + expect(scope.$$childHead).toBeTruthy(); + expect(scope.$$childHead).toEqual(scope.$$childTail); + + $location.updateHash('/bar'); + scope.$digest(); + expect(scope.$$childHead).toBeTruthy(); + expect(scope.$$childHead).toEqual(scope.$$childTail); + return + + $location.updateHash('/baz'); + scope.$digest(); + expect(scope.$$childHead).toBeTruthy(); + expect(scope.$$childHead).toEqual(scope.$$childTail); + + $location.updateHash('/'); + scope.$digest(); + expect(scope.$$childHead).toEqual(null); + expect(scope.$$childTail).toEqual(null); + }); + describe('redirection', function() { @@ -134,7 +167,7 @@ describe('$route', function() { expect($route.current).toBeNull(); expect(onChangeSpy).not.toHaveBeenCalled(); - scope.$eval(); //triggers initial route change - match the redirect route + scope.$digest(); //triggers initial route change - match the redirect route $browser.defer.flush(); //triger route change - match the route we redirected to expect($location.hash).toBe('/foo'); @@ -143,7 +176,7 @@ describe('$route', function() { onChangeSpy.reset(); $location.updateHash(''); - scope.$eval(); //match the redirect route + update $browser + scope.$digest(); //match the redirect route + update $browser $browser.defer.flush(); //match the route we redirected to expect($location.hash).toBe('/foo'); @@ -152,7 +185,7 @@ describe('$route', function() { onChangeSpy.reset(); $location.updateHash('/baz'); - scope.$eval(); //match the redirect route + update $browser + scope.$digest(); //match the redirect route + update $browser $browser.defer.flush(); //match the route we redirected to expect($location.hash).toBe('/bar'); @@ -170,10 +203,10 @@ describe('$route', function() { $route.when('/foo/:id/foo/:subid/:extraId', {redirectTo: '/bar/:id/:subid/23'}); $route.when('/bar/:id/:subid/:subsubid', {template: 'bar.html'}); - scope.$eval(); + scope.$digest(); $location.updateHash('/foo/id1/foo/subid3/gah'); - scope.$eval(); //triggers initial route change - match the redirect route + scope.$digest(); //triggers initial route change - match the redirect route $browser.defer.flush(); //triger route change - match the route we redirected to expect($location.hash).toBe('/bar/id1/subid3/23?extraId=gah'); @@ -190,10 +223,10 @@ describe('$route', function() { $route.when('/bar/:id/:subid/:subsubid', {template: 'bar.html'}); $route.when('/foo/:id/:extra', {redirectTo: '/bar/:id/:subid/99'}); - scope.$eval(); + scope.$digest(); $location.hash = '/foo/id3/eId?subid=sid1&appended=true'; - scope.$eval(); //triggers initial route change - match the redirect route + scope.$digest(); //triggers initial route change - match the redirect route $browser.defer.flush(); //triger route change - match the route we redirected to expect($location.hash).toBe('/bar/id3/sid1/99?appended=true&extra=eId'); @@ -210,10 +243,10 @@ describe('$route', function() { $route.when('/bar/:id/:subid/:subsubid', {template: 'bar.html'}); $route.when('/foo/:id', {redirectTo: customRedirectFn}); - scope.$eval(); + scope.$digest(); $location.hash = '/foo/id3?subid=sid1&appended=true'; - scope.$eval(); //triggers initial route change - match the redirect route + scope.$digest(); //triggers initial route change - match the redirect route $browser.defer.flush(); //triger route change - match the route we redirected to expect($location.hash).toBe('custom'); diff --git a/test/service/updateViewSpec.js b/test/service/updateViewSpec.js index 97366973..d8932d29 100644 --- a/test/service/updateViewSpec.js +++ b/test/service/updateViewSpec.js @@ -9,9 +9,9 @@ describe('$updateView', function() { browser.isMock = false; browser.defer = jasmine.createSpy('defer'); - scope = angular.scope(null, null, {$browser:browser}); + scope = angular.scope(null, {$browser:browser}); $updateView = scope.$service('$updateView'); - scope.$onEval(function(){ evalCount++; }); + scope.$observe(function(){ evalCount++; }); evalCount = 0; }); @@ -55,7 +55,7 @@ describe('$updateView', function() { it('should update immediatelly in test/mock mode', function(){ scope = angular.scope(); - scope.$onEval(function(){ evalCount++; }); + scope.$observe(function(){ evalCount++; }); expect(evalCount).toEqual(0); scope.$service('$updateView')(); expect(evalCount).toEqual(1); diff --git a/test/service/xhr.bulkSpec.js b/test/service/xhr.bulkSpec.js index adcb61fa..01e0a365 100644 --- a/test/service/xhr.bulkSpec.js +++ b/test/service/xhr.bulkSpec.js @@ -4,7 +4,10 @@ describe('$xhr.bulk', function() { var scope, $browser, $browserXhr, $log, $xhrBulk, $xhrError, log; beforeEach(function(){ - scope = angular.scope({}, null, {'$xhr.error': $xhrError = jasmine.createSpy('$xhr.error')}); + scope = angular.scope(angular.service, { + '$xhr.error': $xhrError = jasmine.createSpy('$xhr.error'), + '$log': $log = {} + }); $browser = scope.$service('$browser'); $browserXhr = $browser.xhr; $xhrBulk = scope.$service('$xhr.bulk'); diff --git a/test/service/xhr.cacheSpec.js b/test/service/xhr.cacheSpec.js index f4654cd4..7bf5d40b 100644 --- a/test/service/xhr.cacheSpec.js +++ b/test/service/xhr.cacheSpec.js @@ -4,7 +4,7 @@ describe('$xhr.cache', function() { var scope, $browser, $browserXhr, $xhrErr, cache, log; beforeEach(function() { - scope = angular.scope({}, null, {'$xhr.error': $xhrErr = jasmine.createSpy('$xhr.error')}); + scope = angular.scope(angularService, {'$xhr.error': $xhrErr = jasmine.createSpy('$xhr.error')}); $browser = scope.$service('$browser'); $browserXhr = $browser.xhr; cache = scope.$service('$xhr.cache'); @@ -126,22 +126,22 @@ describe('$xhr.cache', function() { it('should call eval after callbacks for both cache hit and cache miss execute', function() { - var evalSpy = this.spyOn(scope, '$eval').andCallThrough(); + var flushSpy = this.spyOn(scope, '$flush').andCallThrough(); $browserXhr.expectGET('/url').respond('+'); cache('GET', '/url', null, callback); - expect(evalSpy).not.toHaveBeenCalled(); + expect(flushSpy).not.toHaveBeenCalled(); $browserXhr.flush(); - expect(evalSpy).toHaveBeenCalled(); + expect(flushSpy).toHaveBeenCalled(); - evalSpy.reset(); //reset the spy + flushSpy.reset(); //reset the spy cache('GET', '/url', null, callback); - expect(evalSpy).not.toHaveBeenCalled(); + expect(flushSpy).not.toHaveBeenCalled(); $browser.defer.flush(); - expect(evalSpy).toHaveBeenCalled(); + expect(flushSpy).toHaveBeenCalled(); }); it('should call the error callback on error if provided', function() { diff --git a/test/service/xhr.errorSpec.js b/test/service/xhr.errorSpec.js index d3af4565..82fafe80 100644 --- a/test/service/xhr.errorSpec.js +++ b/test/service/xhr.errorSpec.js @@ -4,7 +4,7 @@ describe('$xhr.error', function() { var scope, $browser, $browserXhr, $xhr, $xhrError, log; beforeEach(function(){ - scope = angular.scope({}, angular.service, { + scope = angular.scope(angular.service, { '$xhr.error': $xhrError = jasmine.createSpy('$xhr.error') }); $browser = scope.$service('$browser'); diff --git a/test/service/xhrSpec.js b/test/service/xhrSpec.js index 9f496535..b01eb385 100644 --- a/test/service/xhrSpec.js +++ b/test/service/xhrSpec.js @@ -4,7 +4,8 @@ describe('$xhr', function() { var scope, $browser, $browserXhr, $log, $xhr, $xhrErr, log; beforeEach(function(){ - var scope = angular.scope({}, null, {'$xhr.error': $xhrErr = jasmine.createSpy('xhr.error')}); + var scope = angular.scope(angular.service, { + '$xhr.error': $xhrErr = jasmine.createSpy('xhr.error')}); $log = scope.$service('$log'); $browser = scope.$service('$browser'); $browserXhr = $browser.xhr; diff --git a/test/testabilityPatch.js b/test/testabilityPatch.js index bb553d68..606d29f0 100644 --- a/test/testabilityPatch.js +++ b/test/testabilityPatch.js @@ -130,10 +130,11 @@ function clearJqCache(){ count ++; delete jqCache[key]; forEach(value, function(value, key){ - if (value.$element) - dump(key, sortedHtml(value.$element)); - else - dump(key, toJson(value)); + if (value.$element) { + dump('LEAK', key, value.$id, sortedHtml(value.$element)); + } else { + dump('LEAK', key, toJson(value)); + } }); }); if (count) { diff --git a/test/widgetsSpec.js b/test/widgetsSpec.js index 225f0a1f..978392ec 100644 --- a/test/widgetsSpec.js +++ b/test/widgetsSpec.js @@ -13,7 +13,9 @@ describe("widget", function(){ } else { element = jqLite(html); } - return scope = angular.compile(element)(); + scope = angular.compile(element)(); + scope.$apply(); + return scope; }; }); @@ -26,25 +28,25 @@ describe("widget", 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.$get('name')).toEqual("Misko"); - expect(scope.$get('count')).toEqual(0); + expect(scope.name).toEqual("Misko"); + expect(scope.count).toEqual(0); - scope.$set('name', 'Adam'); - scope.$eval(); + scope.name = 'Adam'; + scope.$digest(); expect(element.val()).toEqual("Adam"); element.val('Shyam'); browserTrigger(element, 'keydown'); // keydown event must be deferred - expect(scope.$get('name')).toEqual('Adam'); + expect(scope.name).toEqual('Adam'); scope.$service('$browser').defer.flush(); - expect(scope.$get('name')).toEqual('Shyam'); - expect(scope.$get('count')).toEqual(1); + expect(scope.name).toEqual('Shyam'); + expect(scope.count).toEqual(1); element.val('Kai'); browserTrigger(element, 'change'); - expect(scope.$get('name')).toEqual('Kai'); - expect(scope.$get('count')).toEqual(2); + expect(scope.name).toEqual('Kai'); + expect(scope.count).toEqual(2); }); it('should not trigger eval if value does not change', function(){ @@ -67,15 +69,15 @@ describe("widget", function(){ it("should format text", function(){ compile('<input type="Text" name="list" value="a,b,c" ng:format="list"/>'); - expect(scope.$get('list')).toEqual(['a', 'b', 'c']); + expect(scope.list).toEqual(['a', 'b', 'c']); - scope.$set('list', ['x', 'y', 'z']); - scope.$eval(); + scope.list = ['x', 'y', 'z']; + scope.$digest(); expect(element.val()).toEqual("x, y, z"); element.val('1, 2, 3'); browserTrigger(element); - expect(scope.$get('list')).toEqual(['1', '2', '3']); + expect(scope.list).toEqual(['1', '2', '3']); }); it("should come up blank if null", function(){ @@ -87,7 +89,7 @@ describe("widget", 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.$eval(); + scope.$digest(); scope.$element.val('123X'); browserTrigger(scope.$element, 'change'); expect(scope.$element.val()).toEqual('123X'); @@ -98,11 +100,11 @@ describe("widget", 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.$eval(); + scope.$digest(); expect(scope.$element.val()).toEqual('456'); }); - it("should not clober text if model changes doe 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,7 +130,7 @@ describe("widget", function(){ it("should come up blank when no value specifiend", function(){ compile('<input type="text" name="age" ng:format="number"/>'); - scope.$eval(); + scope.$digest(); expect(scope.$element.val()).toEqual(''); expect(scope.age).toEqual(null); }); @@ -173,7 +175,7 @@ describe("widget", function(){ expect(scope.$element[0].checked).toEqual(false); scope.state = "Worked"; - scope.$eval(); + scope.$digest(); expect(scope.state).toEqual("Worked"); expect(scope.$element[0].checked).toEqual(true); }); @@ -186,8 +188,8 @@ describe("widget", function(){ expect(element.hasClass('ng-validation-error')).toBeTruthy(); expect(element.attr('ng-validation-error')).toEqual('Not a number'); - scope.$set('price', '123'); - scope.$eval(); + scope.price = '123'; + scope.$digest(); expect(element.hasClass('ng-validation-error')).toBeFalsy(); expect(element.attr('ng-validation-error')).toBeFalsy(); @@ -202,8 +204,8 @@ describe("widget", function(){ expect(element.hasClass('ng-validation-error')).toBeTruthy(); expect(element.attr('ng-validation-error')).toEqual('Required'); - scope.$set('price', '123'); - scope.$eval(); + scope.price = '123'; + scope.$digest(); expect(element.hasClass('ng-validation-error')).toBeFalsy(); expect(element.attr('ng-validation-error')).toBeFalsy(); }); @@ -215,7 +217,7 @@ describe("widget", function(){ expect(lastValue).toEqual("NOT_CALLED"); scope.url = 'http://server'; - scope.$eval(); + scope.$digest(); expect(lastValue).toEqual("http://server"); delete angularValidator.myValidator; @@ -240,8 +242,8 @@ describe("widget", function(){ expect(element.hasClass('ng-validation-error')).toBeTruthy(); expect(element.attr('ng-validation-error')).toEqual('Required'); - scope.$set('price', 'xxx'); - scope.$eval(); + scope.price = 'xxx'; + scope.$digest(); expect(element.hasClass('ng-validation-error')).toBeFalsy(); expect(element.attr('ng-validation-error')).toBeFalsy(); @@ -254,19 +256,19 @@ describe("widget", function(){ it('should allow conditions on ng:required', function() { compile('<input type="text" name="price" ng:required="ineedz"/>', jqLite(document.body)); - scope.$set('ineedz', false); - scope.$eval(); + scope.ineedz = false; + scope.$digest(); expect(element.hasClass('ng-validation-error')).toBeFalsy(); expect(element.attr('ng-validation-error')).toBeFalsy(); - scope.$set('price', 'xxx'); - scope.$eval(); + scope.price = 'xxx'; + scope.$digest(); expect(element.hasClass('ng-validation-error')).toBeFalsy(); expect(element.attr('ng-validation-error')).toBeFalsy(); - scope.$set('price', ''); - scope.$set('ineedz', true); - scope.$eval(); + scope.price = ''; + scope.ineedz = true; + scope.$digest(); expect(element.hasClass('ng-validation-error')).toBeTruthy(); expect(element.attr('ng-validation-error')).toEqual('Required'); @@ -278,31 +280,31 @@ describe("widget", function(){ it("should process ng:required2", function() { compile('<textarea name="name">Misko</textarea>'); - expect(scope.$get('name')).toEqual("Misko"); + expect(scope.name).toEqual("Misko"); - scope.$set('name', 'Adam'); - scope.$eval(); + scope.name = 'Adam'; + scope.$digest(); expect(element.val()).toEqual("Adam"); element.val('Shyam'); browserTrigger(element); - expect(scope.$get('name')).toEqual('Shyam'); + expect(scope.name).toEqual('Shyam'); element.val('Kai'); browserTrigger(element); - expect(scope.$get('name')).toEqual('Kai'); + expect(scope.name).toEqual('Kai'); }); it('should call ng:change on button click', function(){ compile('<input type="button" value="Click Me" ng:change="clicked = true"/>'); browserTrigger(element); - expect(scope.$get('clicked')).toEqual(true); + expect(scope.clicked).toEqual(true); }); it('should support button alias', function(){ compile('<button ng:change="clicked = true">Click {{"Me"}}.</button>'); browserTrigger(element); - expect(scope.$get('clicked')).toEqual(true); + expect(scope.clicked).toEqual(true); expect(scope.$element.text()).toEqual("Click Me."); }); @@ -319,11 +321,11 @@ describe("widget", function(){ expect(b.name.split('@')[1]).toEqual('chose'); expect(scope.chose).toEqual('B'); scope.chose = 'A'; - scope.$eval(); + scope.$digest(); expect(a.checked).toEqual(true); scope.chose = 'B'; - scope.$eval(); + scope.$digest(); expect(a.checked).toEqual(false); expect(b.checked).toEqual(true); expect(scope.clicked).not.toBeDefined(); @@ -364,12 +366,11 @@ describe("widget", function(){ '</select>'); expect(scope.selection).toEqual('B'); scope.selection = 'A'; - scope.$eval(); + scope.$digest(); expect(scope.selection).toEqual('A'); expect(element[0].childNodes[0].selected).toEqual(true); }); - it('should compile children of a select without a name, but not create a model for it', function() { compile('<select>' + @@ -379,7 +380,7 @@ describe("widget", function(){ '</select>'); scope.a = 'foo'; scope.b = 'bar'; - scope.$eval(); + scope.$flush(); expect(scope.$element.text()).toBe('foobarC'); }); @@ -394,9 +395,10 @@ describe("widget", function(){ '</select>'); expect(scope.selection).toEqual(['B']); scope.selection = ['A']; - scope.$eval(); + scope.$digest(); expect(element[0].childNodes[0].selected).toEqual(true); }); + }); it('should ignore text widget which have no name', function(){ @@ -412,19 +414,12 @@ describe("widget", function(){ }); it('should report error on assignment error', function(){ - compile('<input type="text" name="throw \'\'" value="x"/>'); - expect(element.hasClass('ng-exception')).toBeTruthy(); - expect(scope.$service('$log').error.logs.shift()[0]). - toMatchError(/Syntax Error: Token '''' is an unexpected token/); + 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(); }); - it('should report error on ng:change exception', function(){ - compile('<button ng:change="a-2=x">click</button>'); - browserTrigger(element); - expect(element.hasClass('ng-exception')).toBeTruthy(); - expect(scope.$service('$log').error.logs.shift()[0]). - toMatchError(/Syntax Error: Token '=' implies assignment but \[a-2\] can not be assigned to/); - }); }); describe('ng:switch', function(){ @@ -436,43 +431,38 @@ describe("widget", function(){ '</ng:switch>'); expect(element.html()).toEqual(''); scope.select = 1; - scope.$eval(); + scope.$apply(); expect(element.text()).toEqual('first:'); scope.name="shyam"; - scope.$eval(); + scope.$apply(); expect(element.text()).toEqual('first:shyam'); scope.select = 2; - scope.$eval(); + scope.$apply(); expect(element.text()).toEqual('second:shyam'); scope.name = 'misko'; - scope.$eval(); + scope.$apply(); expect(element.text()).toEqual('second:misko'); scope.select = true; - scope.$eval(); + scope.$apply(); expect(element.text()).toEqual('true:misko'); }); - it("should compare stringified versions", function(){ - var switchWidget = angular.widget('ng:switch'); - expect(switchWidget.equals(true, 'true')).toEqual(true); - }); - 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>' + - '</ng:switch>'); - scope.$eval(); + '<div ng:switch-when="1">one</div>' + + '<div ng:switch-default>other</div>' + + '</ng:switch>'); + scope.$apply(); expect(element.text()).toEqual('other'); scope.select = 1; - scope.$eval(); + scope.$apply(); expect(element.text()).toEqual('one'); }); 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.$eval(); + scope.$apply(); expect(scope.name).toEqual(undefined); expect(scope.$element.text()).toEqual('works'); dealoc(scope); @@ -483,11 +473,11 @@ describe("widget", function(){ it('should include on external file', function() { var element = jqLite('<ng:include src="url" scope="childScope"></ng:include>'); var scope = angular.compile(element)(); - scope.childScope = createScope(); + scope.childScope = scope.$new(); scope.childScope.name = 'misko'; scope.url = 'myUrl'; scope.$service('$xhr.cache').data.myUrl = {value:'{{name}}'}; - scope.$eval(); + scope.$flush(); expect(element.text()).toEqual('misko'); dealoc(scope); }); @@ -495,16 +485,16 @@ describe("widget", function(){ it('should remove previously included text if a falsy value is bound to src', function() { var element = jqLite('<ng:include src="url" scope="childScope"></ng:include>'); var scope = angular.compile(element)(); - scope.childScope = createScope(); + scope.childScope = scope.$new(); scope.childScope.name = 'igor'; scope.url = 'myUrl'; scope.$service('$xhr.cache').data.myUrl = {value:'{{name}}'}; - scope.$eval(); + scope.$flush(); expect(element.text()).toEqual('igor'); scope.url = undefined; - scope.$eval(); + scope.$flush(); expect(element.text()).toEqual(''); dealoc(scope); @@ -515,11 +505,14 @@ describe("widget", function(){ var scope = angular.compile(element)(); scope.url = 'myUrl'; scope.$service('$xhr.cache').data.myUrl = {value:'{{c=c+1}}'}; - scope.$eval(); - - // this one should really be just '1', but due to lack of real events things are not working - // properly. see discussion at: http://is.gd/ighKk - expect(element.text()).toEqual('4'); + scope.$flush(); + // TODO(misko): because we are using scope==this, the eval gets registered + // during the flush phase and hence does not get called. + // I don't think passing 'this' makes sense. Does having scope on ng:include makes sense? + // should we make scope="this" ilegal? + scope.$flush(); + + expect(element.text()).toEqual('1'); dealoc(element); }); @@ -531,11 +524,28 @@ describe("widget", function(){ scope.url = 'myUrl'; scope.$service('$xhr.cache').data.myUrl = {value:'my partial'}; - scope.$eval(); + scope.$flush(); expect(element.text()).toEqual('my partial'); expect(scope.loaded).toBe(true); dealoc(element); }); + + it('should destroy old scope', function(){ + var element = jqLite('<ng:include src="url"></ng:include>'); + var scope = angular.compile(element)(); + + expect(scope.$$childHead).toBeFalsy(); + + scope.url = 'myUrl'; + scope.$service('$xhr.cache').data.myUrl = {value:'my partial'}; + scope.$flush(); + expect(scope.$$childHead).toBeTruthy(); + + scope.url = null; + scope.$flush(); + expect(scope.$$childHead).toBeFalsy(); + dealoc(element); + }); }); describe('a', function() { @@ -624,7 +634,7 @@ describe("widget", function(){ createSingleSelect(); scope.values = [{name:'A'}, {name:'B'}, {name:'C'}]; scope.selected = scope.values[0]; - scope.$eval(); + scope.$flush(); var options = select.find('option'); expect(options.length).toEqual(3); expect(sortedHtml(options[0])).toEqual('<option value="0">A</option>'); @@ -639,7 +649,7 @@ describe("widget", function(){ }); scope.object = {'red':'FF0000', 'green':'00FF00', 'blue':'0000FF'}; scope.selected = scope.object.red; - scope.$eval(); + scope.$flush(); var options = select.find('option'); expect(options.length).toEqual(3); expect(sortedHtml(options[0])).toEqual('<option value="blue">blue</option>'); @@ -648,7 +658,7 @@ describe("widget", function(){ expect(options[2].selected).toEqual(true); scope.object.azur = '8888FF'; - scope.$eval(); + scope.$flush(); options = select.find('option'); expect(options[3].selected).toEqual(true); }); @@ -656,18 +666,18 @@ describe("widget", function(){ it('should grow list', function(){ createSingleSelect(); scope.values = []; - scope.$eval(); + scope.$flush(); expect(select.find('option').length).toEqual(1); // because we add special empty option expect(sortedHtml(select.find('option')[0])).toEqual('<option value="?"></option>'); scope.values.push({name:'A'}); scope.selected = scope.values[0]; - scope.$eval(); + scope.$flush(); 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(); + scope.$flush(); 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>'); @@ -677,23 +687,23 @@ describe("widget", function(){ createSingleSelect(); scope.values = [{name:'A'}, {name:'B'}, {name:'C'}]; scope.selected = scope.values[0]; - scope.$eval(); + scope.$flush(); expect(select.find('option').length).toEqual(3); scope.values.pop(); - scope.$eval(); + scope.$flush(); 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(); + scope.$flush(); 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(); + scope.$flush(); expect(select.find('option').length).toEqual(1); // we add back the special empty option }); @@ -701,17 +711,17 @@ describe("widget", function(){ createSingleSelect(); scope.values = [{name:'A'}, {name:'B'}, {name:'C'}]; scope.selected = scope.values[0]; - scope.$eval(); + scope.$flush(); expect(select.find('option').length).toEqual(3); scope.values = [{name:'1'}, {name:'2'}]; scope.selected = scope.values[0]; - scope.$eval(); + scope.$flush(); expect(select.find('option').length).toEqual(2); scope.values = [{name:'A'}, {name:'B'}, {name:'C'}]; scope.selected = scope.values[0]; - scope.$eval(); + scope.$flush(); expect(select.find('option').length).toEqual(3); }); @@ -719,11 +729,11 @@ describe("widget", function(){ createSingleSelect(); scope.values = [{name:'A'}, {name:'B'}, {name:'C'}]; scope.selected = scope.values[0]; - scope.$eval(); + scope.$flush(); scope.values = [{name:'B'}, {name:'C'}, {name:'D'}]; scope.selected = scope.values[0]; - scope.$eval(); + scope.$flush(); var options = select.find('option'); expect(options.length).toEqual(3); expect(sortedHtml(options[0])).toEqual('<option value="0">B</option>'); @@ -734,19 +744,19 @@ describe("widget", function(){ it('should preserve existing options', function(){ createSingleSelect(true); - scope.$eval(); + scope.$flush(); expect(select.find('option').length).toEqual(1); scope.values = [{name:'A'}]; scope.selected = scope.values[0]; - scope.$eval(); + scope.$flush(); 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(); + scope.$flush(); expect(select.find('option').length).toEqual(1); expect(jqLite(select.find('option')[0]).text()).toEqual('blank'); }); @@ -756,11 +766,11 @@ describe("widget", function(){ createSingleSelect(); scope.values = [{name:'A'}, {name:'B'}]; scope.selected = scope.values[0]; - scope.$eval(); + scope.$flush(); expect(select.val()).toEqual('0'); scope.selected = scope.values[1]; - scope.$eval(); + scope.$flush(); expect(select.val()).toEqual('1'); }); @@ -775,7 +785,7 @@ describe("widget", function(){ {name:'D', group:'first'}, {name:'E', group:'second'}]; scope.selected = scope.values[3]; - scope.$eval(); + scope.$flush(); expect(select.val()).toEqual('3'); var first = jqLite(select.find('optgroup')[0]); @@ -793,7 +803,7 @@ describe("widget", function(){ expect(e.text()).toEqual('E'); scope.selected = scope.values[0]; - scope.$eval(); + scope.$flush(); expect(select.val()).toEqual('0'); }); @@ -801,11 +811,11 @@ describe("widget", 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; - scope.$eval(); + scope.$flush(); expect(select.val()).toEqual('0'); scope.selected = scope.values[1].id; - scope.$eval(); + scope.$flush(); expect(select.val()).toEqual('1'); }); @@ -816,11 +826,11 @@ describe("widget", function(){ }); scope.object = {'red':'FF0000', 'green':'00FF00', 'blue':'0000FF'}; scope.selected = 'green'; - scope.$eval(); + scope.$flush(); expect(select.val()).toEqual('green'); scope.selected = 'blue'; - scope.$eval(); + scope.$flush(); expect(select.val()).toEqual('blue'); }); @@ -831,11 +841,11 @@ describe("widget", function(){ }); scope.object = {'red':'FF0000', 'green':'00FF00', 'blue':'0000FF'}; scope.selected = '00FF00'; - scope.$eval(); + scope.$flush(); expect(select.val()).toEqual('green'); scope.selected = '0000FF'; - scope.$eval(); + scope.$flush(); expect(select.val()).toEqual('blue'); }); @@ -843,13 +853,13 @@ describe("widget", function(){ createSingleSelect(); scope.values = [{name:'A'}]; scope.selected = null; - scope.$eval(); + scope.$flush(); 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(); + scope.$flush(); expect(select.val()).toEqual('0'); expect(select.find('option').length).toEqual(1); }); @@ -858,13 +868,13 @@ describe("widget", function(){ createSingleSelect(true); scope.values = [{name:'A'}]; scope.selected = null; - scope.$eval(); + scope.$flush(); 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(); + scope.$flush(); expect(select.val()).toEqual('0'); expect(select.find('option').length).toEqual(2); }); @@ -873,13 +883,13 @@ describe("widget", function(){ createSingleSelect(); scope.values = [{name:'A'}]; scope.selected = {}; - scope.$eval(); + scope.$flush(); 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(); + scope.$flush(); expect(select.val()).toEqual('0'); expect(select.find('option').length).toEqual(1); }); @@ -890,7 +900,7 @@ describe("widget", function(){ createSingleSelect(); scope.values = [{name:'A'}, {name:'B'}]; scope.selected = scope.values[0]; - scope.$eval(); + scope.$flush(); expect(select.val()).toEqual('0'); select.val('1'); @@ -907,7 +917,7 @@ describe("widget", function(){ scope.values = [{name:'A'}, {name:'B'}]; scope.selected = scope.values[0]; scope.count = 0; - scope.$eval(); + scope.$flush(); expect(scope.count).toEqual(0); select.val('1'); @@ -924,7 +934,7 @@ describe("widget", 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; - scope.$eval(); + scope.$flush(); expect(select.val()).toEqual('0'); select.val('1'); @@ -937,7 +947,7 @@ describe("widget", function(){ scope.values = [{name:'A'}, {name:'B'}]; scope.selected = scope.values[0]; select.val('0'); - scope.$eval(); + scope.$flush(); select.val(''); browserTrigger(select, 'change'); @@ -951,19 +961,19 @@ describe("widget", function(){ scope.values = [{name:'A'}, {name:'B'}]; scope.selected = []; - scope.$eval(); + scope.$flush(); 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(); + scope.$flush(); 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(); + scope.$flush(); expect(select.find('option').length).toEqual(2); expect(select.find('option')[0].selected).toEqual(true); expect(select.find('option')[1].selected).toEqual(true); @@ -974,7 +984,7 @@ describe("widget", function(){ scope.values = [{name:'A'}, {name:'B'}]; scope.selected = []; - scope.$eval(); + scope.$flush(); select.find('option')[0].selected = true; browserTrigger(select, 'change'); @@ -991,24 +1001,30 @@ describe("widget", 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"; + // INIT scope.items = ['misko', 'shyam']; - scope.$eval(); + scope.$flush(); + expect(element.find('li').length).toEqual(2); expect(element.text()).toEqual('misko;shyam;'); delete Array.prototype.extraProperty; + // GROW scope.items = ['adam', 'kai', 'brad']; - scope.$eval(); + scope.$flush(); + expect(element.find('li').length).toEqual(3); expect(element.text()).toEqual('adam;kai;brad;'); + // SHRINK scope.items = ['brad']; - scope.$eval(); + scope.$flush(); + expect(element.find('li').length).toEqual(1); expect(element.text()).toEqual('brad;'); }); 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.$set('items', {misko:'swe', shyam:'set'}); - scope.$eval(); + scope.items = {misko:'swe', shyam:'set'}; + scope.$flush(); expect(element.text()).toEqual('misko:swe;shyam:set;'); }); @@ -1020,28 +1036,23 @@ describe("widget", function(){ var scope = compile('<ul><li ng:repeat="(key, value) in items" ng:bind="key + \':\' + value + \';\' "></li></ul>'); scope.items = new Class(); scope.items.name = 'value'; - scope.$eval(); + scope.$flush(); expect(element.text()).toEqual('name:value;'); }); it('should error on wrong parsing of ng:repeat', 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'."); - - expect(scope.$element.attr('ng-exception')). - toMatch(/Expected ng:repeat in form of '_item_ in _collection_' but got 'i dont parse'/); - expect(scope.$element).toHaveClass('ng-exception'); + 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'."); - dealoc(scope); + $logMock.error.logs.shift(); }); it('should expose iterator offset as $index when iterating over arrays', function() { var scope = compile('<ul><li ng:repeat="item in items" ' + 'ng:bind="item + $index + \'|\'"></li></ul>'); scope.items = ['misko', 'shyam', 'frodo']; - scope.$eval(); + scope.$flush(); expect(element.text()).toEqual('misko0|shyam1|frodo2|'); }); @@ -1049,7 +1060,7 @@ describe("widget", function(){ var scope = compile('<ul><li ng:repeat="(key, val) in items" ' + 'ng:bind="key + \':\' + val + $index + \'|\'"></li></ul>'); scope.items = {'misko':'m', 'shyam':'s', 'frodo':'f'}; - scope.$eval(); + scope.$flush(); expect(element.text()).toEqual('misko:m0|shyam:s1|frodo:f2|'); }); @@ -1057,16 +1068,16 @@ describe("widget", function(){ var scope = compile('<ul><li ng:repeat="item in items" ' + 'ng:bind="item + \':\' + $position + \'|\'"></li></ul>'); scope.items = ['misko', 'shyam', 'doug']; - scope.$eval(); + scope.$flush(); expect(element.text()).toEqual('misko:first|shyam:middle|doug:last|'); scope.items.push('frodo'); - scope.$eval(); + scope.$flush(); expect(element.text()).toEqual('misko:first|shyam:middle|doug:middle|frodo:last|'); scope.items.pop(); scope.items.pop(); - scope.$eval(); + scope.$flush(); expect(element.text()).toEqual('misko:first|shyam:last|'); }); @@ -1074,12 +1085,12 @@ describe("widget", function(){ 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.$eval(); + scope.$flush(); expect(element.text()).toEqual('misko:m:first|shyam:s:middle|doug:d:middle|frodo:f:last|'); delete scope.items.doug; delete scope.items.frodo; - scope.$eval(); + scope.$flush(); expect(element.text()).toEqual('misko:m:first|shyam:s:last|'); }); }); @@ -1089,8 +1100,8 @@ describe("widget", 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.$set('name', 'misko'); - scope.$eval(); + scope.name = 'misko'; + scope.$digest(); expect(element.text()).toEqual(''); }); }); @@ -1113,7 +1124,7 @@ describe("widget", function(){ it('should do nothing when no routes are defined', function() { $location.updateHash('/unknown'); - rootScope.$eval(); + rootScope.$digest(); expect(rootScope.$element.text()).toEqual(''); }); @@ -1126,13 +1137,15 @@ describe("widget", function(){ $location.updateHash('/foo'); $browser.xhr.expectGET('myUrl1').respond('<div>{{1+3}}</div>'); - rootScope.$eval(); + rootScope.$digest(); + rootScope.$flush(); $browser.xhr.flush(); expect(rootScope.$element.text()).toEqual('4'); $location.updateHash('/bar'); $browser.xhr.expectGET('myUrl2').respond('angular is da best'); - rootScope.$eval(); + rootScope.$digest(); + rootScope.$flush(); $browser.xhr.flush(); expect(rootScope.$element.text()).toEqual('angular is da best'); }); @@ -1142,12 +1155,14 @@ describe("widget", function(){ $location.updateHash('/foo'); $browser.xhr.expectGET('myUrl1').respond('<div>{{1+3}}</div>'); - rootScope.$eval(); + rootScope.$digest(); + rootScope.$flush(); $browser.xhr.flush(); expect(rootScope.$element.text()).toEqual('4'); $location.updateHash('/unknown'); - rootScope.$eval(); + rootScope.$digest(); + rootScope.$flush(); expect(rootScope.$element.text()).toEqual(''); }); @@ -1157,16 +1172,20 @@ describe("widget", function(){ $location.updateHash('/foo'); $browser.xhr.expectGET('myUrl1').respond('<div>{{parentVar}}</div>'); - rootScope.$eval(); + rootScope.$digest(); + rootScope.$flush(); $browser.xhr.flush(); expect(rootScope.$element.text()).toEqual('parent'); rootScope.parentVar = 'new parent'; - rootScope.$eval(); + rootScope.$digest(); + rootScope.$flush(); expect(rootScope.$element.text()).toEqual('new parent'); }); it('should be possible to nest ng:view in ng:include', function() { + dealoc(rootScope); // we are about to override it. + var myApp = angular.scope(); var $browser = myApp.$service('$browser'); $browser.xhr.expectGET('includePartial.html').respond('view: <ng:view></ng:view>'); @@ -1175,13 +1194,14 @@ describe("widget", function(){ var $route = myApp.$service('$route'); $route.when('/foo', {controller: angular.noop, template: 'viewPartial.html'}); - dealoc(rootScope); // we are about to override it. rootScope = angular.compile( '<div>' + 'include: <ng:include src="\'includePartial.html\'">' + '</ng:include></div>')(myApp); + rootScope.$apply(); $browser.xhr.expectGET('viewPartial.html').respond('content'); + rootScope.$flush(); $browser.xhr.flush(); expect(rootScope.$element.text()).toEqual('include: view: content'); @@ -1211,21 +1231,21 @@ describe("widget", function(){ respond('<div ng:init="log.push(\'init\')">' + '<div ng:controller="ChildCtrl"></div>' + '</div>'); - rootScope.$eval(); + rootScope.$apply(); $browser.xhr.flush(); - expect(rootScope.log).toEqual(['parent', 'init', 'child']); + expect(rootScope.log).toEqual(['parent', 'child', 'init']); $location.updateHash(''); - rootScope.$eval(); - expect(rootScope.log).toEqual(['parent', 'init', 'child']); + rootScope.$apply(); + expect(rootScope.log).toEqual(['parent', 'child', 'init']); rootScope.log = []; $location.updateHash('/foo'); - rootScope.$eval(); + rootScope.$apply(); $browser.defer.flush(); - expect(rootScope.log).toEqual(['parent', 'init', 'child']); + expect(rootScope.log).toEqual(['parent', 'child', 'init']); }); }); }); |
