aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md4
-rw-r--r--src/Angular.js32
-rw-r--r--src/Compiler.js5
-rw-r--r--src/jqLite.js461
-rw-r--r--src/scenario/Scenario.js6
-rw-r--r--test/jqLiteSpec.js283
-rw-r--r--test/testabilityPatch.js9
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>&nbsp;</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>&nbsp;</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, '&lt;').
replace(/>/g, '&gt;');
} else {
- html += '<' + node.nodeName.toLowerCase();
+ html += '<' + (node.nodeName || '?NOT_A_NODE?').toLowerCase();
var attributes = node.attributes || [];
var attrs = [];
var className = node.className || '';