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']);      });    });  });  | 
