prototype: Merge the selector branch into trunk, bringing vast performance improvements, bug fixes, and near-complete CSS3 compliance to $$ and Selector. Closes #7568.
This commit is contained in:
parent
f160bc4d4d
commit
00bce412d2
|
@ -1,5 +1,8 @@
|
|||
*SVN*
|
||||
|
||||
* Merge the selector branch into trunk, bringing vast performance improvements, bug fixes, and near-complete CSS3 compliance to $$ and Selector. Closes #7568. [Andrew Dupont]
|
||||
Selector speed test: http://andrewdupont.net/test/double-dollar/
|
||||
|
||||
* Add support for JSON encoding and decoding. Closes #7427. [Tobie Langel]
|
||||
|
||||
* Fix double escaping of query parameters in Hash.prototype.toQueryString, and prevent Safari from iterating over shadowed properties when creating hashes. Closes #7421. [Tobie Langel, Mislav Marohnić]
|
||||
|
|
29
src/dom.js
29
src/dom.js
|
@ -18,10 +18,12 @@ if (Prototype.BrowserFeatures.XPath) {
|
|||
results.push(query.snapshotItem(i));
|
||||
return results;
|
||||
};
|
||||
|
||||
document.getElementsByClassName = function(className, parentElement) {
|
||||
var q = ".//*[contains(concat(' ', @class, ' '), ' " + className + " ')]";
|
||||
return document._getElementsByXPath(q, parentElement);
|
||||
}
|
||||
|
||||
} else document.getElementsByClassName = function(className, parentElement) {
|
||||
var children = ($(parentElement) || document.body).getElementsByTagName('*');
|
||||
var elements = [], child;
|
||||
|
@ -177,19 +179,27 @@ Element.Methods = {
|
|||
},
|
||||
|
||||
up: function(element, expression, index) {
|
||||
return Selector.findElement($(element).ancestors(), expression, index);
|
||||
var ancestors = $(element).ancestors();
|
||||
return expression ? Selector.findElement(ancestors, expression, index) :
|
||||
ancestors[index || 0];
|
||||
},
|
||||
|
||||
down: function(element, expression, index) {
|
||||
return Selector.findElement($(element).descendants(), expression, index);
|
||||
var descendants = $(element).descendants();
|
||||
return expression ? Selector.findElement(descendants, expression, index) :
|
||||
descendants[index || 0];
|
||||
},
|
||||
|
||||
previous: function(element, expression, index) {
|
||||
return Selector.findElement($(element).previousSiblings(), expression, index);
|
||||
var previousSiblings = $(element).previousSiblings();
|
||||
return expression ? Selector.findElement(previousSiblings, expression, index) :
|
||||
previousSiblings[index || 0];
|
||||
},
|
||||
|
||||
next: function(element, expression, index) {
|
||||
return Selector.findElement($(element).nextSiblings(), expression, index);
|
||||
var nextSiblings = $(element).nextSiblings();
|
||||
return expression ? Selector.findElement(nextSiblings, expression, index) :
|
||||
nextSiblings[index || 0];
|
||||
},
|
||||
|
||||
getElementsBySelector: function() {
|
||||
|
@ -203,12 +213,12 @@ Element.Methods = {
|
|||
|
||||
readAttribute: function(element, name) {
|
||||
element = $(element);
|
||||
if (document.all && !window.opera) {
|
||||
if (Prototype.Browser.IE) {
|
||||
var t = Element._attributeTranslations;
|
||||
if (t.values[name]) return t.values[name](element, name);
|
||||
if (t.names[name]) name = t.names[name];
|
||||
var attribute = element.attributes[name];
|
||||
if(attribute) return attribute.nodeValue;
|
||||
return attribute ? attribute.nodeValue : null;
|
||||
}
|
||||
return element.getAttribute(name);
|
||||
},
|
||||
|
@ -398,7 +408,7 @@ Element.Methods = {
|
|||
element.style.overflow = element._overflow == 'auto' ? '' : element._overflow;
|
||||
element._overflow = null;
|
||||
return element;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Object.extend(Element.Methods, {childOf: Element.Methods.descendantOf});
|
||||
|
@ -554,6 +564,11 @@ if (!Prototype.BrowserFeatures.ElementExtensions &&
|
|||
Prototype.BrowserFeatures.ElementExtensions = true;
|
||||
}
|
||||
|
||||
Element.hasAttribute = function(element, attribute) {
|
||||
if (element.hasAttribute) return element.hasAttribute(attribute);
|
||||
return Element.Methods.Simulated.hasAttribute(element, attribute);
|
||||
};
|
||||
|
||||
Element.addMethods = function(methods) {
|
||||
var F = Prototype.BrowserFeatures, T = Element.Methods.ByTag;
|
||||
if (arguments.length == 2) {
|
||||
|
|
626
src/selector.js
626
src/selector.js
|
@ -1,126 +1,550 @@
|
|||
var Selector = Class.create();
|
||||
|
||||
Selector.prototype = {
|
||||
initialize: function(expression) {
|
||||
this.params = {classNames: []};
|
||||
this.expression = expression.toString().strip();
|
||||
this.parseExpression();
|
||||
this.compileMatcher();
|
||||
this.expression = expression.strip();
|
||||
this.compileMatcher();
|
||||
},
|
||||
|
||||
parseExpression: function() {
|
||||
function abort(message) { throw 'Parse error in selector: ' + message; }
|
||||
|
||||
if (this.expression == '') abort('empty expression');
|
||||
|
||||
var params = this.params, expr = this.expression, match, modifier, clause, rest;
|
||||
while (match = expr.match(/^(.*)\[([a-z0-9_:-]+?)(?:([~\|!]?=)(?:"([^"]*)"|([^\]\s]*)))?\]$/i)) {
|
||||
params.attributes = params.attributes || [];
|
||||
params.attributes.push({name: match[2], operator: match[3], value: match[4] || match[5] || ''});
|
||||
expr = match[1];
|
||||
}
|
||||
|
||||
if (expr == '*') return this.params.wildcard = true;
|
||||
|
||||
while (match = expr.match(/^([^a-z0-9_-])?([a-z0-9_-]+)(.*)/i)) {
|
||||
modifier = match[1], clause = match[2], rest = match[3];
|
||||
switch (modifier) {
|
||||
case '#': params.id = clause; break;
|
||||
case '.': params.classNames.push(clause); break;
|
||||
case '':
|
||||
case undefined: params.tagName = clause.toUpperCase(); break;
|
||||
default: abort(expr.inspect());
|
||||
}
|
||||
expr = rest;
|
||||
}
|
||||
|
||||
if (expr.length > 0) abort(expr.inspect());
|
||||
},
|
||||
|
||||
buildMatchExpression: function() {
|
||||
var params = this.params, conditions = [], clause;
|
||||
|
||||
if (params.wildcard)
|
||||
conditions.push('true');
|
||||
if (clause = params.id)
|
||||
conditions.push('element.readAttribute("id") == ' + clause.inspect());
|
||||
if (clause = params.tagName)
|
||||
conditions.push('element.tagName.toUpperCase() == ' + clause.inspect());
|
||||
if ((clause = params.classNames).length > 0)
|
||||
for (var i = 0, length = clause.length; i < length; i++)
|
||||
conditions.push('element.hasClassName(' + clause[i].inspect() + ')');
|
||||
if (clause = params.attributes) {
|
||||
clause.each(function(attribute) {
|
||||
var value = 'element.readAttribute(' + attribute.name.inspect() + ')';
|
||||
var splitValueBy = function(delimiter) {
|
||||
return value + ' && ' + value + '.split(' + delimiter.inspect() + ')';
|
||||
}
|
||||
|
||||
switch (attribute.operator) {
|
||||
case '=': conditions.push(value + ' == ' + attribute.value.inspect()); break;
|
||||
case '~=': conditions.push(splitValueBy(' ') + '.include(' + attribute.value.inspect() + ')'); break;
|
||||
case '|=': conditions.push(
|
||||
splitValueBy('-') + '.first().toUpperCase() == ' + attribute.value.toUpperCase().inspect()
|
||||
); break;
|
||||
case '!=': conditions.push(value + ' != ' + attribute.value.inspect()); break;
|
||||
case '':
|
||||
case undefined: conditions.push('element.hasAttribute(' + attribute.name.inspect() + ')'); break;
|
||||
default: throw 'Unknown operator ' + attribute.operator + ' in selector';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return conditions.join(' && ');
|
||||
},
|
||||
|
||||
|
||||
compileMatcher: function() {
|
||||
this.match = new Function('element', 'if (!element.tagName) return false; \
|
||||
element = $(element); \
|
||||
return ' + this.buildMatchExpression());
|
||||
// Selectors with namespaced attributes can't use the XPath version
|
||||
if (Prototype.BrowserFeatures.XPath && !(/\[[\w-]*?:/).test(this.expression))
|
||||
return this.compileXPathMatcher();
|
||||
|
||||
var e = this.expression, ps = Selector.patterns, h = Selector.handlers,
|
||||
c = Selector.criteria, le, p, m;
|
||||
|
||||
if (Selector._cache[e]) {
|
||||
this.matcher = Selector._cache[e]; return;
|
||||
}
|
||||
this.matcher = ["this.matcher = function(root) {",
|
||||
"var r = root, h = Selector.handlers, c = false, n;"];
|
||||
|
||||
while (e && le != e && (/\S/).test(e)) {
|
||||
le = e;
|
||||
for (var i in ps) {
|
||||
p = ps[i];
|
||||
if (m = e.match(p)) {
|
||||
this.matcher.push(typeof c[i] == 'function' ? c[i](m) :
|
||||
new Template(c[i]).evaluate(m));
|
||||
e = e.replace(m[0], '');
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.matcher.push("return h.unique(n);\n}");
|
||||
eval(this.matcher.join('\n'));
|
||||
Selector._cache[this.expression] = this.matcher;
|
||||
},
|
||||
|
||||
compileXPathMatcher: function() {
|
||||
var e = this.expression, ps = Selector.patterns,
|
||||
x = Selector.xpath, le, p, m;
|
||||
|
||||
findElements: function(scope) {
|
||||
var element;
|
||||
if (Selector._cache[e]) {
|
||||
this.xpath = Selector._cache[e]; return;
|
||||
}
|
||||
|
||||
if (element = $(this.params.id))
|
||||
if (this.match(element))
|
||||
if (!scope || Element.childOf(element, scope))
|
||||
return [element];
|
||||
|
||||
scope = (scope || document).getElementsByTagName(this.params.tagName || '*');
|
||||
|
||||
var results = [];
|
||||
for (var i = 0, length = scope.length; i < length; i++)
|
||||
if (this.match(element = scope[i]))
|
||||
results.push(Element.extend(element));
|
||||
|
||||
return results;
|
||||
this.matcher = ['.//*'];
|
||||
while (e && le != e && (/\S/).test(e)) {
|
||||
le = e;
|
||||
for (var i in ps) {
|
||||
if (m = e.match(ps[i])) {
|
||||
this.matcher.push(typeof x[i] == 'function' ? x[i](m) :
|
||||
new Template(x[i]).evaluate(m));
|
||||
e = e.replace(m[0], '');
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.xpath = this.matcher.join('');
|
||||
Selector._cache[this.expression] = this.xpath;
|
||||
},
|
||||
|
||||
|
||||
findElements: function(root) {
|
||||
root = root || document;
|
||||
if (this.xpath) return document._getElementsByXPath(this.xpath, root);
|
||||
return this.matcher(root);
|
||||
},
|
||||
|
||||
match: function(element) {
|
||||
return this.findElements(document).include(element);
|
||||
},
|
||||
|
||||
toString: function() {
|
||||
return this.expression;
|
||||
},
|
||||
|
||||
inspect: function() {
|
||||
return "#<Selector:" + this.expression.inspect() + ">";
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Object.extend(Selector, {
|
||||
_cache: {},
|
||||
|
||||
xpath: {
|
||||
descendant: "//*",
|
||||
child: "/*",
|
||||
adjacent: "/following-sibling::*[1]",
|
||||
laterSibling: '/following-sibling::*',
|
||||
tagName: function(m) {
|
||||
if (m[1] == '*') return '';
|
||||
return "[local-name()='" + m[1].toLowerCase() +
|
||||
"' or local-name()='" + m[1].toUpperCase() + "']";
|
||||
},
|
||||
className: "[contains(concat(' ', @class, ' '), ' #{1} ')]",
|
||||
id: "[@id='#{1}']",
|
||||
attrPresence: "[@#{1}]",
|
||||
attr: function(m) {
|
||||
m[3] = m[5] || m[6];
|
||||
return new Template(Selector.xpath.operators[m[2]]).evaluate(m);
|
||||
},
|
||||
pseudo: function(m) {
|
||||
var h = Selector.xpath.pseudos[m[1]];
|
||||
if (!h) return '';
|
||||
if (typeof h === 'function') return h(m);
|
||||
return new Template(Selector.xpath.pseudos[m[1]]).evaluate(m);
|
||||
},
|
||||
operators: {
|
||||
'=': "[@#{1}='#{3}']",
|
||||
'!=': "[@#{1}!='#{3}']",
|
||||
'^=': "[starts-with(@#{1}, '#{3}')]",
|
||||
'$=': "[substring(@#{1}, (string-length(@#{1}) - string-length('#{3}') + 1))='#{3}']",
|
||||
'*=': "[contains(@#{1}, '#{3}')]",
|
||||
'~=': "[contains(concat(' ', @#{1}, ' '), ' #{3} ')]",
|
||||
'|=': "[contains(concat('-', @#{1}, '-'), '-#{3}-')]"
|
||||
},
|
||||
pseudos: {
|
||||
'first-child': '[not(preceding-sibling::*)]',
|
||||
'last-child': '[not(following-sibling::*)]',
|
||||
'only-child': '[not(preceding-sibling::* or following-sibling::*)]',
|
||||
'empty': "[count(*) = 0 and (count(text()) = 0 or translate(text(), ' \t\r\n', '') = '')]",
|
||||
'checked': "[@checked]",
|
||||
'disabled': "[@disabled]",
|
||||
'enabled': "[not(@disabled)]",
|
||||
'not': function(m) {
|
||||
if (!m[6]) return '';
|
||||
var p = Selector.patterns, x = Selector.xpath;
|
||||
for (var i in p) {
|
||||
if (mm = m[6].match(p[i])) {
|
||||
var ss = typeof x[i] == 'function' ? x[i](mm) : new Template(x[i]).evaluate(mm);
|
||||
m[6] = ss.substring(1, ss.length - 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return "[not(" + m[6] + ")]";
|
||||
},
|
||||
'nth-child': function(m) {
|
||||
return Selector.xpath.pseudos.nth("(count(./preceding-sibling::*) + 1) ", m);
|
||||
},
|
||||
'nth-last-child': function(m) {
|
||||
return Selector.xpath.pseudos.nth("(count(./following-sibling::*) + 1) ", m);
|
||||
},
|
||||
'nth-of-type': function(m) {
|
||||
return Selector.xpath.pseudos.nth("position() ", m);
|
||||
},
|
||||
'nth-last-of-type': function(m) {
|
||||
return Selector.xpath.pseudos.nth("(last() + 1 - position()) ", m);
|
||||
},
|
||||
'first-of-type': function(m) {
|
||||
m[6] = "1"; return Selector.xpath.pseudos['nth-of-type'](m);
|
||||
},
|
||||
'last-of-type': function(m) {
|
||||
m[6] = "1"; return Selector.xpath.pseudos['nth-last-of-type'](m);
|
||||
},
|
||||
'only-of-type': function(m) {
|
||||
var p = Selector.xpath.pseudos; return p['first-of-type'](m) + p['last-of-type'](m);
|
||||
},
|
||||
nth: function(predicate, m) {
|
||||
var mm, formula = m[6];
|
||||
if (formula == 'even') formula = '2n+0';
|
||||
if (formula == 'odd') formula = '2n+1';
|
||||
if (mm = formula.match(/^(\d+)$/)) // digit only
|
||||
predicate += "= " + mm[1];
|
||||
if (mm = formula.match(/^(\d+)?n(\+(\d+))?/)) { // an+b
|
||||
var a = mm[1] ? Number(mm[1]) : 1;
|
||||
var b = mm[3] ? Number(mm[3]) : 0;
|
||||
predicate += "mod " + a + " = " + b;
|
||||
}
|
||||
return "[" + predicate + "]";
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
criteria: {
|
||||
tagName: 'n = h.tagName(n, r, "#{1}", c); c = false;',
|
||||
className: 'n = h.className(n, r, "#{1}", c); c = false;',
|
||||
id: 'n = h.id(n, r, "#{1}", c); c = false;',
|
||||
attrPresence: 'n = h.attrPresence(n, r, "#{1}"); c = false;',
|
||||
attr: function(m) {
|
||||
m[3] = m[5] || m[6];
|
||||
return new Template('n = h.attr(n, r, "#{1}", "#{3}", "#{2}"); c = false;').evaluate(m);
|
||||
},
|
||||
pseudo: 'n = h.pseudo(n, "#{1}", "#{6}", r, c); c = false;',
|
||||
descendant: 'c = "descendant";',
|
||||
child: 'c = "child";',
|
||||
adjacent: 'c = "adjacent";',
|
||||
laterSibling: 'c = "laterSibling";'
|
||||
},
|
||||
|
||||
patterns: {
|
||||
// combinators must be listed first
|
||||
// (and descendant needs to be last combinator)
|
||||
laterSibling: /^\s*~\s*/,
|
||||
child: /^\s*>\s*/,
|
||||
adjacent: /^\s*\+\s*/,
|
||||
descendant: /^\s/,
|
||||
|
||||
// selectors follow
|
||||
tagName: /^\s*(\*|[\w\-]+)(\b|$)?/,
|
||||
id: /^#([\w\-\*]+)(\b|$)/,
|
||||
className: /^\.([\w\-\*]+)(\b|$)/,
|
||||
pseudo: /^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$)/,
|
||||
attrPresence: /^\[([\w]+)\]/,
|
||||
attr: /\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['"])([^\]]*?)\4|([^'"][^\]]*?)))?\]/
|
||||
},
|
||||
|
||||
handlers: {
|
||||
// UTILITY FUNCTIONS
|
||||
// joins two collections
|
||||
concat: function(a, b) {
|
||||
for (var i = 0, node; node = b[i]; i++)
|
||||
a.push(node);
|
||||
return a;
|
||||
},
|
||||
|
||||
// marks an array of nodes for counting
|
||||
mark: function(nodes) {
|
||||
for (var i = 0, node; node = nodes[i]; i++)
|
||||
node._counted = true;
|
||||
return nodes;
|
||||
},
|
||||
|
||||
unmark: function(nodes) {
|
||||
for (var i = 0, node; node = nodes[i]; i++)
|
||||
node._counted = undefined;
|
||||
return nodes;
|
||||
},
|
||||
|
||||
// mark each child node with its position (for nth calls)
|
||||
// "ofType" flag indicates whether we're indexing for nth-of-type
|
||||
// rather than nth-child
|
||||
index: function(parentNode, reverse, ofType) {
|
||||
parentNode._counted = true;
|
||||
if (reverse) {
|
||||
for (var nodes = parentNode.childNodes, i = nodes.length - 1, j = 1; i >= 0; i--) {
|
||||
node = nodes[i];
|
||||
if (node.nodeType == 1 && (!ofType || node._counted)) node.nodeIndex = j++;
|
||||
}
|
||||
} else {
|
||||
for (var i = 0, j = 1, nodes = parentNode.childNodes; node = nodes[i]; i++)
|
||||
if (node.nodeType == 1 && (!ofType || node._counted)) node.nodeIndex = j++;
|
||||
}
|
||||
},
|
||||
|
||||
// filters out duplicates and extends all nodes
|
||||
unique: function(nodes) {
|
||||
if (nodes.length == 0) return nodes;
|
||||
var results = [nodes[0]], n;
|
||||
nodes[0]._counted = true;
|
||||
for (var i = 0, l = nodes.length; i < l; i++) {
|
||||
n = nodes[i];
|
||||
if (!n._counted) {
|
||||
n._counted = true;
|
||||
results.push(Element.extend(n));
|
||||
}
|
||||
}
|
||||
return Selector.handlers.unmark(results);
|
||||
},
|
||||
|
||||
// COMBINATOR FUNCTIONS
|
||||
descendant: function(nodes) {
|
||||
var h = Selector.handlers;
|
||||
for (var i = 0, results = [], node; node = nodes[i]; i++)
|
||||
h.concat(results, Element.descendants(node));
|
||||
return results;
|
||||
},
|
||||
|
||||
child: function(nodes) {
|
||||
var h = Selector.handlers;
|
||||
for (var i = 0, results = [], node; node = nodes[i]; i++)
|
||||
h.concat(results, Element.immediateDescendants(node));
|
||||
return results;
|
||||
},
|
||||
|
||||
adjacent: function(nodes) {
|
||||
for (var i = 0, results = [], node; node = nodes[i]; i++) {
|
||||
var next = this.nextElementSibling(node);
|
||||
if (next) results.push(next);
|
||||
}
|
||||
return results;
|
||||
},
|
||||
|
||||
laterSibling: function(nodes) {
|
||||
var h = Selector.handlers;
|
||||
for (var i = 0, results = [], node; node = nodes[i]; i++)
|
||||
h.concat(results, Element.nextSiblings(node));
|
||||
return results;
|
||||
},
|
||||
|
||||
nextElementSibling: function(node) {
|
||||
while (node = node.nextSibling)
|
||||
if (node.nodeType == 1) return node;
|
||||
return null;
|
||||
},
|
||||
|
||||
previousElementSibling: function(node) {
|
||||
while (node = node.previousSibling)
|
||||
if (node.nodeType == 1) return node;
|
||||
return null;
|
||||
},
|
||||
|
||||
// TOKEN FUNCTIONS
|
||||
tagName: function(nodes, root, tagName, combinator) {
|
||||
tagName = tagName.toUpperCase();
|
||||
var results = [], h = Selector.handlers;
|
||||
if (nodes) {
|
||||
if (combinator) {
|
||||
// fastlane for ordinary descendant combinators
|
||||
if (combinator == "descendant") {
|
||||
for (var i = 0, node; node = nodes[i]; i++)
|
||||
h.concat(results, node.getElementsByTagName(tagName));
|
||||
return results;
|
||||
} else nodes = this[combinator](nodes);
|
||||
if (tagName == "*") return nodes;
|
||||
}
|
||||
for (var i = 0, node; node = nodes[i]; i++)
|
||||
if (node.tagName.toUpperCase() == tagName) results.push(node);
|
||||
return results;
|
||||
} else return root.getElementsByTagName(tagName);
|
||||
},
|
||||
|
||||
id: function(nodes, root, id, combinator) {
|
||||
var targetNode = $(id), h = Selector.handlers;
|
||||
if (!nodes && root == document) return targetNode ? [targetNode] : [];
|
||||
if (nodes) {
|
||||
if (combinator) {
|
||||
if (combinator == 'child') {
|
||||
for (var i = 0, node; node = nodes[i]; i++)
|
||||
if (targetNode.parentNode == node) return [targetNode];
|
||||
} else if (combinator == 'descendant') {
|
||||
for (var i = 0, node; node = nodes[i]; i++)
|
||||
if (Element.descendantOf(targetNode, node)) return [targetNode];
|
||||
} else if (combinator == 'adjacent') {
|
||||
for (var i = 0, node; node = nodes[i]; i++)
|
||||
if (Selector.handlers.previousElementSibling(targetNode) == node)
|
||||
return [targetNode];
|
||||
} else nodes = h[combinator](nodes);
|
||||
}
|
||||
for (var i = 0, node; node = nodes[i]; i++)
|
||||
if (node == targetNode) return [targetNode];
|
||||
return [];
|
||||
}
|
||||
return (targetNode && Element.descendantOf(targetNode, root)) ? [targetNode] : [];
|
||||
},
|
||||
|
||||
className: function(nodes, root, className, combinator) {
|
||||
if (nodes && combinator) nodes = this[combinator](nodes);
|
||||
return Selector.handlers.byClassName(nodes, root, className);
|
||||
},
|
||||
|
||||
byClassName: function(nodes, root, className) {
|
||||
if (!nodes) nodes = Selector.handlers.descendant([root]);
|
||||
var needle = ' ' + className + ' ';
|
||||
for (var i = 0, results = [], node, nodeClassName; node = nodes[i]; i++) {
|
||||
nodeClassName = node.className;
|
||||
if (nodeClassName.length == 0) continue;
|
||||
if (nodeClassName == className || (' ' + nodeClassName + ' ').include(needle))
|
||||
results.push(node);
|
||||
}
|
||||
return results;
|
||||
},
|
||||
|
||||
attrPresence: function(nodes, root, attr) {
|
||||
var results = [];
|
||||
for (var i = 0, node; node = nodes[i]; i++)
|
||||
if (Element.hasAttribute(node, attr)) results.push(node);
|
||||
return results;
|
||||
},
|
||||
|
||||
attr: function(nodes, root, attr, value, operator) {
|
||||
var handler = Selector.operators[operator], results = [];
|
||||
for (var i = 0, node; node = nodes[i]; i++) {
|
||||
var nodeValue = Element.readAttribute(node, attr);
|
||||
if (nodeValue === null) continue;
|
||||
if (handler(nodeValue, value)) results.push(node);
|
||||
}
|
||||
return results;
|
||||
},
|
||||
|
||||
pseudo: function(nodes, name, value, root, combinator) {
|
||||
if (combinator) nodes = this[combinator](nodes);
|
||||
return Selector.pseudos[name](nodes, value, root);
|
||||
}
|
||||
},
|
||||
|
||||
pseudos: {
|
||||
'first-child': function(nodes, value, root) {
|
||||
for (var i = 0, results = [], node; node = nodes[i]; i++) {
|
||||
if (Selector.handlers.previousElementSibling(node)) continue;
|
||||
results.push(node);
|
||||
}
|
||||
return results;
|
||||
},
|
||||
'last-child': function(nodes, value, root) {
|
||||
for (var i = 0, results = [], node; node = nodes[i]; i++) {
|
||||
if (Selector.handlers.nextElementSibling(node)) continue;
|
||||
results.push(node);
|
||||
}
|
||||
return results;
|
||||
},
|
||||
'only-child': function(nodes, value, root) {
|
||||
var h = Selector.handlers;
|
||||
for (var i = 0, results = [], node; node = nodes[i]; i++)
|
||||
if (!h.previousElementSibling(node) && !h.nextElementSibling(node))
|
||||
results.push(node);
|
||||
return results;
|
||||
},
|
||||
'nth-child': function(nodes, formula, root) {
|
||||
return Selector.pseudos.nth(nodes, formula, root);
|
||||
},
|
||||
'nth-last-child': function(nodes, formula, root) {
|
||||
return Selector.pseudos.nth(nodes, formula, root, true);
|
||||
},
|
||||
'nth-of-type': function(nodes, formula, root) {
|
||||
return Selector.pseudos.nth(nodes, formula, root, false, true);
|
||||
},
|
||||
'nth-last-of-type': function(nodes, formula, root) {
|
||||
return Selector.pseudos.nth(nodes, formula, root, true, true);
|
||||
},
|
||||
'first-of-type': function(nodes, formula, root) {
|
||||
return Selector.pseudos.nth(nodes, "1", root, false, true);
|
||||
},
|
||||
'last-of-type': function(nodes, formula, root) {
|
||||
return Selector.pseudos.nth(nodes, "1", root, true, true);
|
||||
},
|
||||
'only-of-type': function(nodes, formula, root) {
|
||||
var p = Selector.pseudos;
|
||||
return p['last-of-type'](p['first-of-type'](nodes, formula, root), formula, root);
|
||||
},
|
||||
|
||||
// handles nth(-last)-child, nth(-last)-of-type, and (first|last)-of-type
|
||||
nth: function(nodes, formula, root, reverse, ofType) {
|
||||
if (formula == 'even') formula = '2n+0';
|
||||
if (formula == 'odd') formula = '2n+1';
|
||||
var h = Selector.handlers, results = [], indexed = [], m;
|
||||
h.mark(nodes);
|
||||
for (var i = 0, node; node = nodes[i]; i++) {
|
||||
if (!node.parentNode._counted) {
|
||||
h.index(node.parentNode, reverse, ofType);
|
||||
indexed.push(node.parentNode);
|
||||
}
|
||||
}
|
||||
if (formula.match(/^\d+$/)) { // just a number
|
||||
formula = Number(formula);
|
||||
for (var i = 0, node; node = nodes[i]; i++)
|
||||
if (node.nodeIndex == formula) results.push(node);
|
||||
} else if (m = formula.match(/^(\d+)?n(\+(\d+))?$/)) { // an+b
|
||||
var a = m[1] ? Number(m[1]) : 1;
|
||||
var b = m[3] ? Number(m[3]) : 0;
|
||||
for (var i = 0, node; node = nodes[i]; i++)
|
||||
if (node.nodeIndex % a == b) results.push(node);
|
||||
}
|
||||
h.unmark(nodes);
|
||||
h.unmark(indexed);
|
||||
return results;
|
||||
},
|
||||
|
||||
'empty': function(nodes, value, root) {
|
||||
for (var i = 0, results = [], node; node = nodes[i]; i++) {
|
||||
// IE treats comments as element nodes
|
||||
if (node.tagName == '!' || (node.firstChild && !node.innerHTML.match(/^\s*$/))) continue;
|
||||
results.push(node);
|
||||
}
|
||||
return results;
|
||||
},
|
||||
|
||||
'not': function(nodes, selector, root) {
|
||||
var h = Selector.handlers, exclusions = $A(nodes), selectorType, m;
|
||||
for (var i in Selector.patterns) {
|
||||
if (m = selector.match(Selector.patterns[i])) {
|
||||
selectorType = i; break;
|
||||
}
|
||||
}
|
||||
switch(selectorType) {
|
||||
case 'className': case 'tagName': case 'id': // fallthroughs
|
||||
case 'attrPresence': exclusions = h[selectorType](exclusions, root, m[1], false); break;
|
||||
case 'attr': m[3] = m[5] || m[6]; exclusions = h.attr(exclusions, root, m[1], m[3], m[2]); break;
|
||||
case 'pseudo': exclusions = h.pseudo(exclusions, m[1], m[6], root, false); break;
|
||||
// only 'simple selectors' (one token) allowed in a :not clause
|
||||
default: throw 'Illegal selector in :not clause.';
|
||||
}
|
||||
h.mark(exclusions);
|
||||
for (var i = 0, results = [], node; node = nodes[i]; i++)
|
||||
if (!node._counted) results.push(node);
|
||||
h.unmark(exclusions);
|
||||
return results;
|
||||
},
|
||||
|
||||
'enabled': function(nodes, value, root) {
|
||||
for (var i = 0, results = [], node; node = nodes[i]; i++)
|
||||
if (!node.disabled) results.push(node);
|
||||
return results;
|
||||
},
|
||||
|
||||
'disabled': function(nodes, value, root) {
|
||||
for (var i = 0, results = [], node; node = nodes[i]; i++)
|
||||
if (node.disabled) results.push(node);
|
||||
return results;
|
||||
},
|
||||
|
||||
'checked': function(nodes, value, root) {
|
||||
for (var i = 0, results = [], node; node = nodes[i]; i++)
|
||||
if (node.checked) results.push(node);
|
||||
return results;
|
||||
}
|
||||
},
|
||||
|
||||
operators: {
|
||||
'=': function(nv, v) { return nv == v; },
|
||||
'!=': function(nv, v) { return nv != v; },
|
||||
'^=': function(nv, v) { return nv.startsWith(v); },
|
||||
'$=': function(nv, v) { return nv.endsWith(v); },
|
||||
'*=': function(nv, v) { return nv.include(v); },
|
||||
'~=': function(nv, v) { return (' ' + nv + ' ').include(' ' + v + ' '); },
|
||||
'|=': function(nv, v) { return ('-' + nv.toUpperCase() + '-').include('-' + v.toUpperCase() + '-'); }
|
||||
},
|
||||
|
||||
matchElements: function(elements, expression) {
|
||||
var selector = new Selector(expression);
|
||||
return elements.select(selector.match.bind(selector)).map(Element.extend);
|
||||
var matches = new Selector(expression).findElements(), h = Selector.handlers;
|
||||
h.mark(matches);
|
||||
for (var i = 0, results = [], element; element = elements[i]; i++)
|
||||
if (element._counted) results.push(element);
|
||||
h.unmark(matches);
|
||||
return results;
|
||||
},
|
||||
|
||||
findElement: function(elements, expression, index) {
|
||||
if (typeof expression == 'number') index = expression, expression = false;
|
||||
if (typeof expression == 'number') {
|
||||
index = expression; expression = false;
|
||||
}
|
||||
return Selector.matchElements(elements, expression || '*')[index || 0];
|
||||
},
|
||||
|
||||
findChildElements: function(element, expressions) {
|
||||
return expressions.map(function(expression) {
|
||||
return expression.match(/[^\s"]+(?:"[^"]*"[^\s"]+)*/g).inject([null], function(results, expr) {
|
||||
var selector = new Selector(expr);
|
||||
return results.inject([], function(elements, result) {
|
||||
return elements.concat(selector.findElements(result || element));
|
||||
});
|
||||
});
|
||||
}).flatten();
|
||||
var exprs = expressions.join(','), expressions = [];
|
||||
exprs.scan(/(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)/, function(m) {
|
||||
expressions.push(m[1].strip());
|
||||
});
|
||||
var results = [], h = Selector.handlers;
|
||||
for (var i = 0, l = expressions.length, selector; i < l; i++) {
|
||||
selector = new Selector(expressions[i].strip());
|
||||
h.concat(results, selector.findElements(element));
|
||||
}
|
||||
return (l > 1) ? h.unique(results) : results;
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -305,7 +305,9 @@ Test.Unit.Assertions.prototype = {
|
|||
},
|
||||
assertEnumEqual: function(expected, actual) {
|
||||
var message = arguments[2] || "assertEnumEqual";
|
||||
try { $A(expected).length == $A(actual).length &&
|
||||
expected = $A(expected);
|
||||
actual = $A(actual);
|
||||
try { expected.length == actual.length &&
|
||||
expected.zip(actual).all(function(pair) { return pair[0] == pair[1] }) ?
|
||||
this.pass() : this.fail(message + ': expected ' + Test.Unit.inspect(expected) +
|
||||
', actual ' + Test.Unit.inspect(actual)); }
|
||||
|
|
|
@ -1,43 +1,90 @@
|
|||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
|
||||
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
|
||||
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"
|
||||
xmlns:html="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<title>Prototype Unit test file</title>
|
||||
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
|
||||
<script src="../../dist/prototype.js" type="text/javascript"></script>
|
||||
<script src="../lib/unittest.js" type="text/javascript"></script>
|
||||
<link rel="stylesheet" href="../test.css" type="text/css" />
|
||||
<style type="text/css" media="screen">
|
||||
/* <![CDATA[ */
|
||||
#testcss1 { font-size:11px; color: #f00; }
|
||||
#testcss2 { font-size:12px; color: #0f0; display: none; }
|
||||
/* ]]> */
|
||||
</style>
|
||||
<title>Prototype Unit test file</title>
|
||||
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
|
||||
<script src="../../dist/prototype.js" type="text/javascript"></script>
|
||||
<script src="../lib/unittest.js" type="text/javascript"></script>
|
||||
<link rel="stylesheet" href="../test.css" type="text/css" />
|
||||
<style type="text/css" media="screen">
|
||||
/* <![CDATA[ */
|
||||
#testcss1 { font-size:11px; color: #f00; }
|
||||
#testcss2 { font-size:12px; color: #0f0; display: none; }
|
||||
/* ]]> */
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Prototype Unit test file</h1>
|
||||
<p>
|
||||
Test of utility functions in selector.js
|
||||
Test of utility functions in selector.js
|
||||
</p>
|
||||
|
||||
<div id="fixtures" style="display: none">
|
||||
<h1 class="title">Some title <span>here</span></h1>
|
||||
<p id="p" class="first summary">
|
||||
<strong id="strong">This</strong> is a short blurb
|
||||
<a id="link_1" class="first internal" href="#">with a link</a> or
|
||||
<a id="link_2" class="internal highlight" href="#"><em id="em">two</em></a>.
|
||||
Or <cite id="with_title" title="hello world!">three</cite>.
|
||||
</p>
|
||||
<ul id="list">
|
||||
<li id="item_1" class="first"><a id="link_3" href="#" class="external"><span id="span">Another link</span></a></li>
|
||||
<li id="item_2">Some text</li>
|
||||
<h1 class="title">Some title <span>here</span></h1>
|
||||
<p id="p" class="first summary">
|
||||
<strong id="strong">This</strong> is a short blurb
|
||||
<a id="link_1" class="first internal" rel="external nofollow" href="#">with a link</a> or
|
||||
<a id="link_2" class="internal highlight" href="#"><em id="em">two</em></a>.
|
||||
Or <cite id="with_title" title="hello world!">a citation</cite>.
|
||||
</p>
|
||||
<ul id="list">
|
||||
<li id="item_1" class="first"><a id="link_3" href="#" class="external"><span id="span">Another link</span></a></li>
|
||||
<li id="item_2">Some text</li>
|
||||
<li id="item_3" xml:lang="es-us" class="">Otra cosa</li>
|
||||
</ul>
|
||||
</ul>
|
||||
|
||||
<!-- this form has a field with the name 'id',
|
||||
therefore its ID property won't be 'troubleForm': -->
|
||||
<form id="troubleForm"><input type="hidden" name="id" /></form>
|
||||
</div>
|
||||
<!-- this form has a field with the name 'id',
|
||||
therefore its ID property won't be 'troubleForm': -->
|
||||
<form id="troubleForm">
|
||||
<input type="hidden" name="id" id="hidden" />
|
||||
<input type="text" name="disabled_text_field" id="disabled_text_field" disabled="disabled" />
|
||||
<input type="text" name="enabled_text_field" id="enabled_text_field" />
|
||||
<input type="checkbox" name="checkboxes" id="checked_box" checked="checked" value="Checked" />
|
||||
<input type="checkbox" name="checkboxes" id="unchecked_box" value="Unchecked"/>
|
||||
<input type="radio" name="radiobuttons" id="checked_radio" checked="checked" value="Checked" />
|
||||
<input type="radio" name="radiobuttons" id="unchecked_radio" value="Unchecked" />
|
||||
</form>
|
||||
|
||||
<div id="level1">
|
||||
<span id="level2_1">
|
||||
<span id="level3_1"></span>
|
||||
<!-- This comment should be ignored by the adjacent selector -->
|
||||
<span id="level3_2"></span>
|
||||
</span>
|
||||
<span id="level2_2">
|
||||
<em id="level_only_child">
|
||||
</em>
|
||||
</span>
|
||||
<div id="level2_3"></div>
|
||||
</div> <!-- #level1 -->
|
||||
|
||||
<div id="dupContainer">
|
||||
<span id="dupL1">
|
||||
<span id="dupL2">
|
||||
<span id="dupL3">
|
||||
<span id="dupL4">
|
||||
<span id="dupL5"></span>
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</div> <!-- #dupContainer -->
|
||||
|
||||
<div id="grandfather"> grandfather
|
||||
<div id="father" class="brothers men"> father
|
||||
<div id="son"> son </div>
|
||||
</div>
|
||||
<div id="uncle" class="brothers men"> uncle </div>
|
||||
</div>
|
||||
|
||||
<form id="commaParent" title="commas,are,good">
|
||||
<input type="hidden" id="commaChild" name="foo" value="#commaOne,#commaTwo" />
|
||||
<input type="hidden" id="commaTwo" name="foo2" value="oops" />
|
||||
</form>
|
||||
|
||||
</div> <!-- #fixtures -->
|
||||
|
||||
<!-- Log output -->
|
||||
<div id="testlog"> </div>
|
||||
|
@ -45,112 +92,275 @@
|
|||
<!-- Tests follow -->
|
||||
<script type="text/javascript" language="javascript" charset="utf-8">
|
||||
// <![CDATA[
|
||||
new Test.Unit.Runner({
|
||||
testSelectorWithTagName: function() {with(this) {
|
||||
assertEnumEqual($A(document.getElementsByTagName('li')), $$('li'));
|
||||
assertEnumEqual([$('strong')], $$('strong'));
|
||||
assertEnumEqual([], $$('nonexistent'));
|
||||
assertEnumEqual($A(document.getElementsByTagName('*')), $$('*'));
|
||||
}},
|
||||
|
||||
testSelectorWithId: function() {with(this) {
|
||||
assertEnumEqual([$('fixtures')], $$('#fixtures'));
|
||||
assertEnumEqual([], $$('#nonexistent'));
|
||||
assertEnumEqual([$('troubleForm')], $$('#troubleForm'));
|
||||
}},
|
||||
|
||||
testSelectorWithClassName: function() {with(this) {
|
||||
assertEnumEqual($('p', 'link_1', 'item_1'), $$('.first'));
|
||||
assertEnumEqual([], $$('.second'));
|
||||
}},
|
||||
|
||||
testSelectorWithTagNameAndId: function() {with(this) {
|
||||
assertEnumEqual([$('strong')], $$('strong#strong'));
|
||||
assertEnumEqual([], $$('p#strong'));
|
||||
}},
|
||||
|
||||
testSelectorWithTagNameAndClassName: function() {with(this) {
|
||||
assertEnumEqual($('link_1', 'link_2'), $$('a.internal'));
|
||||
assertEnumEqual([$('link_2')], $$('a.internal.highlight'));
|
||||
assertEnumEqual([$('link_2')], $$('a.highlight.internal'));
|
||||
assertEnumEqual([], $$('a.highlight.internal.nonexistent'));
|
||||
}},
|
||||
|
||||
testSelectorWithIdAndClassName: function() {with(this) {
|
||||
assertEnumEqual([$('link_2')], $$('#link_2.internal'));
|
||||
assertEnumEqual([$('link_2')], $$('.internal#link_2'));
|
||||
assertEnumEqual([$('link_2')], $$('#link_2.internal.highlight'));
|
||||
assertEnumEqual([], $$('#link_2.internal.nonexistent'));
|
||||
}},
|
||||
|
||||
testSelectorWithTagNameAndIdAndClassName: function() {with(this) {
|
||||
assertEnumEqual([$('link_2')], $$('a#link_2.internal'));
|
||||
assertEnumEqual([$('link_2')], $$('a.internal#link_2'));
|
||||
assertEnumEqual([$('item_1')], $$('li#item_1.first'));
|
||||
assertEnumEqual([], $$('li#item_1.nonexistent'));
|
||||
assertEnumEqual([], $$('li#item_1.first.nonexistent'));
|
||||
}},
|
||||
|
||||
test$$MatchesAncestryWithTokensSeparatedByWhitespace: function() {with(this) {
|
||||
assertEnumEqual($('em', 'span'), $$('#fixtures a *'));
|
||||
assertEnumEqual([$('p')], $$('div#fixtures p'));
|
||||
}},
|
||||
|
||||
test$$CombinesResultsWhenMultipleExpressionsArePassed: function() {with(this) {
|
||||
assertEnumEqual($('link_1', 'link_2', 'item_1', 'item_2', 'item_3'), $$('#p a', ' ul#list li '));
|
||||
}},
|
||||
|
||||
testSelectorWithTagNameAndAttributeExistence: function() {with(this) {
|
||||
assertEnumEqual($$('#fixtures h1'), $$('h1[class]'));
|
||||
assertEnumEqual($$('#fixtures h1'), $$('h1[CLASS]'));
|
||||
assertEnumEqual([$('item_3')], $$('li#item_3[class]'));
|
||||
}},
|
||||
|
||||
testSelectorWithTagNameAndSpecificAttributeValue: function() {with(this) {
|
||||
assertEnumEqual($('link_1', 'link_2', 'link_3'), $$('a[href="#"]'));
|
||||
assertEnumEqual($('link_1', 'link_2', 'link_3'), $$('a[href=#]'));
|
||||
}},
|
||||
|
||||
testSelectorWithTagNameAndWhitespaceTokenizedAttributeValue: function() {with(this) {
|
||||
assertEnumEqual($('link_1', 'link_2'), $$('a[class~="internal"]'));
|
||||
assertEnumEqual($('link_1', 'link_2'), $$('a[class~=internal]'));
|
||||
}},
|
||||
|
||||
testSelectorWithUniversalAndHyphenTokenizedAttributeValue: function() {with(this) {
|
||||
assertEnumEqual([$('item_3')], $$('*[xml:lang|="es"]'));
|
||||
assertEnumEqual([$('item_3')], $$('*[xml:lang|="ES"]'));
|
||||
}},
|
||||
|
||||
testSelectorWithTagNameAndNegatedAttributeValue: function() {with(this) {
|
||||
assertEnumEqual([], $$('a[href!=#]'));
|
||||
}},
|
||||
|
||||
test$$WithNestedAttributeSelectors: function() {with(this) {
|
||||
assertEnumEqual([$('strong')], $$('div[style] p[id] strong'));
|
||||
}},
|
||||
|
||||
testSelectorWithMultipleConditions: function() {with(this) {
|
||||
assertEnumEqual([$('link_3')], $$('a[class~=external][href="#"]'));
|
||||
assertEnumEqual([], $$('a[class~=external][href!="#"]'));
|
||||
}},
|
||||
|
||||
testSelectorMatchElements: function() {with(this) {
|
||||
assertElementsMatch(Selector.matchElements($('list').descendants(), 'li'), '#item_1', '#item_2', '#item_3');
|
||||
assertElementsMatch(Selector.matchElements($('fixtures').descendants(), 'a.internal'), '#link_1', '#link_2');
|
||||
assertEnumEqual([], Selector.matchElements($('fixtures').descendants(), 'p.last'));
|
||||
}},
|
||||
|
||||
testSelectorFindElement: function() {with(this) {
|
||||
assertElementMatches(Selector.findElement($('list').descendants(), 'li'), 'li#item_1.first');
|
||||
assertElementMatches(Selector.findElement($('list').descendants(), 'li', 1), 'li#item_2');
|
||||
assertElementMatches(Selector.findElement($('list').descendants(), 'li#item_3'), 'li');
|
||||
assertEqual(undefined, Selector.findElement($('list').descendants(), 'em'));
|
||||
}},
|
||||
// Added by TDD - 2007.02.20
|
||||
$RunBenchmarks = false;
|
||||
|
||||
testSelectorWithSpaceInAttributeValue: function() {with(this) {
|
||||
assertEnumEqual([$('with_title')], $$('cite[title="hello world!"]'));
|
||||
}}
|
||||
}, 'testlog');
|
||||
new Test.Unit.Runner({
|
||||
|
||||
testSelectorWithTagName: function() {with(this) {
|
||||
assertEnumEqual($A(document.getElementsByTagName('li')), $$('li'));
|
||||
assertEnumEqual([$('strong')], $$('strong'));
|
||||
assertEnumEqual([], $$('nonexistent'));
|
||||
assertEnumEqual($A(document.getElementsByTagName('*')), $$('*'));
|
||||
}},
|
||||
|
||||
testSelectorWithId: function() {with(this) {
|
||||
assertEnumEqual([$('fixtures')], $$('#fixtures'));
|
||||
assertEnumEqual([], $$('#nonexistent'));
|
||||
assertEnumEqual([$('troubleForm')], $$('#troubleForm'));
|
||||
}},
|
||||
|
||||
testSelectorWithClassName: function() {with(this) {
|
||||
assertEnumEqual($('p', 'link_1', 'item_1'), $$('.first'));
|
||||
assertEnumEqual([], $$('.second'));
|
||||
}},
|
||||
|
||||
testSelectorWithTagNameAndId: function() {with(this) {
|
||||
assertEnumEqual([$('strong')], $$('strong#strong'));
|
||||
assertEnumEqual([], $$('p#strong'));
|
||||
}},
|
||||
|
||||
testSelectorWithTagNameAndClassName: function() {with(this) {
|
||||
assertEnumEqual($('link_1', 'link_2'), $$('a.internal'));
|
||||
assertEnumEqual([$('link_2')], $$('a.internal.highlight'));
|
||||
assertEnumEqual([$('link_2')], $$('a.highlight.internal'));
|
||||
assertEnumEqual([], $$('a.highlight.internal.nonexistent'));
|
||||
}},
|
||||
|
||||
testSelectorWithIdAndClassName: function() {with(this) {
|
||||
assertEnumEqual([$('link_2')], $$('#link_2.internal'));
|
||||
assertEnumEqual([$('link_2')], $$('.internal#link_2'));
|
||||
assertEnumEqual([$('link_2')], $$('#link_2.internal.highlight'));
|
||||
assertEnumEqual([], $$('#link_2.internal.nonexistent'));
|
||||
}},
|
||||
|
||||
testSelectorWithTagNameAndIdAndClassName: function() {with(this) {
|
||||
assertEnumEqual([$('link_2')], $$('a#link_2.internal'));
|
||||
assertEnumEqual([$('link_2')], $$('a.internal#link_2'));
|
||||
assertEnumEqual([$('item_1')], $$('li#item_1.first'));
|
||||
assertEnumEqual([], $$('li#item_1.nonexistent'));
|
||||
assertEnumEqual([], $$('li#item_1.first.nonexistent'));
|
||||
}},
|
||||
|
||||
test$$MatchesAncestryWithTokensSeparatedByWhitespace: function() {with(this) {
|
||||
assertEnumEqual($('em', 'span'), $$('#fixtures a *'));
|
||||
assertEnumEqual([$('p')], $$('div#fixtures p'));
|
||||
}},
|
||||
|
||||
test$$CombinesResultsWhenMultipleExpressionsArePassed: function() {with(this) {
|
||||
assertEnumEqual($('link_1', 'link_2', 'item_1', 'item_2', 'item_3'), $$('#p a', ' ul#list li '));
|
||||
}},
|
||||
|
||||
testSelectorWithTagNameAndAttributeExistence: function() {with(this) {
|
||||
assertEnumEqual($$('#fixtures h1'), $$('h1[class]'));
|
||||
assertEnumEqual($$('#fixtures h1'), $$('h1[CLASS]'));
|
||||
assertEnumEqual([$('item_3')], $$('li#item_3[class]'));
|
||||
}},
|
||||
|
||||
testSelectorWithTagNameAndSpecificAttributeValue: function() {with(this) {
|
||||
assertEnumEqual($('link_1', 'link_2', 'link_3'), $$('a[href="#"]'));
|
||||
assertEnumEqual($('link_1', 'link_2', 'link_3'), $$('a[href=#]'));
|
||||
}},
|
||||
|
||||
testSelectorWithTagNameAndWhitespaceTokenizedAttributeValue: function() {with(this) {
|
||||
assertEnumEqual($('link_1', 'link_2'), $$('a[class~="internal"]'));
|
||||
assertEnumEqual($('link_1', 'link_2'), $$('a[class~=internal]'));
|
||||
}},
|
||||
|
||||
testSelectorWithUniversalAndHyphenTokenizedAttributeValue: function() {with(this) {
|
||||
assertEnumEqual([$('item_3')], $$('*[xml:lang|="es"]'));
|
||||
assertEnumEqual([$('item_3')], $$('*[xml:lang|="ES"]'));
|
||||
}},
|
||||
|
||||
testSelectorWithTagNameAndNegatedAttributeValue: function() {with(this) {
|
||||
assertEnumEqual([], $$('a[href!=#]'));
|
||||
}},
|
||||
|
||||
test$$WithNestedAttributeSelectors: function() {with(this) {
|
||||
assertEnumEqual([$('strong')], $$('div[style] p[id] strong'));
|
||||
}},
|
||||
|
||||
testSelectorWithMultipleConditions: function() {with(this) {
|
||||
assertEnumEqual([$('link_3')], $$('a[class~=external][href="#"]'));
|
||||
assertEnumEqual([], $$('a[class~=external][href!="#"]'));
|
||||
}},
|
||||
|
||||
testSelectorMatchElements: function() {with(this) {
|
||||
assertElementsMatch(Selector.matchElements($('list').descendants(), 'li'), '#item_1', '#item_2', '#item_3');
|
||||
assertElementsMatch(Selector.matchElements($('fixtures').descendants(), 'a.internal'), '#link_1', '#link_2');
|
||||
assertEnumEqual([], Selector.matchElements($('fixtures').descendants(), 'p.last'));
|
||||
}},
|
||||
|
||||
testSelectorFindElement: function() {with(this) {
|
||||
assertElementMatches(Selector.findElement($('list').descendants(), 'li'), 'li#item_1.first');
|
||||
assertElementMatches(Selector.findElement($('list').descendants(), 'li', 1), 'li#item_2');
|
||||
assertElementMatches(Selector.findElement($('list').descendants(), 'li#item_3'), 'li');
|
||||
assertEqual(undefined, Selector.findElement($('list').descendants(), 'em'));
|
||||
}},
|
||||
|
||||
testSelectorWithSpaceInAttributeValue: function() {with(this) {
|
||||
assertEnumEqual([$('with_title')], $$('cite[title="hello world!"]'));
|
||||
}},
|
||||
|
||||
// AND NOW COME THOSE NEW TESTS AFTER ANDREW'S REWRITE!
|
||||
|
||||
testSelectorWithNamespacedAttributes: function() { with(this) {
|
||||
if (Prototype.BrowserFeatures.XPath) {
|
||||
assertUndefined(new Selector('html[xml:lang]').xpath);
|
||||
assertUndefined(new Selector('body p[xml:lang]').xpath);
|
||||
} else
|
||||
info("Could not test XPath bypass: no XPath to begin with!");
|
||||
}},
|
||||
|
||||
testSelectorWithChild: function() { with(this) {
|
||||
assertEnumEqual($('link_1', 'link_2'), $$('p.first > a'));
|
||||
assertEnumEqual($('father', 'uncle'), $$('div#grandfather > div'));
|
||||
assertEnumEqual($('level2_1', 'level2_2'), $$('#level1>span'));
|
||||
assertEnumEqual($('level2_1', 'level2_2'), $$('#level1 > span'));
|
||||
assertEnumEqual($('level3_1', 'level3_2'), $$('#level2_1 > *'));
|
||||
$RunBenchmarks && wait(500, function() {
|
||||
benchmark(function() { $$('#level1 > span') }, 1000);
|
||||
});
|
||||
}},
|
||||
|
||||
testSelectorWithAdjacence: function() { with(this) {
|
||||
assertEnumEqual([$('uncle')], $$('div.brothers + div.brothers'));
|
||||
assertEnumEqual([$('uncle')], $$('div.brothers + div'));
|
||||
assertEqual($('level2_2'), $$('#level2_1+span').reduce());
|
||||
assertEqual($('level2_2'), $$('#level2_1 + span').reduce());
|
||||
assertEqual($('level2_2'), $$('#level2_1 + *').reduce());
|
||||
assertEnumEqual([], $$('#level2_2 + span'));
|
||||
assertEqual($('level3_2'), $$('#level3_1 + span').reduce());
|
||||
assertEqual($('level3_2'), $$('#level3_1 + *').reduce());
|
||||
assertEnumEqual([], $$('#level3_2 + *'));
|
||||
assertEnumEqual([], $$('#level3_1 + em'));
|
||||
$RunBenchmarks && wait(500, function() {
|
||||
benchmark(function() { $$('#level3_1 + span') }, 1000);
|
||||
});
|
||||
}},
|
||||
|
||||
testSelectorWithLaterSibling: function() { with(this) {
|
||||
assertEnumEqual([$('list')], $$('h1 ~ ul'));
|
||||
assertEqual($('level2_2'), $$('#level2_1 ~ span').reduce());
|
||||
assertEnumEqual($('level2_2', 'level2_3'), $$('#level2_1 ~ *').reduce());
|
||||
assertEnumEqual([], $$('#level2_2 ~ span'));
|
||||
assertEnumEqual([], $$('#level3_2 ~ *'));
|
||||
assertEnumEqual([], $$('#level3_1 ~ em'));
|
||||
assertEnumEqual([$('level3_2')], $$('#level3_1 ~ #level3_2'));
|
||||
assertEnumEqual([$('level3_2')], $$('span ~ #level3_2'));
|
||||
assertEnumEqual([], $$('div ~ #level3_2'));
|
||||
assertEnumEqual([], $$('div ~ #level2_3'));
|
||||
$RunBenchmarks && wait(500, function() {
|
||||
benchmark(function() { $$('#level2_1 ~ span') }, 1000);
|
||||
});
|
||||
}},
|
||||
|
||||
testSelectorWithNewAttributeOperators: function() { with(this) {
|
||||
assertEnumEqual($('father', 'uncle'), $$('div[class^=bro]'), 'matching beginning of string');
|
||||
assertEnumEqual($('father', 'uncle'), $$('div[class$=men]'), 'matching end of string');
|
||||
assertEnumEqual($('father', 'uncle'), $$('div[class*="ers m"]'), 'matching substring')
|
||||
assertEnumEqual($('level2_1', 'level2_2', 'level2_3'), $$('#level1 *[id^="level2_"]'));
|
||||
assertEnumEqual($('level2_1', 'level2_2', 'level2_3'), $$('#level1 *[id^=level2_]'));
|
||||
assertEnumEqual($('level2_1', 'level3_1'), $$('#level1 *[id$="_1"]'));
|
||||
assertEnumEqual($('level2_1', 'level3_1'), $$('#level1 *[id$=_1]'));
|
||||
assertEnumEqual($('level2_1', 'level3_2', 'level2_2', 'level2_3'), $$('#level1 *[id*="2"]'));
|
||||
assertEnumEqual($('level2_1', 'level3_2', 'level2_2', 'level2_3'), $$('#level1 *[id*=2]'));
|
||||
$RunBenchmarks && wait(500, function() {
|
||||
benchmark(function() { $$('#level1 *[id^=level2_]') }, 1000, '[^=]');
|
||||
benchmark(function() { $$('#level1 *[id$=_1]') }, 1000, '[$=]');
|
||||
benchmark(function() { $$('#level1 *[id*=_2]') }, 1000, '[*=]');
|
||||
});
|
||||
}},
|
||||
|
||||
testSelectorWithDuplicates: function() { with(this) {
|
||||
assertEnumEqual($$('div div'), $$('div div').uniq());
|
||||
assertEnumEqual($('dupL2', 'dupL3', 'dupL4', 'dupL5'), $$('#dupContainer span span'));
|
||||
$RunBenchmarks && wait(500, function() {
|
||||
benchmark(function() { $$('#dupContainer span span') }, 1000);
|
||||
});
|
||||
}},
|
||||
|
||||
testSelectorWithFirstLastOnlyNthNthLastChild: function() { with(this) {
|
||||
assertEnumEqual([$('level2_1')], $$('#level1>*:first-child'));
|
||||
assertEnumEqual($('level2_1', 'level3_1', 'level_only_child'), $$('#level1 *:first-child'));
|
||||
assertEnumEqual([$('level2_3')], $$('#level1>*:last-child'));
|
||||
assertEnumEqual($('level3_2', 'level_only_child', 'level2_3'), $$('#level1 *:last-child'));
|
||||
assertEnumEqual([$('level2_3')], $$('#level1>div:last-child'));
|
||||
assertEnumEqual([$('level2_3')], $$('#level1 div:last-child'));
|
||||
assertEnumEqual([], $$('#level1>div:first-child'));
|
||||
assertEnumEqual([], $$('#level1>span:last-child'));
|
||||
assertEnumEqual($('level2_1', 'level3_1'), $$('#level1 span:first-child'));
|
||||
assertEnumEqual([], $$('#level1:first-child'));
|
||||
assertEnumEqual([], $$('#level1>*:only-child'));
|
||||
assertEnumEqual([$('level_only_child')], $$('#level1 *:only-child'));
|
||||
assertEnumEqual([], $$('#level1:only-child'));
|
||||
assertEnumEqual([$('link_2')], $$('#p *:nth-last-child(2)'), 'nth-last-child');
|
||||
assertEnumEqual([$('link_2')], $$('#p *:nth-child(3)'), 'nth-child');
|
||||
assertEnumEqual([$('link_2')], $$('#p a:nth-child(3)'), 'nth-child');
|
||||
$RunBenchmarks && wait(500, function() {
|
||||
benchmark(function() { $$('#level1 *:first-child') }, 1000, ':first-child');
|
||||
benchmark(function() { $$('#level1 *:last-child') }, 1000, ':last-child');
|
||||
benchmark(function() { $$('#level1 *:only-child') }, 1000, ':only-child');
|
||||
});
|
||||
}},
|
||||
|
||||
testSelectorWithFirstLastNthNthLastOfType: function() {with(this) {
|
||||
assertEnumEqual([$('link_2')], $$('#p a:nth-of-type(2)'), 'nth-of-type');
|
||||
assertEnumEqual([$('link_1')], $$('#p a:nth-of-type(1)'), 'nth-of-type');
|
||||
assertEnumEqual([$('link_2')], $$('#p a:nth-last-of-type(1)'), 'nth-last-of-type');
|
||||
assertEnumEqual([$('link_1')], $$('#p a:first-of-type'), 'first-of-type');
|
||||
assertEnumEqual([$('link_2')], $$('#p a:last-of-type'), 'last-of-type');
|
||||
}},
|
||||
|
||||
testSelectorWithNot: function() {with(this) {
|
||||
assertEnumEqual([$('link_2')], $$('#p a:not(:first-of-type)'));
|
||||
assertEnumEqual([$('link_1')], $$('#p a:not(:last-of-type)'));
|
||||
assertEnumEqual([$('link_2')], $$('#p a:not(:nth-of-type(1))'));
|
||||
assertEnumEqual([$('link_1')], $$('#p a:not(:nth-last-of-type(1))'));
|
||||
assertEnumEqual([$('link_2')], $$('#p a:not([rel~=nofollow])'));
|
||||
assertEnumEqual([$('link_2')], $$('#p a:not([rel^=external])'));
|
||||
assertEnumEqual([$('link_2')], $$('#p a:not([rel$=nofollow])'));
|
||||
}},
|
||||
|
||||
testSelectorWithEnabledDisabledChecked: function() {with(this) {
|
||||
assertEnumEqual([$('disabled_text_field')], $$('#troubleForm > *:disabled'));
|
||||
assertEnumEqual($('troubleForm').getInputs().without($('disabled_text_field')), $$('#troubleForm > *:enabled'));
|
||||
assertEnumEqual($('checked_box', 'checked_radio'), $$('#troubleForm *:checked'));
|
||||
}},
|
||||
|
||||
testSelectorWithEmpty: function() {with(this) {
|
||||
assertEnumEqual($('level3_1', 'level3_2', 'level_only_child', 'level2_3'), $$('#level1 *:empty'));
|
||||
assertEnumEqual([$('level_only_child')], $$('#level_only_child:empty'));
|
||||
}},
|
||||
|
||||
testIdenticalResultsFromEquivalentSelectors: function() {with(this) {
|
||||
assertEnumEqual($$('div.brothers'), $$('div[class~=brothers]'));
|
||||
assertEnumEqual($$('div.brothers'), $$('div[class~=brothers].brothers'));
|
||||
assertEnumEqual($$('div:not(.brothers)'), $$('div:not([class~=brothers])'));
|
||||
assertEnumEqual($$('li ~ li'), $$('li:not(:first-child)'));
|
||||
assertEnumEqual($$('ul > li'), $$('ul > li:nth-child(n)'));
|
||||
assertEnumEqual($$('ul > li:nth-child(even)'), $$('ul > li:nth-child(2n)'));
|
||||
assertEnumEqual($$('ul > li:nth-child(odd)'), $$('ul > li:nth-child(2n+1)'));
|
||||
assertEnumEqual($$('ul > li:first-child'), $$('ul > li:nth-child(1)'));
|
||||
assertEnumEqual($$('ul > li:last-child'), $$('ul > li:nth-last-child(1)'));
|
||||
assertEnumEqual($$('#troubleForm *:enabled'), $$('#troubleForm *:not(:disabled)'));
|
||||
}},
|
||||
|
||||
testSelectorsThatShouldReturnNothing: function() {with(this) {
|
||||
assertEnumEqual([], $$('span:empty > *'));
|
||||
assertEnumEqual([], $$('div.brothers:not(.brothers)'));
|
||||
assertEnumEqual([], $$('#level2_2 :only-child:not(:last-child)'));
|
||||
assertEnumEqual([], $$('#level2_2 :only-child:not(:first-child)'));
|
||||
}},
|
||||
|
||||
testCommasFor$$: function() {with(this) {
|
||||
assertEnumEqual($('list', 'p', 'link_1', 'item_1', 'item_3', 'troubleForm'), $$('#list, .first,*[xml:lang="es-us"] , #troubleForm'));
|
||||
assertEnumEqual($('list', 'p', 'link_1', 'item_1', 'item_3', 'troubleForm'), $$('#list, .first,', '*[xml:lang="es-us"] , #troubleForm'));
|
||||
assertEnumEqual($('commaParent', 'commaChild'), $$('form[title*="commas,"], input[value="#commaOne,#commaTwo"]'));
|
||||
assertEnumEqual($('commaParent', 'commaChild'), $$('form[title*="commas,"]', 'input[value="#commaOne,#commaTwo"]'));
|
||||
}}
|
||||
}, 'testlog');
|
||||
// ]]>
|
||||
</script>
|
||||
</body>
|
||||
|
|
Loading…
Reference in New Issue