diff options
| -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 || ''; |
