Skip to content
Browse files

Big update, see README

  • Loading branch information...
1 parent da5598f commit fb41724c200abde0dfc03090d14813d694cfbe2f @sylvinus committed
Showing with 1,394 additions and 803 deletions.
  1. +1 −0 .gitignore
  2. +108 −33 README.md
  3. +227 −151 lib/crawler.js
  4. +17 −20 package.json
  5. +10 −0 test/mockserver.js
  6. +0 −554 test/selector.js
  7. +0 −26 test/simple.js
  8. +16 −2 test/testrunner.js
  9. +39 −0 test/units/errors.js
  10. +29 −0 test/units/forceutf8.js
  11. +64 −0 test/units/simple.js
  12. +33 −17 test/{index.html → units/sizzle.html}
  13. +848 −0 test/units/sizzle.js
  14. +2 −0 vendor/jquery-1.8.1.min.js
View
1 .gitignore
@@ -1 +1,2 @@
node_modules
+.DS_Store
View
141 README.md
@@ -1,58 +1,58 @@
-[![build status](https://secure.travis-ci.org/joshfire/node-crawler.png)](http://travis-ci.org/joshfire/node-crawler)
node-crawler
------------
-How to install
+node-crawler aims to be the best crawling/scraping package for Node.
- $ npm install crawler
+It features:
+ * A clean, simple API
+ * server-side DOM & automatic jQuery insertion
+ * Configurable pool size and retries
+ * Priority of requests
+ * forceUTF8 mode to let node-crawler deal for you with charset detection and conversion
+ * A local cache
-How to test
+The argument for creating this package was made at ParisJS #2 in 2010 ( [lightning talk slides](http://www.slideshare.net/sylvinus/web-crawling-with-nodejs) )
- $ node test/simple.js
- $ node test/testrunner.js
-
+Help & Forks welcomed!
-Why / What ?
-------------
-
-For now just check my [lightning talk slides](http://www.slideshare.net/sylvinus/web-crawling-with-nodejs)
+How to install
+--------------
-Help & Forks welcomed! This is just starting for now.
+ $ npm install crawler
-Rough todolist :
-
- * Make Sizzle tests pass (jsdom bug? https://github.com/tmpvar/jsdom/issues#issue/81)
- * More crawling tests
- * Document the API
- * Get feedback on featureset for a 1.0 release (option for autofollowing links?)
- * Check how we can support other mimetypes than HTML
- * Add+test timeout parameter
- * Option to wait for callback to finish before freeing the pool resource (via another callback like next())
- * Events on queue empty / full
-API
----
+Crash course
+------------
- var Crawler = require("node-crawler").Crawler;
+ var Crawler = require("crawler").Crawler;
var c = new Crawler({
"maxConnections":10,
+
+ // This will be called for each crawled page
"callback":function(error,result,$) {
+
+ // $ is a jQuery instance scoped to the server-side DOM of the page
$("#content a:link").each(function(a) {
c.queue(a.href);
- })
+ });
}
});
- // Queue a list of URLs, with default callback
- c.queue(["http://jamendo.com/","http://tedxparis.com", ...]);
+ // Queue just one URL, with default callback
+ c.queue("http://joshfire.com");
+
+ // Queue a list of URLs
+ c.queue(["http://jamendo.com/","http://tedxparis.com"]);
- // Queue URLs with custom callbacks
+ // Queue URLs with custom callbacks & parameters
c.queue([{
- "uri":"http://parisjs.org/register",
- "method":"POST",
- "callback":function(error,result,$) {
- $("div:contains(Thank you)").after(" very much");
+ "uri":"http://parishackers.org/",
+ "jQuery":false,
+
+ // The global callback won't be called
+ "callback":function(error,result) {
+ console.log("Grabbed",result.body.length,"bytes");
}
}]);
@@ -61,10 +61,85 @@ API
"html":"<p>This is a <strong>test</strong></p>"
}]);
+
+Options reference
+-----------------
+
+You can pass these options to the Crawler() constructor if you want them to be global or as
+items in the queue() calls if you want them to be specific to that item (overwriting global options)
+
+This options list is a strict superset of mikeal's request options and will be directly passed to
+the request() method.
+
+Basic request options:
+
+ * uri: String, the URL you want to crawl
+ * timeout : Number, in milliseconds (Default 60000)
+ * method, xxx: All mikeal's requests options are accepted
+
+Callbacks:
+
+ * callback(error, result, $): A request was completed
+ * onDrain(): There is no more queued requests
+
+Pool options:
+
+ * maxConnections: Number, Size of the worker pool (Default 10),
+ * priorityRange: Number, Range of acceptable priorities starting from 0 (Default 10),
+ * priority: Number, Priority of this request (Default 5),
+
+Retry options:
+
+ * retries: Number of retries if the request fails (Default 3),
+ * retryTimeout: Number of milliseconds to wait before retrying (Default 10000),
+
+Server-side DOM options:
+
+ * jQuery: Boolean, if true creates a server-side DOM and adds jQuery (Default true)
+ * jQueryUrl: String, path to the jQuery file you want to insert (Defaults to bundled jquery-1.8.1.min.js)
+
+Charset encoding:
+
+ * forceUTF8: Boolean, if true will try to detect the page charset and convert it to UTF8 if necessary. Never worry about encoding anymore! (Default false),
+
+Cache:
+
+ * cache: Boolean, if true stores requests in memory (Default false)
+ * skipDuplicates: Boolean, if true skips URIs that were already crawled, without even calling callback() (Default false)
+
+
+
+How to test
+-----------
+
+ $ npm install && npm test
+
+Feel free to add more tests!
+
+
+Rough todolist
+--------------
+
+ * Make Sizzle tests pass (jsdom bug? https://github.com/tmpvar/jsdom/issues#issue/81)
+ * More crawling tests
+ * Document the API
+ * Get feedback on featureset for a 1.0 release (option for autofollowing links?)
+ * Check how we can support other mimetypes than HTML
+ * Option to wait for callback to finish before freeing the pool resource (via another callback like next())
+
ChangeLog
---------
+0.2.0
+ - Updated code & dependencies for node 0.6/0.8, cleaned package.json
+ - Added a forceUTF8 mode
+ - Added real unit tests & travis-ci
+ - Added some docs!
+ - Added Crawler.onDrain()
+ - Code refactor
+ - [BACKWARD-INCOMPATIBLE] Timeout parameters now in milliseconds (weren't documented)
+
0.1.0
- Updated dependencies, notably node 0.4.x
- Fixes jQuery being redownloaded at each page + include it in the tree
View
378 lib/crawler.js
@@ -2,199 +2,275 @@
var http = require('http'),
path = require('path'),
url = require('url'),
- util = require('util'),
request = require('request'),
- jQuery = require('jquery'),
+ _ = require('underscore'),
+ jschardet = require('jschardet'),
+ Iconv = require('iconv').Iconv,
jsdom = require('jsdom'),
Pool = require('generic-pool').Pool;
exports.Crawler = function(options) {
+ var self = this;
+
//Default options
- this.options = jQuery.extend({},{
- timeout: 60,
+ self.options = _.extend({
+ timeout: 60000,
jQuery: true,
- jQueryUrl: require.resolve('jquery'),
+ jQueryUrl: path.resolve(__dirname,"../vendor/jquery-1.8.1.min.js"),
maxConnections: 10,
priorityRange: 10,
priority: 5,
retries: 3,
- retryTimeout: 10,
+ forceUTF8: false,
+ retryTimeout: 10000,
method: "GET",
cache: false, //false,true, [ttl?]
skipDuplicates: false,
- priority: 0
+ onDrain: false
},options);
- //Do talks one by one
- this.pool = Pool({
- name : 'crawler',
- //log : this.options.debug,
- max : this.options.maxConnections,
- priorityRange:this.options.priorityRange,
- create : function(callback) {
+ // Don't make these options persist to individual queries
+ var masterOnlyOptions = ["maxConnections", "priorityRange", "onDrain"];
+
+ //Setup a worker pool w/ https://github.com/coopernurse/node-pool
+ self.pool = Pool({
+ name : 'crawler',
+ //log : self.options.debug,
+ max : self.options.maxConnections,
+ priorityRange: self.options.priorityRange,
+ create : function(callback) {
callback(1);
- },
- destroy : function(client) { }
+ },
+ destroy : function(client) {
+
+ }
});
+
+ var plannedQueueCallsCount = 0;
+ var queuedCount = 0;
+
+ var release = function(opts) {
+
+ queuedCount--;
+ // console.log("Released... count",queuedCount,plannedQueueCallsCount);
+
+ if (opts._poolRef) self.pool.release(opts._poolRef);
+
+ // Pool stats are behaving weird - have to implement our own counter
+ // console.log("POOL STATS",{"name":self.pool.getName(),"size":self.pool.getPoolSize(),"avail":self.pool.availableObjectsCount(),"waiting":self.pool.waitingClientsCount()});
+
+
+ if (queuedCount+plannedQueueCallsCount === 0) {
+ if (self.options.onDrain) self.options.onDrain();
+ }
+ }
+
+ self.onDrain = function() {};
- this.cache = {};
+ self.cache = {};
+
+ var useCache = function(opts) {
+ return (opts.uri && (opts.cache || opts.skipDuplicates) && (opts.method=="GET" || opts.method=="HEAD"));
+ };
+
+ self.request = function(opts) {
+
+ // console.log("OPTS",opts);
+
+ if (useCache(opts)) {
+
+ var cacheData = self.cache[opts.uri];
+
+ //If a query has already been made to self URL, don't callback again
+ if (cacheData) {
+
+ // Make sure we actually have cached data, and not just a note
+ // that the page was already crawled
+ if (_.isArray(cacheData)) {
+ self.onContent(null,opts,cacheData[0],true);
+ } else {
+ release(opts);
+ }
+ return;
+
+ }
+ }
+
+ if (opts.debug) {
+ console.log(opts.method+" "+opts.uri+" ...");
+ }
+
+ // Cloning keeps the opts parameter clean:
+ // - some versions of "request" apply the second parameter as a
+ // property called "callback" to the first parameter
+ // - keeps the query object fresh in case of a retry
+ // Doing parse/stringify instead of _.clone will do a deep clone and remove functions
+
+ var ropts = JSON.parse(JSON.stringify(opts));
+
+ if (!ropts.headers) ropts.headers={};
+ if (opts.forceUTF8) {
+ if (!ropts.headers["Accept-Charset"] && !ropts.headers["accept-charset"]) ropts.headers["Accept-Charset"] = 'utf-8;q=0.7,*;q=0.3';
+ if (!ropts.encoding) ropts.encoding=null;
+ }
+
+ request(ropts, function(error,response,body) {
+ if (error) return self.onContent(error, opts);
+
+ response.uri = opts.uri;
+ self.onContent(error,opts,response,false);
+ });
+ };
+
+ self.onContent = function (error, toQueue, response, fromCache) {
+
+ if (true || toQueue.debug) {
+ if (error) {
+ console.log("Error "+error+" when fetching "+toQueue.uri+(toQueue.retries?" ("+toQueue.retries+" retries left)":""));
+ } else {
+ console.log("Got "+(toQueue.uri||"html")+" ("+response.body.length+" bytes)...");
+ }
+ }
+
+ if (error) {
+ if (toQueue.retries) {
+ plannedQueueCallsCount++;
+ setTimeout(function() {
+ toQueue.retries--;
+ plannedQueueCallsCount--;
+ self.queue(toQueue);
+ },toQueue.retryTimeout);
+
+ } else if (toQueue.callback) {
+ toQueue.callback(error);
+ }
+
+ return release(toQueue);
+ }
+
+ if (toQueue.forceUTF8) {
+ //TODO check http header or meta equiv?
+ var detected = jschardet.detect(response.body);
+
+ if (detected && detected.encoding) {
+ if (toQueue.debug) {
+ console.log("Detected charset "+detected.encoding+" ("+Math.floor(detected.confidence*100)+"% confidence)");
+ }
+ if (detected.encoding!="utf-8" && detected.encoding!="ascii") {
+ var iconv = new Iconv(detected.encoding, "UTF-8//TRANSLIT//IGNORE");
+ response.body = iconv.convert(response.body).toString();
+ } else if (typeof response.body != "string") {
+ response.body = response.body.toString();
+ }
+
+ } else {
+ response.body = response.body.toString("utf8"); //hope for the best
+ }
- this.queue = function(item) {
+ }
+
+ if (useCache(toQueue) && !fromCache) {
+ if (toQueue.cache) {
+ self.cache[toQueue.uri] = [response];
+
+ //If we don't cache but still want to skip duplicates we have to maintain a list of fetched URLs.
+ } else if (toQueue.skipDuplicates) {
+ self.cache[toQueue.uri] = true;
+ }
+ }
+
+ if (!toQueue.callback) return release(toQueue);
+
+ response.options = toQueue;
+
+ if (toQueue.jQuery && toQueue.method!="HEAD") {
+
+ jsdom.env(response.body,[toQueue.jQueryUrl],function(errors,window) {
+ if (errors) {
+ toQueue.callback(errors);
+ } else {
+ response.window = window;
+ toQueue.callback(null,response,window.jQuery);
+ }
+
+ release(toQueue);
+ });
+
+ } else {
+
+ toQueue.callback(null,response);
+ release(toQueue);
+ }
+
+ };
+
+ self.queue = function(item) {
//Did we get a list ? Queue all the URLs.
- if (item instanceof Array) {
+ if (_.isArray(item)) {
for (var i=0;i<item.length;i++) {
- this.queue(item[i]);
+ self.queue(item[i]);
}
return;
}
+
+ queuedCount++;
- var toQueue = {};
-
+ var toQueue=item;
+
//Allow passing just strings as URLs
- if (typeof item=="string") {
- toQueue = jQuery.extend({}, this.options, { "uri":item });
- } else {
- toQueue = jQuery.extend({}, this.options, item);
+ if (_.isString(item)) {
+ toQueue = {"uri":item};
}
+
+ _.defaults(toQueue,self.options);
+
+ // Cleanup options
+ _.each(masterOnlyOptions,function(o) {
+ delete toQueue[o];
+ });
- var useCache = function() {
- return ((toQueue.cache || toQueue.skipDuplicates) && (toQueue.method=="GET" || toQueue.method=="HEAD"));
- };
-
- // If duplicate skipping is enabled, avoid queueing entirely for URLs we
- // already crawled
- if (toQueue.skipDuplicates && this.cache[toQueue.uri]) {
- return;
+ // If duplicate skipping is enabled, avoid queueing entirely for URLs we already crawled
+ if (toQueue.skipDuplicates && self.cache[toQueue.uri]) {
+ return release(toQueue);
}
- var self = this;
- this.pool.acquire(function(poolRef) {
-
- var makeRequest;
-
- var onContent = function (error, response, body, fromCache) {
-
- if (toQueue.debug) {
- if (error) {
- console.log("Error "+error+" when fetching "+toQueue.uri+(toQueue.retries?" ("+toQueue.retries+" retries left)":""));
- } else {
- console.log("Got "+toQueue.uri+" ("+body.length+" bytes)...");
- }
- }
-
- if (error && toQueue.retries) {
- setTimeout(function() {
- toQueue.retries--;
- makeRequest(toQueue);
- },toQueue.retryTimeout*1000);
-
- //Don't return the poolRef yet.
- return;
- }
-
- if (useCache() && !fromCache) {
- if (toQueue.cache) {
- self.cache[toQueue.uri] = [error,response,body];
- } else {
- self.cache[toQueue.uri] = true;
- }
- }
-
- if (typeof toQueue.callback=="function") {
-
- if (error) {
- //No retries left here
- toQueue.callback(error);
-
- } else {
-
- response.content = body;
- response.request = toQueue;
-
- if (toQueue.jQuery && toQueue.method!="HEAD") {
-
- jsdom.env(response.content,[toQueue.jQueryUrl],function(errors,window) {
- if (errors) return toQueue.callback(errors);
-
- response.window = window;
- toQueue.callback(null,response,window.jQuery);
- });
-
- } else {
- toQueue.callback(null,response);
- }
- }
- }
- self.pool.release(poolRef);
- };
+ self.pool.acquire(function(err, poolRef) {
+ //TODO - which errback to call?
+ if (err) {
+ console.error("pool acquire error:",err);
+ return release(toQueue);
+ }
+
+ toQueue._poolRef = poolRef;
- //Static HTML was given
+ // We need to check again for duplicates because the cache might have
+ // been completed since we queued self task.
+ if (toQueue.skipDuplicates && self.cache[toQueue.uri]) {
+ return release(toQueue);
+ }
+
+ //Static HTML was given, skip request
if (toQueue.html) {
- onContent(null,{},toQueue.html,false);
-
+ self.onContent(null,toQueue,{body:toQueue.html},false);
+ return;
+ }
+
//Make a HTTP request
+ if (typeof toQueue.uri=="function") {
+ toQueue.uri(function(uri) {
+ toQueue.uri=uri;
+ self.request(toQueue);
+ });
} else {
-
- makeRequest = function(q) {
-
- if (useCache()) {
- if (self.cache[q.uri]) {
-
- //If a query has already been made to this URL, don't callback again
- if (!q.skipDuplicates) {
- var cacheData = self.cache[q.uri];
-
- // Make sure we actually have cached data, and not just a note
- // that the page was already crawled
- if (Array.isArray(cacheData)) {
- onContent.apply(this, cacheData.concat(true));
- return;
- }
- } else {
- // No further action required. Return this crawling slot back to
- // the pool
- self.pool.release(poolRef);
- return;
- }
- }
- }
-
-
-
- if (q.debug) {
- console.log(q.method+" "+q.uri+" ...");
- }
-
- // Cloning keeps the q parameter clean:
- // - some versions of "request" apply the second parameter as a
- // property called "callback" to the first parameter
- // - keeps the query object fresh in case of a retry
- q = jQuery.extend({}, q);
- request(q, function(error,response,body) {
- response.uri = q.uri;
- onContent(error,response,body,false);
- });
- };
-
- if (typeof toQueue.uri=="function") {
- toQueue.uri(function(uri) {
- toQueue.uri=uri;
- makeRequest(toQueue);
- });
- } else {
- makeRequest(toQueue);
- }
-
-
+ self.request(toQueue);
}
-
},toQueue.priority);
}
-}
+};
View
37 package.json
@@ -1,11 +1,14 @@
{
"name": "crawler",
- "version": "0.1.0",
+ "version": "0.2.0",
"description": "Crawler is a web spider written with Nodejs. It gives you the full power of jQuery on the server to parse a big number of pages as they are downloaded, asynchronously.",
"keywords": [
"dom",
"javascript",
"crawling",
+ "spider",
+ "scraper",
+ "scraping",
"jquery"
],
"maintainers": [
@@ -32,29 +35,23 @@
}
,
"dependencies": {
- "request": ">= 1.9.8",
- "jsdom": ">= 0.2.0",
- "generic-pool": ">= 1.0.6",
- "qunit": ">= 0.0.7",
- "htmlparser": ">= 1.7.3",
- "jquery": ">= 1.5.1"
+ "request": "2.11.1",
+ "jsdom": "0.2.15",
+ "generic-pool": "2.0.1",
+ "htmlparser": "1.7.6",
+ "underscore": "1.3.3",
+ "jschardet": "1.0.2",
+ "iconv": "1.2.3"
},
"devDependencies": {
- "qunit": ">= 0.0.7"
+ "qunit": "0.5.8",
+ "express": "2.5.x"
+ },
+ "scripts": {
+ "test": "node test/testrunner.js"
},
- "os": [
- "linux",
- "darwin",
- "win"
- ],
- "cpu": [
- "x86",
- "ppc",
- "x86_64",
- "x64"
- ],
"engines": [
- "node"
+ "node >=0.6.x"
],
"directories": {
"lib": "lib"
View
10 test/mockserver.js
@@ -0,0 +1,10 @@
+var express = require('express');
+var app = express.createServer();
+
+app.get('/timeout', function(req, res){
+ setTimeout(function() {
+ res.send('<html><body>ok</body></html>');
+ },req.param("timeout") || 0);
+});
+
+exports.app = app;
View
554 test/selector.js
@@ -1,554 +0,0 @@
-
-QUnit.module("selector");
-
-var path = require( "path" ).normalize( __dirname + "/.." ),
- util = require('util'),
- fs = require("fs"),
- Crawler = require(path + "/lib/crawler.js").Crawler;
-
-
-var document,window,jQuery,navigator={userAgent:"Mozilla/5.0"};
-
-var crawler = new Crawler();
-
-var myTest = function(name,actualTest) {
-
- test(name,function() {
-
- fs.readFile(require( "path" ).normalize( __dirname + "/index.html" ), 'utf-8', function (err, data) {
-
- crawler.queue({"html":data,"callback":function(error,response,$) {
-
- window = response.window;
- document = window.document;
- jQuery = $;
-
- actualTest();
-
- }});
- });
- });
-};
-
-
-
-
-/* Below are the official Sizzle tests */
-
-
-
-/**
- * Returns an array of elements with the given IDs, eg.
- * @example q("main", "foo", "bar")
- * @result [<div id="main">, <span id="foo">, <input id="bar">]
- */
-function q() {
- var r = [];
-
- for ( var i = 0; i < arguments.length; i++ ) {
- r.push( document.getElementById( arguments[i] ) );
- }
-
- return r;
-}
-
-/**
- * Asserts that a select matches the given IDs * @example t("Check for something", "//[a]", ["foo", "baar"]);
- * @result returns true if "//[a]" return two elements with the IDs 'foo' and 'baa
-r'
- */
-function t(a,b,c) {
- var f = jQuery(b).get(), s = "";
-
- for ( var i = 0; i < f.length; i++ ) {
- s += (s && ",") + '"' + f[i].id + '"';
- }
-
- same(f, q.apply(q,c), a + " (" + b + ")");
-}
-
-/**
- * Add random number to url to stop IE from caching
- *
- * @example url("data/test.html")
- * @result "data/test.html?10538358428943"
- *
- * @example url("data/test.php?foo=bar")
- * @result "data/test.php?foo=bar&10538358345554"
- */
-function url(value) {
- return value + (/\?/.test(value) ? "&" : "?") + new Date().getTime() + "" + parseInt(Math.random()*100000);
-}
-
-
-
-
-
-
-/* Actual tests */
-
-
-myTest("element", function() {
- expect(21);
- //QUnit.reset();
-
- ok( jQuery("*").size() >= 30, "Select all" );
- var all = jQuery("*"), good = true;
- for ( var i = 0; i < all.length; i++ )
- if ( all[i].nodeType == 8 )
- good = false;
- ok( good, "Select all elements, no comment nodes" );
- t( "Element Selector", "p", ["firstp","ap","sndp","en","sap","first"] );
- t( "Element Selector", "body", ["body"] );
- t( "Element Selector", "html", ["html"] );
- t( "Parent Element", "div p", ["firstp","ap","sndp","en","sap","first"] );
- equals( jQuery("param", "#object1").length, 2, "Object/param as context" );
-
- same( jQuery("p", document.getElementsByTagName("div")).get(), q("firstp","ap","sndp","en","sap","first"), "Finding elements with a context." );
- same( jQuery("p", "div").get(), q("firstp","ap","sndp","en","sap","first"), "Finding elements with a context." );
- same( jQuery("p", jQuery("div")).get(), q("firstp","ap","sndp","en","sap","first"), "Finding elements with a context." );
- same( jQuery("div").find("p").get(), q("firstp","ap","sndp","en","sap","first"), "Finding elements with a context." );
-
- same( jQuery("#form").find("select").get(), q("select1","select2","select3","select4","select5"), "Finding selects with a context." );
-
- ok( jQuery("#length").length, '&lt;input name="length"&gt; cannot be found under IE, see #945' );
- ok( jQuery("#lengthtest input").length, '&lt;input name="length"&gt; cannot be found under IE, see #945' );
-
- // Check for unique-ness and sort order
- same( jQuery("p, div p").get(), jQuery("p").get(), "Check for duplicates: p, div p" );
-
- t( "Checking sort order", "h2, h1", ["qunit-header", "qunit-banner", "qunit-userAgent"] );
- t( "Checking sort order", "h2:first, h1:first", ["qunit-header", "qunit-banner"] );
- t( "Checking sort order", "p, p a", ["firstp", "simon1", "ap", "google", "groups", "anchor1", "mark", "sndp", "en", "yahoo", "sap", "anchor2", "simon", "first"] );
-
- // Test Conflict ID
- same( jQuery("#lengthtest").find("#idTest").get(), q("idTest"), "Finding element with id of ID." );
- same( jQuery("#lengthtest").find("[name='id']").get(), q("idTest"), "Finding element with id of ID." );
- same( jQuery("#lengthtest").find("input[id='idTest']").get(), q("idTest"), "Finding elements with a context." );
-});
-/*
-if ( window.location.protocol != "file:" ) {
- myTest("XML Document Selectors", function() {
- expect(8);
- stop();
- jQuery.get("data/with_fries.xml", function(xml) {
- equals( jQuery("foo_bar", xml).length, 1, "Element Selector with underscore" );
- equals( jQuery(".component", xml).length, 1, "Class selector" );
- equals( jQuery("[class*=component]", xml).length, 1, "Attribute selector for class" );
- equals( jQuery("property[name=prop2]", xml).length, 1, "Attribute selector with name" );
- equals( jQuery("[name=prop2]", xml).length, 1, "Attribute selector with name" );
- equals( jQuery("#seite1", xml).length, 1, "Attribute selector with ID" );
- equals( jQuery("component#seite1", xml).length, 1, "Attribute selector with ID" );
- equals( jQuery("component", xml).filter("#seite1").length, 1, "Attribute selector filter with ID" );
- start();
- });
- });
-}
-*/
-
-myTest("broken", function() {
- expect(8);
- function broken(name, selector) {
- try {
- jQuery(selector);
- ok( false, name + ": " + selector );
- } catch(e){
- ok( typeof e === "string" && e.indexOf("Syntax error") >= 0,
- name + ": " + selector );
- }
- }
-
- broken( "Broken Selector", "[", [] );
- broken( "Broken Selector", "(", [] );
- broken( "Broken Selector", "{", [] );
- broken( "Broken Selector", "<", [] );
- broken( "Broken Selector", "()", [] );
- broken( "Broken Selector", "<>", [] );
- broken( "Broken Selector", "{}", [] );
- broken( "Doesn't exist", ":visble", [] );
-});
-
-myTest("id", function() {
- expect(28);
- t( "ID Selector", "#body", ["body"] );
- t( "ID Selector w/ Element", "body#body", ["body"] );
- t( "ID Selector w/ Element", "ul#first", [] );
- t( "ID selector with existing ID descendant", "#firstp #simon1", ["simon1"] );
- t( "ID selector with non-existant descendant", "#firstp #foobar", [] );
- t( "ID selector using UTF8", "#台北Táiběi", ["台北Táiběi"] );
- t( "Multiple ID selectors using UTF8", "#台北Táiběi, #台北", ["台北Táiběi","台北"] );
- t( "Descendant ID selector using UTF8", "div #台北", ["台北"] );
- t( "Child ID selector using UTF8", "form > #台北", ["台北"] );
-
- t( "Escaped ID", "#foo\\:bar", ["foo:bar"] );
- t( "Escaped ID", "#test\\.foo\\[5\\]bar", ["test.foo[5]bar"] );
- t( "Descendant escaped ID", "div #foo\\:bar", ["foo:bar"] );
- t( "Descendant escaped ID", "div #test\\.foo\\[5\\]bar", ["test.foo[5]bar"] );
- t( "Child escaped ID", "form > #foo\\:bar", ["foo:bar"] );
- t( "Child escaped ID", "form > #test\\.foo\\[5\\]bar", ["test.foo[5]bar"] );
-
- t( "ID Selector, child ID present", "#form > #radio1", ["radio1"] ); // bug #267
- t( "ID Selector, not an ancestor ID", "#form #first", [] );
- t( "ID Selector, not a child ID", "#form > #option1a", [] );
-
- t( "All Children of ID", "#foo > *", ["sndp", "en", "sap"] );
- t( "All Children of ID with no children", "#firstUL > *", [] );
-
- var a = jQuery('<div><a name="tName1">tName1 A</a><a name="tName2">tName2 A</a><div id="tName1">tName1 Div</div></div>').appendTo('#main');
- equals( jQuery("#tName1")[0].id, 'tName1', "ID selector with same value for a name attribute" );
- equals( jQuery("#tName2").length, 0, "ID selector non-existing but name attribute on an A tag" );
- a.remove();
-
- t( "ID Selector on Form with an input that has a name of 'id'", "#lengthtest", ["lengthtest"] );
-
- t( "ID selector with non-existant ancestor", "#asdfasdf #foobar", [] ); // bug #986
-
- same( jQuery("body").find("div#form").get(), [], "ID selector within the context of another element" );
-
- t( "Underscore ID", "#types_all", ["types_all"] );
- t( "Dash ID", "#fx-queue", ["fx-queue"] );
-
- t( "ID with weird characters in it", "#name\\+value", ["name+value"] );
-});
-
-myTest("class", function() {
- expect(22);
- t( "Class Selector", ".blog", ["mark","simon"] );
- t( "Class Selector", ".GROUPS", ["groups"] );
- t( "Class Selector", ".blog.link", ["simon"] );
- t( "Class Selector w/ Element", "a.blog", ["mark","simon"] );
- t( "Parent Class Selector", "p .blog", ["mark","simon"] );
-
- same( jQuery(".blog", document.getElementsByTagName("p")).get(), q("mark", "simon"), "Finding elements with a context." );
- same( jQuery(".blog", "p").get(), q("mark", "simon"), "Finding elements with a context." );
- same( jQuery(".blog", jQuery("p")).get(), q("mark", "simon"), "Finding elements with a context." );
- same( jQuery("p").find(".blog").get(), q("mark", "simon"), "Finding elements with a context." );
-
- t( "Class selector using UTF8", ".台北Táiběi", ["utf8class1"] );
- //t( "Class selector using UTF8", ".台北", ["utf8class1","utf8class2"] );
- t( "Class selector using UTF8", ".台北Táiběi.台北", ["utf8class1"] );
- t( "Class selector using UTF8", ".台北Táiběi, .台北", ["utf8class1","utf8class2"] );
- t( "Descendant class selector using UTF8", "div .台北Táiběi", ["utf8class1"] );
- t( "Child class selector using UTF8", "form > .台北Táiběi", ["utf8class1"] );
-
- t( "Escaped Class", ".foo\\:bar", ["foo:bar"] );
- t( "Escaped Class", ".test\\.foo\\[5\\]bar", ["test.foo[5]bar"] );
- t( "Descendant scaped Class", "div .foo\\:bar", ["foo:bar"] );
- t( "Descendant scaped Class", "div .test\\.foo\\[5\\]bar", ["test.foo[5]bar"] );
- t( "Child escaped Class", "form > .foo\\:bar", ["foo:bar"] );
- t( "Child escaped Class", "form > .test\\.foo\\[5\\]bar", ["test.foo[5]bar"] );
-
- var div = document.createElement("div");
- div.innerHTML = "<div class='test e'></div><div class='test'></div>";
- same( jQuery(".e", div).get(), [ div.firstChild ], "Finding a second class." );
-
- div.lastChild.className = "e";
-
- same( jQuery(".e", div).get(), [ div.firstChild, div.lastChild ], "Finding a modified class." );
-});
-
-myTest("name", function() {
- expect(15);
-
- t( "Name selector", "input[name=action]", ["text1"] );
- t( "Name selector with single quotes", "input[name='action']", ["text1"] );
- t( "Name selector with double quotes", 'input[name="action"]', ["text1"] );
-
- t( "Name selector non-input", "[name=test]", ["length", "fx-queue"] );
- t( "Name selector non-input", "[name=div]", ["fadein"] );
- t( "Name selector non-input", "*[name=iframe]", ["iframe"] );
-
- t( "Name selector for grouped input", "input[name='types[]']", ["types_all", "types_anime", "types_movie"] )
-
- same( jQuery("#form").find("input[name=action]").get(), q("text1"), "Name selector within the context of another element" );
- same( jQuery("#form").find("input[name='foo[bar]']").get(), q("hidden2"), "Name selector for grouped form element within the context of another element" );
-
- var form = jQuery("<form><input name='id'/></form>").appendTo("body");
-
- equals( form.find("input").length, 1, "Make sure that rooted queries on forms (with possible expandos) work." );
-
- form.remove();
-
- var a = jQuery('<div><a id="tName1ID" name="tName1">tName1 A</a><a id="tName2ID" name="tName2">tName2 A</a><div id="tName1">tName1 Div</div></div>').appendTo('#main').children();
-
- equals( a.length, 3, "Make sure the right number of elements were inserted." );
- equals( a[1].id, "tName2ID", "Make sure the right number of elements were inserted." );
-
- equals( jQuery("[name=tName1]")[0], a[0], "Find elements that have similar IDs" );
- equals( jQuery("[name=tName2]")[0], a[1], "Find elements that have similar IDs" );
- t( "Find elements that have similar IDs", "#tName2ID", ["tName2ID"] );
-
- a.remove();
-});
-
-myTest("multiple", function() {
- expect(4);
-
- t( "Comma Support", "h2, p", ["qunit-banner","qunit-userAgent","firstp","ap","sndp","en","sap","first"]);
- t( "Comma Support", "h2 , p", ["qunit-banner","qunit-userAgent","firstp","ap","sndp","en","sap","first"]);
- t( "Comma Support", "h2 , p", ["qunit-banner","qunit-userAgent","firstp","ap","sndp","en","sap","first"]);
- t( "Comma Support", "h2,p", ["qunit-banner","qunit-userAgent","firstp","ap","sndp","en","sap","first"]);
-});
-
-myTest("child and adjacent", function() {
- expect(27);
- t( "Child", "p > a", ["simon1","google","groups","mark","yahoo","simon"] );
- t( "Child", "p> a", ["simon1","google","groups","mark","yahoo","simon"] );
- t( "Child", "p >a", ["simon1","google","groups","mark","yahoo","simon"] );
- t( "Child", "p>a", ["simon1","google","groups","mark","yahoo","simon"] );
- t( "Child w/ Class", "p > a.blog", ["mark","simon"] );
- t( "All Children", "code > *", ["anchor1","anchor2"] );
- t( "All Grandchildren", "p > * > *", ["anchor1","anchor2"] );
- t( "Adjacent", "a + a", ["groups"] );
- t( "Adjacent", "a +a", ["groups"] );
- t( "Adjacent", "a+ a", ["groups"] );
- t( "Adjacent", "a+a", ["groups"] );
- t( "Adjacent", "p + p", ["ap","en","sap"] );
- t( "Adjacent", "p#firstp + p", ["ap"] );
- t( "Adjacent", "p[lang=en] + p", ["sap"] );
- t( "Adjacent", "a.GROUPS + code + a", ["mark"] );
- t( "Comma, Child, and Adjacent", "a + a, code > a", ["groups","anchor1","anchor2"] );
- t( "Element Preceded By", "p ~ div", ["foo", "moretests","tabindex-tests", "liveHandlerOrder", "siblingTest"] );
- t( "Element Preceded By", "#first ~ div", ["moretests","tabindex-tests", "liveHandlerOrder", "siblingTest"] );
- t( "Element Preceded By", "#groups ~ a", ["mark"] );
- t( "Element Preceded By", "#length ~ input", ["idTest"] );
- t( "Element Preceded By", "#siblingfirst ~ em", ["siblingnext"] );
-
- t( "Verify deep class selector", "div.blah > p > a", [] );
-
- t( "No element deep selector", "div.foo > span > a", [] );
-
- same( jQuery("> :first", document.getElementById("nothiddendiv")).get(), q("nothiddendivchild"), "Verify child context positional selctor" );
- same( jQuery("> :eq(0)", document.getElementById("nothiddendiv")).get(), q("nothiddendivchild"), "Verify child context positional selctor" );
- same( jQuery("> *:first", document.getElementById("nothiddendiv")).get(), q("nothiddendivchild"), "Verify child context positional selctor" );
-
- t( "Non-existant ancestors", ".fototab > .thumbnails > a", [] );
-});
-
-myTest("attributes", function() {
- expect(39);
- t( "Attribute Exists", "a[title]", ["google"] );
- t( "Attribute Exists", "*[title]", ["google"] );
- t( "Attribute Exists", "[title]", ["google"] );
- t( "Attribute Exists", "a[ title ]", ["google"] );
-
- t( "Attribute Equals", "a[rel='bookmark']", ["simon1"] );
- t( "Attribute Equals", 'a[rel="bookmark"]', ["simon1"] );
- t( "Attribute Equals", "a[rel=bookmark]", ["simon1"] );
- t( "Attribute Equals", "a[href='http://www.google.com/']", ["google"] );
- t( "Attribute Equals", "a[ rel = 'bookmark' ]", ["simon1"] );
-
- document.getElementById("anchor2").href = "#2";
- t( "href Attribute", "p a[href^=#]", ["anchor2"] );
- t( "href Attribute", "p a[href*=#]", ["simon1", "anchor2"] );
-
- t( "for Attribute", "form label[for]", ["label-for"] );
- t( "for Attribute in form", "#form [for=action]", ["label-for"] );
-
- t( "Attribute containing []", "input[name^='foo[']", ["hidden2"] );
- t( "Attribute containing []", "input[name^='foo[bar]']", ["hidden2"] );
- t( "Attribute containing []", "input[name*='[bar]']", ["hidden2"] );
- t( "Attribute containing []", "input[name$='bar]']", ["hidden2"] );
- t( "Attribute containing []", "input[name$='[bar]']", ["hidden2"] );
- t( "Attribute containing []", "input[name$='foo[bar]']", ["hidden2"] );
- t( "Attribute containing []", "input[name*='foo[bar]']", ["hidden2"] );
-
- t( "Multiple Attribute Equals", "#form input[type='radio'], #form input[type='hidden']", ["radio1", "radio2", "hidden1"] );
- t( "Multiple Attribute Equals", "#form input[type='radio'], #form input[type=\"hidden\"]", ["radio1", "radio2", "hidden1"] );
- t( "Multiple Attribute Equals", "#form input[type='radio'], #form input[type=hidden]", ["radio1", "radio2", "hidden1"] );
-
- t( "Attribute selector using UTF8", "span[lang=中文]", ["台北"] );
-
- t( "Attribute Begins With", "a[href ^= 'http://www']", ["google","yahoo"] );
- t( "Attribute Ends With", "a[href $= 'org/']", ["mark"] );
- t( "Attribute Contains", "a[href *= 'google']", ["google","groups"] );
- t( "Attribute Is Not Equal", "#ap a[hreflang!='en']", ["google","groups","anchor1"] );
-
- var opt = document.getElementById("option1a"),
- match = (window.Sizzle || window.jQuery.find).matchesSelector;
-
- opt.setAttribute("test", "");
-
- ok( match( opt, "[id*=option1][type!=checkbox]" ), "Attribute Is Not Equal Matches" );
- ok( match( opt, "[id*=option1]" ), "Attribute With No Quotes Contains Matches" );
- ok( match( opt, "[test=]" ), "Attribute With No Quotes No Content Matches" );
- ok( match( opt, "[id=option1a]" ), "Attribute With No Quotes Equals Matches" );
- ok( match( document.getElementById("simon1"), "a[href*=#]" ), "Attribute With No Quotes Href Contains Matches" );
-
- t("Empty values", "#select1 option[value='']", ["option1a"]);
- t("Empty values", "#select1 option[value!='']", ["option1b","option1c","option1d"]);
-
- t("Select options via :selected", "#select1 option:selected", ["option1a"] );
- t("Select options via :selected", "#select2 option:selected", ["option2d"] );
- t("Select options via :selected", "#select3 option:selected", ["option3b", "option3c"] );
-
- t( "Grouped Form Elements", "input[name='foo[bar]']", ["hidden2"] );
-});
-
-myTest("pseudo - child", function() {
- expect(31);
- t( "First Child", "p:first-child", ["firstp","sndp"] );
- t( "Last Child", "p:last-child", ["sap"] );
- t( "Only Child", "#main a:only-child", ["simon1","anchor1","yahoo","anchor2","liveLink1","liveLink2"] );
- t( "Empty", "ul:empty", ["firstUL"] );
- t( "Is A Parent", "p:parent", ["firstp","ap","sndp","en","sap","first"] );
-
- t( "First Child", "p:first-child", ["firstp","sndp"] );
- t( "Nth Child", "p:nth-child(1)", ["firstp","sndp"] );
- t( "Not Nth Child", "p:not(:nth-child(1))", ["ap","en","sap","first"] );
-
- // Verify that the child position isn't being cached improperly
- jQuery("p:first-child").after("<div></div>");
- jQuery("p:first-child").before("<div></div>").next().remove();
-
- t( "First Child", "p:first-child", [] );
-
- QUnit.reset();
-
- t( "Last Child", "p:last-child", ["sap"] );
- t( "Last Child", "#main a:last-child", ["simon1","anchor1","mark","yahoo","anchor2","simon","liveLink1","liveLink2"] );
-
- t( "Nth-child", "#main form#form > *:nth-child(2)", ["text1"] );
- t( "Nth-child", "#main form#form > :nth-child(2)", ["text1"] );
-
- t( "Nth-child", "#form select:first option:nth-child(3)", ["option1c"] );
- t( "Nth-child", "#form select:first option:nth-child(0n+3)", ["option1c"] );
- t( "Nth-child", "#form select:first option:nth-child(1n+0)", ["option1a", "option1b", "option1c", "option1d"] );
- t( "Nth-child", "#form select:first option:nth-child(1n)", ["option1a", "option1b", "option1c", "option1d"] );
- t( "Nth-child", "#form select:first option:nth-child(n)", ["option1a", "option1b", "option1c", "option1d"] );
- t( "Nth-child", "#form select:first option:nth-child(even)", ["option1b", "option1d"] );
- t( "Nth-child", "#form select:first option:nth-child(odd)", ["option1a", "option1c"] );
- t( "Nth-child", "#form select:first option:nth-child(2n)", ["option1b", "option1d"] );
- t( "Nth-child", "#form select:first option:nth-child(2n+1)", ["option1a", "option1c"] );
- t( "Nth-child", "#form select:first option:nth-child(3n)", ["option1c"] );
- t( "Nth-child", "#form select:first option:nth-child(3n+1)", ["option1a", "option1d"] );
- t( "Nth-child", "#form select:first option:nth-child(3n+2)", ["option1b"] );
- t( "Nth-child", "#form select:first option:nth-child(3n+3)", ["option1c"] );
- t( "Nth-child", "#form select:first option:nth-child(3n-1)", ["option1b"] );
- t( "Nth-child", "#form select:first option:nth-child(3n-2)", ["option1a", "option1d"] );
- t( "Nth-child", "#form select:first option:nth-child(3n-3)", ["option1c"] );
- t( "Nth-child", "#form select:first option:nth-child(3n+0)", ["option1c"] );
- t( "Nth-child", "#form select:first option:nth-child(-n+3)", ["option1a", "option1b", "option1c"] );
-});
-
-myTest("pseudo - misc", function() {
- expect(7);
-
- t( "Headers", ":header", ["qunit-header", "qunit-banner", "qunit-userAgent"] );
- t( "Has Children - :has()", "p:has(a)", ["firstp","ap","en","sap"] );
-
- var select = document.getElementById("select1");
- ok( (window.Sizzle || window.jQuery.find).matchesSelector( select, ":has(option)" ), "Has Option Matches" );
-
- t( "Text Contains", "a:contains(Google)", ["google","groups"] );
- t( "Text Contains", "a:contains(Google Groups)", ["groups"] );
-
- t( "Text Contains", "a:contains(Google Groups (Link))", ["groups"] );
- t( "Text Contains", "a:contains((Link))", ["groups"] );
-});
-
-
-myTest("pseudo - :not", function() {
- expect(24);
- t( "Not", "a.blog:not(.link)", ["mark"] );
-
- t( "Not - multiple", "#form option:not(:contains(Nothing),#option1b,:selected)", ["option1c", "option1d", "option2b", "option2c", "option3d", "option3e", "option4e", "option5b", "option5c"] );
- t( "Not - recursive", "#form option:not(:not(:selected))[id^='option3']", [ "option3b", "option3c"] );
-
- t( ":not() failing interior", "p:not(.foo)", ["firstp","ap","sndp","en","sap","first"] );
- t( ":not() failing interior", "p:not(div.foo)", ["firstp","ap","sndp","en","sap","first"] );
- t( ":not() failing interior", "p:not(p.foo)", ["firstp","ap","sndp","en","sap","first"] );
- t( ":not() failing interior", "p:not(#blargh)", ["firstp","ap","sndp","en","sap","first"] );
- t( ":not() failing interior", "p:not(div#blargh)", ["firstp","ap","sndp","en","sap","first"] );
- t( ":not() failing interior", "p:not(p#blargh)", ["firstp","ap","sndp","en","sap","first"] );
-
- t( ":not Multiple", "p:not(a)", ["firstp","ap","sndp","en","sap","first"] );
- t( ":not Multiple", "p:not(a, b)", ["firstp","ap","sndp","en","sap","first"] );
- t( ":not Multiple", "p:not(a, b, div)", ["firstp","ap","sndp","en","sap","first"] );
- t( ":not Multiple", "p:not(p)", [] );
- t( ":not Multiple", "p:not(a,p)", [] );
- t( ":not Multiple", "p:not(p,a)", [] );
- t( ":not Multiple", "p:not(a,p,b)", [] );
- t( ":not Multiple", ":input:not(:image,:input,:submit)", [] );
-
- t( "No element not selector", ".container div:not(.excluded) div", [] );
-
- t( ":not() Existing attribute", "#form select:not([multiple])", ["select1", "select2", "select5"]);
- t( ":not() Equals attribute", "#form select:not([name=select1])", ["select2", "select3", "select4","select5"]);
- t( ":not() Equals quoted attribute", "#form select:not([name='select1'])", ["select2", "select3", "select4", "select5"]);
-
- t( ":not() Multiple Class", "#foo a:not(.blog)", ["yahoo","anchor2"] );
- t( ":not() Multiple Class", "#foo a:not(.link)", ["yahoo","anchor2"] );
- t( ":not() Multiple Class", "#foo a:not(.blog.link)", ["yahoo","anchor2"] );
-});
-
-myTest("pseudo - position", function() {
- expect(25);
- t( "nth Element", "p:nth(1)", ["ap"] );
- t( "First Element", "p:first", ["firstp"] );
- t( "Last Element", "p:last", ["first"] );
- t( "Even Elements", "p:even", ["firstp","sndp","sap"] );
- t( "Odd Elements", "p:odd", ["ap","en","first"] );
- t( "Position Equals", "p:eq(1)", ["ap"] );
- t( "Position Greater Than", "p:gt(0)", ["ap","sndp","en","sap","first"] );
- t( "Position Less Than", "p:lt(3)", ["firstp","ap","sndp"] );
-
- t( "Check position filtering", "div#nothiddendiv:eq(0)", ["nothiddendiv"] );
- t( "Check position filtering", "div#nothiddendiv:last", ["nothiddendiv"] );
- t( "Check position filtering", "div#nothiddendiv:not(:gt(0))", ["nothiddendiv"] );
- t( "Check position filtering", "#foo > :not(:first)", ["en", "sap"] );
- t( "Check position filtering", "select > :not(:gt(2))", ["option1a", "option1b", "option1c"] );
- t( "Check position filtering", "select:lt(2) :not(:first)", ["option1b", "option1c", "option1d", "option2a", "option2b", "option2c", "option2d"] );
- t( "Check position filtering", "div.nothiddendiv:eq(0)", ["nothiddendiv"] );
- t( "Check position filtering", "div.nothiddendiv:last", ["nothiddendiv"] );
- t( "Check position filtering", "div.nothiddendiv:not(:lt(0))", ["nothiddendiv"] );
-
- t( "Check element position", "div div:eq(0)", ["nothiddendivchild"] );
- t( "Check element position", "div div:eq(5)", ["t2037"] );
- t( "Check element position", "div div:eq(28)", ["hide"] );
- t( "Check element position", "div div:first", ["nothiddendivchild"] );
- t( "Check element position", "div > div:first", ["nothiddendivchild"] );
- t( "Check element position", "#dl div:first div:first", ["foo"] );
- t( "Check element position", "#dl div:first > div:first", ["foo"] );
- t( "Check element position", "div#nothiddendiv:first > div:first", ["nothiddendivchild"] );
-});
-/*
-if ( (window.Sizzle || jQuery.find).selectors.filters.visibility ) {
-myTest("pseudo - visibility", function() {
- expect(11);
-
- t( "Is Visible", "#form input:visible", [] );
- t( "Is Visible", "div:visible:not(#qunit-testrunner-toolbar):lt(2)", ["nothiddendiv", "nothiddendivchild"] );
- t( "Is Hidden", "#form input:hidden", ["text1","text2","radio1","radio2","check1","check2","hidden1","hidden2","name","search"] );
- t( "Is Hidden", "#main:hidden", ["main"] );
- t( "Is Hidden", "#dl:hidden", ["dl"] );
-
- var $div = jQuery('<div/>').appendTo("body");
- $div.css({ fontSize: 0, lineHeight: 0 });// IE also needs to set font-size and line-height to 0
- $div.width(1).height(0);
- t( "Is Visible", '#nothiddendivchild:visible', ['nothiddendivchild'] );
- t( "Is Not Visible", '#nothiddendivchild:hidden', [] );
- $div.width(0).height(1);
- t( "Is Visible", '#nothiddendivchild:visible', ['nothiddendivchild'] );
- t( "Is Not Visible", '#nothiddendivchild:hidden', [] );
- $div.width(1).height(1);
- t( "Is Visible", '#nothiddendivchild:visible', ['nothiddendivchild'] );
- t( "Is Not Visible", '#nothiddendivchild:hidden', [] );
- $div.remove();
-});
-}
-*/
-
-myTest("pseudo - form", function() {
- expect(8);
-
- t( "Form element :input", "#form :input", ["text1", "text2", "radio1", "radio2", "check1", "check2", "hidden1", "hidden2", "name", "search", "button", "area1", "select1", "select2", "select3", "select4", "select5"] );
- t( "Form element :radio", "#form :radio", ["radio1", "radio2"] );
- t( "Form element :checkbox", "#form :checkbox", ["check1", "check2"] );
- t( "Form element :text", "#form :text:not(#search)", ["text1", "text2", "hidden2", "name"] );
- t( "Form element :radio:checked", "#form :radio:checked", ["radio2"] );
- t( "Form element :checkbox:checked", "#form :checkbox:checked", ["check1"] );
- t( "Form element :radio:checked, :checkbox:checked", "#form :radio:checked, #form :checkbox:checked", ["radio2", "check1"] );
-
- t( "Selected Option Element", "#form option:selected", ["option1a","option2d","option3b","option3c","option4b","option4c","option4d","option5a"] );
-});
View
26 test/simple.js
@@ -1,26 +0,0 @@
-var Crawler = require("../lib/crawler").Crawler;
-
-var c = new Crawler({
- "maxConnections":10,
- "timeout":60,
- "debug":true,
- "callback":function(error,result,$) {
- console.log("Got page");
- $("a").each(function(i,a) {
- console.log(a.href);
- //c.queue(a.href);
- })
- }
-});
-
-c.queue(["http://jamendo.com/","http://tedxparis.com"]);
-/*
-c.queue([{
- "uri":"http://parisjs.org/register",
- "method":"POST",
- "timeout":120,
- "callback":function(error,result,$) {
- $("div:contains(Thank you)").after(" very much");
- }
-}]);
-*/
View
18 test/testrunner.js
@@ -4,9 +4,23 @@ var testrunner = require( "qunit" ),
path = require( "path" ).normalize( __dirname + "/.." );
+testrunner.setup({
+
+});
+
+var mockserver = require("./mockserver").app;
+
+mockserver.listen(30045);
+
testrunner.run([
{
code: path + "/lib/crawler.js",
- tests: [ path + "/test/selector.js"]
+ tests: [
+ path + "/test/units/forceutf8.js",
+ path + "/test/units/simple.js",
+ path + "/test/units/errors.js"
+ ]
}
-]);
+],function() {
+ mockserver.close();
+});
View
39 test/units/errors.js
@@ -0,0 +1,39 @@
+var Crawler = require("../../lib/crawler").Crawler;
+
+QUnit.module("errors");
+
+var DEBUG = false;
+var MOCKPORT = 30045;
+
+
+test("request timeout", function() {
+ expect( 2 );
+
+ stop();
+
+ var c = new Crawler({
+ "debug":DEBUG,
+ "timeout":500,
+ "retryTimeout":1000,
+ "retries":1
+ });
+
+ c.onDrain = function() {
+ start();
+ };
+
+ c.queue([{
+ "uri":"http://127.0.0.1:"+MOCKPORT+"/timeout?timeout=100",
+ "callback":function(error,result,$) {
+ equal(error,null);
+ }
+ },{
+ "uri":"http://localhost:"+MOCKPORT+"/timeout?timeout=600",
+ "callback":function(error,result,$) {
+ ok(!!error);
+ }
+ }]);
+
+
+});
+
View
29 test/units/forceutf8.js
@@ -0,0 +1,29 @@
+var Crawler = require("../../lib/crawler").Crawler;
+
+QUnit.module("forceUTF8");
+
+var DEBUG = true;
+var MOCKPORT = 30045;
+
+var c = new Crawler({
+ "debug":DEBUG,
+ "forceUTF8":true
+});
+
+test("forceutf8 - from latin-1", function() {
+ expect( 2 );
+
+ stop();
+
+ c.queue([{
+ "uri":"http://czyborra.com/charsets/iso8859.html",
+ "callback":function(error,result,$) {
+ equal(error,null);
+ ok(result.body.indexOf("Jörg")>=0);
+ start();
+ }
+ }]);
+
+
+});
+
View
64 test/units/simple.js
@@ -0,0 +1,64 @@
+var Crawler = require("../../lib/crawler").Crawler;
+
+QUnit.module("simple");
+
+var DEBUG = false;
+
+test("inline html", function() {
+ expect( 2 );
+
+ stop();
+
+ var c = new Crawler({
+ "debug":DEBUG
+ });
+
+ c.queue({
+ "html":"<p><i>great!</i></p>",
+ "callback":function(error,result,$) {
+ equal(error,null);
+ equal($("i").html(),"great!");
+ start();
+ }
+ });
+
+});
+
+
+test("one request", function() {
+ expect( 2 );
+
+ stop();
+
+ var c = new Crawler({
+ "debug":DEBUG,
+ "callback":function(error,result,$) {
+ equal(error,null);
+ ok(result.body.length>1000);
+ start();
+ }
+ });
+
+ c.queue(["http://google.com/"]);
+
+});
+
+test("two requests", function() {
+ expect( 4 );
+
+ stop();
+
+ var c = new Crawler({
+ "debug":DEBUG,
+ "callback":function(error,result,$) {
+ equal(error,null);
+ ok(result.body.length>1000);
+ start();
+ }
+ });
+
+ c.queue(["http://google.com/", "http://google.fr/"]);
+
+});
+
+/* */
View
50 test/index.html → test/units/sizzle.html
@@ -2,19 +2,18 @@
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en" dir="ltr" id="html">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
- <title>jQuery Test Suite</title>
+ <title>Sizzle Test Suite</title>
<link rel="Stylesheet" media="screen" href="qunit/qunit/qunit.css" />
-<!-- <script type="text/javascript" src="jquery.js"></script>
- <script type="text/javascript" src="../sizzle.js"></script>
- <script type="text/javascript" src="data/sizzle-jquery.js"></script>
- <script type="text/javascript" src="data/testinit.js"></script>
<script type="text/javascript" src="qunit/qunit/qunit.js"></script>
+ <script type="text/javascript" src="data/testinit.js"></script>
+ <script type="text/javascript" src="jquery.js"></script>
+ <script type="text/javascript" src="../sizzle.js"></script>
<script type="text/javascript" src="unit/selector.js"></script>
- -->
+ <script type="text/javascript" src="unit/utilities.js"></script>
</head>
<body id="body">
- <h1 id="qunit-header">jQuery Test Suite</h1>
+ <h1 id="qunit-header">Sizzle Test Suite</h1>
<h2 id="qunit-banner"></h2>
<div id="qunit-testrunner-toolbar"></div>
<h2 id="qunit-userAgent"></h2>
@@ -24,10 +23,8 @@ <h2 id="qunit-userAgent"></h2>
<div id="nothiddendiv" style="height:1px;background:white;" class="nothiddendiv">
<div id="nothiddendivchild"></div>
</div>
- <!-- this iframe is outside the #main so it won't reload constantly wasting time, but it means the tests must be "safe" and clean up after themselves -->
- <iframe id="loadediframe" name="loadediframe" style="display:none;" src="data/iframe.html"></iframe>
- <dl id="dl" style="position:absolute;top:-32767px;left:-32767px;">
- <div id="main">
+ <dl id="dl" style="position:absolute;top:-32767px;left:-32767px;width:1px">
+ <div id="qunit-fixture">
<p id="firstp">See <a id="simon1" href="http://simon.incutio.com/archive/2003/03/25/#getElementsBySelector" rel="bookmark">this blog entry</a> for more information.</p>
<p id="ap">
Here are some links in a normal paragraph: <a id="google" href="http://www.google.com/" title="Google!">Google</a>,
@@ -45,7 +42,7 @@ <h2 id="qunit-userAgent"></h2>
<span id="name+value"></span>
<p id="first">Try them out:</p>
<ul id="firstUL"></ul>
- <ol id="empty"></ol>
+ <ol id="empty"><!-- comment --></ol>
<form id="form" action="formaction">
<label for="action" id="label-for">Action:</label>
<input type="text" name="action" value="Test" id="text1" maxlength="30"/>
@@ -105,11 +102,11 @@ <h2 id="qunit-userAgent"></h2>
<param name="p2" value="x2" />
</object>
- <span id="台北Táiběi"></span>
+ <span id="台北Táiběi"></span>
<span id="台北" lang="中文"></span>
- <span id="utf8class1" class="台北Táiběi 台北"></span>
+ <span id="utf8class1" class="台北Táiběi 台北"></span>
<span id="utf8class2" class="台北"></span>
- <span id="foo:bar" class="foo:bar"></span>
+ <span id="foo:bar" class="foo:bar"><span id="foo_descendent"></span></span>
<span id="test.foo[5]bar" class="test.foo[5]bar"></span>
<foo_bar id="foobar">test element</foo_bar>
@@ -169,6 +166,10 @@ <h2 id="qunit-userAgent"></h2>
<select name="D4" disabled="disabled">
<option selected="selected" value="NO">NO</option>
</select>
+ <input id="list-test" type="text" />
+ <datalist id="datalist">
+ <option value="option"></option>
+ </datalist>
</form>
<div id="moretests">
<form>
@@ -183,6 +184,13 @@ <h2 id="qunit-userAgent"></h2>
<div id="t2037">
<div><div class="hidden">hidden</div></div>
</div>
+ <div id="t6652">
+ <div></div>
+ </div>
+ <div id="t12087">
+ <input type="hidden" id="el12087" data-comma="0,1"/>
+ </div>
+ <div id="no-clone-exception"><object><embed></embed></object></div>
</div>
<div id="tabindex-tests">
@@ -210,7 +218,15 @@ <h2 id="qunit-userAgent"></h2>
<div id="siblingTest">
<em id="siblingfirst">1</em>
<em id="siblingnext">2</em>
- </div>
+ <em id="siblingthird">
+ <em id="siblingchild">
+ <em id="siblinggrandchild">
+ <em id="siblinggreatgrandchild"></em>
+ </em>
+ </em>
+ </em>
+ <span id="siblingspan"></span>
+ </div>​
</div>
</dl>
<div id="fx-test-group" style="position:absolute;width:1px;height:1px;overflow:hidden;">
@@ -232,7 +248,7 @@ <h2 id="qunit-userAgent"></h2>
<div id="slidetoggleout" class='chain test out'>slideToggleOut<div>slideToggleOut</div></div>
<div id="fadetogglein" class='chain test'>fadeToggleIn<div>fadeToggleIn</div></div>
- <div id="fadetoggleout" class='chain test out'>fadeToggleOut<div>fadeToggleOut</div></div>
+ <div id="fadetoggleout" class='chain test out'>fadeToggleOut<div>fadeToggleOut</div></div>
<div id="fadeto" class='chain test'>fadeTo<div>fadeTo</div></div>
</div>
View
848 test/units/sizzle.js
@@ -0,0 +1,848 @@
+
+QUnit.module("selector");
+
+var path = require( "path" ).normalize( __dirname + "/.." ),
+ util = require('util'),
+ fs = require("fs"),
+ Crawler = require(path + "/lib/crawler.js").Crawler;
+
+global.Sizzle = function(s) {
+ console.error("Sizzle not overriden!");
+}
+
+var document,window,jQuery,navigator={userAgent:"Mozilla/5.0"};
+
+var crawler = new Crawler();
+
+var oldTest = test;
+
+test = function(name,actualTest) {
+
+ fs.readFile(require( "path" ).normalize( __dirname + "/sizzle.html" ), 'utf-8', function (err, data) {
+
+ crawler.queue({"html":data,"callback":function(error,response,$) {
+
+ window = response.window;
+ global.document = window.document;
+ global.jQuery = $;
+ global.Sizzle = $;
+
+ oldTest(name,actualTest);
+
+ }});
+ });
+};
+
+
+
+
+/* Below are the official Sizzle tests */
+/*
+(function() {
+ // Nullify querySelector all if noqsa=true is in the params
+ // Isolates the Sizzle API
+ QUnit.config.urlConfig.push( "noqsa" );
+ if ( QUnit.urlParams.noqsa ) {
+ document.querySelectorAll = null;
+ }
+})();
+*/
+
+/**
+ * Returns an array of elements with the given IDs
+ * @example q("main", "foo", "bar")
+ * @result [<div id="main">, <span id="foo">, <input id="bar">]
+ */
+function q() {
+ var r = [],
+ i = 0;
+
+ for ( ; i < arguments.length; i++ ) {
+ r.push( document.getElementById( arguments[i] ) );
+ }
+ return r;
+}
+
+/**
+ * Asserts that a select matches the given IDs
+ * @param {String} a - Assertion name
+ * @param {String} b - Sizzle selector
+ * @param {String} c - Array of ids to construct what is expected
+ * @example t("Check for something", "//[a]", ["foo", "baar"]);
+ * @result returns true if "//[a]" return two elements with the IDs 'foo' and 'baar'
+ */
+function t( a, b, c ) {
+ var f = Sizzle(b),
+ s = "",
+ i = 0;
+
+ for ( ; i < f.length; i++ ) {
+ s += ( s && "," ) + '"' + f[ i ].id + '"';
+ }
+
+ deepEqual(f, q.apply( q, c ), a + " (" + b + ")");
+}
+
+/**
+ * Add random number to url to stop caching
+ *
+ * @example url("data/test.html")
+ * @result "data/test.html?10538358428943"
+ *
+ * @example url("data/test.php?foo=bar")
+ * @result "data/test.php?foo=bar&10538358345554"
+ */
+function url( value ) {
+ return value + (/\?/.test(value) ? "&" : "?") + new Date().getTime() + "" + parseInt(Math.random()*100000);
+}
+
+var createWithFriesXML = function() {
+ var string = '<?xml version="1.0" encoding="UTF-8"?> \
+ <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" \
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema" \
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> \
+ <soap:Body> \
+ <jsconf xmlns="http://www.example.com/ns1"> \
+ <response xmlns:ab="http://www.example.com/ns2"> \
+ <meta> \
+ <component id="seite1" class="component"> \
+ <properties xmlns:cd="http://www.example.com/ns3"> \
+ <property name="prop1"> \
+ <thing /> \
+ <value>1</value> \
+ </property> \
+ <property name="prop2"> \
+ <thing att="something" /> \
+ </property> \
+ <foo_bar>foo</foo_bar> \
+ </properties> \
+ </component> \
+ </meta> \
+ </response> \
+ </jsconf> \
+ </soap:Body> \
+ </soap:Envelope>';
+
+ return jQuery.parseXML(string);
+};
+
+
+
+
+/* Actual tests */
+
+test("element", function() {
+ expect( 35 );
+
+ equal( Sizzle("").length, 0, "Empty selector returns an empty array" );
+ equal( Sizzle(" ").length, 0, "Empty selector returns an empty array" );
+ equal( Sizzle("\t").length, 0, "Empty selector returns an empty array" );
+ var form = document.getElementById("form");
+ ok( !Sizzle.matchesSelector( form, "" ), "Empty string passed to matchesSelector does not match" );
+
+ ok( Sizzle("*").length >= 30, "Select all" );
+ var all = Sizzle("*"), good = true;
+ for ( var i = 0; i < all.length; i++ ) {
+ if ( all[i].nodeType == 8 ) {
+ good = false;
+ }
+ }
+ ok( good, "Select all elements, no comment nodes" );
+ t( "Element Selector", "html", ["html"] );
+ t( "Element Selector", "body", ["body"] );
+ t( "Element Selector", "#qunit-fixture p", ["firstp","ap","sndp","en","sap","first"] );
+
+ t( "Leading space", " #qunit-fixture p", ["firstp","ap","sndp","en","sap","first"] );
+ t( "Leading tab", "\t#qunit-fixture p", ["firstp","ap","sndp","en","sap","first"] );
+ t( "Leading carriage return", "\r#qunit-fixture p", ["firstp","ap","sndp","en","sap","first"] );
+ t( "Leading line feed", "\n#qunit-fixture p", ["firstp","ap","sndp","en","sap","first"] );
+ t( "Leading form feed", "\f#qunit-fixture p", ["firstp","ap","sndp","en","sap","first"] );
+ t( "Trailing space", "#qunit-fixture p ", ["firstp","ap","sndp","en","sap","first"] );
+ t( "Trailing tab", "#qunit-fixture p\t", ["firstp","ap","sndp","en","sap","first"] );
+ t( "Trailing carriage return", "#qunit-fixture p\r", ["firstp","ap","sndp","en","sap","first"] );
+ t( "Trailing line feed", "#qunit-fixture p\n", ["firstp","ap","sndp","en","sap","first"] );
+ t( "Trailing form feed", "#qunit-fixture p\f", ["firstp","ap","sndp","en","sap","first"] );
+
+ t( "Parent Element", "div p", ["firstp","ap","sndp","en","sap","first"] );
+ t( "Parent Element (non-space descendant combinator)", "div\tp", ["firstp","ap","sndp","en","sap","first"] );
+ var obj1 = document.getElementById("object1");
+ equal( Sizzle("param", obj1).length, 2, "Object/param as context" );
+
+ deepEqual( Sizzle("select", form), q("select1","select2","select3","select4","select5"), "Finding selects with a context." );
+
+ // Check for unique-ness and sort order
+ deepEqual( Sizzle("p, div p"), Sizzle("p"), "Check for duplicates: p, div p" );
+
+ t( "Checking sort order", "h2, h1", ["qunit-header", "qunit-banner", "qunit-userAgent"] );
+ t( "Checking sort order", "h2:first, h1:first", ["qunit-header", "qunit-banner"] );
+ t( "Checking sort order", "#qunit-fixture p, #qunit-fixture p a", ["firstp", "simon1", "ap", "google", "groups", "anchor1", "mark", "sndp", "en", "yahoo", "sap", "anchor2", "simon", "first"] );
+
+ // Test Conflict ID
+ var lengthtest = document.getElementById("lengthtest");
+ deepEqual( Sizzle("#idTest", lengthtest), q("idTest"), "Finding element with id of ID." );
+ deepEqual( Sizzle("[name='id']", lengthtest), q("idTest"), "Finding element with id of ID." );
+ deepEqual( Sizzle("input[id='idTest']", lengthtest), q("idTest"), "Finding elements with id of ID." );
+
+ var siblingTest = document.getElementById("siblingTest");
+ deepEqual( Sizzle("div em", siblingTest), [], "Element-rooted QSA does not select based on document context" );
+ deepEqual( Sizzle("div em, div em, div em:not(div em)", siblingTest), [], "Element-rooted QSA does not select based on document context" );
+ deepEqual( Sizzle("div em, em\\,", siblingTest), [], "Escaped commas do not get treated with an id in element-rooted QSA" );
+
+ var html = "";
+ for ( i = 0; i < 100; i++ ) {
+ html = "<div>" + html + "</div>";
+ }
+ html = jQuery( html ).appendTo( document.body );
+ ok( !!Sizzle("body div div div").length, "No stack or performance problems with large amounts of descendents" );
+ ok( !!Sizzle("body>div div div").length, "No stack or performance problems with large amounts of descendents" );
+ html.remove();
+});
+
+test("XML Document Selectors", function() {
+ var xml = createWithFriesXML();
+ expect( 10 );
+
+ equal( Sizzle("foo_bar", xml).length, 1, "Element Selector with underscore" );
+ equal( Sizzle(".component", xml).length, 1, "Class selector" );
+ equal( Sizzle("[class*=component]", xml).length, 1, "Attribute selector for class" );
+ equal( Sizzle("property[name=prop2]", xml).length, 1, "Attribute selector with name" );
+ equal( Sizzle("[name=prop2]", xml).length, 1, "Attribute selector with name" );
+ equal( Sizzle("#seite1", xml).length, 1, "Attribute selector with ID" );
+ equal( Sizzle("component#seite1", xml).length, 1, "Attribute selector with ID" );
+ equal( Sizzle.matches( "#seite1", Sizzle("component", xml) ).length, 1, "Attribute selector filter with ID" );
+ equal( Sizzle("meta property thing", xml).length, 2, "Descendent selector and dir caching" );
+ ok( Sizzle.matchesSelector( xml.lastChild, "soap\\:Envelope" ), "Check for namespaced element" );
+});
+
+test("broken", function() {
+ expect( 21 );
+
+ function broken( name, selector ) {
+ raises(function() {
+ Sizzle( selector );
+ }, function( e ) {
+ return e.message.indexOf("Syntax error") >= 0;
+ }, name + ": " + selector );
+ }
+
+ broken( "Broken Selector", "[" );
+ broken( "Broken Selector", "(" );
+ broken( "Broken Selector", "{" );
+ broken( "Broken Selector", "<" );
+ broken( "Broken Selector", "()" );
+ broken( "Broken Selector", "<>" );
+ broken( "Broken Selector", "{}" );
+ broken( "Broken Selector", "," );
+ broken( "Broken Selector", ",a" );
+ // Hangs on IE 9 if regular expression is inefficient
+ broken( "Broken Selector", "[id=012345678901234567890123456789");
+ broken( "Doesn't exist", ":visble" );
+ broken( "Nth-child", ":nth-child" );
+ // Sigh again. IE 9 thinks this is also a real selector
+ // not super critical that we fix this case
+ //broken( "Nth-child", ":nth-child(-)" );
+ // Sigh. WebKit thinks this is a real selector in qSA
+ // They've already fixed this and it'll be coming into
+ // current browsers soon. Currently, Safari 5.0 still has this problem
+ // broken( "Nth-child", ":nth-child(asdf)", [] );
+ broken( "Nth-child", ":nth-child(2n+-0)" );
+ broken( "Nth-child", ":nth-child(2+0)" );
+ broken( "Nth-child", ":nth-child(- 1n)" );
+ broken( "Nth-child", ":nth-child(-1 n)" );
+ broken( "First-child", ":first-child(n)" );
+ broken( "Last-child", ":last-child(n)" );
+ broken( "Only-child", ":only-child(n)" );
+
+ // Make sure attribute value quoting works correctly. See: #6093
+ var attrbad = jQuery('<input type="hidden" value="2" name="foo.baz" id="attrbad1"/><input type="hidden" value="2" name="foo[baz]" id="attrbad2"/>').appendTo("body");
+
+ broken( "Attribute not escaped", "input[name=foo.baz]", [] );
+ // Shouldn't be matching those inner brackets
+ broken( "Attribute not escaped", "input[name=foo[baz]]", [] );
+
+ attrbad.remove();
+});
+
+test("id", function() {
+ expect( 31 );
+
+ t( "ID Selector", "#body", ["body"] );
+ t( "ID Selector w/ Element", "body#body", ["body"] );
+ t( "ID Selector w/ Element", "ul#first", [] );
+ t( "ID selector with existing ID descendant", "#firstp #simon1", ["simon1"] );
+ t( "ID selector with non-existant descendant", "#firstp #foobar", [] );
+ t( "ID selector using UTF8", "#台北Táiběi", ["台北Táiběi"] );
+ t( "Multiple ID selectors using UTF8", "#台北Táiběi, #台北", ["台北Táiběi","台北"] );
+ t( "Descendant ID selector using UTF8", "div #台北", ["台北"] );
+ t( "Child ID selector using UTF8", "form > #台北", ["台北"] );
+
+ t( "Escaped ID", "#foo\\:bar", ["foo:bar"] );
+ t( "Escaped ID with descendent", "#foo\\:bar span:not(:input)", ["foo_descendent"] );
+ t( "Escaped ID", "#test\\.foo\\[5\\]bar", ["test.foo[5]bar"] );
+ t( "Descendant escaped ID", "div #foo\\:bar", ["foo:bar"] );
+ t( "Descendant escaped ID", "div #test\\.foo\\[5\\]bar", ["test.foo[5]bar"] );
+ t( "Child escaped ID", "form > #foo\\:bar", ["foo:bar"] );
+ t( "Child escaped ID", "form > #test\\.foo\\[5\\]bar", ["test.foo[5]bar"] );
+
+ var fiddle = jQuery("<div id='fiddle\\Foo'><span id='fiddleSpan'></span></div>").appendTo("#qunit-fixture");
+ deepEqual( Sizzle( "> span", Sizzle("#fiddle\\\\Foo")[0] ), q([ "fiddleSpan" ]), "Escaped ID as context" );
+ fiddle.remove();
+
+ t( "ID Selector, child ID present", "#form > #radio1", ["radio1"] ); // bug #267
+ t( "ID Selector, not an ancestor ID", "#form #first", [] );
+ t( "ID Selector, not a child ID", "#form > #option1a", [] );
+
+ t( "All Children of ID", "#foo > *", ["sndp", "en", "sap"] );
+ t( "All Children of ID with no children", "#firstUL > *", [] );
+
+ var a = jQuery("<div><a name=\"tName1\">tName1 A</a><a name=\"tName2\">tName2 A</a><div id=\"tName1\">tName1 Div</div></div>").appendTo("#qunit-fixture");
+ equal( Sizzle("#tName1")[0].id, 'tName1', "ID selector with same value for a name attribute" );
+ equal( Sizzle("#tName2").length, 0, "ID selector non-existing but name attribute on an A tag" );
+ a.remove();
+
+ a = jQuery("<a id='backslash\\foo'></a>").appendTo("#qunit-fixture");
+ t( "ID Selector contains backslash", "#backslash\\\\foo", ["backslash\\foo"] );
+
+ t( "ID Selector on Form with an input that has a name of 'id'", "#lengthtest", ["lengthtest"] );
+
+ t( "ID selector with non-existant ancestor", "#asdfasdf #foobar", [] ); // bug #986
+
+ deepEqual( Sizzle("div#form", document.body), [], "ID selector within the context of another element" );
+
+ t( "Underscore ID", "#types_all", ["types_all"] );
+ t( "Dash ID", "#fx-queue", ["fx-queue"] );
+
+ t( "ID with weird characters in it", "#name\\+value", ["name+value"] );
+});
+
+test("class", function() {
+ expect( 24 );
+
+ t( "Class Selector", ".blog", ["mark","simon"] );
+ t( "Class Selector", ".GROUPS", ["groups"] );
+ t( "Class Selector", ".blog.link", ["simon"] );
+ t( "Class Selector w/ Element", "a.blog", ["mark","simon"] );
+ t( "Parent Class Selector", "p .blog", ["mark","simon"] );
+
+ t( "Class selector using UTF8", ".台北Táiběi", ["utf8class1"] );
+ //t( "Class selector using UTF8", ".台北", ["utf8class1","utf8class2"] );
+ t( "Class selector using UTF8", ".台北Táiběi.台北", ["utf8class1"] );
+ t( "Class selector using UTF8", ".台北Táiběi, .台北", ["utf8class1","utf8class2"] );
+ t( "Descendant class selector using UTF8", "div .台北Táiběi", ["utf8class1"] );
+ t( "Child class selector using UTF8", "form > .台北Táiběi", ["utf8class1"] );
+
+ t( "Escaped Class", ".foo\\:bar", ["foo:bar"] );
+ t( "Escaped Class", ".test\\.foo\\[5\\]bar", ["test.foo[5]bar"] );
+ t( "Descendant scaped Class", "div .foo\\:bar", ["foo:bar"] );
+ t( "Descendant scaped Class", "div .test\\.foo\\[5\\]bar", ["test.foo[5]bar"] );
+ t( "Child escaped Class", "form > .foo\\:bar", ["foo:bar"] );
+ t( "Child escaped Class", "form > .test\\.foo\\[5\\]bar", ["test.foo[5]bar"] );
+
+ var div = document.createElement("div");
+ div.innerHTML = "<div class='test e'></div><div class='test'></div>";
+ deepEqual( Sizzle(".e", div), [ div.firstChild ], "Finding a second class." );
+
+ div.lastChild.className = "e";
+
+ deepEqual( Sizzle(".e", div), [ div.firstChild, div.lastChild ], "Finding a modified class." );
+
+ ok( !Sizzle.matchesSelector( div, ".null"), ".null does not match an element with no class" );
+ ok( !Sizzle.matchesSelector( div.firstChild, ".null div"), ".null does not match an element with no class" );
+ div.className = "null";
+ ok( Sizzle.matchesSelector( div, ".null"), ".null matches element with class 'null'" );
+ ok( Sizzle.matchesSelector( div.firstChild, ".null div"), "caching system respects DOM changes" );
+ ok( !Sizzle.matchesSelector( document, ".foo" ), "testing class on document doesn't error" );
+ ok( !Sizzle.matchesSelector( window, ".foo" ), "testing class on window doesn't error" );
+});
+
+test("name", function() {
+ expect( 15 );
+
+ t( "Name selector", "input[name=action]", ["text1"] );
+ t( "Name selector with single quotes", "input[name='action']", ["text1"] );
+ t( "Name selector with double quotes", 'input[name="action"]', ["text1"] );
+
+ t( "Name selector non-input", "[name=test]", ["length", "fx-queue"] );
+ t( "Name selector non-input", "[name=div]", ["fadein"] );
+ t( "Name selector non-input", "*[name=iframe]", ["iframe"] );
+
+ t( "Name selector for grouped input", "input[name='types[]']", ["types_all", "types_anime", "types_movie"] );
+
+ var form = document.getElementById("form");
+ deepEqual( Sizzle("input[name=action]", form), q("text1"), "Name selector within the context of another element" );
+ deepEqual( Sizzle("input[name='foo[bar]']", form), q("hidden2"), "Name selector for grouped form element within the context of another element" );
+
+ form = jQuery("<form><input name='id'/></form>").appendTo("body");
+ equal( Sizzle("input", form[0]).length, 1, "Make sure that rooted queries on forms (with possible expandos) work." );
+
+ form.remove();
+
+ var a = jQuery("<div><a id=\"tName1ID\" name=\"tName1\">tName1 A</a><a id=\"tName2ID\" name=\"tName2\">tName2 A</a><div id=\"tName1\">tName1 Div</div></div>")
+ .appendTo("#qunit-fixture").children();
+
+ equal( a.length, 3, "Make sure the right number of elements were inserted." );
+ equal( a[1].id, "tName2ID", "Make sure the right number of elements were inserted." );
+
+ equal( Sizzle("[name=tName1]")[0], a[0], "Find elements that have similar IDs" );
+ equal( Sizzle("[name=tName2]")[0], a[1],