diff options
| author | Misko Hevery | 2011-02-10 11:20:49 -0800 | 
|---|---|---|
| committer | Misko Hevery | 2011-02-16 08:59:42 -0500 | 
| commit | 00cc9eb32a9387040d0175fcfd21cf9dcab6514f (patch) | |
| tree | 0edaac339a3ec69ff769e20b28b6ebe51d040272 | |
| parent | ef4bb28be13e99f96c9ace5936cf26a174a0e5f0 (diff) | |
| download | angular.js-00cc9eb32a9387040d0175fcfd21cf9dcab6514f.tar.bz2 | |
rewrite of JQuery lite implementation, which now better supports selected sets
| -rw-r--r-- | CHANGELOG.md | 4 | ||||
| -rw-r--r-- | src/Angular.js | 32 | ||||
| -rw-r--r-- | src/Compiler.js | 5 | ||||
| -rw-r--r-- | src/jqLite.js | 461 | ||||
| -rw-r--r-- | src/scenario/Scenario.js | 6 | ||||
| -rw-r--r-- | test/jqLiteSpec.js | 283 | ||||
| -rw-r--r-- | test/testabilityPatch.js | 9 | 
7 files changed, 601 insertions, 199 deletions
| diff --git a/CHANGELOG.md b/CHANGELOG.md index 04d511d0..579dd61b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@  <a name="0.9.12"><a/>  # <angular/> 0.9.12 thought-implanter (in-progress) # +### API +- rewrite of JQuery lite implementation for better supports operations on multiple nodes when +  matched by a selector. +  ### Breaking changes  - Removed the $init() method after the compilation. The old way of compiling the DOM element was    angular.compile(element).$init(); The $init was there to allow the users to do any work to the diff --git a/src/Angular.js b/src/Angular.js index 9eaeb093..99a4528d 100644 --- a/src/Angular.js +++ b/src/Angular.js @@ -268,24 +268,6 @@ function extensionMap(angular, name, transform) {    });  } -function jqLiteWrap(element) { -  // for some reasons the parentNode of an orphan looks like _null but its typeof is object. -  if (element) { -    if (isString(element)) { -      var div = document.createElement('div'); -      // Read about the NoScope elements here: -      // http://msdn.microsoft.com/en-us/library/ms533897(VS.85).aspx -      div.innerHTML = '<div> </div>' + element; // IE insanity to make NoScope elements work! -      div.removeChild(div.firstChild); // remove the superfluous div -      element = new JQLite(div.childNodes); -    } else if (!(element instanceof JQLite)) { -      element =  new JQLite(element); -    } -  } -  return element; -} - -  /**   * @workInProgress   * @ngdoc function @@ -422,7 +404,9 @@ function isBoolean(value) { return typeof value == $boolean;}  function isTextNode(node) { return nodeName_(node) == '#text'; }  function trim(value) { return isString(value) ? value.replace(/^\s*/, '').replace(/\s*$/, '') : value; }  function isElement(node) { -  return node && (node.nodeName || node instanceof JQLite || (jQuery && node instanceof jQuery)); +  return node && +    (node.nodeName  // we are a direct element +    || (node.bind && node.find));  // we have a bind and find method part of jQuery API  }  /** @@ -1057,11 +1041,11 @@ function bindJQuery(){    // bind to jQuery if present;    jQuery = window.jQuery;    // reset to jQuery or default to us. -  if (window.jQuery) { -    jqLite = window.jQuery; -    extend(jqLite.fn, { -      scope: JQLite.prototype.scope, -      cloneNode: cloneNode +  if (jQuery) { +    jqLite = jQuery; +    extend(jQuery.fn, { +      scope: JQLitePrototype.scope, +      cloneNode: JQLitePrototype.cloneNode      });    } else {      jqLite = jqLiteWrap; diff --git a/src/Compiler.js b/src/Compiler.js index 890f2510..77c83846 100644 --- a/src/Compiler.js +++ b/src/Compiler.js @@ -26,7 +26,6 @@ Template.prototype = {      if (!queue) {        inits[this.priority] = queue = [];      } -    element = jqLite(element);      if (this.newScope) {        childScope = createScope(scope);        scope.$onEval(childScope.$eval); @@ -45,7 +44,7 @@ Template.prototype = {          paths = this.paths,          length = paths.length;      for (i = 0; i < length; i++) { -      children[i].collectInits(childNodes[paths[i]], inits, childScope); +      children[i].collectInits(jqLite(childNodes[paths[i]]), inits, childScope);      }    }, @@ -98,7 +97,7 @@ Compiler.prototype = {        scope = scope || createScope();        element = element === true          ? templateElement.cloneNode() -        : (jqLite(element) || templateElement); +        : (element ? jqLite(element) : templateElement);        element.data($$scope, scope);        template.attach(element, scope);        scope.$element = element; diff --git a/src/jqLite.js b/src/jqLite.js index 01a563b6..40994161 100644 --- a/src/jqLite.js +++ b/src/jqLite.js @@ -14,20 +14,6 @@ var jqCache = {},  function jqNextId() { return (jqId++); } -function jqClearData(element) { -  var cacheId = element[jqName], -      cache = jqCache[cacheId]; -  if (cache) { -    forEach(cache.bind || {}, function(fn, type){ -      removeEventListenerFn(element, type, fn); -    }); -    delete jqCache[cacheId]; -    if (msie) -      element[jqName] = ''; // ie does not allow deletion of attributes on elements. -    else -      delete element[jqName]; -  } -}  function getStyle(element) {    var current = {}, style = element[0].style, value, name, i; @@ -46,56 +32,120 @@ function getStyle(element) {    return current;  } -function JQLite(element) { -  if (!isElement(element) && isDefined(element.length) && element.item && !isWindow(element)) { -    for(var i=0; i < element.length; i++) { -      this[i] = element[i]; +if (msie) { +  extend(JQLite.prototype, { +    text: function(value) { +      var e = this[0]; +      // NodeType == 3 is text node +      if (e.nodeType == 3) { +        if (isDefined(value)) e.nodeValue = value; +        return e.nodeValue; +      } else { +        if (isDefined(value)) e.innerText = value; +        return e.innerText; +      }      } -    this.length = element.length; +  }); +} + +///////////////////////////////////////////// +function jqLiteWrap(element) { +  if (isString(element) && element.charAt(0) != '<') { +    throw new Error('selectors not implemented'); +  } +  return new JQLite(element); +} + +function JQLite(element) { +  if (element instanceof JQLite) { +    return element; +  } else if (isString(element)) { +    var div = document.createElement('div'); +    // Read about the NoScope elements here: +    // http://msdn.microsoft.com/en-us/library/ms533897(VS.85).aspx +    div.innerHTML = '<div> </div>' + element; // IE insanity to make NoScope elements work! +    div.removeChild(div.firstChild); // remove the superfluous div +    JQLiteAddNodes(this, div.childNodes); +    this.remove(); // detach the elements form the temporary DOM div.    } else { -    this[0] = element; -    this.length = 1; +    JQLiteAddNodes(this, element);    }  } -JQLite.prototype = { -  data: function(key, value) { -    var element = this[0], -        cacheId = element[jqName], -        cache = jqCache[cacheId || -1]; -    if (isDefined(value)) { -      if (!cache) { -        element[jqName] = cacheId = jqNextId(); -        cache = jqCache[cacheId] = {}; -      } -      cache[key] = value; -    } else { -      return cache ? cache[key] : _null; -    } -  }, +function JQLiteCloneNode(element) { +  return element.cloneNode(true); +} -  removeData: function(){ -    jqClearData(this[0]); -  }, +function JQLiteDealoc(element){ +  JQLiteRemoveData(element); +  for ( var i = 0, children = element.childNodes || []; i < children.length; i++) { +    JQLiteDealoc(children[i]); +  } +} -  dealoc: function(){ -    (function dealoc(element){ -      jqClearData(element); -      for ( var i = 0, children = element.childNodes || []; i < children.length; i++) { -        dealoc(children[i]); -      } -    })(this[0]); -  }, +function JQLiteRemoveData(element) { +  var cacheId = element[jqName], +  cache = jqCache[cacheId]; +  if (cache) { +    forEach(cache.bind || {}, function(fn, type){ +      removeEventListenerFn(element, type, fn); +    }); +    delete jqCache[cacheId]; +    element[jqName] = undefined; // ie does not allow deletion of attributes on elements. +  } +} -  scope: function() { -    var scope, element = this; -    while (element && element.length && !(scope = element.data($$scope))) { -      element = element.parent(); +function JQLiteData(element, key, value) { +  var cacheId = element[jqName], +      cache = jqCache[cacheId || -1]; +  if (isDefined(value)) { +    if (!cache) { +      element[jqName] = cacheId = jqNextId(); +      cache = jqCache[cacheId] = {};      } -    return scope; -  }, +    cache[key] = value; +  } else { +    return cache ? cache[key] : _null; +  } +} + +function JQLiteHasClass(element, selector, _) { +  // the argument '_' is important, since it makes the function have 3 arguments, which +  // is neede for delegate function to realize the this is a getter. +  var className = " " + selector + " "; +  return ((" " + element.className + " ").replace(/[\n\t]/g, " ").indexOf( className ) > -1); +} + +function JQLiteRemoveClass(element, selector) { +  element.className = trim( +      (" " + element.className + " ") +      .replace(/[\n\t]/g, " ") +      .replace(" " + selector + " ", "") +  ); +} +function JQLiteAddClass(element, selector ) { +  if (!JQLiteHasClass(element, selector)) { +    element.className = trim(element.className + ' ' + selector); +  } +} + +function JQLiteAddNodes(root, elements) { +  if (elements) { +    elements = (!elements.nodeName && isDefined(elements.length)) +      ? elements +      : [ elements ]; +    for(var i=0; i < elements.length; i++) { +      if (elements[i].nodeType != 11) +        root.push(elements[i]); +    } +  } +} +////////////////////////////////////////// +// Functions which are declared directly. +////////////////////////////////////////// +var JQLitePrototype = JQLite.prototype = extend([], {    ready: function(fn) {      var fired = false; @@ -107,15 +157,137 @@ JQLite.prototype = {      this.bind('DOMContentLoaded', trigger); // works for modern browsers and IE9      // we can not use jqLite since we are not done loading and jQuery could be loaded later. -    new JQLite(window).bind('load', trigger); // fallback to window.onload for others +    jqLiteWrap(window).bind('load', trigger); // fallback to window.onload for others +  }, +  toString: function(){ +    var value = []; +    forEach(this, function(e){ value.push('' + e);}); +    return '[' + value.join(', ') + ']'; +  } +}); + +////////////////////////////////////////// +// Functions iterating getter/setters. +// these functions return self on setter and +// value on get. +////////////////////////////////////////// +forEach({ +  data: JQLiteData, + +  scope: function(element) { +    var scope; +    while (element && !(scope = jqLite(element).data($$scope))) { +      element = element.parentNode; +    } +    return scope;    }, -  bind: function(type, fn){ -    var self = this, -        element = self[0], -        bind = self.data('bind'), +  removeAttr: function(element,name) { +    element.removeAttribute(name); +  }, + +  hasClass: JQLiteHasClass, + +  css: function(element, name, value) { +    if (isDefined(value)) { +      element.style[name] = value; +    } else { +      return element.style[name]; +    } +  }, + +  attr: function(element, name, value){ +    if (isDefined(value)) { +      element.setAttribute(name, value); +    } else if (element.getAttribute) { +      // the extra argument "2" is to get the right thing for a.href in IE, see jQuery code +      // some elements (e.g. Document) don't have get attribute, so return undefined +      return element.getAttribute(name, 2); +    } +  }, + +  text: extend(msie +      ? function(element, value) { +        // NodeType == 3 is text node +        if (element.nodeType == 3) { +          if (isUndefined(value)) +            return element.nodeValue; +          element.nodeValue = value; +        } else { +          if (isUndefined(value)) +            return element.innerText; +          element.innerText = value; +        } +      } +      : function(element, value) { +        if (isUndefined(value)) { +          return element.textContent; +        } +        element.textContent = value; +      }, {$dv:''}), + +  val: function(element, value) { +    if (isUndefined(value)) { +      return element.value; +    } +    element.value = value; +  }, + +  html: function(element, value) { +    if (isUndefined(value)) { +      return element.innerHTML; +    } +    for (var i = 0, childNodes = element.childNodes; i < childNodes.length; i++) { +      JQLiteDealoc(childNodes[i]); +    } +    element.innerHTML = value; +  } +}, function(fn, name){ +  /** +   * Properties: writes return selection, reads return first value +   */ +  JQLite.prototype[name] = function(arg1, arg2) { +    if ((fn.length == 2 ? arg1 : arg2) === undefined) { +      if (isObject(arg1)) { +        // we are a write, but the object properties are the key/values +        for(var i=0; i < this.length; i++) { +          for ( var key in arg1) { +            fn(this[i], key, arg1[key]); +          } +        } +        // return self for chaining +        return this; +      } else { +        // we are a read, so read the first child. +        if (this.length) +          return fn(this[0], arg1, arg2); +      } +    } else { +      // we are a write, so apply to all children +      for(var i=0; i < this.length; i++) { +        fn(this[i], arg1, arg2); +      } +      // return self for chaining +      return this; +    } +    return fn.$dv; +  }; +}); + +////////////////////////////////////////// +// Functions iterating traversal. +// These functions chain results into a single +// selector. +////////////////////////////////////////// +forEach({ +  removeData: JQLiteRemoveData, + +  dealoc: JQLiteDealoc, + +  bind: function(element, type, fn){ +    var bind = JQLiteData(element, 'bind'),          eventHandler; -    if (!bind) this.data('bind', bind = {}); +    if (!bind) JQLiteData(element, 'bind', bind = {});      forEach(type.split(' '), function(type){        eventHandler = bind[type];        if (!eventHandler) { @@ -131,7 +303,7 @@ JQLite.prototype = {              };            }            forEach(eventHandler.fns, function(fn){ -            fn.call(self, event); +            fn.call(element, event);            });          };          eventHandler.fns = []; @@ -141,133 +313,86 @@ JQLite.prototype = {      });    }, -  replaceWith: function(replaceNode) { -    this[0].parentNode.replaceChild(jqLite(replaceNode)[0], this[0]); -  }, - -  children: function() { -    return new JQLite(this[0].childNodes); -  }, - -  append: function(node) { -    var self = this[0]; -    node = jqLite(node); -    forEach(node, function(child){ -      self.appendChild(child); +  replaceWith: function(element, replaceNode) { +    var index, parent = element.parentNode; +    JQLiteDealoc(element); +    forEach(new JQLite(replaceNode), function(node){ +      if (index) { +        parent.insertBefore(node, index.nextSibling); +      } else { +        parent.replaceChild(node, element); +      } +      index = node;      });    }, -  remove: function() { -    this.dealoc(); -    var parentNode = this[0].parentNode; -    if (parentNode) parentNode.removeChild(this[0]); -  }, - -  removeAttr: function(name) { -    this[0].removeAttribute(name); -  }, - -  after: function(element) { -    this[0].parentNode.insertBefore(jqLite(element)[0], this[0].nextSibling); -  }, - -  hasClass: function(selector) { -    var className = " " + selector + " "; -    if ( (" " + this[0].className + " ").replace(/[\n\t]/g, " ").indexOf( className ) > -1 ) { -      return true; -    } -    return false; -  }, - -  removeClass: function(selector) { -    this[0].className = trim((" " + this[0].className + " ").replace(/[\n\t]/g, " ").replace(" " + selector + " ", "")); +  children: function(element) { +    var children = []; +    forEach(element.childNodes, function(element){ +      if (element.nodeName != '#text') +        children.push(element); +    }); +    return children;    }, -  toggleClass: function(selector, condition) { -   var self = this; -   (condition ? self.addClass : self.removeClass).call(self, selector); +  append: function(element, node) { +    forEach(new JQLite(node), function(child){ +      element.appendChild(child); +    });    }, -  addClass: function( selector ) { -    if (!this.hasClass(selector)) { -      this[0].className = trim(this[0].className + ' ' + selector); -    } +  remove: function(element) { +    JQLiteDealoc(element); +    var parent = element.parentNode; +    if (parent) parent.removeChild(element);    }, -  css: function(name, value) { -    var style = this[0].style; -    if (isString(name)) { -      if (isDefined(value)) { -        style[name] = value; -      } else { -        return style[name]; -      } -    } else { -      extend(style, name); -    } +  after: function(element, newElement) { +    var index = element, parent = element.parentNode; +    forEach(new JQLite(newElement), function(node){ +      parent.insertBefore(node, index.nextSibling); +      index = node; +    });    }, -  attr: function(name, value){ -    var e = this[0]; -    if (isObject(name)) { -      forEach(name, function(value, name){ -        e.setAttribute(name, value); -      }); -    } else if (isDefined(value)) { -      e.setAttribute(name, value); -    } else { -      // the extra argument "2" is to get the right thing for a.href in IE, see jQuery code -      // some elements (e.g. Document) don't have get attribute, so return undefined -      if (e.getAttribute) return e.getAttribute(name, 2); -    } -  }, +  addClass: JQLiteAddClass, +  removeClass: JQLiteRemoveClass, -  text: function(value) { -    if (isDefined(value)) { -      this[0].textContent = value; +  toggleClass: function(element, selector, condition) { +    if (isUndefined(condition)) { +      condition = !JQLiteHasClass(element, selector);      } -    return this[0].textContent; +    (condition ? JQLiteAddClass : JQLiteRemoveClass)(element, selector);    }, -  val: function(value) { -    if (isDefined(value)) { -      this[0].value = value; -    } -    return this[0].value; +  parent: function(element) { +    // in IE it returns undefined, but we need differentiate it from functions which have no return +    return element.parentNode || null;    }, -  html: function(value) { -    if (isDefined(value)) { -      var i = 0, childNodes = this[0].childNodes; -      for ( ; i < childNodes.length; i++) { -        jqLite(childNodes[i]).dealoc(); -      } -      this[0].innerHTML = value; -    } -    return this[0].innerHTML; +  find: function(element, selector) { +    return element.getElementsByTagName(selector);    }, -  parent: function() { -    return jqLite(this[0].parentNode); -  }, - -  clone: cloneNode, -  cloneNode: cloneNode -}; -function cloneNode() { return jqLite(this[0].cloneNode(true)); } - -if (msie) { -  extend(JQLite.prototype, { -    text: function(value) { -      var e = this[0]; -      // NodeType == 3 is text node -      if (e.nodeType == 3) { -        if (isDefined(value)) e.nodeValue = value; -        return e.nodeValue; +  clone: JQLiteCloneNode, +  cloneNode: JQLiteCloneNode +}, function(fn, name){ +  /** +   * chaining functions +   */ +  JQLite.prototype[name] = function(arg1, arg2) { +    var value; +    for(var i=0; i < this.length; i++) { +      if (value == undefined) { +        value = fn(this[i], arg1, arg2); +        if (value !== undefined) { +          // any function which returns a value needs to be wrapped +          value = jqLite(value); +        }        } else { -        if (isDefined(value)) e.innerText = value; -        return e.innerText; +        JQLiteAddNodes(value, fn(this[i], arg1, arg2));        }      } -  }); -} +    return value == undefined ? this : value; +  }; +}); diff --git a/src/scenario/Scenario.js b/src/scenario/Scenario.js index d1f1eb33..81c85a61 100644 --- a/src/scenario/Scenario.js +++ b/src/scenario/Scenario.js @@ -256,6 +256,12 @@ function browserTrigger(element, type) {          element.checked = !element.checked;          break;      } +    // WTF!!! Error: Unspecified error. +    // Don't know why, but some elements when detached seem to be in inconsistent state and +    // calling .fireEvent() on them will result in very unhelpful error (Error: Unspecified error) +    // forcing the browser to compute the element position (by reading its CSS) +    // puts the element in consistent state. +    element.style.posLeft;      element.fireEvent('on' + type);      if (lowercase(element.type) == 'submit') {        while(element) { diff --git a/test/jqLiteSpec.js b/test/jqLiteSpec.js index da8ab206..c5e92c0f 100644 --- a/test/jqLiteSpec.js +++ b/test/jqLiteSpec.js @@ -1,8 +1,52 @@  describe('jqLite', function(){    var scope; +  var a, b, c; +  beforeEach(function(){ +    a = jqLite('<div>A</div>')[0]; +    b = jqLite('<div>B</div>')[0]; +    c = jqLite('<div>C</div>')[0]; +  });    beforeEach(function(){      scope = angular.scope(); +    this.addMatchers({ +      toJqEqual: function(expected) { +        var msg = "Unequal length"; +        this.message = function() { return msg; }; + +        var value = this.actual && expected && this.actual.length == expected.length; +        for (var i = 0; value && i < expected.length; i++) { +          var actual = jqLite(this.actual[i])[0]; +          var expect = jqLite(expected[i])[0]; +          value = value && equals(expect, actual); +          msg = "Not equal at index: " + i +              + " - Expected: " + expect +              + " - Actual: " + actual; +        } +        return value; +      } +    }); +  }); + +  afterEach(function(){ +    dealoc(a); +    dealoc(b); +    dealoc(c); +  }); + +  describe('construction', function(){ +    it('should allow construction with text node', function(){ +      var text = a.firstChild; +      var selected = jqLite(text); +      expect(selected.length).toEqual(1); +      expect(selected[0]).toEqual(text); +    }); +    it('should allow construction with html', function(){ +      var nodes = jqLite('<div>1</div><span>2</span>'); +      expect(nodes.length).toEqual(2); +      expect(nodes[0].innerHTML).toEqual('1'); +      expect(nodes[1].innerHTML).toEqual('2'); +    });    });    describe('scope', function() { @@ -24,8 +68,245 @@ describe('jqLite', function(){      it('should return undefined when no scope was found', function() {        var element = jqLite('<ul><li><p><b>deep deep</b><p></li></ul>');        var deepChild = jqLite(element[0].getElementsByTagName('b')[0]); -      expect(deepChild.scope()).toBeNull(); +      expect(deepChild.scope()).toBeFalsy();        dealoc(element);      });    }); + +  describe('data', function(){ +    it('should set and get ande remove data', function(){ +      var selected = jqLite([a, b, c]); + +      expect(selected.data('prop', 'value')).toEqual(selected); +      expect(selected.data('prop')).toEqual('value'); +      expect(jqLite(a).data('prop')).toEqual('value'); +      expect(jqLite(b).data('prop')).toEqual('value'); +      expect(jqLite(c).data('prop')).toEqual('value'); + +      jqLite(a).data('prop', 'new value'); +      expect(jqLite(a).data('prop')).toEqual('new value'); +      expect(selected.data('prop')).toEqual('new value'); +      expect(jqLite(b).data('prop')).toEqual('value'); +      expect(jqLite(c).data('prop')).toEqual('value'); + +      expect(selected.removeData('prop')).toEqual(selected); +      expect(jqLite(a).data('prop')).toEqual(undefined); +      expect(jqLite(b).data('prop')).toEqual(undefined); +      expect(jqLite(c).data('prop')).toEqual(undefined); +    }); +  }); + +  describe('attr', function(){ +    it('shoul read wirite and remove attr', function(){ +      var selector = jqLite([a, b]); + +      expect(selector.attr('prop', 'value')).toEqual(selector); +      expect(jqLite(a).attr('prop')).toEqual('value'); +      expect(jqLite(b).attr('prop')).toEqual('value'); + +      expect(selector.attr({'prop': 'new value'})).toEqual(selector); +      expect(jqLite(a).attr('prop')).toEqual('new value'); +      expect(jqLite(b).attr('prop')).toEqual('new value'); + +      jqLite(b).attr({'prop': 'new value 2'}); +      expect(jqLite(selector).attr('prop')).toEqual('new value'); +      expect(jqLite(b).attr('prop')).toEqual('new value 2'); + +      selector.removeAttr('prop'); +      expect(jqLite(a).attr('prop')).toBeFalsy(); +      expect(jqLite(b).attr('prop')).toBeFalsy(); +    }); +  }); +  describe('class', function(){ +    describe('hasClass', function(){ +      it('should check class', function(){ +        var selector = jqLite([a, b]); +        expect(selector.hasClass('abc')).toEqual(false); +      }); +    }); +    describe('addClass', function(){ +      it('should allow adding of class', function(){ +        var selector = jqLite([a, b]); +        expect(selector.addClass('abc')).toEqual(selector); +        expect(jqLite(a).hasClass('abc')).toEqual(true); +        expect(jqLite(b).hasClass('abc')).toEqual(true); +      }); +    }); +    describe('toggleClass', function(){ +      it('should allow toggling of class', function(){ +        var selector = jqLite([a, b]); +        expect(selector.toggleClass('abc')).toEqual(selector); +        expect(jqLite(a).hasClass('abc')).toEqual(true); +        expect(jqLite(b).hasClass('abc')).toEqual(true); + +        expect(selector.toggleClass('abc')).toEqual(selector); +        expect(jqLite(a).hasClass('abc')).toEqual(false); +        expect(jqLite(b).hasClass('abc')).toEqual(false); + +        expect(selector.toggleClass('abc'), true).toEqual(selector); +        expect(jqLite(a).hasClass('abc')).toEqual(true); +        expect(jqLite(b).hasClass('abc')).toEqual(true); + +        expect(selector.toggleClass('abc'), false).toEqual(selector); +        expect(jqLite(a).hasClass('abc')).toEqual(false); +        expect(jqLite(b).hasClass('abc')).toEqual(false); + +      }); +    }); +    describe('removeClass', function(){ +      it('should allow removal of class', function(){ +        var selector = jqLite([a, b]); +        expect(selector.addClass('abc')).toEqual(selector); +        expect(selector.removeClass('abc')).toEqual(selector); +        expect(jqLite(a).hasClass('abc')).toEqual(false); +        expect(jqLite(b).hasClass('abc')).toEqual(false); +      }); +    }); +  }); +  describe('css', function(){ +    it('should set and read css', function(){ +      var selector = jqLite([a, b]); + +      expect(selector.css('prop', 'value')).toEqual(selector); +      expect(jqLite(a).css('prop')).toEqual('value'); +      expect(jqLite(b).css('prop')).toEqual('value'); + +      expect(selector.css({'prop': 'new value'})).toEqual(selector); +      expect(jqLite(a).css('prop')).toEqual('new value'); +      expect(jqLite(b).css('prop')).toEqual('new value'); + +      jqLite(b).css({'prop': 'new value 2'}); +      expect(jqLite(selector).css('prop')).toEqual('new value'); +      expect(jqLite(b).css('prop')).toEqual('new value 2'); + +      selector.css('prop', null); +      expect(jqLite(a).css('prop')).toBeFalsy(); +      expect(jqLite(b).css('prop')).toBeFalsy(); +    }); +  }); +  describe('text', function(){ +    it('should return null on empty', function(){ +      expect(jqLite().length).toEqual(0); +      expect(jqLite().text()).toEqual(''); +    }); + +    it('should read/write value', function(){ +      var element = jqLite('<div>abc</div>'); +      expect(element.length).toEqual(1); +      expect(element[0].innerHTML).toEqual('abc'); +      expect(element.text()).toEqual('abc'); +      expect(element.text('xyz') == element).toBeTruthy(); +      expect(element.text()).toEqual('xyz'); +    }); +  }); +  describe('val', function(){ +    it('should read, write value', function(){ +      var input = jqLite('<input type="text"/>'); +      expect(input.val('abc')).toEqual(input); +      expect(input[0].value).toEqual('abc'); +      expect(input.val()).toEqual('abc'); +    }); +  }); +  describe('html', function(){ +    it('should return null on empty', function(){ +      expect(jqLite().length).toEqual(0); +      expect(jqLite().html()).toEqual(null); +    }); + +    it('should read/write value', function(){ +      var element = jqLite('<div>abc</div>'); +      expect(element.length).toEqual(1); +      expect(element[0].innerHTML).toEqual('abc'); +      expect(element.html()).toEqual('abc'); +      expect(element.html('xyz') == element).toBeTruthy(); +      expect(element.html()).toEqual('xyz'); +    }); +  }); + +  describe('bind', function(){ +    it('should bind to all elements and return functions', function(){ +      var selected = jqLite([a, b]); +      var log = ''; +      expect(selected.bind('click', function(){ +        log += 'click on: ' + jqLite(this).text() + ';'; +      })).toEqual(selected); +      browserTrigger(a, 'click'); +      expect(log).toEqual('click on: A;'); +      browserTrigger(b, 'click'); +      expect(log).toEqual('click on: A;click on: B;'); +    }); +  }); + +  describe('replaceWith', function(){ +    it('should replaceWith', function(){ +      var root = jqLite('<div>').html('before-<div></div>after'); +      var div = root.find('div'); +      expect(div.replaceWith('<span>span-</span><b>bold-</b>')).toEqual(div); +      expect(root.text()).toEqual('before-span-bold-after'); +    }); +    it('should replaceWith text', function(){ +      var root = jqLite('<div>').html('before-<div></div>after'); +      var div = root.find('div'); +      expect(div.replaceWith('text-')).toEqual(div); +      expect(root.text()).toEqual('before-text-after'); +    }); +  }); +  describe('children', function(){ +    it('should select non-text children', function(){ +      var root = jqLite('<div>').html('before-<div></div>after-<span></span>'); +      var div = root.find('div'); +      var span = root.find('span'); +      expect(root.children()).toJqEqual([div, span]); +    }); +  }); +  describe('append', function(){ +    it('should append', function(){ +      var root = jqLite('<div>'); +      expect(root.append('<span>abc</span>')).toEqual(root); +      expect(root.html().toLowerCase()).toEqual('<span>abc</span>'); +    }); +    it('should append text', function(){ +      var root = jqLite('<div>'); +      expect(root.append('text')).toEqual(root); +      expect(root.html()).toEqual('text'); +    }); +  }); +  describe('remove', function(){ +    it('should remove', function(){ +      var root = jqLite('<div><span>abc</span></div>'); +      var span = root.find('span'); +      expect(span.remove()).toEqual(span); +      expect(root.html()).toEqual(''); +    }); +  }); +  describe('after', function(){ +    it('should after', function(){ +      var root = jqLite('<div><span></span></div>'); +      var span = root.find('span'); +      expect(span.after('<i></i><b></b>')).toEqual(span); +      expect(root.html().toLowerCase()).toEqual('<span></span><i></i><b></b>'); +    }); +    it('should allow taking text', function(){ +      var root = jqLite('<div><span></span></div>'); +      var span = root.find('span'); +      span.after('abc'); +      expect(root.html().toLowerCase()).toEqual('<span></span>abc'); +    }); +  }); +  describe('parent', function(){ +    it('should return empty set when no parent', function(){ +      var element = jqLite('<div>abc</div>'); +      expect(element.parent()).toBeTruthy(); +      expect(element.parent().length).toEqual(0); +    }); +  }); +  describe('find', function(){ +    it('should find child by name', function(){ +      var root = jqLite('<div><div>text</div></div>'); +      var innerDiv = root.find('div'); +      expect(innerDiv.length).toEqual(1); +      expect(innerDiv.html()).toEqual('text'); +    }); +  }); +  }); diff --git a/test/testabilityPatch.js b/test/testabilityPatch.js index dc6acf8b..b5c2c3f4 100644 --- a/test/testabilityPatch.js +++ b/test/testabilityPatch.js @@ -142,8 +142,11 @@ function childNode(element, index) {  }  function dealoc(obj) { -  var element = (obj||{}).$element || obj; -  if (element && element.dealoc) element.dealoc(); +  if (obj) { +    var element = obj.$element || obj || {}; +    if (element.nodeName) element = jqLite(element); +    if (element.dealoc) element.dealoc(); +  }  }  extend(angular, { @@ -179,7 +182,7 @@ function sortedHtml(element, showNgClass) {          replace(/</g, '<').          replace(/>/g, '>');      } else { -      html += '<' + node.nodeName.toLowerCase(); +      html += '<' + (node.nodeName || '?NOT_A_NODE?').toLowerCase();        var attributes = node.attributes || [];        var attrs = [];        var className = node.className || ''; | 
