diff options
| author | Misko Hevery | 2010-12-01 20:29:54 -0800 | 
|---|---|---|
| committer | Misko Hevery | 2010-12-02 22:45:57 -0800 | 
| commit | 5a8ad8fe329fc09898ff43a060710265d38393be (patch) | |
| tree | 95058036d40b1dd993e2a9c4094ebd34b2751707 | |
| parent | 41d5938883a3d06ffe8a88a51efd8d1896f7d747 (diff) | |
| download | angular.js-5a8ad8fe329fc09898ff43a060710265d38393be.tar.bz2 | |
Closes #170. Corrected the behavior of select when options are ng:repeated
 - Delete $postEval method, as it was a hack
| -rw-r--r-- | regression/issue-170.html | 28 | ||||
| -rw-r--r-- | src/Angular.js | 5 | ||||
| -rw-r--r-- | src/Compiler.js | 13 | ||||
| -rw-r--r-- | src/Scope.js | 27 | ||||
| -rw-r--r-- | src/directives.js | 21 | ||||
| -rw-r--r-- | src/validators.js | 2 | ||||
| -rw-r--r-- | src/widgets.js | 49 | ||||
| -rw-r--r-- | test/AngularSpec.js | 1 | ||||
| -rw-r--r-- | test/BinderTest.js | 1 | ||||
| -rw-r--r-- | test/CompilerSpec.js | 25 | ||||
| -rw-r--r-- | test/ResourceSpec.js | 2 | ||||
| -rw-r--r-- | test/ScenarioSpec.js | 104 | ||||
| -rw-r--r-- | test/ScopeSpec.js | 22 | ||||
| -rw-r--r-- | test/ValidatorsTest.js | 6 | ||||
| -rw-r--r-- | test/directivesSpec.js | 3 | ||||
| -rw-r--r-- | test/markupSpec.js | 176 | ||||
| -rw-r--r-- | test/servicesSpec.js | 11 | ||||
| -rw-r--r-- | test/testabilityPatch.js | 24 | ||||
| -rw-r--r-- | test/widgetsSpec.js | 37 | 
19 files changed, 328 insertions, 229 deletions
| diff --git a/regression/issue-170.html b/regression/issue-170.html new file mode 100644 index 00000000..392abf81 --- /dev/null +++ b/regression/issue-170.html @@ -0,0 +1,28 @@ +<!DOCTYPE HTML> +<html xmlns:ng="http://angularjs.org"> +<head> +  <script type="text/javascript" src="../lib/jquery/jquery-1.4.2.js"></script> +  <script type="text/javascript" src="../src/angular-bootstrap.js" ng:autobind></script> +<head> +<body> +  <select name='selection0' style="display:block;"> +      <option ng:repeat='value in ["FOO","BAR"]'">{{value}}</option> +  </select> +  {{selection0}} <-- FOO should be shown here +   +  <hr/> +   +  <select ng:init="selection1='ignore'" name='selection1' style="display:block;"> +      <option ng:repeat='value in ["FOO","BAR"]' ng:bind-attr="{selected:'{{value==\'BAR\'}}'}">{{value}}</option> +  </select> +  {{selection1}} <-- BAR should be shown here +   +  <hr/> + +  <select ng:init="selection2=1" name="selection2" style="display:block;"> +    <option value="{{$index}}" ng:repeat="opt in ['zero', 'one']">{{opt}}</option> +  </select> +  {{selection2}} <-- 1 should be shown here +   +</body> +</html>
\ No newline at end of file diff --git a/src/Angular.js b/src/Angular.js index 4d3c360f..97792868 100644 --- a/src/Angular.js +++ b/src/Angular.js @@ -53,6 +53,9 @@ function fromCharCode(code) { return String.fromCharCode(code); }  var _undefined        = undefined,      _null             = null,      $$element         = '$element', +    $$update          = '$update', +    $$scope           = '$scope', +    $$validate        = '$validate',      $angular          = 'angular',      $array            = 'array',      $boolean          = 'boolean', @@ -69,6 +72,8 @@ var _undefined        = undefined,      $number           = 'number',      $object           = 'object',      $string           = 'string', +    $value            = 'value', +    $selected         = 'selected',      $undefined        = 'undefined',      NG_EXCEPTION      = 'ng-exception',      NG_VALIDATION_ERROR = 'ng-validation-error', diff --git a/src/Compiler.js b/src/Compiler.js index 10d19ea8..a98bd502 100644 --- a/src/Compiler.js +++ b/src/Compiler.js @@ -30,6 +30,7 @@ Template.prototype = {      if (this.newScope) {        childScope = createScope(scope);        scope.$onEval(childScope.$eval); +      element.data($$scope, childScope);      }      foreach(this.inits, function(fn) {        queue.push(function() { @@ -68,6 +69,17 @@ Template.prototype = {    }  }; +/* + * Function walks up the element chain looking for the scope associated with the give element. + */ +function retrieveScope(element) { +  var scope; +  while (element && !(scope = element.data($$scope))) { +    element = element.parent(); +  } +  return scope; +} +  ///////////////////////////////////  //Compiler  ////////////////////////////////// @@ -97,6 +109,7 @@ Compiler.prototype = {        element = jqLite(element);        var scope = parentScope && parentScope.$eval ?            parentScope : createScope(parentScope); +      element.data($$scope, scope);        return extend(scope, {          $element:element,          $init: function() { diff --git a/src/Scope.js b/src/Scope.js index 09779453..203507a3 100644 --- a/src/Scope.js +++ b/src/Scope.js @@ -243,7 +243,6 @@ function createScope(parent, providers, instanceCache) {    parent = Parent.prototype = (parent || {});    var instance = new Parent();    var evalLists = {sorted:[]}; -  var postList = [], postHash = {}, postId = 0;    extend(instance, {      'this': instance, @@ -371,11 +370,6 @@ function createScope(parent, providers, instanceCache) {              instance.$tryEval(queue[j].fn, queue[j].handler);            }          } -        while(postList.length) { -          fn = postList.shift(); -          delete postHash[fn.$postEvalId]; -          instance.$tryEval(fn); -        }        } else if (type === $function) {          return exp.call(instance);        } else  if (type === 'string') { @@ -552,27 +546,6 @@ function createScope(parent, providers, instanceCache) {      /**       * @workInProgress       * @ngdoc function -     * @name angular.scope.$postEval -     * @function -     */ -    $postEval: function(expr) { -      if (expr) { -        var fn = expressionCompile(expr); -        var id = fn.$postEvalId; -        if (!id) { -          id = '$' + instance.$id + "_" + (postId++); -          fn.$postEvalId = id; -        } -        if (!postHash[id]) { -          postList.push(postHash[id] = fn); -        } -      } -    }, - - -    /** -     * @workInProgress -     * @ngdoc function       * @name angular.scope.$become       * @function       * @deprecated This method will be removed before 1.0 diff --git a/src/directives.js b/src/directives.js index 8a76f17a..d40d6120 100644 --- a/src/directives.js +++ b/src/directives.js @@ -304,7 +304,8 @@ angularDirective("ng:bind-template", function(expression, element){  var REMOVE_ATTRIBUTES = {    'disabled':'disabled',    'readonly':'readOnly', -  'checked':'checked' +  'checked':'checked', +  'selected':'selected'  };  /**   * @workInProgress @@ -359,27 +360,31 @@ var REMOVE_ATTRIBUTES = {  angularDirective("ng:bind-attr", function(expression){    return function(element){      var lastValue = {}; -    var updateFn = element.parent().data('$update'); +    var updateFn = element.data($$update) || noop;      this.$onEval(function(){ -      var values = this.$eval(expression); +      var values = this.$eval(expression), +          dirty = noop;        for(var key in values) {          var value = compileBindTemplate(values[key]).call(this, element),              specialName = REMOVE_ATTRIBUTES[lowercase(key)];          if (lastValue[key] !== value) {            lastValue[key] = value;            if (specialName) { -            if (element[specialName] = toBoolean(value)) { -              element.attr(specialName, value); +            if (toBoolean(value)) { +              element.attr(specialName, specialName); +              element.attr('ng-' + specialName, value);              } else { -              element.removeAttr(key); +              element.removeAttr(specialName); +              element.removeAttr('ng-' + specialName);              } -            (element.data('$validate')||noop)(); +            (element.data($$validate)||noop)();            } else {              element.attr(key, value);            } -          this.$postEval(updateFn); +          dirty = updateFn;          }        } +      dirty();      }, element);    };  }); diff --git a/src/validators.js b/src/validators.js index ea35558e..7318333a 100644 --- a/src/validators.js +++ b/src/validators.js @@ -394,7 +394,7 @@ extend(angularValidator, {            element.removeClass('ng-input-indicator-wait');            scope.$invalidWidgets.markValid(element);          } -        element.data('$validate')(); +        element.data($$validate)();          scope.$root.$eval();        });      } else if (inputState.inFlight) { diff --git a/src/widgets.js b/src/widgets.js index f34ff05c..04353bd5 100644 --- a/src/widgets.js +++ b/src/widgets.js @@ -282,7 +282,7 @@ function valueAccessor(scope, element) {      required = requiredExpr === '';    } -  element.data('$validate', validate); +  element.data($$validate, validate);    return {      get: function(){        if (lastError) @@ -391,6 +391,7 @@ var textWidget = inputWidget('keyup change', modelAccessor, valueAccessor, initW  //      'file':            fileWidget???      }; +  function initWidgetValue(initValue) {    return function (model, view) {      var value = view.get(); @@ -461,18 +462,13 @@ function inputWidget(events, modelAccessor, viewAccessor, initFn) {      this.$eval(element.attr('ng:init')||'');      // Don't register a handler if we are a button (noopAccessor) and there is no action      if (action || modelAccessor !== noopAccessor) { -      element.bind(events, function(event){ +      element.bind(events, function (){          model.set(view.get());          lastValue = model.get();          scope.$tryEval(action, element);          scope.$root.$eval();        });      } -    function updateView(){ -      view.set(lastValue = model.get()); -    } -    updateView(); -    element.data('$update', updateView);      scope.$watch(model.get, function(value){        if (lastValue !== value) {          view.set(lastValue = value); @@ -494,15 +490,50 @@ angularWidget('select', function(element){    return inputWidgetSelector.call(this, element);  }); + +/* + * Consider this: + * <select name="selection"> + *   <option ng:repeat="x in [1,2]">{{x}}</option> + * </select> + *  + * The issue is that the select gets evaluated before option is unrolled. + * This means that the selection is undefined, but the browser + * default behavior is to show the top selection in the list.  + * To fix that we register a $update function on the select element + * and the option creation then calls the $update function when it is  + * unrolled. The $update function then calls this update function, which  + * then tries to determine if the model is unassigned, and if so it tries to + * chose one of the options from the list. + */  angularWidget('option', function(){    this.descend(true);    this.directives(true);    return function(element) { -    this.$postEval(element.parent().data('$update')); +    var select = element.parent(); +    var scope = retrieveScope(select); +    var model = modelFormattedAccessor(scope, select); +    var view = valueAccessor(scope, select); +    var option = element; +    var lastValue = option.attr($value); +    var lastSelected = option.attr('ng-' + $selected); +    element.data($$update, function(){ +      var value = option.attr($value); +      var selected = option.attr('ng-' + $selected); +      var modelValue = model.get(); +      if (lastSelected != selected || lastValue != value) { +        lastSelected = selected; +        lastValue = value; +        if (selected || modelValue == _null || modelValue == _undefined)  +          model.set(value); +        if (value == modelValue) { +          view.set(lastValue); +        } +      } +    });    };  }); -  /**   * @workInProgress   * @ngdoc widget diff --git a/test/AngularSpec.js b/test/AngularSpec.js index 6733a7ab..f5a202fe 100644 --- a/test/AngularSpec.js +++ b/test/AngularSpec.js @@ -10,6 +10,7 @@ describe('Angular', function(){      scope.$init();      scope.$eval();      expect(onUpdateView).wasCalled(); +    dealoc(scope);    });  }); diff --git a/test/BinderTest.js b/test/BinderTest.js index d35d46f4..58081f25 100644 --- a/test/BinderTest.js +++ b/test/BinderTest.js @@ -5,6 +5,7 @@ BinderTest.prototype.setUp = function(){    this.compile = function(html, initialScope, parent) {      var compiler = new Compiler(angularTextMarkup, angularAttrMarkup, angularDirective, angularWidget); +    if (self.element) dealoc(self.element);      var element = self.element = jqLite(html);      var scope = compiler.compile(element)(element); diff --git a/test/CompilerSpec.js b/test/CompilerSpec.js index fa63ab77..d8c7c1b8 100644 --- a/test/CompilerSpec.js +++ b/test/CompilerSpec.js @@ -1,5 +1,5 @@  describe('compiler', function(){ -  var compiler, markup, directives, widgets, compile, log; +  var compiler, markup, directives, widgets, compile, log, scope;    beforeEach(function(){      log = ""; @@ -32,6 +32,10 @@ describe('compiler', function(){        return scope;      };    }); +   +  afterEach(function(){ +    dealoc(scope); +  });    it('should recognize a directive', function(){      var e = jqLite('<div directive="expr" ignore="me"></div>'); @@ -44,7 +48,8 @@ describe('compiler', function(){        };      };      var template = compiler.compile(e); -    var init = template(e).$init; +    scope = template(e); +    var init = scope.$init;      expect(log).toEqual("found");      init();      expect(e.hasClass('ng-directive')).toEqual(true); @@ -52,12 +57,12 @@ describe('compiler', function(){    });    it('should recurse to children', function(){ -    var scope = compile('<div><span hello="misko"/></div>'); +    scope = compile('<div><span hello="misko"/></div>');      expect(log).toEqual("hello misko");    });    it('should watch scope', function(){ -    var scope = compile('<span watch="name"/>'); +    scope = compile('<span watch="name"/>');      expect(log).toEqual("");      scope.$eval();      scope.$set('name', 'misko'); @@ -71,7 +76,7 @@ describe('compiler', function(){    it('should prevent descend', function(){      directives.stop = function(){ this.descend(false); }; -    var scope = compile('<span hello="misko" stop="true"><span hello="adam"/></span>'); +    scope = compile('<span hello="misko" stop="true"><span hello="adam"/></span>');      expect(log).toEqual("hello misko");    }); @@ -87,7 +92,7 @@ describe('compiler', function(){          });        };      }; -    var scope = compile('before<span duplicate="expr">x</span>after'); +    scope = compile('before<span duplicate="expr">x</span>after');      expect(sortedHtml(scope.$element)).toEqual('<div>before<#comment></#comment><span>x</span>after</div>');      scope.$eval();      expect(sortedHtml(scope.$element)).toEqual('<div>before<#comment></#comment><span>x</span><span>x</span>after</div>'); @@ -103,7 +108,7 @@ describe('compiler', function(){          textNode[0].nodeValue = 'replaced';        }      }); -    var scope = compile('before<span>middle</span>after'); +    scope = compile('before<span>middle</span>after');      expect(sortedHtml(scope.$element[0], true)).toEqual('<div>before<span class="ng-directive" hello="middle">replaced</span>after</div>');      expect(log).toEqual("hello middle");    }); @@ -116,7 +121,7 @@ describe('compiler', function(){          log += 'init';        };      }; -    var scope = compile('<ng:button>push me</ng:button>'); +    scope = compile('<ng:button>push me</ng:button>');      expect(lowercase(scope.$element[0].innerHTML)).toEqual('<div>button</div>');      expect(log).toEqual('init');    }); @@ -135,7 +140,7 @@ describe('compiler', function(){        if (text == '{{1+2}}')          parent.text('3');      }); -    var scope = compile('<div><h1>ignore me</h1></div>'); +    scope = compile('<div><h1>ignore me</h1></div>');      expect(scope.$element.text()).toEqual('3');    }); @@ -158,7 +163,7 @@ describe('compiler', function(){          textNode.remove();        }      }); -    var scope = compile('A---B---C===D'); +    scope = compile('A---B---C===D');      expect(sortedHtml(scope.$element)).toEqual('<div>A<hr></hr>B<hr></hr>C<p></p>D</div>');    }); diff --git a/test/ResourceSpec.js b/test/ResourceSpec.js index e258d5a3..a026263b 100644 --- a/test/ResourceSpec.js +++ b/test/ResourceSpec.js @@ -169,6 +169,7 @@ describe("resource", function() {      var person = Person.get({id:123});      $browser.xhr.flush();      expect(person.name).toEqual('misko'); +    dealoc(scope);    });    it('should return the same object when verifying the cache', function(){ @@ -188,6 +189,7 @@ describe("resource", function() {      $browser.xhr.flush();      expect(person2Cache).toEqual(person2);      expect(person2[0].name).toEqual('rob'); +    dealoc(scope);    });    describe('failure mode', function(){ diff --git a/test/ScenarioSpec.js b/test/ScenarioSpec.js index 730019a2..4a8b5e69 100644 --- a/test/ScenarioSpec.js +++ b/test/ScenarioSpec.js @@ -1,52 +1,64 @@  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]; -    var scope = compile(node); -    scope.$init(); -    expect(scope.a).toEqual(1); -    expect(scope.b).toEqual(2); +  var scope; +   +  beforeEach(function(){ +    scope = null;    }); - -  it("should compile jQuery node and return scope", function(){ -    var scope = compile(jqLite('<div>{{a=123}}</div>')).$init(); -    expect(jqLite(scope.$element).text()).toEqual('123'); +   +  afterEach(function(){ +    dealoc(scope);    }); - -  it("should compile text node and return scope", function(){ -    var scope = compile('<div>{{a=123}}</div>').$init(); -    expect(jqLite(scope.$element).text()).toEqual('123'); +   +  describe('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 = compile(node); +      scope.$init(); +      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>')).$init(); +      expect(jqLite(scope.$element).text()).toEqual('123'); +    }); +     +    it("should compile text node and return scope", function(){ +      scope = compile('<div>{{a=123}}</div>').$init(); +      expect(jqLite(scope.$element).text()).toEqual('123'); +    });    }); -}); - -describe("ScenarioSpec: Scope", function(){ -  it("should have set, get, eval, $init, updateView methods", function(){ -    var scope = compile('<div>{{a}}</div>').$init(); -    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'); +   +  describe('scope', function(){ +    it("should have set, get, eval, $init, updateView methods", function(){ +      scope = compile('<div>{{a}}</div>').$init(); +      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 = compile('<div></div>', {$config: {a:"b"}}); +      expect(scope.$get('$location')).toBeDefined(); +      expect(scope.$get('$eval')).toBeDefined(); +      expect(scope.$get('$config')).toBeDefined(); +      expect(scope.$get('$config.a')).toEqual("b"); +    });    }); - -  it("should have $ objects", function(){ -    var scope = compile('<div></div>', {$config: {a:"b"}}); -    expect(scope.$get('$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"; +      scope = compile("<div>{{$location}}</div>"); +      var $location = scope.$location; +      var $browser = scope.$inject('$browser'); +      expect($location.hashSearch.book).toBeUndefined(); +      $browser.setUrl(url); +      $browser.poll(); +      expect($location.hashSearch.book).toEqual('moby'); +    });    }); -}); - -describe("ScenarioSpec: configuration", function(){ -  it("should take location object", function(){ -    var url = "http://server/#?book=moby"; -    var scope = compile("<div>{{$location}}</div>"); -    var $location = scope.$location; -    var $browser = scope.$inject('$browser'); -    expect($location.hashSearch.book).toBeUndefined(); -    $browser.setUrl(url); -    $browser.poll(); -    expect($location.hashSearch.book).toEqual('moby'); -  }); -}); +});
\ No newline at end of file diff --git a/test/ScopeSpec.js b/test/ScopeSpec.js index 38350b17..acded34b 100644 --- a/test/ScopeSpec.js +++ b/test/ScopeSpec.js @@ -209,28 +209,6 @@ describe('scope/model', function(){      });    }); -  describe('$postEval', function(){ -    it('should eval function once and last', function(){ -      var log = ''; -      var scope = createScope(); -      function onceOnly(){log+= '@';} -      scope.$onEval(function(){log+= '.';}); -      scope.$postEval(function(){log+= '!';}); -      scope.$postEval(onceOnly); -      scope.$postEval(onceOnly); -      scope.$postEval(); // ignore -      scope.$eval(); -      expect(log).toEqual('.!@'); -      scope.$eval(); -      expect(log).toEqual('.!@.'); - -      scope.$postEval(onceOnly); -      scope.$postEval(onceOnly); -      scope.$eval(); -      expect(log).toEqual('.!@..@'); -    }); -  }); -    describe('$new', function(){      it('should $new should create new child scope and $become controller', function(){        var parent = createScope(null, {exampleService: function(){return 'Example Service';}}); diff --git a/test/ValidatorsTest.js b/test/ValidatorsTest.js index cc77b6da..f740c7b2 100644 --- a/test/ValidatorsTest.js +++ b/test/ValidatorsTest.js @@ -104,12 +104,6 @@ describe('Validator:asynchronous', function(){    afterEach(function(){      if (self.$element) self.$element.remove(); -    var oldCache = jqCache; -    jqCache = {}; -    if (size(oldCache)) { -      dump(oldCache); -    } -    expect(size(oldCache)).toEqual(0);    });    it('should make a request and show spinner', function(){ diff --git a/test/directivesSpec.js b/test/directivesSpec.js index b70067ba..ab1813c3 100644 --- a/test/directivesSpec.js +++ b/test/directivesSpec.js @@ -13,8 +13,7 @@ describe("directive", function(){    });    afterEach(function() { -    if (model && model.$element) model.$element.remove(); -    expect(size(jqCache)).toEqual(0); +    dealoc(model);    });    it("should ng:init", function() { diff --git a/test/markupSpec.js b/test/markupSpec.js index cb8ee23f..3234bf2f 100644 --- a/test/markupSpec.js +++ b/test/markupSpec.js @@ -14,8 +14,7 @@ describe("markups", function(){    });    afterEach(function(){ -    if (element) element.remove(); -    expect(size(jqCache)).toEqual(0); +    dealoc(element);    });    it('should translate {{}} in text', function(){ @@ -63,92 +62,91 @@ describe("markups", function(){      compile('<a ng:href="{{url}}" rel="{{rel}}"></a>');      expect(sortedHtml(element)).toEqual('<a ng:bind-attr="{"href":"{{url}}","rel":"{{rel}}"}"></a>');    }); -}); +  it('should Parse Text With No Bindings', function(){ +    var parts = parseBindings("a"); +    assertEquals(parts.length, 1); +    assertEquals(parts[0], "a"); +    assertTrue(!binding(parts[0])); +  }); + +  it('should Parse Empty Text', function(){ +    var parts = parseBindings(""); +    assertEquals(parts.length, 1); +    assertEquals(parts[0], ""); +    assertTrue(!binding(parts[0])); +  }); + +  it('should Parse Inner Binding', function(){ +    var parts = parseBindings("a{{b}}C"); +    assertEquals(parts.length, 3); +    assertEquals(parts[0], "a"); +    assertTrue(!binding(parts[0])); +    assertEquals(parts[1], "{{b}}"); +    assertEquals(binding(parts[1]), "b"); +    assertEquals(parts[2], "C"); +    assertTrue(!binding(parts[2])); +  }); + +  it('should Parse Ending Binding', function(){ +    var parts = parseBindings("a{{b}}"); +    assertEquals(parts.length, 2); +    assertEquals(parts[0], "a"); +    assertTrue(!binding(parts[0])); +    assertEquals(parts[1], "{{b}}"); +    assertEquals(binding(parts[1]), "b"); +  }); + +  it('should Parse Begging Binding', function(){ +    var parts = parseBindings("{{b}}c"); +    assertEquals(parts.length, 2); +    assertEquals(parts[0], "{{b}}"); +    assertEquals(binding(parts[0]), "b"); +    assertEquals(parts[1], "c"); +    assertTrue(!binding(parts[1])); +  }); + +  it('should Parse Loan Binding', function(){ +    var parts = parseBindings("{{b}}"); +    assertEquals(parts.length, 1); +    assertEquals(parts[0], "{{b}}"); +    assertEquals(binding(parts[0]), "b"); +  }); + +  it('should Parse Two Bindings', function(){ +    var parts = parseBindings("{{b}}{{c}}"); +    assertEquals(parts.length, 2); +    assertEquals(parts[0], "{{b}}"); +    assertEquals(binding(parts[0]), "b"); +    assertEquals(parts[1], "{{c}}"); +    assertEquals(binding(parts[1]), "c"); +  }); + +  it('should Parse Two Bindings With Text In Middle', function(){ +    var parts = parseBindings("{{b}}x{{c}}"); +    assertEquals(parts.length, 3); +    assertEquals(parts[0], "{{b}}"); +    assertEquals(binding(parts[0]), "b"); +    assertEquals(parts[1], "x"); +    assertTrue(!binding(parts[1])); +    assertEquals(parts[2], "{{c}}"); +    assertEquals(binding(parts[2]), "c"); +  }); + +  it('should Parse Multiline', function(){ +    var parts = parseBindings('"X\nY{{A\nB}}C\nD"'); +    assertTrue(!!binding('{{A\nB}}')); +    assertEquals(parts.length, 3); +    assertEquals(parts[0], '"X\nY'); +    assertEquals(parts[1], '{{A\nB}}'); +    assertEquals(parts[2], 'C\nD"'); +  }); + +  it('should Has Binding', function(){ +    assertTrue(hasBindings(parseBindings("{{a}}"))); +    assertTrue(!hasBindings(parseBindings("a"))); +    assertTrue(hasBindings(parseBindings("{{b}}x{{c}}"))); +  }); +   +}); -var BindingMarkupTest = TestCase("BindingMarkupTest"); - -BindingMarkupTest.prototype.testParseTextWithNoBindings = function(){ -  var parts = parseBindings("a"); -  assertEquals(parts.length, 1); -  assertEquals(parts[0], "a"); -  assertTrue(!binding(parts[0])); -}; - -BindingMarkupTest.prototype.testParseEmptyText = function(){ -  var parts = parseBindings(""); -  assertEquals(parts.length, 1); -  assertEquals(parts[0], ""); -  assertTrue(!binding(parts[0])); -}; - -BindingMarkupTest.prototype.testParseInnerBinding = function(){ -  var parts = parseBindings("a{{b}}c"); -  assertEquals(parts.length, 3); -  assertEquals(parts[0], "a"); -  assertTrue(!binding(parts[0])); -  assertEquals(parts[1], "{{b}}"); -  assertEquals(binding(parts[1]), "b"); -  assertEquals(parts[2], "c"); -  assertTrue(!binding(parts[2])); -}; - -BindingMarkupTest.prototype.testParseEndingBinding = function(){ -  var parts = parseBindings("a{{b}}"); -  assertEquals(parts.length, 2); -  assertEquals(parts[0], "a"); -  assertTrue(!binding(parts[0])); -  assertEquals(parts[1], "{{b}}"); -  assertEquals(binding(parts[1]), "b"); -}; - -BindingMarkupTest.prototype.testParseBeggingBinding = function(){ -  var parts = parseBindings("{{b}}c"); -  assertEquals(parts.length, 2); -  assertEquals(parts[0], "{{b}}"); -  assertEquals(binding(parts[0]), "b"); -  assertEquals(parts[1], "c"); -  assertTrue(!binding(parts[1])); -}; - -BindingMarkupTest.prototype.testParseLoanBinding = function(){ -  var parts = parseBindings("{{b}}"); -  assertEquals(parts.length, 1); -  assertEquals(parts[0], "{{b}}"); -  assertEquals(binding(parts[0]), "b"); -}; - -BindingMarkupTest.prototype.testParseTwoBindings = function(){ -  var parts = parseBindings("{{b}}{{c}}"); -  assertEquals(parts.length, 2); -  assertEquals(parts[0], "{{b}}"); -  assertEquals(binding(parts[0]), "b"); -  assertEquals(parts[1], "{{c}}"); -  assertEquals(binding(parts[1]), "c"); -}; - -BindingMarkupTest.prototype.testParseTwoBindingsWithTextInMiddle = function(){ -  var parts = parseBindings("{{b}}x{{c}}"); -  assertEquals(parts.length, 3); -  assertEquals(parts[0], "{{b}}"); -  assertEquals(binding(parts[0]), "b"); -  assertEquals(parts[1], "x"); -  assertTrue(!binding(parts[1])); -  assertEquals(parts[2], "{{c}}"); -  assertEquals(binding(parts[2]), "c"); -}; - -BindingMarkupTest.prototype.testParseMultiline = function(){ -  var parts = parseBindings('"X\nY{{A\nB}}C\nD"'); -  assertTrue(!!binding('{{A\nB}}')); -  assertEquals(parts.length, 3); -  assertEquals(parts[0], '"X\nY'); -  assertEquals(parts[1], '{{A\nB}}'); -  assertEquals(parts[2], 'C\nD"'); -}; - -BindingMarkupTest.prototype.testHasBinding = function(){ -  assertTrue(hasBindings(parseBindings("{{a}}"))); -  assertTrue(!hasBindings(parseBindings("a"))); -  assertTrue(hasBindings(parseBindings("{{b}}x{{c}}"))); -}; diff --git a/test/servicesSpec.js b/test/servicesSpec.js index 13e61b18..ff90e0a1 100644 --- a/test/servicesSpec.js +++ b/test/servicesSpec.js @@ -17,8 +17,7 @@ describe("service", function(){    });    afterEach(function(){ -    if (scope && scope.$element) -      scope.$element.remove(); +    dealoc(scope);    }); @@ -202,7 +201,7 @@ describe("service", function(){      });      it('should update hash before any processing', function(){ -      var scope = compile('<div>'); +      scope = compile('<div>');        var log = '';        scope.$watch('$location.hash', function(){          log += this.$location.hashPath + ';'; @@ -259,7 +258,7 @@ describe("service", function(){    describe("$invalidWidgets", function(){      it("should count number of invalid widgets", function(){ -      var scope = compile('<input name="price" ng:required ng:validate="number"></input>'); +      scope = compile('<input name="price" ng:required ng:validate="number"></input>');        jqLite(document.body).append(scope.$element);        scope.$init();        expect(scope.$invalidWidgets.length).toEqual(1); @@ -291,8 +290,8 @@ describe("service", function(){        function BookChapter() {          this.log = '<init>';        } -      var scope = compile('<div></div>').$init(); -      var $route = scope.$inject('$route'); +      scope = compile('<div></div>').$init(); +      $route = scope.$inject('$route');        $route.when('/Book/:book/Chapter/:chapter', {controller: BookChapter, template:'Chapter.html'});        $route.when('/Blank');        $route.onChange(function(){ diff --git a/test/testabilityPatch.js b/test/testabilityPatch.js index e8041ac7..ea5c8ab7 100644 --- a/test/testabilityPatch.js +++ b/test/testabilityPatch.js @@ -50,6 +50,25 @@ beforeEach(function(){    });  }); +afterEach(clearJqCache); + +function clearJqCache(){ +  var count = 0; +  foreachSorted(jqCache, function(value, key){ +    count ++; +    delete jqCache[key]; +    foreach(value, function(value, key){ +      if (value.$element)  +        dump(key, sortedHtml(value.$element)); +      else  +        dump(key, toJson(value)); +    }); +  }); +  if (count) { +    fail('Found jqCache references that were not deallocated!'); +  } +} +  function nakedExpect(obj) {    return expect(angular.fromJson(angular.toJson(obj)));  } @@ -58,6 +77,11 @@ function childNode(element, index) {    return jqLite(element[0].childNodes[index]);  } +function dealoc(obj) { +  var element = (obj||{}).$element || obj; +  if (element && element.dealoc) element.dealoc(); +} +  extend(angular, {    'element': jqLite,    'compile': compile, diff --git a/test/widgetsSpec.js b/test/widgetsSpec.js index d3957e66..cb3b76a1 100644 --- a/test/widgetsSpec.js +++ b/test/widgetsSpec.js @@ -16,8 +16,7 @@ describe("widget", function(){    });    afterEach(function(){ -    if (element && element.dealoc) element.dealoc(); -    expect(size(jqCache)).toEqual(0); +    dealoc(element);    });    describe("input", function(){ @@ -362,7 +361,7 @@ describe("widget", function(){                '<option value="{{$index}}" ng:repeat="name in [\'A\', \'B\', \'C\']">{{name}}</option>' +              '</select>');          // childNodes[0] is repeater comment -        expect(scope.selection).toEqual(undefined); +        expect(scope.selection).toEqual(0);          browserTrigger(element[0].childNodes[2], 'change');          expect(scope.selection).toEqual(1); @@ -398,6 +397,32 @@ describe("widget", function(){          scope.$eval();          expect(element[0].childNodes[1].selected).toEqual(true);        }); +       +      it('should select default option on repeater', function(){ +        compile( +            '<select name="selection">' + +              '<option ng:repeat="no in [1,2]">{{no}}</option>' + +            '</select>'); +        expect(scope.selection).toEqual('1'); +      }); +       +      it('should select selected option on repeater', function(){ +        compile( +            '<select name="selection">' + +              '<option ng:repeat="no in [1,2]">{{no}}</option>' + +              '<option selected>ABC</option>' + +            '</select>'); +        expect(scope.selection).toEqual('ABC'); +      }); +       +      it('should select dynamically selected option on repeater', function(){ +        compile( +            '<select name="selection">' + +              '<option ng:repeat="no in [1,2]" ng:bind-attr="{selected:\'{{no==2}}\'}">{{no}}</option>' + +            '</select>'); +        expect(scope.selection).toEqual('2'); +      }); +            });      it('should support type="select-multiple"', function(){ @@ -476,6 +501,7 @@ describe("widget", function(){        scope.url = '/Book/Moby';        scope.$init();        expect(scope.$element.text()).toEqual('Moby'); +      dealoc(scope);      });      it("should match sandwich ids", function(){ @@ -491,6 +517,7 @@ describe("widget", function(){        scope.$init();        expect(scope.name).toEqual(undefined);        expect(scope.$element.text()).toEqual('works'); +      dealoc(scope);      });    }); @@ -504,6 +531,7 @@ describe("widget", function(){        scope.$inject('$xhr.cache').data.myUrl = {value:'{{name}}'};        scope.$init();        expect(element.text()).toEqual('misko'); +      dealoc(scope);      });      it('should remove previously included text if a falsy value is bound to src', function() { @@ -521,6 +549,7 @@ describe("widget", function(){        scope.$eval();        expect(element.text()).toEqual(''); +      dealoc(scope);      });      it('should allow this for scope', function(){ @@ -532,6 +561,7 @@ describe("widget", function(){        // This should not be 4, but to fix this properly        // we need to have real events on the scopes.        expect(element.text()).toEqual('4'); +      dealoc(scope);      });      it('should evaluate onload expression when a partial is loaded', function() { @@ -545,6 +575,7 @@ describe("widget", function(){        scope.$init();        expect(element.text()).toEqual('my partial');        expect(scope.loaded).toBe(true); +      dealoc(scope);      });    }); | 
