[Pkg-javascript-commits] [node-cheerio] 01/04: Import Upstream version 0.22.0
Paolo Greppi
paolog-guest at moszumanska.debian.org
Wed Dec 21 11:51:27 UTC 2016
This is an automated email from the git hooks/post-receive script.
paolog-guest pushed a commit to branch master
in repository node-cheerio.
commit be6a27b8d094c2613e6dac08d343be61555377dc
Author: Paolo Greppi <paolo.greppi at libpf.com>
Date: Fri Dec 16 08:57:34 2016 +0000
Import Upstream version 0.22.0
---
.gitignore | 9 +
.jshintrc | 10 +
.travis.yml | 15 +
CONTRIBUTING.md | 47 ++
History.md | 576 ++++++++++++++++
Makefile | 36 +
Readme.md | 1093 +++++++++++++++++++++++++++++
benchmark/.gitattributes | 2 +
benchmark/benchmark.js | 333 +++++++++
benchmark/documents/jquery.html | 1199 ++++++++++++++++++++++++++++++++
benchmark/suite.js | 91 +++
index.js | 11 +
lib/api/attributes.js | 495 +++++++++++++
lib/api/css.js | 121 ++++
lib/api/forms.js | 65 ++
lib/api/manipulation.js | 425 ++++++++++++
lib/api/traversing.js | 429 ++++++++++++
lib/cheerio.js | 148 ++++
lib/parse.js | 86 +++
lib/static.js | 187 +++++
lib/utils.js | 83 +++
package.json | 59 ++
scripts/prepublish | 17 +
test/.jshintrc | 9 +
test/api/attributes.js | 785 +++++++++++++++++++++
test/api/css.js | 88 +++
test/api/forms.js | 155 +++++
test/api/manipulation.js | 1457 +++++++++++++++++++++++++++++++++++++++
test/api/traversing.js | 1413 +++++++++++++++++++++++++++++++++++++
test/api/utils.js | 233 +++++++
test/cheerio.js | 354 ++++++++++
test/fixtures.js | 72 ++
test/mocha.opts | 2 +
test/parse.js | 252 +++++++
test/xml.js | 60 ++
35 files changed, 10417 insertions(+)
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..2ae05e1
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,9 @@
+node_modules
+npm-debug.log
+coverage.html
+.DS_Store
+lib-cov
+.coverage_data
+cover_html
+.c9revisions
+coverage
diff --git a/.jshintrc b/.jshintrc
new file mode 100644
index 0000000..1a078d4
--- /dev/null
+++ b/.jshintrc
@@ -0,0 +1,10 @@
+{
+ "indent": 2,
+ "eqnull": true,
+ "laxbreak": true,
+ "proto": true,
+ "undef": true,
+ "unused": true,
+ "node": true,
+ "quotmark": "single"
+}
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..b7d7df2
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,15 @@
+language: node_js
+node_js:
+ - "stable"
+ - "unstable"
+ - "6"
+ - "4"
+ - "0.12"
+script: make travis-test
+matrix:
+ fast_finish: true
+ allow_failures:
+ - node_js: unstable
+ include:
+ - env: BENCHMARK=true
+ script: "node benchmark/benchmark.js --regex '^(?!.*highmem)'"
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..cc38bb8
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,47 @@
+# Contributing to Cheerio
+
+Thanks for your interest in contributing to the project! Here's a rundown of
+how we'd like to work with you:
+
+1. File an issue on GitHub describing the contribution you'd like to make. This
+ will help us to get you started on the right foot.
+2. Create a single commit that addresses the issue:
+ 1. Follow the project's code style (see below)
+ 2. Add enough unit tests to "prove" that your patch is correct
+ 3. Update the project documentation as needed (see below)
+ 4. Describe your approach with as much detail as necessary in the git
+ commit message
+3. Open a pull request, and reference the initial issue in the pull request
+ message.
+
+# Documentation
+
+Any API change should be reflected in the project's README.md file. Reuse
+[jQuery's documentation](http://api.jquery.com) wherever possible, but take
+care to note aspects that make Cheerio distinct.
+
+# Code Style
+
+This section is by no means complete. For undocumented stylistic choices,
+please try to maintain consistency with the code base.
+
+- Single quotes: `'`
+- Whitespace
+ - Two-space "soft" tabs
+ - Once space following control flow statements (`if (condition) {` rather
+ than `if(condition) {`)
+ - Remove trailing spaces
+ - [End each file with a newline
+ character.](https://github.com/editorconfig/editorconfig/wiki/Newline-at-End-of-File-Support)
+- Terminate every statement with a semicolon
+- Private functionality (for re-using functionality that isn't part of the
+ jQuery API)
+ - *Static methods*: If the functionality does not require a reference to a
+ Cheerio instance, simply define a named function within the module it is
+ needed.
+ - *Instance methods*: If the functionality requires a reference to a Cheerio
+ instance, informally define the method as "private" using the following
+ conventions:
+ - Define the method as a function on the Cheerio prototype
+ - Prefix the method name with an underscore (`_`) character
+ - Include `@api private` in the code comment the documents the method
diff --git a/History.md b/History.md
new file mode 100644
index 0000000..c7e38e6
--- /dev/null
+++ b/History.md
@@ -0,0 +1,576 @@
+
+0.22.0 / 2016-08-23
+==================
+
+ * Return undefined in .prop if given an invalid element or tag (#880)
+ * Merge pull request #884 from cheeriojs/readme-cleanup
+ * readme updates
+ * Merge pull request #881 from piamancini/patch-1
+ * Added backers and sponsors from OpenCollective
+ * Use jQuery from the jquery module in benchmarks (#871)
+ * Document, test, and extend static `$.text` method (#855)
+ * Fix typo on calling _.extend (#861)
+ * Update versions (#870)
+ * Use individual lodash functions (#864)
+ * Added `.serialize()` support. Fixes #69 (#827)
+ * Update Readme.md (#857)
+ * add extension for JSON require call
+ * remove gittask badge
+ * Merge pull request #672 from underdogio/dev/checkbox.radio.values.sqwished
+ * Added default value for checkboxes/radios
+
+0.20.0 / 2016-02-01
+==================
+
+ * Add coveralls badge, remove link to old report (Felix Böhm)
+ * Update lodash dependeny to 4.1.0 (leif.hanack)
+ * Fix PR #726 adding 'appendTo()' and 'prependTo()' (Delgan)
+ * Added appendTo and prependTo with tests #641 (digihaven)
+ * Fix #780 by changing options context in '.find()' (Felix Böhm)
+ * Add an unit test checking the query of child (Delgan)
+ * fix #667: attr({foo: null}) removes attribute foo, like attr('foo', null) (Ray Waldin)
+ * Include reference to dedicated "Loading" section (Mike Pennisi)
+ * Added load method to $ (alanev)
+ * update css-select to 1.2.0 (Felix Böhm)
+ * Fixing Grammatical Error (Dan Corman)
+ * Test against node v0.12 --> v4.2 (Jason Kurian)
+ * Correct output in example (Felix Böhm)
+ * Fix npm files filter (Bogdan Chadkin)
+ * Enable setting data on all elements in selection (Mike Pennisi)
+ * Reinstate `$.fn.toArray` (Mike Pennisi)
+ * update css-select to 1.1.0 (Thomas Shafer)
+ * Complete implementation of `wrap` (Mike Pennisi)
+ * Correct name of unit test (Mike Pennisi)
+ * Correct grammar in test titles (Mike Pennisi)
+ * Normalize whitespace (Mike Pennisi)
+ * Insert omitted assertion (Mike Pennisi)
+ * Update invocation of `children` (Mike Pennisi)
+ * Begin implementation of `wrap` method (Dandlezzz)
+ * Update Readme.md (Sven Slootweg)
+ * fix document's mistake in Readme.md (exoticknight)
+ * Add tests for setting text and html as non-strings (Ryc O'Chet)
+ * Fix for passing non-string values to .html or .text (Ryc O'Chet)
+ * use a selector to filter form elements (fb55)
+ * fix README.md typo (Yutian Li)
+ * README: fix spelling (Chris Rebert)
+ * Added support for options without a `value` attribute. Fixes #633 (Todd Wolfson)
+ * responding to pull request feedback - remove item() method and related tests (Ray Waldin)
+ * add length property and item method to object returned by prop('style'), plus tests (Ray Waldin)
+ * Added .prop method to readme (Artem Burtsev)
+ * Added .prop method (Artem Burtsev)
+ * Added Gitter badge (The Gitter Badger)
+
+0.19.0 / 2015-03-21
+==================
+
+ * fixed allignment (fb55)
+ * added test case for malformed json in data attributes (fb55)
+ * fix: handle some extreme cases like `data-custom="{{templatevar}}"`. There is possibility error while parsing json . (Harish.K)
+ * Add missing optional selector doc for {prev,next}{All,Until} (Jérémie Astori)
+ * update to dom-serializer at 0.1.0 (Felix Böhm)
+ * Document `Cheerio#serialzeArray` (Mike Pennisi)
+ * Fixed up `serializeArray()` and added multiple support (Todd Wolfson)
+ * Implement serializeArray() (Jarno Leppänen)
+ * recognize options in $.xml() (fb55)
+ * lib/static.js: text(): rm errant space before ++ (Chris Rebert)
+ * Do not expose internal `children` array (Mike Pennisi)
+ * Change lodash dependencies to ^3.1.0 (Samy Pessé)
+ * Update lodash at 3.1.0 (Samy Pessé)
+ * Updates Readme.md: .not(function (index, elem)) (Patrick Ward)
+ * update to css-select at 1.0.0 (fb55)
+ * Allow failures in Node.js v0.11 (Mike Pennisi)
+ * Added: Gittask badge (Matthew Mueller)
+ * Isolate prototypes of functions created via `load` (Mike Pennisi)
+ * Updates Readme.md: adds JS syntax highlighting (frankcash)
+ * #608 -- Add support for insertBefore/insertAfter syntax. Supports target types of: $, [$], selector (both single and multiple results) (Ben Cochran)
+ * Clone input nodes when inserting over a set (Mike Pennisi)
+ * Move unit test files (Mike Pennisi)
+ * remove unnecessarily tricky code (David Chambers)
+ * pass options to $.html in toString (fb55)
+ * add license info to package.json (Chris Rebert)
+ * xyz@~0.5.0 (David Chambers)
+ * Remove unofficial signature of `children` (Mike Pennisi)
+ * Fix bug in `css` method (Mike Pennisi)
+ * Correct bug in implementation of `Cheerio#val` (Mike Pennisi)
+
+0.18.0 / 2014-11-06
+==================
+
+ * bump htmlparser2 dependency to ~3.8.1 (Chris Rebert)
+ * Correct unit test titles (Mike Pennisi)
+ * Correct behavior of `after` and `before` (Mike Pennisi)
+ * implement jQuery's .has() (Chris Rebert)
+ * Update repository url (haqii)
+ * attr() should return undefined or name for booleans (Raoul Millais)
+ * Update Readme.md (Ryan Breen)
+ * Implement `Cheerio#not` (Mike Pennisi)
+ * Clone nodes according to original parsing options (Mike Pennisi)
+ * fix lint error (David Chambers)
+ * Add explicit tests for DOM level 1 API (Mike Pennisi)
+ * Expose DOM level 1 API for Node-like objects (Mike Pennisi)
+ * Correct error in documentation (Mike Pennisi)
+ * Return a fully-qualified Function from `$.load` (Mike Pennisi)
+ * Update tests to avoid duck typing (Mike Pennisi)
+ * Alter "loaded" functions to produce true instances (Mike Pennisi)
+ * Organize tests for `cheerio.load` (Mike Pennisi)
+ * Complete `$.prototype.find` (Mike Pennisi)
+ * Use JSHint's `extends` option (Mike Pennisi)
+ * Remove aliases for exported methods (Mike Pennisi)
+ * Disallow unused variables (Mike Pennisi)
+ * Remove unused internal variables (Mike Pennisi)
+ * Remove unused variables from unit tests (Mike Pennisi)
+ * Remove unused API method references (Mike Pennisi)
+ * Move tests for `contains` method (Mike Pennisi)
+ * xyz at 0.4.0 (David Chambers)
+ * Created a wiki for companies using cheerio in production (Matthew Mueller)
+ * Implement `$.prototype.index` (Mike Pennisi)
+ * Implement `$.prototype.addBack` (Mike Pennisi)
+ * Added double quotes to radio attribute name to account for characters such as brackets (akant10)
+ * Update History.md (Gabriel Falkenberg)
+ * add 0.17.0 changelog (David Chambers)
+ * exit prepublish script if tag not found (David Chambers)
+ * alphabetize devDependencies (fb55)
+ * ignore coverage dir (fb55)
+ * submit coverage to coveralls (fb55)
+ * replace jscoverage with istanbul (fb55)
+
+0.17.0 / 2014-06-10
+==================
+
+ * Fix bug in internal `uniqueSplice` function (Mike Pennisi)
+ * accept buffer argument to cheerio.load (David Chambers)
+ * Respect options on the element level (Alex Indigo)
+ * Change state definition to more readable (Artem Burtsev)
+ * added test (0xBADC0FFEE)
+ * add class only if doesn't exist (Artem Burtsev)
+ * Made it less insane. (Alex Indigo)
+ * Implement `Cheerio#add` (Mike Pennisi)
+ * Use "loaded" instance of Cheerio in unit tests (Mike Pennisi)
+ * Be more strict with object check. (Alex Indigo)
+ * Added options argument to .html() static method. (Alex Indigo)
+ * Fixed encoding mishaps. Adjusted tests. (Alex Indigo)
+ * use dom-serializer module (fb55)
+ * don't test on 0.8, don't ignore 0.11 (Felix Böhm)
+ * parse: rm unused variables (coderaiser)
+ * cheerio: rm unused variable (coderaiser)
+ * Fixed test (Avi Kohn)
+ * Added test (Avi Kohn)
+ * Changed == to === (Avi Kohn)
+ * Fixed a bug in removing type="hidden" attr (Avi Kohn)
+ * sorted (Alexey Raspopov)
+ * add `muted` attr to booleanAttributes (Alexey Raspopov)
+ * fixed context of `this` in .html (Felix Böhm)
+ * append new elements for each element in selection (fb55)
+
+0.16.0 / 2014-05-08
+==================
+
+ * fix `make bench` (David Chambers)
+ * makefile: add release-* targets (David Chambers)
+ * alphabetize dependencies (David Chambers)
+ * Rewrite `data` internals with caching behavior (Mike Pennisi)
+ * Fence .val example as js (Kevin Sawicki)
+ * Fixed typos. Deleted trailing whitespace from test/render.js (Nattaphoom Ch)
+ * Fix manipulation APIs with removed elements (kpdecker)
+ * Perform manual string parsing for hasClass (kpdecker)
+ * Fix existing element removal (kpdecker)
+ * update render tests (Felix Böhm)
+ * fixed cheerio path (Felix Böhm)
+ * use `entities.escape` for attribute values (Felix Böhm)
+ * bump entities version (Felix Böhm)
+ * remove lowerCaseTags option from readme (Felix Böhm)
+ * added test case for .html in xmlMode (fb55)
+ * render xml in `html()` when `xmlMode: true` (fb55)
+ * use a map for booleanAttributes (fb55)
+ * update singleTags, use utils.isTag (fb55)
+ * update travis badge URL (Felix Böhm)
+ * use typeof instead of _.isString and _.isNumber (fb55)
+ * use Array.isArray instead of _.isArray (fb55)
+ * replace _.isFunction with typeof (fb55)
+ * removed unnecessary error message (fb55)
+ * decode entities in htmlparser2 (fb55)
+ * pass options object to CSSselect (fb55)
+
+0.15.0 / 2014-04-08
+==================
+
+ * Update callbacks to pass element per docs (@kpdecker)
+ * preserve options (@fb55)
+ * Use SVG travis badge (@t3chnoboy)
+ * only use static requires (@fb55)
+ * Optimize manipulation methods (@kpdecker)
+ * Optimize add and remove class cases (@kpdecker)
+ * accept dom of DomHandler to cheerio.load (@nleush)
+ * added parentsUntil method (@finspin)
+ * Add performance optimization and bug fix `empty` method (@kpdecker)
+
+0.14.0 / 2014-04-01
+==================
+
+ * call encodeXML and directly expose decodeHTML (@fb55)
+ * use latest htmlparser2 and entities versions (@fb55)
+ * Deprecate `$.fn.toArray` (@jugglinmike)
+ * Implement `$.fn.get` (@jugglinmike)
+ * .replaceWith now replaces all selected elements. (@xavi-)
+ * Correct arguments for 'replaceWith' callback (@jugglinmike)
+ * switch to lodash (@fb55)
+ * update to entities at 0.5.0 (@fb55)
+ * Fix attr when $ collection contains text modules (@kpdecker)
+ * Update to latest version of expect.js (@jugglinmike)
+ * Remove nodes from their previous structures (@jugglinmike)
+ * Update render.js (@stevenvachon)
+ * CDATA test (@stevenvachon)
+ * only ever one child index for cdata (@stevenvachon)
+ * don't loop through cdata children array (@stevenvachon)
+ * proper rendering of CDATA (@stevenvachon)
+ * Add cheerio-only bench option (@kpdecker)
+ * Avoid delete operations (@kpdecker)
+ * Add independent html benchmark (@kpdecker)
+ * Cache tag check in render (@kpdecker)
+ * Simplify attribute rendering step (@kpdecker)
+ * Add html rendering bench case (@kpdecker)
+ * Remove unnecessary check from removeAttr (@kpdecker)
+ * Remove unnecessary encoding step for attrs (@kpdecker)
+ * Add test for removeAttr+attr on boolean attributes (@kpdecker)
+ * Add single element benchmark case (@kpdecker)
+ * Optimize filter with selector (@kpdecker)
+ * Fix passing context as dom node (@alfred-nsh)
+ * Fix bug in `nextUntil` (@jugglinmike)
+ * Fix bug in `nextAll` (@jugglinmike)
+ * Implement `selector` argument of `next` method (@jugglinmike)
+ * Fix bug in `prevUntil` (@jugglinmike)
+ * Implement `selector` argument of `prev` method (@jugglinmike)
+ * Fix bug in `prevAll` (@jugglinmike)
+ * Fix bug in `siblings` (@jugglinmike)
+ * Avoid unnecessary indexOf from toggleClass (@kpdecker)
+ * Use strict equality rather than falsy check in eq (@kpdecker)
+ * Add benchmark coverage for all $ APIs (@kpdecker)
+ * Optimize filter Cheerio intermediate creation (@kpdecker)
+ * Optimize siblings cheerio instance creation (@kpdecker)
+ * Optimize identity cases for first/last/eq (@kpdecker)
+ * Use domEach for traversal (@kpdecker)
+ * Inline children lookup in find (@kpdecker)
+ * Use domEach in data accessor (@kpdecker)
+ * Avoid cheerio creation in add/remove/toggleClass (@kpdecker)
+ * Implement getAttr local helper (@kpdecker)
+
+0.13.1 / 2014-01-07
+==================
+
+ * Fix select with context in Cheerio function (@jugglinmike)
+ * Remove unecessary DOM maintenance logic (@jugglinmike)
+ * Deprecate support for node 0.6
+
+0.13.0 / 2013-12-30
+==================
+
+ * Remove "root" node (@jugglinmike)
+ * Fix bug in `prevAll`, `prev`, `nextAll`, `next`, `prevUntil`, `nextUntil` (@jugglinmike)
+ * Fix `replaceWith` method (@jugglinmike)
+ * added nextUntil() and prevUntil() (@finspin)
+ * Remove internal `connect` function (@jugglinmike)
+ * Rename `Cheerio#make` to document private status (@jugginmike)
+ * Remove extraneous call to `_.uniq` (@jugglinmike)
+ * Use CSSselect library directly (@jugglinmike)
+ * Run CI against Node v0.11 as an allowed failure (@jugginmike)
+ * Correct bug in `Cheerio#parents` (@jugglinmike)
+ * Implement `$.fn.end` (@jugginmike)
+ * Ignore colons inside of url(.*) when parsing css (@Meekohi)
+ * Introduce rudimentary benchmark suite (@jugglinmike)
+ * Update HtmlParser2 version (@jugglinmike)
+ * Correct inconsistency in `$.fn.map` (@jugglinmike)
+ * fixed traversing tests (@finspin)
+ * Simplify `make` method (@jugglinmike)
+ * Avoid shadowing instance methods from arrays (@jugglinmike)
+
+0.12.4 / 2013-11-12
+==================
+
+ * Coerce JSON values returned by `data` (@jugglinmike)
+ * issue #284: when rendering HTML, use original data attributes (@Trott)
+ * Introduce JSHint for automated code linting (@jugglinmike)
+ * Prevent `find` from returning duplicate elements (@jugglinmike)
+ * Implement function signature of `replaceWith` (@jugglinmike)
+ * Implement function signature of `before` (@jugglinmike)
+ * Implement function signature of `after` (@jugglinmike)
+ * Implement function signature of `append`/`prepend` (@jugglinmike)
+ * Extend iteration methods to accept nodes (@jugglinmike)
+ * Improve `removeClass` (@jugglinmike)
+ * Complete function signature of `addClass` (@jugglinmike)
+ * Fix bug in `removeClass` (@jugglinmike)
+ * Improve contributing.md (@jugglinmike)
+ * Fix and document .css() (@jugglinmike)
+
+0.12.3 / 2013-10-04
+===================
+
+ * Add .toggleClass() function (@cyberthom)
+ * Add contributing guidelines (@jugglinmike)
+ * Fix bug in `siblings` (@jugglinmike)
+ * Correct the implementation `filter` and `is` (@jugglinmike)
+ * add .data() function (@andi-neck)
+ * add .css() (@yields)
+ * Implements contents() (@jlep)
+
+0.12.2 / 2013-09-04
+==================
+
+ * Correct implementation of `$.fn.text` (@jugglinmike)
+ * Refactor Cheerio array creation (@jugglinmike)
+ * Extend manipulation methods to accept Arrays (@jugglinmike)
+ * support .attr(attributeName, function(index, attr)) (@xiaohwan)
+
+0.12.1 / 2013-07-30
+==================
+
+ * Correct behavior of `Cheerio#parents` (@jugglinmike)
+ * Double quotes inside attributes kills HTML (@khoomeister)
+ * Making next({}) and prev({}) return empty object (@absentTelegraph)
+ * Implement $.parseHTML (@jugglinmike)
+ * Correct bug in jQuery.fn.closest (@jugglinmike)
+ * Correct behavior of $.fn.val on 'option' elements (@jugglinmike)
+
+0.12.0 / 2013-06-09
+===================
+
+ * Breaking Change: Changed context from parent to the actual passed one (@swissmanu)
+ * Fixed: jquery checkbox val behavior (@jhubble)
+ * Added: output xml with $.xml() (@Maciek416)
+ * Bumped: htmlparser2 to 3.1.1
+ * Fixed: bug in attr(key, val) on empty objects (@farhadi)
+ * Added: prevAll, nextAll (@lessmind)
+ * Fixed: Safety check in parents and closest (@zero21xxx)
+ * Added: .is(sel) (@zero21xxx)
+
+0.11.0 / 2013-04-22
+==================
+
+* Added: .closest() (@jeremy-dentel)
+* Added: .parents() (@zero21xxx)
+* Added: .val() (@rschmukler & @leahciMic)
+* Added: Travis support for node 0.10.0 (@jeremy-dentel)
+* Fixed: .find() if no selector (@davidchambers)
+* Fixed: Propagate syntax errors caused by invalid selectors (@davidchambers)
+
+0.10.8 / 2013-03-11
+==================
+
+* Add slice method (SBoudrias)
+
+0.10.7 / 2013-02-10
+==================
+
+* Code & doc cleanup (davidchambers)
+* Fixed bug in filter (jugglinmike)
+
+0.10.6 / 2013-01-29
+==================
+
+* Added `$.contains(...)` (jugglinmike)
+* formatting cleanup (davidchambers)
+* Bug fix for `.children()` (jugglinmike & davidchambers)
+* Remove global `render` bug (wvl)
+
+0.10.5 / 2012-12-18
+===================
+
+* Fixed botched publish from 0.10.4 - changes should now be present
+
+0.10.4 / 2012-12-16
+==================
+
+* $.find should query descendants only (@jugglinmike)
+* Tighter underscore dependency
+
+0.10.3 / 2012-11-18
+===================
+
+* fixed outer html bug
+* Updated documentation for $(...).html() and $.html()
+
+0.10.2 / 2012-11-17
+===================
+
+* Added a toString() method (@bensheldon)
+* use `_.each` and `_.map` to simplify cheerio namesakes (@davidchambers)
+* Added filter() with tests and updated readme (@bensheldon & @davidchambers)
+* Added spaces between attributes rewritten by removeClass (@jos3000)
+* updated docs to remove reference to size method (@ironchefpython)
+* removed HTML tidy/pretty print from cheerio
+
+0.10.1 / 2012-10-04
+===================
+
+* Fixed regression, filtering with a context (#106)
+
+0.10.0 / 2012-09-24
+===================
+
+* Greatly simplified and reorganized the library, reducing the loc by 30%
+* Now supports mocha's test-coverage
+* Deprecated self-closing tags (HTML5 doesn't require them)
+* Fixed error thrown in removeClass(...) @robashton
+
+0.9.2 / 2012-08-10
+==================
+
+* added $(...).map(fn)
+* manipulation: refactor `makeCheerioArray`
+* make .removeClass() remove *all* occurrences (#64)
+
+0.9.1 / 2012-08-03
+==================
+
+* fixed bug causing options not to make it to the parser
+
+0.9.0 / 2012-07-24
+==================
+
+* Added node 8.x support
+* Removed node 4.x support
+* Add html(dom) support (@wvl)
+* fixed xss vulnerabilities on .attr(), .text(), & .html() (@benatkin, @FB55)
+* Rewrote tests into javascript, removing coffeescript dependency (@davidchambers)
+* Tons of cleanup (@davidchambers)
+
+0.8.3 / 2012-06-12
+==================
+
+* Fixed minor package regression (closes #60)
+
+0.8.2 / 2012-06-11
+==================
+
+* Now fails gracefully in cases that involve special chars, which is inline with jQuery (closes #59)
+* text() now decode special entities (closes #52)
+* updated travis.yml to test node 4.x
+
+0.8.1 / 2012-06-02
+==================
+
+* fixed regression where if you created an element, it would update the root
+* compatible with node 4.x (again)
+
+0.8.0 / 2012-05-27
+==================
+
+* Updated CSS parser to use FB55/CSSselect. Cheerio now supports most CSS3 psuedo selectors thanks to @FB55.
+* ignoreWhitespace now on by default again. See #55 for context.
+* Changed $(':root') to $.root(), cleaned up $.clone()
+* Support for .eq(i) thanks to @alexbardas
+* Removed support for node 0.4.x
+* Fixed memory leak where package.json was continually loaded
+* Tons more tests
+
+0.7.0 / 2012-04-08
+==================
+
+* Now testing with node v0.7.7
+* Added travis-ci integration
+* Replaced should.js with expect.js. Browser testing to come
+* Fixed spacing between attributes and their values
+* Added HTML tidy/pretty print
+* Exposed node-htmlparser2 parsing options
+* Revert .replaceWith(...) to be consistent with jQuery
+
+0.6.2 / 2012-02-12
+==================
+
+* Fixed .replaceWith(...) regression
+
+0.6.1 / 2012-02-12
+==================
+
+* Added .first(), .last(), and .clone() commands.
+* Option to parse using whitespace added to `.load`.
+* Many bug fixes to make cheerio more aligned with jQuery.
+* Added $(':root') to select the highest level element.
+
+Many thanks to the contributors that made this release happen: @ironchefpython and @siddMahen
+
+0.6.0 / 2012-02-07
+==================
+
+* *Important:* `$(...).html()` now returns inner HTML, which is in line with the jQuery spec
+* `$.html()` returns the full HTML string. `$.html([cheerioObject])` will return the outer(selected element's tag) and inner HTML of that object
+* Fixed bug that prevented HTML strings with depth (eg. `append('<ul><li><li></ul>')`) from getting `parent`, `next`, `prev` attributes.
+* Halted [htmlparser2](https://github.com/FB55/node-htmlparser) at v2.2.2 until single attributes bug gets fixed.
+
+0.5.1 / 2012-02-05
+==================
+
+* Fixed minor regression: $(...).text(fn) would fail
+
+0.5.1 / 2012-02-05
+==================
+
+* Fixed regression: HTML pages with comments would fail
+
+0.5.0 / 2012-02-04
+==================
+
+* Transitioned from Coffeescript back to Javascript
+* Parser now ignores whitespace
+* Fixed issue with double slashes on self-enclosing tags
+* Added boolean attributes to html rendering
+
+0.4.2 / 2012-01-16
+==================
+
+* Multiple selectors support: $('.apple, .orange'). Thanks @siddMahen!
+* Update package.json to always use latest cheerio-soupselect
+* Fix memory leak in index.js
+
+0.4.1 / 2011-12-19
+==================
+* Minor packaging changes to allow `make test` to work from npm installation
+
+0.4.0 / 2011-12-19
+==================
+
+* Rewrote all unit tests as cheerio transitioned from vows -> mocha
+* Internally, renderer.render -> render(...), parser.parse -> parse(...)
+* Append, prepend, html, before, after all work with only text (no tags)
+* Bugfix: Attributes can now be removed from script and style tags
+* Added yield as a single tag
+* Cheerio now compatible with node >=0.4.7
+
+0.3.2 / 2011-12-1
+=================
+
+* Fixed $(...).text(...) to work with "root" element
+
+0.3.1 / 2011-11-25
+==================
+
+* Now relying on cheerio-soupselect instead of node-soupselect
+* Removed all lingering htmlparser dependencies
+* parser now returns parent "root" element. Root now never needs to be updated when there is multiple roots. This fixes ongoing issues with before(...), after(...) and other manipulation functions
+* Added jQuery's $(...).replaceWith(...)
+
+0.3.0 / 2011-11-19
+==================
+
+* Now using htmlparser2 for parsing (2x speed increase, cleaner, actively developed)
+* Added benchmark directory for future speed tests
+* $('...').dom() was funky, so it was removed in favor of $('...').get(). $.dom() still works the same.
+* $.root now correctly static across all instances of $
+* Added a screencast
+
+0.2.2 / 2011-11-9
+=================
+
+* Traversing will select `<script>` and `<style>` tags (Closes Issue: #8)
+* .text(string) now working with empty elements (Closes Issue: #7)
+* Fixed before(...) & after(...) again if there is no parent (Closes Issue: #2)
+
+0.2.1 / 2011-11-5
+=================
+
+* Fixed before(...) & after(...) if there is no parent (Closes Issue: #2)
+* Comments now rendered correctly (Closes Issue: #5)
+
+< 0.2.0 / 2011-10-31
+====================
+
+* Initial release (untracked development)
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..0edf534
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,36 @@
+REPORTER = dot
+XYZ = node_modules/.bin/xyz --message 'Release X.Y.Z' --tag X.Y.Z --repo git at github.com:cheeriojs/cheerio.git --script scripts/prepublish
+
+lint:
+ @./node_modules/.bin/jshint lib/ test/
+
+test: lint
+ @./node_modules/.bin/mocha --recursive --reporter $(REPORTER)
+
+setup:
+ @npm install
+
+subl:
+ @subl lib/ test/ package.json index.js
+
+test-cov:
+ @./node_modules/.bin/istanbul cover node_modules/.bin/_mocha -- --recursive --reporter $(REPORTER)
+
+report-cov: test-cov
+ @cat coverage/lcov.info | ./node_modules/.bin/coveralls
+
+travis-test: lint
+ @make report-cov || echo "Couldn't submit"
+
+bench:
+ @./benchmark/benchmark.js
+
+.PHONY: release-major release-minor release-patch
+release-major: LEVEL = major
+release-minor: LEVEL = minor
+release-patch: LEVEL = patch
+
+release-major release-minor release-patch:
+ @$(XYZ) --increment $(LEVEL)
+
+.PHONY: test build setup subl
diff --git a/Readme.md b/Readme.md
new file mode 100644
index 0000000..784a08d
--- /dev/null
+++ b/Readme.md
@@ -0,0 +1,1093 @@
+<h1 align="center">cheerio</h1>
+
+<h5 align="center">Fast, flexible & lean implementation of core jQuery designed specifically for the server.</h5>
+
+<div align="center">
+ <a href="http://travis-ci.org/cheeriojs/cheerio">
+ <img src="https://secure.travis-ci.org/cheeriojs/cheerio.svg?branch=master" alt="Travis CI" />
+ </a>
+ <a href="https://coveralls.io/r/cheeriojs/cheerio">
+ <img src="http://img.shields.io/coveralls/cheeriojs/cheerio.svg?branch=master&style=flat" alt="Coverage" />
+ </a>
+ <a href="https://gitter.im/cheeriojs/cheerio?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge">
+ <img src="https://badges.gitter.im/Join%20Chat.svg" alt="Join the chat at https://gitter.im/cheeriojs/cheerio" />
+ </a>
+ <a href="#backers">
+ <img src="https://opencollective.com/cheerio/backers/badge.svg" alt="OpenCollective backers"/>
+ </a>
+ <a href="#sponsors">
+ <img src="https://opencollective.com/cheerio/sponsors/badge.svg" alt="OpenCollective sponsors"/>
+ </a>
+</div>
+
+<br />
+
+```js
+let cheerio = require('cheerio')
+let $ = cheerio.load('<h2 class="title">Hello world</h2>')
+
+$('h2.title').text('Hello there!')
+$('h2').addClass('welcome')
+
+$.html()
+//=> <h2 class="title welcome">Hello there!</h2>
+```
+
+## Installation
+`npm install cheerio`
+
+## Features
+__❤ Familiar syntax:__
+Cheerio implements a subset of core jQuery. Cheerio removes all the DOM inconsistencies and browser cruft from the jQuery library, revealing its truly gorgeous API.
+
+__ϟ Blazingly fast:__
+Cheerio works with a very simple, consistent DOM model. As a result parsing, manipulating, and rendering are incredibly efficient. Preliminary end-to-end benchmarks suggest that cheerio is about __8x__ faster than JSDOM.
+
+__❁ Incredibly flexible:__
+Cheerio wraps around @FB55's forgiving [htmlparser2](https://github.com/fb55/htmlparser2/). Cheerio can parse nearly any HTML or XML document.
+
+## Sponsors
+
+Does your company use Cheerio in production? Please consider [sponsoring this project](https://opencollective.com/cheerio#sponsor). Your help will allow maintainers to dedicate more time and resources to its development and support.
+
+<a href="https://opencollective.com/cheerio/sponsor/0/website" target="_blank"><img src="https://opencollective.com/cheerio/sponsor/0/avatar.svg"></a>
+<a href="https://opencollective.com/cheerio/sponsor/1/website" target="_blank"><img src="https://opencollective.com/cheerio/sponsor/1/avatar.svg"></a>
+<a href="https://opencollective.com/cheerio/sponsor/2/website" target="_blank"><img src="https://opencollective.com/cheerio/sponsor/2/avatar.svg"></a>
+<a href="https://opencollective.com/cheerio/sponsor/3/website" target="_blank"><img src="https://opencollective.com/cheerio/sponsor/3/avatar.svg"></a>
+<a href="https://opencollective.com/cheerio/sponsor/4/website" target="_blank"><img src="https://opencollective.com/cheerio/sponsor/4/avatar.svg"></a>
+<a href="https://opencollective.com/cheerio/sponsor/5/website" target="_blank"><img src="https://opencollective.com/cheerio/sponsor/5/avatar.svg"></a>
+<a href="https://opencollective.com/cheerio/sponsor/6/website" target="_blank"><img src="https://opencollective.com/cheerio/sponsor/6/avatar.svg"></a>
+<a href="https://opencollective.com/cheerio/sponsor/7/website" target="_blank"><img src="https://opencollective.com/cheerio/sponsor/7/avatar.svg"></a>
+<a href="https://opencollective.com/cheerio/sponsor/8/website" target="_blank"><img src="https://opencollective.com/cheerio/sponsor/8/avatar.svg"></a>
+<a href="https://opencollective.com/cheerio/sponsor/9/website" target="_blank"><img src="https://opencollective.com/cheerio/sponsor/9/avatar.svg"></a>
+<a href="https://opencollective.com/cheerio/sponsor/10/website" target="_blank"><img src="https://opencollective.com/cheerio/sponsor/10/avatar.svg"></a>
+<a href="https://opencollective.com/cheerio/sponsor/11/website" target="_blank"><img src="https://opencollective.com/cheerio/sponsor/11/avatar.svg"></a>
+<a href="https://opencollective.com/cheerio/sponsor/12/website" target="_blank"><img src="https://opencollective.com/cheerio/sponsor/12/avatar.svg"></a>
+<a href="https://opencollective.com/cheerio/sponsor/13/website" target="_blank"><img src="https://opencollective.com/cheerio/sponsor/13/avatar.svg"></a>
+<a href="https://opencollective.com/cheerio/sponsor/14/website" target="_blank"><img src="https://opencollective.com/cheerio/sponsor/14/avatar.svg"></a>
+<a href="https://opencollective.com/cheerio/sponsor/15/website" target="_blank"><img src="https://opencollective.com/cheerio/sponsor/15/avatar.svg"></a>
+<a href="https://opencollective.com/cheerio/sponsor/16/website" target="_blank"><img src="https://opencollective.com/cheerio/sponsor/16/avatar.svg"></a>
+<a href="https://opencollective.com/cheerio/sponsor/17/website" target="_blank"><img src="https://opencollective.com/cheerio/sponsor/17/avatar.svg"></a>
+<a href="https://opencollective.com/cheerio/sponsor/18/website" target="_blank"><img src="https://opencollective.com/cheerio/sponsor/18/avatar.svg"></a>
+<a href="https://opencollective.com/cheerio/sponsor/19/website" target="_blank"><img src="https://opencollective.com/cheerio/sponsor/19/avatar.svg"></a>
+<a href="https://opencollective.com/cheerio/sponsor/20/website" target="_blank"><img src="https://opencollective.com/cheerio/sponsor/20/avatar.svg"></a>
+<a href="https://opencollective.com/cheerio/sponsor/21/website" target="_blank"><img src="https://opencollective.com/cheerio/sponsor/21/avatar.svg"></a>
+<a href="https://opencollective.com/cheerio/sponsor/22/website" target="_blank"><img src="https://opencollective.com/cheerio/sponsor/22/avatar.svg"></a>
+<a href="https://opencollective.com/cheerio/sponsor/23/website" target="_blank"><img src="https://opencollective.com/cheerio/sponsor/23/avatar.svg"></a>
+<a href="https://opencollective.com/cheerio/sponsor/24/website" target="_blank"><img src="https://opencollective.com/cheerio/sponsor/24/avatar.svg"></a>
+<a href="https://opencollective.com/cheerio/sponsor/25/website" target="_blank"><img src="https://opencollective.com/cheerio/sponsor/25/avatar.svg"></a>
+<a href="https://opencollective.com/cheerio/sponsor/26/website" target="_blank"><img src="https://opencollective.com/cheerio/sponsor/26/avatar.svg"></a>
+<a href="https://opencollective.com/cheerio/sponsor/27/website" target="_blank"><img src="https://opencollective.com/cheerio/sponsor/27/avatar.svg"></a>
+<a href="https://opencollective.com/cheerio/sponsor/28/website" target="_blank"><img src="https://opencollective.com/cheerio/sponsor/28/avatar.svg"></a>
+<a href="https://opencollective.com/cheerio/sponsor/29/website" target="_blank"><img src="https://opencollective.com/cheerio/sponsor/29/avatar.svg"></a>
+
+## Backers
+
+[Become a backer](https://opencollective.com/cheerio#backer) to show your support for Cheerio and help us maintain and improve this open source project.
+
+<a href="https://opencollective.com/cheerio/backer/0/website" target="_blank"><img src="https://opencollective.com/cheerio/backer/0/avatar.svg"></a>
+<a href="https://opencollective.com/cheerio/backer/1/website" target="_blank"><img src="https://opencollective.com/cheerio/backer/1/avatar.svg"></a>
+<a href="https://opencollective.com/cheerio/backer/2/website" target="_blank"><img src="https://opencollective.com/cheerio/backer/2/avatar.svg"></a>
+<a href="https://opencollective.com/cheerio/backer/3/website" target="_blank"><img src="https://opencollective.com/cheerio/backer/3/avatar.svg"></a>
+<a href="https://opencollective.com/cheerio/backer/4/website" target="_blank"><img src="https://opencollective.com/cheerio/backer/4/avatar.svg"></a>
+<a href="https://opencollective.com/cheerio/backer/5/website" target="_blank"><img src="https://opencollective.com/cheerio/backer/5/avatar.svg"></a>
+<a href="https://opencollective.com/cheerio/backer/6/website" target="_blank"><img src="https://opencollective.com/cheerio/backer/6/avatar.svg"></a>
+<a href="https://opencollective.com/cheerio/backer/7/website" target="_blank"><img src="https://opencollective.com/cheerio/backer/7/avatar.svg"></a>
+<a href="https://opencollective.com/cheerio/backer/8/website" target="_blank"><img src="https://opencollective.com/cheerio/backer/8/avatar.svg"></a>
+<a href="https://opencollective.com/cheerio/backer/9/website" target="_blank"><img src="https://opencollective.com/cheerio/backer/9/avatar.svg"></a>
+<a href="https://opencollective.com/cheerio/backer/10/website" target="_blank"><img src="https://opencollective.com/cheerio/backer/10/avatar.svg"></a>
+<a href="https://opencollective.com/cheerio/backer/11/website" target="_blank"><img src="https://opencollective.com/cheerio/backer/11/avatar.svg"></a>
+<a href="https://opencollective.com/cheerio/backer/12/website" target="_blank"><img src="https://opencollective.com/cheerio/backer/12/avatar.svg"></a>
+<a href="https://opencollective.com/cheerio/backer/13/website" target="_blank"><img src="https://opencollective.com/cheerio/backer/13/avatar.svg"></a>
+<a href="https://opencollective.com/cheerio/backer/14/website" target="_blank"><img src="https://opencollective.com/cheerio/backer/14/avatar.svg"></a>
+<a href="https://opencollective.com/cheerio/backer/15/website" target="_blank"><img src="https://opencollective.com/cheerio/backer/15/avatar.svg"></a>
+<a href="https://opencollective.com/cheerio/backer/16/website" target="_blank"><img src="https://opencollective.com/cheerio/backer/16/avatar.svg"></a>
+<a href="https://opencollective.com/cheerio/backer/17/website" target="_blank"><img src="https://opencollective.com/cheerio/backer/17/avatar.svg"></a>
+<a href="https://opencollective.com/cheerio/backer/18/website" target="_blank"><img src="https://opencollective.com/cheerio/backer/18/avatar.svg"></a>
+<a href="https://opencollective.com/cheerio/backer/19/website" target="_blank"><img src="https://opencollective.com/cheerio/backer/19/avatar.svg"></a>
+<a href="https://opencollective.com/cheerio/backer/20/website" target="_blank"><img src="https://opencollective.com/cheerio/backer/20/avatar.svg"></a>
+<a href="https://opencollective.com/cheerio/backer/21/website" target="_blank"><img src="https://opencollective.com/cheerio/backer/21/avatar.svg"></a>
+<a href="https://opencollective.com/cheerio/backer/22/website" target="_blank"><img src="https://opencollective.com/cheerio/backer/22/avatar.svg"></a>
+<a href="https://opencollective.com/cheerio/backer/23/website" target="_blank"><img src="https://opencollective.com/cheerio/backer/23/avatar.svg"></a>
+<a href="https://opencollective.com/cheerio/backer/24/website" target="_blank"><img src="https://opencollective.com/cheerio/backer/24/avatar.svg"></a>
+<a href="https://opencollective.com/cheerio/backer/25/website" target="_blank"><img src="https://opencollective.com/cheerio/backer/25/avatar.svg"></a>
+<a href="https://opencollective.com/cheerio/backer/26/website" target="_blank"><img src="https://opencollective.com/cheerio/backer/26/avatar.svg"></a>
+<a href="https://opencollective.com/cheerio/backer/27/website" target="_blank"><img src="https://opencollective.com/cheerio/backer/27/avatar.svg"></a>
+<a href="https://opencollective.com/cheerio/backer/28/website" target="_blank"><img src="https://opencollective.com/cheerio/backer/28/avatar.svg"></a>
+<a href="https://opencollective.com/cheerio/backer/29/website" target="_blank"><img src="https://opencollective.com/cheerio/backer/29/avatar.svg"></a>
+
+## API
+
+### Markup example we'll be using:
+
+```html
+<ul id="fruits">
+ <li class="apple">Apple</li>
+ <li class="orange">Orange</li>
+ <li class="pear">Pear</li>
+</ul>
+```
+
+This is the HTML markup we will be using in all of the API examples.
+
+### Loading
+First you need to load in the HTML. This step in jQuery is implicit, since jQuery operates on the one, baked-in DOM. With Cheerio, we need to pass in the HTML document.
+
+This is the _preferred_ method:
+
+```js
+var cheerio = require('cheerio'),
+ $ = cheerio.load('<ul id="fruits">...</ul>');
+```
+
+Optionally, you can also load in the HTML by passing the string as the context:
+
+```js
+$ = require('cheerio');
+$('ul', '<ul id="fruits">...</ul>');
+```
+
+Or as the root:
+
+```js
+$ = require('cheerio');
+$('li', 'ul', '<ul id="fruits">...</ul>');
+```
+
+You can also pass an extra object to `.load()` if you need to modify any
+of the default parsing options:
+
+```js
+$ = cheerio.load('<ul id="fruits">...</ul>', {
+ normalizeWhitespace: true,
+ xmlMode: true
+});
+```
+
+These parsing options are taken directly from [htmlparser2](https://github.com/fb55/htmlparser2/wiki/Parser-options), therefore any options that can be used in `htmlparser2` are valid in cheerio as well. The default options are:
+
+```js
+{
+ withDomLvl1: true,
+ normalizeWhitespace: false,
+ xmlMode: false,
+ decodeEntities: true
+}
+
+```
+
+For a full list of options and their effects, see [this](https://github.com/fb55/DomHandler) and
+[htmlparser2's options](https://github.com/fb55/htmlparser2/wiki/Parser-options).
+
+### Selectors
+
+Cheerio's selector implementation is nearly identical to jQuery's, so the API is very similar.
+
+#### $( selector, [context], [root] )
+`selector` searches within the `context` scope which searches within the `root` scope. `selector` and `context` can be a string expression, DOM Element, array of DOM elements, or cheerio object. `root` is typically the HTML document string.
+
+This selector method is the starting point for traversing and manipulating the document. Like jQuery, it's the primary method for selecting elements in the document, but unlike jQuery it's built on top of the CSSSelect library, which implements most of the Sizzle selectors.
+
+```js
+$('.apple', '#fruits').text()
+//=> Apple
+
+$('ul .pear').attr('class')
+//=> pear
+
+$('li[class=orange]').html()
+//=> Orange
+```
+
+### Attributes
+Methods for getting and modifying attributes.
+
+#### .attr( name, value )
+Method for getting and setting attributes. Gets the attribute value for only the first element in the matched set. If you set an attribute's value to `null`, you remove that attribute. You may also pass a `map` and `function` like jQuery.
+
+```js
+$('ul').attr('id')
+//=> fruits
+
+$('.apple').attr('id', 'favorite').html()
+//=> <li class="apple" id="favorite">Apple</li>
+```
+
+> See http://api.jquery.com/attr/ for more information
+
+#### .prop( name, value )
+Method for getting and setting properties. Gets the property value for only the first element in the matched set.
+
+```js
+$('input[type="checkbox"]').prop('checked')
+//=> false
+
+$('input[type="checkbox"]').prop('checked', true).val()
+//=> ok
+```
+
+> See http://api.jquery.com/prop/ for more information
+
+#### .data( name, value )
+Method for getting and setting data attributes. Gets or sets the data attribute value for only the first element in the matched set.
+
+```js
+$('<div data-apple-color="red"></div>').data()
+//=> { appleColor: 'red' }
+
+$('<div data-apple-color="red"></div>').data('apple-color')
+//=> 'red'
+
+var apple = $('.apple').data('kind', 'mac')
+apple.data('kind')
+//=> 'mac'
+```
+
+> See http://api.jquery.com/data/ for more information
+
+#### .val( [value] )
+Method for getting and setting the value of input, select, and textarea. Note: Support for `map`, and `function` has not been added yet.
+
+```js
+$('input[type="text"]').val()
+//=> input_text
+
+$('input[type="text"]').val('test').html()
+//=> <input type="text" value="test"/>
+```
+
+#### .removeAttr( name )
+Method for removing attributes by `name`.
+
+```js
+$('.pear').removeAttr('class').html()
+//=> <li>Pear</li>
+```
+
+#### .hasClass( className )
+Check to see if *any* of the matched elements have the given `className`.
+
+```js
+$('.pear').hasClass('pear')
+//=> true
+
+$('apple').hasClass('fruit')
+//=> false
+
+$('li').hasClass('pear')
+//=> true
+```
+
+#### .addClass( className )
+Adds class(es) to all of the matched elements. Also accepts a `function` like jQuery.
+
+```js
+$('.pear').addClass('fruit').html()
+//=> <li class="pear fruit">Pear</li>
+
+$('.apple').addClass('fruit red').html()
+//=> <li class="apple fruit red">Apple</li>
+```
+
+> See http://api.jquery.com/addClass/ for more information.
+
+#### .removeClass( [className] )
+Removes one or more space-separated classes from the selected elements. If no `className` is defined, all classes will be removed. Also accepts a `function` like jQuery.
+
+```js
+$('.pear').removeClass('pear').html()
+//=> <li class="">Pear</li>
+
+$('.apple').addClass('red').removeClass().html()
+//=> <li class="">Apple</li>
+```
+
+> See http://api.jquery.com/removeClass/ for more information.
+
+#### .toggleClass( className, [switch] )
+Add or remove class(es) from the matched elements, depending on either the class's presence or the value of the switch argument. Also accepts a `function` like jQuery.
+
+```js
+$('.apple.green').toggleClass('fruit green red').html()
+//=> <li class="apple fruit red">Apple</li>
+
+$('.apple.green').toggleClass('fruit green red', true).html()
+//=> <li class="apple green fruit red">Apple</li>
+```
+
+> See http://api.jquery.com/toggleClass/ for more information.
+
+#### .is( selector )
+#### .is( element )
+#### .is( selection )
+#### .is( function(index) )
+Checks the current list of elements and returns `true` if _any_ of the elements match the selector. If using an element or Cheerio selection, returns `true` if _any_ of the elements match. If using a predicate function, the function is executed in the context of the selected element, so `this` refers to the current element.
+
+### Forms
+
+#### .serializeArray()
+
+Encode a set of form elements as an array of names and values.
+
+```js
+$('<form><input name="foo" value="bar" /></form>').serializeArray()
+//=> [ { name: 'foo', value: 'bar' } ]
+```
+
+### Traversing
+
+#### .find(selector)
+#### .find(selection)
+#### .find(node)
+Get the descendants of each element in the current set of matched elements, filtered by a selector, jQuery object, or element.
+
+```js
+$('#fruits').find('li').length
+//=> 3
+$('#fruits').find($('.apple')).length
+//=> 1
+```
+
+#### .parent([selector])
+Get the parent of each element in the current set of matched elements, optionally filtered by a selector.
+
+```js
+$('.pear').parent().attr('id')
+//=> fruits
+```
+
+#### .parents([selector])
+Get a set of parents filtered by `selector` of each element in the current set of match elements.
+```js
+$('.orange').parents().length
+// => 2
+$('.orange').parents('#fruits').length
+// => 1
+```
+
+#### .parentsUntil([selector][,filter])
+Get the ancestors of each element in the current set of matched elements, up to but not including the element matched by the selector, DOM node, or cheerio object.
+```js
+$('.orange').parentsUntil('#food').length
+// => 1
+```
+
+#### .closest(selector)
+For each element in the set, get the first element that matches the selector by testing the element itself and traversing up through its ancestors in the DOM tree.
+
+```js
+$('.orange').closest()
+// => []
+$('.orange').closest('.apple')
+// => []
+$('.orange').closest('li')
+// => [<li class="orange">Orange</li>]
+$('.orange').closest('#fruits')
+// => [<ul id="fruits"> ... </ul>]
+```
+
+#### .next([selector])
+Gets the next sibling of the first selected element, optionally filtered by a selector.
+
+```js
+$('.apple').next().hasClass('orange')
+//=> true
+```
+
+#### .nextAll([selector])
+Gets all the following siblings of the first selected element, optionally filtered by a selector.
+
+```js
+$('.apple').nextAll()
+//=> [<li class="orange">Orange</li>, <li class="pear">Pear</li>]
+$('.apple').nextAll('.orange')
+//=> [<li class="orange">Orange</li>]
+```
+
+#### .nextUntil([selector], [filter])
+Gets all the following siblings up to but not including the element matched by the selector, optionally filtered by another selector.
+
+```js
+$('.apple').nextUntil('.pear')
+//=> [<li class="orange">Orange</li>]
+```
+
+#### .prev([selector])
+Gets the previous sibling of the first selected element optionally filtered by a selector.
+
+```js
+$('.orange').prev().hasClass('apple')
+//=> true
+```
+
+#### .prevAll([selector])
+Gets all the preceding siblings of the first selected element, optionally filtered by a selector.
+
+```js
+$('.pear').prevAll()
+//=> [<li class="orange">Orange</li>, <li class="apple">Apple</li>]
+$('.pear').prevAll('.orange')
+//=> [<li class="orange">Orange</li>]
+```
+
+#### .prevUntil([selector], [filter])
+Gets all the preceding siblings up to but not including the element matched by the selector, optionally filtered by another selector.
+
+```js
+$('.pear').prevUntil('.apple')
+//=> [<li class="orange">Orange</li>]
+```
+
+#### .slice( start, [end] )
+Gets the elements matching the specified range
+
+```js
+$('li').slice(1).eq(0).text()
+//=> 'Orange'
+
+$('li').slice(1, 2).length
+//=> 1
+```
+
+#### .siblings([selector])
+Gets the first selected element's siblings, excluding itself.
+
+```js
+$('.pear').siblings().length
+//=> 2
+
+$('.pear').siblings('.orange').length
+//=> 1
+
+```
+
+#### .children([selector])
+Gets the children of the first selected element.
+
+```js
+$('#fruits').children().length
+//=> 3
+
+$('#fruits').children('.pear').text()
+//=> Pear
+```
+
+#### .contents()
+Gets the children of each element in the set of matched elements, including text and comment nodes.
+
+```js
+$('#fruits').contents().length
+//=> 3
+```
+
+#### .each( function(index, element) )
+Iterates over a cheerio object, executing a function for each matched element. When the callback is fired, the function is fired in the context of the DOM element, so `this` refers to the current element, which is equivalent to the function parameter `element`. To break out of the `each` loop early, return with `false`.
+
+```js
+var fruits = [];
+
+$('li').each(function(i, elem) {
+ fruits[i] = $(this).text();
+});
+
+fruits.join(', ');
+//=> Apple, Orange, Pear
+```
+
+#### .map( function(index, element) )
+Pass each element in the current matched set through a function, producing a new Cheerio object containing the return values. The function can return an individual data item or an array of data items to be inserted into the resulting set. If an array is returned, the elements inside the array are inserted into the set. If the function returns null or undefined, no element will be inserted.
+
+```js
+$('li').map(function(i, el) {
+ // this === el
+ return $(this).text();
+}).get().join(' ');
+//=> "apple orange pear"
+```
+
+#### .filter( selector ) <br /> .filter( selection ) <br /> .filter( element ) <br /> .filter( function(index) )
+
+Iterates over a cheerio object, reducing the set of selector elements to those that match the selector or pass the function's test. When a Cheerio selection is specified, return only the elements contained in that selection. When an element is specified, return only that element (if it is contained in the original selection). If using the function method, the function is executed in the context of the selected element, so `this` refers to the current element.
+
+Selector:
+
+```js
+$('li').filter('.orange').attr('class');
+//=> orange
+```
+
+Function:
+
+```js
+$('li').filter(function(i, el) {
+ // this === el
+ return $(this).attr('class') === 'orange';
+}).attr('class')
+//=> orange
+```
+
+#### .not( selector ) <br /> .not( selection ) <br /> .not( element ) <br /> .not( function(index, elem) )
+
+Remove elements from the set of matched elements. Given a jQuery object that represents a set of DOM elements, the `.not()` method constructs a new jQuery object from a subset of the matching elements. The supplied selector is tested against each element; the elements that don't match the selector will be included in the result. The `.not()` method can take a function as its argument in the same way that `.filter()` does. Elements for which the function returns true are excluded from the [...]
+
+Selector:
+
+```js
+$('li').not('.apple').length;
+//=> 2
+```
+
+Function:
+
+```js
+$('li').not(function(i, el) {
+ // this === el
+ return $(this).attr('class') === 'orange';
+}).length;
+//=> 2
+```
+
+#### .has( selector ) <br /> .has( element )
+
+Filters the set of matched elements to only those which have the given DOM element as a descendant or which have a descendant that matches the given selector. Equivalent to `.filter(':has(selector)')`.
+
+Selector:
+
+```js
+$('ul').has('.pear').attr('id');
+//=> fruits
+```
+
+Element:
+
+```js
+$('ul').has($('.pear')[0]).attr('id');
+//=> fruits
+```
+
+#### .first()
+Will select the first element of a cheerio object
+
+```js
+$('#fruits').children().first().text()
+//=> Apple
+```
+
+#### .last()
+Will select the last element of a cheerio object
+
+```js
+$('#fruits').children().last().text()
+//=> Pear
+```
+
+#### .eq( i )
+Reduce the set of matched elements to the one at the specified index. Use `.eq(-i)` to count backwards from the last selected element.
+
+```js
+$('li').eq(0).text()
+//=> Apple
+
+$('li').eq(-1).text()
+//=> Pear
+```
+
+#### .get( [i] )
+
+Retrieve the DOM elements matched by the Cheerio object. If an index is specified, retrieve one of the elements matched by the Cheerio object:
+
+```js
+$('li').get(0).tagName
+//=> li
+```
+
+If no index is specified, retrieve all elements matched by the Cheerio object:
+
+```js
+$('li').get().length
+//=> 3
+```
+
+#### .index()
+#### .index( selector )
+#### .index( nodeOrSelection )
+
+Search for a given element from among the matched elements.
+
+```js
+$('.pear').index()
+//=> 2
+$('.orange').index('li')
+//=> 1
+$('.apple').index($('#fruit, li'))
+//=> 1
+```
+
+#### .end()
+End the most recent filtering operation in the current chain and return the set of matched elements to its previous state.
+
+```js
+$('li').eq(0).end().length
+//=> 3
+```
+
+#### .add( selector [, context] )
+#### .add( element )
+#### .add( elements )
+#### .add( html )
+#### .add( selection )
+Add elements to the set of matched elements.
+
+```js
+$('.apple').add('.orange').length
+//=> 2
+```
+
+#### .addBack( [filter] )
+
+Add the previous set of elements on the stack to the current set, optionally filtered by a selector.
+
+```js
+$('li').eq(0).addBack('.orange').length
+//=> 2
+```
+
+### Manipulation
+Methods for modifying the DOM structure.
+
+#### .append( content, [content, ...] )
+Inserts content as the *last* child of each of the selected elements.
+
+```js
+$('ul').append('<li class="plum">Plum</li>')
+$.html()
+//=> <ul id="fruits">
+// <li class="apple">Apple</li>
+// <li class="orange">Orange</li>
+// <li class="pear">Pear</li>
+// <li class="plum">Plum</li>
+// </ul>
+```
+
+#### .appendTo( target )
+Insert every element in the set of matched elements to the end of the target.
+
+```js
+$('<li class="plum">Plum</li>').appendTo('#fruits')
+$.html()
+//=> <ul id="fruits">
+// <li class="apple">Apple</li>
+// <li class="orange">Orange</li>
+// <li class="pear">Pear</li>
+// <li class="plum">Plum</li>
+// </ul>
+```
+
+#### .prepend( content, [content, ...] )
+Inserts content as the *first* child of each of the selected elements.
+
+```js
+$('ul').prepend('<li class="plum">Plum</li>')
+$.html()
+//=> <ul id="fruits">
+// <li class="plum">Plum</li>
+// <li class="apple">Apple</li>
+// <li class="orange">Orange</li>
+// <li class="pear">Pear</li>
+// </ul>
+```
+
+#### .prependTo( target )
+Insert every element in the set of matched elements to the beginning of the target.
+
+```js
+$('<li class="plum">Plum</li>').prependTo('#fruits')
+$.html()
+//=> <ul id="fruits">
+// <li class="plum">Plum</li>
+// <li class="apple">Apple</li>
+// <li class="orange">Orange</li>
+// <li class="pear">Pear</li>
+// </ul>
+```
+
+#### .after( content, [content, ...] )
+Insert content next to each element in the set of matched elements.
+
+```js
+$('.apple').after('<li class="plum">Plum</li>')
+$.html()
+//=> <ul id="fruits">
+// <li class="apple">Apple</li>
+// <li class="plum">Plum</li>
+// <li class="orange">Orange</li>
+// <li class="pear">Pear</li>
+// </ul>
+```
+
+#### .insertAfter( target )
+Insert every element in the set of matched elements after the target.
+
+```js
+$('<li class="plum">Plum</li>').insertAfter('.apple')
+$.html()
+//=> <ul id="fruits">
+// <li class="apple">Apple</li>
+// <li class="plum">Plum</li>
+// <li class="orange">Orange</li>
+// <li class="pear">Pear</li>
+// </ul>
+```
+
+#### .before( content, [content, ...] )
+Insert content previous to each element in the set of matched elements.
+
+```js
+$('.apple').before('<li class="plum">Plum</li>')
+$.html()
+//=> <ul id="fruits">
+// <li class="plum">Plum</li>
+// <li class="apple">Apple</li>
+// <li class="orange">Orange</li>
+// <li class="pear">Pear</li>
+// </ul>
+```
+
+#### .insertBefore( target )
+Insert every element in the set of matched elements before the target.
+
+```js
+$('<li class="plum">Plum</li>').insertBefore('.apple')
+$.html()
+//=> <ul id="fruits">
+// <li class="plum">Plum</li>
+// <li class="apple">Apple</li>
+// <li class="orange">Orange</li>
+// <li class="pear">Pear</li>
+// </ul>
+```
+
+#### .remove( [selector] )
+Removes the set of matched elements from the DOM and all their children. `selector` filters the set of matched elements to be removed.
+
+```js
+$('.pear').remove()
+$.html()
+//=> <ul id="fruits">
+// <li class="apple">Apple</li>
+// <li class="orange">Orange</li>
+// </ul>
+```
+
+#### .replaceWith( content )
+Replaces matched elements with `content`.
+
+```js
+var plum = $('<li class="plum">Plum</li>')
+$('.pear').replaceWith(plum)
+$.html()
+//=> <ul id="fruits">
+// <li class="apple">Apple</li>
+// <li class="orange">Orange</li>
+// <li class="plum">Plum</li>
+// </ul>
+```
+
+#### .empty()
+Empties an element, removing all its children.
+
+```js
+$('ul').empty()
+$.html()
+//=> <ul id="fruits"></ul>
+```
+
+#### .html( [htmlString] )
+Gets an html content string from the first selected element. If `htmlString` is specified, each selected element's content is replaced by the new content.
+
+```js
+$('.orange').html()
+//=> Orange
+
+$('#fruits').html('<li class="mango">Mango</li>').html()
+//=> <li class="mango">Mango</li>
+```
+
+#### .text( [textString] )
+Get the combined text contents of each element in the set of matched elements, including their descendants.. If `textString` is specified, each selected element's content is replaced by the new text content.
+
+```js
+$('.orange').text()
+//=> Orange
+
+$('ul').text()
+//=> Apple
+// Orange
+// Pear
+```
+
+#### .wrap( content )
+The .wrap() function can take any string or object that could be passed to the $() factory function to specify a DOM structure. This structure may be nested several levels deep, but should contain only one inmost element. A copy of this structure will be wrapped around each of the elements in the set of matched elements. This method returns the original set of elements for chaining purposes.
+
+```js
+var redFruit = $('<div class="red-fruit"></div>')
+$('.apple').wrap(redFruit)
+
+//=> <ul id="fruits">
+// <div class="red-fruit">
+// <li class="apple">Apple</li>
+// </div>
+// <li class="orange">Orange</li>
+// <li class="plum">Plum</li>
+// </ul>
+
+var healthy = $('<div class="healthy"></div>')
+$('li').wrap(healthy)
+
+//=> <ul id="fruits">
+// <div class="healthy">
+// <li class="apple">Apple</li>
+// </div>
+// <div class="healthy">
+// <li class="orange">Orange</li>
+// </div>
+// <div class="healthy">
+// <li class="plum">Plum</li>
+// </div>
+// </ul>
+```
+
+#### .css( [propertName] ) <br /> .css( [ propertyNames] ) <br /> .css( [propertyName], [value] ) <br /> .css( [propertName], [function] ) <br /> .css( [properties] )
+
+Get the value of a style property for the first element in the set of matched elements or set one or more CSS properties for every matched element.
+
+### Rendering
+When you're ready to render the document, you can use the `html` utility function:
+
+```js
+$.html()
+//=> <ul id="fruits">
+// <li class="apple">Apple</li>
+// <li class="orange">Orange</li>
+// <li class="pear">Pear</li>
+// </ul>
+```
+
+If you want to return the outerHTML you can use `$.html(selector)`:
+
+```js
+$.html('.pear')
+//=> <li class="pear">Pear</li>
+```
+
+By default, `html` will leave some tags open. Sometimes you may instead want to render a valid XML document. For example, you might parse the following XML snippet:
+
+```xml
+$ = cheerio.load('<media:thumbnail url="http://www.foo.com/keyframe.jpg" width="75" height="50" time="12:05:01.123"/>');
+```
+
+... and later want to render to XML. To do this, you can use the 'xml' utility function:
+
+```js
+$.xml()
+//=> <media:thumbnail url="http://www.foo.com/keyframe.jpg" width="75" height="50" time="12:05:01.123"/>
+```
+
+You may also render the text content of a Cheerio object using the `text` static method:
+
+```js
+$ = cheerio.load('This is <em>content</em>.')
+$.text()
+//=> This is content.
+```
+
+The method may be called on the Cheerio module itself--be sure to pass a collection of nodes!
+
+```js
+$ = cheerio.load('<div>This is <em>content</em>.</div>')
+cheerio.text($('div'))
+//=> This is content.
+```
+
+### Miscellaneous
+DOM element methods that don't fit anywhere else
+
+#### .toArray()
+Retrieve all the DOM elements contained in the jQuery set as an array.
+
+```js
+$('li').toArray()
+//=> [ {...}, {...}, {...} ]
+```
+
+#### .clone() ####
+Clone the cheerio object.
+
+```js
+var moreFruit = $('#fruits').clone()
+```
+
+### Utilities
+
+#### $.root
+
+Sometimes you need to work with the top-level root element. To query it, you can use `$.root()`.
+
+```js
+$.root().append('<ul id="vegetables"></ul>').html();
+//=> <ul id="fruits">...</ul><ul id="vegetables"></ul>
+```
+
+#### $.contains( container, contained )
+Checks to see if the `contained` DOM element is a descendant of the `container` DOM element.
+
+#### $.parseHTML( data [, context ] [, keepScripts ] )
+Parses a string into an array of DOM nodes. The `context` argument has no meaning for Cheerio, but it is maintained for API compatability.
+
+#### $.load( html[, options ] )
+Load in the HTML. (See the previous section titled "Loading" for more information.)
+
+### Plugins
+
+Once you have loaded a document, you may extend the prototype or the equivalent `fn` property with custom plugin methods:
+
+```js
+var $ = cheerio.load('<html><body>Hello, <b>world</b>!</body></html>');
+$.prototype.logHtml = function() {
+ console.log(this.html());
+};
+
+$('body').logHtml(); // logs "Hello, <b>world</b>!" to the console
+```
+
+### The "DOM Node" object
+
+Cheerio collections are made up of objects that bear some resemblence to [browser-based DOM nodes](https://developer.mozilla.org/en-US/docs/Web/API/Node). You can expect them to define the following properties:
+
+- `tagName`
+- `parentNode`
+- `previousSibling`
+- `nextSibling`
+- `nodeValue`
+- `firstChild`
+- `childNodes`
+- `lastChild`
+
+## What about JSDOM?
+I wrote cheerio because I found myself increasingly frustrated with JSDOM. For me, there were three main sticking points that I kept running into again and again:
+
+__• JSDOM's built-in parser is too strict:__
+ JSDOM's bundled HTML parser cannot handle many popular sites out there today.
+
+__• JSDOM is too slow:__
+Parsing big websites with JSDOM has a noticeable delay.
+
+__• JSDOM feels too heavy:__
+The goal of JSDOM is to provide an identical DOM environment as what we see in the browser. I never really needed all this, I just wanted a simple, familiar way to do HTML manipulation.
+
+## When I would use JSDOM
+
+Cheerio will not solve all your problems. I would still use JSDOM if I needed to work in a browser-like environment on the server, particularly if I wanted to automate functional tests.
+
+## Screencasts
+
+http://vimeo.com/31950192
+
+> This video tutorial is a follow-up to Nettut's "How to Scrape Web Pages with Node.js and jQuery", using cheerio instead of JSDOM + jQuery. This video shows how easy it is to use cheerio and how much faster cheerio is than JSDOM + jQuery.
+
+## Contributors
+
+These are some of the contributors that have made cheerio possible:
+
+```
+project : cheerio
+ repo age : 2 years, 6 months
+ active : 285 days
+ commits : 762
+ files : 36
+ authors :
+ 293 Matt Mueller 38.5%
+ 133 Matthew Mueller 17.5%
+ 92 Mike Pennisi 12.1%
+ 54 David Chambers 7.1%
+ 30 kpdecker 3.9%
+ 19 Felix Böhm 2.5%
+ 17 fb55 2.2%
+ 15 Siddharth Mahendraker 2.0%
+ 11 Adam Bretz 1.4%
+ 8 Nazar Leush 1.0%
+ 7 ironchefpython 0.9%
+ 6 Jarno Leppänen 0.8%
+ 5 Ben Sheldon 0.7%
+ 5 Jos Shepherd 0.7%
+ 5 Ryan Schmukler 0.7%
+ 5 Steven Vachon 0.7%
+ 4 Maciej Adwent 0.5%
+ 4 Amir Abu Shareb 0.5%
+ 3 jeremy.dentel at brandingbrand.com 0.4%
+ 3 Andi Neck 0.4%
+ 2 steve 0.3%
+ 2 alexbardas 0.3%
+ 2 finspin 0.3%
+ 2 Ali Farhadi 0.3%
+ 2 Chris Khoo 0.3%
+ 2 Rob Ashton 0.3%
+ 2 Thomas Heymann 0.3%
+ 2 Jaro Spisak 0.3%
+ 2 Dan Dascalescu 0.3%
+ 2 Torstein Thune 0.3%
+ 2 Wayne Larsen 0.3%
+ 1 Timm Preetz 0.1%
+ 1 Xavi 0.1%
+ 1 Alex Shaindlin 0.1%
+ 1 mattym 0.1%
+ 1 Felix Böhm 0.1%
+ 1 Farid Neshat 0.1%
+ 1 Dmitry Mazuro 0.1%
+ 1 Jeremy Hubble 0.1%
+ 1 nevermind 0.1%
+ 1 Manuel Alabor 0.1%
+ 1 Matt Liegey 0.1%
+ 1 Chris O'Hara 0.1%
+ 1 Michael Holroyd 0.1%
+ 1 Michiel De Mey 0.1%
+ 1 Ben Atkin 0.1%
+ 1 Rich Trott 0.1%
+ 1 Rob "Hurricane" Ashton 0.1%
+ 1 Robin Gloster 0.1%
+ 1 Simon Boudrias 0.1%
+ 1 Sindre Sorhus 0.1%
+ 1 xiaohwan 0.1%
+```
+
+## Cheerio in the real world
+
+Are you using cheerio in production? Add it to the [wiki](https://github.com/cheeriojs/cheerio/wiki/Cheerio-in-Production)!
+
+## Testing
+
+To run the test suite, download the repository, then within the cheerio directory, run:
+
+```shell
+make setup
+make test
+```
+
+This will download the development packages and run the test suite.
+
+## Special Thanks
+
+This library stands on the shoulders of some incredible developers. A special thanks to:
+
+__• @FB55 for node-htmlparser2 & CSSSelect:__
+Felix has a knack for writing speedy parsing engines. He completely re-wrote both @tautologistic's `node-htmlparser` and @harry's `node-soupselect` from the ground up, making both of them much faster and more flexible. Cheerio would not be possible without his foundational work
+
+__• @jQuery team for jQuery:__
+The core API is the best of its class and despite dealing with all the browser inconsistencies the code base is extremely clean and easy to follow. Much of cheerio's implementation and documentation is from jQuery. Thanks guys.
+
+__• @visionmedia:__
+The style, the structure, the open-source"-ness" of this library comes from studying TJ's style and using many of his libraries. This dude consistently pumps out high-quality libraries and has always been more than willing to help or answer questions. You rock TJ.
+
+## License
+
+MIT
diff --git a/benchmark/.gitattributes b/benchmark/.gitattributes
new file mode 100644
index 0000000..79f1443
--- /dev/null
+++ b/benchmark/.gitattributes
@@ -0,0 +1,2 @@
+documents/* binary
+jquery*.js binary
diff --git a/benchmark/benchmark.js b/benchmark/benchmark.js
new file mode 100755
index 0000000..6d1825d
--- /dev/null
+++ b/benchmark/benchmark.js
@@ -0,0 +1,333 @@
+#!/usr/bin/env node
+
+var Suites = require('./suite');
+var suites = new Suites();
+
+var regexIdx = process.argv.indexOf('--regex') + 1;
+if (regexIdx > 0) {
+ if (regexIdx === process.argv.length) {
+ console.error('Error: the "--regex" option requires a value');
+ process.exit(1);
+ }
+ suites.filter(process.argv[regexIdx]);
+}
+if (process.argv.indexOf('--cheerio-only') >= 0) {
+ suites.cheerioOnly();
+}
+
+suites.add('Select all', 'jquery.html', {
+ test: function($) { $('*').length; }
+});
+suites.add('Select some', 'jquery.html', {
+ test: function($) { $('li').length; }
+});
+
+/*
+ * Manipulation Tests
+ */
+suites.add('manipulation - append', 'jquery.html', {
+ setup: function($) {
+ return $('body');
+ },
+ test: function($, $body) {
+ $body.append(new Array(50).join('<div>'));
+ }
+});
+
+// These tests run out of memory in jsdom
+suites.add('manipulation - prepend - highmem', 'jquery.html', {
+ setup: function($) {
+ return $('body');
+ },
+ test: function($, $body) {
+ $body.prepend(new Array(50).join('<div>'));
+ }
+});
+suites.add('manipulation - after - highmem', 'jquery.html', {
+ setup: function($) {
+ return $('body');
+ },
+ test: function($, $body) {
+ $body.after(new Array(50).join('<div>'));
+ }
+});
+suites.add('manipulation - before - highmem', 'jquery.html', {
+ setup: function($) {
+ return $('body');
+ },
+ test: function($, $body) {
+ $body.before(new Array(50).join('<div>'));
+ }
+});
+
+suites.add('manipulation - remove', 'jquery.html', {
+ setup: function($) {
+ return $('body');
+ },
+ test: function($, $lis) {
+ var child = $('<div>');
+ $lis.append(child);
+ child.remove();
+ }
+});
+
+suites.add('manipulation - replaceWith', 'jquery.html', {
+ setup: function($) {
+ $('body').append('<div id="foo">');
+ },
+ test: function($, $lis) {
+ $('#foo').replaceWith('<div id="foo">');
+ }
+});
+
+suites.add('manipulation - empty', 'jquery.html', {
+ setup: function($) {
+ return $('li');
+ },
+ test: function($, $lis) {
+ $lis.empty();
+ }
+});
+suites.add('manipulation - html', 'jquery.html', {
+ setup: function($) {
+ return $('li');
+ },
+ test: function($, $lis) {
+ $lis.html();
+ $lis.html('foo');
+ }
+});
+suites.add('manipulation - html render', 'jquery.html', {
+ setup: function($) {
+ return $('body');
+ },
+ test: function($, $lis) {
+ $lis.html();
+ }
+});
+suites.add('manipulation - html independent', 'jquery.html', {
+ setup: function() {
+ return '<div class="foo"><div id="bar">bat<hr>baz</div> </div>'
+ + '<div class="foo"><div id="bar">bat<hr>baz</div> </div>'
+ + '<div class="foo"><div id="bar">bat<hr>baz</div> </div>'
+ + '<div class="foo"><div id="bar">bat<hr>baz</div> </div>'
+ + '<div class="foo"><div id="bar">bat<hr>baz</div> </div>'
+ + '<div class="foo"><div id="bar">bat<hr>baz</div> </div>';
+ },
+ test: function($, content) {
+ $(content).html();
+ }
+});
+suites.add('manipulation - text', 'jquery.html', {
+ setup: function($) {
+ return $('li');
+ },
+ test: function($, $lis) {
+ $lis.text();
+ $lis.text('foo');
+ }
+});
+
+
+/*
+ * Traversing Tests
+ */
+suites.add('traversing - Find', 'jquery.html', {
+ setup: function($) {
+ return $('li');
+ },
+ test: function($, $lis) {
+ $lis.find('li').length;
+ }
+});
+suites.add('traversing - Parent', 'jquery.html', {
+ setup: function($) {
+ return $('li');
+ },
+ test: function($, $lis) {
+ $lis.parent('div').length;
+ }
+});
+suites.add('traversing - Parents', 'jquery.html', {
+ setup: function($) {
+ return $('li');
+ },
+ test: function($, $lis) {
+ $lis.parents('div').length;
+ }
+});
+suites.add('traversing - Closest', 'jquery.html', {
+ setup: function($) {
+ return $('li');
+ },
+ test: function($, $lis) {
+ $lis.closest('div').length;
+ }
+});
+suites.add('traversing - next', 'jquery.html', {
+ setup: function($) {
+ return $('li');
+ },
+ test: function($, $lis) {
+ $lis.next().length;
+ }
+});
+suites.add('traversing - nextAll', 'jquery.html', {
+ setup: function($) {
+ return $('li');
+ },
+ test: function($, $lis) {
+ $lis.nextAll('li').length;
+ }
+});
+suites.add('traversing - nextUntil', 'jquery.html', {
+ setup: function($) {
+ return $('li');
+ },
+ test: function($, $lis) {
+ $lis.nextUntil('li').length;
+ }
+});
+suites.add('traversing - prev', 'jquery.html', {
+ setup: function($) {
+ return $('li');
+ },
+ test: function($, $lis) {
+ $lis.prev().length;
+ }
+});
+suites.add('traversing - prevAll', 'jquery.html', {
+ setup: function($) {
+ return $('li');
+ },
+ test: function($, $lis) {
+ $lis.prevAll('li').length;
+ }
+});
+suites.add('traversing - prevUntil', 'jquery.html', {
+ setup: function($) {
+ return $('li');
+ },
+ test: function($, $lis) {
+ $lis.prevUntil('li').length;
+ }
+});
+suites.add('traversing - siblings', 'jquery.html', {
+ setup: function($) {
+ return $('li');
+ },
+ test: function($, $lis) {
+ $lis.siblings('li').length;
+ }
+});
+suites.add('traversing - Children', 'jquery.html', {
+ setup: function($) {
+ return $('li');
+ },
+ test: function($, $lis) {
+ $lis.children('a').length;
+ }
+});
+suites.add('traversing - Filter', 'jquery.html', {
+ setup: function($) {
+ return $('li');
+ },
+ test: function($, $lis) {
+ $lis.filter('li').length;
+ }
+});
+suites.add('traversing - First', 'jquery.html', {
+ setup: function($) {
+ return $('li');
+ },
+ test: function($, $lis) {
+ $lis.first().first().length;
+ }
+});
+suites.add('traversing - Last', 'jquery.html', {
+ setup: function($) {
+ return $('li');
+ },
+ test: function($, $lis) {
+ $lis.last().last().length;
+ }
+});
+suites.add('traversing - Eq', 'jquery.html', {
+ setup: function($) {
+ return $('li');
+ },
+ test: function($, $lis) {
+ $lis.eq(0).eq(0).length;
+ }
+});
+
+
+/*
+ * Attributes Tests
+ */
+suites.add('attributes - Attributes', 'jquery.html', {
+ setup: function($) {
+ return $('li');
+ },
+ test: function($, $lis) {
+ $lis.attr('foo', 'bar');
+ $lis.attr('foo');
+ $lis.removeAttr('foo');
+ }
+});
+suites.add('attributes - Single Attribute', 'jquery.html', {
+ setup: function($) {
+ return $('body');
+ },
+ test: function($, $lis) {
+ $lis.attr('foo', 'bar');
+ $lis.attr('foo');
+ $lis.removeAttr('foo');
+ }
+});
+suites.add('attributes - Data', 'jquery.html', {
+ setup: function($) {
+ return $('li');
+ },
+ test: function($, $lis) {
+ $lis.data('foo', 'bar');
+ $lis.data('foo');
+ }
+});
+suites.add('attributes - Val', 'jquery.html', {
+ setup: function($) {
+ return $('select,input,textarea,option');
+ },
+ test: function($, $lis) {
+ $lis.each(function() {
+ $(this).val();
+ $(this).val('foo');
+ });
+ }
+});
+
+suites.add('attributes - Has class', 'jquery.html', {
+ setup: function($) {
+ return $('li');
+ },
+ test: function($, $lis) {
+ $lis.hasClass('foo');
+ }
+});
+suites.add('attributes - Toggle class', 'jquery.html', {
+ setup: function($) {
+ return $('li');
+ },
+ test: function($, $lis) {
+ $lis.toggleClass('foo');
+ }
+});
+suites.add('attributes - Add Remove class', 'jquery.html', {
+ setup: function($) {
+ return $('li');
+ },
+ test: function($, $lis) {
+ $lis.addClass('foo');
+ $lis.removeClass('foo');
+ }
+});
diff --git a/benchmark/documents/jquery.html b/benchmark/documents/jquery.html
new file mode 100644
index 0000000..1b3535b
--- /dev/null
+++ b/benchmark/documents/jquery.html
@@ -0,0 +1,1199 @@
+<html class="js multiplebgs boxshadow cssgradients wf-klavikaweb-i7-active wf-klavikaweb-n7-active wf-sourcecodepro-n4-active wf-sourcecodepro-n7-active wf-active" lang="en-US">
+ <head data-live-domain="api.jquery.com"><script type="text/javascript" async="" src="null"></script>
+ <meta charset="utf-8">
+ <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+
+ <title>jQuery() | jQuery API Documentation</title>
+
+ <meta name="author" content="jQuery Foundation - jquery.org">
+ <meta name="description" content="jQuery: The Write Less, Do More, JavaScript Library">
+
+ <meta name="viewport" content="width=device-width">
+
+ <link rel="shortcut icon" href="http://api.jquery.com/jquery-wp-content/themes/api.jquery.com/i/favicon.ico" src="null">
+
+ <link rel="stylesheet" href="http://api.jquery.com/jquery-wp-content/themes/jquery/css/base.css?v=1" src="null">
+ <link rel="stylesheet" href="http://api.jquery.com/jquery-wp-content/themes/api.jquery.com/style.css" src="null">
+ <link rel="pingback" href="http://api.jquery.com/xmlrpc.php" src="null">
+ <!--[if lt IE 7]><link rel="stylesheet" href="css/font-awesome-ie7.min.css"><![endif]-->
+
+ <script type="text/javascript" async="" src="null"></script><script src="null"></script>
+
+ <script src="null"></script>
+ <script>window.jQuery || document.write(unescape('%3Cscript src="http://api.jquery.com/jquery-wp-content/themes/jquery/js/jquery-1.9.1.min.js"%3E%3C/script%3E'))</script>
+ <script src="null"></script>
+
+ <script src="null"></script>
+ <script src="null"></script>
+
+ <script src="null"></script>
+ <style type="text/css">.tk-source-code-pro{font-family:"source-code-pro",sans-serif;}.tk-klavika-web{font-family:"klavika-web",sans-serif;}</style><link rel="stylesheet" href="http://use.typekit.net/c/d4d852/klavika-web:i7:n7,source-code-pro:n4:n7.PYh:F:2,PYg:F:2,Y1M:F:2,Y1P:F:2/d?3bb2a6e53c9684ffdc9a98f2135b2a6250f2340d8ca0853b7df9676f5fa610fe069f9d29c9b5e67ae7b6312a16ff95d3a73356eed53502d6630d88cb0fe9789e0ac2d9a6c14ac282069f97be80efceecd4f5e0e58b889e8649ff22efc0c4063e9f9f87c7a8920dca [...]
+
+ <link rel="alternate" type="application/rss+xml" title="jQuery API Documentation » Feed" href="http://api.jquery.com/feed/" src="null">
+ <link rel="alternate" type="application/rss+xml" title="jQuery API Documentation » Comments Feed" href="http://api.jquery.com/comments/feed/" src="null">
+ <link rel="alternate" type="application/rss+xml" title="jQuery API Documentation » jQuery() Comments Feed" href="http://api.jquery.com/jQuery/feed/" src="null">
+ <script type="text/javascript" src="null"></script>
+ <link rel="EditURI" type="application/rsd+xml" title="RSD" href="http://api.jquery.com/xmlrpc.php?rsd" src="null">
+ <link rel="wlwmanifest" type="application/wlwmanifest+xml" href="http://api.jquery.com/wp-includes/wlwmanifest.xml" src="null">
+ <link rel="prev" title="jQuery.holdReady()" href="http://api.jquery.com/jQuery.holdReady/" src="null">
+ <link rel="next" title="jQuery.inArray()" href="http://api.jquery.com/jQuery.inArray/" src="null">
+ <meta name="generator" content="WordPress 3.7">
+ <link rel="canonical" href="http://api.jquery.com/jQuery/" src="null">
+ <link rel="shortlink" href="http://api.jquery.com/?p=339" src="null">
+
+</head>
+<body class="api jquery single single-post postid-339 single-format-standard single-author singular">
+
+<!--[if lt IE 7]>
+<p class="chromeframe">You are using an outdated browser. <a href="http://browsehappy.com/">Upgrade your browser today</a> or <a href="http://www.google.com/chromeframe/?redirect=true">install Google Chrome Frame</a> to better experience this site.</p>
+<![endif]-->
+
+<header>
+ <section id="global-nav">
+ <nav>
+ <div class="constrain">
+ <ul class="projects">
+ <li class="project jquery"><a href="http://jquery.com/" title="jQuery" src="null">jQuery</a></li>
+ <li class="project jquery-ui"><a href="http://jqueryui.com/" title="jQuery UI" src="null">jQuery UI</a></li>
+ <li class="project jquery-mobile"><a href="http://jquerymobile.com/" title="jQuery Mobile" src="null">jQuery Mobile</a></li>
+ <li class="project sizzlejs"><a href="http://sizzlejs.com/" title="Sizzle" src="null">Sizzle</a></li>
+ <li class="project qunitjs"><a href="http://qunitjs.com/" title="QUnit" src="null">QUnit</a></li>
+ </ul>
+ <ul class="links l_tinynav1">
+ <li><a href="http://plugins.jquery.com/" src="null">Plugins</a></li>
+ <li class="dropdown"><a href="http://contribute.jquery.org/" src="null">Contribute</a>
+ <ul>
+ <li><a href="http://contribute.jquery.org/cla/" src="null">CLA</a></li>
+ <li><a href="http://contribute.jquery.org/style-guide/" src="null">Style Guides</a></li>
+ <li><a href="http://contribute.jquery.org/triage/" src="null">Bug Triage</a></li>
+ <li><a href="http://contribute.jquery.org/code/" src="null">Code</a></li>
+ <li><a href="http://contribute.jquery.org/documentation/" src="null">Documentation</a></li>
+ <li><a href="http://contribute.jquery.org/web-sites/" src="null">Web Sites</a></li>
+ </ul>
+ </li>
+ <li class="dropdown"><a href="http://events.jquery.org/" src="null">Events</a>
+ <ul class="wide">
+ <li><a href="http://www.deque.com/deque-partners-jquery-create-accessibility-summit" src="null">Oct 10-11 | jQuery Accessibility Summit</a></li>
+ <li><a href="http://jquery.itmozg.ru/" src="null">Oct 15 | jQuery Russia</a></li>
+ <li><a href="http://modernweb.com/training/jquery-oct-2013.php" src="null">Oct 15-17 | jQuery Virtual Training</a></li>
+ <li><a href="http://2013.cssdevconf.com/" src="null">Oct 21-22 | CSS Dev Conf</a></li>
+ <li><a href="http://javascriptsummit.com/" src="null">Nov 19-21 | JavaScript Summit</a></li>
+ <li><a href="http://events.jquery.org/2014/san-diego/" src="null">Feb 12-13 | jQuery San Diego</a></li>
+ <li><a href="http://www.gentics.com/jquery-europe" src="null">Feb 28-Mar 1 | jQuery Europe</a></li>
+ <li><a href="http://jqueryuk.com" src="null">May 16 | jQuery UK</a></li>
+ </ul>
+ </li>
+ <li class="dropdown"><a href="https://jquery.org/support/" src="null">Support</a>
+ <ul>
+ <li><a href="http://learn.jquery.com/" src="null">Learning Center</a></li>
+ <li><a href="http://try.jquery.com/" src="null">Try jQuery</a></li>
+ <li><a href="http://irc.jquery.org/" src="null">IRC/Chat</a></li>
+ <li><a href="http://forum.jquery.com/" src="null">Forums</a></li>
+ <li><a href="http://stackoverflow.com/tags/jquery/info" src="null">Stack Overflow</a></li>
+ <li><a href="https://jquery.org/support/" src="null">Commercial Support</a></li>
+ </ul>
+ </li>
+ <li class="dropdown"><a href="https://jquery.org/" src="null">jQuery Foundation</a>
+ <ul>
+ <li><a href="https://jquery.org/join/" src="null">Join</a></li>
+ <li><a href="https://jquery.org/members/" src="null">Members</a></li>
+ <li><a href="https://jquery.org/team/" src="null">Team</a></li>
+ <li><a href="http://brand.jquery.org/" src="null">Brand Guide</a></li>
+ <li><a href="https://jquery.org/donate/" src="null">Donate</a></li>
+ </ul>
+ </li>
+ </ul>
+ </div>
+ </nav>
+ </section>
+</header>
+
+<div id="container">
+ <div id="logo-events" class="constrain clearfix">
+ <h2 class="logo"><a href="http://jquery.com/" title="jQuery API Documentation" src="null">jQuery API Documentation</a></h2>
+
+ <aside><div id="broadcast"><a href="http://engine.adzerk.net/r?e=eyJhdiI6MjIyMzYsImF0IjoxMzE0LCJjbSI6NjM3NjcsImNoIjo4MzY4LCJjciI6MTc4ODcxLCJkaSI6ImZhNDViODgwMzBhOTQxYzNhZmMyZTg0MmIwYzFiZDI1IiwiZG0iOjEsImZjIjoyMjgyOTgsImZsIjoxMTM1MjksImt3IjoidW5kZWZpbmVkIiwibnciOjU0NDksInJmIjoiaHR0cDovL2FwaS5qcXVlcnkuY29tLz9zPWpxdWVyeSIsInJ2IjowLCJwciI6MjE5MzcsInN0Ijo1MzgyOSwidHMiOjEzODU4Mzk5ODgyOTIsInVyIjoiaHR0cDovL2V2ZW50cy5qcXVlcnkub3JnLzIwMTQvc2FuLWRpZWdvLyJ9&s=pUb8fXw8Ar8bGvQszZJHXjkx8Gk" rel [...]
+ </div>
+
+ <nav id="main" class="constrain clearfix">
+ <div class="menu-top-container">
+ <ul id="menu-top" class="menu l_tinynav2">
+<li class="menu-item"><a href="http://jquery.com/download/" src="null">Download</a></li>
+<li class="menu-item current"><a href="http://api.jquery.com/" src="null">API Documentation</a></li>
+<li class="menu-item"><a href="http://blog.jquery.com/" src="null">Blog</a></li>
+<li class="menu-item"><a href="http://plugins.jquery.com/" src="null">Plugins</a></li>
+<li class="menu-item"><a href="http://jquery.com/browser-support/" src="null">Browser Support</a></li>
+ </ul><select id="tinynav2" class="tinynav tinynav2"><option>Navigate...</option><option value="http://jquery.com/download/">Download</option><option value="http://api.jquery.com/">API Documentation</option><option value="http://blog.jquery.com/">Blog</option><option value="http://plugins.jquery.com/">Plugins</option><option value="http://jquery.com/browser-support/">Browser Support</option></select>
+</div>
+
+ <form method="get" class="searchform" action="http://api.jquery.com/">
+ <button type="submit" class="icon-search"><span class="visuallyhidden">search</span></button>
+ <label>
+ <span class="visuallyhidden">Search jQuery API Documentation</span>
+ <input type="text" name="s" value="" placeholder="Search">
+ </label>
+ <label>
+ <span class="visuallyhidden">Search jQuery API Documentation</span>
+ <input type="radio" name="rad" value="" placeholder="Search">
+ </label>
+ <label>
+ <span class="visuallyhidden">Search jQuery API Documentation</span>
+ <input type="radio" name="rad" value="foo" placeholder="Search">
+ </label>
+</form>
+ </nav>
+
+ <div id="content-wrapper" class="clearfix row">
+
+<div class="content-right twelve columns">
+ <div id="content">
+
+<article id="post-339" class="post-339 post type-post status-publish format-standard hentry category-core category-10 category-14">
+ <header class="entry-header">
+ <h1 class="entry-title">jQuery()</h1>
+ <hr>
+ <div class="entry-meta">
+ Categories: <span class="category"><a href="http://api.jquery.com/category/core/" title="View all posts in Core" src="null">Core</a></span> </div><!-- .entry-meta -->
+ </header><!-- .entry-header -->
+
+ <div class="entry-content">
+ Return a collection of matched elements either found in the DOM based on passed argument(s) or created by passing an HTML string.<div class="toc">
+<h4><span>Contents:</span></h4>
+<ul class="toc-list">
+<li>
+<a href="#jQuery1" src="null">jQuery( selector [, context ] )</a><ul>
+<li><a href="#jQuery-selector-context" src="null">jQuery( selector [, context ] )</a></li>
+<li><a href="#jQuery-element" src="null">jQuery( element )</a></li>
+<li><a href="#jQuery-elementArray" src="null">jQuery( elementArray )</a></li>
+<li><a href="#jQuery-object" src="null">jQuery( object )</a></li>
+<li><a href="#jQuery-jQuery-object" src="null">jQuery( jQuery object )</a></li>
+<li><a href="#jQuery" src="null">jQuery()</a></li>
+</ul>
+</li>
+<li>
+<a href="#jQuery2" src="null">jQuery( html [, ownerDocument ] )</a><ul>
+<li><a href="#jQuery-html-ownerDocument" src="null">jQuery( html [, ownerDocument ] )</a></li>
+<li><a href="#jQuery-html-attributes" src="null">jQuery( html, attributes )</a></li>
+</ul>
+</li>
+<li>
+<a href="#jQuery3" src="null">jQuery( callback )</a><ul><li><a href="#jQuery-callback" src="null">jQuery( callback )</a></li></ul>
+</li>
+</ul>
+</div><article id="jQuery1" class="entry method"><h2 class="section-title">
+<span class="name">jQuery( selector [, context ] )</span><span class="returns">Returns: <a href="http://api.jquery.com/Types/#jQuery" src="null">jQuery</a></span>
+</h2>
+<div class="entry-wrapper">
+<p class="desc"><strong>Description: </strong>Accepts a string containing a CSS selector which is then used to match a set of elements.</p>
+<ul class="signatures">
+<li class="signature">
+<h4 class="name">
+<span class="version-details">version added: <a href="/category/version/1.0/" src="null">1.0</a></span><a id="jQuery-selector-context" href="#jQuery-selector-context" src="null"><span class="icon-link"></span>jQuery( selector [, context ] )</a>
+</h4>
+<ul>
+<li>
+<div><strong>selector</strong></div>
+<div>Type: <a href="http://api.jquery.com/Types/#Selector" src="null">Selector</a>
+</div>
+<div>A string containing a selector expression</div>
+</li>
+<li>
+<div><strong>context</strong></div>
+<div>Type: <a href="http://api.jquery.com/Types/#Element" src="null">Element</a> or <a href="http://api.jquery.com/Types/#jQuery" src="null">jQuery</a>
+</div>
+<div>A DOM Element, Document, or jQuery to use as context</div>
+</li>
+</ul>
+</li>
+<li class="signature">
+<h4 class="name">
+<span class="version-details">version added: <a href="/category/version/1.0/" src="null">1.0</a></span><a id="jQuery-element" href="#jQuery-element" src="null"><span class="icon-link"></span>jQuery( element )</a>
+</h4>
+<ul><li>
+<div><strong>element</strong></div>
+<div>Type: <a href="http://api.jquery.com/Types/#Element" src="null">Element</a>
+</div>
+<div>A DOM element to wrap in a jQuery object.</div>
+</li></ul>
+</li>
+<li class="signature">
+<h4 class="name">
+<span class="version-details">version added: <a href="/category/version/1.0/" src="null">1.0</a></span><a id="jQuery-elementArray" href="#jQuery-elementArray" src="null"><span class="icon-link"></span>jQuery( elementArray )</a>
+</h4>
+<ul><li>
+<div><strong>elementArray</strong></div>
+<div>Type: <a href="http://api.jquery.com/Types/#Array" src="null">Array</a>
+</div>
+<div>An array containing a set of DOM elements to wrap in a jQuery object.</div>
+</li></ul>
+</li>
+<li class="signature">
+<h4 class="name">
+<span class="version-details">version added: <a href="/category/version/1.0/" src="null">1.0</a></span><a id="jQuery-object" href="#jQuery-object" src="null"><span class="icon-link"></span>jQuery( object )</a>
+</h4>
+<ul><li>
+<div><strong>object</strong></div>
+<div>Type: <a href="http://api.jquery.com/Types/#PlainObject" src="null">PlainObject</a>
+</div>
+<div>A plain object to wrap in a jQuery object.</div>
+</li></ul>
+</li>
+<li class="signature">
+<h4 class="name">
+<span class="version-details">version added: <a href="/category/version/1.0/" src="null">1.0</a></span><a id="jQuery-jQuery-object" href="#jQuery-jQuery-object" src="null"><span class="icon-link"></span>jQuery( jQuery object )</a>
+</h4>
+<ul><li>
+<div><strong>jQuery object</strong></div>
+<div>Type: <a href="http://api.jquery.com/Types/#PlainObject" src="null">PlainObject</a>
+</div>
+<div>An existing jQuery object to clone.</div>
+</li></ul>
+</li>
+<li class="signature">
+<h4 class="name">
+<span class="version-details">version added: <a href="/category/version/1.4/" src="null">1.4</a></span><a id="jQuery" href="#jQuery" src="null"><span class="icon-link"></span>jQuery()</a>
+</h4>
+<ul><li><div class="null-signature">This signature does not accept any arguments.</div></li></ul>
+</li>
+</ul>
+<div class="longdesc" id="entry-longdesc">
+ <p>In the first formulation listed above, <code>jQuery()</code> — which can also be written as <code>$()</code> — searches through the DOM for any elements that match the provided selector and creates a new jQuery object that references these elements:</p>
+ <div class="syntaxhighlighter javascript nogutter">
+ <table>
+ <tbody>
+ <tr>
+ <td class="gutter">
+
+ <div class="line n1">1</div>
+
+ </td>
+ <td class="code">
+ <pre><div class="container"><div class="line"><code>$( <span class="string">"div.foo"</span> );</code></div></div></pre>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+</div>
+
+ <p>If no elements match the provided selector, the new jQuery object is "empty"; that is, it contains no elements and has <code><a href="/length/" src="null">.length</a></code> property of 0.</p>
+ <h4 id="selector-context">Selector Context</h4>
+ <p>By default, selectors perform their searches within the DOM starting at the document root. However, an alternate context can be given for the search by using the optional second parameter to the <code>$()</code> function. For example, to do a search within an event handler, the search can be restricted like so:</p>
+ <div class="syntaxhighlighter javascript nogutter">
+ <table>
+ <tbody>
+ <tr>
+ <td class="gutter">
+
+ <div class="line n1">1</div>
+
+ <div class="line n2">2</div>
+
+ <div class="line n3">3</div>
+
+ </td>
+ <td class="code">
+ <pre><div class="container"><div class="line"><code>$( <span class="string">"div.foo"</span> ).click(<span class="keyword">function</span>() {</code></div></div><div class="container"><div class="line"><code> $( <span class="string">"span"</span>, <span class="keyword">this</span> ).addClass( <span class="string">"bar"</span> );</code></div></div><div class="container"><div class="line"><code>});</code></div></div></pre>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+</div>
+
+ <p>When the search for the span selector is restricted to the context of <code>this</code>, only spans within the clicked element will get the additional class.</p>
+ <p>Internally, selector context is implemented with the <code>.find()</code> method, so <code>$( "span", this )</code> is equivalent to <code>$( this ).find( "span" )</code>.</p>
+
+ <h4 id="using-dom-elements">Using DOM elements</h4>
+ <p>The second and third formulations of this function create a jQuery object using one or more DOM elements that were already selected in some other way.</p>
+ <p><strong>Note:</strong> These formulations are meant to consume only DOM elements; feeding mixed data to the elementArray form is particularly discouraged.</p>
+ <p>A common use of this facility is to call jQuery methods on an element that has been passed to a callback function through the keyword <code>this</code>:</p>
+ <div class="syntaxhighlighter javascript nogutter">
+ <table>
+ <tbody>
+ <tr>
+ <td class="gutter">
+
+ <div class="line n1">1</div>
+
+ <div class="line n2">2</div>
+
+ <div class="line n3">3</div>
+
+ </td>
+ <td class="code">
+ <pre><div class="container"><div class="line"><code>$( <span class="string">"div.foo"</span> ).click(<span class="keyword">function</span>() {</code></div></div><div class="container"><div class="line"><code> $( <span class="keyword">this</span> ).slideUp();</code></div></div><div class="container"><div class="line"><code>});</code></div></div></pre>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+</div>
+
+ <p>This example causes elements to be hidden with a sliding animation when clicked. Because the handler receives the clicked item in the <code>this</code> keyword as a bare DOM element, the element must be passed to the <code>$()</code> function before applying jQuery methods to it.</p>
+ <p>XML data returned from an Ajax call can be passed to the <code>$()</code> function so individual elements of the XML structure can be retrieved using <code>.find()</code> and other DOM traversal methods.</p>
+ <div class="syntaxhighlighter javascript nogutter">
+ <table>
+ <tbody>
+ <tr>
+ <td class="gutter">
+
+ <div class="line n1">1</div>
+
+ <div class="line n2">2</div>
+
+ <div class="line n3">3</div>
+
+ </td>
+ <td class="code">
+ <pre><div class="container"><div class="line"><code>$.post( <span class="string">"url.xml"</span>, <span class="keyword">function</span>( data ) {</code></div></div><div class="container"><div class="line"><code> <span class="keyword">var</span> $child = $( data ).find( <span class="string">"child"</span> );</code></div></div><div class="container"><div class="line"><code>});</code></div></div></pre>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+</div>
+
+
+ <h4 id="cloning-jquery-objects">Cloning jQuery Objects</h4>
+ <p>When a jQuery object is passed to the <code>$()</code> function, a clone of the object is created. This new jQuery object references the same DOM elements as the initial one.</p>
+
+ <h4 id="returning-empty-set">Returning an Empty Set</h4>
+ <p>As of jQuery 1.4, calling the <code>jQuery()</code> method with <em>no arguments</em> returns an empty jQuery set (with a <code><a href="/length/" src="null">.length</a></code> property of 0). In previous versions of jQuery, this would return a set containing the document node.</p>
+ <h4 id="working-with-plain-objects">Working With Plain Objects</h4>
+ <p>At present, the only operations supported on plain JavaScript objects wrapped in jQuery are: <code>.data()</code>,<code>.prop()</code>,<code>.on()</code>, <code>.off()</code>, <code>.trigger()</code> and <code>.triggerHandler()</code>. The use of <code>.data()</code> (or any method requiring <code>.data()</code>) on a plain object will result in a new property on the object called jQuery{randomNumber} (eg. jQuery123456789).</p>
+ <div class="syntaxhighlighter javascript nogutter">
+ <table>
+ <tbody>
+ <tr>
+ <td class="gutter">
+
+ <div class="line n1">1</div>
+
+ <div class="line n2">2</div>
+
+ <div class="line n3">3</div>
+
+ <div class="line n4">4</div>
+
+ <div class="line n5">5</div>
+
+ <div class="line n6">6</div>
+
+ <div class="line n7">7</div>
+
+ <div class="line n8">8</div>
+
+ <div class="line n9">9</div>
+
+ <div class="line n10">10</div>
+
+ <div class="line n11">11</div>
+
+ <div class="line n12">12</div>
+
+ <div class="line n13">13</div>
+
+ <div class="line n14">14</div>
+
+ <div class="line n15">15</div>
+
+ <div class="line n16">16</div>
+
+ <div class="line n17">17</div>
+
+ <div class="line n18">18</div>
+
+ <div class="line n19">19</div>
+
+ <div class="line n20">20</div>
+
+ <div class="line n21">21</div>
+
+ <div class="line n22">22</div>
+
+ <div class="line n23">23</div>
+
+ </td>
+ <td class="code">
+ <pre><div class="container"><div class="line"><code><span class="comment">// Define a plain object</span></code></div></div><div class="container"><div class="line"><code><span class="keyword">var</span> foo = { foo: <span class="string">"bar"</span>, hello: <span class="string">"world"</span> };</code></div></div><div class="container"><div class="line"><code> </code></div></div><div class="container"><div class="line"><code><span class="comment">// Pass it to the jQuery funct [...]
+ </td>
+ </tr>
+ </tbody>
+ </table>
+</div>
+
+ <p>Should <code>.trigger( "eventName" )</code> be used, it will search for an "eventName" property on the object and attempt to execute it after any attached jQuery handlers are executed. It does not check whether the property is a function or not. To avoid this behavior, <code>.triggerHandler( "eventName" )</code> should be used instead.</p>
+ <div class="syntaxhighlighter javascript nogutter">
+ <table>
+ <tbody>
+ <tr>
+ <td class="gutter">
+
+ <div class="line n1">1</div>
+
+ </td>
+ <td class="code">
+ <pre><div class="container"><div class="line"><code>$foo.triggerHandler( <span class="string">"eventName"</span> ); <span class="comment">// Also logs "eventName was called"</span></code></div></div></pre>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+</div>
+
+ </div>
+<section class="entry-examples" id="entry-examples"><header><h2>Examples:</h2></header><div class="entry-example" id="example-0">
+<h4>Example: <span class="desc">Find all p elements that are children of a div element and apply a border to them.</span>
+</h4>
+<div class="syntaxhighlighter xml ">
+ <table>
+ <tbody>
+ <tr>
+ <td class="gutter">
+
+ <div class="line n1">1</div>
+
+ <div class="line n2">2</div>
+
+ <div class="line n3">3</div>
+
+ <div class="line n4">4</div>
+
+ <div class="line n5">5</div>
+
+ <div class="line n6">6</div>
+
+ <div class="line n7">7</div>
+
+ <div class="line n8">8</div>
+
+ <div class="line n9">9</div>
+
+ <div class="line n10">10</div>
+
+ <div class="line n11">11</div>
+
+ <div class="line n12">12</div>
+
+ <div class="line n13">13</div>
+
+ <div class="line n14">14</div>
+
+ <div class="line n15">15</div>
+
+ <div class="line n16">16</div>
+
+ <div class="line n17">17</div>
+
+ <div class="line n18">18</div>
+
+ <div class="line n19">19</div>
+
+ </td>
+ <td class="code">
+ <pre><div class="container"><div class="line"><code><span class="doctype"><!doctype html></span></code></div></div><div class="container"><div class="line"><code><span class="tag"><<span class="title">html</span> <span class="attribute">lang</span>=<span class="value">"en"</span>></span></code></div></div><div class="container"><div class="line"><code><span class="tag"><<span class="title">head</span>></span></code></div></div><div class="container"><div class [...]
+ </td>
+ </tr>
+ </tbody>
+ </table>
+</div>
+
+<h4>Demo:</h4>
+<div class="demo code-demo"><iframe width="100%" height="250"></iframe></div>
+</div>
+<div class="entry-example" id="example-1">
+<h4>Example: <span class="desc">Find all inputs of type radio within the first form in the document.</span>
+</h4>
+<div class="syntaxhighlighter javascript ">
+ <table>
+ <tbody>
+ <tr>
+ <td class="gutter">
+
+ <div class="line n1">1</div>
+
+ </td>
+ <td class="code">
+ <pre><div class="container"><div class="line"><code>$( <span class="string">"input:radio"</span>, document.forms[ <span class="number">0</span> ] );</code></div></div></pre>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+</div>
+
+</div>
+<div class="entry-example" id="example-2">
+<h4>Example: <span class="desc">Find all div elements within an XML document from an Ajax response.</span>
+</h4>
+<div class="syntaxhighlighter javascript ">
+ <table>
+ <tbody>
+ <tr>
+ <td class="gutter">
+
+ <div class="line n1">1</div>
+
+ </td>
+ <td class="code">
+ <pre><div class="container"><div class="line"><code>$( <span class="string">"div"</span>, xml.responseXML );</code></div></div></pre>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+</div>
+
+</div>
+<div class="entry-example" id="example-3">
+<h4>Example: <span class="desc">Set the background color of the page to black.</span>
+</h4>
+<div class="syntaxhighlighter javascript ">
+ <table>
+ <tbody>
+ <tr>
+ <td class="gutter">
+
+ <div class="line n1">1</div>
+
+ </td>
+ <td class="code">
+ <pre><div class="container"><div class="line"><code>$( document.body ).css( <span class="string">"background"</span>, <span class="string">"black"</span> );</code></div></div></pre>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+</div>
+
+</div>
+<div class="entry-example" id="example-4">
+<h4>Example: <span class="desc">Hide all the input elements within a form.</span>
+</h4>
+<div class="syntaxhighlighter javascript ">
+ <table>
+ <tbody>
+ <tr>
+ <td class="gutter">
+
+ <div class="line n1">1</div>
+
+ </td>
+ <td class="code">
+ <pre><div class="container"><div class="line"><code>$( myForm.elements ).hide();</code></div></div></pre>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+</div>
+
+</div></section>
+</div></article><article id="jQuery2" class="entry method"><h2 class="section-title">
+<span class="name">jQuery( html [, ownerDocument ] )</span><span class="returns">Returns: <a href="http://api.jquery.com/Types/#jQuery" src="null">jQuery</a></span>
+</h2>
+<div class="entry-wrapper">
+<p class="desc"><strong>Description: </strong>Creates DOM elements on the fly from the provided string of raw HTML.</p>
+<ul class="signatures">
+<li class="signature">
+<h4 class="name">
+<span class="version-details">version added: <a href="/category/version/1.0/" src="null">1.0</a></span><a id="jQuery-html-ownerDocument" href="#jQuery-html-ownerDocument" src="null"><span class="icon-link"></span>jQuery( html [, ownerDocument ] )</a>
+</h4>
+<ul>
+<li>
+<div><strong>html</strong></div>
+<div>Type: <a href="http://api.jquery.com/Types/#htmlString" src="null">htmlString</a>
+</div>
+<div>A string of HTML to create on the fly. Note that this parses HTML, <strong>not</strong> XML.</div>
+</li>
+<li>
+<div><strong>ownerDocument</strong></div>
+<div>Type: <a href="http://api.jquery.com/Types/#document" src="null">document</a>
+</div>
+<div>A document in which the new elements will be created.</div>
+</li>
+</ul>
+</li>
+<li class="signature">
+<h4 class="name">
+<span class="version-details">version added: <a href="/category/version/1.4/" src="null">1.4</a></span><a id="jQuery-html-attributes" href="#jQuery-html-attributes" src="null"><span class="icon-link"></span>jQuery( html, attributes )</a>
+</h4>
+<ul>
+<li>
+<div><strong>html</strong></div>
+<div>Type: <a href="http://api.jquery.com/Types/#htmlString" src="null">htmlString</a>
+</div>
+<div>A string defining a single, standalone, HTML element (e.g. <div/> or <div></div>).</div>
+</li>
+<li>
+<div><strong>attributes</strong></div>
+<div>Type: <a href="http://api.jquery.com/Types/#PlainObject" src="null">PlainObject</a>
+</div>
+<div>An object of attributes, events, and methods to call on the newly-created element.</div>
+</li>
+</ul>
+</li>
+</ul>
+<div class="longdesc" id="entry-longdesc-1">
+ <h4 id="creating-new-elements">Creating New Elements</h4>
+ <p>If a string is passed as the parameter to <code>$()</code>, jQuery examines the string to see if it looks like HTML (i.e., it starts with <code><tag ... ></code>). If not, the string is interpreted as a selector expression, as explained above. But if the string appears to be an HTML snippet, jQuery attempts to create new DOM elements as described by the HTML. Then a jQuery object is created and returned that refers to these elements. You can perform any of the usual jQuery [...]
+ <div class="syntaxhighlighter javascript nogutter">
+ <table>
+ <tbody>
+ <tr>
+ <td class="gutter">
+
+ <div class="line n1">1</div>
+
+ </td>
+ <td class="code">
+ <pre><div class="container"><div class="line"><code>$( <span class="string">"<p id='test'>My <em>new</em> text</p>"</span> ).appendTo( <span class="string">"body"</span> );</code></div></div></pre>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+</div>
+
+ <p>For explicit parsing of a string to HTML, use the <a href="/jQuery.parseHTML/" src="null">$.parseHTML()</a> method.</p>
+ <p>By default, elements are created with an <code>ownerDocument</code> matching the document into which the jQuery library was loaded. Elements being injected into a different document should be created using that document, e.g., <code>$("<p>hello iframe</p>", $("#myiframe").prop("contentWindow").document)</code>.</p>
+ <p>If the HTML is more complex than a single tag without attributes, as it is in the above example, the actual creation of the elements is handled by the browser's <code>innerHTML</code> mechanism. In most cases, jQuery creates a new <div> element and sets the innerHTML property of the element to the HTML snippet that was passed in. When the parameter has a single tag (with optional closing tag or quick-closing) — <code>$( "<img />" )</code> or <code>$( "<img> [...]
+ <p>When passing in complex HTML, some browsers may not generate a DOM that exactly replicates the HTML source provided. As mentioned, jQuery uses the browser"s <code>.innerHTML</code> property to parse the passed HTML and insert it into the current document. During this process, some browsers filter out certain elements such as <code><html></code>, <code><title></code>, or <code><head></code> elements. As a result, the elements inserted may not be representat [...]
+ <p>Filtering isn't, however, limited to these tags. For example, Internet Explorer prior to version 8 will also convert all <code>href</code> properties on links to absolute URLs, and Internet Explorer prior to version 9 will not correctly handle HTML5 elements without the addition of a separate <a href="http://code.google.com/p/html5shiv/" src="null">compatibility layer</a>.</p>
+ <p>To ensure cross-platform compatibility, the snippet must be well-formed. Tags that can contain other elements should be paired with a closing tag:</p>
+ <div class="syntaxhighlighter javascript nogutter">
+ <table>
+ <tbody>
+ <tr>
+ <td class="gutter">
+
+ <div class="line n1">1</div>
+
+ </td>
+ <td class="code">
+ <pre><div class="container"><div class="line"><code>$( <span class="string">"<a href='http://jquery.com'></a>"</span> );</code></div></div></pre>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+</div>
+
+ <p>Tags that cannot contain elements may be quick-closed or not:</p>
+ <div class="syntaxhighlighter javascript nogutter">
+ <table>
+ <tbody>
+ <tr>
+ <td class="gutter">
+
+ <div class="line n1">1</div>
+
+ <div class="line n2">2</div>
+
+ </td>
+ <td class="code">
+ <pre><div class="container"><div class="line"><code>$( <span class="string">"<img>"</span> );</code></div></div><div class="container"><div class="line"><code>$( <span class="string">"<input>"</span> );</code></div></div></pre>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+</div>
+
+ <p>When passing HTML to <code>jQuery()</code>, please also note that text nodes are not treated as DOM elements. With the exception of a few methods (such as <code>.content()</code>), they are generally otherwise ignored or removed. E.g:</p>
+ <div class="syntaxhighlighter javascript nogutter">
+ <table>
+ <tbody>
+ <tr>
+ <td class="gutter">
+
+ <div class="line n1">1</div>
+
+ <div class="line n2">2</div>
+
+ </td>
+ <td class="code">
+ <pre><div class="container"><div class="line"><code><span class="keyword">var</span> el = $( <span class="string">"1<br>2<br>3"</span> ); <span class="comment">// returns [<br>, "2", <br>]</span></code></div></div><div class="container"><div class="line"><code>el = $( <span class="string">"1<br>2<br>3 >"</span> ); <span class="comment">// returns [<br>, "2", <br>, "3 >"]</span></code></div></div></pre>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+</div>
+
+ <p>This behavior is expected. </p>
+ <p>As of jQuery 1.4, the second argument to <code>jQuery()</code> can accept a plain object consisting of a superset of the properties that can be passed to the <a href="/attr/" src="null">.attr()</a> method.</p>
+ <p><strong>Important:</strong> If the second argument is passed, the HTML string in the first argument must represent a a simple element with no attributes. <strong>As of jQuery 1.4</strong>, any <a href="/category/events/" src="null">event type</a> can be passed in, and the following jQuery methods can be called: <a href="/val/" src="null">val</a>, <a href="/css/" src="null">css</a>, <a href="/html/" src="null">html</a>, <a href="/text/" src="null">text</a>, <a href="/data/" src=" [...]
+ <p><strong>As of jQuery 1.8</strong>, any jQuery instance method (a method of <code>jQuery.fn</code>) can be used as a property of the object passed to the second parameter:</p>
+ <div class="syntaxhighlighter javascript nogutter">
+ <table>
+ <tbody>
+ <tr>
+ <td class="gutter">
+
+ <div class="line n1">1</div>
+
+ <div class="line n2">2</div>
+
+ <div class="line n3">3</div>
+
+ <div class="line n4">4</div>
+
+ <div class="line n5">5</div>
+
+ <div class="line n6">6</div>
+
+ <div class="line n7">7</div>
+
+ <div class="line n8">8</div>
+
+ </td>
+ <td class="code">
+ <pre><div class="container"><div class="line"><code>$( <span class="string">"<div></div>"</span>, {</code></div></div><div class="container"><div class="line"><code> <span class="string">"class"</span>: <span class="string">"my-div"</span>,</code></div></div><div class="container"><div class="line"><code> on: {</code></div></div><div class="container"><div class="line"><code> touchstart: <span class="keyword">function</span>( event ) {</code></div></div><div cl [...]
+ </td>
+ </tr>
+ </tbody>
+ </table>
+</div>
+
+ <p>The name <code>"class"</code> must be quoted in the object since it is a JavaScript reserved word, and <code>"className"</code> cannot be used since it refers to the DOM property, not the attribute. </p>
+ <p>While the second argument is convenient, its flexibility can lead to unintended consequences (e.g. <code>$( "<input>", {size: "4"} )</code> calling the <code>.size()</code> method instead of setting the size attribute). The previous code block could thus be written instead as:</p>
+<div class="syntaxhighlighter javascript nogutter">
+ <table>
+ <tbody>
+ <tr>
+ <td class="gutter">
+
+ <div class="line n1">1</div>
+
+ <div class="line n2">2</div>
+
+ <div class="line n3">3</div>
+
+ <div class="line n4">4</div>
+
+ <div class="line n5">5</div>
+
+ <div class="line n6">6</div>
+
+ <div class="line n7">7</div>
+
+ <div class="line n8">8</div>
+
+ </td>
+ <td class="code">
+ <pre><div class="container"><div class="line"><code>$( <span class="string">"<div></div>"</span> )</code></div></div><div class="container"><div class="line"><code> .addClass( <span class="string">"my-div"</span> )</code></div></div><div class="container"><div class="line"><code> .on({</code></div></div><div class="container"><div class="line"><code> touchstart: <span class="keyword">function</span>( event ) {</code></div></div><div class="container"><div class [...]
+ </td>
+ </tr>
+ </tbody>
+ </table>
+</div>
+
+ </div>
+<section class="entry-examples" id="entry-examples-1"><header><h2>Examples:</h2></header><div class="entry-example" id="example-1-0">
+<h4>Example: <span class="desc">Create a div element (and all of its contents) dynamically and append it to the body element. Internally, an element is created and its innerHTML property set to the given markup.</span>
+</h4>
+<div class="syntaxhighlighter javascript ">
+ <table>
+ <tbody>
+ <tr>
+ <td class="gutter">
+
+ <div class="line n1">1</div>
+
+ </td>
+ <td class="code">
+ <pre><div class="container"><div class="line"><code>$( <span class="string">"<div><p>Hello</p></div>"</span> ).appendTo( <span class="string">"body"</span> )</code></div></div></pre>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+</div>
+
+</div>
+<div class="entry-example" id="example-1-1">
+<h4>Example: <span class="desc">Create some DOM elements.</span>
+</h4>
+<div class="syntaxhighlighter javascript ">
+ <table>
+ <tbody>
+ <tr>
+ <td class="gutter">
+
+ <div class="line n1">1</div>
+
+ <div class="line n2">2</div>
+
+ <div class="line n3">3</div>
+
+ <div class="line n4">4</div>
+
+ <div class="line n5">5</div>
+
+ <div class="line n6">6</div>
+
+ <div class="line n7">7</div>
+
+ <div class="line n8">8</div>
+
+ </td>
+ <td class="code">
+ <pre><div class="container"><div class="line"><code>$( <span class="string">"<div/>"</span>, {</code></div></div><div class="container"><div class="line"><code> <span class="string">"class"</span>: <span class="string">"test"</span>,</code></div></div><div class="container"><div class="line"><code> text: <span class="string">"Click me!"</span>,</code></div></div><div class="container"><div class="line"><code> click: <span class="keyword">function</span>() {</code></div [...]
+ </td>
+ </tr>
+ </tbody>
+ </table>
+</div>
+
+</div></section>
+</div></article><article id="jQuery3" class="entry method"><h2 class="section-title">
+<span class="name">jQuery( callback )</span><span class="returns">Returns: <a href="http://api.jquery.com/Types/#jQuery" src="null">jQuery</a></span>
+</h2>
+<div class="entry-wrapper">
+<p class="desc"><strong>Description: </strong>Binds a function to be executed when the DOM has finished loading.</p>
+<ul class="signatures"><li class="signature">
+<h4 class="name">
+<span class="version-details">version added: <a href="/category/version/1.0/" src="null">1.0</a></span><a id="jQuery-callback" href="#jQuery-callback" src="null"><span class="icon-link"></span>jQuery( callback )</a>
+</h4>
+<ul><li>
+<div><strong>callback</strong></div>
+<div>Type: <a href="http://api.jquery.com/Types/#Function" src="null">Function</a>()</div>
+<div>The function to execute when the DOM is ready.</div>
+</li></ul>
+</li></ul>
+<div class="longdesc" id="entry-longdesc-2">
+ <p>This function behaves just like <code>$( document ).ready()</code>, in that it should be used to wrap other <code>$()</code> operations on your page that depend on the DOM being ready. While this function is, technically, chainable, there really isn"t much use for chaining against it.</p>
+ </div>
+<section class="entry-examples" id="entry-examples-2"><header><h2>Examples:</h2></header><div class="entry-example" id="example-2-0">
+<h4>Example: <span class="desc">Execute the function when the DOM is ready to be used.</span>
+</h4>
+<div class="syntaxhighlighter javascript ">
+ <table>
+ <tbody>
+ <tr>
+ <td class="gutter">
+
+ <div class="line n1">1</div>
+
+ <div class="line n2">2</div>
+
+ <div class="line n3">3</div>
+
+ </td>
+ <td class="code">
+ <pre><div class="container"><div class="line"><code>$(<span class="keyword">function</span>() {</code></div></div><div class="container"><div class="line"><code> <span class="comment">// Document is ready</span></code></div></div><div class="container"><div class="line"><code>});</code></div></div></pre>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+</div>
+
+</div>
+<div class="entry-example" id="example-2-1">
+<h4>Example: <span class="desc">Use both the shortcut for $(document).ready() and the argument to write failsafe jQuery code using the $ alias, without relying on the global alias.</span>
+</h4>
+<div class="syntaxhighlighter javascript ">
+ <table>
+ <tbody>
+ <tr>
+ <td class="gutter">
+
+ <div class="line n1">1</div>
+
+ <div class="line n2">2</div>
+
+ <div class="line n3">3</div>
+
+ </td>
+ <td class="code">
+ <pre><div class="container"><div class="line"><code>jQuery(<span class="keyword">function</span>( $ ) {</code></div></div><div class="container"><div class="line"><code> <span class="comment">// Your code using failsafe $ alias here...</span></code></div></div><div class="container"><div class="line"><code>});</code></div></div></pre>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+</div>
+
+</div></section>
+</div></article> </div><!-- .entry-content -->
+
+</article><!-- #post-339 -->
+ </div>
+
+ <div id="sidebar" class="widget-area" role="complementary">
+ <aside id="categories" class="widget">
+ <ul>
+ <li class="cat-item cat-item-1"><a href="http://api.jquery.com/category/ajax/" title="View all posts filed under Ajax" src="null">Ajax</a>
+<ul class="children">
+ <li class="cat-item cat-item-2"><a href="http://api.jquery.com/category/ajax/global-ajax-event-handlers/" title="View all posts filed under Global Ajax Event Handlers" src="null">Global Ajax Event Handlers</a>
+</li>
+ <li class="cat-item cat-item-3"><a href="http://api.jquery.com/category/ajax/helper-functions/" title="View all posts filed under Helper Functions" src="null">Helper Functions</a>
+</li>
+ <li class="cat-item cat-item-4"><a href="http://api.jquery.com/category/ajax/low-level-interface/" title="View all posts filed under Low-Level Interface" src="null">Low-Level Interface</a>
+</li>
+ <li class="cat-item cat-item-5"><a href="http://api.jquery.com/category/ajax/shorthand-methods/" title="View all posts filed under Shorthand Methods" src="null">Shorthand Methods</a>
+</li>
+</ul>
+</li>
+ <li class="cat-item cat-item-6"><a href="http://api.jquery.com/category/attributes/" title="View all posts filed under Attributes" src="null">Attributes</a>
+</li>
+ <li class="cat-item cat-item-7"><a href="http://api.jquery.com/category/callbacks-object/" title="View all posts filed under Callbacks Object" src="null">Callbacks Object</a>
+</li>
+ <li class="cat-item cat-item-8 current-cat"><a href="http://api.jquery.com/category/core/" title="View all posts filed under Core" src="null">Core</a>
+</li>
+ <li class="cat-item cat-item-9"><a href="http://api.jquery.com/category/css/" title="View all posts filed under CSS" src="null">CSS</a>
+</li>
+ <li class="cat-item cat-item-10"><a href="http://api.jquery.com/category/data/" title="View all posts filed under Data" src="null">Data</a>
+</li>
+ <li class="cat-item cat-item-11"><a href="http://api.jquery.com/category/deferred-object/" title="View all posts filed under Deferred Object" src="null">Deferred Object</a>
+</li>
+ <li class="cat-item cat-item-87"><a href="http://api.jquery.com/category/deprecated/" title="View all posts filed under Deprecated" src="null">Deprecated</a>
+<ul class="children">
+ <li class="cat-item cat-item-93"><a href="http://api.jquery.com/category/deprecated/deprecated-1.10/" title="View all posts filed under Deprecated 1.10" src="null">Deprecated 1.10</a>
+</li>
+ <li class="cat-item cat-item-90"><a href="http://api.jquery.com/category/deprecated/deprecated-1.3/" title="View all posts filed under Deprecated 1.3" src="null">Deprecated 1.3</a>
+</li>
+ <li class="cat-item cat-item-88"><a href="http://api.jquery.com/category/deprecated/deprecated-1.7/" title="View all posts filed under Deprecated 1.7" src="null">Deprecated 1.7</a>
+</li>
+ <li class="cat-item cat-item-89"><a href="http://api.jquery.com/category/deprecated/deprecated-1.8/" title="View all posts filed under Deprecated 1.8" src="null">Deprecated 1.8</a>
+</li>
+</ul>
+</li>
+ <li class="cat-item cat-item-12"><a href="http://api.jquery.com/category/dimensions/" title="View all posts filed under Dimensions" src="null">Dimensions</a>
+</li>
+ <li class="cat-item cat-item-13"><a href="http://api.jquery.com/category/effects/" title="View all posts filed under Effects" src="null">Effects</a>
+<ul class="children">
+ <li class="cat-item cat-item-14"><a href="http://api.jquery.com/category/effects/basics/" title="View all posts filed under Basics" src="null">Basics</a>
+</li>
+ <li class="cat-item cat-item-15"><a href="http://api.jquery.com/category/effects/custom-effects/" title="View all posts filed under Custom" src="null">Custom</a>
+</li>
+ <li class="cat-item cat-item-16"><a href="http://api.jquery.com/category/effects/fading/" title="View all posts filed under Fading" src="null">Fading</a>
+</li>
+ <li class="cat-item cat-item-17"><a href="http://api.jquery.com/category/effects/sliding/" title="View all posts filed under Sliding" src="null">Sliding</a>
+</li>
+</ul>
+</li>
+ <li class="cat-item cat-item-18"><a href="http://api.jquery.com/category/events/" title="View all posts filed under Events" src="null">Events</a>
+<ul class="children">
+ <li class="cat-item cat-item-19"><a href="http://api.jquery.com/category/events/browser-events/" title="View all posts filed under Browser Events" src="null">Browser Events</a>
+</li>
+ <li class="cat-item cat-item-20"><a href="http://api.jquery.com/category/events/document-loading/" title="View all posts filed under Document Loading" src="null">Document Loading</a>
+</li>
+ <li class="cat-item cat-item-21"><a href="http://api.jquery.com/category/events/event-handler-attachment/" title="View all posts filed under Event Handler Attachment" src="null">Event Handler Attachment</a>
+</li>
+ <li class="cat-item cat-item-22"><a href="http://api.jquery.com/category/events/event-object/" title="View all posts filed under Event Object" src="null">Event Object</a>
+</li>
+ <li class="cat-item cat-item-23"><a href="http://api.jquery.com/category/events/form-events/" title="View all posts filed under Form Events" src="null">Form Events</a>
+</li>
+ <li class="cat-item cat-item-24"><a href="http://api.jquery.com/category/events/keyboard-events/" title="View all posts filed under Keyboard Events" src="null">Keyboard Events</a>
+</li>
+ <li class="cat-item cat-item-25"><a href="http://api.jquery.com/category/events/mouse-events/" title="View all posts filed under Mouse Events" src="null">Mouse Events</a>
+</li>
+</ul>
+</li>
+ <li class="cat-item cat-item-26"><a href="http://api.jquery.com/category/forms/" title="View all posts filed under Forms" src="null">Forms</a>
+</li>
+ <li class="cat-item cat-item-27"><a href="http://api.jquery.com/category/internals/" title="View all posts filed under Internals" src="null">Internals</a>
+</li>
+ <li class="cat-item cat-item-28"><a href="http://api.jquery.com/category/manipulation/" title="View all posts filed under Manipulation" src="null">Manipulation</a>
+<ul class="children">
+ <li class="cat-item cat-item-29"><a href="http://api.jquery.com/category/manipulation/class-attribute/" title="View all posts filed under Class Attribute" src="null">Class Attribute</a>
+</li>
+ <li class="cat-item cat-item-30"><a href="http://api.jquery.com/category/manipulation/copying/" title="View all posts filed under Copying" src="null">Copying</a>
+</li>
+ <li class="cat-item cat-item-32"><a href="http://api.jquery.com/category/manipulation/dom-insertion-around/" title="View all posts filed under DOM Insertion, Around" src="null">DOM Insertion, Around</a>
+</li>
+ <li class="cat-item cat-item-33"><a href="http://api.jquery.com/category/manipulation/dom-insertion-inside/" title="View all posts filed under DOM Insertion, Inside" src="null">DOM Insertion, Inside</a>
+</li>
+ <li class="cat-item cat-item-34"><a href="http://api.jquery.com/category/manipulation/dom-insertion-outside/" title="View all posts filed under DOM Insertion, Outside" src="null">DOM Insertion, Outside</a>
+</li>
+ <li class="cat-item cat-item-35"><a href="http://api.jquery.com/category/manipulation/dom-removal/" title="View all posts filed under DOM Removal" src="null">DOM Removal</a>
+</li>
+ <li class="cat-item cat-item-36"><a href="http://api.jquery.com/category/manipulation/dom-replacement/" title="View all posts filed under DOM Replacement" src="null">DOM Replacement</a>
+</li>
+ <li class="cat-item cat-item-37"><a href="http://api.jquery.com/category/manipulation/general-attributes/" title="View all posts filed under General Attributes" src="null">General Attributes</a>
+</li>
+ <li class="cat-item cat-item-38"><a href="http://api.jquery.com/category/manipulation/style-properties/" title="View all posts filed under Style Properties" src="null">Style Properties</a>
+</li>
+</ul>
+</li>
+ <li class="cat-item cat-item-39"><a href="http://api.jquery.com/category/miscellaneous/" title="View all posts filed under Miscellaneous" src="null">Miscellaneous</a>
+<ul class="children">
+ <li class="cat-item cat-item-40"><a href="http://api.jquery.com/category/miscellaneous/collection-manipulation/" title="View all posts filed under Collection Manipulation" src="null">Collection Manipulation</a>
+</li>
+ <li class="cat-item cat-item-41"><a href="http://api.jquery.com/category/miscellaneous/data-storage/" title="View all posts filed under Data Storage" src="null">Data Storage</a>
+</li>
+ <li class="cat-item cat-item-42"><a href="http://api.jquery.com/category/miscellaneous/dom-element-methods/" title="View all posts filed under DOM Element Methods" src="null">DOM Element Methods</a>
+</li>
+ <li class="cat-item cat-item-43"><a href="http://api.jquery.com/category/miscellaneous/setup-methods/" title="View all posts filed under Setup Methods" src="null">Setup Methods</a>
+</li>
+</ul>
+</li>
+ <li class="cat-item cat-item-44"><a href="http://api.jquery.com/category/offset/" title="View all posts filed under Offset" src="null">Offset</a>
+</li>
+ <li class="cat-item cat-item-45"><a href="http://api.jquery.com/category/properties/" title="View all posts filed under Properties" src="null">Properties</a>
+<ul class="children">
+ <li class="cat-item cat-item-46"><a href="http://api.jquery.com/category/properties/jquery-object-instance-properties/" title="View all posts filed under Properties of jQuery Object Instances" src="null">Properties of jQuery Object Instances</a>
+</li>
+ <li class="cat-item cat-item-47"><a href="http://api.jquery.com/category/properties/global-jquery-object-properties/" title="View all posts filed under Properties of the Global jQuery Object" src="null">Properties of the Global jQuery Object</a>
+</li>
+</ul>
+</li>
+ <li class="cat-item cat-item-92"><a href="http://api.jquery.com/category/removed/" title="View all posts filed under Removed" src="null">Removed</a>
+</li>
+ <li class="cat-item cat-item-48"><a href="http://api.jquery.com/category/selectors/" title="View all posts filed under Selectors" src="null">Selectors</a>
+<ul class="children">
+ <li class="cat-item cat-item-49"><a href="http://api.jquery.com/category/selectors/attribute-selectors/" title="View all posts filed under Attribute" src="null">Attribute</a>
+</li>
+ <li class="cat-item cat-item-50"><a href="http://api.jquery.com/category/selectors/basic-css-selectors/" title="View all posts filed under Basic" src="null">Basic</a>
+</li>
+ <li class="cat-item cat-item-51"><a href="http://api.jquery.com/category/selectors/basic-filter-selectors/" title="View all posts filed under Basic Filter" src="null">Basic Filter</a>
+</li>
+ <li class="cat-item cat-item-52"><a href="http://api.jquery.com/category/selectors/child-filter-selectors/" title="View all posts filed under Child Filter" src="null">Child Filter</a>
+</li>
+ <li class="cat-item cat-item-53"><a href="http://api.jquery.com/category/selectors/content-filter-selector/" title="View all posts filed under Content Filter" src="null">Content Filter</a>
+</li>
+ <li class="cat-item cat-item-54"><a href="http://api.jquery.com/category/selectors/form-selectors/" title="View all posts filed under Form" src="null">Form</a>
+</li>
+ <li class="cat-item cat-item-55"><a href="http://api.jquery.com/category/selectors/hierarchy-selectors/" title="View all posts filed under Hierarchy" src="null">Hierarchy</a>
+</li>
+ <li class="cat-item cat-item-56"><a href="http://api.jquery.com/category/selectors/jquery-selector-extensions/" title="View all posts filed under jQuery Extensions" src="null">jQuery Extensions</a>
+</li>
+ <li class="cat-item cat-item-57"><a href="http://api.jquery.com/category/selectors/visibility-filter-selectors/" title="View all posts filed under Visibility Filter" src="null">Visibility Filter</a>
+</li>
+</ul>
+</li>
+ <li class="cat-item cat-item-58"><a href="http://api.jquery.com/category/traversing/" title="View all posts filed under Traversing" src="null">Traversing</a>
+<ul class="children">
+ <li class="cat-item cat-item-59"><a href="http://api.jquery.com/category/traversing/filtering/" title="View all posts filed under Filtering" src="null">Filtering</a>
+</li>
+ <li class="cat-item cat-item-60"><a href="http://api.jquery.com/category/traversing/miscellaneous-traversal/" title="View all posts filed under Miscellaneous Traversing" src="null">Miscellaneous Traversing</a>
+</li>
+ <li class="cat-item cat-item-61"><a href="http://api.jquery.com/category/traversing/tree-traversal/" title="View all posts filed under Tree Traversal" src="null">Tree Traversal</a>
+</li>
+</ul>
+</li>
+ <li class="cat-item cat-item-63"><a href="http://api.jquery.com/category/utilities/" title="View all posts filed under Utilities" src="null">Utilities</a>
+</li>
+ <li class="cat-item cat-item-64"><a href="http://api.jquery.com/category/version/" title="View all posts filed under Version" src="null">Version</a>
+<ul class="children">
+ <li class="cat-item cat-item-65"><a href="http://api.jquery.com/category/version/1.0/" title="View all posts filed under Version 1.0" src="null">Version 1.0</a>
+</li>
+ <li class="cat-item cat-item-66"><a href="http://api.jquery.com/category/version/1.0.4/" title="View all posts filed under Version 1.0.4" src="null">Version 1.0.4</a>
+</li>
+ <li class="cat-item cat-item-67"><a href="http://api.jquery.com/category/version/1.1/" title="View all posts filed under Version 1.1" src="null">Version 1.1</a>
+</li>
+ <li class="cat-item cat-item-68"><a href="http://api.jquery.com/category/version/1.1.2/" title="View all posts filed under Version 1.1.2" src="null">Version 1.1.2</a>
+</li>
+ <li class="cat-item cat-item-69"><a href="http://api.jquery.com/category/version/1.1.3/" title="View all posts filed under Version 1.1.3" src="null">Version 1.1.3</a>
+</li>
+ <li class="cat-item cat-item-70"><a href="http://api.jquery.com/category/version/1.1.4/" title="View all posts filed under Version 1.1.4" src="null">Version 1.1.4</a>
+</li>
+ <li class="cat-item cat-item-71"><a href="http://api.jquery.com/category/version/1.2/" title="View all posts filed under Version 1.2" src="null">Version 1.2</a>
+</li>
+ <li class="cat-item cat-item-72"><a href="http://api.jquery.com/category/version/1.2.3/" title="View all posts filed under Version 1.2.3" src="null">Version 1.2.3</a>
+</li>
+ <li class="cat-item cat-item-73"><a href="http://api.jquery.com/category/version/1.2.6/" title="View all posts filed under Version 1.2.6" src="null">Version 1.2.6</a>
+</li>
+ <li class="cat-item cat-item-74"><a href="http://api.jquery.com/category/version/1.3/" title="View all posts filed under Version 1.3" src="null">Version 1.3</a>
+</li>
+ <li class="cat-item cat-item-75"><a href="http://api.jquery.com/category/version/1.4/" title="View all posts filed under Version 1.4" src="null">Version 1.4</a>
+</li>
+ <li class="cat-item cat-item-76"><a href="http://api.jquery.com/category/version/1.4.1/" title="View all posts filed under Version 1.4.1" src="null">Version 1.4.1</a>
+</li>
+ <li class="cat-item cat-item-77"><a href="http://api.jquery.com/category/version/1.4.2/" title="View all posts filed under Version 1.4.2" src="null">Version 1.4.2</a>
+</li>
+ <li class="cat-item cat-item-78"><a href="http://api.jquery.com/category/version/1.4.3/" title="View all posts filed under Version 1.4.3" src="null">Version 1.4.3</a>
+</li>
+ <li class="cat-item cat-item-79"><a href="http://api.jquery.com/category/version/1.4.4/" title="View all posts filed under Version 1.4.4" src="null">Version 1.4.4</a>
+</li>
+ <li class="cat-item cat-item-80"><a href="http://api.jquery.com/category/version/1.5/" title="View all posts filed under Version 1.5" src="null">Version 1.5</a>
+</li>
+ <li class="cat-item cat-item-81"><a href="http://api.jquery.com/category/version/1.5.1/" title="View all posts filed under Version 1.5.1" src="null">Version 1.5.1</a>
+</li>
+ <li class="cat-item cat-item-82"><a href="http://api.jquery.com/category/version/1.6/" title="View all posts filed under Version 1.6" src="null">Version 1.6</a>
+</li>
+ <li class="cat-item cat-item-83"><a href="http://api.jquery.com/category/version/1.7/" title="View all posts filed under Version 1.7" src="null">Version 1.7</a>
+</li>
+ <li class="cat-item cat-item-84"><a href="http://api.jquery.com/category/version/1.8/" title="View all posts filed under Version 1.8" src="null">Version 1.8</a>
+</li>
+ <li class="cat-item cat-item-86"><a href="http://api.jquery.com/category/version/1.9/" title="View all posts filed under Version 1.9" src="null">Version 1.9</a>
+</li>
+</ul>
+</li>
+ </ul>
+ </aside>
+</div>
+</div>
+ </div>
+</div>
+
+<footer class="clearfix simple">
+ <div class="constrain">
+ <div class="row">
+ <div class="eight columns">
+ <h3><span>Quick Access</span></h3>
+ <div class="cdn">
+ <strong>CDN</strong>
+ <input value="//code.jquery.com/jquery-1.10.2.min.js" readonly="">
+ </div>
+ <div class="download">
+ <strong><a href="http://jquery.com/download/" src="null">Download jQuery 1.10.2 →</a></strong>
+ </div>
+ <div class="tinynav-container"><h3><span>More jQuery Sites</span></h3><select id="tinynav1" class="tinynav tinynav1"><option>Browse...</option><option value="http://plugins.jquery.com/">Plugins</option><option value="http://contribute.jquery.org/">Contribute</option><option value="http://contribute.jquery.org/cla/">- CLA</option><option value="http://contribute.jquery.org/style-guide/">- Style Guides</option><option value="http://contribute.jquery.org/triage/">- Bug Triage</optio [...]
+ <li><a class="icon-github" href="http://github.com/jquery/jquery" src="null">GitHub <small>jQuery <br>Source</small></a></li>
+ <li><a class="icon-group" href="http://forum.jquery.com" src="null">Forum <small>Community <br>Support</small></a></li>
+ <li><a class="icon-warning-sign" href="http://bugs.jquery.com" src="null">Bugs <small>Issue <br>Tracker</small></a></li>
+ </ul>
+ </div>
+
+ <div class="four columns">
+ <h3><span>Books</span></h3>
+ <ul class="books">
+ <li>
+ <a href="http://www.packtpub.com/learning-jquery-with-simple-javascript-techniques-fourth-edition/book" src="null">
+ <span class="bottom"><img src="null" alt="Learning jQuery 4th Edition by Karl Swedberg and Jonathan Chaffer" width="92" height="114"></span>
+ <strong>Learning jQuery Fourth Edition</strong><br>
+ <cite>Karl Swedberg and Jonathan Chaffer</cite>
+ </a>
+ </li>
+ <li>
+ <a href="http://www.manning.com/affiliate/idevaffiliate.php?id=648_176" src="null">
+ <span><img src="null" alt="jQuery in Action by Bear Bibeault and Yehuda Katz" width="92" height="114"></span>
+ <strong>jQuery in Action</strong><br>
+ <cite>Bear Bibeault and Yehuda Katz</cite>
+ </a>
+ </li>
+ <li>
+ <a href="http://www.syncfusion.com/resources/techportal/ebooks/jquery?utm_medium=BizDev-jQuery.org0513" src="null">
+ <span><img src="null" alt="jQuery Succinctly by Cody Lindley" width="92" height="114"></span>
+ <strong>jQuery Succinctly</strong><br>
+ <cite>Cody Lindley</cite>
+ </a>
+ </li>
+ </ul>
+ </div>
+ </div>
+
+ <div id="legal">
+ <ul class="footer-site-links">
+ <li><a class="icon-pencil" href="http://learn.jquery.com/" src="null">Learning Center</a></li>
+ <li><a class="icon-group" href="http://forum.jquery.com/" src="null">Forum</a></li>
+ <li><a class="icon-wrench" href="http://api.jquery.com/" src="null">API</a></li>
+ <li><a class="icon-twitter" href="http://twitter.com/jquery" src="null">Twitter</a></li>
+ <li><a class="icon-comments" href="http://irc.jquery.org/" src="null">IRC</a></li>
+ </ul>
+ <p class="copyright">
+ Copyright 2013 <a href="https://jquery.org/team/" src="null">The jQuery Foundation</a>.<br>
+ <span class="sponsor-line"><a href="http://mediatemple.net" rel="noindex,nofollow" class="mt-link" src="null">Web hosting by Media Temple</a> | <a href="http://www.maxcdn.com" rel="noindex,nofollow" class="mc-link" src="null">CDN by MaxCDN</a> | <a href="http://wordpress.org/" class="wp-link" src="null">Powered by WordPress</a> | Thanks: <a href="https://jquery.org/members/" src="null">Members</a>, <a href="https://jquery.org/sponsors/" src="null">Sponsors</a></span>
+ </p>
+ </div>
+ </div>
+</footer>
+
+<script>
+ var _gaq = _gaq || [];
+ _gaq.push(['_setAccount', 'UA-1076265-1']);
+ _gaq.push(['_setDomainName', 'api.jquery.com']);
+ _gaq.push(['_setAllowLinker', true]);
+ _gaq.push(['_trackPageview']);
+
+ (function() {
+ var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
+ ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
+ var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
+ })();
+</script>
+
+
+
+<div id="cboxOverlay" style="display: none;"></div><div id="colorbox" class="" style="display: none;"><div id="cboxWrapper"><div><div id="cboxTopLeft" style="float: left;"></div><div id="cboxTopCenter" style="float: left;"></div><div id="cboxTopRight" style="float: left;"></div></div><div style="clear: left;"><div id="cboxMiddleLeft" style="float: left;"></div><div id="cboxContent" style="float: left;"><div id="cboxLoadedContent" style="width: 0px; height: 0px; overflow: hidden; float: l [...]
+</body>
+</html>
diff --git a/benchmark/suite.js b/benchmark/suite.js
new file mode 100644
index 0000000..da029ad
--- /dev/null
+++ b/benchmark/suite.js
@@ -0,0 +1,91 @@
+var fs = require('fs');
+var path = require('path');
+
+var Benchmark = require('benchmark');
+var jsdom = require('jsdom');
+var cheerio = require('..');
+
+var documentDir = path.join(__dirname, 'documents');
+var jQuerySrc = path.join(__dirname, '../node_modules/jquery/dist/jquery.slim.js');
+var filterRe = /./;
+var cheerioOnly = false;
+
+var Suites = module.exports = function() {};
+
+Suites.prototype.filter = function(str) {
+ filterRe = new RegExp(str, 'i');
+};
+
+Suites.prototype.cheerioOnly = function() {
+ cheerioOnly = true;
+};
+
+Suites.prototype.add = function(name, fileName, options) {
+ var markup, suite, testFn;
+ if (!filterRe.test(name)) {
+ return;
+ }
+ markup = fs.readFileSync(path.join(documentDir, fileName), 'utf8');
+ suite = new Benchmark.Suite(name);
+ testFn = options.test;
+
+ suite.on('start', function(event) {
+ console.log('Test: ' + name + ' (file: ' + fileName + ')');
+ });
+ suite.on('cycle', function(event) {
+ if (event.target.error) {
+ return;
+ }
+ console.log('\t' + String(event.target));
+ });
+ suite.on('error', function(event) {
+ console.log('*** Error in ' + event.target.name + ': ***');
+ console.log('\t' + event.target.error);
+ console.log('*** Test invalidated. ***');
+ });
+ suite.on('complete', function(event) {
+ if (event.target.error) {
+ console.log();
+ return;
+ }
+ console.log('\tFastest: ' + this.filter('fastest')[0].name + '\n');
+ });
+
+ this._benchCheerio(suite, markup, options);
+ if (!cheerioOnly) {
+ this._benchJsDom(suite, markup, options);
+ } else {
+ suite.run();
+ }
+};
+
+Suites.prototype._benchJsDom = function(suite, markup, options) {
+ var testFn = options.test;
+
+ jsdom.env({
+ html: markup,
+ scripts: jQuerySrc,
+ done: function(err, window) {
+ var setupData;
+ if (options.setup) {
+ setupData = options.setup.call(null, window.$);
+ }
+ suite.add('jsdom', function() {
+ testFn.call(null, window.$, setupData);
+ });
+ suite.run();
+ }
+ });
+};
+
+Suites.prototype._benchCheerio = function(suite, markup, options) {
+ var $ = cheerio.load(markup);
+ var testFn = options.test;
+ var setupData;
+ if (options.setup) {
+ setupData = options.setup.call(null, $);
+ }
+ suite.add('cheerio', function() {
+ testFn.call(null, $, setupData);
+ });
+};
diff --git a/index.js b/index.js
new file mode 100644
index 0000000..4235888
--- /dev/null
+++ b/index.js
@@ -0,0 +1,11 @@
+/**
+ * Export cheerio (with )
+ */
+
+exports = module.exports = require('./lib/cheerio');
+
+/*
+ Export the version
+*/
+
+exports.version = require('./package.json').version;
diff --git a/lib/api/attributes.js b/lib/api/attributes.js
new file mode 100644
index 0000000..199abb5
--- /dev/null
+++ b/lib/api/attributes.js
@@ -0,0 +1,495 @@
+var $ = require('../static'),
+ utils = require('../utils'),
+ isTag = utils.isTag,
+ domEach = utils.domEach,
+ hasOwn = Object.prototype.hasOwnProperty,
+ camelCase = utils.camelCase,
+ cssCase = utils.cssCase,
+ rspace = /\s+/,
+ dataAttrPrefix = 'data-',
+ _ = {
+ forEach: require('lodash.foreach'),
+ extend: require('lodash.assignin'),
+ some: require('lodash.some')
+ },
+
+ // Lookup table for coercing string data-* attributes to their corresponding
+ // JavaScript primitives
+ primitives = {
+ null: null,
+ true: true,
+ false: false
+ },
+
+ // Attributes that are booleans
+ rboolean = /^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i,
+ // Matches strings that look like JSON objects or arrays
+ rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/;
+
+
+var getAttr = function(elem, name) {
+ if (!elem || !isTag(elem)) return;
+
+ if (!elem.attribs) {
+ elem.attribs = {};
+ }
+
+ // Return the entire attribs object if no attribute specified
+ if (!name) {
+ return elem.attribs;
+ }
+
+ if (hasOwn.call(elem.attribs, name)) {
+ // Get the (decoded) attribute
+ return rboolean.test(name) ? name : elem.attribs[name];
+ }
+
+ // Mimic the DOM and return text content as value for `option's`
+ if (elem.name === 'option' && name === 'value') {
+ return $.text(elem.children);
+ }
+
+ // Mimic DOM with default value for radios/checkboxes
+ if (elem.name === 'input' &&
+ (elem.attribs.type === 'radio' || elem.attribs.type === 'checkbox') &&
+ name === 'value') {
+ return 'on';
+ }
+};
+
+var setAttr = function(el, name, value) {
+
+ if (value === null) {
+ removeAttribute(el, name);
+ } else {
+ el.attribs[name] = value+'';
+ }
+};
+
+exports.attr = function(name, value) {
+ // Set the value (with attr map support)
+ if (typeof name === 'object' || value !== undefined) {
+ if (typeof value === 'function') {
+ return domEach(this, function(i, el) {
+ setAttr(el, name, value.call(el, i, el.attribs[name]));
+ });
+ }
+ return domEach(this, function(i, el) {
+ if (!isTag(el)) return;
+
+ if (typeof name === 'object') {
+ _.forEach(name, function(value, name) {
+ setAttr(el, name, value);
+ });
+ } else {
+ setAttr(el, name, value);
+ }
+ });
+ }
+
+ return getAttr(this[0], name);
+};
+
+var getProp = function (el, name) {
+ if (!el || !isTag(el)) return;
+
+ return el.hasOwnProperty(name)
+ ? el[name]
+ : rboolean.test(name)
+ ? getAttr(el, name) !== undefined
+ : getAttr(el, name);
+};
+
+var setProp = function (el, name, value) {
+ el[name] = rboolean.test(name) ? !!value : value;
+};
+
+exports.prop = function (name, value) {
+ var i = 0,
+ property;
+
+ if (typeof name === 'string' && value === undefined) {
+
+ switch (name) {
+ case 'style':
+ property = this.css();
+
+ _.forEach(property, function (v, p) {
+ property[i++] = p;
+ });
+
+ property.length = i;
+
+ break;
+ case 'tagName':
+ case 'nodeName':
+ property = this[0].name.toUpperCase();
+ break;
+ default:
+ property = getProp(this[0], name);
+ }
+
+ return property;
+ }
+
+ if (typeof name === 'object' || value !== undefined) {
+
+ if (typeof value === 'function') {
+ return domEach(this, function(i, el) {
+ setProp(el, name, value.call(el, i, getProp(el, name)));
+ });
+ }
+
+ return domEach(this, function(i, el) {
+ if (!isTag(el)) return;
+
+ if (typeof name === 'object') {
+
+ _.forEach(name, function(val, name) {
+ setProp(el, name, val);
+ });
+
+ } else {
+ setProp(el, name, value);
+ }
+ });
+
+ }
+};
+
+var setData = function(el, name, value) {
+ if (!el.data) {
+ el.data = {};
+ }
+
+ if (typeof name === 'object') return _.extend(el.data, name);
+ if (typeof name === 'string' && value !== undefined) {
+ el.data[name] = value;
+ } else if (typeof name === 'object') {
+ _.extend(el.data, name);
+ }
+};
+
+// Read the specified attribute from the equivalent HTML5 `data-*` attribute,
+// and (if present) cache the value in the node's internal data store. If no
+// attribute name is specified, read *all* HTML5 `data-*` attributes in this
+// manner.
+var readData = function(el, name) {
+ var readAll = arguments.length === 1;
+ var domNames, domName, jsNames, jsName, value, idx, length;
+
+ if (readAll) {
+ domNames = Object.keys(el.attribs).filter(function(attrName) {
+ return attrName.slice(0, dataAttrPrefix.length) === dataAttrPrefix;
+ });
+ jsNames = domNames.map(function(domName) {
+ return camelCase(domName.slice(dataAttrPrefix.length));
+ });
+ } else {
+ domNames = [dataAttrPrefix + cssCase(name)];
+ jsNames = [name];
+ }
+
+ for (idx = 0, length = domNames.length; idx < length; ++idx) {
+ domName = domNames[idx];
+ jsName = jsNames[idx];
+ if (hasOwn.call(el.attribs, domName)) {
+ value = el.attribs[domName];
+
+ if (hasOwn.call(primitives, value)) {
+ value = primitives[value];
+ } else if (value === String(Number(value))) {
+ value = Number(value);
+ } else if (rbrace.test(value)) {
+ try {
+ value = JSON.parse(value);
+ } catch(e){ }
+ }
+
+ el.data[jsName] = value;
+ }
+ }
+
+ return readAll ? el.data : value;
+};
+
+exports.data = function(name, value) {
+ var elem = this[0];
+
+ if (!elem || !isTag(elem)) return;
+
+ if (!elem.data) {
+ elem.data = {};
+ }
+
+ // Return the entire data object if no data specified
+ if (!name) {
+ return readData(elem);
+ }
+
+ // Set the value (with attr map support)
+ if (typeof name === 'object' || value !== undefined) {
+ domEach(this, function(i, el) {
+ setData(el, name, value);
+ });
+ return this;
+ } else if (hasOwn.call(elem.data, name)) {
+ return elem.data[name];
+ }
+
+ return readData(elem, name);
+};
+
+/**
+ * Get the value of an element
+ */
+
+exports.val = function(value) {
+ var querying = arguments.length === 0,
+ element = this[0];
+
+ if(!element) return;
+
+ switch (element.name) {
+ case 'textarea':
+ return this.text(value);
+ case 'input':
+ switch (this.attr('type')) {
+ case 'radio':
+ if (querying) {
+ return this.attr('value');
+ } else {
+ this.attr('value', value);
+ return this;
+ }
+ break;
+ default:
+ return this.attr('value', value);
+ }
+ return;
+ case 'select':
+ var option = this.find('option:selected'),
+ returnValue;
+ if (option === undefined) return undefined;
+ if (!querying) {
+ if (!this.attr().hasOwnProperty('multiple') && typeof value == 'object') {
+ return this;
+ }
+ if (typeof value != 'object') {
+ value = [value];
+ }
+ this.find('option').removeAttr('selected');
+ for (var i = 0; i < value.length; i++) {
+ this.find('option[value="' + value[i] + '"]').attr('selected', '');
+ }
+ return this;
+ }
+ returnValue = option.attr('value');
+ if (this.attr().hasOwnProperty('multiple')) {
+ returnValue = [];
+ domEach(option, function(i, el) {
+ returnValue.push(getAttr(el, 'value'));
+ });
+ }
+ return returnValue;
+ case 'option':
+ if (!querying) {
+ this.attr('value', value);
+ return this;
+ }
+ return this.attr('value');
+ }
+};
+
+/**
+ * Remove an attribute
+ */
+
+var removeAttribute = function(elem, name) {
+ if (!elem.attribs || !hasOwn.call(elem.attribs, name))
+ return;
+
+ delete elem.attribs[name];
+};
+
+
+exports.removeAttr = function(name) {
+ domEach(this, function(i, elem) {
+ removeAttribute(elem, name);
+ });
+
+ return this;
+};
+
+exports.hasClass = function(className) {
+ return _.some(this, function(elem) {
+ var attrs = elem.attribs,
+ clazz = attrs && attrs['class'],
+ idx = -1,
+ end;
+
+ if (clazz) {
+ while ((idx = clazz.indexOf(className, idx+1)) > -1) {
+ end = idx + className.length;
+
+ if ((idx === 0 || rspace.test(clazz[idx-1]))
+ && (end === clazz.length || rspace.test(clazz[end]))) {
+ return true;
+ }
+ }
+ }
+ });
+};
+
+exports.addClass = function(value) {
+ // Support functions
+ if (typeof value === 'function') {
+ return domEach(this, function(i, el) {
+ var className = el.attribs['class'] || '';
+ exports.addClass.call([el], value.call(el, i, className));
+ });
+ }
+
+ // Return if no value or not a string or function
+ if (!value || typeof value !== 'string') return this;
+
+ var classNames = value.split(rspace),
+ numElements = this.length;
+
+
+ for (var i = 0; i < numElements; i++) {
+ // If selected element isn't a tag, move on
+ if (!isTag(this[i])) continue;
+
+ // If we don't already have classes
+ var className = getAttr(this[i], 'class'),
+ numClasses,
+ setClass;
+
+ if (!className) {
+ setAttr(this[i], 'class', classNames.join(' ').trim());
+ } else {
+ setClass = ' ' + className + ' ';
+ numClasses = classNames.length;
+
+ // Check if class already exists
+ for (var j = 0; j < numClasses; j++) {
+ var appendClass = classNames[j] + ' ';
+ if (setClass.indexOf(' ' + appendClass) < 0)
+ setClass += appendClass;
+ }
+
+ setAttr(this[i], 'class', setClass.trim());
+ }
+ }
+
+ return this;
+};
+
+var splitClass = function(className) {
+ return className ? className.trim().split(rspace) : [];
+};
+
+exports.removeClass = function(value) {
+ var classes,
+ numClasses,
+ removeAll;
+
+ // Handle if value is a function
+ if (typeof value === 'function') {
+ return domEach(this, function(i, el) {
+ exports.removeClass.call(
+ [el], value.call(el, i, el.attribs['class'] || '')
+ );
+ });
+ }
+
+ classes = splitClass(value);
+ numClasses = classes.length;
+ removeAll = arguments.length === 0;
+
+ return domEach(this, function(i, el) {
+ if (!isTag(el)) return;
+
+ if (removeAll) {
+ // Short circuit the remove all case as this is the nice one
+ el.attribs.class = '';
+ } else {
+ var elClasses = splitClass(el.attribs.class),
+ index,
+ changed;
+
+ for (var j = 0; j < numClasses; j++) {
+ index = elClasses.indexOf(classes[j]);
+
+ if (index >= 0) {
+ elClasses.splice(index, 1);
+ changed = true;
+
+ // We have to do another pass to ensure that there are not duplicate
+ // classes listed
+ j--;
+ }
+ }
+ if (changed) {
+ el.attribs.class = elClasses.join(' ');
+ }
+ }
+ });
+};
+
+exports.toggleClass = function(value, stateVal) {
+ // Support functions
+ if (typeof value === 'function') {
+ return domEach(this, function(i, el) {
+ exports.toggleClass.call(
+ [el],
+ value.call(el, i, el.attribs['class'] || '', stateVal),
+ stateVal
+ );
+ });
+ }
+
+ // Return if no value or not a string or function
+ if (!value || typeof value !== 'string') return this;
+
+ var classNames = value.split(rspace),
+ numClasses = classNames.length,
+ state = typeof stateVal === 'boolean' ? stateVal ? 1 : -1 : 0,
+ numElements = this.length,
+ elementClasses,
+ index;
+
+ for (var i = 0; i < numElements; i++) {
+ // If selected element isn't a tag, move on
+ if (!isTag(this[i])) continue;
+
+ elementClasses = splitClass(this[i].attribs.class);
+
+ // Check if class already exists
+ for (var j = 0; j < numClasses; j++) {
+ // Check if the class name is currently defined
+ index = elementClasses.indexOf(classNames[j]);
+
+ // Add if stateValue === true or we are toggling and there is no value
+ if (state >= 0 && index < 0) {
+ elementClasses.push(classNames[j]);
+ } else if (state <= 0 && index >= 0) {
+ // Otherwise remove but only if the item exists
+ elementClasses.splice(index, 1);
+ }
+ }
+
+ this[i].attribs.class = elementClasses.join(' ');
+ }
+
+ return this;
+};
+
+exports.is = function (selector) {
+ if (selector) {
+ return this.filter(selector).length > 0;
+ }
+ return false;
+};
+
diff --git a/lib/api/css.js b/lib/api/css.js
new file mode 100644
index 0000000..3e0ee82
--- /dev/null
+++ b/lib/api/css.js
@@ -0,0 +1,121 @@
+var domEach = require('../utils').domEach,
+ _ = {
+ pick: require('lodash.pick'),
+ };
+
+var toString = Object.prototype.toString;
+
+/**
+ * Set / Get css.
+ *
+ * @param {String|Object} prop
+ * @param {String} val
+ * @return {self}
+ * @api public
+ */
+
+exports.css = function(prop, val) {
+ if (arguments.length === 2 ||
+ // When `prop` is a "plain" object
+ (toString.call(prop) === '[object Object]')) {
+ return domEach(this, function(idx, el) {
+ setCss(el, prop, val, idx);
+ });
+ } else {
+ return getCss(this[0], prop);
+ }
+};
+
+/**
+ * Set styles of all elements.
+ *
+ * @param {String|Object} prop
+ * @param {String} val
+ * @param {Number} idx - optional index within the selection
+ * @return {self}
+ * @api private
+ */
+
+function setCss(el, prop, val, idx) {
+ if ('string' == typeof prop) {
+ var styles = getCss(el);
+ if (typeof val === 'function') {
+ val = val.call(el, idx, styles[prop]);
+ }
+
+ if (val === '') {
+ delete styles[prop];
+ } else if (val != null) {
+ styles[prop] = val;
+ }
+
+ el.attribs.style = stringify(styles);
+ } else if ('object' == typeof prop) {
+ Object.keys(prop).forEach(function(k){
+ setCss(el, k, prop[k]);
+ });
+ }
+}
+
+/**
+ * Get parsed styles of the first element.
+ *
+ * @param {String} prop
+ * @return {Object}
+ * @api private
+ */
+
+function getCss(el, prop) {
+ var styles = parse(el.attribs.style);
+ if (typeof prop === 'string') {
+ return styles[prop];
+ } else if (Array.isArray(prop)) {
+ return _.pick(styles, prop);
+ } else {
+ return styles;
+ }
+}
+
+/**
+ * Stringify `obj` to styles.
+ *
+ * @param {Object} obj
+ * @return {Object}
+ * @api private
+ */
+
+function stringify(obj) {
+ return Object.keys(obj || {})
+ .reduce(function(str, prop){
+ return str += ''
+ + (str ? ' ' : '')
+ + prop
+ + ': '
+ + obj[prop]
+ + ';';
+ }, '');
+}
+
+/**
+ * Parse `styles`.
+ *
+ * @param {String} styles
+ * @return {Object}
+ * @api private
+ */
+
+function parse(styles) {
+ styles = (styles || '').trim();
+
+ if (!styles) return {};
+
+ return styles
+ .split(';')
+ .reduce(function(obj, str){
+ var n = str.indexOf(':');
+ // skip if there is no :, or if it is the first/last character
+ if (n < 1 || n === str.length-1) return obj;
+ obj[str.slice(0,n).trim()] = str.slice(n+1).trim();
+ return obj;
+ }, {});
+}
diff --git a/lib/api/forms.js b/lib/api/forms.js
new file mode 100644
index 0000000..2f4e58c
--- /dev/null
+++ b/lib/api/forms.js
@@ -0,0 +1,65 @@
+// https://github.com/jquery/jquery/blob/2.1.3/src/manipulation/var/rcheckableType.js
+// https://github.com/jquery/jquery/blob/2.1.3/src/serialize.js
+var submittableSelector = 'input,select,textarea,keygen',
+ r20 = /%20/g,
+ rCRLF = /\r?\n/g,
+ _ = {
+ map: require('lodash.map')
+ };
+
+exports.serialize = function() {
+ // Convert form elements into name/value objects
+ var arr = this.serializeArray();
+
+ // Serialize each element into a key/value string
+ var retArr = _.map(arr, function(data) {
+ return encodeURIComponent(data.name) + '=' + encodeURIComponent(data.value);
+ });
+
+ // Return the resulting serialization
+ return retArr.join('&').replace(r20, '+');
+};
+
+exports.serializeArray = function() {
+ // Resolve all form elements from either forms or collections of form elements
+ var Cheerio = this.constructor;
+ return this.map(function() {
+ var elem = this;
+ var $elem = Cheerio(elem);
+ if (elem.name === 'form') {
+ return $elem.find(submittableSelector).toArray();
+ } else {
+ return $elem.filter(submittableSelector).toArray();
+ }
+ }).filter(
+ // Verify elements have a name (`attr.name`) and are not disabled (`:disabled`)
+ '[name!=""]:not(:disabled)'
+ // and cannot be clicked (`[type=submit]`) or are used in `x-www-form-urlencoded` (`[type=file]`)
+ + ':not(:submit, :button, :image, :reset, :file)'
+ // and are either checked/don't have a checkable state
+ + ':matches([checked], :not(:checkbox, :radio))'
+ // Convert each of the elements to its value(s)
+ ).map(function(i, elem) {
+ var $elem = Cheerio(elem);
+ var name = $elem.attr('name');
+ var val = $elem.val();
+
+ // If there is no value set (e.g. `undefined`, `null`), then return nothing
+ if (val == null) {
+ return null;
+ } else {
+ // If we have an array of values (e.g. `<select multiple>`), return an array of key/value pairs
+ if (Array.isArray(val)) {
+ return _.map(val, function(val) {
+ // We trim replace any line endings (e.g. `\r` or `\r\n` with `\r\n`) to guarantee consistency across platforms
+ // These can occur inside of `<textarea>'s`
+ return {name: name, value: val.replace( rCRLF, '\r\n' )};
+ });
+ // Otherwise (e.g. `<input type="text">`, return only one key/value pair
+ } else {
+ return {name: name, value: val.replace( rCRLF, '\r\n' )};
+ }
+ }
+ // Convert our result to an array
+ }).get();
+};
diff --git a/lib/api/manipulation.js b/lib/api/manipulation.js
new file mode 100644
index 0000000..6979337
--- /dev/null
+++ b/lib/api/manipulation.js
@@ -0,0 +1,425 @@
+var parse = require('../parse'),
+ $ = require('../static'),
+ updateDOM = parse.update,
+ evaluate = parse.evaluate,
+ utils = require('../utils'),
+ domEach = utils.domEach,
+ cloneDom = utils.cloneDom,
+ isHtml = utils.isHtml,
+ slice = Array.prototype.slice,
+ _ = {
+ flatten: require('lodash.flatten'),
+ bind: require('lodash.bind'),
+ forEach: require('lodash.foreach')
+ };
+
+// Create an array of nodes, recursing into arrays and parsing strings if
+// necessary
+exports._makeDomArray = function makeDomArray(elem, clone) {
+ if (elem == null) {
+ return [];
+ } else if (elem.cheerio) {
+ return clone ? cloneDom(elem.get(), elem.options) : elem.get();
+ } else if (Array.isArray(elem)) {
+ return _.flatten(elem.map(function(el) {
+ return this._makeDomArray(el, clone);
+ }, this));
+ } else if (typeof elem === 'string') {
+ return evaluate(elem, this.options);
+ } else {
+ return clone ? cloneDom([elem]) : [elem];
+ }
+};
+
+var _insert = function(concatenator) {
+ return function() {
+ var elems = slice.call(arguments),
+ lastIdx = this.length - 1;
+
+ return domEach(this, function(i, el) {
+ var dom, domSrc;
+
+ if (typeof elems[0] === 'function') {
+ domSrc = elems[0].call(el, i, $.html(el.children));
+ } else {
+ domSrc = elems;
+ }
+
+ dom = this._makeDomArray(domSrc, i < lastIdx);
+ concatenator(dom, el.children, el);
+ });
+ };
+};
+
+/*
+ * Modify an array in-place, removing some number of elements and adding new
+ * elements directly following them.
+ *
+ * @param {Array} array Target array to splice.
+ * @param {Number} spliceIdx Index at which to begin changing the array.
+ * @param {Number} spliceCount Number of elements to remove from the array.
+ * @param {Array} newElems Elements to insert into the array.
+ *
+ * @api private
+ */
+var uniqueSplice = function(array, spliceIdx, spliceCount, newElems, parent) {
+ var spliceArgs = [spliceIdx, spliceCount].concat(newElems),
+ prev = array[spliceIdx - 1] || null,
+ next = array[spliceIdx] || null;
+ var idx, len, prevIdx, node, oldParent;
+
+ // Before splicing in new elements, ensure they do not already appear in the
+ // current array.
+ for (idx = 0, len = newElems.length; idx < len; ++idx) {
+ node = newElems[idx];
+ oldParent = node.parent || node.root;
+ prevIdx = oldParent && oldParent.children.indexOf(newElems[idx]);
+
+ if (oldParent && prevIdx > -1) {
+ oldParent.children.splice(prevIdx, 1);
+ if (parent === oldParent && spliceIdx > prevIdx) {
+ spliceArgs[0]--;
+ }
+ }
+
+ node.root = null;
+ node.parent = parent;
+
+ if (node.prev) {
+ node.prev.next = node.next || null;
+ }
+
+ if (node.next) {
+ node.next.prev = node.prev || null;
+ }
+
+ node.prev = newElems[idx - 1] || prev;
+ node.next = newElems[idx + 1] || next;
+ }
+
+ if (prev) {
+ prev.next = newElems[0];
+ }
+ if (next) {
+ next.prev = newElems[newElems.length - 1];
+ }
+ return array.splice.apply(array, spliceArgs);
+};
+
+exports.appendTo = function(target) {
+ if (!target.cheerio) {
+ target = this.constructor.call(this.constructor, target, null, this._originalRoot);
+ }
+
+ target.append(this);
+
+ return this;
+};
+
+exports.prependTo = function(target) {
+ if (!target.cheerio) {
+ target = this.constructor.call(this.constructor, target, null, this._originalRoot);
+ }
+
+ target.prepend(this);
+
+ return this;
+};
+
+exports.append = _insert(function(dom, children, parent) {
+ uniqueSplice(children, children.length, 0, dom, parent);
+});
+
+exports.prepend = _insert(function(dom, children, parent) {
+ uniqueSplice(children, 0, 0, dom, parent);
+});
+
+exports.wrap = function(wrapper) {
+ var wrapperFn = typeof wrapper === 'function' && wrapper,
+ lastIdx = this.length - 1;
+
+ _.forEach(this, _.bind(function(el, i) {
+ var parent = el.parent || el.root,
+ siblings = parent.children,
+ dom, index;
+
+ if (!parent) {
+ return;
+ }
+
+ if (wrapperFn) {
+ wrapper = wrapperFn.call(el, i);
+ }
+
+ if (typeof wrapper === 'string' && !isHtml(wrapper)) {
+ wrapper = this.parents().last().find(wrapper).clone();
+ }
+
+ dom = this._makeDomArray(wrapper, i < lastIdx).slice(0, 1);
+ index = siblings.indexOf(el);
+
+ updateDOM([el], dom[0]);
+ // The previous operation removed the current element from the `siblings`
+ // array, so the `dom` array can be inserted without removing any
+ // additional elements.
+ uniqueSplice(siblings, index, 0, dom, parent);
+ }, this));
+
+ return this;
+};
+
+exports.after = function() {
+ var elems = slice.call(arguments),
+ lastIdx = this.length - 1;
+
+ domEach(this, function(i, el) {
+ var parent = el.parent || el.root;
+ if (!parent) {
+ return;
+ }
+
+ var siblings = parent.children,
+ index = siblings.indexOf(el),
+ domSrc, dom;
+
+ // If not found, move on
+ if (index < 0) return;
+
+ if (typeof elems[0] === 'function') {
+ domSrc = elems[0].call(el, i, $.html(el.children));
+ } else {
+ domSrc = elems;
+ }
+ dom = this._makeDomArray(domSrc, i < lastIdx);
+
+ // Add element after `this` element
+ uniqueSplice(siblings, index + 1, 0, dom, parent);
+ });
+
+ return this;
+};
+
+exports.insertAfter = function(target) {
+ var clones = [],
+ self = this;
+ if (typeof target === 'string') {
+ target = this.constructor.call(this.constructor, target, null, this._originalRoot);
+ }
+ target = this._makeDomArray(target);
+ self.remove();
+ domEach(target, function(i, el) {
+ var clonedSelf = self._makeDomArray(self.clone());
+ var parent = el.parent || el.root;
+ if (!parent) {
+ return;
+ }
+
+ var siblings = parent.children,
+ index = siblings.indexOf(el);
+
+ // If not found, move on
+ if (index < 0) return;
+
+ // Add cloned `this` element(s) after target element
+ uniqueSplice(siblings, index + 1, 0, clonedSelf, parent);
+ clones.push(clonedSelf);
+ });
+ return this.constructor.call(this.constructor, this._makeDomArray(clones));
+};
+
+exports.before = function() {
+ var elems = slice.call(arguments),
+ lastIdx = this.length - 1;
+
+ domEach(this, function(i, el) {
+ var parent = el.parent || el.root;
+ if (!parent) {
+ return;
+ }
+
+ var siblings = parent.children,
+ index = siblings.indexOf(el),
+ domSrc, dom;
+
+ // If not found, move on
+ if (index < 0) return;
+
+ if (typeof elems[0] === 'function') {
+ domSrc = elems[0].call(el, i, $.html(el.children));
+ } else {
+ domSrc = elems;
+ }
+
+ dom = this._makeDomArray(domSrc, i < lastIdx);
+
+ // Add element before `el` element
+ uniqueSplice(siblings, index, 0, dom, parent);
+ });
+
+ return this;
+};
+
+exports.insertBefore = function(target) {
+ var clones = [],
+ self = this;
+ if (typeof target === 'string') {
+ target = this.constructor.call(this.constructor, target, null, this._originalRoot);
+ }
+ target = this._makeDomArray(target);
+ self.remove();
+ domEach(target, function(i, el) {
+ var clonedSelf = self._makeDomArray(self.clone());
+ var parent = el.parent || el.root;
+ if (!parent) {
+ return;
+ }
+
+ var siblings = parent.children,
+ index = siblings.indexOf(el);
+
+ // If not found, move on
+ if (index < 0) return;
+
+ // Add cloned `this` element(s) after target element
+ uniqueSplice(siblings, index, 0, clonedSelf, parent);
+ clones.push(clonedSelf);
+ });
+ return this.constructor.call(this.constructor, this._makeDomArray(clones));
+};
+
+/*
+ remove([selector])
+*/
+exports.remove = function(selector) {
+ var elems = this;
+
+ // Filter if we have selector
+ if (selector)
+ elems = elems.filter(selector);
+
+ domEach(elems, function(i, el) {
+ var parent = el.parent || el.root;
+ if (!parent) {
+ return;
+ }
+
+ var siblings = parent.children,
+ index = siblings.indexOf(el);
+
+ if (index < 0) return;
+
+ siblings.splice(index, 1);
+ if (el.prev) {
+ el.prev.next = el.next;
+ }
+ if (el.next) {
+ el.next.prev = el.prev;
+ }
+ el.prev = el.next = el.parent = el.root = null;
+ });
+
+ return this;
+};
+
+exports.replaceWith = function(content) {
+ var self = this;
+
+ domEach(this, function(i, el) {
+ var parent = el.parent || el.root;
+ if (!parent) {
+ return;
+ }
+
+ var siblings = parent.children,
+ dom = self._makeDomArray(typeof content === 'function' ? content.call(el, i, el) : content),
+ index;
+
+ // In the case that `dom` contains nodes that already exist in other
+ // structures, ensure those nodes are properly removed.
+ updateDOM(dom, null);
+
+ index = siblings.indexOf(el);
+
+ // Completely remove old element
+ uniqueSplice(siblings, index, 1, dom, parent);
+ el.parent = el.prev = el.next = el.root = null;
+ });
+
+ return this;
+};
+
+exports.empty = function() {
+ domEach(this, function(i, el) {
+ _.forEach(el.children, function(el) {
+ el.next = el.prev = el.parent = null;
+ });
+
+ el.children.length = 0;
+ });
+ return this;
+};
+
+/**
+ * Set/Get the HTML
+ */
+exports.html = function(str) {
+ if (str === undefined) {
+ if (!this[0] || !this[0].children) return null;
+ return $.html(this[0].children, this.options);
+ }
+
+ var opts = this.options;
+
+ domEach(this, function(i, el) {
+ _.forEach(el.children, function(el) {
+ el.next = el.prev = el.parent = null;
+ });
+
+ var content = str.cheerio ? str.clone().get() : evaluate('' + str, opts);
+
+ updateDOM(content, el);
+ });
+
+ return this;
+};
+
+exports.toString = function() {
+ return $.html(this, this.options);
+};
+
+exports.text = function(str) {
+ // If `str` is undefined, act as a "getter"
+ if (str === undefined) {
+ return $.text(this);
+ } else if (typeof str === 'function') {
+ // Function support
+ return domEach(this, function(i, el) {
+ var $el = [el];
+ return exports.text.call($el, str.call(el, i, $.text($el)));
+ });
+ }
+
+ // Append text node to each selected elements
+ domEach(this, function(i, el) {
+ _.forEach(el.children, function(el) {
+ el.next = el.prev = el.parent = null;
+ });
+
+ var elem = {
+ data: '' + str,
+ type: 'text',
+ parent: el,
+ prev: null,
+ next: null,
+ children: []
+ };
+
+ updateDOM(elem, el);
+ });
+
+ return this;
+};
+
+exports.clone = function() {
+ return this._make(cloneDom(this.get(), this.options));
+};
diff --git a/lib/api/traversing.js b/lib/api/traversing.js
new file mode 100644
index 0000000..3eac48e
--- /dev/null
+++ b/lib/api/traversing.js
@@ -0,0 +1,429 @@
+var select = require('css-select'),
+ utils = require('../utils'),
+ domEach = utils.domEach,
+ uniqueSort = require('htmlparser2').DomUtils.uniqueSort,
+ isTag = utils.isTag,
+ _ = {
+ bind: require('lodash.bind'),
+ forEach: require('lodash.foreach'),
+ reject: require('lodash.reject'),
+ filter: require('lodash.filter'),
+ reduce: require('lodash.reduce')
+ };
+
+exports.find = function(selectorOrHaystack) {
+ var elems = _.reduce(this, function(memo, elem) {
+ return memo.concat(_.filter(elem.children, isTag));
+ }, []);
+ var contains = this.constructor.contains;
+ var haystack;
+
+ if (selectorOrHaystack && typeof selectorOrHaystack !== 'string') {
+ if (selectorOrHaystack.cheerio) {
+ haystack = selectorOrHaystack.get();
+ } else {
+ haystack = [selectorOrHaystack];
+ }
+
+ return this._make(haystack.filter(function(elem) {
+ var idx, len;
+ for (idx = 0, len = this.length; idx < len; ++idx) {
+ if (contains(this[idx], elem)) {
+ return true;
+ }
+ }
+ }, this));
+ }
+
+ var options = {__proto__: this.options, context: this.toArray()};
+
+ return this._make(select(selectorOrHaystack, elems, options));
+};
+
+// Get the parent of each element in the current set of matched elements,
+// optionally filtered by a selector.
+exports.parent = function(selector) {
+ var set = [];
+
+ domEach(this, function(idx, elem) {
+ var parentElem = elem.parent;
+ if (parentElem && set.indexOf(parentElem) < 0) {
+ set.push(parentElem);
+ }
+ });
+
+ if (arguments.length) {
+ set = exports.filter.call(set, selector, this);
+ }
+
+ return this._make(set);
+};
+
+exports.parents = function(selector) {
+ var parentNodes = [];
+
+ // When multiple DOM elements are in the original set, the resulting set will
+ // be in *reverse* order of the original elements as well, with duplicates
+ // removed.
+ this.get().reverse().forEach(function(elem) {
+ traverseParents(this, elem.parent, selector, Infinity)
+ .forEach(function(node) {
+ if (parentNodes.indexOf(node) === -1) {
+ parentNodes.push(node);
+ }
+ }
+ );
+ }, this);
+
+ return this._make(parentNodes);
+};
+
+exports.parentsUntil = function(selector, filter) {
+ var parentNodes = [], untilNode, untilNodes;
+
+ if (typeof selector === 'string') {
+ untilNode = select(selector, this.parents().toArray(), this.options)[0];
+ } else if (selector && selector.cheerio) {
+ untilNodes = selector.toArray();
+ } else if (selector) {
+ untilNode = selector;
+ }
+
+ // When multiple DOM elements are in the original set, the resulting set will
+ // be in *reverse* order of the original elements as well, with duplicates
+ // removed.
+
+ this.toArray().reverse().forEach(function(elem) {
+ while ((elem = elem.parent)) {
+ if ((untilNode && elem !== untilNode) ||
+ (untilNodes && untilNodes.indexOf(elem) === -1) ||
+ (!untilNode && !untilNodes)) {
+ if (isTag(elem) && parentNodes.indexOf(elem) === -1) { parentNodes.push(elem); }
+ } else {
+ break;
+ }
+ }
+ }, this);
+
+ return this._make(filter ? select(filter, parentNodes, this.options) : parentNodes);
+};
+
+// For each element in the set, get the first element that matches the selector
+// by testing the element itself and traversing up through its ancestors in the
+// DOM tree.
+exports.closest = function(selector) {
+ var set = [];
+
+ if (!selector) {
+ return this._make(set);
+ }
+
+ domEach(this, function(idx, elem) {
+ var closestElem = traverseParents(this, elem, selector, 1)[0];
+
+ // Do not add duplicate elements to the set
+ if (closestElem && set.indexOf(closestElem) < 0) {
+ set.push(closestElem);
+ }
+ }.bind(this));
+
+ return this._make(set);
+};
+
+exports.next = function(selector) {
+ if (!this[0]) { return this; }
+ var elems = [];
+
+ _.forEach(this, function(elem) {
+ while ((elem = elem.next)) {
+ if (isTag(elem)) {
+ elems.push(elem);
+ return;
+ }
+ }
+ });
+
+ return selector ?
+ exports.filter.call(elems, selector, this) :
+ this._make(elems);
+};
+
+exports.nextAll = function(selector) {
+ if (!this[0]) { return this; }
+ var elems = [];
+
+ _.forEach(this, function(elem) {
+ while ((elem = elem.next)) {
+ if (isTag(elem) && elems.indexOf(elem) === -1) {
+ elems.push(elem);
+ }
+ }
+ });
+
+ return selector ?
+ exports.filter.call(elems, selector, this) :
+ this._make(elems);
+};
+
+exports.nextUntil = function(selector, filterSelector) {
+ if (!this[0]) { return this; }
+ var elems = [], untilNode, untilNodes;
+
+ if (typeof selector === 'string') {
+ untilNode = select(selector, this.nextAll().get(), this.options)[0];
+ } else if (selector && selector.cheerio) {
+ untilNodes = selector.get();
+ } else if (selector) {
+ untilNode = selector;
+ }
+
+ _.forEach(this, function(elem) {
+ while ((elem = elem.next)) {
+ if ((untilNode && elem !== untilNode) ||
+ (untilNodes && untilNodes.indexOf(elem) === -1) ||
+ (!untilNode && !untilNodes)) {
+ if (isTag(elem) && elems.indexOf(elem) === -1) {
+ elems.push(elem);
+ }
+ } else {
+ break;
+ }
+ }
+ });
+
+ return filterSelector ?
+ exports.filter.call(elems, filterSelector, this) :
+ this._make(elems);
+};
+
+exports.prev = function(selector) {
+ if (!this[0]) { return this; }
+ var elems = [];
+
+ _.forEach(this, function(elem) {
+ while ((elem = elem.prev)) {
+ if (isTag(elem)) {
+ elems.push(elem);
+ return;
+ }
+ }
+ });
+
+ return selector ?
+ exports.filter.call(elems, selector, this) :
+ this._make(elems);
+};
+
+exports.prevAll = function(selector) {
+ if (!this[0]) { return this; }
+ var elems = [];
+
+ _.forEach(this, function(elem) {
+ while ((elem = elem.prev)) {
+ if (isTag(elem) && elems.indexOf(elem) === -1) {
+ elems.push(elem);
+ }
+ }
+ });
+
+ return selector ?
+ exports.filter.call(elems, selector, this) :
+ this._make(elems);
+};
+
+exports.prevUntil = function(selector, filterSelector) {
+ if (!this[0]) { return this; }
+ var elems = [], untilNode, untilNodes;
+
+ if (typeof selector === 'string') {
+ untilNode = select(selector, this.prevAll().get(), this.options)[0];
+ } else if (selector && selector.cheerio) {
+ untilNodes = selector.get();
+ } else if (selector) {
+ untilNode = selector;
+ }
+
+ _.forEach(this, function(elem) {
+ while ((elem = elem.prev)) {
+ if ((untilNode && elem !== untilNode) ||
+ (untilNodes && untilNodes.indexOf(elem) === -1) ||
+ (!untilNode && !untilNodes)) {
+ if (isTag(elem) && elems.indexOf(elem) === -1) {
+ elems.push(elem);
+ }
+ } else {
+ break;
+ }
+ }
+ });
+
+ return filterSelector ?
+ exports.filter.call(elems, filterSelector, this) :
+ this._make(elems);
+};
+
+exports.siblings = function(selector) {
+ var parent = this.parent();
+
+ var elems = _.filter(
+ parent ? parent.children() : this.siblingsAndMe(),
+ _.bind(function(elem) { return isTag(elem) && !this.is(elem); }, this)
+ );
+
+ if (selector !== undefined) {
+ return exports.filter.call(elems, selector, this);
+ } else {
+ return this._make(elems);
+ }
+};
+
+exports.children = function(selector) {
+
+ var elems = _.reduce(this, function(memo, elem) {
+ return memo.concat(_.filter(elem.children, isTag));
+ }, []);
+
+ if (selector === undefined) return this._make(elems);
+
+ return exports.filter.call(elems, selector, this);
+};
+
+exports.contents = function() {
+ return this._make(_.reduce(this, function(all, elem) {
+ all.push.apply(all, elem.children);
+ return all;
+ }, []));
+};
+
+exports.each = function(fn) {
+ var i = 0, len = this.length;
+ while (i < len && fn.call(this[i], i, this[i]) !== false) ++i;
+ return this;
+};
+
+exports.map = function(fn) {
+ return this._make(_.reduce(this, function(memo, el, i) {
+ var val = fn.call(el, i, el);
+ return val == null ? memo : memo.concat(val);
+ }, []));
+};
+
+var makeFilterMethod = function(filterFn) {
+ return function(match, container) {
+ var testFn;
+ container = container || this;
+
+ if (typeof match === 'string') {
+ testFn = select.compile(match, container.options);
+ } else if (typeof match === 'function') {
+ testFn = function(el, i) {
+ return match.call(el, i, el);
+ };
+ } else if (match.cheerio) {
+ testFn = match.is.bind(match);
+ } else {
+ testFn = function(el) {
+ return match === el;
+ };
+ }
+
+ return container._make(filterFn(this, testFn));
+ };
+};
+
+exports.filter = makeFilterMethod(_.filter);
+exports.not = makeFilterMethod(_.reject);
+
+exports.has = function(selectorOrHaystack) {
+ var that = this;
+ return exports.filter.call(this, function() {
+ return that._make(this).find(selectorOrHaystack).length > 0;
+ });
+};
+
+exports.first = function() {
+ return this.length > 1 ? this._make(this[0]) : this;
+};
+
+exports.last = function() {
+ return this.length > 1 ? this._make(this[this.length - 1]) : this;
+};
+
+// Reduce the set of matched elements to the one at the specified index.
+exports.eq = function(i) {
+ i = +i;
+
+ // Use the first identity optimization if possible
+ if (i === 0 && this.length <= 1) return this;
+
+ if (i < 0) i = this.length + i;
+ return this[i] ? this._make(this[i]) : this._make([]);
+};
+
+// Retrieve the DOM elements matched by the jQuery object.
+exports.get = function(i) {
+ if (i == null) {
+ return Array.prototype.slice.call(this);
+ } else {
+ return this[i < 0 ? (this.length + i) : i];
+ }
+};
+
+// Search for a given element from among the matched elements.
+exports.index = function(selectorOrNeedle) {
+ var $haystack, needle;
+
+ if (arguments.length === 0) {
+ $haystack = this.parent().children();
+ needle = this[0];
+ } else if (typeof selectorOrNeedle === 'string') {
+ $haystack = this._make(selectorOrNeedle);
+ needle = this[0];
+ } else {
+ $haystack = this;
+ needle = selectorOrNeedle.cheerio ? selectorOrNeedle[0] : selectorOrNeedle;
+ }
+
+ return $haystack.get().indexOf(needle);
+};
+
+exports.slice = function() {
+ return this._make([].slice.apply(this, arguments));
+};
+
+function traverseParents(self, elem, selector, limit) {
+ var elems = [];
+ while (elem && elems.length < limit) {
+ if (!selector || exports.filter.call([elem], selector, self).length) {
+ elems.push(elem);
+ }
+ elem = elem.parent;
+ }
+ return elems;
+}
+
+// End the most recent filtering operation in the current chain and return the
+// set of matched elements to its previous state.
+exports.end = function() {
+ return this.prevObject || this._make([]);
+};
+
+exports.add = function(other, context) {
+ var selection = this._make(other, context);
+ var contents = uniqueSort(selection.get().concat(this.get()));
+
+ for (var i = 0; i < contents.length; ++i) {
+ selection[i] = contents[i];
+ }
+ selection.length = contents.length;
+
+ return selection;
+};
+
+// Add the previous set of elements on the stack to the current set, optionally
+// filtered by a selector.
+exports.addBack = function(selector) {
+ return this.add(
+ arguments.length ? this.prevObject.filter(selector) : this.prevObject
+ );
+};
diff --git a/lib/cheerio.js b/lib/cheerio.js
new file mode 100644
index 0000000..9dbbac6
--- /dev/null
+++ b/lib/cheerio.js
@@ -0,0 +1,148 @@
+/*
+ Module dependencies
+*/
+
+var parse = require('./parse'),
+ isHtml = require('./utils').isHtml,
+ _ = {
+ extend: require('lodash.assignin'),
+ bind: require('lodash.bind'),
+ forEach: require('lodash.foreach'),
+ defaults: require('lodash.defaults')
+ };
+
+/*
+ * The API
+ */
+
+var api = [
+ require('./api/attributes'),
+ require('./api/traversing'),
+ require('./api/manipulation'),
+ require('./api/css'),
+ require('./api/forms')
+];
+
+/*
+ * Instance of cheerio
+ */
+
+var Cheerio = module.exports = function(selector, context, root, options) {
+ if (!(this instanceof Cheerio)) return new Cheerio(selector, context, root, options);
+
+ this.options = _.defaults(options || {}, this.options);
+
+ // $(), $(null), $(undefined), $(false)
+ if (!selector) return this;
+
+ if (root) {
+ if (typeof root === 'string') root = parse(root, this.options);
+ this._root = Cheerio.call(this, root);
+ }
+
+ // $($)
+ if (selector.cheerio) return selector;
+
+ // $(dom)
+ if (isNode(selector))
+ selector = [selector];
+
+ // $([dom])
+ if (Array.isArray(selector)) {
+ _.forEach(selector, _.bind(function(elem, idx) {
+ this[idx] = elem;
+ }, this));
+ this.length = selector.length;
+ return this;
+ }
+
+ // $(<html>)
+ if (typeof selector === 'string' && isHtml(selector)) {
+ return Cheerio.call(this, parse(selector, this.options).children);
+ }
+
+ // If we don't have a context, maybe we have a root, from loading
+ if (!context) {
+ context = this._root;
+ } else if (typeof context === 'string') {
+ if (isHtml(context)) {
+ // $('li', '<ul>...</ul>')
+ context = parse(context, this.options);
+ context = Cheerio.call(this, context);
+ } else {
+ // $('li', 'ul')
+ selector = [context, selector].join(' ');
+ context = this._root;
+ }
+ // $('li', node), $('li', [nodes])
+ } else if (!context.cheerio) {
+ context = Cheerio.call(this, context);
+ }
+
+ // If we still don't have a context, return
+ if (!context) return this;
+
+ // #id, .class, tag
+ return context.find(selector);
+};
+
+/**
+ * Mix in `static`
+ */
+
+_.extend(Cheerio, require('./static'));
+
+/*
+ * Set a signature of the object
+ */
+
+Cheerio.prototype.cheerio = '[cheerio object]';
+
+/*
+ * Cheerio default options
+ */
+
+Cheerio.prototype.options = {
+ withDomLvl1: true,
+ normalizeWhitespace: false,
+ xmlMode: false,
+ decodeEntities: true
+};
+
+/*
+ * Make cheerio an array-like object
+ */
+
+Cheerio.prototype.length = 0;
+Cheerio.prototype.splice = Array.prototype.splice;
+
+/*
+ * Make a cheerio object
+ *
+ * @api private
+ */
+
+Cheerio.prototype._make = function(dom, context) {
+ var cheerio = new this.constructor(dom, context, this._root, this.options);
+ cheerio.prevObject = this;
+ return cheerio;
+};
+
+/**
+ * Turn a cheerio object into an array
+ */
+
+Cheerio.prototype.toArray = function() {
+ return this.get();
+};
+
+/**
+ * Plug in the API
+ */
+api.forEach(function(mod) {
+ _.extend(Cheerio.prototype, mod);
+});
+
+var isNode = function(obj) {
+ return obj.name || obj.type === 'text' || obj.type === 'comment';
+};
diff --git a/lib/parse.js b/lib/parse.js
new file mode 100644
index 0000000..b06245d
--- /dev/null
+++ b/lib/parse.js
@@ -0,0 +1,86 @@
+/*
+ Module Dependencies
+*/
+var htmlparser = require('htmlparser2');
+
+/*
+ Parser
+*/
+exports = module.exports = function(content, options) {
+ var dom = exports.evaluate(content, options),
+ // Generic root element
+ root = exports.evaluate('<root></root>', options)[0];
+
+ root.type = 'root';
+
+ // Update the dom using the root
+ exports.update(dom, root);
+
+ return root;
+};
+
+exports.evaluate = function(content, options) {
+ // options = options || $.fn.options;
+
+ var dom;
+
+ if (typeof content === 'string' || Buffer.isBuffer(content)) {
+ dom = htmlparser.parseDOM(content, options);
+ } else {
+ dom = content;
+ }
+
+ return dom;
+};
+
+/*
+ Update the dom structure, for one changed layer
+*/
+exports.update = function(arr, parent) {
+ // normalize
+ if (!Array.isArray(arr)) arr = [arr];
+
+ // Update parent
+ if (parent) {
+ parent.children = arr;
+ } else {
+ parent = null;
+ }
+
+ // Update neighbors
+ for (var i = 0; i < arr.length; i++) {
+ var node = arr[i];
+
+ // Cleanly remove existing nodes from their previous structures.
+ var oldParent = node.parent || node.root,
+ oldSiblings = oldParent && oldParent.children;
+ if (oldSiblings && oldSiblings !== arr) {
+ oldSiblings.splice(oldSiblings.indexOf(node), 1);
+ if (node.prev) {
+ node.prev.next = node.next;
+ }
+ if (node.next) {
+ node.next.prev = node.prev;
+ }
+ }
+
+ if (parent) {
+ node.prev = arr[i - 1] || null;
+ node.next = arr[i + 1] || null;
+ } else {
+ node.prev = node.next = null;
+ }
+
+ if (parent && parent.type === 'root') {
+ node.root = parent;
+ node.parent = null;
+ } else {
+ node.root = null;
+ node.parent = parent;
+ }
+ }
+
+ return parent;
+};
+
+// module.exports = $.extend(exports);
diff --git a/lib/static.js b/lib/static.js
new file mode 100644
index 0000000..16ad5b2
--- /dev/null
+++ b/lib/static.js
@@ -0,0 +1,187 @@
+/**
+ * Module dependencies
+ */
+
+var serialize = require('dom-serializer'),
+ select = require('css-select'),
+ parse = require('./parse'),
+ _ = {
+ merge: require('lodash.merge'),
+ defaults: require('lodash.defaults')
+ };
+
+/**
+ * $.load(str)
+ */
+
+exports.load = function(content, options) {
+ var Cheerio = require('./cheerio');
+
+ options = _.defaults(options || {}, Cheerio.prototype.options);
+
+ var root = parse(content, options);
+
+ var initialize = function(selector, context, r, opts) {
+ if (!(this instanceof initialize)) {
+ return new initialize(selector, context, r, opts);
+ }
+ opts = _.defaults(opts || {}, options);
+ return Cheerio.call(this, selector, context, r || root, opts);
+ };
+
+ // Ensure that selections created by the "loaded" `initialize` function are
+ // true Cheerio instances.
+ initialize.prototype = Object.create(Cheerio.prototype);
+ initialize.prototype.constructor = initialize;
+
+ // Mimic jQuery's prototype alias for plugin authors.
+ initialize.fn = initialize.prototype;
+
+ // Keep a reference to the top-level scope so we can chain methods that implicitly
+ // resolve selectors; e.g. $("<span>").(".bar"), which otherwise loses ._root
+ initialize.prototype._originalRoot = root;
+
+ // Add in the static methods
+ _.merge(initialize, exports);
+
+ // Add in the root
+ initialize._root = root;
+ // store options
+ initialize._options = options;
+
+ return initialize;
+};
+
+/*
+* Helper function
+*/
+
+function render(that, dom, options) {
+ if (!dom) {
+ if (that._root && that._root.children) {
+ dom = that._root.children;
+ } else {
+ return '';
+ }
+ } else if (typeof dom === 'string') {
+ dom = select(dom, that._root, options);
+ }
+
+ return serialize(dom, options);
+}
+
+/**
+ * $.html([selector | dom], [options])
+ */
+
+exports.html = function(dom, options) {
+ var Cheerio = require('./cheerio');
+
+ // be flexible about parameters, sometimes we call html(),
+ // with options as only parameter
+ // check dom argument for dom element specific properties
+ // assume there is no 'length' or 'type' properties in the options object
+ if (Object.prototype.toString.call(dom) === '[object Object]' && !options && !('length' in dom) && !('type' in dom))
+ {
+ options = dom;
+ dom = undefined;
+ }
+
+ // sometimes $.html() used without preloading html
+ // so fallback non existing options to the default ones
+ options = _.defaults(options || {}, this._options, Cheerio.prototype.options);
+
+ return render(this, dom, options);
+};
+
+/**
+ * $.xml([selector | dom])
+ */
+
+exports.xml = function(dom) {
+ var options = _.defaults({xmlMode: true}, this._options);
+
+ return render(this, dom, options);
+};
+
+/**
+ * $.text(dom)
+ */
+
+exports.text = function(elems) {
+ if (!elems) {
+ elems = this.root();
+ }
+
+ var ret = '',
+ len = elems.length,
+ elem;
+
+ for (var i = 0; i < len; i++) {
+ elem = elems[i];
+ if (elem.type === 'text') ret += elem.data;
+ else if (elem.children && elem.type !== 'comment') {
+ ret += exports.text(elem.children);
+ }
+ }
+
+ return ret;
+};
+
+/**
+ * $.parseHTML(data [, context ] [, keepScripts ])
+ * Parses a string into an array of DOM nodes. The `context` argument has no
+ * meaning for Cheerio, but it is maintained for API compatibility with jQuery.
+ */
+exports.parseHTML = function(data, context, keepScripts) {
+ var parsed;
+
+ if (!data || typeof data !== 'string') {
+ return null;
+ }
+
+ if (typeof context === 'boolean') {
+ keepScripts = context;
+ }
+
+ parsed = this.load(data);
+ if (!keepScripts) {
+ parsed('script').remove();
+ }
+
+ // The `children` array is used by Cheerio internally to group elements that
+ // share the same parents. When nodes created through `parseHTML` are
+ // inserted into previously-existing DOM structures, they will be removed
+ // from the `children` array. The results of `parseHTML` should remain
+ // constant across these operations, so a shallow copy should be returned.
+ return parsed.root()[0].children.slice();
+};
+
+/**
+ * $.root()
+ */
+exports.root = function() {
+ return this(this._root);
+};
+
+/**
+ * $.contains()
+ */
+exports.contains = function(container, contained) {
+
+ // According to the jQuery API, an element does not "contain" itself
+ if (contained === container) {
+ return false;
+ }
+
+ // Step up the descendants, stopping when the root element is reached
+ // (signaled by `.parent` returning a reference to the same object)
+ while (contained && contained !== contained.parent) {
+ contained = contained.parent;
+ if (contained === container) {
+ return true;
+ }
+ }
+
+ return false;
+};
diff --git a/lib/utils.js b/lib/utils.js
new file mode 100644
index 0000000..baa62fc
--- /dev/null
+++ b/lib/utils.js
@@ -0,0 +1,83 @@
+var parse = require('./parse'),
+ render = require('dom-serializer');
+
+/**
+ * HTML Tags
+ */
+
+var tags = { tag: true, script: true, style: true };
+
+/**
+ * Check if the DOM element is a tag
+ *
+ * isTag(type) includes <script> and <style> tags
+ */
+
+exports.isTag = function(type) {
+ if (type.type) type = type.type;
+ return tags[type] || false;
+};
+
+/**
+ * Convert a string to camel case notation.
+ * @param {String} str String to be converted.
+ * @return {String} String in camel case notation.
+ */
+
+exports.camelCase = function(str) {
+ return str.replace(/[_.-](\w|$)/g, function(_, x) {
+ return x.toUpperCase();
+ });
+};
+
+/**
+ * Convert a string from camel case to "CSS case", where word boundaries are
+ * described by hyphens ("-") and all characters are lower-case.
+ * @param {String} str String to be converted.
+ * @return {string} String in "CSS case".
+ */
+exports.cssCase = function(str) {
+ return str.replace(/[A-Z]/g, '-$&').toLowerCase();
+};
+
+/**
+ * Iterate over each DOM element without creating intermediary Cheerio instances.
+ *
+ * This is indented for use internally to avoid otherwise unnecessary memory pressure introduced
+ * by _make.
+ */
+
+exports.domEach = function(cheerio, fn) {
+ var i = 0, len = cheerio.length;
+ while (i < len && fn.call(cheerio, i, cheerio[i]) !== false) ++i;
+ return cheerio;
+};
+
+/**
+ * Create a deep copy of the given DOM structure by first rendering it to a
+ * string and then parsing the resultant markup.
+ *
+ * @argument {Object} dom - The htmlparser2-compliant DOM structure
+ * @argument {Object} options - The parsing/rendering options
+ */
+exports.cloneDom = function(dom, options) {
+ return parse(render(dom, options), options).children;
+};
+
+/*
+ * A simple way to check for HTML strings or ID strings
+ */
+
+var quickExpr = /^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/;
+
+/*
+ * Check if string is HTML
+ */
+exports.isHtml = function(str) {
+ // Faster than running regex, if str starts with `<` and ends with `>`, assume it's HTML
+ if (str.charAt(0) === '<' && str.charAt(str.length - 1) === '>' && str.length >= 3) return true;
+
+ // Run the regex
+ var match = quickExpr.exec(str);
+ return !!(match && match[1]);
+};
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..9a3a55e
--- /dev/null
+++ b/package.json
@@ -0,0 +1,59 @@
+{
+ "name": "cheerio",
+ "version": "0.22.0",
+ "description": "Tiny, fast, and elegant implementation of core jQuery designed specifically for the server",
+ "author": "Matt Mueller <mattmuelle at gmail.com> (mat.io)",
+ "license": "MIT",
+ "keywords": [
+ "htmlparser",
+ "jquery",
+ "selector",
+ "scraper",
+ "parser",
+ "html"
+ ],
+ "repository": {
+ "type": "git",
+ "url": "git://github.com/cheeriojs/cheerio.git"
+ },
+ "main": "./index.js",
+ "files": [
+ "index.js",
+ "lib"
+ ],
+ "engines": {
+ "node": ">= 0.6"
+ },
+ "dependencies": {
+ "css-select": "~1.2.0",
+ "dom-serializer": "~0.1.0",
+ "entities": "~1.1.1",
+ "htmlparser2": "^3.9.1",
+ "lodash.assignin": "^4.0.9",
+ "lodash.bind": "^4.1.4",
+ "lodash.defaults": "^4.0.1",
+ "lodash.filter": "^4.4.0",
+ "lodash.flatten": "^4.2.0",
+ "lodash.foreach": "^4.3.0",
+ "lodash.map": "^4.4.0",
+ "lodash.merge": "^4.4.0",
+ "lodash.pick": "^4.2.1",
+ "lodash.reduce": "^4.4.0",
+ "lodash.reject": "^4.4.0",
+ "lodash.some": "^4.4.0"
+ },
+ "devDependencies": {
+ "benchmark": "^2.1.0",
+ "coveralls": "^2.11.9",
+ "expect.js": "~0.3.1",
+ "istanbul": "^0.4.3",
+ "jsdom": "^9.2.1",
+ "jquery": "^3.0.0",
+ "jshint": "^2.9.2",
+ "mocha": "^2.5.3",
+ "xyz": "~0.5.0"
+ },
+ "scripts": {
+ "test": "make test"
+ }
+}
diff --git a/scripts/prepublish b/scripts/prepublish
new file mode 100755
index 0000000..e24c7a2
--- /dev/null
+++ b/scripts/prepublish
@@ -0,0 +1,17 @@
+#!/usr/bin/env bash
+set -e
+
+tmp="$(mktemp -t "$(basename "$0").XXXXXXXXXX")"
+
+log="$(git --no-pager log --no-merges --pretty="format: * %s (%an)" $PREVIOUS_VERSION..)"
+
+printf "
+$VERSION / $(date +"%Y-%m-%d")
+==================
+
+$log
+" >"$tmp"
+
+cat History.md >>"$tmp"
+mv "$tmp" History.md
+git add History.md
diff --git a/test/.jshintrc b/test/.jshintrc
new file mode 100644
index 0000000..7b01de5
--- /dev/null
+++ b/test/.jshintrc
@@ -0,0 +1,9 @@
+{
+ "extends": "../.jshintrc",
+ "globals": {
+ "expect": true,
+ "it": true,
+ "describe": true,
+ "beforeEach": true
+ }
+}
diff --git a/test/api/attributes.js b/test/api/attributes.js
new file mode 100644
index 0000000..21f29e7
--- /dev/null
+++ b/test/api/attributes.js
@@ -0,0 +1,785 @@
+var expect = require('expect.js');
+
+var cheerio = require('../..');
+var fruits = require('../fixtures').fruits;
+var vegetables = require('../fixtures').vegetables;
+var food = require('../fixtures').food;
+var chocolates = require('../fixtures').chocolates;
+var inputs = require('../fixtures').inputs;
+var toArray = Function.call.bind(Array.prototype.slice);
+
+describe('$(...)', function() {
+ var $;
+
+ beforeEach(function() {
+ $ = cheerio.load(fruits);
+ });
+
+ describe('.attr', function() {
+
+ it('() : should get all the attributes', function() {
+ var attrs = $('ul').attr();
+ expect(attrs.id).to.equal('fruits');
+ });
+
+ it('(invalid key) : invalid attr should get undefined', function() {
+ var attr = $('.apple').attr('lol');
+ expect(attr).to.be(undefined);
+ });
+
+ it('(valid key) : valid attr should get value', function() {
+ var cls = $('.apple').attr('class');
+ expect(cls).to.equal('apple');
+ });
+
+ it('(valid key) : valid attr should get name when boolean', function() {
+ var attr = $('<input name=email autofocus>').attr('autofocus');
+ expect(attr).to.equal('autofocus');
+ });
+
+ it('(key, value) : should set attr', function() {
+ var $pear = $('.pear').attr('id', 'pear');
+ expect($('#pear')).to.have.length(1);
+ expect($pear).to.be.a($);
+ });
+
+ it('(key, value) : should set attr', function() {
+ var $el = cheerio('<div></div> <div></div>').attr('class', 'pear');
+
+ expect($el[0].attribs['class']).to.equal('pear');
+ expect($el[1].attribs).to.equal(undefined);
+ expect($el[2].attribs['class']).to.equal('pear');
+ });
+
+ it('(key, value) : should return an empty object for an empty object', function() {
+ var $src = $().attr('key', 'value');
+ expect($src.length).to.equal(0);
+ expect($src[0]).to.be(undefined);
+ });
+
+ it('(map) : object map should set multiple attributes', function() {
+ $('.apple').attr({
+ id: 'apple',
+ style: 'color:red;',
+ 'data-url': 'http://apple.com'
+ });
+ var attrs = $('.apple').attr();
+ expect(attrs.id).to.equal('apple');
+ expect(attrs.style).to.equal('color:red;');
+ expect(attrs['data-url']).to.equal('http://apple.com');
+ });
+
+ it('(key, function) : should call the function and update the attribute with the return value', function() {
+ var $fruits = $('#fruits');
+ $fruits.attr('id', function(index, value) {
+ expect(index).to.equal(0);
+ expect(value).to.equal('fruits');
+ return 'ninja';
+ });
+ var attrs = $fruits.attr();
+ expect(attrs.id).to.equal('ninja');
+ });
+
+ it('(key, value) : should correctly encode then decode unsafe values', function() {
+ var $apple = $('.apple');
+ $apple.attr('href', 'http://github.com/"><script>alert("XSS!")</script><br');
+ expect($apple.attr('href')).to.equal('http://github.com/"><script>alert("XSS!")</script><br');
+
+ $apple.attr('href', 'http://github.com/"><script>alert("XSS!")</script><br');
+ expect($apple.html()).to.not.contain('<script>alert("XSS!")</script>');
+ });
+
+ it('(key, value) : should coerce values to a string', function() {
+ var $apple = $('.apple');
+ $apple.attr('data-test', 1);
+ expect($apple[0].attribs['data-test']).to.equal('1');
+ expect($apple.attr('data-test')).to.equal('1');
+ });
+
+ it('(key, value) : handle removed boolean attributes', function() {
+ var $apple = $('.apple');
+ $apple.attr('autofocus', 'autofocus');
+ expect($apple.attr('autofocus')).to.equal('autofocus');
+ $apple.removeAttr('autofocus');
+ expect($apple.attr('autofocus')).to.be(undefined);
+ });
+
+ it('(key, value) : should remove non-boolean attributes with names or values similar to boolean ones', function() {
+ var $apple = $('.apple');
+ $apple.attr('data-autofocus', 'autofocus');
+ expect($apple.attr('data-autofocus')).to.equal('autofocus');
+ $apple.removeAttr('data-autofocus');
+ expect($apple.attr('data-autofocus')).to.be(undefined);
+ });
+
+ it('(key, value) : should remove attributes when called with null value', function() {
+ var $pear = $('.pear').attr('autofocus', 'autofocus');
+ expect($pear.attr('autofocus')).to.equal('autofocus');
+ $pear.attr('autofocus', null);
+ expect($pear.attr('autofocus')).to.be(undefined);
+ });
+
+ it('(map) : should remove attributes with null values', function() {
+ var $pear = $('.pear').attr({'autofocus': 'autofocus', 'style': 'color:red'});
+ expect($pear.attr('autofocus')).to.equal('autofocus');
+ expect($pear.attr('style')).to.equal('color:red');
+ $pear.attr({'autofocus': null, 'style': 'color:blue'});
+ expect($pear.attr('autofocus')).to.be(undefined);
+ expect($pear.attr('style')).to.equal('color:blue');
+ });
+ });
+
+ describe('.prop', function () {
+ var $,
+ checkbox;
+
+ beforeEach(function () {
+ $ = cheerio.load(inputs);
+ checkbox = $('input[name=checkbox_on]');
+ });
+
+ it('(valid key) : valid prop should get value', function() {
+ expect(checkbox.prop('checked')).to.equal(true);
+ checkbox.css('display', 'none');
+ expect(checkbox.prop('style').display).to.equal('none');
+ expect(checkbox.prop('style')).to.have.length(1);
+ expect(checkbox.prop('style')).to.contain('display');
+ expect(checkbox.prop('tagName')).to.equal('INPUT');
+ expect(checkbox.prop('nodeName')).to.equal('INPUT');
+ });
+
+ it('(invalid key) : invalid prop should get undefined', function() {
+ var attr = checkbox.prop('lol');
+ expect(attr).to.be(undefined);
+ });
+
+ it('(key, value) : should set prop', function() {
+ expect(checkbox.prop('checked')).to.equal(true);
+ checkbox.prop('checked', false);
+ expect(checkbox.prop('checked')).to.equal(false);
+ checkbox.prop('checked', true);
+ expect(checkbox.prop('checked')).to.equal(true);
+ });
+
+ it('(map) : object map should set multiple props', function() {
+ checkbox.prop({
+ id: 'check',
+ checked: false
+ });
+ expect(checkbox.prop('id')).to.equal('check');
+ expect(checkbox.prop('checked')).to.equal(false);
+ });
+
+ it('(key, function) : should call the function and update the prop with the return value', function() {
+ checkbox.prop('checked', function(index, value) {
+ expect(index).to.equal(0);
+ expect(value).to.equal(true);
+ return false;
+ });
+ expect(checkbox.prop('checked')).to.equal(false);
+ });
+
+ it('(key, value) : should support chaining after setting props', function() {
+ expect(checkbox.prop('checked', false)).to.equal(checkbox);
+ });
+
+ it('(invalid element/tag) : prop should return undefined', function() {
+ expect($(undefined).prop('prop')).to.be(undefined);
+ expect($(null).prop('prop')).to.be(undefined);
+ });
+ });
+
+ describe('.data', function() {
+
+ beforeEach(function() {
+ $ = cheerio.load(chocolates);
+ });
+
+ it('() : should get all data attributes initially declared in the markup', function() {
+ var data = $('.linth').data();
+ expect(data).to.eql({
+ highlight: 'Lindor',
+ origin: 'swiss'
+ });
+ });
+
+ it('() : should get all data set via `data`', function() {
+ var $el = cheerio('<div>');
+ $el.data('a', 1);
+ $el.data('b', 2);
+
+ expect($el.data()).to.eql({
+ a: 1,
+ b: 2
+ });
+ });
+
+ it('() : should get all data attributes initially declared in the markup merged with all data additionally set via `data`', function() {
+ var $el = cheerio('<div data-a="a">');
+ $el.data('b', 'b');
+
+ expect($el.data()).to.eql({
+ a: 'a',
+ b: 'b'
+ });
+ });
+
+ it('() : no data attribute should return an empty object', function() {
+ var data = $('.cailler').data();
+ expect(data).to.be.empty();
+ });
+
+ it('(invalid key) : invalid data attribute should return `undefined` ', function() {
+ var data = $('.frey').data('lol');
+ expect(data).to.be(undefined);
+ });
+
+ it('(valid key) : valid data attribute should get value', function() {
+ var highlight = $('.linth').data('highlight');
+ var origin = $('.linth').data('origin');
+
+ expect(highlight).to.equal('Lindor');
+ expect(origin).to.equal('swiss');
+ });
+
+ it('(key) : should translate camel-cased key values to hyphen-separated versions', function() {
+ var $el = cheerio('<div data--three-word-attribute="a" data-foo-Bar_BAZ-="b">');
+
+ expect($el.data('ThreeWordAttribute')).to.be('a');
+ expect($el.data('fooBar_baz-')).to.be('b');
+ });
+
+ it('(key) : should retrieve object values', function() {
+ var data = {};
+ var $el = cheerio('<div>');
+
+ $el.data('test', data);
+
+ expect($el.data('test')).to.be(data);
+ });
+
+ it('(key) : should parse JSON data derived from the markup', function() {
+ var $el = cheerio('<div data-json="[1, 2, 3]">');
+
+ expect($el.data('json')).to.eql([1,2,3]);
+ });
+
+ it('(key) : should not parse JSON data set via the `data` API', function() {
+ var $el = cheerio('<div>');
+ $el.data('json', '[1, 2, 3]');
+
+ expect($el.data('json')).to.be('[1, 2, 3]');
+ });
+
+ // See http://api.jquery.com/data/ and http://bugs.jquery.com/ticket/14523
+ it('(key) : should ignore the markup value after the first access', function() {
+ var $el = cheerio('<div data-test="a">');
+
+ expect($el.data('test')).to.be('a');
+
+ $el.attr('data-test', 'b');
+
+ expect($el.data('test')).to.be('a');
+ });
+
+ it('(key) : should recover from malformed JSON', function() {
+ var $el = cheerio('<div data-custom="{{templatevar}}">');
+
+ expect($el.data('custom')).to.be('{{templatevar}}');
+ });
+
+ it('(hyphen key) : data addribute with hyphen should be camelized ;-)', function() {
+ var data = $('.frey').data();
+ expect(data).to.eql({
+ taste: 'sweet',
+ bestCollection: 'Mahony'
+ });
+ });
+
+ it('(key, value) : should set data attribute', function() {
+ // Adding as object.
+ var a = $('.frey').data({
+ balls: 'giandor'
+ });
+ // Adding as string.
+ var b = $('.linth').data('snack', 'chocoletti');
+
+ expect(a.data('balls')).to.eql('giandor');
+ expect(b.data('snack')).to.eql('chocoletti');
+ });
+
+ it('(key, value) : should set data for all elements in the selection', function() {
+ $('li').data('foo', 'bar');
+
+ expect($('li').eq(0).data('foo')).to.eql('bar');
+ expect($('li').eq(1).data('foo')).to.eql('bar');
+ expect($('li').eq(2).data('foo')).to.eql('bar');
+ });
+
+ it('(map) : object map should set multiple data attributes', function() {
+ var data = $('.linth').data({
+ id: 'Cailler',
+ flop: 'Pippilotti Rist',
+ top: 'Frigor',
+ url: 'http://www.cailler.ch/'
+ })['0'].data;
+
+ expect(data.id).to.equal('Cailler');
+ expect(data.flop).to.equal('Pippilotti Rist');
+ expect(data.top).to.equal('Frigor');
+ expect(data.url).to.equal('http://www.cailler.ch/');
+ });
+
+ describe('(attr) : data-* attribute type coercion :', function() {
+ it('boolean', function() {
+ var $el = cheerio('<div data-bool="true">');
+ expect($el.data('bool')).to.be(true);
+ });
+
+ it('number', function() {
+ var $el = cheerio('<div data-number="23">');
+ expect($el.data('number')).to.be(23);
+ });
+
+ it('number (scientific notation is not coerced)', function() {
+ var $el = cheerio('<div data-sci="1E10">');
+ expect($el.data('sci')).to.be('1E10');
+ });
+
+ it('null', function() {
+ var $el = cheerio('<div data-null="null">');
+ expect($el.data('null')).to.be(null);
+ });
+
+ it('object', function() {
+ var $el = cheerio('<div data-obj=\'{ "a": 45 }\'>');
+ expect($el.data('obj')).to.eql({ a: 45 });
+ });
+
+ it('array', function() {
+ var $el = cheerio('<div data-array="[1, 2, 3]">');
+ expect($el.data('array')).to.eql([1, 2, 3]);
+ });
+
+ });
+
+ });
+
+
+ describe('.val', function() {
+
+ beforeEach(function() {
+ $ = cheerio.load(inputs);
+ });
+
+ it('.val(): on select should get value', function() {
+ var val = $('select#one').val();
+ expect(val).to.equal('option_selected');
+ });
+ it('.val(): on select with no value should get text', function() {
+ var val = $('select#one-valueless').val();
+ expect(val).to.equal('Option selected');
+ });
+ it('.val(): on select with no value should get converted HTML', function() {
+ var val = $('select#one-html-entity').val();
+ expect(val).to.equal('Option <selected>');
+ });
+ it('.val(): on select with no value should get text content', function() {
+ var val = $('select#one-nested').val();
+ expect(val).to.equal('Option selected');
+ });
+ it('.val(): on option should get value', function() {
+ var val = $('select#one option').eq(0).val();
+ expect(val).to.equal('option_not_selected');
+ });
+ it('.val(): on text input should get value', function() {
+ var val = $('input[type="text"]').val();
+ expect(val).to.equal('input_text');
+ });
+ it('.val(): on checked checkbox should get value', function() {
+ var val = $('input[name="checkbox_on"]').val();
+ expect(val).to.equal('on');
+ });
+ it('.val(): on unchecked checkbox should get value', function() {
+ var val = $('input[name="checkbox_off"]').val();
+ expect(val).to.equal('off');
+ });
+ it('.val(): on valueless checkbox should get value', function() {
+ var val = $('input[name="checkbox_valueless"]').val();
+ expect(val).to.equal('on');
+ });
+ it('.val(): on radio should get value', function() {
+ var val = $('input[type="radio"]').val();
+ expect(val).to.equal('off');
+ });
+ it('.val(): on valueless radio should get value', function() {
+ var val = $('input[name="radio_valueless"]').val();
+ expect(val).to.equal('on');
+ });
+ it('.val(): on multiple select should get an array of values', function() {
+ var val = $('select#multi').val();
+ expect(val).to.eql(['2', '3']);
+ });
+ it('.val(): on multiple select with no value attribute should get an array of text content', function() {
+ var val = $('select#multi-valueless').val();
+ expect(val).to.eql(['2', '3']);
+ });
+ it('.val(value): on input text should set value', function() {
+ var element = $('input[type="text"]').val('test');
+ expect(element.val()).to.equal('test');
+ });
+ it('.val(value): on select should set value', function() {
+ var element = $('select#one').val('option_not_selected');
+ expect(element.val()).to.equal('option_not_selected');
+ });
+ it('.val(value): on option should set value', function() {
+ var element = $('select#one option').eq(0).val('option_changed');
+ expect(element.val()).to.equal('option_changed');
+ });
+ it('.val(value): on radio should set value', function() {
+ var element = $('input[name="radio"]').val('off');
+ expect(element.val()).to.equal('off');
+ });
+ it('.val(value): on radio with special characters should set value', function() {
+ var element = $('input[name="radio[brackets]"]').val('off');
+ expect(element.val()).to.equal('off');
+ });
+ it('.val(values): on multiple select should set multiple values', function() {
+ var element = $('select#multi').val(['1', '3', '4']);
+ expect(element.val()).to.have.length(3);
+ });
+ });
+
+ describe('.removeAttr', function() {
+
+ it('(key) : should remove a single attr', function() {
+ var $fruits = $('#fruits');
+ expect($fruits.attr('id')).to.not.be(undefined);
+ $fruits.removeAttr('id');
+ expect($fruits.attr('id')).to.be(undefined);
+ });
+
+ it('should return cheerio object', function() {
+ var obj = $('ul').removeAttr('id');
+ expect(obj).to.be.a($);
+ });
+
+ });
+
+ describe('.hasClass', function() {
+ function test(attr) {
+ return cheerio('<div class="' + attr + '"></div>');
+ }
+
+ it('(valid class) : should return true', function() {
+ var cls = $('.apple').hasClass('apple');
+ expect(cls).to.be.ok();
+
+ expect(test('foo').hasClass('foo')).to.be.ok();
+ expect(test('foo bar').hasClass('foo')).to.be.ok();
+ expect(test('bar foo').hasClass('foo')).to.be.ok();
+ expect(test('bar foo bar').hasClass('foo')).to.be.ok();
+ });
+
+ it('(invalid class) : should return false', function() {
+ var cls = $('#fruits').hasClass('fruits');
+ expect(cls).to.not.be.ok();
+ expect(test('foo-bar').hasClass('foo')).to.not.be.ok();
+ expect(test('foo-bar').hasClass('foo')).to.not.be.ok();
+ expect(test('foo-bar').hasClass('foo-ba')).to.not.be.ok();
+ });
+
+ it('should check multiple classes', function() {
+ // Add a class
+ $('.apple').addClass('red');
+ expect($('.apple').hasClass('apple')).to.be.ok();
+ expect($('.apple').hasClass('red')).to.be.ok();
+
+ // Remove one and test again
+ $('.apple').removeClass('apple');
+ expect($('li').eq(0).hasClass('apple')).to.not.be.ok();
+ // expect($('li', $fruits).eq(0).hasClass('red')).to.be.ok();
+ });
+ });
+
+ describe('.addClass', function() {
+
+ it('(first class) : should add the class to the element', function() {
+ var $fruits = $('#fruits');
+ $fruits.addClass('fruits');
+ var cls = $fruits.hasClass('fruits');
+ expect(cls).to.be.ok();
+ });
+
+ it('(single class) : should add the class to the element', function() {
+ $('.apple').addClass('fruit');
+ var cls = $('.apple').hasClass('fruit');
+ expect(cls).to.be.ok();
+ });
+
+ it('(class): adds classes to many selected items', function() {
+ $('li').addClass('fruit');
+ expect($('.apple').hasClass('fruit')).to.be.ok();
+ expect($('.orange').hasClass('fruit')).to.be.ok();
+ expect($('.pear').hasClass('fruit')).to.be.ok();
+ });
+
+ it('(class class class) : should add multiple classes to the element', function() {
+ $('.apple').addClass('fruit red tasty');
+ expect($('.apple').hasClass('apple')).to.be.ok();
+ expect($('.apple').hasClass('fruit')).to.be.ok();
+ expect($('.apple').hasClass('red')).to.be.ok();
+ expect($('.apple').hasClass('tasty')).to.be.ok();
+ });
+
+ it('(fn) : should add classes returned from the function', function() {
+ var $fruits = $('#fruits').children();
+ var args = [];
+ var thisVals = [];
+ var toAdd = ['apple red', '', undefined];
+
+ $fruits.addClass(function(idx) {
+ args.push(toArray(arguments));
+ thisVals.push(this);
+ return toAdd[idx];
+ });
+
+ expect(args).to.eql([
+ [0, 'apple'],
+ [1, 'orange'],
+ [2, 'pear']
+ ]);
+ expect(thisVals).to.eql([
+ $fruits[0],
+ $fruits[1],
+ $fruits[2]
+ ]);
+ expect($fruits.eq(0).hasClass('apple')).to.be.ok();
+ expect($fruits.eq(0).hasClass('red')).to.be.ok();
+ expect($fruits.eq(1).hasClass('orange')).to.be.ok();
+ expect($fruits.eq(2).hasClass('pear')).to.be.ok();
+ });
+
+ });
+
+ describe('.removeClass', function() {
+
+ it('() : should remove all the classes', function() {
+ $('.pear').addClass('fruit');
+ $('.pear').removeClass();
+ expect($('.pear').attr('class')).to.be(undefined);
+ });
+
+ it('("") : should not modify class list', function() {
+ var $fruits = $('#fruits');
+ $fruits.children().removeClass('');
+ expect($('.apple')).to.have.length(1);
+ });
+
+ it('(invalid class) : should not remove anything', function() {
+ $('.pear').removeClass('fruit');
+ expect($('.pear').hasClass('pear')).to.be.ok();
+ });
+
+ it('(no class attribute) : should not throw an exception', function() {
+ var $vegetables = cheerio(vegetables);
+
+ expect(function() {
+ $('li', $vegetables).removeClass('vegetable');
+ })
+ .to.not.throwException();
+ });
+
+ it('(single class) : should remove a single class from the element', function() {
+ $('.pear').addClass('fruit');
+ expect($('.pear').hasClass('fruit')).to.be.ok();
+ $('.pear').removeClass('fruit');
+ expect($('.pear').hasClass('fruit')).to.not.be.ok();
+ expect($('.pear').hasClass('pear')).to.be.ok();
+ });
+
+ it('(single class) : should remove a single class from multiple classes on the element', function() {
+ $('.pear').addClass('fruit green tasty');
+ expect($('.pear').hasClass('fruit')).to.be.ok();
+ expect($('.pear').hasClass('green')).to.be.ok();
+ expect($('.pear').hasClass('tasty')).to.be.ok();
+
+ $('.pear').removeClass('green');
+ expect($('.pear').hasClass('fruit')).to.be.ok();
+ expect($('.pear').hasClass('green')).to.not.be.ok();
+ expect($('.pear').hasClass('tasty')).to.be.ok();
+ });
+
+ it('(class class class) : should remove multiple classes from the element', function() {
+ $('.apple').addClass('fruit red tasty');
+ expect($('.apple').hasClass('apple')).to.be.ok();
+ expect($('.apple').hasClass('fruit')).to.be.ok();
+ expect($('.apple').hasClass('red')).to.be.ok();
+ expect($('.apple').hasClass('tasty')).to.be.ok();
+
+ $('.apple').removeClass('apple red tasty');
+ expect($('.fruit').hasClass('apple')).to.not.be.ok();
+ expect($('.fruit').hasClass('red')).to.not.be.ok();
+ expect($('.fruit').hasClass('tasty')).to.not.be.ok();
+ expect($('.fruit').hasClass('fruit')).to.be.ok();
+ });
+
+ it('(class) : should remove all occurrences of a class name', function() {
+ var $div = cheerio('<div class="x x y x z"></div>');
+ expect($div.removeClass('x').hasClass('x')).to.be(false);
+ });
+
+ it('(fn) : should remove classes returned from the function', function() {
+ var $fruits = $('#fruits').children();
+ var args = [];
+ var thisVals = [];
+ var toAdd = ['apple red', '', undefined];
+
+ $fruits.removeClass(function(idx) {
+ args.push(toArray(arguments));
+ thisVals.push(this);
+ return toAdd[idx];
+ });
+
+ expect(args).to.eql([
+ [0, 'apple'],
+ [1, 'orange'],
+ [2, 'pear']
+ ]);
+ expect(thisVals).to.eql([
+ $fruits[0],
+ $fruits[1],
+ $fruits[2]
+ ]);
+ expect($fruits.eq(0).hasClass('apple')).to.not.be.ok();
+ expect($fruits.eq(0).hasClass('red')).to.not.be.ok();
+ expect($fruits.eq(1).hasClass('orange')).to.be.ok();
+ expect($fruits.eq(2).hasClass('pear')).to.be.ok();
+ });
+
+ });
+
+ describe('.toggleClass', function() {
+
+ it('(class class) : should toggle multiple classes from the element', function() {
+ $('.apple').addClass('fruit');
+ expect($('.apple').hasClass('apple')).to.be.ok();
+ expect($('.apple').hasClass('fruit')).to.be.ok();
+ expect($('.apple').hasClass('red')).to.not.be.ok();
+
+ $('.apple').toggleClass('apple red');
+ expect($('.fruit').hasClass('apple')).to.not.be.ok();
+ expect($('.fruit').hasClass('red')).to.be.ok();
+ expect($('.fruit').hasClass('fruit')).to.be.ok();
+ });
+
+ it('(class class, true) : should add multiple classes to the element', function() {
+ $('.apple').addClass('fruit');
+ expect($('.apple').hasClass('apple')).to.be.ok();
+ expect($('.apple').hasClass('fruit')).to.be.ok();
+ expect($('.apple').hasClass('red')).to.not.be.ok();
+
+ $('.apple').toggleClass('apple red', true);
+ expect($('.fruit').hasClass('apple')).to.be.ok();
+ expect($('.fruit').hasClass('red')).to.be.ok();
+ expect($('.fruit').hasClass('fruit')).to.be.ok();
+ });
+
+ it('(class true) : should add only one instance of class', function () {
+ $('.apple').toggleClass('tasty', true);
+ $('.apple').toggleClass('tasty', true);
+ expect($('.apple').attr('class').match(/tasty/g).length).to.equal(1);
+ });
+
+ it('(class class, false) : should remove multiple classes from the element', function() {
+ $('.apple').addClass('fruit');
+ expect($('.apple').hasClass('apple')).to.be.ok();
+ expect($('.apple').hasClass('fruit')).to.be.ok();
+ expect($('.apple').hasClass('red')).to.not.be.ok();
+
+ $('.apple').toggleClass('apple red', false);
+ expect($('.fruit').hasClass('apple')).to.not.be.ok();
+ expect($('.fruit').hasClass('red')).to.not.be.ok();
+ expect($('.fruit').hasClass('fruit')).to.be.ok();
+ });
+
+ it('(fn) : should toggle classes returned from the function', function() {
+ $ = cheerio.load(food);
+
+ $('.apple').addClass('fruit');
+ $('.carrot').addClass('vegetable');
+ expect($('.apple').hasClass('fruit')).to.be.ok();
+ expect($('.apple').hasClass('vegetable')).to.not.be.ok();
+ expect($('.orange').hasClass('fruit')).to.not.be.ok();
+ expect($('.orange').hasClass('vegetable')).to.not.be.ok();
+ expect($('.carrot').hasClass('fruit')).to.not.be.ok();
+ expect($('.carrot').hasClass('vegetable')).to.be.ok();
+ expect($('.sweetcorn').hasClass('fruit')).to.not.be.ok();
+ expect($('.sweetcorn').hasClass('vegetable')).to.not.be.ok();
+
+ $('li').toggleClass(function() {
+ return $(this).parent().is('#fruits') ? 'fruit' : 'vegetable';
+ });
+ expect($('.apple').hasClass('fruit')).to.not.be.ok();
+ expect($('.apple').hasClass('vegetable')).to.not.be.ok();
+ expect($('.orange').hasClass('fruit')).to.be.ok();
+ expect($('.orange').hasClass('vegetable')).to.not.be.ok();
+ expect($('.carrot').hasClass('fruit')).to.not.be.ok();
+ expect($('.carrot').hasClass('vegetable')).to.not.be.ok();
+ expect($('.sweetcorn').hasClass('fruit')).to.not.be.ok();
+ expect($('.sweetcorn').hasClass('vegetable')).to.be.ok();
+ });
+
+ });
+
+ describe('.is', function () {
+ it('() : should return false', function() {
+ expect($('li.apple').is()).to.be(false);
+ });
+
+ it('(true selector) : should return true', function() {
+ expect(cheerio('#vegetables', vegetables).is('ul')).to.be(true);
+ });
+
+ it('(false selector) : should return false', function() {
+ expect(cheerio('#vegetables', vegetables).is('div')).to.be(false);
+ });
+
+ it('(true selection) : should return true', function() {
+ var $vegetables = cheerio('li', vegetables);
+ expect($vegetables.is($vegetables.eq(1))).to.be(true);
+ });
+
+ it('(false selection) : should return false', function() {
+ var $vegetableList = cheerio(vegetables);
+ var $vegetables = $vegetableList.find('li');
+ expect($vegetables.is($vegetableList)).to.be(false);
+ });
+
+ it('(true element) : should return true', function() {
+ var $vegetables = cheerio('li', vegetables);
+ expect($vegetables.is($vegetables[0])).to.be(true);
+ });
+
+ it('(false element) : should return false', function() {
+ var $vegetableList = cheerio(vegetables);
+ var $vegetables = $vegetableList.find('li');
+ expect($vegetables.is($vegetableList[0])).to.be(false);
+ });
+
+ it('(true predicate) : should return true', function() {
+ var result = $('li').is(function() {
+ return this.tagName === 'li' && $(this).hasClass('pear');
+ });
+ expect(result).to.be(true);
+ });
+
+ it('(false predicate) : should return false', function () {
+ var result = $('li').last().is(function() {
+ return this.tagName === 'ul';
+ });
+ expect(result).to.be(false);
+ });
+ });
+
+});
diff --git a/test/api/css.js b/test/api/css.js
new file mode 100644
index 0000000..9e394c4
--- /dev/null
+++ b/test/api/css.js
@@ -0,0 +1,88 @@
+var expect = require('expect.js');
+var cheerio = require('../..');
+
+describe('$(...)', function() {
+
+ describe('.css', function() {
+ it('(prop): should return a css property value', function() {
+ var el = cheerio('<li style="hai: there">');
+ expect(el.css('hai')).to.equal('there');
+ });
+
+ it('([prop1, prop2]): should return the specified property values as an object', function() {
+ var el = cheerio('<li style="margin: 1px; padding: 2px; color: blue;">');
+ expect(el.css(['margin', 'color'])).to.eql({ margin: '1px', color: 'blue' });
+ });
+
+ it('(prop, val): should set a css property', function() {
+ var el = cheerio('<li style="margin: 0;"></li><li></li>');
+ el.css('color', 'red');
+ expect(el.attr('style')).to.equal('margin: 0; color: red;');
+ expect(el.eq(1).attr('style')).to.equal('color: red;');
+ });
+
+ it('(prop, ""): should unset a css property', function() {
+ var el = cheerio('<li style="padding: 1px; margin: 0;">');
+ el.css('padding', '');
+ expect(el.attr('style')).to.equal('margin: 0;');
+ });
+
+ it('(prop): should not mangle embedded urls', function() {
+ var el = cheerio('<li style="background-image:url(http://example.com/img.png);">');
+ expect(el.css('background-image')).to.equal('url(http://example.com/img.png)');
+ });
+
+ it('(prop): should ignore blank properties', function() {
+ var el = cheerio('<li style=":#ccc;color:#aaa;">');
+ expect(el.css()).to.eql({color:'#aaa'});
+ });
+
+ it('(prop): should ignore blank values', function() {
+ var el = cheerio('<li style="color:;position:absolute;">');
+ expect(el.css()).to.eql({position:'absolute'});
+ });
+
+ describe('(prop, function):', function() {
+ beforeEach(function() {
+ this.$el = cheerio('<div style="margin: 0px;"></div><div style="margin: 1px;"></div><div style="margin: 2px;">');
+ });
+
+ it('should iterate over the selection', function() {
+ var count = 0;
+ var $el = this.$el;
+ this.$el.css('margin', function(idx, value) {
+ expect(idx).to.equal(count);
+ expect(value).to.equal(count + 'px');
+ expect(this).to.equal($el[count]);
+ count++;
+ });
+ expect(count).to.equal(3);
+ });
+
+ it('should set each attribute independently', function() {
+ var values = ['4px', '', undefined];
+ this.$el.css('margin', function(idx) {
+ return values[idx];
+ });
+ expect(this.$el.eq(0).attr('style')).to.equal('margin: 4px;');
+ expect(this.$el.eq(1).attr('style')).to.equal('');
+ expect(this.$el.eq(2).attr('style')).to.equal('margin: 2px;');
+ });
+ });
+
+ it('(obj): should set each key and val', function() {
+ var el = cheerio('<li style="padding: 0;"></li><li></li>');
+ el.css({ foo: 0 });
+ expect(el.eq(0).attr('style')).to.equal('padding: 0; foo: 0;');
+ expect(el.eq(1).attr('style')).to.equal('foo: 0;');
+ });
+
+ describe('parser', function(){
+ it('should allow any whitespace between declarations', function() {
+ var el = cheerio('<li style="one \t:\n 0;\n two \f\r:\v 1">');
+ expect(el.css(['one', 'two'])).to.eql({ one: 0, two: 1 });
+ });
+ });
+ });
+
+});
diff --git a/test/api/forms.js b/test/api/forms.js
new file mode 100644
index 0000000..f91df17
--- /dev/null
+++ b/test/api/forms.js
@@ -0,0 +1,155 @@
+var expect = require('expect.js'),
+ cheerio = require('../..'),
+ forms = require('../fixtures').forms;
+
+describe('$(...)', function() {
+
+ var $;
+
+ beforeEach(function() {
+ $ = cheerio.load(forms);
+ });
+
+ describe('.serializeArray', function() {
+
+ it('() : should get form controls', function() {
+ expect($('form#simple').serializeArray()).to.eql([
+ {
+ name: 'fruit',
+ value: 'Apple'
+ }
+ ]);
+ });
+
+ it('() : should get nested form controls', function() {
+ expect($('form#nested').serializeArray()).to.have.length(2);
+ var data = $('form#nested').serializeArray();
+ data.sort(function (a, b) {
+ return a.value - b.value;
+ });
+ expect(data).to.eql([
+ {
+ name: 'fruit',
+ value: 'Apple'
+ },
+ {
+ name: 'vegetable',
+ value: 'Carrot'
+ }
+ ]);
+ });
+
+ it('() : should not get disabled form controls', function() {
+ expect($('form#disabled').serializeArray()).to.eql([]);
+ });
+
+ it('() : should not get form controls with the wrong type', function() {
+ expect($('form#submit').serializeArray()).to.eql([
+ {
+ name: 'fruit',
+ value: 'Apple'
+ }
+ ]);
+ });
+
+ it('() : should get selected options', function() {
+ expect($('form#select').serializeArray()).to.eql([
+ {
+ name: 'fruit',
+ value: 'Orange'
+ }
+ ]);
+ });
+
+ it('() : should not get unnamed form controls', function() {
+ expect($('form#unnamed').serializeArray()).to.eql([
+ {
+ name: 'fruit',
+ value: 'Apple'
+ }
+ ]);
+ });
+
+ it('() : should get multiple selected options', function() {
+ expect($('form#multiple').serializeArray()).to.have.length(2);
+ var data = $('form#multiple').serializeArray();
+ data.sort(function (a, b) {
+ return a.value - b.value;
+ });
+ expect(data).to.eql([
+ {
+ name: 'fruit',
+ value: 'Apple'
+ },
+ {
+ name: 'fruit',
+ value: 'Orange'
+ }
+ ]);
+ });
+
+ it('() : should get individually selected elements', function() {
+ var data = $('form#nested input').serializeArray();
+ data.sort(function (a, b) {
+ return a.value - b.value;
+ });
+ expect(data).to.eql([
+ {
+ name: 'fruit',
+ value: 'Apple'
+ },
+ {
+ name: 'vegetable',
+ value: 'Carrot'
+ }
+ ]);
+
+ });
+
+ it('() : should standardize line breaks', function() {
+ expect($('form#textarea').serializeArray()).to.eql([
+ {
+ name: 'fruits',
+ value: 'Apple\r\nOrange'
+ }
+ ]);
+ });
+
+ it('() : shouldn\'t serialize the empty string', function() {
+ expect($('<input value=pineapple>').serializeArray()).to.eql([]);
+ expect($('<input name="" value=pineapple>').serializeArray()).to.eql([]);
+ expect($('<input name="fruit" value=pineapple>').serializeArray()).to.eql([
+ {
+ name: 'fruit',
+ value: 'pineapple'
+ }
+ ]);
+ });
+
+ });
+
+ describe('.serialize', function() {
+
+ it('() : should get form controls', function() {
+ expect($('form#simple').serialize()).to.equal('fruit=Apple');
+ });
+
+ it('() : should get nested form controls', function() {
+ expect($('form#nested').serialize()).to.equal('fruit=Apple&vegetable=Carrot');
+ });
+
+ it('() : should not get disabled form controls', function() {
+ expect($('form#disabled').serialize()).to.equal('');
+ });
+
+ it('() : should get multiple selected options', function() {
+ expect($('form#multiple').serialize()).to.equal('fruit=Apple&fruit=Orange');
+ });
+
+ it('() : should encode spaces as +\'s', function() {
+ expect($('form#spaces').serialize()).to.equal('fruit=Blood+orange');
+ });
+
+ });
+
+});
diff --git a/test/api/manipulation.js b/test/api/manipulation.js
new file mode 100644
index 0000000..0d4ba2a
--- /dev/null
+++ b/test/api/manipulation.js
@@ -0,0 +1,1457 @@
+var expect = require('expect.js'),
+ cheerio = require('../..'),
+ fruits = require('../fixtures').fruits,
+ toArray = Function.call.bind(Array.prototype.slice);
+
+describe('$(...)', function() {
+
+ var $, $fruits;
+
+ beforeEach(function() {
+ $ = cheerio.load(fruits);
+ $fruits = $('#fruits');
+ });
+
+ describe('.wrap', function() {
+ it('(Cheerio object) : should insert the element and add selected element(s) as its child', function() {
+ var $redFruits = $('<div class="red-fruits"></div>');
+ $('.apple').wrap($redFruits);
+
+ expect($fruits.children().eq(0).hasClass('red-fruits')).to.be.ok();
+ expect($('.red-fruits').children().eq(0).hasClass('apple')).to.be.ok();
+ expect($fruits.children().eq(1).hasClass('orange')).to.be.ok();
+ expect($redFruits.children()).to.have.length(1);
+ });
+
+ it('(element) : should insert the element and add selected element(s) as its child', function() {
+ var $redFruits = $('<div class="red-fruits"></div>');
+ $('.apple').wrap($redFruits[0]);
+
+ expect($fruits.children()[0]).to.be($redFruits[0]);
+ expect($redFruits.children()).to.have.length(1);
+ expect($redFruits.children()[0]).to.be($('.apple')[0]);
+ expect($fruits.children()[1]).to.be($('.orange')[0]);
+ });
+
+ it('(html) : should insert the markup and add selected element(s) as its child', function() {
+ $('.apple').wrap('<div class="red-fruits"> </div>');
+ expect($fruits.children().eq(0).hasClass('red-fruits')).to.be.ok();
+ expect($('.red-fruits').children().eq(0).hasClass('apple')).to.be.ok();
+ expect($fruits.children().eq(1).hasClass('orange')).to.be.ok();
+ expect($('.red-fruits').children()).to.have.length(1);
+ });
+
+ it('(html) : discards extraneous markup', function() {
+ $('.apple').wrap('<div></div><p></p>');
+ expect($('div')).to.have.length(1);
+ expect($('p')).to.have.length(0);
+ });
+
+ it('(selector) : wraps the content with a copy of the first matched element', function() {
+ var $oranges;
+
+ $('.apple').wrap('.orange, .pear');
+
+ $oranges = $('.orange');
+ expect($('.pear')).to.have.length(1);
+ expect($oranges).to.have.length(2);
+ expect($oranges.eq(0).parent()[0]).to.be($fruits[0]);
+ expect($oranges.eq(0).children()).to.have.length(1);
+ expect($oranges.eq(0).children()[0]).to.be($('.apple')[0]);
+ expect($('.apple').parent()[0]).to.be($oranges[0]);
+ expect($oranges.eq(1).children()).to.have.length(0);
+ });
+
+ it('(fn) : should invoke the provided function with the correct arguments and context', function() {
+ var $children = $fruits.children();
+ var args = [];
+ var thisValues = [];
+
+ $children.wrap(function() {
+ args.push(toArray(arguments));
+ thisValues.push(this);
+ });
+
+ expect(args).to.eql([
+ [0],
+ [1],
+ [2]
+ ]);
+ expect(thisValues).to.eql([
+ $children[0],
+ $children[1],
+ $children[2]
+ ]);
+ });
+
+ it('(fn) : should use the returned HTML to wrap each element', function() {
+ var $children = $fruits.children();
+ var tagNames = ['div', 'span', 'p'];
+
+ $children.wrap(function() {
+ return '<' + tagNames.shift() + '>';
+ });
+
+ expect($fruits.find('div')).to.have.length(1);
+ expect($fruits.find('div')[0]).to.be($fruits.children()[0]);
+ expect($fruits.find('.apple')).to.have.length(1);
+ expect($fruits.find('.apple').parent()[0]).to.be($fruits.find('div')[0]);
+
+ expect($fruits.find('span')).to.have.length(1);
+ expect($fruits.find('span')[0]).to.be($fruits.children()[1]);
+ expect($fruits.find('.orange')).to.have.length(1);
+ expect($fruits.find('.orange').parent()[0]).to.be($fruits.find('span')[0]);
+
+ expect($fruits.find('p')).to.have.length(1);
+ expect($fruits.find('p')[0]).to.be($fruits.children()[2]);
+ expect($fruits.find('.pear')).to.have.length(1);
+ expect($fruits.find('.pear').parent()[0]).to.be($fruits.find('p')[0]);
+ });
+
+ it('(fn) : should use the returned Cheerio object to wrap each element', function() {
+ var $children = $fruits.children();
+ var tagNames = ['span', 'p', 'div'];
+
+ $children.wrap(function() {
+ return $('<' + tagNames.shift() + '>');
+ });
+
+ expect($fruits.find('span')).to.have.length(1);
+ expect($fruits.find('span')[0]).to.be($fruits.children()[0]);
+ expect($fruits.find('.apple')).to.have.length(1);
+ expect($fruits.find('.apple').parent()[0]).to.be($fruits.find('span')[0]);
+
+ expect($fruits.find('p')).to.have.length(1);
+ expect($fruits.find('p')[0]).to.be($fruits.children()[1]);
+ expect($fruits.find('.orange')).to.have.length(1);
+ expect($fruits.find('.orange').parent()[0]).to.be($fruits.find('p')[0]);
+
+ expect($fruits.find('div')).to.have.length(1);
+ expect($fruits.find('div')[0]).to.be($fruits.children()[2]);
+ expect($fruits.find('.pear')).to.have.length(1);
+ expect($fruits.find('.pear').parent()[0]).to.be($fruits.find('div')[0]);
+ });
+
+ it('($(...)) : for each element it should add a wrapper elment and add the selected element as its child', function() {
+ var $fruitDecorator = $('<div class="fruit-decorator"></div>');
+ $('li').wrap($fruitDecorator);
+ expect($fruits.children().eq(0).hasClass('fruit-decorator')).to.be.ok();
+ expect($fruits.children().eq(0).children().eq(0).hasClass('apple')).to.be.ok();
+ expect($fruits.children().eq(1).hasClass('fruit-decorator')).to.be.ok();
+ expect($fruits.children().eq(1).children().eq(0).hasClass('orange')).to.be.ok();
+ expect($fruits.children().eq(2).hasClass('fruit-decorator')).to.be.ok();
+ expect($fruits.children().eq(2).children().eq(0).hasClass('pear')).to.be.ok();
+ });
+ });
+
+ describe('.append', function() {
+
+ it('() : should do nothing', function() {
+ expect($('#fruits').append()[0].tagName).to.equal('ul');
+ });
+
+ it('(html) : should add element as last child', function() {
+ $fruits.append('<li class="plum">Plum</li>');
+ expect($fruits.children().eq(3).hasClass('plum')).to.be.ok();
+ });
+
+ it('($(...)) : should add element as last child', function() {
+ var $plum = $('<li class="plum">Plum</li>');
+ $fruits.append($plum);
+ expect($fruits.children().eq(3).hasClass('plum')).to.be.ok();
+ });
+
+ it('(Node) : should add element as last child', function() {
+ var plum = $('<li class="plum">Plum</li>')[0];
+ $fruits.append(plum);
+ expect($fruits.children().eq(3).hasClass('plum')).to.be.ok();
+ });
+
+ it('(existing Node) : should remove node from previous location', function() {
+ var apple = $fruits.children()[0];
+ var $children;
+
+ expect($fruits.children()).to.have.length(3);
+ $fruits.append(apple);
+ $children = $fruits.children();
+
+ expect($children).to.have.length(3);
+ expect($children[0]).to.not.equal(apple);
+ expect($children[2]).to.equal(apple);
+ });
+
+ it('(existing Node) : should remove existing node from previous location', function() {
+ var apple = $fruits.children()[0];
+ var $children;
+ var $dest = $('<div></div>');
+
+ expect($fruits.children()).to.have.length(3);
+ $dest.append(apple);
+ $children = $fruits.children();
+
+ expect($children).to.have.length(2);
+ expect($children[0]).to.not.equal(apple);
+
+ expect($dest.children()).to.have.length(1);
+ expect($dest.children()[0]).to.equal(apple);
+ });
+
+ it('(existing Node) : should update original direct siblings', function() {
+ $('.pear').append($('.orange'));
+ expect($('.apple').next()[0]).to.be($('.pear')[0]);
+ expect($('.pear').prev()[0]).to.be($('.apple')[0]);
+ });
+
+ it('(existing Node) : should clone all but the last occurrence', function() {
+ var $originalApple = $('.apple');
+ var $apples;
+
+ $('.orange, .pear').append($originalApple);
+
+ $apples = $('.apple');
+ expect($apples).to.have.length(2);
+ expect($apples.eq(0).parent()[0]).to.be($('.orange')[0]);
+ expect($apples.eq(1).parent()[0]).to.be($('.pear')[0]);
+ expect($apples[1]).to.be($originalApple[0]);
+ });
+
+ it('(elem) : should NOP if removed', function() {
+ var $apple = $('.apple');
+
+ $apple.remove();
+ $fruits.append($apple);
+ expect($fruits.children().eq(2).hasClass('apple')).to.be.ok();
+ });
+
+ it('($(...), html) : should add multiple elements as last children', function() {
+ var $plum = $('<li class="plum">Plum</li>');
+ var grape = '<li class="grape">Grape</li>';
+ $fruits.append($plum, grape);
+ expect($fruits.children().eq(3).hasClass('plum')).to.be.ok();
+ expect($fruits.children().eq(4).hasClass('grape')).to.be.ok();
+ });
+
+ it('(Array) : should append all elements in the array', function() {
+ var more = $('<li class="plum">Plum</li><li class="grape">Grape</li>')
+ .get();
+ $fruits.append(more);
+ expect($fruits.children().eq(3).hasClass('plum')).to.be.ok();
+ expect($fruits.children().eq(4).hasClass('grape')).to.be.ok();
+ });
+
+ it('(fn) : should invoke the callback with the correct arguments and context', function() {
+ $fruits = $fruits.children();
+ var args = [];
+ var thisValues = [];
+
+ $fruits.append(function() {
+ args.push(toArray(arguments));
+ thisValues.push(this);
+ });
+
+ expect(args).to.eql([
+ [0, 'Apple'],
+ [1, 'Orange'],
+ [2, 'Pear']
+ ]);
+ expect(thisValues).to.eql([
+ $fruits[0],
+ $fruits[1],
+ $fruits[2]
+ ]);
+ });
+
+ it('(fn) : should add returned string as last child', function() {
+ $fruits = $fruits.children();
+ var $apple, $orange, $pear;
+
+ $fruits.append(function() {
+ return '<div class="first">';
+ });
+
+ $apple = $fruits.eq(0);
+ $orange = $fruits.eq(1);
+ $pear = $fruits.eq(2);
+
+ expect($apple.find('.first')[0]).to.equal($apple.contents()[1]);
+ expect($orange.find('.first')[0]).to.equal($orange.contents()[1]);
+ expect($pear.find('.first')[0]).to.equal($pear.contents()[1]);
+ });
+
+ it('(fn) : should add returned Cheerio object as last child', function() {
+ var $apple, $orange, $pear;
+ $fruits = $fruits.children();
+
+ $fruits.append(function() {
+ return $('<div class="second">');
+ });
+
+ $apple = $fruits.eq(0);
+ $orange = $fruits.eq(1);
+ $pear = $fruits.eq(2);
+
+ expect($apple.find('.second')[0]).to.equal($apple.contents()[1]);
+ expect($orange.find('.second')[0]).to.equal($orange.contents()[1]);
+ expect($pear.find('.second')[0]).to.equal($pear.contents()[1]);
+ });
+
+ it('(fn) : should add returned Node as last child', function() {
+ var $apple, $orange, $pear;
+ $fruits = $fruits.children();
+
+ $fruits.append(function() {
+ return $('<div class="third">')[0];
+ });
+
+ $apple = $fruits.eq(0);
+ $orange = $fruits.eq(1);
+ $pear = $fruits.eq(2);
+
+ expect($apple.find('.third')[0]).to.equal($apple.contents()[1]);
+ expect($orange.find('.third')[0]).to.equal($orange.contents()[1]);
+ expect($pear.find('.third')[0]).to.equal($pear.contents()[1]);
+ });
+
+ it('should maintain correct object state (Issue: #10)', function() {
+ var $obj = $('<div></div>')
+ .append('<div><div></div></div>')
+ .children()
+ .children()
+ .parent();
+ expect($obj).to.be.ok();
+ });
+
+ it('($(...)) : should remove from root element', function() {
+ var $plum = $('<li class="plum">Plum</li>');
+ var root = $plum[0].root;
+ expect(root).to.be.ok();
+
+ $fruits.append($plum);
+ expect($plum[0].root).to.not.be.ok();
+ expect(root.childNodes).to.not.contain($plum[0]);
+ });
+ });
+
+ describe('.prepend', function() {
+
+ it('() : should do nothing', function() {
+ expect($('#fruits').prepend()[0].tagName).to.equal('ul');
+ });
+
+ it('(html) : should add element as first child', function() {
+ $fruits.prepend('<li class="plum">Plum</li>');
+ expect($fruits.children().eq(0).hasClass('plum')).to.be.ok();
+ });
+
+ it('($(...)) : should add element as first child', function() {
+ var $plum = $('<li class="plum">Plum</li>');
+ $fruits.prepend($plum);
+ expect($fruits.children().eq(0).hasClass('plum')).to.be.ok();
+ });
+
+ it('(Node) : should add node as first child', function() {
+ var plum = $('<li class="plum">Plum</li>')[0];
+ $fruits.prepend(plum);
+ expect($fruits.children().eq(0).hasClass('plum')).to.be.ok();
+ });
+
+ it('(existing Node) : should remove existing nodes from previous locations', function() {
+ var pear = $fruits.children()[2];
+ var $children;
+
+ expect($fruits.children()).to.have.length(3);
+ $fruits.prepend(pear);
+ $children = $fruits.children();
+
+ expect($children).to.have.length(3);
+ expect($children[2]).to.not.equal(pear);
+ expect($children[0]).to.equal(pear);
+ });
+
+ it('(existing Node) : should update original direct siblings', function() {
+ $('.pear').prepend($('.orange'));
+ expect($('.apple').next()[0]).to.be($('.pear')[0]);
+ expect($('.pear').prev()[0]).to.be($('.apple')[0]);
+ });
+
+ it('(existing Node) : should clone all but the last occurrence', function() {
+ var $originalApple = $('.apple');
+ var $apples;
+
+ $('.orange, .pear').prepend($originalApple);
+
+ $apples = $('.apple');
+ expect($apples).to.have.length(2);
+ expect($apples.eq(0).parent()[0]).to.be($('.orange')[0]);
+ expect($apples.eq(1).parent()[0]).to.be($('.pear')[0]);
+ expect($apples[1]).to.be($originalApple[0]);
+ });
+
+ it('(elem) : should handle if removed', function() {
+ var $apple = $('.apple');
+
+ $apple.remove();
+ $fruits.prepend($apple);
+ expect($fruits.children().eq(0).hasClass('apple')).to.be.ok();
+ });
+
+ it('(Array) : should add all elements in the array as inital children', function() {
+ var more = $('<li class="plum">Plum</li><li class="grape">Grape</li>')
+ .get();
+ $fruits.prepend(more);
+ expect($fruits.children().eq(0).hasClass('plum')).to.be.ok();
+ expect($fruits.children().eq(1).hasClass('grape')).to.be.ok();
+ });
+
+ it('(html, $(...), html) : should add multiple elements as first children', function() {
+ var $plum = $('<li class="plum">Plum</li>');
+ var grape = '<li class="grape">Grape</li>';
+ $fruits.prepend($plum, grape);
+ expect($fruits.children().eq(0).hasClass('plum')).to.be.ok();
+ expect($fruits.children().eq(1).hasClass('grape')).to.be.ok();
+ });
+
+ it('(fn) : should invoke the callback with the correct arguments and context', function() {
+ var args = [];
+ var thisValues = [];
+ $fruits = $fruits.children();
+
+ $fruits.prepend(function() {
+ args.push(toArray(arguments));
+ thisValues.push(this);
+ });
+
+ expect(args).to.eql([
+ [0, 'Apple'],
+ [1, 'Orange'],
+ [2, 'Pear']
+ ]);
+ expect(thisValues).to.eql([
+ $fruits[0],
+ $fruits[1],
+ $fruits[2]
+ ]);
+ });
+
+ it('(fn) : should add returned string as first child', function() {
+ var $apple, $orange, $pear;
+ $fruits = $fruits.children();
+
+ $fruits.prepend(function() {
+ return '<div class="first">';
+ });
+
+ $apple = $fruits.eq(0);
+ $orange = $fruits.eq(1);
+ $pear = $fruits.eq(2);
+
+ expect($apple.find('.first')[0]).to.equal($apple.contents()[0]);
+ expect($orange.find('.first')[0]).to.equal($orange.contents()[0]);
+ expect($pear.find('.first')[0]).to.equal($pear.contents()[0]);
+ });
+
+ it('(fn) : should add returned Cheerio object as first child', function() {
+ var $apple, $orange, $pear;
+ $fruits = $fruits.children();
+
+ $fruits.prepend(function() {
+ return $('<div class="second">');
+ });
+
+ $apple = $fruits.eq(0);
+ $orange = $fruits.eq(1);
+ $pear = $fruits.eq(2);
+
+ expect($apple.find('.second')[0]).to.equal($apple.contents()[0]);
+ expect($orange.find('.second')[0]).to.equal($orange.contents()[0]);
+ expect($pear.find('.second')[0]).to.equal($pear.contents()[0]);
+ });
+
+ it('(fn) : should add returned Node as first child', function() {
+ var $apple, $orange, $pear;
+ $fruits = $fruits.children();
+
+ $fruits.prepend(function() {
+ return $('<div class="third">')[0];
+ });
+
+ $apple = $fruits.eq(0);
+ $orange = $fruits.eq(1);
+ $pear = $fruits.eq(2);
+
+ expect($apple.find('.third')[0]).to.equal($apple.contents()[0]);
+ expect($orange.find('.third')[0]).to.equal($orange.contents()[0]);
+ expect($pear.find('.third')[0]).to.equal($pear.contents()[0]);
+ });
+
+
+ it('($(...)) : should remove from root element', function() {
+ var $plum = $('<li class="plum">Plum</li>');
+ var root = $plum[0].root;
+ expect(root).to.be.ok();
+
+ $fruits.prepend($plum);
+ expect($plum[0].root).to.not.be.ok();
+ expect(root.childNodes).to.not.contain($plum[0]);
+ });
+ });
+
+ describe('.appendTo', function() {
+
+ it('(html) : should add element as last child', function() {
+ var $plum = $('<li class="plum">Plum</li>').appendTo(fruits);
+ expect($plum.parent().children().eq(3).hasClass('plum')).to.be.ok();
+ });
+
+ it('($(...)) : should add element as last child', function() {
+ $('<li class="plum">Plum</li>').appendTo($fruits);
+ expect($fruits.children().eq(3).hasClass('plum')).to.be.ok();
+ });
+
+ it('(Node) : should add element as last child', function() {
+ $('<li class="plum">Plum</li>').appendTo($fruits[0]);
+ expect($fruits.children().eq(3).hasClass('plum')).to.be.ok();
+ });
+
+ it('(selector) : should add element as last child', function() {
+ $('<li class="plum">Plum</li>').appendTo('#fruits');
+ expect($fruits.children().eq(3).hasClass('plum')).to.be.ok();
+ });
+
+ it('(Array) : should add element as last child of all elements in the array', function() {
+ var $multiple = $('<ul><li>Apple</li></ul><ul><li>Orange</li></ul>');
+ $('<li class="plum">Plum</li>').appendTo($multiple.get());
+ expect($multiple.first().children().eq(1).hasClass('plum')).to.be.ok();
+ expect($multiple.last().children().eq(1).hasClass('plum')).to.be.ok();
+ });
+ });
+
+ describe('.prependTo', function() {
+
+ it('(html) : should add element as first child', function() {
+ var $plum = $('<li class="plum">Plum</li>').prependTo(fruits);
+ expect($plum.parent().children().eq(0).hasClass('plum')).to.be.ok();
+ });
+
+ it('($(...)) : should add element as first child', function() {
+ $('<li class="plum">Plum</li>').prependTo($fruits);
+ expect($fruits.children().eq(0).hasClass('plum')).to.be.ok();
+ });
+
+ it('(Node) : should add node as first child', function() {
+ $('<li class="plum">Plum</li>').prependTo($fruits[0]);
+ expect($fruits.children().eq(0).hasClass('plum')).to.be.ok();
+ });
+
+ it('(selector) : should add element as first child', function() {
+ $('<li class="plum">Plum</li>').prependTo('#fruits');
+ expect($fruits.children().eq(0).hasClass('plum')).to.be.ok();
+ });
+
+ it('(Array) : should add element as first child of all elements in the array', function() {
+ var $multiple = $('<ul><li>Apple</li></ul><ul><li>Orange</li></ul>');
+ $('<li class="plum">Plum</li>').prependTo($multiple.get());
+ expect($multiple.first().children().eq(0).hasClass('plum')).to.be.ok();
+ expect($multiple.last().children().eq(0).hasClass('plum')).to.be.ok();
+ });
+ });
+
+ describe('.after', function() {
+
+ it('() : should do nothing', function() {
+ expect($('#fruits').after()[0].tagName).to.equal('ul');
+ });
+
+ it('(html) : should add element as next sibling', function() {
+ var grape = '<li class="grape">Grape</li>';
+ $('.apple').after(grape);
+ expect($('.apple').next().hasClass('grape')).to.be.ok();
+ });
+
+ it('(Array) : should add all elements in the array as next sibling', function() {
+ var more = $('<li class="plum">Plum</li><li class="grape">Grape</li>')
+ .get();
+ $('.apple').after(more);
+ expect($fruits.children().eq(1).hasClass('plum')).to.be.ok();
+ expect($fruits.children().eq(2).hasClass('grape')).to.be.ok();
+ });
+
+ it('($(...)) : should add element as next sibling', function() {
+ var $plum = $('<li class="plum">Plum</li>');
+ $('.apple').after($plum);
+ expect($('.apple').next().hasClass('plum')).to.be.ok();
+ });
+
+ it('(Node) : should add element as next sibling', function() {
+ var plum = $('<li class="plum">Plum</li>')[0];
+ $('.apple').after(plum);
+ expect($('.apple').next().hasClass('plum')).to.be.ok();
+ });
+
+ it('(existing Node) : should remove existing nodes from previous locations', function() {
+ var pear = $fruits.children()[2];
+ var $children;
+
+ $('.apple').after(pear);
+
+ $children = $fruits.children();
+ expect($children).to.have.length(3);
+ expect($children[1]).to.be(pear);
+ });
+
+ it('(existing Node) : should update original direct siblings', function() {
+ $('.pear').after($('.orange'));
+ expect($('.apple').next()[0]).to.be($('.pear')[0]);
+ expect($('.pear').prev()[0]).to.be($('.apple')[0]);
+ });
+
+ it('(existing Node) : should clone all but the last occurrence', function() {
+ var $originalApple = $('.apple');
+ $('.orange, .pear').after($originalApple);
+
+ expect($('.apple')).to.have.length(2);
+ expect($('.apple').eq(0).prev()[0]).to.be($('.orange')[0]);
+ expect($('.apple').eq(0).next()[0]).to.be($('.pear')[0]);
+ expect($('.apple').eq(1).prev()[0]).to.be($('.pear')[0]);
+ expect($('.apple').eq(1).next()).to.have.length(0);
+ expect($('.apple')[0]).to.not.eql($originalApple[0]);
+ expect($('.apple')[1]).to.eql($originalApple[0]);
+ });
+
+ it('(elem) : should handle if removed', function() {
+ var $apple = $('.apple');
+ var $plum = $('<li class="plum">Plum</li>');
+
+ $apple.remove();
+ $apple.after($plum);
+ expect($plum.prev()).to.be.empty();
+ });
+
+ it('($(...), html) : should add multiple elements as next siblings', function() {
+ var $plum = $('<li class="plum">Plum</li>');
+ var grape = '<li class="grape">Grape</li>';
+ $('.apple').after($plum, grape);
+ expect($('.apple').next().hasClass('plum')).to.be.ok();
+ expect($('.plum').next().hasClass('grape')).to.be.ok();
+ });
+
+ it('(fn) : should invoke the callback with the correct arguments and context', function() {
+ var args = [];
+ var thisValues = [];
+ $fruits = $fruits.children();
+
+ $fruits.after(function() {
+ args.push(toArray(arguments));
+ thisValues.push(this);
+ });
+
+ expect(args).to.eql([[0, 'Apple'], [1, 'Orange'], [2, 'Pear']]);
+ expect(thisValues).to.eql([
+ $fruits[0],
+ $fruits[1],
+ $fruits[2]
+ ]);
+ });
+
+ it('(fn) : should add returned string as next sibling', function() {
+ $fruits = $fruits.children();
+
+ $fruits.after(function() {
+ return '<li class="first">';
+ });
+
+ expect($('.first')[0]).to.equal($('#fruits').contents()[1]);
+ expect($('.first')[1]).to.equal($('#fruits').contents()[3]);
+ expect($('.first')[2]).to.equal($('#fruits').contents()[5]);
+ });
+
+ it('(fn) : should add returned Cheerio object as next sibling', function() {
+ $fruits = $fruits.children();
+
+ $fruits.after(function() {
+ return $('<li class="second">');
+ });
+
+ expect($('.second')[0]).to.equal($('#fruits').contents()[1]);
+ expect($('.second')[1]).to.equal($('#fruits').contents()[3]);
+ expect($('.second')[2]).to.equal($('#fruits').contents()[5]);
+ });
+
+ it('(fn) : should add returned element as next sibling', function() {
+ $fruits = $fruits.children();
+
+ $fruits.after(function() {
+ return $('<li class="third">')[0];
+ });
+
+ expect($('.third')[0]).to.equal($('#fruits').contents()[1]);
+ expect($('.third')[1]).to.equal($('#fruits').contents()[3]);
+ expect($('.third')[2]).to.equal($('#fruits').contents()[5]);
+ });
+
+ it('($(...)) : should remove from root element', function() {
+ var $plum = $('<li class="plum">Plum</li>');
+ var root = $plum[0].root;
+ expect(root).to.be.ok();
+
+ $fruits.after($plum);
+ expect($plum[0].root).to.not.be.ok();
+ expect(root.childNodes).to.not.contain($plum[0]);
+ });
+ });
+
+ describe('.insertAfter', function() {
+
+ it('(selector) : should create element and add as next sibling', function() {
+ var grape = $('<li class="grape">Grape</li>');
+ grape.insertAfter('.apple');
+ expect($('.apple').next().hasClass('grape')).to.be.ok();
+ });
+
+ it('(selector) : should create element and add as next sibling of multiple elements', function() {
+ var grape = $('<li class="grape">Grape</li>');
+ grape.insertAfter('.apple, .pear');
+ expect($('.apple').next().hasClass('grape')).to.be.ok();
+ expect($('.pear').next().hasClass('grape')).to.be.ok();
+ });
+
+ it('($(...)) : should create element and add as next sibling', function() {
+ var grape = $('<li class="grape">Grape</li>');
+ grape.insertAfter($('.apple'));
+ expect($('.apple').next().hasClass('grape')).to.be.ok();
+ });
+
+ it('($(...)) : should create element and add as next sibling of multiple elements', function() {
+ var grape = $('<li class="grape">Grape</li>');
+ grape.insertAfter($('.apple, .pear'));
+ expect($('.apple').next().hasClass('grape')).to.be.ok();
+ expect($('.pear').next().hasClass('grape')).to.be.ok();
+ });
+
+ it('($(...)) : should create all elements in the array and add as next siblings', function() {
+ var more = $('<li class="plum">Plum</li><li class="grape">Grape</li>');
+ more.insertAfter($('.apple'));
+ expect($fruits.children().eq(0).hasClass('apple')).to.be.ok();
+ expect($fruits.children().eq(1).hasClass('plum')).to.be.ok();
+ expect($fruits.children().eq(2).hasClass('grape')).to.be.ok();
+ });
+
+ it('(existing Node) : should remove existing nodes from previous locations', function() {
+ $('.orange').insertAfter('.pear');
+ expect($fruits.children().eq(1).hasClass('orange')).to.not.be.ok();
+ expect($fruits.children().length).to.be(3);
+ expect($('.orange').length).to.be(1);
+ });
+
+ it('(existing Node) : should update original direct siblings', function() {
+ $('.orange').insertAfter('.pear');
+ expect($('.apple').next().hasClass('pear')).to.be.ok();
+ expect($('.pear').prev().hasClass('apple')).to.be.ok();
+ expect($('.pear').next().hasClass('orange')).to.be.ok();
+ expect($('.orange').next()).to.be.empty();
+ });
+
+ it('(existing Node) : should update original direct siblings of multiple elements', function() {
+ $('.apple').insertAfter('.orange, .pear');
+ expect($('.orange').prev()).to.be.empty();
+ expect($('.orange').next().hasClass('apple')).to.be.ok();
+ expect($('.pear').next().hasClass('apple')).to.be.ok();
+ expect($('.pear').prev().hasClass('apple')).to.be.ok();
+ expect($fruits.children().length).to.be(4);
+ var apples = $('.apple');
+ expect(apples.length).to.be(2);
+ expect(apples.eq(0).prev().hasClass('orange')).to.be.ok();
+ expect(apples.eq(1).prev().hasClass('pear')).to.be.ok();
+ });
+
+ it('(elem) : should handle if removed', function() {
+ var $apple = $('.apple');
+ var $plum = $('<li class="plum">Plum</li>');
+ $apple.remove();
+ $plum.insertAfter($apple);
+ expect($plum.prev()).to.be.empty();
+ });
+
+ it('(single) should return the new element for chaining', function() {
+ var $grape = $('<li class="grape">Grape</li>').insertAfter('.apple');
+ expect($grape.cheerio).to.be.ok();
+ expect($grape.each).to.be.ok();
+ expect($grape.length).to.be(1);
+ expect($grape.hasClass('grape')).to.be.ok();
+ });
+
+ it('(single) should return the new elements for chaining', function() {
+ var $purple = $('<li class="grape">Grape</li><li class="plum">Plum</li>').insertAfter('.apple');
+ expect($purple.cheerio).to.be.ok();
+ expect($purple.each).to.be.ok();
+ expect($purple.length).to.be(2);
+ expect($purple.eq(0).hasClass('grape')).to.be.ok();
+ expect($purple.eq(1).hasClass('plum')).to.be.ok();
+ });
+
+ it('(multiple) should return the new elements for chaining', function() {
+ var $purple = $('<li class="grape">Grape</li><li class="plum">Plum</li>').insertAfter('.apple, .pear');
+ expect($purple.cheerio).to.be.ok();
+ expect($purple.each).to.be.ok();
+ expect($purple.length).to.be(4);
+ expect($purple.eq(0).hasClass('grape')).to.be.ok();
+ expect($purple.eq(1).hasClass('plum')).to.be.ok();
+ expect($purple.eq(2).hasClass('grape')).to.be.ok();
+ expect($purple.eq(3).hasClass('plum')).to.be.ok();
+ });
+
+ it('(single) should return the existing element for chaining', function() {
+ var $pear = $('.pear').insertAfter('.apple');
+ expect($pear.cheerio).to.be.ok();
+ expect($pear.each).to.be.ok();
+ expect($pear.length).to.be(1);
+ expect($pear.hasClass('pear')).to.be.ok();
+ });
+
+ it('(single) should return the existing elements for chaining', function() {
+ var $things = $('.orange, .apple').insertAfter('.pear');
+ expect($things.cheerio).to.be.ok();
+ expect($things.each).to.be.ok();
+ expect($things.length).to.be(2);
+ expect($things.eq(0).hasClass('apple')).to.be.ok();
+ expect($things.eq(1).hasClass('orange')).to.be.ok();
+ });
+
+ it('(multiple) should return the existing elements for chaining', function() {
+ $('<li class="grape">Grape</li>').insertAfter('.apple');
+ var $things = $('.orange, .apple').insertAfter('.pear, .grape');
+ expect($things.cheerio).to.be.ok();
+ expect($things.each).to.be.ok();
+ expect($things.length).to.be(4);
+ expect($things.eq(0).hasClass('apple')).to.be.ok();
+ expect($things.eq(1).hasClass('orange')).to.be.ok();
+ expect($things.eq(2).hasClass('apple')).to.be.ok();
+ expect($things.eq(3).hasClass('orange')).to.be.ok();
+ });
+
+ });
+
+ describe('.before', function() {
+
+ it('() : should do nothing', function() {
+ expect($('#fruits').before()[0].tagName).to.equal('ul');
+ });
+
+ it('(html) : should add element as previous sibling', function() {
+ var grape = '<li class="grape">Grape</li>';
+ $('.apple').before(grape);
+ expect($('.apple').prev().hasClass('grape')).to.be.ok();
+ });
+
+ it('($(...)) : should add element as previous sibling', function() {
+ var $plum = $('<li class="plum">Plum</li>');
+ $('.apple').before($plum);
+ expect($('.apple').prev().hasClass('plum')).to.be.ok();
+ });
+
+ it('(Node) : should add element as previous sibling', function() {
+ var plum = $('<li class="plum">Plum</li>')[0];
+ $('.apple').before(plum);
+ expect($('.apple').prev().hasClass('plum')).to.be.ok();
+ });
+
+ it('(existing Node) : should remove existing nodes from previous locations', function() {
+ var pear = $fruits.children()[2];
+ var $children;
+
+ $('.apple').before(pear);
+
+ $children = $fruits.children();
+ expect($children).to.have.length(3);
+ expect($children[0]).to.be(pear);
+ });
+
+ it('(existing Node) : should update original direct siblings', function() {
+ $('.apple').before($('.orange'));
+ expect($('.apple').next()[0]).to.be($('.pear')[0]);
+ expect($('.pear').prev()[0]).to.be($('.apple')[0]);
+ });
+
+ it('(existing Node) : should clone all but the last occurrence', function() {
+ var $originalPear = $('.pear');
+ $('.apple, .orange').before($originalPear);
+
+ expect($('.pear')).to.have.length(2);
+ expect($('.pear').eq(0).prev()).to.have.length(0);
+ expect($('.pear').eq(0).next()[0]).to.be($('.apple')[0]);
+ expect($('.pear').eq(1).prev()[0]).to.be($('.apple')[0]);
+ expect($('.pear').eq(1).next()[0]).to.be($('.orange')[0]);
+ expect($('.pear')[0]).to.not.eql($originalPear[0]);
+ expect($('.pear')[1]).to.eql($originalPear[0]);
+ });
+
+ it('(elem) : should handle if removed', function() {
+ var $apple = $('.apple');
+ var $plum = $('<li class="plum">Plum</li>');
+
+ $apple.remove();
+ $apple.before($plum);
+ expect($plum.next()).to.be.empty();
+ });
+
+ it('(Array) : should add all elements in the array as previous sibling', function() {
+ var more = $('<li class="plum">Plum</li><li class="grape">Grape</li>')
+ .get();
+ $('.apple').before(more);
+ expect($fruits.children().eq(0).hasClass('plum')).to.be.ok();
+ expect($fruits.children().eq(1).hasClass('grape')).to.be.ok();
+ });
+
+ it('($(...), html) : should add multiple elements as previous siblings', function() {
+ var $plum = $('<li class="plum">Plum</li>');
+ var grape = '<li class="grape">Grape</li>';
+ $('.apple').before($plum, grape);
+ expect($('.apple').prev().hasClass('grape')).to.be.ok();
+ expect($('.grape').prev().hasClass('plum')).to.be.ok();
+ });
+
+ it('(fn) : should invoke the callback with the correct arguments and context', function() {
+ var args = [];
+ var thisValues = [];
+ $fruits = $fruits.children();
+
+ $fruits.before(function() {
+ args.push(toArray(arguments));
+ thisValues.push(this);
+ });
+
+ expect(args).to.eql([[0, 'Apple'], [1, 'Orange'], [2, 'Pear']]);
+ expect(thisValues).to.eql([
+ $fruits[0],
+ $fruits[1],
+ $fruits[2]
+ ]);
+ });
+
+ it('(fn) : should add returned string as previous sibling', function() {
+ $fruits = $fruits.children();
+
+ $fruits.before(function() {
+ return '<li class="first">';
+ });
+
+ expect($('.first')[0]).to.equal($('#fruits').contents()[0]);
+ expect($('.first')[1]).to.equal($('#fruits').contents()[2]);
+ expect($('.first')[2]).to.equal($('#fruits').contents()[4]);
+ });
+
+ it('(fn) : should add returned Cheerio object as previous sibling', function() {
+ $fruits = $fruits.children();
+
+ $fruits.before(function() {
+ return $('<li class="second">');
+ });
+
+ expect($('.second')[0]).to.equal($('#fruits').contents()[0]);
+ expect($('.second')[1]).to.equal($('#fruits').contents()[2]);
+ expect($('.second')[2]).to.equal($('#fruits').contents()[4]);
+ });
+
+ it('(fn) : should add returned Node as previous sibling', function() {
+ $fruits = $fruits.children();
+
+ $fruits.before(function() {
+ return $('<li class="third">')[0];
+ });
+
+ expect($('.third')[0]).to.equal($('#fruits').contents()[0]);
+ expect($('.third')[1]).to.equal($('#fruits').contents()[2]);
+ expect($('.third')[2]).to.equal($('#fruits').contents()[4]);
+ });
+
+ it('($(...)) : should remove from root element', function() {
+ var $plum = $('<li class="plum">Plum</li>');
+ var root = $plum[0].root;
+ expect(root).to.be.ok();
+
+ $fruits.before($plum);
+ expect($plum[0].root).to.not.be.ok();
+ expect(root.childNodes).to.not.contain($plum[0]);
+ });
+ });
+
+ describe('.insertBefore', function() {
+
+ it('(selector) : should create element and add as prev sibling', function() {
+ var grape = $('<li class="grape">Grape</li>');
+ grape.insertBefore('.apple');
+ expect($('.apple').prev().hasClass('grape')).to.be.ok();
+ });
+
+ it('(selector) : should create element and add as prev sibling of multiple elements', function() {
+ var grape = $('<li class="grape">Grape</li>');
+ grape.insertBefore('.apple, .pear');
+ expect($('.apple').prev().hasClass('grape')).to.be.ok();
+ expect($('.pear').prev().hasClass('grape')).to.be.ok();
+ });
+
+ it('($(...)) : should create element and add as prev sibling', function() {
+ var grape = $('<li class="grape">Grape</li>');
+ grape.insertBefore($('.apple'));
+ expect($('.apple').prev().hasClass('grape')).to.be.ok();
+ });
+
+ it('($(...)) : should create element and add as next sibling of multiple elements', function() {
+ var grape = $('<li class="grape">Grape</li>');
+ grape.insertBefore($('.apple, .pear'));
+ expect($('.apple').prev().hasClass('grape')).to.be.ok();
+ expect($('.pear').prev().hasClass('grape')).to.be.ok();
+ });
+
+ it('($(...)) : should create all elements in the array and add as prev siblings', function() {
+ var more = $('<li class="plum">Plum</li><li class="grape">Grape</li>');
+ more.insertBefore($('.apple'));
+ expect($fruits.children().eq(0).hasClass('plum')).to.be.ok();
+ expect($fruits.children().eq(1).hasClass('grape')).to.be.ok();
+ expect($fruits.children().eq(2).hasClass('apple')).to.be.ok();
+ });
+
+ it('(existing Node) : should remove existing nodes from previous locations', function() {
+ $('.pear').insertBefore('.apple');
+ expect($fruits.children().eq(2).hasClass('pear')).to.not.be.ok();
+ expect($fruits.children().length).to.be(3);
+ expect($('.pear').length).to.be(1);
+ });
+
+ it('(existing Node) : should update original direct siblings', function() {
+ $('.pear').insertBefore('.apple');
+ expect($('.apple').prev().hasClass('pear')).to.be.ok();
+ expect($('.apple').next().hasClass('orange')).to.be.ok();
+ expect($('.pear').next().hasClass('apple')).to.be.ok();
+ expect($('.pear').prev()).to.be.empty();
+ });
+
+ it('(existing Node) : should update original direct siblings of multiple elements', function() {
+ $('.pear').insertBefore('.apple, .orange');
+ expect($('.apple').prev().hasClass('pear')).to.be.ok();
+ expect($('.apple').next().hasClass('pear')).to.be.ok();
+ expect($('.orange').prev().hasClass('pear')).to.be.ok();
+ expect($('.orange').next()).to.be.empty();
+ expect($fruits.children().length).to.be(4);
+ var pears = $('.pear');
+ expect(pears.length).to.be(2);
+ expect(pears.eq(0).next().hasClass('apple')).to.be.ok();
+ expect(pears.eq(1).next().hasClass('orange')).to.be.ok();
+ });
+
+ it('(elem) : should handle if removed', function() {
+ var $apple = $('.apple');
+ var $plum = $('<li class="plum">Plum</li>');
+
+ $apple.remove();
+ $plum.insertBefore($apple);
+ expect($plum.next()).to.be.empty();
+ });
+
+ it('(single) should return the new element for chaining', function() {
+ var $grape = $('<li class="grape">Grape</li>').insertBefore('.apple');
+ expect($grape.cheerio).to.be.ok();
+ expect($grape.each).to.be.ok();
+ expect($grape.length).to.be(1);
+ expect($grape.hasClass('grape')).to.be.ok();
+ });
+
+ it('(single) should return the new elements for chaining', function() {
+ var $purple = $('<li class="grape">Grape</li><li class="plum">Plum</li>').insertBefore('.apple');
+ expect($purple.cheerio).to.be.ok();
+ expect($purple.each).to.be.ok();
+ expect($purple.length).to.be(2);
+ expect($purple.eq(0).hasClass('grape')).to.be.ok();
+ expect($purple.eq(1).hasClass('plum')).to.be.ok();
+ });
+
+ it('(multiple) should return the new elements for chaining', function() {
+ var $purple = $('<li class="grape">Grape</li><li class="plum">Plum</li>').insertBefore('.apple, .pear');
+ expect($purple.cheerio).to.be.ok();
+ expect($purple.each).to.be.ok();
+ expect($purple.length).to.be(4);
+ expect($purple.eq(0).hasClass('grape')).to.be.ok();
+ expect($purple.eq(1).hasClass('plum')).to.be.ok();
+ expect($purple.eq(2).hasClass('grape')).to.be.ok();
+ expect($purple.eq(3).hasClass('plum')).to.be.ok();
+ });
+
+ it('(single) should return the existing element for chaining', function() {
+ var $orange = $('.orange').insertBefore('.apple');
+ expect($orange.cheerio).to.be.ok();
+ expect($orange.each).to.be.ok();
+ expect($orange.length).to.be(1);
+ expect($orange.hasClass('orange')).to.be.ok();
+ });
+
+ it('(single) should return the existing elements for chaining', function() {
+ var $things = $('.orange, .pear').insertBefore('.apple');
+ expect($things.cheerio).to.be.ok();
+ expect($things.each).to.be.ok();
+ expect($things.length).to.be(2);
+ expect($things.eq(0).hasClass('orange')).to.be.ok();
+ expect($things.eq(1).hasClass('pear')).to.be.ok();
+ });
+
+ it('(multiple) should return the existing elements for chaining', function() {
+ $('<li class="grape">Grape</li>').insertBefore('.apple');
+ var $things = $('.orange, .apple').insertBefore('.pear, .grape');
+ expect($things.cheerio).to.be.ok();
+ expect($things.each).to.be.ok();
+ expect($things.length).to.be(4);
+ expect($things.eq(0).hasClass('apple')).to.be.ok();
+ expect($things.eq(1).hasClass('orange')).to.be.ok();
+ expect($things.eq(2).hasClass('apple')).to.be.ok();
+ expect($things.eq(3).hasClass('orange')).to.be.ok();
+ });
+
+ });
+
+ describe('.remove', function() {
+
+ it('() : should remove selected elements', function() {
+ $('.apple').remove();
+ expect($fruits.find('.apple')).to.have.length(0);
+ });
+
+ it('() : should be reentrant', function() {
+ var $apple = $('.apple');
+ $apple.remove();
+ $apple.remove();
+ expect($fruits.find('.apple')).to.have.length(0);
+ });
+
+ it('(selector) : should remove matching selected elements', function() {
+ $('li').remove('.apple');
+ expect($fruits.find('.apple')).to.have.length(0);
+ });
+
+ it('($(...)) : should remove from root element', function() {
+ var $plum = $('<li class="plum">Plum</li>');
+ var root = $plum[0].root;
+ expect(root).to.be.ok();
+
+ $plum.remove();
+ expect($plum[0].root).to.not.be.ok();
+ expect(root.childNodes).to.not.contain($plum[0]);
+ });
+ });
+
+ describe('.replaceWith', function() {
+
+ it('(elem) : should replace one <li> tag with another', function() {
+ var $plum = $('<li class="plum">Plum</li>');
+ $('.pear').replaceWith($plum);
+ expect($('.orange').next().hasClass('plum')).to.be.ok();
+ expect($('.orange').next().html()).to.equal('Plum');
+ });
+
+ it('(Array) : should replace one <li> tag with the elements in the array', function() {
+ var more = $('<li class="plum">Plum</li><li class="grape">Grape</li>')
+ .get();
+ $('.pear').replaceWith(more);
+
+ expect($fruits.children().eq(2).hasClass('plum')).to.be.ok();
+ expect($fruits.children().eq(3).hasClass('grape')).to.be.ok();
+ expect($fruits.children()).to.have.length(4);
+ });
+
+ it('(Node) : should replace the selected element with given node', function() {
+ var $src = $('<h2>hi <span>there</span></h2>');
+ var $new = $('<ul></ul>');
+ var $replaced = $src.find('span').replaceWith($new[0]);
+ expect($new[0].parentNode).to.equal($src[0]);
+ expect($replaced[0].parentNode).to.equal(null);
+ expect($.html($src)).to.equal('<h2>hi <ul></ul></h2>');
+ });
+
+ it('(existing element) : should remove element from its previous location', function() {
+ $('.pear').replaceWith($('.apple'));
+ expect($fruits.children()).to.have.length(2);
+ expect($fruits.children()[0]).to.equal($('.orange')[0]);
+ expect($fruits.children()[1]).to.equal($('.apple')[0]);
+ });
+
+ it('(elem) : should NOP if removed', function() {
+ var $pear = $('.pear');
+ var $plum = $('<li class="plum">Plum</li>');
+
+ $pear.remove();
+ $pear.replaceWith($plum);
+ expect($('.orange').next().hasClass('plum')).to.not.be.ok();
+ });
+
+ it('(elem) : should replace the single selected element with given element', function() {
+ var $src = $('<h2>hi <span>there</span></h2>');
+ var $new = $('<div>here</div>');
+ var $replaced = $src.find('span').replaceWith($new);
+ expect($new[0].parentNode).to.equal($src[0]);
+ expect($replaced[0].parentNode).to.equal(null);
+ expect($.html($src)).to.equal('<h2>hi <div>here</div></h2>');
+ });
+
+ it('(str) : should accept strings', function() {
+ var $src = $('<h2>hi <span>there</span></h2>');
+ var newStr = '<div>here</div>';
+ var $replaced = $src.find('span').replaceWith(newStr);
+ expect($replaced[0].parentNode).to.equal(null);
+ expect($.html($src)).to.equal('<h2>hi <div>here</div></h2>');
+ });
+
+ it('(str) : should replace all selected elements', function() {
+ var $src = $('<b>a<br>b<br>c<br>d</b>');
+ var $replaced = $src.find('br').replaceWith(' ');
+ expect($replaced[0].parentNode).to.equal(null);
+ expect($.html($src)).to.equal('<b>a b c d</b>');
+ });
+
+ it('(fn) : should invoke the callback with the correct argument and context', function() {
+ var origChildren = $fruits.children().get();
+ var args = [];
+ var thisValues = [];
+
+ $fruits.children().replaceWith(function() {
+ args.push(toArray(arguments));
+ thisValues.push(this);
+ return '<li class="first">';
+ });
+
+ expect(args).to.eql([
+ [0, origChildren[0]],
+ [1, origChildren[1]],
+ [2, origChildren[2]]
+ ]);
+ expect(thisValues).to.eql([
+ origChildren[0],
+ origChildren[1],
+ origChildren[2]
+ ]);
+ });
+
+ it('(fn) : should replace the selected element with the returned string', function() {
+ $fruits.children().replaceWith(function() {
+ return '<li class="first">';
+ });
+
+ expect($fruits.find('.first')).to.have.length(3);
+ });
+
+ it('(fn) : should replace the selected element with the returned Cheerio object', function() {
+ $fruits.children().replaceWith(function() {
+ return $('<li class="second">');
+ });
+
+ expect($fruits.find('.second')).to.have.length(3);
+ });
+
+ it('(fn) : should replace the selected element with the returned node', function() {
+ $fruits.children().replaceWith(function() {
+ return $('<li class="third">')[0];
+ });
+
+ expect($fruits.find('.third')).to.have.length(3);
+ });
+
+ it('($(...)) : should remove from root element', function() {
+ var $plum = $('<li class="plum">Plum</li>');
+ var root = $plum[0].root;
+ expect(root).to.be.ok();
+
+ $fruits.children().replaceWith($plum);
+ expect($plum[0].root).to.not.be.ok();
+ expect(root.childNodes).to.not.contain($plum[0]);
+ });
+ });
+
+ describe('.empty', function() {
+ it('() : should remove all children from selected elements', function() {
+ expect($fruits.children()).to.have.length(3);
+
+ $fruits.empty();
+ expect($fruits.children()).to.have.length(0);
+ });
+
+ it('() : should allow element reinsertion', function() {
+ var $children = $fruits.children();
+
+ $fruits.empty();
+ expect($fruits.children()).to.have.length(0);
+ expect($children).to.have.length(3);
+
+ $fruits.append($('<div></div><div></div>'));
+ var $remove = $fruits.children().eq(0);
+
+ $remove.replaceWith($children);
+ expect($fruits.children()).to.have.length(4);
+ });
+
+ it('() : should destroy children\'s references to the parent', function() {
+ var $children = $fruits.children();
+
+ $fruits.empty();
+
+ expect($children.eq(0).parent()).to.have.length(0);
+ expect($children.eq(0).next()).to.have.length(0);
+ expect($children.eq(0).prev()).to.have.length(0);
+ expect($children.eq(1).parent()).to.have.length(0);
+ expect($children.eq(1).next()).to.have.length(0);
+ expect($children.eq(1).prev()).to.have.length(0);
+ expect($children.eq(2).parent()).to.have.length(0);
+ expect($children.eq(2).next()).to.have.length(0);
+ expect($children.eq(2).prev()).to.have.length(0);
+ });
+
+ });
+
+ describe('.html', function() {
+
+ it('() : should get the innerHTML for an element', function() {
+ expect($fruits.html()).to.equal([
+ '<li class="apple">Apple</li>',
+ '<li class="orange">Orange</li>',
+ '<li class="pear">Pear</li>'
+ ].join(''));
+ });
+
+ it('() : should get innerHTML even if its just text', function() {
+ var item = '<li class="pear">Pear</li>';
+ expect($('.pear', item).html()).to.equal('Pear');
+ });
+
+ it('() : should return empty string if nothing inside', function() {
+ var item = '<li></li>';
+ expect($('li', item).html()).to.equal('');
+ });
+
+ it('(html) : should set the html for its children', function() {
+ $fruits.html('<li class="durian">Durian</li>');
+ var html = $fruits.html();
+ expect(html).to.equal('<li class="durian">Durian</li>');
+ });
+
+ it('(html) : should add new elements for each element in selection', function() {
+ var $fruits = $('li');
+ $fruits.html('<li class="durian">Durian</li>');
+ var tested = 0;
+ $fruits.each(function(){
+ expect($(this).children().parent().get(0)).to.equal(this);
+ tested++;
+ });
+ expect(tested).to.equal(3);
+ });
+
+ it('(elem) : should set the html for its children with element', function() {
+ $fruits.html($('<li class="durian">Durian</li>'));
+ var html = $fruits.html();
+ expect(html).to.equal('<li class="durian">Durian</li>');
+ });
+
+ it('() : should allow element reinsertion', function() {
+ var $children = $fruits.children();
+
+ $fruits.html('<div></div><div></div>');
+ expect($fruits.children()).to.have.length(2);
+
+ var $remove = $fruits.children().eq(0);
+
+ $remove.replaceWith($children);
+ expect($fruits.children()).to.have.length(4);
+ });
+ });
+
+ describe('.toString', function() {
+ it('() : should get the outerHTML for an element', function() {
+ expect($fruits.toString()).to.equal(fruits);
+ });
+
+ it('() : should return an html string for a set of elements', function() {
+ expect($fruits.find('li').toString()).to.equal('<li class="apple">Apple</li><li class="orange">Orange</li><li class="pear">Pear</li>');
+ });
+
+ it('() : should be called implicitly', function() {
+ var string = [$('<foo>'), $('<bar>'), $('<baz>')].join('');
+ expect(string).to.equal('<foo></foo><bar></bar><baz></baz>');
+ });
+
+ it('() : should pass options', function() {
+ var dom = cheerio.load('&', {decodeEntities: false});
+ expect(dom.root().toString()).to.equal('&');
+ });
+ });
+
+ describe('.text', function() {
+
+ it('() : gets the text for a single element', function() {
+ expect($('.apple').text()).to.equal('Apple');
+ });
+
+ it('() : combines all text from children text nodes', function() {
+ expect($('#fruits').text()).to.equal('AppleOrangePear');
+ });
+
+ it('(text) : sets the text for the child node', function() {
+ $('.apple').text('Granny Smith Apple');
+ expect($('.apple')[0].childNodes[0].data).to.equal('Granny Smith Apple');
+ });
+
+ it('(text) : inserts separate nodes for all children', function() {
+ $('li').text('Fruits');
+ var tested = 0;
+ $('li').each(function(){
+ expect(this.childNodes[0].parent).to.equal(this);
+ tested++;
+ });
+ expect(tested).to.equal(3);
+ });
+
+ it('should allow functions as arguments', function() {
+ $('.apple').text(function(idx, content) {
+ expect(idx).to.equal(0);
+ expect(content).to.equal('Apple');
+ return 'whatever mate';
+ });
+ expect($('.apple')[0].childNodes[0].data).to.equal('whatever mate');
+ });
+
+ it('should decode special chars', function() {
+ var text = $('<p>M&M</p>').text();
+ expect(text).to.equal('M&M');
+ });
+
+ it('should work with special chars added as strings', function() {
+ var text = $('<p>M&M</p>').text();
+ expect(text).to.equal('M&M');
+ });
+
+ it('( undefined ) : should act as an accessor', function() {
+ var $div = $('<div>test</div>');
+ expect($div.text(undefined)).to.be.a('string');
+ expect($div.text()).to.be('test');
+ });
+
+ it('( "" ) : should convert to string', function() {
+ var $div = $('<div>test</div>');
+ expect($div.text('').text()).to.equal('');
+ });
+
+ it('( null ) : should convert to string', function() {
+ expect($('<div>').text(null).text()).to.equal('null');
+ });
+
+ it('( 0 ) : should convert to string', function() {
+ expect($('<div>').text(0).text()).to.equal('0');
+ });
+
+ it('(str) should encode then decode unsafe characters', function() {
+ var $apple = $('.apple');
+
+ $apple.text('blah <script>alert("XSS!")</script> blah');
+ expect($apple[0].childNodes[0].data).to.equal('blah <script>alert("XSS!")</script> blah');
+ expect($apple.text()).to.equal('blah <script>alert("XSS!")</script> blah');
+
+ $apple.text('blah <script>alert("XSS!")</script> blah');
+ expect($apple.html()).to.not.contain('<script>alert("XSS!")</script>');
+ });
+ });
+
+});
diff --git a/test/api/traversing.js b/test/api/traversing.js
new file mode 100644
index 0000000..7ace244
--- /dev/null
+++ b/test/api/traversing.js
@@ -0,0 +1,1413 @@
+var expect = require('expect.js'),
+ cheerio = require('../..'),
+ food = require('../fixtures').food,
+ fruits = require('../fixtures').fruits,
+ drinks = require('../fixtures').drinks,
+ text = require('../fixtures').text;
+
+describe('$(...)', function() {
+
+ var $;
+
+ beforeEach(function() {
+ $ = cheerio.load(fruits);
+ });
+
+ describe('.find', function() {
+
+ it('() : should find nothing', function() {
+ expect($('ul').find()).to.have.length(0);
+ });
+
+ it('(single) : should find one descendant', function() {
+ expect($('#fruits').find('.apple')[0].attribs['class']).to.equal('apple');
+ });
+
+ it('(many) : should find all matching descendant', function() {
+ expect($('#fruits').find('li')).to.have.length(3);
+ });
+
+ it('(many) : should merge all selected elems with matching descendants', function() {
+ expect($('#fruits, #food', food).find('.apple')).to.have.length(1);
+ });
+
+ it('(invalid single) : should return empty if cant find', function() {
+ expect($('ul').find('blah')).to.have.length(0);
+ });
+
+ it('(invalid single) : should query descendants only', function() {
+ expect($('#fruits').find('ul')).to.have.length(0);
+ });
+
+ it('should return empty if search already empty result', function() {
+ expect($('#not-fruits').find('li')).to.have.length(0);
+ });
+
+ it('should lowercase selectors', function() {
+ expect($('#fruits').find('LI')).to.have.length(3);
+ });
+
+ it('should query immediate descendant only', function() {
+ var $ = cheerio.load('<foo><bar><bar></bar><bar></bar></bar></foo>');
+ expect($('foo').find('> bar')).to.have.length(1);
+ });
+
+ it('should query case-sensitively when in xmlMode', function() {
+ var q = cheerio.load('<caseSenSitive allTheWay>', {xmlMode: true});
+ expect(q('caseSenSitive')).to.have.length(1);
+ expect(q('[allTheWay]')).to.have.length(1);
+ expect(q('casesensitive')).to.have.length(0);
+ expect(q('[alltheway]')).to.have.length(0);
+ });
+
+ it('should throw a SyntaxError if given an invalid selector', function() {
+ expect(function() {
+ $('#fruits').find(':bah');
+ }).to.throwException(function(err) {
+ expect(err).to.be.a(SyntaxError);
+ });
+ });
+
+ describe('(cheerio object) :', function() {
+ it('returns only those nodes contained within the current selection', function() {
+ var $ = cheerio.load(food);
+ var $selection = $('#fruits').find($('li'));
+
+ expect($selection).to.have.length(3);
+ expect($selection[0]).to.be($('.apple')[0]);
+ expect($selection[1]).to.be($('.orange')[0]);
+ expect($selection[2]).to.be($('.pear')[0]);
+ });
+ it('returns only those nodes contained within any element in the current selection', function() {
+ var $ = cheerio.load(food);
+ var $selection = $('.apple, #vegetables').find($('li'));
+
+ expect($selection).to.have.length(2);
+ expect($selection[0]).to.be($('.carrot')[0]);
+ expect($selection[1]).to.be($('.sweetcorn')[0]);
+ });
+ });
+
+ describe('(node) :', function() {
+ it('returns node when contained within the current selection', function() {
+ var $ = cheerio.load(food);
+ var $selection = $('#fruits').find($('.apple')[0]);
+
+ expect($selection).to.have.length(1);
+ expect($selection[0]).to.be($('.apple')[0]);
+ });
+ it('returns node when contained within any element the current selection', function() {
+ var $ = cheerio.load(food);
+ var $selection = $('#fruits, #vegetables').find($('.carrot')[0]);
+
+ expect($selection).to.have.length(1);
+ expect($selection[0]).to.be($('.carrot')[0]);
+ });
+ it('does not return node that is not contained within the current selection', function() {
+ var $ = cheerio.load(food);
+ var $selection = $('#fruits').find($('.carrot')[0]);
+
+ expect($selection).to.have.length(0);
+ });
+ });
+ });
+
+ describe('.children', function() {
+
+ it('() : should get all children', function() {
+ expect($('ul').children()).to.have.length(3);
+ });
+
+ it('() : should return children of all matched elements', function() {
+ expect($('ul ul', food).children()).to.have.length(5);
+ });
+
+ it('(selector) : should return children matching selector', function() {
+ var cls = $('ul').children('.orange')[0].attribs['class'];
+ expect(cls).to.equal('orange');
+ });
+
+ it('(invalid selector) : should return empty', function() {
+ expect($('ul').children('.lulz')).to.have.length(0);
+ });
+
+ it('should only match immediate children, not ancestors', function() {
+ expect($(food).children('li')).to.have.length(0);
+ });
+
+ });
+
+ describe('.contents', function() {
+
+ beforeEach(function() {
+ $ = cheerio.load(text);
+ });
+
+ it('() : should get all contents', function() {
+ expect($('p').contents()).to.have.length(5);
+ });
+
+ it('() : should include text nodes', function() {
+ expect($('p').contents().first()[0].type).to.equal('text');
+ });
+
+ it('() : should include comment nodes', function() {
+ expect($('p').contents().last()[0].type).to.equal('comment');
+ });
+
+ });
+
+ describe('.next', function() {
+
+ it('() : should return next element', function() {
+ var cls = $('.orange').next()[0].attribs['class'];
+ expect(cls).to.equal('pear');
+ });
+
+ it('(no next) : should return empty for last child', function() {
+ expect($('.pear').next()).to.have.length(0);
+ });
+
+ it('(next on empty object) : should return empty', function() {
+ expect($('.banana').next()).to.have.length(0);
+ });
+
+ it('() : should operate over all elements in the selection', function() {
+ expect($('.apple, .orange', food).next()).to.have.length(2);
+ });
+
+ describe('(selector) :', function() {
+ it('should reject elements that violate the filter', function() {
+ expect($('.apple').next('.non-existent')).to.have.length(0);
+ });
+
+ it('should accept elements that satisify the filter', function() {
+ expect($('.apple').next('.orange')).to.have.length(1);
+ });
+ });
+
+ });
+
+ describe('.nextAll', function() {
+
+ it('() : should return all following siblings', function() {
+ var elems = $('.apple').nextAll();
+ expect(elems).to.have.length(2);
+ expect(elems[0].attribs['class']).to.equal('orange');
+ expect(elems[1].attribs['class']).to.equal('pear');
+ });
+
+ it('(no next) : should return empty for last child', function() {
+ expect($('.pear').nextAll()).to.have.length(0);
+ });
+
+ it('(nextAll on empty object) : should return empty', function() {
+ expect($('.banana').nextAll()).to.have.length(0);
+ });
+
+ it('() : should operate over all elements in the selection', function() {
+ expect($('.apple, .carrot', food).nextAll()).to.have.length(3);
+ });
+
+ it('() : should not contain duplicate elements', function() {
+ var elems = $('.apple, .orange', food);
+ expect(elems.nextAll()).to.have.length(2);
+ });
+
+ describe('(selector) :', function() {
+ it('should filter according to the provided selector', function() {
+ expect($('.apple').nextAll('.pear')).to.have.length(1);
+ });
+
+ it('should not consider siblings\' contents when filtering', function() {
+ expect($('#fruits', food).nextAll('li')).to.have.length(0);
+ });
+ });
+
+ });
+
+ describe('.nextUntil', function() {
+
+ it('() : should return all following siblings if no selector specified', function() {
+ var elems = $('.apple', food).nextUntil();
+ expect(elems).to.have.length(2);
+ expect(elems[0].attribs['class']).to.equal('orange');
+ expect(elems[1].attribs['class']).to.equal('pear');
+ });
+
+ it('() : should filter out non-element nodes', function() {
+ var elems = $('<div><div></div><!-- comment -->text<div></div></div>');
+ var div = elems.children().eq(0);
+ expect(div.nextUntil()).to.have.length(1);
+ });
+
+ it('() : should operate over all elements in the selection', function() {
+ var elems = $('.apple, .carrot', food);
+ expect(elems.nextUntil()).to.have.length(3);
+ });
+
+ it('() : should not contain duplicate elements', function() {
+ var elems = $('.apple, .orange', food);
+ expect(elems.nextUntil()).to.have.length(2);
+ });
+
+ it('(selector) : should return all following siblings until selector', function() {
+ var elems = $('.apple', food).nextUntil('.pear');
+ expect(elems).to.have.length(1);
+ expect(elems[0].attribs['class']).to.equal('orange');
+ });
+
+ it('(selector not sibling) : should return all following siblings', function() {
+ var elems = $('.apple').nextUntil('#vegetables');
+ expect(elems).to.have.length(2);
+ });
+
+ it('(selector, filterString) : should return all following siblings until selector, filtered by filter', function() {
+ var elems = $('.beer', drinks).nextUntil('.water', '.milk');
+ expect(elems).to.have.length(1);
+ expect(elems[0].attribs['class']).to.equal('milk');
+ });
+
+ it('(null, filterString) : should return all following siblings until selector, filtered by filter', function() {
+ var elems = $('<ul><li></li><li><p></p></li></ul>');
+ var empty = elems.find('li').eq(0).nextUntil(null, 'p');
+ expect(empty).to.have.length(0);
+ });
+
+ it('() : should return an empty object for last child', function() {
+ expect($('.pear').nextUntil()).to.have.length(0);
+ });
+
+ it('() : should return an empty object when called on an empty object', function() {
+ expect($('.banana').nextUntil()).to.have.length(0);
+ });
+
+ it('(node) : should return all following siblings until the node', function() {
+ var $fruits = $('#fruits').children();
+ var elems = $fruits.eq(0).nextUntil($fruits[2]);
+ expect(elems).to.have.length(1);
+ });
+
+ it('(cheerio object) : should return all following siblings until any member of the cheerio object', function() {
+ var $drinks = $(drinks).children();
+ var $until = $([$drinks[4], $drinks[3]]);
+ var elems = $drinks.eq(0).nextUntil($until);
+ expect(elems).to.have.length(2);
+ });
+
+ });
+
+ describe('.prev', function() {
+
+ it('() : should return previous element', function() {
+ var cls = $('.orange').prev()[0].attribs['class'];
+ expect(cls).to.equal('apple');
+ });
+
+ it('(no prev) : should return empty for first child', function() {
+ expect($('.apple').prev()).to.have.length(0);
+ });
+
+ it('(prev on empty object) : should return empty', function() {
+ expect($('.banana').prev()).to.have.length(0);
+ });
+
+ it('() : should operate over all elements in the selection', function() {
+ expect($('.orange, .pear', food).prev()).to.have.length(2);
+ });
+
+ describe('(selector) :', function() {
+ it('should reject elements that violate the filter', function() {
+ expect($('.orange').prev('.non-existent')).to.have.length(0);
+ });
+
+ it('should accept elements that satisify the filter', function() {
+ expect($('.orange').prev('.apple')).to.have.length(1);
+ });
+ });
+
+ });
+
+ describe('.prevAll', function() {
+
+ it('() : should return all preceding siblings', function() {
+ var elems = $('.pear').prevAll();
+ expect(elems).to.have.length(2);
+ expect(elems[0].attribs['class']).to.equal('orange');
+ expect(elems[1].attribs['class']).to.equal('apple');
+ });
+
+ it('(no prev) : should return empty for first child', function() {
+ expect($('.apple').prevAll()).to.have.length(0);
+ });
+
+ it('(prevAll on empty object) : should return empty', function() {
+ expect($('.banana').prevAll()).to.have.length(0);
+ });
+
+ it('() : should operate over all elements in the selection', function() {
+ expect($('.orange, .sweetcorn', food).prevAll()).to.have.length(2);
+ });
+
+ it('() : should not contain duplicate elements', function() {
+ var elems = $('.orange, .pear', food);
+ expect(elems.prevAll()).to.have.length(2);
+ });
+
+ describe('(selector) :', function() {
+ it('should filter returned elements', function() {
+ var elems = $('.pear').prevAll('.apple');
+ expect(elems).to.have.length(1);
+ });
+
+ it('should not consider siblings\'s descendents', function() {
+ var elems = $('#vegetables', food).prevAll('li');
+ expect(elems).to.have.length(0);
+ });
+ });
+
+ });
+
+ describe('.prevUntil', function() {
+
+ it('() : should return all preceding siblings if no selector specified', function() {
+ var elems = $('.pear').prevUntil();
+ expect(elems).to.have.length(2);
+ expect(elems[0].attribs['class']).to.equal('orange');
+ expect(elems[1].attribs['class']).to.equal('apple');
+ });
+
+ it('() : should filter out non-element nodes', function() {
+ var elems = $('<div class="1"><div class="2"></div><!-- comment -->text<div class="3"></div></div>');
+ var div = elems.children().last();
+ expect(div.prevUntil()).to.have.length(1);
+ });
+
+ it('() : should operate over all elements in the selection', function() {
+ var elems = $('.pear, .sweetcorn', food);
+ expect(elems.prevUntil()).to.have.length(3);
+ });
+
+ it('() : should not contain duplicate elements', function() {
+ var elems = $('.orange, .pear', food);
+ expect(elems.prevUntil()).to.have.length(2);
+ });
+
+ it('(selector) : should return all preceding siblings until selector', function() {
+ var elems = $('.pear').prevUntil('.apple');
+ expect(elems).to.have.length(1);
+ expect(elems[0].attribs['class']).to.equal('orange');
+ });
+
+ it('(selector not sibling) : should return all preceding siblings', function() {
+ var elems = $('.sweetcorn', food).prevUntil('#fruits');
+ expect(elems).to.have.length(1);
+ expect(elems[0].attribs['class']).to.equal('carrot');
+ });
+
+ it('(selector, filterString) : should return all preceding siblings until selector, filtered by filter', function() {
+ var elems = $('.cider', drinks).prevUntil('.juice', '.water');
+ expect(elems).to.have.length(1);
+ expect(elems[0].attribs['class']).to.equal('water');
+ });
+
+ it('(selector, filterString) : should return all preceding siblings until selector', function() {
+ var elems = $('<ul><li><p></p></li><li></li></ul>');
+ var empty = elems.find('li').eq(1).prevUntil(null, 'p');
+ expect(empty).to.have.length(0);
+ });
+
+ it('() : should return an empty object for first child', function() {
+ expect($('.apple').prevUntil()).to.have.length(0);
+ });
+
+ it('() : should return an empty object when called on an empty object', function() {
+ expect($('.banana').prevUntil()).to.have.length(0);
+ });
+
+ it('(node) : should return all previous siblings until the node', function() {
+ var $fruits = $('#fruits').children();
+ var elems = $fruits.eq(2).prevUntil($fruits[0]);
+ expect(elems).to.have.length(1);
+ });
+
+ it('(cheerio object) : should return all previous siblings until any member of the cheerio object', function() {
+ var $drinks = $(drinks).children();
+ var $until = $([$drinks[0], $drinks[1]]);
+ var elems = $drinks.eq(4).prevUntil($until);
+ expect(elems).to.have.length(2);
+ });
+
+ });
+
+ describe('.siblings', function() {
+
+ it('() : should get all the siblings', function() {
+ expect($('.orange').siblings()).to.have.length(2);
+ expect($('#fruits').siblings()).to.have.length(0);
+ expect($('.apple, .carrot', food).siblings()).to.have.length(3);
+ });
+
+ it('(selector) : should get all siblings that match the selector', function() {
+ expect($('.orange').siblings('.apple')).to.have.length(1);
+ expect($('.orange').siblings('.peach')).to.have.length(0);
+ });
+
+ it('(selector) : should throw a SyntaxError if given an invalid selector', function() {
+ expect(function() {
+ $('.orange').siblings(':bah');
+ }).to.throwException(function(err) {
+ expect(err).to.be.a(SyntaxError);
+ });
+ });
+
+ it('(selector) : does not consider the contents of siblings when filtering (GH-374)', function() {
+ expect($('#fruits', food).siblings('li')).to.have.length(0);
+ });
+
+ });
+
+ describe('.parents', function() {
+
+ beforeEach(function() {
+ $ = cheerio.load(food);
+ });
+
+ it('() : should get all of the parents in logical order', function(){
+ var result = $('.orange').parents();
+ expect(result).to.have.length(2);
+ expect(result[0].attribs.id).to.be('fruits');
+ expect(result[1].attribs.id).to.be('food');
+ result = $('#fruits').parents();
+ expect(result).to.have.length(1);
+ expect(result[0].attribs.id).to.be('food');
+ });
+
+ it('(selector) : should get all of the parents that match the selector in logical order', function() {
+ var result = $('.orange').parents('#fruits');
+ expect(result).to.have.length(1);
+ expect(result[0].attribs.id).to.be('fruits');
+ result = $('.orange').parents('ul');
+ expect(result).to.have.length(2);
+ expect(result[0].attribs.id).to.be('fruits');
+ expect(result[1].attribs.id).to.be('food');
+ });
+
+ it('() : should not break if the selector does not have any results', function() {
+ var result = $('.saladbar').parents();
+ expect(result).to.have.length(0);
+ });
+
+ it('() : should return an empty set for top-level elements', function() {
+ var result = $('#food').parents();
+ expect(result).to.have.length(0);
+ });
+
+ it('() : should return the parents of every element in the *reveresed* collection, omitting duplicates', function() {
+ var $parents = $('li').parents();
+
+ expect($parents).to.have.length(3);
+ expect($parents[0]).to.be($('#vegetables')[0]);
+ expect($parents[1]).to.be($('#food')[0]);
+ expect($parents[2]).to.be($('#fruits')[0]);
+ });
+
+ });
+
+ describe('.parentsUntil', function() {
+
+ beforeEach(function() {
+ $ = cheerio.load(food);
+ });
+
+ it('() : should get all of the parents in logical order', function() {
+ var result = $('.orange').parentsUntil();
+ expect(result).to.have.length(2);
+ expect(result[0].attribs.id).to.be('fruits');
+ expect(result[1].attribs.id).to.be('food');
+ });
+
+ it('() : should get all of the parents in reversed order, omitting duplicates', function() {
+ var result = $('.apple, .sweetcorn').parentsUntil();
+ expect(result).to.have.length(3);
+ expect(result[0].attribs.id).to.be('vegetables');
+ expect(result[1].attribs.id).to.be('food');
+ expect(result[2].attribs.id).to.be('fruits');
+ });
+
+ it('(selector) : should get all of the parents until selector', function() {
+ var result = $('.orange').parentsUntil('#food');
+ expect(result).to.have.length(1);
+ expect(result[0].attribs.id).to.be('fruits');
+ result = $('.orange').parentsUntil('#fruits');
+ expect(result).to.have.length(0);
+ });
+
+ it('(selector not parent) : should return all parents', function() {
+ var result = $('.orange').parentsUntil('.apple');
+ expect(result).to.have.length(2);
+ expect(result[0].attribs.id).to.be('fruits');
+ expect(result[1].attribs.id).to.be('food');
+ });
+
+ it('(selector, filter) : should get all of the parents that match the filter', function() {
+ var result = $('.apple, .sweetcorn').parentsUntil('.saladbar', '#vegetables');
+ expect(result).to.have.length(1);
+ expect(result[0].attribs.id).to.be('vegetables');
+ });
+
+ it('() : should return empty object when called on an empty object', function() {
+ var result = $('.saladbar').parentsUntil();
+ expect(result).to.have.length(0);
+ });
+
+ it('() : should return an empty set for top-level elements', function() {
+ var result = $('#food').parentsUntil();
+ expect(result).to.have.length(0);
+ });
+
+ it('(cheerio object) : should return all parents until any member of the cheerio object', function() {
+ var $fruits = $('#fruits');
+ var $until = $('#food');
+ var result = $fruits.children().eq(1).parentsUntil($until);
+ expect(result).to.have.length(1);
+ expect(result[0].attribs.id).to.be('fruits');
+ });
+
+ });
+
+ describe('.parent', function() {
+
+ it('() : should return the parent of each matched element', function() {
+ var result = $('.orange').parent();
+ expect(result).to.have.length(1);
+ expect(result[0].attribs.id).to.be('fruits');
+ result = $('li', food).parent();
+ expect(result).to.have.length(2);
+ expect(result[0].attribs.id).to.be('fruits');
+ expect(result[1].attribs.id).to.be('vegetables');
+ });
+
+ it('() : should return an empty object for top-level elements', function() {
+ var result = $('ul').parent();
+ expect(result).to.have.length(0);
+ });
+
+ it('() : should not contain duplicate elements', function() {
+ var result = $('li').parent();
+ expect(result).to.have.length(1);
+ });
+
+ it('(selector) : should filter the matched parent elements by the selector', function() {
+ var result = $('.orange').parent();
+ expect(result).to.have.length(1);
+ expect(result[0].attribs.id).to.be('fruits');
+ result = $('li', food).parent('#fruits');
+ expect(result).to.have.length(1);
+ expect(result[0].attribs.id).to.be('fruits');
+ });
+
+ });
+
+ describe('.closest', function() {
+
+ it('() : should return an empty array', function() {
+ var result = $('.orange').closest();
+ expect(result).to.have.length(0);
+ expect(result).to.be.a(cheerio);
+ });
+
+ it('(selector) : should find the closest element that matches the selector, searching through its ancestors and itself', function() {
+ expect($('.orange').closest('.apple')).to.have.length(0);
+ var result = $('.orange', food).closest('#food');
+ expect(result[0].attribs.id).to.be('food');
+ result = $('.orange', food).closest('ul');
+ expect(result[0].attribs.id).to.be('fruits');
+ result = $('.orange', food).closest('li');
+ expect(result[0].attribs['class']).to.be('orange');
+ });
+
+ it('(selector) : should find the closest element of each item, removing duplicates', function() {
+ var result = $('li', food).closest('ul');
+ expect(result).to.have.length(2);
+ });
+
+ it('() : should not break if the selector does not have any results', function() {
+ var result = $('.saladbar', food).closest('ul');
+ expect(result).to.have.length(0);
+ });
+
+ });
+
+ describe('.each', function() {
+
+ it('( (i, elem) -> ) : should loop selected returning fn with (i, elem)', function() {
+ var items = [],
+ classes = ['apple', 'orange', 'pear'];
+ $('li').each(function(idx, elem) {
+ items[idx] = elem;
+ expect(this.attribs['class']).to.equal(classes[idx]);
+ });
+ expect(items[0].attribs['class']).to.equal('apple');
+ expect(items[1].attribs['class']).to.equal('orange');
+ expect(items[2].attribs['class']).to.equal('pear');
+ });
+
+ it('( (i, elem) -> ) : should break iteration when the iterator function returns false', function() {
+ var iterationCount = 0;
+ $('li').each(function(idx) {
+ iterationCount++;
+ return idx < 1;
+ });
+
+ expect(iterationCount).to.equal(2);
+ });
+
+ });
+
+ describe('.map', function() {
+ it('(fn) : should be invoked with the correct arguments and context', function() {
+ var $fruits = $('li');
+ var args = [];
+ var thisVals = [];
+
+ $fruits.map(function() {
+ args.push(Array.prototype.slice.call(arguments));
+ thisVals.push(this);
+ });
+
+ expect(args).to.eql([
+ [0, $fruits[0]],
+ [1, $fruits[1]],
+ [2, $fruits[2]]
+ ]);
+ expect(thisVals).to.eql([
+ $fruits[0],
+ $fruits[1],
+ $fruits[2]
+ ]);
+ });
+
+ it('(fn) : should return an Cheerio object wrapping the returned items', function() {
+ var $fruits = $('li');
+ var $mapped = $fruits.map(function(i) {
+ return $fruits[2 - i];
+ });
+
+ expect($mapped).to.have.length(3);
+ expect($mapped[0]).to.be($fruits[2]);
+ expect($mapped[1]).to.be($fruits[1]);
+ expect($mapped[2]).to.be($fruits[0]);
+ });
+
+ it('(fn) : should ignore `null` and `undefined` returned by iterator', function() {
+ var $fruits = $('li');
+ var retVals = [null, undefined, $fruits[1]];
+
+ var $mapped = $fruits.map(function(i) {
+ return retVals[i];
+ });
+
+ expect($mapped).to.have.length(1);
+ expect($mapped[0]).to.be($fruits[1]);
+ });
+
+ it('(fn) : should preform a shallow merge on arrays returned by iterator', function() {
+ var $fruits = $('li');
+
+ var $mapped = $fruits.map(function() {
+ return [1, [3, 4]];
+ });
+
+ expect($mapped.get()).to.eql([
+ 1, [3, 4],
+ 1, [3, 4],
+ 1, [3, 4]
+ ]);
+ });
+
+ it('(fn) : should tolerate `null` and `undefined` when flattening arrays returned by iterator', function() {
+ var $fruits = $('li');
+
+ var $mapped = $fruits.map(function() {
+ return [null, undefined];
+ });
+
+ expect($mapped.get()).to.eql([
+ null, undefined,
+ null, undefined,
+ null, undefined,
+ ]);
+ });
+ });
+
+ describe('.filter', function() {
+ it('(selector) : should reduce the set of matched elements to those that match the selector', function() {
+ var pear = $('li').filter('.pear').text();
+ expect(pear).to.be('Pear');
+ });
+
+ it('(selector) : should not consider nested elements', function() {
+ var lis = $('#fruits').filter('li');
+ expect(lis).to.have.length(0);
+ });
+
+ it('(selection) : should reduce the set of matched elements to those that are contained in the provided selection', function() {
+ var $fruits = $('li');
+ var $pear = $fruits.filter('.pear, .apple');
+ expect($fruits.filter($pear)).to.have.length(2);
+ });
+
+ it('(element) : should reduce the set of matched elements to those that specified directly', function() {
+ var $fruits = $('li');
+ var pear = $fruits.filter('.pear')[0];
+ expect($fruits.filter(pear)).to.have.length(1);
+ });
+
+ it('(fn) : should reduce the set of matched elements to those that pass the function\'s test', function() {
+ var orange = $('li').filter(function(i, el) {
+ expect(this).to.be(el);
+ expect(el.tagName).to.be('li');
+ expect(i).to.be.a('number');
+ return $(this).attr('class') === 'orange';
+ }).text();
+
+ expect(orange).to.be('Orange');
+ });
+ });
+
+ describe('.not', function() {
+ it('(selector) : should reduce the set of matched elements to those that do not match the selector', function() {
+ var $fruits = $('li');
+
+ var $notPear = $fruits.not('.pear');
+
+ expect($notPear).to.have.length(2);
+ expect($notPear[0]).to.be($fruits[0]);
+ expect($notPear[1]).to.be($fruits[1]);
+ });
+
+ it('(selector) : should not consider nested elements', function() {
+ var lis = $('#fruits').not('li');
+ expect(lis).to.have.length(1);
+ });
+
+ it('(selection) : should reduce the set of matched elements to those that are mot contained in the provided selection', function() {
+ var $fruits = $('li');
+ var $orange = $('.orange');
+
+ var $notOrange = $fruits.not($orange);
+
+ expect($notOrange).to.have.length(2);
+ expect($notOrange[0]).to.be($fruits[0]);
+ expect($notOrange[1]).to.be($fruits[2]);
+ });
+
+ it('(element) : should reduce the set of matched elements to those that specified directly', function() {
+ var $fruits = $('li');
+ var apple = $('.apple')[0];
+
+ var $notApple = $fruits.not(apple);
+
+ expect($notApple).to.have.length(2);
+ expect($notApple[0]).to.be($fruits[1]);
+ expect($notApple[1]).to.be($fruits[2]);
+ });
+
+ it('(fn) : should reduce the set of matched elements to those that do not pass the function\'s test', function() {
+ var $fruits = $('li');
+
+ var $notOrange = $fruits.not(function(i, el) {
+ expect(this).to.be(el);
+ expect(el.name).to.be('li');
+ expect(i).to.be.a('number');
+ return $(this).attr('class') === 'orange';
+ });
+
+ expect($notOrange).to.have.length(2);
+ expect($notOrange[0]).to.be($fruits[0]);
+ expect($notOrange[1]).to.be($fruits[2]);
+ });
+ });
+
+ describe('.has', function() {
+
+ beforeEach(function() {
+ $ = cheerio.load(food);
+ });
+
+ it('(selector) : should reduce the set of matched elements to those with descendants that match the selector', function() {
+ var $fruits = $('#fruits,#vegetables').has('.pear');
+ expect($fruits).to.have.length(1);
+ expect($fruits[0]).to.be($('#fruits')[0]);
+ });
+
+ it('(selector) : should only consider nested elements', function() {
+ var $empty = $('#fruits').has('#fruits');
+ expect($empty).to.have.length(0);
+ });
+
+ it('(element) : should reduce the set of matched elements to those that are ancestors of the provided element', function() {
+ var $fruits = $('#fruits,#vegetables').has($('.pear')[0]);
+ expect($fruits).to.have.length(1);
+ expect($fruits[0]).to.be($('#fruits')[0]);
+ });
+
+ it('(element) : should only consider nested elements', function() {
+ var $fruits = $('#fruits');
+ var fruits = $fruits[0];
+ var $empty = $fruits.has(fruits);
+
+ expect($empty).to.have.length(0);
+ });
+ });
+
+ describe('.first', function() {
+
+ it('() : should return the first item', function() {
+ var $src = $('<span>foo</span><span>bar</span><span>baz</span>');
+ var $elem = $src.first();
+ expect($elem.length).to.equal(1);
+ expect($elem[0].childNodes[0].data).to.equal('foo');
+ });
+
+ it('() : should return an empty object for an empty object', function() {
+ var $src = $();
+ var $first = $src.first();
+ expect($first.length).to.equal(0);
+ expect($first[0]).to.be(undefined);
+ });
+
+ });
+
+ describe('.last', function() {
+
+ it('() : should return the last element', function() {
+ var $src = $('<span>foo</span><span>bar</span><span>baz</span>');
+ var $elem = $src.last();
+ expect($elem.length).to.equal(1);
+ expect($elem[0].childNodes[0].data).to.equal('baz');
+ });
+
+ it('() : should return an empty object for an empty object', function() {
+ var $src = $();
+ var $last = $src.last();
+ expect($last.length).to.equal(0);
+ expect($last[0]).to.be(undefined);
+ });
+
+ });
+
+ describe('.first & .last', function() {
+
+ it('() : should return equivalent collections if only one element', function() {
+ var $src = $('<span>bar</span>');
+ var $first = $src.first();
+ var $last = $src.last();
+ expect($first.length).to.equal(1);
+ expect($first[0].childNodes[0].data).to.equal('bar');
+ expect($last.length).to.equal(1);
+ expect($last[0].childNodes[0].data).to.equal('bar');
+ expect($first[0]).to.equal($last[0]);
+ });
+
+ });
+
+ describe('.eq', function() {
+
+ function getText(el) {
+ if(!el.length) return '';
+ return el[0].childNodes[0].data;
+ }
+
+ it('(i) : should return the element at the specified index', function() {
+ expect(getText($('li').eq(0))).to.equal('Apple');
+ expect(getText($('li').eq(1))).to.equal('Orange');
+ expect(getText($('li').eq(2))).to.equal('Pear');
+ expect(getText($('li').eq(3))).to.equal('');
+ expect(getText($('li').eq(-1))).to.equal('Pear');
+ });
+
+ });
+
+ describe('.get', function() {
+
+ it('(i) : should return the element at the specified index', function() {
+ var children = $('#fruits').children();
+ expect(children.get(0)).to.be(children[0]);
+ expect(children.get(1)).to.be(children[1]);
+ expect(children.get(2)).to.be(children[2]);
+ });
+
+ it('(-1) : should return the element indexed from the end of the collection', function() {
+ var children = $('#fruits').children();
+ expect(children.get(-1)).to.be(children[2]);
+ expect(children.get(-2)).to.be(children[1]);
+ expect(children.get(-3)).to.be(children[0]);
+ });
+
+ it('() : should return an array containing all of the collection', function() {
+ var children = $('#fruits').children();
+ var all = children.get();
+ expect(Array.isArray(all)).to.be.ok();
+ expect(all).to.eql([
+ children[0],
+ children[1],
+ children[2]
+ ]);
+ });
+
+ });
+
+ describe('.index', function() {
+ describe('() : ', function() {
+ it('returns the index of a child amongst its siblings', function() {
+ expect($('.orange').index()).to.be(1);
+ });
+ it('returns -1 when the selection has no parent', function() {
+ expect($('<div/>').index()).to.be(-1);
+ });
+ });
+
+ describe('(selector) : ', function() {
+ it('returns the index of the first element in the set matched by `selector`', function() {
+ expect($('.apple').index('#fruits, li')).to.be(1);
+ });
+ it('returns -1 when the item is not present in the set matched by `selector`', function() {
+ expect($('.apple').index('#fuits')).to.be(-1);
+ });
+ it('returns -1 when the first element in the set has no parent', function() {
+ expect($('<div/>').index('*')).to.be(-1);
+ });
+ });
+
+ describe('(node) : ', function() {
+ it('returns the index of the given node within the current selection', function() {
+ var $lis = $('li');
+ expect($lis.index($lis.get(1))).to.be(1);
+ });
+ it('returns the index of the given node within the current selection when the current selection has no parent', function() {
+ var $apple = $('.apple').remove();
+
+ expect($apple.index($apple.get(0))).to.be(0);
+ });
+ it('returns -1 when the given node is not present in the current selection', function() {
+ expect($('li').index($('#fruits').get(0))).to.be(-1);
+ });
+ it('returns -1 when the current selection is empty', function() {
+ expect($('.not-fruit').index($('#fruits').get(0))).to.be(-1);
+ });
+ });
+
+ describe('(selection) : ', function() {
+ it('returns the index of the first node in the provided selection within the current selection', function() {
+ var $lis = $('li');
+ expect($lis.index($('.orange, .pear'))).to.be(1);
+ });
+ it('returns -1 when the given node is not present in the current selection', function() {
+ expect($('li').index($('#fruits'))).to.be(-1);
+ });
+ it('returns -1 when the current selection is empty', function() {
+ expect($('.not-fruit').index($('#fruits'))).to.be(-1);
+ });
+ });
+ });
+
+ describe('.slice', function() {
+
+ function getText(el) {
+ if(!el.length) return '';
+ return el[0].childNodes[0].data;
+ }
+
+ it('(start) : should return all elements after the given index', function() {
+ var sliced = $('li').slice(1);
+ expect(sliced).to.have.length(2);
+ expect(getText(sliced.eq(0))).to.equal('Orange');
+ expect(getText(sliced.eq(1))).to.equal('Pear');
+ });
+
+ it('(start, end) : should return all elements matching the given range', function() {
+ var sliced = $('li').slice(1, 2);
+ expect(sliced).to.have.length(1);
+ expect(getText(sliced.eq(0))).to.equal('Orange');
+ });
+
+ it('(-start) : should return element matching the offset from the end', function() {
+ var sliced = $('li').slice(-1);
+ expect(sliced).to.have.length(1);
+ expect(getText(sliced.eq(0))).to.equal('Pear');
+ });
+
+ });
+
+ describe('.end() :', function() {
+ var $fruits;
+
+ beforeEach(function() {
+ $fruits = $('#fruits').children();
+ });
+
+ it('returns an empty object at the end of the chain', function() {
+ expect($fruits.end().end().end()).to.be.ok();
+ expect($fruits.end().end().end()).to.have.length(0);
+ });
+ it('find', function() {
+ expect($fruits.find('.apple').end()).to.be($fruits);
+ });
+ it('filter', function() {
+ expect($fruits.filter('.apple').end()).to.be($fruits);
+ });
+ it('map', function() {
+ expect($fruits.map(function() { return this; }).end()).to.be($fruits);
+ });
+ it('contents', function() {
+ expect($fruits.contents().end()).to.be($fruits);
+ });
+ it('eq', function() {
+ expect($fruits.eq(1).end()).to.be($fruits);
+ });
+ it('first', function() {
+ expect($fruits.first().end()).to.be($fruits);
+ });
+ it('last', function() {
+ expect($fruits.last().end()).to.be($fruits);
+ });
+ it('slice', function() {
+ expect($fruits.slice(1).end()).to.be($fruits);
+ });
+ it('children', function() {
+ expect($fruits.children().end()).to.be($fruits);
+ });
+ it('parent', function() {
+ expect($fruits.parent().end()).to.be($fruits);
+ });
+ it('parents', function() {
+ expect($fruits.parents().end()).to.be($fruits);
+ });
+ it('closest', function() {
+ expect($fruits.closest('ul').end()).to.be($fruits);
+ });
+ it('siblings', function() {
+ expect($fruits.siblings().end()).to.be($fruits);
+ });
+ it('next', function() {
+ expect($fruits.next().end()).to.be($fruits);
+ });
+ it('nextAll', function() {
+ expect($fruits.nextAll().end()).to.be($fruits);
+ });
+ it('prev', function() {
+ expect($fruits.prev().end()).to.be($fruits);
+ });
+ it('prevAll', function() {
+ expect($fruits.prevAll().end()).to.be($fruits);
+ });
+ it('clone', function() {
+ expect($fruits.clone().end()).to.be($fruits);
+ });
+ });
+
+ describe('.add', function() {
+ var $ = cheerio.load(food);
+ var $fruits = $('#fruits');
+ var $apple = $('.apple');
+ var $orange = $('.orange');
+ var $pear = $('.pear');
+ var $carrot = $('.carrot');
+ var $sweetcorn = $('.sweetcorn');
+
+ describe('(selector', function() {
+ describe(') :', function() {
+ describe('matched element', function() {
+ it('occurs before current selection', function() {
+ var $selection = $orange.add('.apple');
+
+ expect($selection).to.have.length(2);
+ expect($selection[0]).to.be($apple[0]);
+ expect($selection[1]).to.be($orange[0]);
+ });
+ it('is identical to the current selection', function() {
+ var $selection = $orange.add('.orange');
+
+ expect($selection).to.have.length(1);
+ expect($selection[0]).to.be($orange[0]);
+ });
+ it('occurs after current selection', function() {
+ var $selection = $orange.add('.pear');
+
+ expect($selection).to.have.length(2);
+ expect($selection[0]).to.be($orange[0]);
+ expect($selection[1]).to.be($pear[0]);
+ });
+ it('contains the current selection', function() {
+ var $selection = $orange.add('#fruits');
+
+ expect($selection).to.have.length(2);
+ expect($selection[0]).to.be($fruits[0]);
+ expect($selection[1]).to.be($orange[0]);
+ });
+ it('is a child of the current selection', function() {
+ var $selection = $fruits.add('.orange');
+
+ expect($selection).to.have.length(2);
+ expect($selection[0]).to.be($fruits[0]);
+ expect($selection[1]).to.be($orange[0]);
+ });
+ });
+ describe('matched elements', function() {
+ it('occur before the current selection', function() {
+ var $selection = $pear.add('.apple, .orange');
+
+ expect($selection).to.have.length(3);
+ expect($selection[0]).to.be($apple[0]);
+ expect($selection[1]).to.be($orange[0]);
+ expect($selection[2]).to.be($pear[0]);
+ });
+ it('include the current selection', function() {
+ var $selection = $pear.add('#fruits li');
+
+ expect($selection).to.have.length(3);
+ expect($selection[0]).to.be($apple[0]);
+ expect($selection[1]).to.be($orange[0]);
+ expect($selection[2]).to.be($pear[0]);
+ });
+ it('occur after the current selection', function() {
+ var $selection = $apple.add('.orange, .pear');
+
+ expect($selection).to.have.length(3);
+ expect($selection[0]).to.be($apple[0]);
+ expect($selection[1]).to.be($orange[0]);
+ expect($selection[2]).to.be($pear[0]);
+ });
+ it('occur within the current selection', function() {
+ var $selection = $fruits.add('#fruits li');
+
+ expect($selection).to.have.length(4);
+ expect($selection[0]).to.be($fruits[0]);
+ expect($selection[1]).to.be($apple[0]);
+ expect($selection[2]).to.be($orange[0]);
+ expect($selection[3]).to.be($pear[0]);
+ });
+ });
+ });
+ it(', context)', function() {
+ var $selection = $fruits.add('li', '#vegetables');
+ expect($selection).to.have.length(3);
+ expect($selection[0]).to.be($fruits[0]);
+ expect($selection[1]).to.be($carrot[0]);
+ expect($selection[2]).to.be($sweetcorn[0]);
+ });
+ });
+
+ describe('(element) :', function() {
+ describe('honors document order when element occurs', function() {
+ it('before the current selection', function() {
+ var $selection = $orange.add($apple[0]);
+
+ expect($selection).to.have.length(2);
+ expect($selection[0]).to.be($apple[0]);
+ expect($selection[1]).to.be($orange[0]);
+ });
+ it('after the current selection', function() {
+ var $selection = $orange.add($pear[0]);
+
+ expect($selection).to.have.length(2);
+ expect($selection[0]).to.be($orange[0]);
+ expect($selection[1]).to.be($pear[0]);
+ });
+ it('within the current selection', function() {
+ var $selection = $fruits.add($orange[0]);
+
+ expect($selection).to.have.length(2);
+ expect($selection[0]).to.be($fruits[0]);
+ expect($selection[1]).to.be($orange[0]);
+ });
+ it('as an ancestor of the current selection', function() {
+ var $selection = $orange.add($fruits[0]);
+
+ expect($selection).to.have.length(2);
+ expect($selection[0]).to.be($fruits[0]);
+ expect($selection[1]).to.be($orange[0]);
+ });
+ });
+ it('does not insert an element already contained within the current selection', function() {
+ var $selection = $apple.add($apple[0]);
+
+ expect($selection).to.have.length(1);
+ expect($selection[0]).to.be($apple[0]);
+ });
+ });
+ describe('([elements]) : elements', function() {
+ it('occur before the current selection', function() {
+ var $selection = $pear.add($('.apple, .orange').get());
+
+ expect($selection).to.have.length(3);
+ expect($selection[0]).to.be($apple[0]);
+ expect($selection[1]).to.be($orange[0]);
+ expect($selection[2]).to.be($pear[0]);
+ });
+ it('include the current selection', function() {
+ var $selection = $pear.add($('#fruits li').get());
+
+ expect($selection).to.have.length(3);
+ expect($selection[0]).to.be($apple[0]);
+ expect($selection[1]).to.be($orange[0]);
+ expect($selection[2]).to.be($pear[0]);
+ });
+ it('occur after the current selection', function() {
+ var $selection = $apple.add($('.orange, .pear').get());
+
+ expect($selection).to.have.length(3);
+ expect($selection[0]).to.be($apple[0]);
+ expect($selection[1]).to.be($orange[0]);
+ expect($selection[2]).to.be($pear[0]);
+ });
+ it('occur within the current selection', function() {
+ var $selection = $fruits.add($('#fruits li').get());
+
+ expect($selection).to.have.length(4);
+ expect($selection[0]).to.be($fruits[0]);
+ expect($selection[1]).to.be($apple[0]);
+ expect($selection[2]).to.be($orange[0]);
+ expect($selection[3]).to.be($pear[0]);
+ });
+ });
+
+ /**
+ * Element order is undefined in this case, so it should not be asserted
+ * here.
+ *
+ * > If the collection consists of elements from different documents or
+ * > ones not in any document, the sort order is undefined.
+ *
+ * http://api.jquery.com/add/
+ */
+ it('(html) : correctly parses and adds the new elements', function() {
+ var $selection = $apple.add('<li class="banana">banana</li>');
+
+ expect($selection).to.have.length(2);
+ expect($selection.is('.apple')).to.be(true);
+ expect($selection.is('.banana')).to.be(true);
+ });
+
+ describe('(selection) :', function() {
+ describe('element in selection', function() {
+ it('occurs before current selection', function() {
+ var $selection = $orange.add($('.apple'));
+
+ expect($selection).to.have.length(2);
+ expect($selection[0]).to.be($apple[0]);
+ expect($selection[1]).to.be($orange[0]);
+ });
+ it('is identical to the current selection', function() {
+ var $selection = $orange.add($('.orange'));
+
+ expect($selection).to.have.length(1);
+ expect($selection[0]).to.be($orange[0]);
+ });
+ it('occurs after current selection', function() {
+ var $selection = $orange.add($('.pear'));
+
+ expect($selection).to.have.length(2);
+ expect($selection[0]).to.be($orange[0]);
+ expect($selection[1]).to.be($pear[0]);
+ });
+ it('contains the current selection', function() {
+ var $selection = $orange.add($('#fruits'));
+
+ expect($selection).to.have.length(2);
+ expect($selection[0]).to.be($fruits[0]);
+ expect($selection[1]).to.be($orange[0]);
+ });
+ it('is a child of the current selection', function() {
+ var $selection = $fruits.add($('.orange'));
+
+ expect($selection).to.have.length(2);
+ expect($selection[0]).to.be($fruits[0]);
+ expect($selection[1]).to.be($orange[0]);
+ });
+ });
+ describe('elements in the selection', function() {
+ it('occur before the current selection', function() {
+ var $selection = $pear.add($('.apple, .orange'));
+
+ expect($selection).to.have.length(3);
+ expect($selection[0]).to.be($apple[0]);
+ expect($selection[1]).to.be($orange[0]);
+ expect($selection[2]).to.be($pear[0]);
+ });
+ it('include the current selection', function() {
+ var $selection = $pear.add($('#fruits li'));
+
+ expect($selection).to.have.length(3);
+ expect($selection[0]).to.be($apple[0]);
+ expect($selection[1]).to.be($orange[0]);
+ expect($selection[2]).to.be($pear[0]);
+ });
+ it('occur after the current selection', function() {
+ var $selection = $apple.add($('.orange, .pear'));
+
+ expect($selection).to.have.length(3);
+ expect($selection[0]).to.be($apple[0]);
+ expect($selection[1]).to.be($orange[0]);
+ expect($selection[2]).to.be($pear[0]);
+ });
+ it('occur within the current selection', function() {
+ var $selection = $fruits.add($('#fruits li'));
+
+ expect($selection).to.have.length(4);
+ expect($selection[0]).to.be($fruits[0]);
+ expect($selection[1]).to.be($apple[0]);
+ expect($selection[2]).to.be($orange[0]);
+ expect($selection[3]).to.be($pear[0]);
+ });
+ });
+ });
+ });
+
+ describe('.addBack', function() {
+ describe('() : ', function() {
+ it('includes siblings and self', function() {
+ var $selection = $('.orange').siblings().addBack();
+
+ expect($selection).to.have.length(3);
+ expect($selection[0]).to.be($('.apple')[0]);
+ expect($selection[1]).to.be($('.orange')[0]);
+ expect($selection[2]).to.be($('.pear')[0]);
+ });
+ it('includes children and self', function() {
+ var $selection = $('#fruits').children().addBack();
+
+ expect($selection).to.have.length(4);
+ expect($selection[0]).to.be($('#fruits')[0]);
+ expect($selection[1]).to.be($('.apple')[0]);
+ expect($selection[2]).to.be($('.orange')[0]);
+ expect($selection[3]).to.be($('.pear')[0]);
+ });
+ it('includes parent and self', function() {
+ var $selection = $('.apple').parent().addBack();
+
+ expect($selection).to.have.length(2);
+ expect($selection[0]).to.be($('#fruits')[0]);
+ expect($selection[1]).to.be($('.apple')[0]);
+ });
+ it('includes parents and self', function() {
+ var $ = cheerio.load(food);
+ var $selection = $('.apple').parents().addBack();
+
+ expect($selection).to.have.length(3);
+ expect($selection[0]).to.be($('#food')[0]);
+ expect($selection[1]).to.be($('#fruits')[0]);
+ expect($selection[2]).to.be($('.apple')[0]);
+ });
+ });
+ it('(filter) : filters the previous selection', function() {
+ var $selection = $('li').eq(1).addBack('.apple');
+
+ expect($selection).to.have.length(2);
+ expect($selection[0]).to.be($('.apple')[0]);
+ expect($selection[1]).to.be($('.orange')[0]);
+ });
+ });
+});
diff --git a/test/api/utils.js b/test/api/utils.js
new file mode 100644
index 0000000..64f5ecb
--- /dev/null
+++ b/test/api/utils.js
@@ -0,0 +1,233 @@
+var expect = require('expect.js'),
+ fixtures = require('../fixtures'),
+ cheerio = require('../..');
+
+describe('cheerio', function() {
+
+ describe('.html', function() {
+
+ it('() : should return innerHTML; $.html(obj) should return outerHTML', function() {
+ var $div = cheerio('div', '<div><span>foo</span><span>bar</span></div>');
+ var span = $div.children()[1];
+ expect(cheerio(span).html()).to.equal('bar');
+ expect(cheerio.html(span)).to.equal('<span>bar</span>');
+ });
+
+ it('(<obj>) : should accept an object, an array, or a cheerio object', function() {
+ var $span = cheerio('<span>foo</span>');
+ expect(cheerio.html($span[0])).to.equal('<span>foo</span>');
+ expect(cheerio.html($span)).to.equal('<span>foo</span>');
+ });
+
+ it('(<value>) : should be able to set to an empty string', function() {
+ var $elem = cheerio('<span>foo</span>').html('');
+ expect(cheerio.html($elem)).to.equal('<span></span>');
+ });
+
+ it('() : of empty cheerio object should return null', function() {
+ expect(cheerio().html()).to.be(null);
+ });
+
+ it('(selector) : should return the outerHTML of the selected element', function() {
+ var $ = cheerio.load(fixtures.fruits);
+ expect($.html('.pear')).to.equal('<li class="pear">Pear</li>');
+ });
+ });
+
+
+ describe('.text', function() {
+ it('(cheerio object) : should return the text contents of the specified elements', function() {
+ var $ = cheerio.load('<a>This is <em>content</em>.</a>');
+ expect($.text($('a'))).to.equal('This is content.');
+ });
+
+ it('(cheerio object) : should omit comment nodes', function() {
+ var $ = cheerio.load('<a>This is <!-- a comment --> not a comment.</a>');
+ expect($.text($('a'))).to.equal('This is not a comment.');
+ });
+
+ it('(cheerio object) : should include text contents of children recursively', function() {
+ var $ = cheerio.load('<a>This is <div>a child with <span>another child and <!-- a comment --> not a comment</span> followed by <em>one last child</em> and some final</div> text.</a>');
+ expect($.text($('a'))).to.equal('This is a child with another child and not a comment followed by one last child and some final text.');
+ });
+
+ it('() : should return the rendered text content of the root', function() {
+ var $ = cheerio.load('<a>This is <div>a child with <span>another child and <!-- a comment --> not a comment</span> followed by <em>one last child</em> and some final</div> text.</a>');
+ expect($.text()).to.equal('This is a child with another child and not a comment followed by one last child and some final text.');
+ });
+ });
+
+
+ describe('.load', function() {
+
+ it('(html) : should retain original root after creating a new node', function() {
+ var $html = cheerio.load('<body><ul id="fruits"></ul></body>');
+ expect($html('body')).to.have.length(1);
+ $html('<script>');
+ expect($html('body')).to.have.length(1);
+ });
+
+ it('(html) : should handle lowercase tag options', function() {
+ var $html = cheerio.load('<BODY><ul id="fruits"></ul></BODY>', { lowerCaseTags : true });
+ expect($html.html()).to.be('<body><ul id="fruits"></ul></body>');
+ });
+
+ it('(html) : should handle the `normalizeWhitepace` option', function() {
+ var $html = cheerio.load('<body><b>foo</b> <b>bar</b></body>', { normalizeWhitespace : true });
+ expect($html.html()).to.be('<body><b>foo</b> <b>bar</b></body>');
+ });
+
+ // TODO:
+ // it('(html) : should handle xml tag option', function() {
+ // var $html = $.load('<body><script>oh hai</script></body>', { xmlMode : true });
+ // console.log($html('script')[0].type);
+ // expect($html('script')[0].type).to.be('tag');
+ // });
+
+ it('(buffer) : should accept a buffer', function() {
+ var $html = cheerio.load(new Buffer('<div>foo</div>'));
+ expect($html.html()).to.be('<div>foo</div>');
+ });
+
+ });
+
+
+ describe('.clone', function() {
+
+ it('() : should return a copy', function() {
+ var $src = cheerio('<div><span>foo</span><span>bar</span><span>baz</span></div>').children();
+ var $elem = $src.clone();
+ expect($elem.length).to.equal(3);
+ expect($elem.parent()).to.have.length(0);
+ expect($elem.text()).to.equal($src.text());
+ $src.text('rofl');
+ expect($elem.text()).to.not.equal($src.text());
+ });
+
+ it('() : should preserve parsing options', function() {
+ var $ = cheerio.load('<div>π</div>', { decodeEntities: false });
+ var $div = $('div');
+
+ expect($div.text()).to.equal($div.clone().text());
+ });
+ });
+
+ describe('.parseHTML', function() {
+
+ it('() : returns null', function() {
+ expect(cheerio.parseHTML()).to.equal(null);
+ });
+
+ it('(null) : returns null', function() {
+ expect(cheerio.parseHTML(null)).to.equal(null);
+ });
+
+ it('("") : returns null', function() {
+ expect(cheerio.parseHTML('')).to.equal(null);
+ });
+
+ it('(largeHtmlString) : parses large HTML strings', function() {
+ var html = new Array(10).join('<div></div>');
+ var nodes = cheerio.parseHTML(html);
+
+ expect(nodes.length).to.be.greaterThan(4);
+ expect(nodes).to.be.an('array');
+ });
+
+ it('("<script>") : ignores scripts by default', function() {
+ var html = '<script>undefined()</script>';
+ expect(cheerio.parseHTML(html)).to.have.length(0);
+ });
+
+ it('("<script>", true) : preserves scripts when requested', function() {
+ var html = '<script>undefined()</script>';
+ expect(cheerio.parseHTML(html, true)[0].tagName).to.match(/script/i);
+ });
+
+ it('("scriptAndNonScript) : preserves non-script nodes', function() {
+ var html = '<script>undefined()</script><div></div>';
+ expect(cheerio.parseHTML(html)[0].tagName).to.match(/div/i);
+ });
+
+ it('(scriptAndNonScript, true) : Preserves script position', function() {
+ var html = '<script>undefined()</script><div></div>';
+ expect(cheerio.parseHTML(html, true)[0].tagName).to.match(/script/i);
+ });
+
+ it('(text) : returns a text node', function() {
+ expect(cheerio.parseHTML('text')[0].type).to.be('text');
+ });
+
+ it('(\\ttext) : preserves leading whitespace', function() {
+ expect(cheerio.parseHTML('\t<div></div>')[0].data).to.equal('\t');
+ });
+
+ it('( text) : Leading spaces are treated as text nodes', function() {
+ expect(cheerio.parseHTML(' <div/> ')[0].type).to.be('text');
+ });
+
+ it('(html) : should preserve content', function() {
+ var html = '<div>test div</div>';
+ expect(cheerio(cheerio.parseHTML(html)[0]).html()).to.equal('test div');
+ });
+
+ it('(malformedHtml) : should not break', function() {
+ expect(cheerio.parseHTML('<span><span>')).to.have.length(1);
+ });
+
+ it('(garbageInput) : should not cause an error', function() {
+ expect(cheerio.parseHTML('<#if><tr><p>This is a test.</p></tr><#/if>') || true).to.be.ok();
+ });
+
+ it('(text) : should return an array that is not effected by DOM manipulation methods', function() {
+ var $ = cheerio.load('<div>');
+ var elems = $.parseHTML('<b></b><i></i>');
+
+ $('div').append(elems);
+
+ expect(elems).to.have.length(2);
+ });
+ });
+
+ describe('.contains', function() {
+
+ var $;
+
+ beforeEach(function() {
+ $ = cheerio.load(fixtures.food);
+ });
+
+ it('(container, contained) : should correctly detect the provided element', function() {
+ var $food = $('#food');
+ var $fruits = $('#fruits');
+ var $apple = $('.apple');
+
+ expect($.contains($food[0], $fruits[0])).to.equal(true);
+ expect($.contains($food[0], $apple[0])).to.equal(true);
+ });
+
+ it('(container, other) : should not detect elements that are not contained', function() {
+ var $fruits = $('#fruits');
+ var $vegetables = $('#vegetables');
+ var $apple = $('.apple');
+
+ expect($.contains($vegetables[0], $apple[0])).to.equal(false);
+ expect($.contains($fruits[0], $vegetables[0])).to.equal(false);
+ expect($.contains($vegetables[0], $fruits[0])).to.equal(false);
+ expect($.contains($fruits[0], $fruits[0])).to.equal(false);
+ expect($.contains($vegetables[0], $vegetables[0])).to.equal(false);
+ });
+
+ });
+
+ describe('.root', function() {
+
+ it('() : should return a cheerio-wrapped root object', function() {
+ var $html = cheerio.load('<div><span>foo</span><span>bar</span></div>');
+ $html.root().append('<div id="test"></div>');
+ expect($html.html()).to.equal('<div><span>foo</span><span>bar</span></div><div id="test"></div>');
+ });
+
+ });
+
+});
diff --git a/test/cheerio.js b/test/cheerio.js
new file mode 100644
index 0000000..c17fd80
--- /dev/null
+++ b/test/cheerio.js
@@ -0,0 +1,354 @@
+var expect = require('expect.js'),
+ htmlparser2 = require('htmlparser2'),
+ $ = require('../'),
+ fixtures = require('./fixtures'),
+ fruits = fixtures.fruits,
+ food = fixtures.food,
+ _ = {
+ filter: require('lodash.filter')
+ };
+
+// HTML
+var script = '<script src="script.js" type="text/javascript"></script>',
+ multiclass = '<p><a class="btn primary" href="#">Save</a></p>';
+
+describe('cheerio', function() {
+
+ it('should get the version', function() {
+ expect(/\d+\.\d+\.\d+/.test($.version)).to.be.ok();
+ });
+
+ it('$(null) should return be empty', function() {
+ expect($(null)).to.be.empty();
+ });
+
+ it('$(undefined) should be empty', function() {
+ expect($(undefined)).to.be.empty();
+ });
+
+ it('$(null) should be empty', function() {
+ expect($('')).to.be.empty();
+ });
+
+ it('$(selector) with no context or root should be empty', function() {
+ expect($('.h2')).to.be.empty();
+ expect($('#fruits')).to.be.empty();
+ });
+
+ it('$(node) : should override previously-loaded nodes', function() {
+ var C = $.load('<div><span></span></div>');
+ var spanNode = C('span')[0];
+ var $span = C(spanNode);
+ expect($span[0]).to.equal(spanNode);
+ });
+
+ it('should be able to create html without a root or context', function() {
+ var $h2 = $('<h2>');
+ expect($h2).to.not.be.empty();
+ expect($h2).to.have.length(1);
+ expect($h2[0].tagName).to.equal('h2');
+ });
+
+ it('should be able to create complicated html', function() {
+ var $script = $(script);
+ expect($script).to.not.be.empty();
+ expect($script).to.have.length(1);
+ expect($script[0].attribs.src).to.equal('script.js');
+ expect($script[0].attribs.type).to.equal('text/javascript');
+ expect($script[0].childNodes).to.be.empty();
+ });
+
+ var testAppleSelect = function($apple) {
+ expect($apple).to.have.length(1);
+ $apple = $apple[0];
+ expect($apple.parentNode.tagName).to.equal('ul');
+ expect($apple.prev).to.be(null);
+ expect($apple.next.attribs['class']).to.equal('orange');
+ expect($apple.childNodes).to.have.length(1);
+ expect($apple.childNodes[0].data).to.equal('Apple');
+ };
+
+ it('should be able to select .apple with only a context', function() {
+ var $apple = $('.apple', fruits);
+ testAppleSelect($apple);
+ });
+
+ it('should be able to select .apple with a node as context', function() {
+ var $apple = $('.apple', $(fruits)[0]);
+ testAppleSelect($apple);
+ });
+
+ it('should be able to select .apple with only a root', function() {
+ var $apple = $('.apple', null, fruits);
+ testAppleSelect($apple);
+ });
+
+ it('should be able to select an id', function() {
+ var $fruits = $('#fruits', null, fruits);
+ expect($fruits).to.have.length(1);
+ expect($fruits[0].attribs.id).to.equal('fruits');
+ });
+
+ it('should be able to select a tag', function() {
+ var $ul = $('ul', fruits);
+ expect($ul).to.have.length(1);
+ expect($ul[0].tagName).to.equal('ul');
+ });
+
+ it('should accept a node reference as a context', function() {
+ var $elems = $('<div><span></span></div>');
+ expect($('span', $elems[0])).to.have.length(1);
+ });
+
+ it('should accept an array of node references as a context', function() {
+ var $elems = $('<div><span></span></div>');
+ expect($('span', $elems.toArray())).to.have.length(1);
+ });
+
+ it('should select only elements inside given context (Issue #193)', function() {
+ var q = $.load(food),
+ fruits = q('#fruits'),
+ fruitElements = q('li', fruits);
+
+ expect(fruitElements).to.have.length(3);
+ });
+
+ it('should be able to select multiple tags', function() {
+ var $fruits = $('li', null, fruits);
+ expect($fruits).to.have.length(3);
+ var classes = ['apple', 'orange', 'pear'];
+ $fruits.each(function(idx, $fruit) {
+ expect($fruit.attribs['class']).to.equal(classes[idx]);
+ });
+ });
+
+ it('should be able to do: $("#fruits .apple")', function() {
+ var $apple = $('#fruits .apple', fruits);
+ testAppleSelect($apple);
+ });
+
+ it('should be able to do: $("li.apple")', function() {
+ var $apple = $('li.apple', fruits);
+ testAppleSelect($apple);
+ });
+
+ it('should be able to select by attributes', function() {
+ var $apple = $('li[class=apple]', fruits);
+ testAppleSelect($apple);
+ });
+
+ it('should be able to select multiple classes: $(".btn.primary")', function() {
+ var $a = $('.btn.primary', multiclass);
+ expect($a).to.have.length(1);
+ expect($a[0].childNodes[0].data).to.equal('Save');
+ });
+
+ it('should not create a top-level node', function() {
+ var $elem = $('* div', '<div>');
+ expect($elem).to.have.length(0);
+ });
+
+ it('should be able to select multiple elements: $(".apple, #fruits")', function() {
+ var $elems = $('.apple, #fruits', fruits);
+ expect($elems).to.have.length(2);
+
+ var $apple = _.filter($elems, function(elem) {
+ return elem.attribs['class'] === 'apple';
+ });
+ var $fruits = _.filter($elems, function(elem) {
+ return elem.attribs.id === 'fruits';
+ });
+ testAppleSelect($apple);
+ expect($fruits[0].attribs.id).to.equal('fruits');
+ });
+
+ it('should select first element $(:first)');
+ // var $elem = $(':first', fruits);
+ // var $h2 = $('<h2>fruits</h2>');
+ // console.log($elem.before('hi'));
+ // console.log($elem.before($h2));
+
+ it('should be able to select immediate children: $("#fruits > .pear")', function() {
+ var $food = $(food);
+ $('.pear', $food).append('<li class="pear">Another Pear!</li>');
+ expect($('#fruits .pear', $food)).to.have.length(2);
+ var $elem = $('#fruits > .pear', $food);
+ expect($elem).to.have.length(1);
+ expect($elem.attr('class')).to.equal('pear');
+ });
+
+ it('should be able to select immediate children: $(".apple + .pear")', function() {
+ var $elem = $('.apple + li', fruits);
+ expect($elem).to.have.length(1);
+ $elem = $('.apple + .pear', fruits);
+ expect($elem).to.have.length(0);
+ $elem = $('.apple + .orange', fruits);
+ expect($elem).to.have.length(1);
+ expect($elem.attr('class')).to.equal('orange');
+ });
+
+ it('should be able to select immediate children: $(".apple ~ .pear")', function() {
+ var $elem = $('.apple ~ li', fruits);
+ expect($elem).to.have.length(2);
+ $elem = $('.apple ~ .pear', fruits);
+ expect($elem.attr('class')).to.equal('pear');
+ });
+
+ it('should handle wildcards on attributes: $("li[class*=r]")', function() {
+ var $elem = $('li[class*=r]', fruits);
+ expect($elem).to.have.length(2);
+ expect($elem.eq(0).attr('class')).to.equal('orange');
+ expect($elem.eq(1).attr('class')).to.equal('pear');
+ });
+
+ it('should handle beginning of attr selectors: $("li[class^=o]")', function() {
+ var $elem = $('li[class^=o]', fruits);
+ expect($elem).to.have.length(1);
+ expect($elem.eq(0).attr('class')).to.equal('orange');
+ });
+
+ it('should handle beginning of attr selectors: $("li[class$=e]")', function() {
+ var $elem = $('li[class$=e]', fruits);
+ expect($elem).to.have.length(2);
+ expect($elem.eq(0).attr('class')).to.equal('apple');
+ expect($elem.eq(1).attr('class')).to.equal('orange');
+ });
+
+ it('should gracefully degrade on complex, unmatched queries', function() {
+ var $elem = $('Eastern States Cup #8-fin <br>Downhill ');
+ expect($elem).to.have.length(0); // []
+ });
+
+ it('(extended Array) should not interfere with prototype methods (issue #119)', function() {
+ var extended = [];
+ extended.find = extended.children = extended.each = function() {};
+ var $empty = $(extended);
+
+ expect($empty.find).to.be($.prototype.find);
+ expect($empty.children).to.be($.prototype.children);
+ expect($empty.each).to.be($.prototype.each);
+ });
+
+ it('should set html(number) as a string', function() {
+ var $elem = $('<div>');
+ $elem.html(123);
+ expect(typeof $elem.text()).to.equal('string');
+ });
+
+ it('should set text(number) as a string', function() {
+ var $elem = $('<div>');
+ $elem.text(123);
+ expect(typeof $elem.text()).to.equal('string');
+ });
+
+ describe('.load', function() {
+
+ it('should generate selections as proper instances', function() {
+ var q = $.load(fruits);
+
+ expect(q('.apple')).to.be.a(q);
+ });
+
+ it('should be able to filter down using the context', function() {
+ var q = $.load(fruits),
+ apple = q('.apple', 'ul'),
+ lis = q('li', 'ul');
+
+ expect(apple).to.have.length(1);
+ expect(lis).to.have.length(3);
+ });
+
+ it('should allow loading a pre-parsed DOM', function() {
+ var dom = htmlparser2.parseDOM(food),
+ q = $.load(dom);
+
+ expect(q('ul')).to.have.length(3);
+ });
+
+ it('should render xml in html() when options.xmlMode = true', function() {
+ var str = '<MixedCaseTag UPPERCASEATTRIBUTE=""></MixedCaseTag>',
+ expected = '<MixedCaseTag UPPERCASEATTRIBUTE=""/>',
+ dom = $.load(str, {xmlMode: true});
+
+ expect(dom('MixedCaseTag').get(0).tagName).to.equal('MixedCaseTag');
+ expect(dom.html()).to.be(expected);
+ });
+
+ it('should render xml in html() when options.xmlMode = true passed to html()', function() {
+ var str = '<MixedCaseTag UPPERCASEATTRIBUTE=""></MixedCaseTag>',
+ // since parsing done without xmlMode flag, all tags converted to lowercase
+ expectedXml = '<mixedcasetag uppercaseattribute=""/>',
+ expectedNoXml = '<mixedcasetag uppercaseattribute=""></mixedcasetag>',
+ dom = $.load(str);
+
+ expect(dom('MixedCaseTag').get(0).tagName).to.equal('mixedcasetag');
+ expect(dom.html()).to.be(expectedNoXml);
+ expect(dom.html({xmlMode: true})).to.be(expectedXml);
+ });
+
+ it('should respect options on the element level', function() {
+ var str = '<!doctype html><html><head><title>Some test</title></head><body><footer><p>Copyright © 2003-2014</p></footer></body></html>',
+ expectedHtml = '<p>Copyright © 2003-2014</p>',
+ expectedXml = '<p>Copyright © 2003-2014</p>',
+ domNotEncoded = $.load(str, {decodeEntities: false}),
+ domEncoded = $.load(str);
+
+ expect(domNotEncoded('footer').html()).to.be(expectedHtml);
+ // TODO: Make it more html friendly, maybe with custom encode tables
+ expect(domEncoded('footer').html()).to.be(expectedXml);
+ });
+
+ it('should return a fully-qualified Function', function() {
+ var $c = $.load('<div>');
+
+ expect($c).to.be.a(Function);
+ });
+
+ describe('prototype extensions', function() {
+ it('should honor extensions defined on `prototype` property', function() {
+ var $c = $.load('<div>');
+ var $div;
+ $c.prototype.myPlugin = function() {
+ return {
+ context: this,
+ args: arguments
+ };
+ };
+
+ $div = $c('div');
+
+ expect($div.myPlugin).to.be.a('function');
+ expect($div.myPlugin().context).to.be($div);
+ expect(Array.prototype.slice.call($div.myPlugin(1, 2, 3).args))
+ .to.eql([1, 2, 3]);
+ });
+
+ it('should honor extensions defined on `fn` property', function() {
+ var $c = $.load('<div>');
+ var $div;
+ $c.fn.myPlugin = function() {
+ return {
+ context: this,
+ args: arguments
+ };
+ };
+
+ $div = $c('div');
+
+ expect($div.myPlugin).to.be.a('function');
+ expect($div.myPlugin().context).to.be($div);
+ expect(Array.prototype.slice.call($div.myPlugin(1, 2, 3).args))
+ .to.eql([1, 2, 3]);
+ });
+
+ it('should isolate extensions between loaded functions', function() {
+ var $a = $.load('<div>');
+ var $b = $.load('<div>');
+
+ $a.prototype.foo = function() {};
+
+ expect($b('div').foo).to.be(undefined);
+ });
+ });
+ });
+});
diff --git a/test/fixtures.js b/test/fixtures.js
new file mode 100644
index 0000000..0c1a009
--- /dev/null
+++ b/test/fixtures.js
@@ -0,0 +1,72 @@
+/* jshint indent: false */
+exports.fruits = [
+ '<ul id="fruits">',
+ '<li class="apple">Apple</li>',
+ '<li class="orange">Orange</li>',
+ '<li class="pear">Pear</li>',
+ '</ul>'
+].join('');
+
+exports.vegetables = [
+ '<ul id="vegetables">',
+ '<li class="carrot">Carrot</li>',
+ '<li class="sweetcorn">Sweetcorn</li>',
+ '</ul>'
+].join('');
+
+exports.chocolates = [
+ '<ul id="chocolates">',
+ '<li class="linth" data-highlight="Lindor" data-origin="swiss">Linth</li>',
+ '<li class="frey" data-taste="sweet" data-best-collection="Mahony">Frey</li>',
+ '<li class="cailler">Cailler</li>',
+ '</ul>'
+].join('');
+
+exports.drinks = [
+ '<ul id="drinks">',
+ '<li class="beer">Beer</li>',
+ '<li class="juice">Juice</li>',
+ '<li class="milk">Milk</li>',
+ '<li class="water">Water</li>',
+ '<li class="cider">Cider</li>',
+ '</ul>'
+].join('');
+
+exports.food = [
+ '<ul id="food">',
+ exports.fruits,
+ exports.vegetables,
+ '</ul>'
+].join('');
+
+exports.inputs = [
+ '<select id="one"><option value="option_not_selected">Option not selected</option><option value="option_selected" selected>Option selected</option></select>',
+ '<select id="one-valueless"><option>Option not selected</option><option selected>Option selected</option></select>',
+ '<select id="one-html-entity"><option>Option not selected</option><option selected>Option <selected></option></select>',
+ '<select id="one-nested"><option>Option not selected</option><option selected>Option <span>selected</span></option></select>',
+ '<input type="text" value="input_text" />',
+ '<input type="checkbox" name="checkbox_off" value="off" /><input type="checkbox" name="checkbox_on" value="on" checked />',
+ '<input type="checkbox" name="checkbox_valueless" />',
+ '<input type="radio" value="off" name="radio" /><input type="radio" name="radio" value="on" checked />',
+ '<input type="radio" value="off" name="radio[brackets]" /><input type="radio" name="radio[brackets]" value="on" checked />',
+ '<input type="radio" name="radio_valueless" />',
+ '<select id="multi" multiple><option value="1">1</option><option value="2" selected>2</option><option value="3" selected>3</option><option value="4">4</option></select>',
+ '<select id="multi-valueless" multiple><option>1</option><option selected>2</option><option selected>3</option><option>4</option></select>'
+].join('');
+
+exports.text = [
+ '<p>Apples, <b>oranges</b> and pears.</p>',
+ '<p>Carrots and <!-- sweetcorn --></p>'
+].join('');
+
+exports.forms = [
+ '<form id="simple"><input type="text" name="fruit" value="Apple" /></form>',
+ '<form id="nested"><div><input type="text" name="fruit" value="Apple" /></div><input type="text" name="vegetable" value="Carrot" /></form>',
+ '<form id="disabled"><input type="text" name="fruit" value="Apple" disabled /></form>',
+ '<form id="submit"><input type="text" name="fruit" value="Apple" /><input type="submit" name="submit" value="Submit" /></form>',
+ '<form id="select"><select name="fruit"><option value="Apple">Apple</option><option value="Orange" selected>Orange</option></select></form>',
+ '<form id="unnamed"><input type="text" name="fruit" value="Apple" /><input type="text" value="Carrot" /></form>',
+ '<form id="multiple"><select name="fruit" multiple><option value="Apple" selected>Apple</option><option value="Orange" selected>Orange</option><option value="Carrot">Carrot</option></select></form>',
+ '<form id="textarea"><textarea name="fruits">Apple\nOrange</textarea></form>',
+ '<form id="spaces"><input type="text" name="fruit" value="Blood orange" /></form>'
+].join('');
diff --git a/test/mocha.opts b/test/mocha.opts
new file mode 100644
index 0000000..9431de4
--- /dev/null
+++ b/test/mocha.opts
@@ -0,0 +1,2 @@
+--reporter list
+--growl
\ No newline at end of file
diff --git a/test/parse.js b/test/parse.js
new file mode 100644
index 0000000..d22bad3
--- /dev/null
+++ b/test/parse.js
@@ -0,0 +1,252 @@
+var expect = require('expect.js'),
+ parse = require('../lib/parse'),
+ defaultOpts = require('..').prototype.options;
+
+
+// Tags
+var basic = '<html></html>';
+var siblings = '<h2></h2><p></p>';
+
+// Single Tags
+var single = '<br/>';
+var singleWrong = '<br>';
+
+// Children
+var children = '<html><br/></html>';
+var li = '<li class="durian">Durian</li>';
+
+// Attributes
+var attributes = '<img src="hello.png" alt="man waving">';
+var noValueAttribute = '<textarea disabled></textarea>';
+
+// Comments
+var comment = '<!-- sexy -->';
+var conditional = '<!--[if IE 8]><html class="no-js ie8" lang="en"><![endif]-->';
+
+// Text
+var text = 'lorem ipsum';
+
+// Script
+var script = '<script type="text/javascript">alert("hi world!");</script>';
+var scriptEmpty = '<script></script>';
+
+// Style
+var style = '<style type="text/css"> h2 { color:blue; } </style>';
+var styleEmpty = '<style></style>';
+
+// Directives
+var directive = '<!doctype html>';
+
+
+describe('parse', function() {
+
+ describe('.eval', function() {
+
+ it('should parse basic empty tags: ' + basic, function() {
+ var tag = parse.evaluate(basic, defaultOpts)[0];
+ expect(tag.type).to.equal('tag');
+ expect(tag.tagName).to.equal('html');
+ expect(tag.childNodes).to.be.empty();
+ });
+
+ it('should handle sibling tags: ' + siblings, function() {
+ var dom = parse.evaluate(siblings, defaultOpts),
+ h2 = dom[0],
+ p = dom[1];
+
+ expect(dom).to.have.length(2);
+ expect(h2.tagName).to.equal('h2');
+ expect(p.tagName).to.equal('p');
+ });
+
+ it('should handle single tags: ' + single, function() {
+ var tag = parse.evaluate(single, defaultOpts)[0];
+ expect(tag.type).to.equal('tag');
+ expect(tag.tagName).to.equal('br');
+ expect(tag.childNodes).to.be.empty();
+ });
+
+ it('should handle malformatted single tags: ' + singleWrong, function() {
+ var tag = parse.evaluate(singleWrong, defaultOpts)[0];
+ expect(tag.type).to.equal('tag');
+ expect(tag.tagName).to.equal('br');
+ expect(tag.childNodes).to.be.empty();
+ });
+
+ it('should handle tags with children: ' + children, function() {
+ var tag = parse.evaluate(children, defaultOpts)[0];
+ expect(tag.type).to.equal('tag');
+ expect(tag.tagName).to.equal('html');
+ expect(tag.childNodes).to.be.ok();
+ expect(tag.childNodes).to.have.length(1);
+ });
+
+ it('should handle tags with children: ' + li, function() {
+ var tag = parse.evaluate(li, defaultOpts)[0];
+ expect(tag.childNodes).to.have.length(1);
+ expect(tag.childNodes[0].data).to.equal('Durian');
+ });
+
+ it('should handle tags with attributes: ' + attributes, function() {
+ var attrs = parse.evaluate(attributes, defaultOpts)[0].attribs;
+ expect(attrs).to.be.ok();
+ expect(attrs.src).to.equal('hello.png');
+ expect(attrs.alt).to.equal('man waving');
+ });
+
+ it('should handle value-less attributes: ' + noValueAttribute, function() {
+ var attrs = parse.evaluate(noValueAttribute, defaultOpts)[0].attribs;
+ expect(attrs).to.be.ok();
+ expect(attrs.disabled).to.equal('');
+ });
+
+ it('should handle comments: ' + comment, function() {
+ var elem = parse.evaluate(comment, defaultOpts)[0];
+ expect(elem.type).to.equal('comment');
+ expect(elem.data).to.equal(' sexy ');
+ });
+
+ it('should handle conditional comments: ' + conditional, function() {
+ var elem = parse.evaluate(conditional, defaultOpts)[0];
+ expect(elem.type).to.equal('comment');
+ expect(elem.data).to.equal(conditional.replace('<!--', '').replace('-->', ''));
+ });
+
+ it('should handle text: ' + text, function() {
+ var text_ = parse.evaluate(text, defaultOpts)[0];
+ expect(text_.type).to.equal('text');
+ expect(text_.data).to.equal('lorem ipsum');
+ });
+
+ it('should handle script tags: ' + script, function() {
+ var script_ = parse.evaluate(script, defaultOpts)[0];
+ expect(script_.type).to.equal('script');
+ expect(script_.tagName).to.equal('script');
+ expect(script_.attribs.type).to.equal('text/javascript');
+ expect(script_.childNodes).to.have.length(1);
+ expect(script_.childNodes[0].type).to.equal('text');
+ expect(script_.childNodes[0].data).to.equal('alert("hi world!");');
+ });
+
+ it('should handle style tags: ' + style, function() {
+ var style_ = parse.evaluate(style, defaultOpts)[0];
+ expect(style_.type).to.equal('style');
+ expect(style_.tagName).to.equal('style');
+ expect(style_.attribs.type).to.equal('text/css');
+ expect(style_.childNodes).to.have.length(1);
+ expect(style_.childNodes[0].type).to.equal('text');
+ expect(style_.childNodes[0].data).to.equal(' h2 { color:blue; } ');
+ });
+
+ it('should handle directives: ' + directive, function() {
+ var elem = parse.evaluate(directive, defaultOpts)[0];
+ expect(elem.type).to.equal('directive');
+ expect(elem.data).to.equal('!doctype html');
+ expect(elem.tagName).to.equal('!doctype');
+ });
+
+ });
+
+ describe('.parse', function() {
+
+ // root test utility
+ function rootTest(root) {
+ expect(root.tagName).to.equal('root');
+
+ // Should exist but be null
+ expect(root.nextSibling).to.be(null);
+ expect(root.previousSibling).to.be(null);
+ expect(root.parentNode).to.be(null);
+
+ var child = root.childNodes[0];
+ expect(child.parentNode).to.be(null);
+ }
+
+ it('should add root to: ' + basic, function() {
+ var root = parse(basic, defaultOpts);
+ rootTest(root);
+ expect(root.childNodes).to.have.length(1);
+ expect(root.childNodes[0].tagName).to.equal('html');
+ });
+
+ it('should add root to: ' + siblings, function() {
+ var root = parse(siblings, defaultOpts);
+ rootTest(root);
+ expect(root.childNodes).to.have.length(2);
+ expect(root.childNodes[0].tagName).to.equal('h2');
+ expect(root.childNodes[1].tagName).to.equal('p');
+ expect(root.childNodes[1].parent).to.equal(null);
+ });
+
+ it('should add root to: ' + comment, function() {
+ var root = parse(comment, defaultOpts);
+ rootTest(root);
+ expect(root.childNodes).to.have.length(1);
+ expect(root.childNodes[0].type).to.equal('comment');
+ });
+
+ it('should add root to: ' + text, function() {
+ var root = parse(text, defaultOpts);
+ rootTest(root);
+ expect(root.childNodes).to.have.length(1);
+ expect(root.childNodes[0].type).to.equal('text');
+ });
+
+ it('should add root to: ' + scriptEmpty, function() {
+ var root = parse(scriptEmpty, defaultOpts);
+ rootTest(root);
+ expect(root.childNodes).to.have.length(1);
+ expect(root.childNodes[0].type).to.equal('script');
+ });
+
+ it('should add root to: ' + styleEmpty, function() {
+ var root = parse(styleEmpty, defaultOpts);
+ rootTest(root);
+ expect(root.childNodes).to.have.length(1);
+ expect(root.childNodes[0].type).to.equal('style');
+ });
+
+ it('should add root to: ' + directive, function() {
+ var root = parse(directive, defaultOpts);
+ rootTest(root);
+ expect(root.childNodes).to.have.length(1);
+ expect(root.childNodes[0].type).to.equal('directive');
+ });
+
+ it('should expose the DOM level 1 API', function() {
+ var root = parse('<div><a></a><span></span><p></p></div>', defaultOpts).childNodes[0];
+ var childNodes = root.childNodes;
+
+ expect(childNodes).to.have.length(3);
+
+ expect(root.tagName).to.be('div');
+ expect(root.firstChild).to.be(childNodes[0]);
+ expect(root.lastChild).to.be(childNodes[2]);
+
+ expect(childNodes[0].tagName).to.be('a');
+ expect(childNodes[0].previousSibling).to.be(null);
+ expect(childNodes[0].nextSibling).to.be(childNodes[1]);
+ expect(childNodes[0].parentNode).to.be(root);
+ expect(childNodes[0].childNodes).to.have.length(0);
+ expect(childNodes[0].firstChild).to.be(null);
+ expect(childNodes[0].lastChild).to.be(null);
+
+ expect(childNodes[1].tagName).to.be('span');
+ expect(childNodes[1].previousSibling).to.be(childNodes[0]);
+ expect(childNodes[1].nextSibling).to.be(childNodes[2]);
+ expect(childNodes[1].parentNode).to.be(root);
+ expect(childNodes[1].childNodes).to.have.length(0);
+ expect(childNodes[1].firstChild).to.be(null);
+ expect(childNodes[1].lastChild).to.be(null);
+
+ expect(childNodes[2].tagName).to.be('p');
+ expect(childNodes[2].previousSibling).to.be(childNodes[1]);
+ expect(childNodes[2].nextSibling).to.be(null);
+ expect(childNodes[2].parentNode).to.be(root);
+ expect(childNodes[2].childNodes).to.have.length(0);
+ expect(childNodes[2].firstChild).to.be(null);
+ expect(childNodes[2].lastChild).to.be(null);
+ });
+ });
+
+});
diff --git a/test/xml.js b/test/xml.js
new file mode 100644
index 0000000..687034e
--- /dev/null
+++ b/test/xml.js
@@ -0,0 +1,60 @@
+var expect = require('expect.js'),
+ cheerio = require('..'),
+ _ = {
+ extend: require('lodash.assignin')
+ };
+
+var xml = function(str, options) {
+ options = _.extend({ xmlMode: true }, options);
+ var dom = cheerio.load(str, options);
+ return dom.xml();
+};
+
+var dom = function(str, options) {
+ var $ = cheerio.load('', options);
+ return $(str).html();
+};
+
+describe('render', function() {
+
+ describe('(xml)', function() {
+
+ it('should render <media:thumbnail /> tags correctly', function() {
+ var str = '<media:thumbnail url="http://www.foo.com/keyframe.jpg" width="75" height="50" time="12:05:01.123" />';
+ expect(xml(str)).to.equal('<media:thumbnail url="http://www.foo.com/keyframe.jpg" width="75" height="50" time="12:05:01.123"/>');
+ });
+
+ it('should render <link /> tags (RSS) correctly', function() {
+ var str = '<link>http://www.github.com/</link>';
+ expect(xml(str)).to.equal('<link>http://www.github.com/</link>');
+ });
+
+ it('should escape entities', function(){
+ var str = '<tag attr="foo & bar"/>';
+ expect(xml(str)).to.equal(str);
+ });
+
+ });
+
+ describe('(dom)', function () {
+
+ it('should keep camelCase for new nodes', function() {
+ var str = '<g><someElem someAttribute="something">hello</someElem></g>';
+ expect(dom(str, {xmlMode: false})).to.equal('<someelem someattribute="something">hello</someelem>');
+ });
+
+ it('should keep camelCase for new nodes', function() {
+ var str = '<g><someElem someAttribute="something">hello</someElem></g>';
+ expect(dom(str, {xmlMode: true})).to.equal('<someElem someAttribute="something">hello</someElem>');
+ });
+
+ it('should maintain the parsing options of distinct contexts independently', function() {
+ var str = '<g><someElem someAttribute="something">hello</someElem></g>';
+ var $x = cheerio.load('', { xmlMode: false });
+
+ expect($x(str).html()).to.equal('<someelem someattribute="something">hello</someelem>');
+ });
+
+ });
+
+});
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-javascript/node-cheerio.git
More information about the Pkg-javascript-commits
mailing list