From be6212a4b460301563843e98addbd6119151960b Mon Sep 17 00:00:00 2001 From: openKylinBot Date: Thu, 19 May 2022 14:51:54 +0800 Subject: [PATCH] Import Upstream version 3.6.0+dfsg+~3.5.13 --- .editorconfig | 16 + .eslintignore | 15 + .eslintrc-browser.json | 32 + .eslintrc-node.json | 20 + .eslintrc.json | 5 + .gitattributes | 5 + .github/ISSUE_TEMPLATE.md | 24 + .github/PULL_REQUEST_TEMPLATE.md | 20 + .github/lock.yml | 13 + .gitignore | 22 + .mailmap | 116 + .npmignore | 16 + .npmrc | 1 + .travis.yml | 56 + AUTHORS.txt | 331 + CODE_OF_CONDUCT.md | 3 + CONTRIBUTING.md | 145 + Gruntfile.js | 386 + LICENSE.txt | 20 + README.md | 383 + build/fixtures/README.md | 62 + build/release.js | 94 + build/release/cdn.js | 120 + build/release/dist.js | 169 + build/release/ensure-sizzle.js | 35 + build/tasks/build.js | 399 + build/tasks/dist.js | 71 + build/tasks/lib/spawn_test.js | 16 + build/tasks/node_smoke_tests.js | 31 + build/tasks/promises_aplus_tests.js | 27 + build/tasks/qunit_fixture.js | 22 + build/tasks/sourcemap.js | 17 + build/tasks/testswarm.js | 62 + package.json | 116 + src/.eslintrc.json | 38 + src/ajax.js | 876 + src/ajax/jsonp.js | 103 + src/ajax/load.js | 77 + src/ajax/script.js | 74 + src/ajax/var/location.js | 5 + src/ajax/var/nonce.js | 5 + src/ajax/var/rquery.js | 5 + src/ajax/xhr.js | 170 + src/attributes.js | 13 + src/attributes/attr.js | 141 + src/attributes/classes.js | 186 + src/attributes/prop.js | 143 + src/attributes/support.js | 33 + src/attributes/val.js | 191 + src/callbacks.js | 236 + src/core.js | 400 + src/core/DOMEval.js | 43 + src/core/access.js | 72 + src/core/camelCase.js | 23 + src/core/init.js | 129 + src/core/isAttached.js | 26 + src/core/nodeName.js | 13 + src/core/parseHTML.js | 65 + src/core/parseXML.js | 35 + src/core/ready-no-deferred.js | 97 + src/core/ready.js | 86 + src/core/readyException.js | 13 + src/core/stripAndCollapse.js | 14 + src/core/support.js | 20 + src/core/toType.js | 20 + src/core/var/rsingleTag.js | 7 + src/css.js | 494 + src/css/addGetHookIf.js | 26 + src/css/adjustCSS.js | 74 + src/css/curCSS.js | 65 + src/css/finalPropName.js | 42 + src/css/hiddenVisibleSelectors.js | 15 + src/css/showHide.js | 105 + src/css/support.js | 152 + src/css/var/cssExpand.js | 5 + src/css/var/getStyles.js | 17 + src/css/var/isHiddenWithinTree.js | 34 + src/css/var/rboxStyle.js | 7 + src/css/var/rnumnonpx.js | 7 + src/css/var/swap.js | 26 + src/data.js | 180 + src/data/Data.js | 162 + src/data/var/acceptData.js | 19 + src/data/var/dataPriv.js | 7 + src/data/var/dataUser.js | 7 + src/deferred.js | 399 + src/deferred/exceptionHook.js | 21 + src/deprecated.js | 87 + src/deprecated/ajax-event-alias.js | 22 + src/deprecated/event.js | 50 + src/dimensions.js | 60 + src/effects.js | 702 + src/effects/Tween.js | 125 + src/effects/animatedSelector.js | 15 + src/event.js | 874 + src/event/focusin.js | 58 + src/event/support.js | 11 + src/event/trigger.js | 199 + src/exports/amd.js | 26 + src/exports/global.js | 34 + src/jquery.js | 41 + src/manipulation.js | 480 + src/manipulation/_evalUrl.js | 32 + src/manipulation/buildFragment.js | 106 + src/manipulation/getAll.js | 32 + src/manipulation/setGlobalEval.js | 22 + src/manipulation/support.js | 41 + src/manipulation/var/rscriptType.js | 5 + src/manipulation/var/rtagName.js | 8 + src/manipulation/wrapMap.js | 30 + src/offset.js | 232 + src/queue.js | 145 + src/queue/delay.js | 24 + src/selector-native.js | 241 + src/selector-sizzle.js | 19 + src/selector.js | 3 + src/serialize.js | 134 + src/traversing.js | 198 + src/traversing/findFilter.js | 97 + src/traversing/var/dir.js | 22 + src/traversing/var/rneedsContext.js | 8 + src/traversing/var/siblings.js | 17 + src/var/ObjectFunctionString.js | 7 + src/var/arr.js | 5 + src/var/class2type.js | 6 + src/var/document.js | 5 + src/var/documentElement.js | 7 + src/var/flat.js | 16 + src/var/fnToString.js | 7 + src/var/getProto.js | 5 + src/var/hasOwn.js | 7 + src/var/indexOf.js | 7 + src/var/isFunction.js | 17 + src/var/isWindow.js | 8 + src/var/pnum.js | 5 + src/var/push.js | 7 + src/var/rcheckableType.js | 5 + src/var/rcssNum.js | 9 + src/var/rnothtmlwhite.js | 8 + src/var/slice.js | 7 + src/var/support.js | 6 + src/var/toString.js | 7 + src/wrap.js | 78 + src/wrapper.js | 52 + test/.eslintrc.json | 68 + test/data/1x1.jpg | Bin 0 -> 693 bytes test/data/1x1.svg | 6 + test/data/ajax/onunload.html | 31 + test/data/ajax/unreleasedXHR.html | 26 + test/data/badcall.js | 1 + test/data/badjson.js | 1 + test/data/cleanScript.html | 10 + test/data/core/aliased.html | 25 + test/data/core/cc_on.html | 23 + test/data/core/dynamic_ready.html | 36 + test/data/core/globaleval-context.html | 15 + .../core/jquery-iterability-transpiled-es6.js | 14 + .../core/jquery-iterability-transpiled.html | 14 + test/data/core/onready.html | 25 + test/data/csp-nonce-external.html | 13 + test/data/csp-nonce-external.js | 5 + test/data/csp-nonce-globaleval.html | 13 + test/data/csp-nonce-globaleval.js | 5 + test/data/csp-nonce.html | 13 + test/data/csp-nonce.js | 8 + test/data/csp.include.html | 14 + test/data/css/cssWidthBeforeDocReady.html | 22 + test/data/css/cssWidthBrowserZoom.html | 30 + test/data/dashboard.xml | 11 + test/data/data/dataAttrs.html | 17 + test/data/dimensions/documentLarge.html | 21 + test/data/event/focusElem.html | 17 + test/data/event/focusinCrossFrame.html | 19 + test/data/event/interactiveReady.html | 24 + test/data/event/onbeforeunload.html | 20 + test/data/event/promiseReady.html | 18 + test/data/event/syncReady.html | 24 + test/data/event/triggerunload.html | 19 + test/data/frame.html | 9 + test/data/iframe.html | 8 + test/data/iframeTest.js | 7 + test/data/inner_module.js | 1 + test/data/inner_nomodule.js | 1 + test/data/jquery-1.9.1.js | 9885 +++++++++++ test/data/json_obj.js | 1 + test/data/manipulation/iframe-denied.html | 37 + test/data/manipulation/scripts-context.html | 15 + .../manipulation/set-global-scripttest.js | 2 + test/data/mock.php | 269 + test/data/module.js | 1 + test/data/name.html | 1 + test/data/nomodule.js | 1 + test/data/offset/absolute.html | 43 + test/data/offset/body.html | 28 + test/data/offset/boxes.html | 99 + test/data/offset/fixed.html | 36 + test/data/offset/relative.html | 34 + test/data/offset/scroll.html | 43 + test/data/offset/static.html | 33 + test/data/offset/table.html | 45 + test/data/qunit-fixture.html | 237 + test/data/readywait.html | 76 + test/data/selector/html5_selector.html | 116 + test/data/selector/sizzle_cache.html | 25 + test/data/support/bodyBackground.html | 29 + test/data/support/csp.js | 3 + test/data/support/csp.log | 0 test/data/support/getComputedSupport.js | 14 + test/data/test.include.html | 7 + test/data/test2.html | 5 + test/data/test3.html | 5 + test/data/testinit-jsdom.js | 54 + test/data/testinit.js | 400 + test/data/testrunner.js | 95 + test/data/testsuite.css | 148 + test/data/text.txt | 12 + test/data/with_fries.xml | 25 + test/delegatetest.html | 228 + test/hovertest.html | 158 + test/index.html | 54 + .../data/gh-1764-fullscreen-iframe.css | 18 + .../data/gh-1764-fullscreen-iframe.html | 21 + test/integration/data/gh-1764-fullscreen.js | 99 + test/integration/gh-1764-fullscreen.html | 34 + test/integration/gh-2343-ie-radio-click.html | 33 + test/jquery.js | 73 + test/karma.context.html | 43 + test/karma.debug.html | 45 + test/localfile.html | 75 + test/middleware-mockserver.js | 310 + test/networkerror.html | 84 + test/node_smoke_tests/.eslintrc.json | 13 + test/node_smoke_tests/document_missing.js | 11 + test/node_smoke_tests/document_passed.js | 12 + .../document_present_originally.js | 15 + .../iterable_with_native_symbol.js | 8 + .../lib/ensure_global_not_created.js | 15 + .../lib/ensure_iterability_es6.js | 25 + test/node_smoke_tests/lib/ensure_jquery.js | 11 + test/promises_aplus_adapters/.eslintrc.json | 5 + test/promises_aplus_adapters/deferred.js | 17 + test/promises_aplus_adapters/when.js | 45 + test/unit/ajax.js | 2943 ++++ test/unit/animation.js | 261 + test/unit/attributes.js | 1753 ++ test/unit/basic.js | 281 + test/unit/callbacks.js | 397 + test/unit/core.js | 1573 ++ test/unit/css.js | 1837 +++ test/unit/data.js | 1002 ++ test/unit/deferred.js | 1164 ++ test/unit/deprecated.js | 662 + test/unit/dimensions.js | 794 + test/unit/effects.js | 2624 +++ test/unit/event.js | 3338 ++++ test/unit/exports.js | 7 + test/unit/manipulation.js | 3016 ++++ test/unit/offset.js | 843 + test/unit/queue.js | 339 + test/unit/ready.js | 166 + test/unit/selector.js | 546 + test/unit/serialize.js | 153 + test/unit/support.js | 409 + test/unit/traversing.js | 1045 ++ test/unit/tween.js | 317 + test/unit/wrap.js | 523 + test/xhtml.php | 5 + types-jquery/JQuery.d.ts | 13024 +++++++++++++++ types-jquery/JQueryStatic.d.ts | 13528 ++++++++++++++++ types-jquery/LICENSE | 21 + types-jquery/README.md | 16 + types-jquery/dist/jquery.slim.d.ts | 3 + types-jquery/index.d.ts | 34 + types-jquery/legacy.d.ts | 200 + types-jquery/misc.d.ts | 6648 ++++++++ types-jquery/package.json | 132 + 276 files changed, 86088 insertions(+) create mode 100644 .editorconfig create mode 100644 .eslintignore create mode 100644 .eslintrc-browser.json create mode 100644 .eslintrc-node.json create mode 100644 .eslintrc.json create mode 100644 .gitattributes create mode 100644 .github/ISSUE_TEMPLATE.md create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 .github/lock.yml create mode 100644 .gitignore create mode 100644 .mailmap create mode 100644 .npmignore create mode 100644 .npmrc create mode 100644 .travis.yml create mode 100644 AUTHORS.txt create mode 100644 CODE_OF_CONDUCT.md create mode 100644 CONTRIBUTING.md create mode 100644 Gruntfile.js create mode 100644 LICENSE.txt create mode 100644 README.md create mode 100644 build/fixtures/README.md create mode 100644 build/release.js create mode 100644 build/release/cdn.js create mode 100644 build/release/dist.js create mode 100644 build/release/ensure-sizzle.js create mode 100644 build/tasks/build.js create mode 100644 build/tasks/dist.js create mode 100644 build/tasks/lib/spawn_test.js create mode 100644 build/tasks/node_smoke_tests.js create mode 100644 build/tasks/promises_aplus_tests.js create mode 100644 build/tasks/qunit_fixture.js create mode 100644 build/tasks/sourcemap.js create mode 100644 build/tasks/testswarm.js create mode 100644 package.json create mode 100644 src/.eslintrc.json create mode 100644 src/ajax.js create mode 100644 src/ajax/jsonp.js create mode 100644 src/ajax/load.js create mode 100644 src/ajax/script.js create mode 100644 src/ajax/var/location.js create mode 100644 src/ajax/var/nonce.js create mode 100644 src/ajax/var/rquery.js create mode 100644 src/ajax/xhr.js create mode 100644 src/attributes.js create mode 100644 src/attributes/attr.js create mode 100644 src/attributes/classes.js create mode 100644 src/attributes/prop.js create mode 100644 src/attributes/support.js create mode 100644 src/attributes/val.js create mode 100644 src/callbacks.js create mode 100644 src/core.js create mode 100644 src/core/DOMEval.js create mode 100644 src/core/access.js create mode 100644 src/core/camelCase.js create mode 100644 src/core/init.js create mode 100644 src/core/isAttached.js create mode 100644 src/core/nodeName.js create mode 100644 src/core/parseHTML.js create mode 100644 src/core/parseXML.js create mode 100644 src/core/ready-no-deferred.js create mode 100644 src/core/ready.js create mode 100644 src/core/readyException.js create mode 100644 src/core/stripAndCollapse.js create mode 100644 src/core/support.js create mode 100644 src/core/toType.js create mode 100644 src/core/var/rsingleTag.js create mode 100644 src/css.js create mode 100644 src/css/addGetHookIf.js create mode 100644 src/css/adjustCSS.js create mode 100644 src/css/curCSS.js create mode 100644 src/css/finalPropName.js create mode 100644 src/css/hiddenVisibleSelectors.js create mode 100644 src/css/showHide.js create mode 100644 src/css/support.js create mode 100644 src/css/var/cssExpand.js create mode 100644 src/css/var/getStyles.js create mode 100644 src/css/var/isHiddenWithinTree.js create mode 100644 src/css/var/rboxStyle.js create mode 100644 src/css/var/rnumnonpx.js create mode 100644 src/css/var/swap.js create mode 100644 src/data.js create mode 100644 src/data/Data.js create mode 100644 src/data/var/acceptData.js create mode 100644 src/data/var/dataPriv.js create mode 100644 src/data/var/dataUser.js create mode 100644 src/deferred.js create mode 100644 src/deferred/exceptionHook.js create mode 100644 src/deprecated.js create mode 100644 src/deprecated/ajax-event-alias.js create mode 100644 src/deprecated/event.js create mode 100644 src/dimensions.js create mode 100644 src/effects.js create mode 100644 src/effects/Tween.js create mode 100644 src/effects/animatedSelector.js create mode 100644 src/event.js create mode 100644 src/event/focusin.js create mode 100644 src/event/support.js create mode 100644 src/event/trigger.js create mode 100644 src/exports/amd.js create mode 100644 src/exports/global.js create mode 100644 src/jquery.js create mode 100644 src/manipulation.js create mode 100644 src/manipulation/_evalUrl.js create mode 100644 src/manipulation/buildFragment.js create mode 100644 src/manipulation/getAll.js create mode 100644 src/manipulation/setGlobalEval.js create mode 100644 src/manipulation/support.js create mode 100644 src/manipulation/var/rscriptType.js create mode 100644 src/manipulation/var/rtagName.js create mode 100644 src/manipulation/wrapMap.js create mode 100644 src/offset.js create mode 100644 src/queue.js create mode 100644 src/queue/delay.js create mode 100644 src/selector-native.js create mode 100644 src/selector-sizzle.js create mode 100644 src/selector.js create mode 100644 src/serialize.js create mode 100644 src/traversing.js create mode 100644 src/traversing/findFilter.js create mode 100644 src/traversing/var/dir.js create mode 100644 src/traversing/var/rneedsContext.js create mode 100644 src/traversing/var/siblings.js create mode 100644 src/var/ObjectFunctionString.js create mode 100644 src/var/arr.js create mode 100644 src/var/class2type.js create mode 100644 src/var/document.js create mode 100644 src/var/documentElement.js create mode 100644 src/var/flat.js create mode 100644 src/var/fnToString.js create mode 100644 src/var/getProto.js create mode 100644 src/var/hasOwn.js create mode 100644 src/var/indexOf.js create mode 100644 src/var/isFunction.js create mode 100644 src/var/isWindow.js create mode 100644 src/var/pnum.js create mode 100644 src/var/push.js create mode 100644 src/var/rcheckableType.js create mode 100644 src/var/rcssNum.js create mode 100644 src/var/rnothtmlwhite.js create mode 100644 src/var/slice.js create mode 100644 src/var/support.js create mode 100644 src/var/toString.js create mode 100644 src/wrap.js create mode 100644 src/wrapper.js create mode 100644 test/.eslintrc.json create mode 100644 test/data/1x1.jpg create mode 100644 test/data/1x1.svg create mode 100644 test/data/ajax/onunload.html create mode 100644 test/data/ajax/unreleasedXHR.html create mode 100644 test/data/badcall.js create mode 100644 test/data/badjson.js create mode 100644 test/data/cleanScript.html create mode 100644 test/data/core/aliased.html create mode 100644 test/data/core/cc_on.html create mode 100644 test/data/core/dynamic_ready.html create mode 100644 test/data/core/globaleval-context.html create mode 100644 test/data/core/jquery-iterability-transpiled-es6.js create mode 100644 test/data/core/jquery-iterability-transpiled.html create mode 100644 test/data/core/onready.html create mode 100644 test/data/csp-nonce-external.html create mode 100644 test/data/csp-nonce-external.js create mode 100644 test/data/csp-nonce-globaleval.html create mode 100644 test/data/csp-nonce-globaleval.js create mode 100644 test/data/csp-nonce.html create mode 100644 test/data/csp-nonce.js create mode 100644 test/data/csp.include.html create mode 100644 test/data/css/cssWidthBeforeDocReady.html create mode 100644 test/data/css/cssWidthBrowserZoom.html create mode 100644 test/data/dashboard.xml create mode 100644 test/data/data/dataAttrs.html create mode 100644 test/data/dimensions/documentLarge.html create mode 100644 test/data/event/focusElem.html create mode 100644 test/data/event/focusinCrossFrame.html create mode 100644 test/data/event/interactiveReady.html create mode 100644 test/data/event/onbeforeunload.html create mode 100644 test/data/event/promiseReady.html create mode 100644 test/data/event/syncReady.html create mode 100644 test/data/event/triggerunload.html create mode 100644 test/data/frame.html create mode 100644 test/data/iframe.html create mode 100644 test/data/iframeTest.js create mode 100644 test/data/inner_module.js create mode 100644 test/data/inner_nomodule.js create mode 100644 test/data/jquery-1.9.1.js create mode 100644 test/data/json_obj.js create mode 100644 test/data/manipulation/iframe-denied.html create mode 100644 test/data/manipulation/scripts-context.html create mode 100644 test/data/manipulation/set-global-scripttest.js create mode 100644 test/data/mock.php create mode 100644 test/data/module.js create mode 100644 test/data/name.html create mode 100644 test/data/nomodule.js create mode 100644 test/data/offset/absolute.html create mode 100644 test/data/offset/body.html create mode 100644 test/data/offset/boxes.html create mode 100644 test/data/offset/fixed.html create mode 100644 test/data/offset/relative.html create mode 100644 test/data/offset/scroll.html create mode 100644 test/data/offset/static.html create mode 100644 test/data/offset/table.html create mode 100644 test/data/qunit-fixture.html create mode 100644 test/data/readywait.html create mode 100644 test/data/selector/html5_selector.html create mode 100644 test/data/selector/sizzle_cache.html create mode 100644 test/data/support/bodyBackground.html create mode 100644 test/data/support/csp.js create mode 100755 test/data/support/csp.log create mode 100644 test/data/support/getComputedSupport.js create mode 100644 test/data/test.include.html create mode 100644 test/data/test2.html create mode 100644 test/data/test3.html create mode 100644 test/data/testinit-jsdom.js create mode 100644 test/data/testinit.js create mode 100644 test/data/testrunner.js create mode 100644 test/data/testsuite.css create mode 100644 test/data/text.txt create mode 100644 test/data/with_fries.xml create mode 100644 test/delegatetest.html create mode 100644 test/hovertest.html create mode 100644 test/index.html create mode 100644 test/integration/data/gh-1764-fullscreen-iframe.css create mode 100644 test/integration/data/gh-1764-fullscreen-iframe.html create mode 100644 test/integration/data/gh-1764-fullscreen.js create mode 100644 test/integration/gh-1764-fullscreen.html create mode 100644 test/integration/gh-2343-ie-radio-click.html create mode 100644 test/jquery.js create mode 100644 test/karma.context.html create mode 100644 test/karma.debug.html create mode 100644 test/localfile.html create mode 100644 test/middleware-mockserver.js create mode 100644 test/networkerror.html create mode 100644 test/node_smoke_tests/.eslintrc.json create mode 100644 test/node_smoke_tests/document_missing.js create mode 100644 test/node_smoke_tests/document_passed.js create mode 100644 test/node_smoke_tests/document_present_originally.js create mode 100644 test/node_smoke_tests/iterable_with_native_symbol.js create mode 100644 test/node_smoke_tests/lib/ensure_global_not_created.js create mode 100644 test/node_smoke_tests/lib/ensure_iterability_es6.js create mode 100644 test/node_smoke_tests/lib/ensure_jquery.js create mode 100644 test/promises_aplus_adapters/.eslintrc.json create mode 100644 test/promises_aplus_adapters/deferred.js create mode 100644 test/promises_aplus_adapters/when.js create mode 100644 test/unit/ajax.js create mode 100644 test/unit/animation.js create mode 100644 test/unit/attributes.js create mode 100644 test/unit/basic.js create mode 100644 test/unit/callbacks.js create mode 100644 test/unit/core.js create mode 100644 test/unit/css.js create mode 100644 test/unit/data.js create mode 100644 test/unit/deferred.js create mode 100644 test/unit/deprecated.js create mode 100644 test/unit/dimensions.js create mode 100644 test/unit/effects.js create mode 100644 test/unit/event.js create mode 100644 test/unit/exports.js create mode 100644 test/unit/manipulation.js create mode 100644 test/unit/offset.js create mode 100644 test/unit/queue.js create mode 100644 test/unit/ready.js create mode 100644 test/unit/selector.js create mode 100644 test/unit/serialize.js create mode 100644 test/unit/support.js create mode 100644 test/unit/traversing.js create mode 100644 test/unit/tween.js create mode 100644 test/unit/wrap.js create mode 100644 test/xhtml.php create mode 100755 types-jquery/JQuery.d.ts create mode 100755 types-jquery/JQueryStatic.d.ts create mode 100755 types-jquery/LICENSE create mode 100755 types-jquery/README.md create mode 100755 types-jquery/dist/jquery.slim.d.ts create mode 100755 types-jquery/index.d.ts create mode 100755 types-jquery/legacy.d.ts create mode 100755 types-jquery/misc.d.ts create mode 100755 types-jquery/package.json diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..b5bd7f6 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,16 @@ +# This file is for unifying the coding style for different editors and IDEs +# editorconfig.org + +root = true + +[*] +indent_style = tab +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[package.json] +indent_style = space +indent_size = 2 + diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..2d52f5c --- /dev/null +++ b/.eslintignore @@ -0,0 +1,15 @@ +external +node_modules +*.min.js +dist/** +!dist/jquery.js +!dist/jquery.min.js +test/data/jquery-1.9.1.js +test/data/badcall.js +test/data/badjson.js +test/data/json_obj.js +test/data/readywaitasset.js +test/data/readywaitloader.js +test/data/support/csp.js +test/data/support/getComputedSupport.js +test/data/core/jquery-iterability-transpiled.js diff --git a/.eslintrc-browser.json b/.eslintrc-browser.json new file mode 100644 index 0000000..d27ff6e --- /dev/null +++ b/.eslintrc-browser.json @@ -0,0 +1,32 @@ +{ + "root": true, + + "extends": "jquery", + + "reportUnusedDisableDirectives": true, + + // Support: IE <=9 only, Android <=4.0 only + // The above browsers are failing a lot of tests in the ES5 + // test suite at http://test262.ecmascript.org. + "parserOptions": { + "ecmaVersion": 3 + }, + + // The browser env is not enabled on purpose so that code takes + // all browser-only globals from window instead of assuming + // they're available as globals. This makes it possible to use + // jQuery with tools like jsdom which provide a custom window + // implementation. + "env": {}, + + "globals": { + "window": true, + "define": true, + "module": true + }, + + "rules": { + "one-var": ["error", {"var": "always"}], + "strict": ["error", "function"] + } +} diff --git a/.eslintrc-node.json b/.eslintrc-node.json new file mode 100644 index 0000000..df9cc24 --- /dev/null +++ b/.eslintrc-node.json @@ -0,0 +1,20 @@ +{ + "root": true, + + "extends": "jquery", + + "reportUnusedDisableDirectives": true, + + "parserOptions": { + "ecmaVersion": 2017 + }, + + "env": { + "es6": true, + "node": true + }, + + "rules": { + "strict": ["error", "global"] + } +} diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..d2c977c --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,5 @@ +{ + "root": true, + + "extends": "./.eslintrc-node.json" +} diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..b7ca95b --- /dev/null +++ b/.gitattributes @@ -0,0 +1,5 @@ +# Auto detect text files and perform LF normalization +* text=auto + +# JS files must always use LF for tools to work +*.js eol=lf diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 0000000..58b8cbe --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,24 @@ + + +### Description ### + + +### Link to test case ### + diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..98d2331 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,20 @@ +### Summary ### + + + +### Checklist ### + + +* [ ] All authors have signed the CLA at https://cla.js.foundation/jquery/jquery +* [ ] New tests have been added to show the fix or feature works +* [ ] Grunt build and unit tests pass locally with these changes +* [ ] If needed, a docs issue/PR was created at https://github.com/jquery/api.jquery.com + + diff --git a/.github/lock.yml b/.github/lock.yml new file mode 100644 index 0000000..04e8a2f --- /dev/null +++ b/.github/lock.yml @@ -0,0 +1,13 @@ +# Configuration for lock-threads - https://github.com/dessant/lock-threads + +# Number of days of inactivity before a closed issue or pull request is locked +daysUntilLock: 180 + +# Issues and pull requests with these labels will not be locked. Set to `[]` to disable +exemptLabels: [] + +# Label to add before locking, such as `outdated`. Set to `false` to disable +lockLabel: false + +# Comment to post before locking. Set to `false` to disable +lockComment: false diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..44a8689 --- /dev/null +++ b/.gitignore @@ -0,0 +1,22 @@ +.project +.settings +*~ +*.diff +*.patch +/*.html +.DS_Store +.bower.json +.sizecache.json +yarn.lock +package-lock.json + +npm-debug.log* + +# Ignore everything in dist folder except for eslint config +/dist/* +!/dist/.eslintrc.json + +/node_modules + +/test/data/core/jquery-iterability-transpiled.js +/test/data/qunit-fixture.js diff --git a/.mailmap b/.mailmap new file mode 100644 index 0000000..365dafc --- /dev/null +++ b/.mailmap @@ -0,0 +1,116 @@ +Adam Coulombe +Adam J. Sontag +Alexander Farkas +Alexander Farkas +Alexis Abril +Andrew E Monat +Andrey Meshkov +Andrey Meshkov +Anton Matzneller +Anton Matzneller +Batiste Bieler +Benjamin Truyman +Brandon Aaron +Carl Danley +Carl Fürstenberg +Carl Fürstenberg +Charles McNulty +Chris Rebert +Chris Rebert +Christian Oliff +Christian Oliff +Christopher Jones cjqed +Colin Snover +Corey Frang +Dan Heberden +Daniel Chatfield +Daniel Gálvez +Danil Somsikov +Dave Methvin +Dave Reed +David Fox +David Hong +David Murdoch +Devin Cooper +Douglas Neiner +Dmitry Gusev +Earle Castledine +Erick Ruiz de Chávez +Gianni Alessandro Chiappetta +Heungsub Lee +Iraê Carvalho +Isaac Z. Schlueter +Ismail Khair +James Burke +James Padolsey +Jason Bedard +Jay Merrifield +Jay Merrifield +Jean Boussier +Jephte Clain +Jess Thrysoee +Joao Henrique de Andrade Bruni +Joe Presbrey +John Resig +John Resig +Jordan Boesch +Josh Varner +Julian Aubourg +Julian Aubourg +Julian Aubourg +John Firebaugh +John Firebaugh +Jörn Zaefferer +Jörn Zaefferer +Jörn Zaefferer +Karl Swedberg +Klaus Hartl +Kris Borchers +Lee Carpenter +Li Xudong +Louis-Rémi Babé +Louis-Rémi Babé +Louis-Rémi Babé +Louis-Rémi Babé +Marcel Greter +Matthias Jäggli +Michael Murray +Michał Gołębiowski-Owczarek +Michał Gołębiowski-Owczarek +Mike Alsup +Nguyen Phuc Lam +Oleg Gaidarenko +Paul Bakaus +Rafaël Blais Masson +Richard D. Worth +Rick Waldron +Rick Waldron +Robert Katić +Roman Reiß +Ron Otten +Sai Lung Wong +Scott González +Scott Jehl +Sebastian Burkhard +Senya Pugach +Shashanka Nataraj +Shashanka Nataraj +Thomas Tortorini Mr21 +Timmy Willison <4timmywil@gmail.com> +Timmy Willison <4timmywil@gmail.com> +Timmy Willison <4timmywil@gmail.com> +Timmy Willison <4timmywil@gmail.com> +Timo Tijhof +TJ Holowaychuk +Tom H Fuertes +Tom H Fuertes Tom H Fuertes +Tom Viner +Wesley Walser +Xavi Ramirez +Xavier Montillet +Yehuda Katz +Yehuda Katz +Yehuda Katz +Yehuda Katz +Yiming He +Terry Jones diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..c95a77e --- /dev/null +++ b/.npmignore @@ -0,0 +1,16 @@ +.eslintignore +.eslintrc.json + +/.editorconfig +/.gitattributes +/.mailmap +/.travis.yml + +/build +/speed +/test +/Gruntfile.js + +/external/qunit +/external/requirejs +/external/sinon diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..cffe8cd --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +save-exact=true diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..65bbc37 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,56 @@ +language: node_js +os: linux +node_js: +- "10" +- "12" +- "14" +- "15" +env: + - NPM_SCRIPT=test:browserless +jobs: + include: + - name: "Browser tests: full build, Chrome & Firefox stable" + node_js: "14" + env: + - NPM_SCRIPT="test:browser" + - BROWSERS="ChromeHeadless,FirefoxHeadless" + addons: + chrome: stable + firefox: latest + - name: "Browser tests: slim build, Chrome stable" + node_js: "14" + env: + - NPM_SCRIPT="test:slim" + - BROWSERS="ChromeHeadless" + addons: + chrome: stable + - name: "Browser tests: no-deprecated build, Chrome stable" + node_js: "14" + env: + - NPM_SCRIPT="test:no-deprecated" + - BROWSERS="ChromeHeadless" + addons: + chrome: stable + - name: "Browser tests: no-Sizzle build, Chrome stable" + node_js: "14" + env: + - NPM_SCRIPT="test:no-sizzle" + - BROWSERS="ChromeHeadless" + addons: + chrome: stable + - name: "Browser tests: AMD build, Chrome stable" + node_js: "14" + env: + - NPM_SCRIPT="test:amd" + - BROWSERS="ChromeHeadless" + addons: + chrome: stable + - name: "Browser tests: full build, Firefox ESR" + node_js: "14" + env: + - NPM_SCRIPT="test:browser" + - BROWSERS="FirefoxHeadless" + addons: + firefox: latest-esr +script: + - npm run $NPM_SCRIPT diff --git a/AUTHORS.txt b/AUTHORS.txt new file mode 100644 index 0000000..0d44990 --- /dev/null +++ b/AUTHORS.txt @@ -0,0 +1,331 @@ +John Resig +Gilles van den Hoven +Michael Geary +Stefan Petre +Yehuda Katz +Corey Jewett +Klaus Hartl +Franck Marcia +Jörn Zaefferer +Paul Bakaus +Brandon Aaron +Mike Alsup +Dave Methvin +Ed Engelhardt +Sean Catchpole +Paul Mclanahan +David Serduke +Richard D. Worth +Scott González +Ariel Flesler +Jon Evans +TJ Holowaychuk +Michael Bensoussan +Robert Katić +Louis-Rémi Babé +Earle Castledine +Damian Janowski +Rich Dougherty +Kim Dalsgaard +Andrea Giammarchi +Mark Gibson +Karl Swedberg +Justin Meyer +Ben Alman +James Padolsey +David Petersen +Batiste Bieler +Alexander Farkas +Rick Waldron +Filipe Fortes +Neeraj Singh +Paul Irish +Iraê Carvalho +Matt Curry +Michael Monteleone +Noah Sloan +Tom Viner +Douglas Neiner +Adam J. Sontag +Dave Reed +Ralph Whitbeck +Carl Fürstenberg +Jacob Wright +J. Ryan Stinnett +unknown +temp01 +Heungsub Lee +Colin Snover +Ryan W Tenney +Pinhook +Ron Otten +Jephte Clain +Anton Matzneller +Alex Sexton +Dan Heberden +Henri Wiechers +Russell Holbrook +Julian Aubourg +Gianni Alessandro Chiappetta +Scott Jehl +James Burke +Jonas Pfenniger +Xavi Ramirez +Jared Grippe +Sylvester Keil +Brandon Sterne +Mathias Bynens +Timmy Willison <4timmywil@gmail.com> +Corey Frang +Digitalxero +Anton Kovalyov +David Murdoch +Josh Varner +Charles McNulty +Jordan Boesch +Jess Thrysoee +Michael Murray +Lee Carpenter +Alexis Abril +Rob Morgan +John Firebaugh +Sam Bisbee +Gilmore Davidson +Brian Brennan +Xavier Montillet +Daniel Pihlstrom +Sahab Yazdani +avaly +Scott Hughes +Mike Sherov +Greg Hazel +Schalk Neethling +Denis Knauf +Timo Tijhof +Steen Nielsen +Anton Ryzhov +Shi Chuan +Berker Peksag +Toby Brain +Matt Mueller +Justin +Daniel Herman +Oleg Gaidarenko +Richard Gibson +Rafaël Blais Masson +cmc3cn <59194618@qq.com> +Joe Presbrey +Sindre Sorhus +Arne de Bree +Vladislav Zarakovsky +Andrew E Monat +Oskari +Joao Henrique de Andrade Bruni +tsinha +Matt Farmer +Trey Hunner +Jason Moon +Jeffery To +Kris Borchers +Vladimir Zhuravlev +Jacob Thornton +Chad Killingsworth +Nowres Rafid +David Benjamin +Uri Gilad +Chris Faulkner +Elijah Manor +Daniel Chatfield +Nikita Govorov +Wesley Walser +Mike Pennisi +Markus Staab +Dave Riddle +Callum Macrae +Benjamin Truyman +James Huston +Erick Ruiz de Chávez +David Bonner +Akintayo Akinwunmi +MORGAN +Ismail Khair +Carl Danley +Mike Petrovich +Greg Lavallee +Daniel Gálvez +Sai Lung Wong +Tom H Fuertes +Roland Eckl +Jay Merrifield +Allen J Schmidt Jr +Jonathan Sampson +Marcel Greter +Matthias Jäggli +David Fox +Yiming He +Devin Cooper +Paul Ramos +Rod Vagg +Bennett Sorbo +Sebastian Burkhard +Zachary Adam Kaplan +nanto_vi +nanto +Danil Somsikov +Ryunosuke SATO +Jean Boussier +Adam Coulombe +Andrew Plummer +Mark Raddatz +Isaac Z. Schlueter +Karl Sieburg +Pascal Borreli +Nguyen Phuc Lam +Dmitry Gusev +Michał Gołębiowski-Owczarek +Li Xudong +Steven Benner +Tom H Fuertes +Renato Oliveira dos Santos +ros3cin +Jason Bedard +Kyle Robinson Young +Chris Talkington +Eddie Monge +Terry Jones +Jason Merino +Jeremy Dunck +Chris Price +Guy Bedford +Amey Sakhadeo +Mike Sidorov +Anthony Ryan +Dominik D. Geyer +George Kats +Lihan Li +Ronny Springer +Chris Antaki +Marian Sollmann +njhamann +Ilya Kantor +David Hong +John Paul +Jakob Stoeck +Christopher Jones +Forbes Lindesay +S. Andrew Sheppard +Leonardo Balter +Roman Reiß +Benjy Cui +Rodrigo Rosenfeld Rosas +John Hoven +Philip Jägenstedt +Christian Kosmowski +Liang Peng +TJ VanToll +Senya Pugach +Aurelio De Rosa +Nazar Mokrynskyi +Amit Merchant +Jason Bedard +Arthur Verschaeve +Dan Hart +Bin Xin +David Corbacho +Veaceslav Grimalschi +Daniel Husar +Frederic Hemberger +Ben Toews +Aditya Raghavan +Victor Homyakov +Shivaji Varma +Nicolas HENRY +Anne-Gaelle Colom +George Mauer +Leonardo Braga +Stephen Edgar +Thomas Tortorini +Winston Howes +Jon Hester +Alexander O'Mara +Bastian Buchholz +Arthur Stolyar +Calvin Metcalf +Mu Haibao +Richard McDaniel +Chris Rebert +Gabriel Schulhof +Gilad Peleg +Martin Naumann +Marek Lewandowski +Bruno Pérel +Reed Loden +Daniel Nill +Yongwoo Jeon +Sean Henderson +Richard Kraaijenhagen +Connor Atherton +Gary Ye +Christian Grete +Liza Ramo +Julian Alexander Murillo +Joelle Fleurantin +Jae Sung Park +Jun Sun +Josh Soref +Henry Wong +Jon Dufresne +Martijn W. van der Lee +Devin Wilson +Steve Mao +Zack Hall +Bernhard M. Wiedemann +Todor Prikumov +Jha Naman +William Robinet +Alexander Lisianoi +Vitaliy Terziev +Joe Trumbull +Alexander K +Damian Senn +Ralin Chimev +Felipe Sateler +Christophe Tafani-Dereeper +Manoj Kumar +David Broder-Rodgers +Alex Louden +Alex Padilla +南漂一卒 +karan-96 +Boom Lee +Andreas Solleder +CDAGaming +Pierre Spring +Shashanka Nataraj +Erik Lax +Matan Kotler-Berkowitz <205matan@gmail.com> +Jordan Beland +Henry Zhu +Saptak Sengupta +Nilton Cesar +basil.belokon +tmybr11 +Luis Emilio Velasco Sanchez +Ed S +Bert Zhang +Andrei Fangli +Marja Hölttä +abnud1 +buddh4 +Pat O'Callaghan +Ahmed.S.ElAfifi +Wonseop Kim +Christian Oliff +Christian Wenz +Sean Robinson +Jonathan +Pierre Grimaud +Beatriz Rezener +Natalia Sroka <37873210+natipo@users.noreply.github.com> +Wonhyoung Park +Dallas Fraser diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..1dc3de9 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,3 @@ +jQuery is an [OpenJS Foundation](https://openjsf.org/) project and subscribes to its code of conduct. + +It is available at https://code-of-conduct.openjsf.org/. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..71fc258 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,145 @@ +# Contributing to jQuery + +1. [Getting Involved](#getting-involved) +2. [Questions and Discussion](#questions-and-discussion) +3. [How To Report Bugs](#how-to-report-bugs) +4. [Tips for Bug Patching](#tips-for-bug-patching) + +Note: This is the code development repository for *jQuery Core* only. Before opening an issue or making a pull request, be sure you're in the right place. +* jQuery plugin issues should be reported to the author of the plugin. +* jQuery Core API documentation issues can be filed [at the API repo](https://github.com/jquery/api.jquery.com/issues). +* Bugs or suggestions for other jQuery organization projects should be filed in [their respective repos](https://github.com/jquery/). + +## Getting Involved + +[API design principles](https://github.com/jquery/jquery/wiki/API-design-guidelines) + +We're always looking for help [identifying bugs](#how-to-report-bugs), writing and reducing test cases, and improving documentation. And although new features are rare, anything passing our [guidelines](https://github.com/jquery/jquery/wiki/Adding-new-features) will be considered. + +More information on how to contribute to this and other jQuery organization projects is at [contribute.jquery.org](https://contribute.jquery.org), including a short guide with tips, tricks, and ideas on [getting started with open source](https://contribute.jquery.org/open-source/). Please review our [commit & pull request guide](https://contribute.jquery.org/commits-and-pull-requests/) and [style guides](https://contribute.jquery.org/style-guide/) for instructions on how to maintain a fork and submit patches. Before we can merge any pull request, we'll also need you to sign our [contributor license agreement](https://contribute.jquery.org/cla/). + + +## Questions and Discussion + +### Forum and IRC + +jQuery is so popular that many developers have knowledge of its capabilities and limitations. Most questions about using jQuery can be answered on popular forums such as [Stack Overflow](https://stackoverflow.com). Please start there when you have questions, even if you think you've found a bug. + +The jQuery Core team watches the [jQuery Development Forum](https://forum.jquery.com/developing-jquery-core). If you have longer posts or questions that can't be answered in places such as Stack Overflow, please feel free to post them there. If you think you've found a bug, please [file it in the bug tracker](#how-to-report-bugs). The Core team can be found in the [#jquery-dev](https://webchat.freenode.net/?channels=jquery-dev) IRC channel on irc.freenode.net. + +### Weekly Status Meetings + +The jQuery Core team has a weekly meeting to discuss the progress of current work. The meeting is held in the [#jquery-meeting](https://webchat.freenode.net/?channels=jquery-meeting) IRC channel on irc.freenode.net at [Noon EST](https://www.timeanddate.com/worldclock/fixedtime.html?month=1&day=17&year=2011&hour=12&min=0&sec=0&p1=43) on Mondays. + +[jQuery Core Meeting Notes](https://meetings.jquery.org/category/core/) + + +## How to Report Bugs + +### Make sure it is a jQuery bug + +Most bugs reported to our bug tracker are actually bugs in user code, not in jQuery code. Keep in mind that just because your code throws an error inside of jQuery, this does *not* mean the bug is a jQuery bug. + +Ask for help first in the [Using jQuery Forum](https://forum.jquery.com/using-jquery) or another discussion forum like [Stack Overflow](https://stackoverflow.com/). You will get much quicker support, and you will help avoid tying up the jQuery team with invalid bug reports. + +### Disable browser extensions + +Make sure you have reproduced the bug with all browser extensions and add-ons disabled, as these can sometimes cause things to break in interesting and unpredictable ways. Try using incognito, stealth or anonymous browsing modes. + +### Try the latest version of jQuery + +Bugs in old versions of jQuery may have already been fixed. In order to avoid reporting known issues, make sure you are always testing against the [latest build](https://code.jquery.com/jquery.js). We cannot fix bugs in older released files, if a bug has been fixed in a subsequent version of jQuery the site should upgrade. + +### Simplify the test case + +When experiencing a problem, [reduce your code](https://webkit.org/quality/reduction.html) to the bare minimum required to reproduce the issue. This makes it *much* easier to isolate and fix the offending code. Bugs reported without reduced test cases take on average 9001% longer to fix than bugs that are submitted with them, so you really should try to do this if at all possible. + +### Search for related or duplicate issues + +Go to the [jQuery Core issue tracker](https://github.com/jquery/jquery/issues) and make sure the problem hasn't already been reported. If not, create a new issue there and include your test case. + + +## Tips For Bug Patching + +We *love* when people contribute back to the project by patching the bugs they find. Since jQuery is used by so many people, we are cautious about the patches we accept and want to be sure they don't have a negative impact on the millions of people using jQuery each day. For that reason it can take a while for any suggested patch to work its way through the review and release process. The reward for you is knowing that the problem you fixed will improve things for millions of sites and billions of visits per day. + +### Build a Local Copy of jQuery + +Create a fork of the jQuery repo on github at https://github.com/jquery/jquery + +Change directory to your web root directory, whatever that might be: + +```bash +$ cd /path/to/your/www/root/ +``` + +Clone your jQuery fork to work locally + +```bash +$ git clone git@github.com:username/jquery.git +``` + +Change directory to the newly created dir jquery/ + +```bash +$ cd jquery +``` + +Add the jQuery main as a remote. I label mine "upstream" + +```bash +$ git remote add upstream git://github.com/jquery/jquery.git +``` + +Get in the habit of pulling in the "upstream" main to stay up to date as jQuery receives new commits + +```bash +$ git pull upstream main +``` + +Run the build script + +```bash +$ npm run build +``` + +Now open the jQuery test suite in a browser at http://localhost/test. If there is a port, be sure to include it. + +Success! You just built and tested jQuery! + + +### Test Suite Tips... + +During the process of writing your patch, you will run the test suite MANY times. You can speed up the process by narrowing the running test suite down to the module you are testing by either double clicking the title of the test or appending it to the url. The following examples assume you're working on a local repo, hosted on your localhost server. + +Example: + +http://localhost/test/?module=css + +This will only run the "css" module tests. This will significantly speed up your development and debugging. + +**ALWAYS RUN THE FULL SUITE BEFORE COMMITTING AND PUSHING A PATCH!** + + +#### Loading changes on the test page + +Rather than rebuilding jQuery with `grunt` every time you make a change, you can use the included `grunt watch` task to rebuild distribution files whenever a file is saved. + +```bash +$ grunt watch +``` + +Alternatively, you can **load tests in AMD** to avoid the need for rebuilding altogether. + +Click "Load with AMD" after loading the test page. + + +### Repo organization + +The jQuery source is organized with AMD modules and then concatenated and compiled at build time. + +jQuery also contains some special modules we call "var modules", which are placed in folders named "var". At build time, these small modules are compiled to simple var statements. This makes it easy for us to share variables across modules. Browse the "src" folder for examples. + +### Browser support + +Remember that jQuery supports multiple browsers and their versions; any contributed code must work in all of them. You can refer to the [browser support page](https://jquery.com/browser-support/) for the current list of supported browsers. diff --git a/Gruntfile.js b/Gruntfile.js new file mode 100644 index 0000000..25223ae --- /dev/null +++ b/Gruntfile.js @@ -0,0 +1,386 @@ +"use strict"; + +module.exports = function( grunt ) { + function readOptionalJSON( filepath ) { + var stripJSONComments = require( "strip-json-comments" ), + data = {}; + try { + data = JSON.parse( stripJSONComments( + fs.readFileSync( filepath, { encoding: "utf8" } ) + ) ); + } catch ( e ) {} + return data; + } + + var fs = require( "fs" ), + gzip = require( "gzip-js" ), + isTravis = process.env.TRAVIS, + travisBrowsers = process.env.BROWSERS && process.env.BROWSERS.split( "," ); + + if ( !grunt.option( "filename" ) ) { + grunt.option( "filename", "jquery.js" ); + } + + grunt.initConfig( { + pkg: grunt.file.readJSON( "package.json" ), + dst: readOptionalJSON( "dist/.destination.json" ), + "compare_size": { + files: [ "dist/jquery.js", "dist/jquery.min.js" ], + options: { + compress: { + gz: function( contents ) { + return gzip.zip( contents, {} ).length; + } + }, + cache: "build/.sizecache.json" + } + }, + babel: { + options: { + sourceMap: "inline", + retainLines: true, + plugins: [ "@babel/transform-for-of" ] + }, + tests: { + files: { + "test/data/core/jquery-iterability-transpiled.js": + "test/data/core/jquery-iterability-transpiled-es6.js" + } + } + }, + build: { + all: { + dest: "dist/jquery.js", + minimum: [ + "core", + "selector" + ], + + // Exclude specified modules if the module matching the key is removed + removeWith: { + ajax: [ "manipulation/_evalUrl", "deprecated/ajax-event-alias" ], + callbacks: [ "deferred" ], + css: [ "effects", "dimensions", "offset" ], + "css/showHide": [ "effects" ], + deferred: { + remove: [ "ajax", "effects", "queue", "core/ready" ], + include: [ "core/ready-no-deferred" ] + }, + event: [ "deprecated/ajax-event-alias", "deprecated/event" ], + sizzle: [ "css/hiddenVisibleSelectors", "effects/animatedSelector" ] + } + } + }, + npmcopy: { + all: { + options: { + destPrefix: "external" + }, + files: { + "sizzle/dist": "sizzle/dist", + "sizzle/LICENSE.txt": "sizzle/LICENSE.txt", + + "npo/npo.js": "native-promise-only/lib/npo.src.js", + + "qunit/qunit.js": "qunit/qunit/qunit.js", + "qunit/qunit.css": "qunit/qunit/qunit.css", + "qunit/LICENSE.txt": "qunit/LICENSE.txt", + + "requirejs/require.js": "requirejs/require.js", + + "sinon/sinon.js": "sinon/pkg/sinon.js", + "sinon/LICENSE.txt": "sinon/LICENSE" + } + } + }, + jsonlint: { + pkg: { + src: [ "package.json" ] + } + }, + eslint: { + options: { + + // See https://github.com/sindresorhus/grunt-eslint/issues/119 + quiet: true + }, + + // We have to explicitly declare "src" property otherwise "newer" + // task wouldn't work properly :/ + dist: { + src: [ "dist/jquery.js", "dist/jquery.min.js" ] + }, + dev: { + src: [ "src/**/*.js", "Gruntfile.js", "test/**/*.js", "build/**/*.js" ] + } + }, + testswarm: { + tests: [ + + // A special module with basic tests, meant for + // not fully supported environments like Android 2.3, + // jsdom or PhantomJS. We run it everywhere, though, + // to make sure tests are not broken. + "basic", + + "ajax", + "animation", + "attributes", + "callbacks", + "core", + "css", + "data", + "deferred", + "deprecated", + "dimensions", + "effects", + "event", + "manipulation", + "offset", + "queue", + "selector", + "serialize", + "support", + "traversing", + "tween" + ] + }, + karma: { + options: { + customContextFile: "test/karma.context.html", + customDebugFile: "test/karma.debug.html", + customLaunchers: { + ChromeHeadlessNoSandbox: { + base: "ChromeHeadless", + flags: [ "--no-sandbox" ] + } + }, + frameworks: [ "qunit" ], + middleware: [ "mockserver" ], + plugins: [ + "karma-*", + { + "middleware:mockserver": [ + "factory", + require( "./test/middleware-mockserver.js" ) + ] + } + ], + client: { + qunit: { + + // We're running `QUnit.start()` ourselves via `loadTests()` + // in test/jquery.js + autostart: false + } + }, + files: [ + "test/data/jquery-1.9.1.js", + "external/sinon/sinon.js", + "external/npo/npo.js", + "external/requirejs/require.js", + "test/data/testinit.js", + + "test/jquery.js", + + { + pattern: "dist/jquery.*", + included: false, + served: true, + nocache: true + }, + { + pattern: "src/**", + included: false, + served: true, + nocache: true + }, + { + pattern: "external/**", + included: false, + served: true, + nocache: true + }, + { pattern: "node_modules/**", included: false, served: true }, + { + pattern: "test/**/*.@(js|css|jpg|html|xml|svg)", + included: false, + served: true, + nocache: true + } + ], + reporters: [ "dots" ], + autoWatch: false, + concurrency: 3, + captureTimeout: 20 * 1000, + singleRun: true + }, + main: { + browsers: isTravis && travisBrowsers || [ "ChromeHeadless", "FirefoxHeadless" ] + }, + amd: { + browsers: isTravis && travisBrowsers || [ "ChromeHeadless" ], + options: { + client: { + qunit: { + + // We're running `QUnit.start()` ourselves via `loadTests()` + // in test/jquery.js + autostart: false, + + amd: true + } + } + } + }, + + jsdom: { + options: { + files: [ + "test/data/jquery-1.9.1.js", + "test/data/testinit-jsdom.js", + + // We don't support various loading methods like AMD, + // choosing a version etc. for jsdom. + "dist/jquery.js", + + // A partial replacement for testinit.js#loadTests() + "test/data/testrunner.js", + + // jsdom only runs basic tests + "test/unit/basic.js", + + { pattern: "external/**", included: false, served: true }, + { + pattern: "test/**/*.@(js|css|jpg|html|xml|svg)", + included: false, + served: true + } + ] + }, + browsers: [ "jsdom" ] + }, + + // To debug tests with Karma: + // 1. Run 'grunt karma:chrome-debug' or 'grunt karma:firefox-debug' + // (any karma subtask that has singleRun=false) + // 2. Press "Debug" in the opened browser window to start + // the tests. Unlike the other karma tasks, the debug task will + // keep the browser window open. + "chrome-debug": { + browsers: [ "Chrome" ], + singleRun: false + }, + "firefox-debug": { + browsers: [ "Firefox" ], + singleRun: false + }, + "ie-debug": { + browsers: [ "IE" ], + singleRun: false + } + }, + watch: { + files: [ "<%= eslint.dev.src %>" ], + tasks: [ "dev" ] + }, + uglify: { + all: { + files: { + "dist/<%= grunt.option('filename').replace('.js', '.min.js') %>": + "dist/<%= grunt.option('filename') %>" + }, + options: { + preserveComments: false, + sourceMap: true, + sourceMapName: + "dist/<%= grunt.option('filename').replace('.js', '.min.map') %>", + report: "min", + output: { + "ascii_only": true, + + // Support: Android 4.0 only + // UglifyJS 3 breaks Android 4.0 if this option is not enabled. + // This is in lieu of setting ie8 for all of mangle, compress, and output + "ie8": true + }, + banner: "/*! jQuery v<%= pkg.version %> | " + + "(c) OpenJS Foundation and other contributors | jquery.org/license */", + compress: { + "hoist_funs": false, + loops: false, + + // Support: IE <11 + // typeofs transformation is unsafe for IE9-10 + // See https://github.com/mishoo/UglifyJS2/issues/2198 + typeofs: false + } + } + } + } + } ); + + // Load grunt tasks from NPM packages + require( "load-grunt-tasks" )( grunt ); + + // Integrate jQuery specific tasks + grunt.loadTasks( "build/tasks" ); + + grunt.registerTask( "lint", [ + "jsonlint", + + // Running the full eslint task without breaking it down to targets + // would run the dist target first which would point to errors in the built + // file, making it harder to fix them. We want to check the built file only + // if we already know the source files pass the linter. + "eslint:dev", + "eslint:dist" + ] ); + + grunt.registerTask( "lint:newer", [ + "newer:jsonlint", + + // Don't replace it with just the task; see the above comment. + "newer:eslint:dev", + "newer:eslint:dist" + ] ); + + grunt.registerTask( "test:fast", "node_smoke_tests" ); + grunt.registerTask( "test:slow", [ + "promises_aplus_tests", + "karma:jsdom" + ] ); + + grunt.registerTask( "test:prepare", [ + "qunit_fixture", + "babel:tests" + ] ); + + grunt.registerTask( "test", [ + "test:prepare", + "test:fast", + "test:slow" + ] ); + + grunt.registerTask( "dev", [ + "build:*:*", + "newer:eslint:dev", + "newer:uglify", + "remove_map_comment", + "dist:*", + "qunit_fixture", + "compare_size" + ] ); + + grunt.registerTask( "default", [ + "eslint:dev", + "build:*:*", + "uglify", + "remove_map_comment", + "dist:*", + "test:prepare", + "eslint:dist", + "test:fast", + "compare_size" + ] ); +}; diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..f642c3f --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,20 @@ +Copyright OpenJS Foundation and other contributors, https://openjsf.org/ + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..f36cefe --- /dev/null +++ b/README.md @@ -0,0 +1,383 @@ +[jQuery](https://jquery.com/) — New Wave JavaScript +================================================== + +[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fjquery%2Fjquery.svg?type=shield)](https://app.fossa.io/projects/git%2Bgithub.com%2Fjquery%2Fjquery?ref=badge_shield) + +[![Gitter](https://badges.gitter.im/jquery/jquery.svg)](https://gitter.im/jquery/jquery?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) + +Contribution Guides +-------------------------------------- + +In the spirit of open source software development, jQuery always encourages community code contribution. To help you get started and before you jump into writing code, be sure to read these important contribution guidelines thoroughly: + +1. [Getting Involved](https://contribute.jquery.org/) +2. [Core Style Guide](https://contribute.jquery.org/style-guide/js/) +3. [Writing Code for jQuery Foundation Projects](https://contribute.jquery.org/code/) + + +Environments in which to use jQuery +-------------------------------------- + +- [Browser support](https://jquery.com/browser-support/) +- jQuery also supports Node, browser extensions, and other non-browser environments. + + +What you need to build your own jQuery +-------------------------------------- + +To build jQuery, you need to have the latest Node.js/npm and git 1.7 or later. Earlier versions might work, but are not supported. + +For Windows, you have to download and install [git](https://git-scm.com/downloads) and [Node.js](https://nodejs.org/en/download/). + +OS X users should install [Homebrew](https://brew.sh/). Once Homebrew is installed, run `brew install git` to install git, +and `brew install node` to install Node.js. + +Linux/BSD users should use their appropriate package managers to install git and Node.js, or build from source +if you swing that way. Easy-peasy. + + +How to build your own jQuery +---------------------------- + +Clone a copy of the main jQuery git repo by running: + +```bash +git clone git://github.com/jquery/jquery.git +``` + +Enter the jquery directory and run the build script: +```bash +cd jquery && npm run build +``` +The built version of jQuery will be put in the `dist/` subdirectory, along with the minified copy and associated map file. + +If you want to create custom build or help with jQuery development, it would be better to install [grunt command line interface](https://github.com/gruntjs/grunt-cli) as a global package: + +``` +npm install -g grunt-cli +``` +Make sure you have `grunt` installed by testing: +``` +grunt -V +``` + +Now by running the `grunt` command, in the jquery directory, you can build a full version of jQuery, just like with an `npm run build` command: +``` +grunt +``` + +There are many other tasks available for jQuery Core: +``` +grunt -help +``` + +### Modules + +Special builds can be created that exclude subsets of jQuery functionality. +This allows for smaller custom builds when the builder is certain that those parts of jQuery are not being used. +For example, an app that only used JSONP for `$.ajax()` and did not need to calculate offsets or positions of elements could exclude the offset and ajax/xhr modules. + +Any module may be excluded except for `core`, and `selector`. To exclude a module, pass its path relative to the `src` folder (without the `.js` extension). + +Some example modules that can be excluded are: + +- **ajax**: All AJAX functionality: `$.ajax()`, `$.get()`, `$.post()`, `$.ajaxSetup()`, `.load()`, transports, and ajax event shorthands such as `.ajaxStart()`. +- **ajax/xhr**: The XMLHTTPRequest AJAX transport only. +- **ajax/script**: The ` +``` + +#### Babel + +[Babel](https://babeljs.io/) is a next generation JavaScript compiler. One of the features is the ability to use ES6/ES2015 modules now, even though browsers do not yet support this feature natively. + +```js +import $ from "jquery"; +``` + +#### Browserify/Webpack + +There are several ways to use [Browserify](http://browserify.org/) and [Webpack](https://webpack.github.io/). For more information on using these tools, please refer to the corresponding project's documentation. In the script, including jQuery will usually look like this... + +```js +var $ = require( "jquery" ); +``` + +#### AMD (Asynchronous Module Definition) + +AMD is a module format built for the browser. For more information, we recommend [require.js' documentation](https://requirejs.org/docs/whyamd.html). + +```js +define( [ "jquery" ], function( $ ) { + +} ); +``` + +### Node + +To include jQuery in [Node](https://nodejs.org/), first install with npm. + +```sh +npm install jquery +``` + +For jQuery to work in Node, a window with a document is required. Since no such window exists natively in Node, one can be mocked by tools such as [jsdom](https://github.com/jsdom/jsdom). This can be useful for testing purposes. + +```js +const { JSDOM } = require( "jsdom" ); +const { window } = new JSDOM( "" ); +const $ = require( "jquery" )( window ); +``` diff --git a/build/release.js b/build/release.js new file mode 100644 index 0000000..6b22d95 --- /dev/null +++ b/build/release.js @@ -0,0 +1,94 @@ +"use strict"; + +var fs = require( "fs" ); + +module.exports = function( Release ) { + + const distFiles = [ + "dist/jquery.js", + "dist/jquery.min.js", + "dist/jquery.min.map", + "dist/jquery.slim.js", + "dist/jquery.slim.min.js", + "dist/jquery.slim.min.map" + ]; + const filesToCommit = [ + ...distFiles, + "src/core.js" + ]; + const cdn = require( "./release/cdn" ); + const dist = require( "./release/dist" ); + const ensureSizzle = require( "./release/ensure-sizzle" ); + + const npmTags = Release.npmTags; + + Release.define( { + npmPublish: true, + issueTracker: "github", + + /** + * Ensure the repo is in a proper state before release + * @param {Function} callback + */ + checkRepoState: function( callback ) { + ensureSizzle( Release, callback ); + }, + + /** + * Set the version in the src folder for distributing AMD + */ + _setSrcVersion: function() { + var corePath = __dirname + "/../src/core.js", + contents = fs.readFileSync( corePath, "utf8" ); + contents = contents.replace( /@VERSION/g, Release.newVersion ); + fs.writeFileSync( corePath, contents, "utf8" ); + }, + + /** + * Generates any release artifacts that should be included in the release. + * The callback must be invoked with an array of files that should be + * committed before creating the tag. + * @param {Function} callback + */ + generateArtifacts: function( callback ) { + Release.exec( "npx grunt", "Grunt command failed" ); + Release.exec( + "npx grunt custom:slim --filename=jquery.slim.js && " + + "npx grunt remove_map_comment --filename=jquery.slim.js", + "Grunt custom failed" + ); + cdn.makeReleaseCopies( Release ); + Release._setSrcVersion(); + callback( filesToCommit ); + }, + + /** + * Acts as insertion point for restoring Release.dir.repo + * It was changed to reuse npm publish code in jquery-release + * for publishing the distribution repo instead + */ + npmTags: function() { + + // origRepo is not defined if dist was skipped + Release.dir.repo = Release.dir.origRepo || Release.dir.repo; + return npmTags(); + }, + + /** + * Publish to distribution repo and npm + * @param {Function} callback + */ + dist: function( callback ) { + cdn.makeArchives( Release, function() { + dist( Release, distFiles, callback ); + } ); + } + } ); +}; + +module.exports.dependencies = [ + "archiver@5.2.0", + "shelljs@0.8.4", + "inquirer@8.0.0", + "chalk@4.1.0" +]; diff --git a/build/release/cdn.js b/build/release/cdn.js new file mode 100644 index 0000000..3d96d01 --- /dev/null +++ b/build/release/cdn.js @@ -0,0 +1,120 @@ +"use strict"; + +var + fs = require( "fs" ), + shell = require( "shelljs" ), + path = require( "path" ), + + cdnFolder = "dist/cdn", + + releaseFiles = { + "jquery-VER.js": "dist/jquery.js", + "jquery-VER.min.js": "dist/jquery.min.js", + "jquery-VER.min.map": "dist/jquery.min.map", + "jquery-VER.slim.js": "dist/jquery.slim.js", + "jquery-VER.slim.min.js": "dist/jquery.slim.min.js", + "jquery-VER.slim.min.map": "dist/jquery.slim.min.map" + }, + + googleFilesCDN = [ + "jquery.js", "jquery.min.js", "jquery.min.map", + "jquery.slim.js", "jquery.slim.min.js", "jquery.slim.min.map" + ], + + msFilesCDN = [ + "jquery-VER.js", "jquery-VER.min.js", "jquery-VER.min.map", + "jquery-VER.slim.js", "jquery-VER.slim.min.js", "jquery-VER.slim.min.map" + ]; + +/** + * Generates copies for the CDNs + */ +function makeReleaseCopies( Release ) { + shell.mkdir( "-p", cdnFolder ); + + Object.keys( releaseFiles ).forEach( function( key ) { + var text, + builtFile = releaseFiles[ key ], + unpathedFile = key.replace( /VER/g, Release.newVersion ), + releaseFile = cdnFolder + "/" + unpathedFile; + + if ( /\.map$/.test( releaseFile ) ) { + + // Map files need to reference the new uncompressed name; + // assume that all files reside in the same directory. + // "file":"jquery.min.js" ... "sources":["jquery.js"] + text = fs.readFileSync( builtFile, "utf8" ) + .replace( /"file":"([^"]+)"/, + "\"file\":\"" + unpathedFile.replace( /\.min\.map/, ".min.js\"" ) ) + .replace( /"sources":\["([^"]+)"\]/, + "\"sources\":[\"" + unpathedFile.replace( /\.min\.map/, ".js" ) + "\"]" ); + fs.writeFileSync( releaseFile, text ); + } else if ( builtFile !== releaseFile ) { + shell.cp( "-f", builtFile, releaseFile ); + } + } ); +} + +function makeArchives( Release, callback ) { + + Release.chdir( Release.dir.repo ); + + function makeArchive( cdn, files, callback ) { + if ( Release.preRelease ) { + console.log( "Skipping archive creation for " + cdn + "; this is a beta release." ); + callback(); + return; + } + + console.log( "Creating production archive for " + cdn ); + + var sum, + archiver = require( "archiver" )( "zip" ), + md5file = cdnFolder + "/" + cdn + "-md5.txt", + output = fs.createWriteStream( + cdnFolder + "/" + cdn + "-jquery-" + Release.newVersion + ".zip" + ), + rver = /VER/; + + output.on( "close", callback ); + + output.on( "error", function( err ) { + throw err; + } ); + + archiver.pipe( output ); + + files = files.map( function( item ) { + return "dist" + ( rver.test( item ) ? "/cdn" : "" ) + "/" + + item.replace( rver, Release.newVersion ); + } ); + + sum = Release.exec( "md5 -r " + files.join( " " ), "Error retrieving md5sum" ); + fs.writeFileSync( md5file, sum ); + files.push( md5file ); + + files.forEach( function( file ) { + archiver.append( fs.createReadStream( file ), + { name: path.basename( file ) } ); + } ); + + archiver.finalize(); + } + + function buildGoogleCDN( callback ) { + makeArchive( "googlecdn", googleFilesCDN, callback ); + } + + function buildMicrosoftCDN( callback ) { + makeArchive( "mscdn", msFilesCDN, callback ); + } + + buildGoogleCDN( function() { + buildMicrosoftCDN( callback ); + } ); +} + +module.exports = { + makeReleaseCopies: makeReleaseCopies, + makeArchives: makeArchives +}; diff --git a/build/release/dist.js b/build/release/dist.js new file mode 100644 index 0000000..3bbccd4 --- /dev/null +++ b/build/release/dist.js @@ -0,0 +1,169 @@ +"use strict"; + +module.exports = function( Release, files, complete ) { + + const fs = require( "fs" ).promises; + const shell = require( "shelljs" ); + const inquirer = require( "inquirer" ); + const pkg = require( `${ Release.dir.repo }/package.json` ); + const distRemote = Release.remote + + // For local and github dists + .replace( /jquery(\.git|$)/, "jquery-dist$1" ); + + // These files are included with the distribution + const extras = [ + "src", + "LICENSE.txt", + "AUTHORS.txt", + "package.json" + ]; + + /** + * Clone the distribution repo + */ + function clone() { + Release.chdir( Release.dir.base ); + Release.dir.dist = `${ Release.dir.base }/dist`; + + console.log( "Using distribution repo: ", distRemote ); + Release.exec( `git clone ${ distRemote } ${ Release.dir.dist }`, + "Error cloning repo." ); + + // Distribution always works on main + Release.chdir( Release.dir.dist ); + Release.exec( "git checkout main", "Error checking out branch." ); + console.log(); + } + + /** + * Generate bower file for jquery-dist + */ + function generateBower() { + return JSON.stringify( { + name: pkg.name, + main: pkg.main, + license: "MIT", + ignore: [ + "package.json" + ], + keywords: pkg.keywords + }, null, 2 ); + } + + /** + * Replace the version in the README + * @param {string} readme + * @param {string} blogPostLink + */ + function editReadme( readme, blogPostLink ) { + return readme + .replace( /@VERSION/g, Release.newVersion ) + .replace( /@BLOG_POST_LINK/g, blogPostLink ); + } + + /** + * Copy necessary files over to the dist repo + */ + async function copy() { + + // Copy dist files + const distFolder = `${ Release.dir.dist }/dist`; + const externalFolder = `${ Release.dir.dist }/external`; + const readme = await fs.readFile( + `${ Release.dir.repo }/build/fixtures/README.md`, "utf8" ); + const rmIgnore = [ ...files, "node_modules" ] + .map( file => `${ Release.dir.dist }/${ file }` ); + + shell.config.globOptions = { + ignore: rmIgnore + }; + + const { blogPostLink } = await inquirer.prompt( [ { + type: "input", + name: "blogPostLink", + message: "Enter URL of the blog post announcing the jQuery release...\n" + } ] ); + + // Remove extraneous files before copy + shell.rm( "-rf", `${ Release.dir.dist }/**/*` ); + + shell.mkdir( "-p", distFolder ); + files.forEach( function( file ) { + shell.cp( "-f", `${ Release.dir.repo }/${ file }`, distFolder ); + } ); + + // Copy Sizzle + shell.mkdir( "-p", externalFolder ); + shell.cp( "-rf", `${ Release.dir.repo }/external/sizzle`, externalFolder ); + + // Copy other files + extras.forEach( function( file ) { + shell.cp( "-rf", `${ Release.dir.repo }/${ file }`, Release.dir.dist ); + } ); + + // Remove the wrapper & the ESLint config from the dist repo + shell.rm( "-f", `${ Release.dir.dist }/src/wrapper.js` ); + shell.rm( "-f", `${ Release.dir.dist }/src/.eslintrc.json` ); + + // Write generated bower file + await fs.writeFile( `${ Release.dir.dist }/bower.json`, generateBower() ); + + await fs.writeFile( `${ Release.dir.dist }/README.md`, + editReadme( readme, blogPostLink ) ); + + console.log( "Files ready to add." ); + } + + /** + * Add, commit, and tag the dist files + */ + function commit() { + console.log( "Adding files to dist..." ); + Release.exec( "git add -A", "Error adding files." ); + Release.exec( + `git commit -m "Release ${ Release.newVersion }"`, + "Error committing files." + ); + console.log(); + + console.log( "Tagging release on dist..." ); + Release.exec( `git tag ${ Release.newVersion }`, + `Error tagging ${ Release.newVersion } on dist repo.` ); + Release.tagTime = Release.exec( "git log -1 --format=\"%ad\"", + "Error getting tag timestamp." ).trim(); + } + + /** + * Push files to dist repo + */ + function push() { + Release.chdir( Release.dir.dist ); + + console.log( "Pushing release to dist repo..." ); + Release.exec( + `git push ${ + Release.isTest ? " --dry-run" : "" + } ${ distRemote } main --tags`, + "Error pushing main and tags to git repo." + ); + + // Set repo for npm publish + Release.dir.origRepo = Release.dir.repo; + Release.dir.repo = Release.dir.dist; + } + + Release.walk( [ + Release._section( "Copy files to distribution repo" ), + clone, + copy, + Release.confirmReview, + + Release._section( "Add, commit, and tag files in distribution repo" ), + commit, + Release.confirmReview, + + Release._section( "Pushing files to distribution repo" ), + push + ], complete ); +}; diff --git a/build/release/ensure-sizzle.js b/build/release/ensure-sizzle.js new file mode 100644 index 0000000..605b141 --- /dev/null +++ b/build/release/ensure-sizzle.js @@ -0,0 +1,35 @@ +"use strict"; + +var fs = require( "fs" ), + chalk = require( "chalk" ), + sizzleLoc = __dirname + "/../../external/sizzle/dist/sizzle.js", + rversion = /Engine v(\d+\.\d+\.\d+(?:-[-\.\d\w]+)?)/; + +/** + * Ensure the /src folder has the latest tag of Sizzle + * @param {Object} Release + * @param {Function} callback + */ +function ensureSizzle( Release, callback ) { + console.log(); + console.log( "Checking Sizzle version..." ); + var match = rversion.exec( fs.readFileSync( sizzleLoc, "utf8" ) ), + version = match ? match[ 1 ] : "Not Found", + latest = Release.exec( { + command: "npm info sizzle version", + silent: true + } ); + + if ( version !== latest ) { + console.log( + "The Sizzle version in the src folder (" + chalk.red( version ) + + ") is not the latest tag (" + chalk.green( latest ) + ")." + ); + Release.confirm( callback ); + } else { + console.log( "Sizzle is latest (" + chalk.green( latest ) + ")" ); + callback(); + } +} + +module.exports = ensureSizzle; diff --git a/build/tasks/build.js b/build/tasks/build.js new file mode 100644 index 0000000..bdde74c --- /dev/null +++ b/build/tasks/build.js @@ -0,0 +1,399 @@ +/** + * Special concat/build task to handle various jQuery build requirements + * Concats AMD modules, removes their definitions, + * and includes/excludes specified modules + */ + +"use strict"; + +module.exports = function( grunt ) { + var fs = require( "fs" ), + requirejs = require( "requirejs" ), + Insight = require( "insight" ), + pkg = require( "../../package.json" ), + srcFolder = __dirname + "/../../src/", + rdefineEnd = /\}\s*?\);[^}\w]*$/, + read = function( fileName ) { + return grunt.file.read( srcFolder + fileName ); + }, + + // Catch `// @CODE` and subsequent comment lines event if they don't start + // in the first column. + wrapper = read( "wrapper.js" ).split( /[\x20\t]*\/\/ @CODE\n(?:[\x20\t]*\/\/[^\n]+\n)*/ ), + + config = { + baseUrl: "src", + name: "jquery", + + // Allow strict mode + useStrict: true, + + // We have multiple minify steps + optimize: "none", + + // Include dependencies loaded with require + findNestedDependencies: true, + + // Avoid inserting define() placeholder + skipModuleInsertion: true, + + // Avoid breaking semicolons inserted by r.js + skipSemiColonInsertion: true, + wrap: { + start: wrapper[ 0 ].replace( /\/\*\s*eslint(?: |-).*\s*\*\/\n/, "" ), + end: wrapper[ 1 ] + }, + rawText: {}, + onBuildWrite: convert + }; + + /** + * Strip all definitions generated by requirejs + * Convert "var" modules to var declarations + * "var module" means the module only contains a return + * statement that should be converted to a var declaration + * This is indicated by including the file in any "var" folder + * @param {String} name + * @param {String} path + * @param {String} contents The contents to be written (including their AMD wrappers) + */ + function convert( name, path, contents ) { + var amdName; + + // Convert var modules + if ( /.\/var\//.test( path.replace( process.cwd(), "" ) ) ) { + contents = contents + .replace( + /define\(\s*(["'])[\w\W]*?\1[\w\W]*?return/, + "var " + + ( /var\/([\w-]+)/.exec( name )[ 1 ] ) + + " =" + ) + .replace( rdefineEnd, "" ); + + // Sizzle treatment + } else if ( /\/sizzle$/.test( name ) ) { + contents = "var Sizzle =\n" + contents + + // Remove EXPOSE lines from Sizzle + .replace( /\/\/\s*EXPOSE[\w\W]*\/\/\s*EXPOSE/, "return Sizzle;" ); + + } else { + + contents = contents + .replace( /\s*return\s+[^\}]+(\}\s*?\);[^\w\}]*)$/, "$1" ) + + // Multiple exports + .replace( /\s*exports\.\w+\s*=\s*\w+;/g, "" ); + + // Remove define wrappers, closure ends, and empty declarations + contents = contents + .replace( /define\([^{]*?{\s*(?:("|')use strict\1(?:;|))?/, "" ) + .replace( rdefineEnd, "" ); + + // Remove anything wrapped with + // /* ExcludeStart */ /* ExcludeEnd */ + // or a single line directly after a // BuildExclude comment + contents = contents + .replace( /\/\*\s*ExcludeStart\s*\*\/[\w\W]*?\/\*\s*ExcludeEnd\s*\*\//ig, "" ) + .replace( /\/\/\s*BuildExclude\n\r?[\w\W]*?\n\r?/ig, "" ); + + // Remove empty definitions + contents = contents + .replace( /define\(\[[^\]]*\]\)[\W\n]+$/, "" ); + } + + // AMD Name + if ( ( amdName = grunt.option( "amd" ) ) != null && /^exports\/amd$/.test( name ) ) { + if ( amdName ) { + grunt.log.writeln( "Naming jQuery with AMD name: " + amdName ); + } else { + grunt.log.writeln( "AMD name now anonymous" ); + } + + // Remove the comma for anonymous defines + contents = contents + .replace( /(\s*)"jquery"(\,\s*)/, amdName ? "$1\"" + amdName + "\"$2" : "" ); + + } + return contents; + } + + grunt.registerMultiTask( + "build", + "Concatenate source, remove sub AMD definitions, " + + "(include/exclude modules with +/- flags), embed date/version", + function() { + var flag, index, + done = this.async(), + flags = this.flags, + optIn = flags[ "*" ], + name = grunt.option( "filename" ), + minimum = this.data.minimum, + removeWith = this.data.removeWith, + excluded = [], + included = [], + version = grunt.config( "pkg.version" ), + + /** + * Recursively calls the excluder to remove on all modules in the list + * @param {Array} list + * @param {String} [prepend] Prepend this to the module name. + * Indicates we're walking a directory + */ + excludeList = function( list, prepend ) { + if ( list ) { + prepend = prepend ? prepend + "/" : ""; + list.forEach( function( module ) { + + // Exclude var modules as well + if ( module === "var" ) { + excludeList( + fs.readdirSync( srcFolder + prepend + module ), prepend + module + ); + return; + } + if ( prepend ) { + + // Skip if this is not a js file and we're walking files in a dir + if ( !( module = /([\w-\/]+)\.js$/.exec( module ) ) ) { + return; + } + + // Prepend folder name if passed + // Remove .js extension + module = prepend + module[ 1 ]; + } + + // Avoid infinite recursion + if ( excluded.indexOf( module ) === -1 ) { + excluder( "-" + module ); + } + } ); + } + }, + + /** + * Adds the specified module to the excluded or included list, depending on the flag + * @param {String} flag A module path relative to + * the src directory starting with + or - to indicate + * whether it should included or excluded + */ + excluder = function( flag ) { + var additional, + m = /^(\+|\-|)([\w\/-]+)$/.exec( flag ), + exclude = m[ 1 ] === "-", + module = m[ 2 ]; + + if ( exclude ) { + + // Can't exclude certain modules + if ( minimum.indexOf( module ) === -1 ) { + + // Add to excluded + if ( excluded.indexOf( module ) === -1 ) { + grunt.log.writeln( flag ); + excluded.push( module ); + + // Exclude all files in the folder of the same name + // These are the removable dependencies + // It's fine if the directory is not there + try { + excludeList( fs.readdirSync( srcFolder + module ), module ); + } catch ( e ) { + grunt.verbose.writeln( e ); + } + } + + additional = removeWith[ module ]; + + // Check removeWith list + if ( additional ) { + excludeList( additional.remove || additional ); + if ( additional.include ) { + included = included.concat( additional.include ); + grunt.log.writeln( "+" + additional.include ); + } + } + } else { + grunt.log.error( "Module \"" + module + "\" is a minimum requirement." ); + if ( module === "selector" ) { + grunt.log.error( + "If you meant to replace Sizzle, use -sizzle instead." + ); + } + } + } else { + grunt.log.writeln( flag ); + included.push( module ); + } + }; + + // Filename can be passed to the command line using + // command line options + // e.g. grunt build --filename=jquery-custom.js + name = name ? ( "dist/" + name ) : this.data.dest; + + // append commit id to version + if ( process.env.COMMIT ) { + version += " " + process.env.COMMIT; + } + + // figure out which files to exclude based on these rules in this order: + // dependency explicit exclude + // > explicit exclude + // > explicit include + // > dependency implicit exclude + // > implicit exclude + // examples: + // * none (implicit exclude) + // *:* all (implicit include) + // *:*:-css all except css and dependents (explicit > implicit) + // *:*:-css:+effects same (excludes effects because explicit include is + // trumped by explicit exclude of dependency) + // *:+effects none except effects and its dependencies + // (explicit include trumps implicit exclude of dependency) + delete flags[ "*" ]; + for ( flag in flags ) { + excluder( flag ); + } + + // Handle Sizzle exclusion + // Replace with selector-native + if ( ( index = excluded.indexOf( "sizzle" ) ) > -1 ) { + config.rawText.selector = "define(['./selector-native']);"; + excluded.splice( index, 1 ); + } + + // Replace exports/global with a noop noConflict + if ( ( index = excluded.indexOf( "exports/global" ) ) > -1 ) { + config.rawText[ "exports/global" ] = "define(['../core']," + + "function( jQuery ) {\njQuery.noConflict = function() {};\n});"; + excluded.splice( index, 1 ); + } + + grunt.verbose.writeflags( excluded, "Excluded" ); + grunt.verbose.writeflags( included, "Included" ); + + // append excluded modules to version + if ( excluded.length ) { + version += " -" + excluded.join( ",-" ); + + // set pkg.version to version with excludes, so minified file picks it up + grunt.config.set( "pkg.version", version ); + grunt.verbose.writeln( "Version changed to " + version ); + + // Have to use shallow or core will get excluded since it is a dependency + config.excludeShallow = excluded; + } + config.include = included; + + /** + * Handle Final output from the optimizer + * @param {String} compiled + */ + config.out = function( compiled ) { + compiled = compiled + + // Embed Version + .replace( /@VERSION/g, version ) + + // Embed Date + // yyyy-mm-ddThh:mmZ + .replace( /@DATE/g, ( new Date() ).toISOString().replace( /:\d+\.\d+Z$/, "Z" ) ); + + // Write concatenated source to file + grunt.file.write( name, compiled ); + }; + + // Turn off opt-in if necessary + if ( !optIn ) { + + // Overwrite the default inclusions with the explicit ones provided + config.rawText.jquery = "define([" + + ( included.length ? included.join( "," ) : "" ) + + "]);"; + } + + // Trace dependencies and concatenate files + requirejs.optimize( config, function( response ) { + grunt.verbose.writeln( response ); + grunt.log.ok( "File '" + name + "' created." ); + done(); + }, function( err ) { + done( err ); + } ); + } ); + + // Special "alias" task to make custom build creation less grawlix-y + // Translation example + // + // grunt custom:+ajax,-dimensions,-effects,-offset + // + // Becomes: + // + // grunt build:*:*:+ajax:-dimensions:-effects:-offset + // + // There's also a special "slim" alias that resolves to the jQuery Slim build + // configuration: + // + // grunt custom:slim + grunt.registerTask( "custom", function() { + var args = this.args, + modules = args.length ? + args[ 0 ] + .split( "," ) + + // Replace "slim" with respective exclusions meant for + // the official slim build + .reduce( ( acc, elem ) => acc.concat( + elem === "slim" ? + [ "-ajax", "-effects" ] : + [ elem ] + ), [] ) + + .join( ":" ) : + "", + done = this.async(), + insight = new Insight( { + trackingCode: "UA-1076265-4", + pkg: pkg + } ); + + function exec( trackingAllowed ) { + var tracks = args.length ? args[ 0 ].split( "," ) : []; + var defaultPath = [ "build", "custom" ]; + + tracks = tracks.map( function( track ) { + return track.replace( /\//g, "+" ); + } ); + + if ( trackingAllowed ) { + + // Track individuals + tracks.forEach( function( module ) { + var path = defaultPath.concat( [ "individual" ], module ); + + insight.track.apply( insight, path ); + } ); + + // Track full command + insight.track.apply( insight, defaultPath.concat( [ "full" ], tracks ) ); + } + + grunt.task.run( [ "build:*:*" + ( modules ? ":" + modules : "" ), "uglify", "dist" ] ); + done(); + } + + grunt.log.writeln( "Creating custom build...\n" ); + + // Ask for permission the first time + if ( insight.optOut === undefined ) { + insight.askPermission( null, function( _error, result ) { + exec( result ); + } ); + } else { + exec( !insight.optOut ); + } + } ); +}; diff --git a/build/tasks/dist.js b/build/tasks/dist.js new file mode 100644 index 0000000..f19929b --- /dev/null +++ b/build/tasks/dist.js @@ -0,0 +1,71 @@ +"use strict"; + +module.exports = function( grunt ) { + var fs = require( "fs" ), + filename = grunt.option( "filename" ), + distpaths = [ + "dist/" + filename, + "dist/" + filename.replace( ".js", ".min.map" ), + "dist/" + filename.replace( ".js", ".min.js" ) + ]; + + // Process files for distribution + grunt.registerTask( "dist", function() { + var stored, flags, paths, nonascii; + + // Check for stored destination paths + // ( set in dist/.destination.json ) + stored = Object.keys( grunt.config( "dst" ) ); + + // Allow command line input as well + flags = Object.keys( this.flags ); + + // Combine all output target paths + paths = [].concat( stored, flags ).filter( function( path ) { + return path !== "*"; + } ); + + // Ensure the dist files are pure ASCII + nonascii = false; + + distpaths.forEach( function( filename ) { + var i, c, + text = fs.readFileSync( filename, "utf8" ); + + // Ensure files use only \n for line endings, not \r\n + if ( /\x0d\x0a/.test( text ) ) { + grunt.log.writeln( filename + ": Incorrect line endings (\\r\\n)" ); + nonascii = true; + } + + // Ensure only ASCII chars so script tags don't need a charset attribute + if ( text.length !== Buffer.byteLength( text, "utf8" ) ) { + grunt.log.writeln( filename + ": Non-ASCII characters detected:" ); + for ( i = 0; i < text.length; i++ ) { + c = text.charCodeAt( i ); + if ( c > 127 ) { + grunt.log.writeln( "- position " + i + ": " + c ); + grunt.log.writeln( "-- " + text.substring( i - 20, i + 20 ) ); + break; + } + } + nonascii = true; + } + + // Optionally copy dist files to other locations + paths.forEach( function( path ) { + var created; + + if ( !/\/$/.test( path ) ) { + path += "/"; + } + + created = path + filename.replace( "dist/", "" ); + grunt.file.write( created, text ); + grunt.log.writeln( "File '" + created + "' created." ); + } ); + } ); + + return !nonascii; + } ); +}; diff --git a/build/tasks/lib/spawn_test.js b/build/tasks/lib/spawn_test.js new file mode 100644 index 0000000..1461554 --- /dev/null +++ b/build/tasks/lib/spawn_test.js @@ -0,0 +1,16 @@ +"use strict"; + +// Run Node with provided parameters: the first one being the Grunt +// done function and latter ones being files to be tested. +// See the comment in ../node_smoke_tests.js for more information. +module.exports = function spawnTest( done, command ) { + var spawn = require( "child_process" ).spawn; + + spawn( command, { + stdio: "inherit", + shell: true + } ) + .on( "close", function( code ) { + done( code === 0 ); + } ); +}; diff --git a/build/tasks/node_smoke_tests.js b/build/tasks/node_smoke_tests.js new file mode 100644 index 0000000..574a63b --- /dev/null +++ b/build/tasks/node_smoke_tests.js @@ -0,0 +1,31 @@ +"use strict"; + +module.exports = ( grunt ) => { + const fs = require( "fs" ); + const spawnTest = require( "./lib/spawn_test.js" ); + const testsDir = "./test/node_smoke_tests/"; + const nodeSmokeTests = []; + + // Fire up all tests defined in test/node_smoke_tests/*.js in spawned sub-processes. + // All the files under test/node_smoke_tests/*.js are supposed to exit with 0 code + // on success or another one on failure. Spawning in sub-processes is + // important so that the tests & the main process don't interfere with + // each other, e.g. so that they don't share the require cache. + + fs.readdirSync( testsDir ) + .filter( ( testFilePath ) => + fs.statSync( testsDir + testFilePath ).isFile() && + /\.js$/.test( testFilePath ) + ) + .forEach( ( testFilePath ) => { + const taskName = `node_${ testFilePath.replace( /\.js$/, "" ) }`; + + grunt.registerTask( taskName, function() { + spawnTest( this.async(), `node "test/node_smoke_tests/${ testFilePath }"` ); + } ); + + nodeSmokeTests.push( taskName ); + } ); + + grunt.registerTask( "node_smoke_tests", nodeSmokeTests ); +}; diff --git a/build/tasks/promises_aplus_tests.js b/build/tasks/promises_aplus_tests.js new file mode 100644 index 0000000..cb1a4fd --- /dev/null +++ b/build/tasks/promises_aplus_tests.js @@ -0,0 +1,27 @@ +"use strict"; + +module.exports = grunt => { + const timeout = 2000; + const spawnTest = require( "./lib/spawn_test.js" ); + + grunt.registerTask( "promises_aplus_tests", + [ "promises_aplus_tests:deferred", "promises_aplus_tests:when" ] ); + + grunt.registerTask( "promises_aplus_tests:deferred", function() { + spawnTest( this.async(), + "\"" + __dirname + "/../../node_modules/.bin/promises-aplus-tests\"" + + " test/promises_aplus_adapters/deferred.js" + + " --reporter dot" + + " --timeout " + timeout + ); + } ); + + grunt.registerTask( "promises_aplus_tests:when", function() { + spawnTest( this.async(), + "\"" + __dirname + "/../../node_modules/.bin/promises-aplus-tests\"" + + " test/promises_aplus_adapters/when.js" + + " --reporter dot" + + " --timeout " + timeout + ); + } ); +}; diff --git a/build/tasks/qunit_fixture.js b/build/tasks/qunit_fixture.js new file mode 100644 index 0000000..138ca66 --- /dev/null +++ b/build/tasks/qunit_fixture.js @@ -0,0 +1,22 @@ +"use strict"; + +var fs = require( "fs" ); + +module.exports = function( grunt ) { + grunt.registerTask( "qunit_fixture", function() { + var dest = "./test/data/qunit-fixture.js"; + fs.writeFileSync( + dest, + "// Generated by build/tasks/qunit_fixture.js\n" + + "QUnit.config.fixture = " + + JSON.stringify( + fs.readFileSync( + "./test/data/qunit-fixture.html", + "utf8" + ).toString().replace( /\r\n/g, "\n" ) + ) + + ";\n" + ); + grunt.log.ok( "Updated " + dest + "." ); + } ); +}; diff --git a/build/tasks/sourcemap.js b/build/tasks/sourcemap.js new file mode 100644 index 0000000..509374f --- /dev/null +++ b/build/tasks/sourcemap.js @@ -0,0 +1,17 @@ +"use strict"; + +var fs = require( "fs" ); + +module.exports = function( grunt ) { + var config = grunt.config( "uglify.all.files" ); + grunt.registerTask( "remove_map_comment", function() { + var minLoc = grunt.config.process( Object.keys( config )[ 0 ] ); + + // Remove the source map comment; it causes way too many problems. + // The map file is still generated for manual associations + // https://github.com/jquery/jquery/issues/1707 + var text = fs.readFileSync( minLoc, "utf8" ) + .replace( /\/\/# sourceMappingURL=\S+/, "" ); + fs.writeFileSync( minLoc, text ); + } ); +}; diff --git a/build/tasks/testswarm.js b/build/tasks/testswarm.js new file mode 100644 index 0000000..d2653e0 --- /dev/null +++ b/build/tasks/testswarm.js @@ -0,0 +1,62 @@ +"use strict"; + +module.exports = function( grunt ) { + grunt.registerTask( "testswarm", function( commit, configFile, projectName, browserSets, + timeout, testMode ) { + var jobName, config, tests, + testswarm = require( "testswarm" ), + runs = {}, + done = this.async(), + pull = /PR-(\d+)/.exec( commit ); + + projectName = projectName || "jquery"; + config = grunt.file.readJSON( configFile )[ projectName ]; + browserSets = browserSets || config.browserSets; + if ( browserSets[ 0 ] === "[" ) { + + // We got an array, parse it + browserSets = JSON.parse( browserSets ); + } + timeout = timeout || 1000 * 60 * 15; + tests = grunt.config( [ this.name, "tests" ] ); + + if ( pull ) { + jobName = "Pull #" + pull[ 1 ] + ""; + } else { + jobName = "Commit " + commit.substr( 0, 10 ) + ""; + } + + if ( testMode === "basic" ) { + runs.basic = config.testUrl + commit + "/test/index.html?module=basic"; + } else { + tests.forEach( function( test ) { + runs[ test ] = config.testUrl + commit + "/test/index.html?module=" + test; + } ); + } + + testswarm.createClient( { + url: config.swarmUrl + } ) + .addReporter( testswarm.reporters.cli ) + .auth( { + id: config.authUsername, + token: config.authToken + } ) + .addjob( + { + name: jobName, + runs: runs, + runMax: config.runMax, + browserSets: browserSets, + timeout: timeout + }, function( err, passed ) { + if ( err ) { + grunt.log.error( err ); + } + done( passed ); + } + ); + } ); +}; diff --git a/package.json b/package.json new file mode 100644 index 0000000..c060d7d --- /dev/null +++ b/package.json @@ -0,0 +1,116 @@ +{ + "name": "jquery", + "title": "jQuery", + "description": "JavaScript library for DOM operations", + "version": "3.6.0", + "main": "dist/jquery.js", + "homepage": "https://jquery.com", + "author": { + "name": "OpenJS Foundation and other contributors", + "url": "https://github.com/jquery/jquery/blob/3.6.0/AUTHORS.txt" + }, + "repository": { + "type": "git", + "url": "https://github.com/jquery/jquery.git" + }, + "keywords": [ + "jquery", + "javascript", + "browser", + "library" + ], + "bugs": { + "url": "https://github.com/jquery/jquery/issues" + }, + "license": "MIT", + "devDependencies": { + "@babel/core": "7.3.3", + "@babel/plugin-transform-for-of": "7.2.0", + "commitplease": "3.2.0", + "core-js": "2.6.5", + "eslint-config-jquery": "3.0.0", + "grunt": "1.3.0", + "grunt-babel": "8.0.0", + "grunt-cli": "1.3.2", + "grunt-compare-size": "0.4.2", + "grunt-contrib-uglify": "3.4.0", + "grunt-contrib-watch": "1.1.0", + "grunt-eslint": "22.0.0", + "grunt-git-authors": "3.2.0", + "grunt-jsonlint": "1.1.0", + "grunt-karma": "4.0.0", + "grunt-newer": "1.3.0", + "grunt-npmcopy": "0.2.0", + "gzip-js": "0.3.2", + "husky": "1.3.1", + "insight": "0.10.1", + "jsdom": "13.2.0", + "karma": "5.2.3", + "karma-browserstack-launcher": "1.4.0", + "karma-chrome-launcher": "2.2.0", + "karma-firefox-launcher": "1.1.0", + "karma-ie-launcher": "1.0.0", + "karma-jsdom-launcher": "8.0.2", + "karma-qunit": "3.0.0", + "load-grunt-tasks": "5.1.0", + "native-promise-only": "0.8.1", + "promises-aplus-tests": "2.1.2", + "q": "1.5.1", + "qunit": "2.9.2", + "raw-body": "2.3.3", + "requirejs": "2.3.6", + "sinon": "2.3.7", + "sizzle": "2.3.6", + "strip-json-comments": "2.0.1", + "testswarm": "1.1.2", + "uglify-js": "3.4.7" + }, + "scripts": { + "build": "npm install && grunt", + "start": "grunt watch", + "test:browserless": "grunt && grunt test:slow", + "test:browser": "grunt && grunt karma:main", + "test:amd": "grunt && grunt karma:amd", + "test:no-deprecated": "grunt test:prepare && grunt custom:-deprecated && grunt karma:main", + "test:no-sizzle": "grunt test:prepare && grunt custom:-sizzle && grunt karma:main", + "test:slim": "grunt test:prepare && grunt custom:slim && grunt karma:main", + "test": "npm run test:slim && npm run test:no-deprecated && npm run test:no-sizzle && grunt && grunt test:slow && grunt karma:main && grunt karma:amd", + "jenkins": "npm run test:browserless" + }, + "commitplease": { + "nohook": true, + "components": [ + "Docs", + "Tests", + "Build", + "Support", + "Release", + "Core", + "Ajax", + "Attributes", + "Callbacks", + "CSS", + "Data", + "Deferred", + "Deprecated", + "Dimensions", + "Effects", + "Event", + "Manipulation", + "Offset", + "Queue", + "Selector", + "Serialize", + "Traversing", + "Wrap" + ], + "markerPattern": "^((clos|fix|resolv)(e[sd]|ing))|^(refs?)", + "ticketPattern": "^((Closes|Fixes) ([a-zA-Z]{2,}-)[0-9]+)|^(Refs? [^#])" + }, + "husky": { + "hooks": { + "commit-msg": "commitplease .git/COMMIT_EDITMSG", + "pre-commit": "grunt lint:newer qunit_fixture" + } + } +} diff --git a/src/.eslintrc.json b/src/.eslintrc.json new file mode 100644 index 0000000..21a0225 --- /dev/null +++ b/src/.eslintrc.json @@ -0,0 +1,38 @@ +{ + "root": true, + + "extends": "../.eslintrc-browser.json", + + "rules": { + "indent": [ "error", "tab", { + "outerIIFEBody": 0, + + // Ignore the top level function defining an AMD module + "ignoredNodes": [ + "Program > ExpressionStatement > CallExpression > :last-child > *" + ] + } ] + }, + + "overrides": [ + { + "files": "wrapper.js", + "rules": { + "no-unused-vars": "off", + "indent": [ "error", "tab", { + + // Unlike other codes, "wrapper.js" is implemented in UMD. + // So it required a specific exception for jQuery's UMD + // Code Style. This makes that indentation check is not + // performed for 1 depth of outer FunctionExpressions + "ignoredNodes": [ + "Program > ExpressionStatement > CallExpression > :last-child > *" + ] + } ] + }, + "globals": { + "jQuery": false + } + } + ] +} diff --git a/src/ajax.js b/src/ajax.js new file mode 100644 index 0000000..4be4a9e --- /dev/null +++ b/src/ajax.js @@ -0,0 +1,876 @@ +define( [ + "./core", + "./var/document", + "./var/isFunction", + "./var/rnothtmlwhite", + "./ajax/var/location", + "./ajax/var/nonce", + "./ajax/var/rquery", + + "./core/init", + "./core/parseXML", + "./event/trigger", + "./deferred", + "./serialize" // jQuery.param +], function( jQuery, document, isFunction, rnothtmlwhite, location, nonce, rquery ) { + +"use strict"; + +var + r20 = /%20/g, + rhash = /#.*$/, + rantiCache = /([?&])_=[^&]*/, + rheaders = /^(.*?):[ \t]*([^\r\n]*)$/mg, + + // #7653, #8125, #8152: local protocol detection + rlocalProtocol = /^(?:about|app|app-storage|.+-extension|file|res|widget):$/, + rnoContent = /^(?:GET|HEAD)$/, + rprotocol = /^\/\//, + + /* Prefilters + * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example) + * 2) These are called: + * - BEFORE asking for a transport + * - AFTER param serialization (s.data is a string if s.processData is true) + * 3) key is the dataType + * 4) the catchall symbol "*" can be used + * 5) execution will start with transport dataType and THEN continue down to "*" if needed + */ + prefilters = {}, + + /* Transports bindings + * 1) key is the dataType + * 2) the catchall symbol "*" can be used + * 3) selection will start with transport dataType and THEN go to "*" if needed + */ + transports = {}, + + // Avoid comment-prolog char sequence (#10098); must appease lint and evade compression + allTypes = "*/".concat( "*" ), + + // Anchor tag for parsing the document origin + originAnchor = document.createElement( "a" ); + +originAnchor.href = location.href; + +// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport +function addToPrefiltersOrTransports( structure ) { + + // dataTypeExpression is optional and defaults to "*" + return function( dataTypeExpression, func ) { + + if ( typeof dataTypeExpression !== "string" ) { + func = dataTypeExpression; + dataTypeExpression = "*"; + } + + var dataType, + i = 0, + dataTypes = dataTypeExpression.toLowerCase().match( rnothtmlwhite ) || []; + + if ( isFunction( func ) ) { + + // For each dataType in the dataTypeExpression + while ( ( dataType = dataTypes[ i++ ] ) ) { + + // Prepend if requested + if ( dataType[ 0 ] === "+" ) { + dataType = dataType.slice( 1 ) || "*"; + ( structure[ dataType ] = structure[ dataType ] || [] ).unshift( func ); + + // Otherwise append + } else { + ( structure[ dataType ] = structure[ dataType ] || [] ).push( func ); + } + } + } + }; +} + +// Base inspection function for prefilters and transports +function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR ) { + + var inspected = {}, + seekingTransport = ( structure === transports ); + + function inspect( dataType ) { + var selected; + inspected[ dataType ] = true; + jQuery.each( structure[ dataType ] || [], function( _, prefilterOrFactory ) { + var dataTypeOrTransport = prefilterOrFactory( options, originalOptions, jqXHR ); + if ( typeof dataTypeOrTransport === "string" && + !seekingTransport && !inspected[ dataTypeOrTransport ] ) { + + options.dataTypes.unshift( dataTypeOrTransport ); + inspect( dataTypeOrTransport ); + return false; + } else if ( seekingTransport ) { + return !( selected = dataTypeOrTransport ); + } + } ); + return selected; + } + + return inspect( options.dataTypes[ 0 ] ) || !inspected[ "*" ] && inspect( "*" ); +} + +// A special extend for ajax options +// that takes "flat" options (not to be deep extended) +// Fixes #9887 +function ajaxExtend( target, src ) { + var key, deep, + flatOptions = jQuery.ajaxSettings.flatOptions || {}; + + for ( key in src ) { + if ( src[ key ] !== undefined ) { + ( flatOptions[ key ] ? target : ( deep || ( deep = {} ) ) )[ key ] = src[ key ]; + } + } + if ( deep ) { + jQuery.extend( true, target, deep ); + } + + return target; +} + +/* Handles responses to an ajax request: + * - finds the right dataType (mediates between content-type and expected dataType) + * - returns the corresponding response + */ +function ajaxHandleResponses( s, jqXHR, responses ) { + + var ct, type, finalDataType, firstDataType, + contents = s.contents, + dataTypes = s.dataTypes; + + // Remove auto dataType and get content-type in the process + while ( dataTypes[ 0 ] === "*" ) { + dataTypes.shift(); + if ( ct === undefined ) { + ct = s.mimeType || jqXHR.getResponseHeader( "Content-Type" ); + } + } + + // Check if we're dealing with a known content-type + if ( ct ) { + for ( type in contents ) { + if ( contents[ type ] && contents[ type ].test( ct ) ) { + dataTypes.unshift( type ); + break; + } + } + } + + // Check to see if we have a response for the expected dataType + if ( dataTypes[ 0 ] in responses ) { + finalDataType = dataTypes[ 0 ]; + } else { + + // Try convertible dataTypes + for ( type in responses ) { + if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[ 0 ] ] ) { + finalDataType = type; + break; + } + if ( !firstDataType ) { + firstDataType = type; + } + } + + // Or just use first one + finalDataType = finalDataType || firstDataType; + } + + // If we found a dataType + // We add the dataType to the list if needed + // and return the corresponding response + if ( finalDataType ) { + if ( finalDataType !== dataTypes[ 0 ] ) { + dataTypes.unshift( finalDataType ); + } + return responses[ finalDataType ]; + } +} + +/* Chain conversions given the request and the original response + * Also sets the responseXXX fields on the jqXHR instance + */ +function ajaxConvert( s, response, jqXHR, isSuccess ) { + var conv2, current, conv, tmp, prev, + converters = {}, + + // Work with a copy of dataTypes in case we need to modify it for conversion + dataTypes = s.dataTypes.slice(); + + // Create converters map with lowercased keys + if ( dataTypes[ 1 ] ) { + for ( conv in s.converters ) { + converters[ conv.toLowerCase() ] = s.converters[ conv ]; + } + } + + current = dataTypes.shift(); + + // Convert to each sequential dataType + while ( current ) { + + if ( s.responseFields[ current ] ) { + jqXHR[ s.responseFields[ current ] ] = response; + } + + // Apply the dataFilter if provided + if ( !prev && isSuccess && s.dataFilter ) { + response = s.dataFilter( response, s.dataType ); + } + + prev = current; + current = dataTypes.shift(); + + if ( current ) { + + // There's only work to do if current dataType is non-auto + if ( current === "*" ) { + + current = prev; + + // Convert response if prev dataType is non-auto and differs from current + } else if ( prev !== "*" && prev !== current ) { + + // Seek a direct converter + conv = converters[ prev + " " + current ] || converters[ "* " + current ]; + + // If none found, seek a pair + if ( !conv ) { + for ( conv2 in converters ) { + + // If conv2 outputs current + tmp = conv2.split( " " ); + if ( tmp[ 1 ] === current ) { + + // If prev can be converted to accepted input + conv = converters[ prev + " " + tmp[ 0 ] ] || + converters[ "* " + tmp[ 0 ] ]; + if ( conv ) { + + // Condense equivalence converters + if ( conv === true ) { + conv = converters[ conv2 ]; + + // Otherwise, insert the intermediate dataType + } else if ( converters[ conv2 ] !== true ) { + current = tmp[ 0 ]; + dataTypes.unshift( tmp[ 1 ] ); + } + break; + } + } + } + } + + // Apply converter (if not an equivalence) + if ( conv !== true ) { + + // Unless errors are allowed to bubble, catch and return them + if ( conv && s.throws ) { + response = conv( response ); + } else { + try { + response = conv( response ); + } catch ( e ) { + return { + state: "parsererror", + error: conv ? e : "No conversion from " + prev + " to " + current + }; + } + } + } + } + } + } + + return { state: "success", data: response }; +} + +jQuery.extend( { + + // Counter for holding the number of active queries + active: 0, + + // Last-Modified header cache for next request + lastModified: {}, + etag: {}, + + ajaxSettings: { + url: location.href, + type: "GET", + isLocal: rlocalProtocol.test( location.protocol ), + global: true, + processData: true, + async: true, + contentType: "application/x-www-form-urlencoded; charset=UTF-8", + + /* + timeout: 0, + data: null, + dataType: null, + username: null, + password: null, + cache: null, + throws: false, + traditional: false, + headers: {}, + */ + + accepts: { + "*": allTypes, + text: "text/plain", + html: "text/html", + xml: "application/xml, text/xml", + json: "application/json, text/javascript" + }, + + contents: { + xml: /\bxml\b/, + html: /\bhtml/, + json: /\bjson\b/ + }, + + responseFields: { + xml: "responseXML", + text: "responseText", + json: "responseJSON" + }, + + // Data converters + // Keys separate source (or catchall "*") and destination types with a single space + converters: { + + // Convert anything to text + "* text": String, + + // Text to html (true = no transformation) + "text html": true, + + // Evaluate text as a json expression + "text json": JSON.parse, + + // Parse text as xml + "text xml": jQuery.parseXML + }, + + // For options that shouldn't be deep extended: + // you can add your own custom options here if + // and when you create one that shouldn't be + // deep extended (see ajaxExtend) + flatOptions: { + url: true, + context: true + } + }, + + // Creates a full fledged settings object into target + // with both ajaxSettings and settings fields. + // If target is omitted, writes into ajaxSettings. + ajaxSetup: function( target, settings ) { + return settings ? + + // Building a settings object + ajaxExtend( ajaxExtend( target, jQuery.ajaxSettings ), settings ) : + + // Extending ajaxSettings + ajaxExtend( jQuery.ajaxSettings, target ); + }, + + ajaxPrefilter: addToPrefiltersOrTransports( prefilters ), + ajaxTransport: addToPrefiltersOrTransports( transports ), + + // Main method + ajax: function( url, options ) { + + // If url is an object, simulate pre-1.5 signature + if ( typeof url === "object" ) { + options = url; + url = undefined; + } + + // Force options to be an object + options = options || {}; + + var transport, + + // URL without anti-cache param + cacheURL, + + // Response headers + responseHeadersString, + responseHeaders, + + // timeout handle + timeoutTimer, + + // Url cleanup var + urlAnchor, + + // Request state (becomes false upon send and true upon completion) + completed, + + // To know if global events are to be dispatched + fireGlobals, + + // Loop variable + i, + + // uncached part of the url + uncached, + + // Create the final options object + s = jQuery.ajaxSetup( {}, options ), + + // Callbacks context + callbackContext = s.context || s, + + // Context for global events is callbackContext if it is a DOM node or jQuery collection + globalEventContext = s.context && + ( callbackContext.nodeType || callbackContext.jquery ) ? + jQuery( callbackContext ) : + jQuery.event, + + // Deferreds + deferred = jQuery.Deferred(), + completeDeferred = jQuery.Callbacks( "once memory" ), + + // Status-dependent callbacks + statusCode = s.statusCode || {}, + + // Headers (they are sent all at once) + requestHeaders = {}, + requestHeadersNames = {}, + + // Default abort message + strAbort = "canceled", + + // Fake xhr + jqXHR = { + readyState: 0, + + // Builds headers hashtable if needed + getResponseHeader: function( key ) { + var match; + if ( completed ) { + if ( !responseHeaders ) { + responseHeaders = {}; + while ( ( match = rheaders.exec( responseHeadersString ) ) ) { + responseHeaders[ match[ 1 ].toLowerCase() + " " ] = + ( responseHeaders[ match[ 1 ].toLowerCase() + " " ] || [] ) + .concat( match[ 2 ] ); + } + } + match = responseHeaders[ key.toLowerCase() + " " ]; + } + return match == null ? null : match.join( ", " ); + }, + + // Raw string + getAllResponseHeaders: function() { + return completed ? responseHeadersString : null; + }, + + // Caches the header + setRequestHeader: function( name, value ) { + if ( completed == null ) { + name = requestHeadersNames[ name.toLowerCase() ] = + requestHeadersNames[ name.toLowerCase() ] || name; + requestHeaders[ name ] = value; + } + return this; + }, + + // Overrides response content-type header + overrideMimeType: function( type ) { + if ( completed == null ) { + s.mimeType = type; + } + return this; + }, + + // Status-dependent callbacks + statusCode: function( map ) { + var code; + if ( map ) { + if ( completed ) { + + // Execute the appropriate callbacks + jqXHR.always( map[ jqXHR.status ] ); + } else { + + // Lazy-add the new callbacks in a way that preserves old ones + for ( code in map ) { + statusCode[ code ] = [ statusCode[ code ], map[ code ] ]; + } + } + } + return this; + }, + + // Cancel the request + abort: function( statusText ) { + var finalText = statusText || strAbort; + if ( transport ) { + transport.abort( finalText ); + } + done( 0, finalText ); + return this; + } + }; + + // Attach deferreds + deferred.promise( jqXHR ); + + // Add protocol if not provided (prefilters might expect it) + // Handle falsy url in the settings object (#10093: consistency with old signature) + // We also use the url parameter if available + s.url = ( ( url || s.url || location.href ) + "" ) + .replace( rprotocol, location.protocol + "//" ); + + // Alias method option to type as per ticket #12004 + s.type = options.method || options.type || s.method || s.type; + + // Extract dataTypes list + s.dataTypes = ( s.dataType || "*" ).toLowerCase().match( rnothtmlwhite ) || [ "" ]; + + // A cross-domain request is in order when the origin doesn't match the current origin. + if ( s.crossDomain == null ) { + urlAnchor = document.createElement( "a" ); + + // Support: IE <=8 - 11, Edge 12 - 15 + // IE throws exception on accessing the href property if url is malformed, + // e.g. http://example.com:80x/ + try { + urlAnchor.href = s.url; + + // Support: IE <=8 - 11 only + // Anchor's host property isn't correctly set when s.url is relative + urlAnchor.href = urlAnchor.href; + s.crossDomain = originAnchor.protocol + "//" + originAnchor.host !== + urlAnchor.protocol + "//" + urlAnchor.host; + } catch ( e ) { + + // If there is an error parsing the URL, assume it is crossDomain, + // it can be rejected by the transport if it is invalid + s.crossDomain = true; + } + } + + // Convert data if not already a string + if ( s.data && s.processData && typeof s.data !== "string" ) { + s.data = jQuery.param( s.data, s.traditional ); + } + + // Apply prefilters + inspectPrefiltersOrTransports( prefilters, s, options, jqXHR ); + + // If request was aborted inside a prefilter, stop there + if ( completed ) { + return jqXHR; + } + + // We can fire global events as of now if asked to + // Don't fire events if jQuery.event is undefined in an AMD-usage scenario (#15118) + fireGlobals = jQuery.event && s.global; + + // Watch for a new set of requests + if ( fireGlobals && jQuery.active++ === 0 ) { + jQuery.event.trigger( "ajaxStart" ); + } + + // Uppercase the type + s.type = s.type.toUpperCase(); + + // Determine if request has content + s.hasContent = !rnoContent.test( s.type ); + + // Save the URL in case we're toying with the If-Modified-Since + // and/or If-None-Match header later on + // Remove hash to simplify url manipulation + cacheURL = s.url.replace( rhash, "" ); + + // More options handling for requests with no content + if ( !s.hasContent ) { + + // Remember the hash so we can put it back + uncached = s.url.slice( cacheURL.length ); + + // If data is available and should be processed, append data to url + if ( s.data && ( s.processData || typeof s.data === "string" ) ) { + cacheURL += ( rquery.test( cacheURL ) ? "&" : "?" ) + s.data; + + // #9682: remove data so that it's not used in an eventual retry + delete s.data; + } + + // Add or update anti-cache param if needed + if ( s.cache === false ) { + cacheURL = cacheURL.replace( rantiCache, "$1" ); + uncached = ( rquery.test( cacheURL ) ? "&" : "?" ) + "_=" + ( nonce.guid++ ) + + uncached; + } + + // Put hash and anti-cache on the URL that will be requested (gh-1732) + s.url = cacheURL + uncached; + + // Change '%20' to '+' if this is encoded form body content (gh-2658) + } else if ( s.data && s.processData && + ( s.contentType || "" ).indexOf( "application/x-www-form-urlencoded" ) === 0 ) { + s.data = s.data.replace( r20, "+" ); + } + + // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. + if ( s.ifModified ) { + if ( jQuery.lastModified[ cacheURL ] ) { + jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ cacheURL ] ); + } + if ( jQuery.etag[ cacheURL ] ) { + jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ cacheURL ] ); + } + } + + // Set the correct header, if data is being sent + if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) { + jqXHR.setRequestHeader( "Content-Type", s.contentType ); + } + + // Set the Accepts header for the server, depending on the dataType + jqXHR.setRequestHeader( + "Accept", + s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[ 0 ] ] ? + s.accepts[ s.dataTypes[ 0 ] ] + + ( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) : + s.accepts[ "*" ] + ); + + // Check for headers option + for ( i in s.headers ) { + jqXHR.setRequestHeader( i, s.headers[ i ] ); + } + + // Allow custom headers/mimetypes and early abort + if ( s.beforeSend && + ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || completed ) ) { + + // Abort if not done already and return + return jqXHR.abort(); + } + + // Aborting is no longer a cancellation + strAbort = "abort"; + + // Install callbacks on deferreds + completeDeferred.add( s.complete ); + jqXHR.done( s.success ); + jqXHR.fail( s.error ); + + // Get transport + transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR ); + + // If no transport, we auto-abort + if ( !transport ) { + done( -1, "No Transport" ); + } else { + jqXHR.readyState = 1; + + // Send global event + if ( fireGlobals ) { + globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] ); + } + + // If request was aborted inside ajaxSend, stop there + if ( completed ) { + return jqXHR; + } + + // Timeout + if ( s.async && s.timeout > 0 ) { + timeoutTimer = window.setTimeout( function() { + jqXHR.abort( "timeout" ); + }, s.timeout ); + } + + try { + completed = false; + transport.send( requestHeaders, done ); + } catch ( e ) { + + // Rethrow post-completion exceptions + if ( completed ) { + throw e; + } + + // Propagate others as results + done( -1, e ); + } + } + + // Callback for when everything is done + function done( status, nativeStatusText, responses, headers ) { + var isSuccess, success, error, response, modified, + statusText = nativeStatusText; + + // Ignore repeat invocations + if ( completed ) { + return; + } + + completed = true; + + // Clear timeout if it exists + if ( timeoutTimer ) { + window.clearTimeout( timeoutTimer ); + } + + // Dereference transport for early garbage collection + // (no matter how long the jqXHR object will be used) + transport = undefined; + + // Cache response headers + responseHeadersString = headers || ""; + + // Set readyState + jqXHR.readyState = status > 0 ? 4 : 0; + + // Determine if successful + isSuccess = status >= 200 && status < 300 || status === 304; + + // Get response data + if ( responses ) { + response = ajaxHandleResponses( s, jqXHR, responses ); + } + + // Use a noop converter for missing script but not if jsonp + if ( !isSuccess && + jQuery.inArray( "script", s.dataTypes ) > -1 && + jQuery.inArray( "json", s.dataTypes ) < 0 ) { + s.converters[ "text script" ] = function() {}; + } + + // Convert no matter what (that way responseXXX fields are always set) + response = ajaxConvert( s, response, jqXHR, isSuccess ); + + // If successful, handle type chaining + if ( isSuccess ) { + + // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. + if ( s.ifModified ) { + modified = jqXHR.getResponseHeader( "Last-Modified" ); + if ( modified ) { + jQuery.lastModified[ cacheURL ] = modified; + } + modified = jqXHR.getResponseHeader( "etag" ); + if ( modified ) { + jQuery.etag[ cacheURL ] = modified; + } + } + + // if no content + if ( status === 204 || s.type === "HEAD" ) { + statusText = "nocontent"; + + // if not modified + } else if ( status === 304 ) { + statusText = "notmodified"; + + // If we have data, let's convert it + } else { + statusText = response.state; + success = response.data; + error = response.error; + isSuccess = !error; + } + } else { + + // Extract error from statusText and normalize for non-aborts + error = statusText; + if ( status || !statusText ) { + statusText = "error"; + if ( status < 0 ) { + status = 0; + } + } + } + + // Set data for the fake xhr object + jqXHR.status = status; + jqXHR.statusText = ( nativeStatusText || statusText ) + ""; + + // Success/Error + if ( isSuccess ) { + deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] ); + } else { + deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] ); + } + + // Status-dependent callbacks + jqXHR.statusCode( statusCode ); + statusCode = undefined; + + if ( fireGlobals ) { + globalEventContext.trigger( isSuccess ? "ajaxSuccess" : "ajaxError", + [ jqXHR, s, isSuccess ? success : error ] ); + } + + // Complete + completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] ); + + if ( fireGlobals ) { + globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] ); + + // Handle the global AJAX counter + if ( !( --jQuery.active ) ) { + jQuery.event.trigger( "ajaxStop" ); + } + } + } + + return jqXHR; + }, + + getJSON: function( url, data, callback ) { + return jQuery.get( url, data, callback, "json" ); + }, + + getScript: function( url, callback ) { + return jQuery.get( url, undefined, callback, "script" ); + } +} ); + +jQuery.each( [ "get", "post" ], function( _i, method ) { + jQuery[ method ] = function( url, data, callback, type ) { + + // Shift arguments if data argument was omitted + if ( isFunction( data ) ) { + type = type || callback; + callback = data; + data = undefined; + } + + // The url can be an options object (which then must have .url) + return jQuery.ajax( jQuery.extend( { + url: url, + type: method, + dataType: type, + data: data, + success: callback + }, jQuery.isPlainObject( url ) && url ) ); + }; +} ); + +jQuery.ajaxPrefilter( function( s ) { + var i; + for ( i in s.headers ) { + if ( i.toLowerCase() === "content-type" ) { + s.contentType = s.headers[ i ] || ""; + } + } +} ); + +return jQuery; +} ); diff --git a/src/ajax/jsonp.js b/src/ajax/jsonp.js new file mode 100644 index 0000000..10186de --- /dev/null +++ b/src/ajax/jsonp.js @@ -0,0 +1,103 @@ +define( [ + "../core", + "../var/isFunction", + "./var/nonce", + "./var/rquery", + "../ajax" +], function( jQuery, isFunction, nonce, rquery ) { + +"use strict"; + +var oldCallbacks = [], + rjsonp = /(=)\?(?=&|$)|\?\?/; + +// Default jsonp settings +jQuery.ajaxSetup( { + jsonp: "callback", + jsonpCallback: function() { + var callback = oldCallbacks.pop() || ( jQuery.expando + "_" + ( nonce.guid++ ) ); + this[ callback ] = true; + return callback; + } +} ); + +// Detect, normalize options and install callbacks for jsonp requests +jQuery.ajaxPrefilter( "json jsonp", function( s, originalSettings, jqXHR ) { + + var callbackName, overwritten, responseContainer, + jsonProp = s.jsonp !== false && ( rjsonp.test( s.url ) ? + "url" : + typeof s.data === "string" && + ( s.contentType || "" ) + .indexOf( "application/x-www-form-urlencoded" ) === 0 && + rjsonp.test( s.data ) && "data" + ); + + // Handle iff the expected data type is "jsonp" or we have a parameter to set + if ( jsonProp || s.dataTypes[ 0 ] === "jsonp" ) { + + // Get callback name, remembering preexisting value associated with it + callbackName = s.jsonpCallback = isFunction( s.jsonpCallback ) ? + s.jsonpCallback() : + s.jsonpCallback; + + // Insert callback into url or form data + if ( jsonProp ) { + s[ jsonProp ] = s[ jsonProp ].replace( rjsonp, "$1" + callbackName ); + } else if ( s.jsonp !== false ) { + s.url += ( rquery.test( s.url ) ? "&" : "?" ) + s.jsonp + "=" + callbackName; + } + + // Use data converter to retrieve json after script execution + s.converters[ "script json" ] = function() { + if ( !responseContainer ) { + jQuery.error( callbackName + " was not called" ); + } + return responseContainer[ 0 ]; + }; + + // Force json dataType + s.dataTypes[ 0 ] = "json"; + + // Install callback + overwritten = window[ callbackName ]; + window[ callbackName ] = function() { + responseContainer = arguments; + }; + + // Clean-up function (fires after converters) + jqXHR.always( function() { + + // If previous value didn't exist - remove it + if ( overwritten === undefined ) { + jQuery( window ).removeProp( callbackName ); + + // Otherwise restore preexisting value + } else { + window[ callbackName ] = overwritten; + } + + // Save back as free + if ( s[ callbackName ] ) { + + // Make sure that re-using the options doesn't screw things around + s.jsonpCallback = originalSettings.jsonpCallback; + + // Save the callback name for future use + oldCallbacks.push( callbackName ); + } + + // Call if it was a function and we have a response + if ( responseContainer && isFunction( overwritten ) ) { + overwritten( responseContainer[ 0 ] ); + } + + responseContainer = overwritten = undefined; + } ); + + // Delegate to script + return "script"; + } +} ); + +} ); diff --git a/src/ajax/load.js b/src/ajax/load.js new file mode 100644 index 0000000..defdb01 --- /dev/null +++ b/src/ajax/load.js @@ -0,0 +1,77 @@ +define( [ + "../core", + "../core/stripAndCollapse", + "../var/isFunction", + "../core/parseHTML", + "../ajax", + "../traversing", + "../manipulation", + "../selector" +], function( jQuery, stripAndCollapse, isFunction ) { + +"use strict"; + +/** + * Load a url into a page + */ +jQuery.fn.load = function( url, params, callback ) { + var selector, type, response, + self = this, + off = url.indexOf( " " ); + + if ( off > -1 ) { + selector = stripAndCollapse( url.slice( off ) ); + url = url.slice( 0, off ); + } + + // If it's a function + if ( isFunction( params ) ) { + + // We assume that it's the callback + callback = params; + params = undefined; + + // Otherwise, build a param string + } else if ( params && typeof params === "object" ) { + type = "POST"; + } + + // If we have elements to modify, make the request + if ( self.length > 0 ) { + jQuery.ajax( { + url: url, + + // If "type" variable is undefined, then "GET" method will be used. + // Make value of this field explicit since + // user can override it through ajaxSetup method + type: type || "GET", + dataType: "html", + data: params + } ).done( function( responseText ) { + + // Save response for use in complete callback + response = arguments; + + self.html( selector ? + + // If a selector was specified, locate the right elements in a dummy div + // Exclude scripts to avoid IE 'Permission Denied' errors + jQuery( "
" ).append( jQuery.parseHTML( responseText ) ).find( selector ) : + + // Otherwise use the full result + responseText ); + + // If the request succeeds, this function gets "data", "status", "jqXHR" + // but they are ignored because response was set above. + // If it fails, this function gets "jqXHR", "status", "error" + } ).always( callback && function( jqXHR, status ) { + self.each( function() { + callback.apply( this, response || [ jqXHR.responseText, status, jqXHR ] ); + } ); + } ); + } + + return this; +}; + +} ); diff --git a/src/ajax/script.js b/src/ajax/script.js new file mode 100644 index 0000000..410c82c --- /dev/null +++ b/src/ajax/script.js @@ -0,0 +1,74 @@ +define( [ + "../core", + "../var/document", + "../ajax" +], function( jQuery, document ) { + +"use strict"; + +// Prevent auto-execution of scripts when no explicit dataType was provided (See gh-2432) +jQuery.ajaxPrefilter( function( s ) { + if ( s.crossDomain ) { + s.contents.script = false; + } +} ); + +// Install script dataType +jQuery.ajaxSetup( { + accepts: { + script: "text/javascript, application/javascript, " + + "application/ecmascript, application/x-ecmascript" + }, + contents: { + script: /\b(?:java|ecma)script\b/ + }, + converters: { + "text script": function( text ) { + jQuery.globalEval( text ); + return text; + } + } +} ); + +// Handle cache's special case and crossDomain +jQuery.ajaxPrefilter( "script", function( s ) { + if ( s.cache === undefined ) { + s.cache = false; + } + if ( s.crossDomain ) { + s.type = "GET"; + } +} ); + +// Bind script tag hack transport +jQuery.ajaxTransport( "script", function( s ) { + + // This transport only deals with cross domain or forced-by-attrs requests + if ( s.crossDomain || s.scriptAttrs ) { + var script, callback; + return { + send: function( _, complete ) { + script = jQuery( " + + + + + + + diff --git a/test/data/ajax/unreleasedXHR.html b/test/data/ajax/unreleasedXHR.html new file mode 100644 index 0000000..51c5b94 --- /dev/null +++ b/test/data/ajax/unreleasedXHR.html @@ -0,0 +1,26 @@ + + + + +Attempt to block tests because of dangling XHR requests (IE) + + + + + + + + diff --git a/test/data/badcall.js b/test/data/badcall.js new file mode 100644 index 0000000..cc2f2b4 --- /dev/null +++ b/test/data/badcall.js @@ -0,0 +1 @@ +undefined(); diff --git a/test/data/badjson.js b/test/data/badjson.js new file mode 100644 index 0000000..ad2de7a --- /dev/null +++ b/test/data/badjson.js @@ -0,0 +1 @@ +{bad: toTheBone} diff --git a/test/data/cleanScript.html b/test/data/cleanScript.html new file mode 100644 index 0000000..c37694a --- /dev/null +++ b/test/data/cleanScript.html @@ -0,0 +1,10 @@ + + diff --git a/test/data/core/aliased.html b/test/data/core/aliased.html new file mode 100644 index 0000000..87b5871 --- /dev/null +++ b/test/data/core/aliased.html @@ -0,0 +1,25 @@ + + + + + alias-masked DOM properties (#14074) + + + + + +
+ +
+ + + diff --git a/test/data/core/cc_on.html b/test/data/core/cc_on.html new file mode 100644 index 0000000..5db1891 --- /dev/null +++ b/test/data/core/cc_on.html @@ -0,0 +1,23 @@ + + + + + + + + + + + + diff --git a/test/data/core/dynamic_ready.html b/test/data/core/dynamic_ready.html new file mode 100644 index 0000000..fade7ad --- /dev/null +++ b/test/data/core/dynamic_ready.html @@ -0,0 +1,36 @@ + + + + + + + + + + + + + diff --git a/test/data/core/globaleval-context.html b/test/data/core/globaleval-context.html new file mode 100644 index 0000000..1b75e3a --- /dev/null +++ b/test/data/core/globaleval-context.html @@ -0,0 +1,15 @@ + + + + + body + + +
+ + + + + diff --git a/test/data/core/jquery-iterability-transpiled-es6.js b/test/data/core/jquery-iterability-transpiled-es6.js new file mode 100644 index 0000000..0c3bda6 --- /dev/null +++ b/test/data/core/jquery-iterability-transpiled-es6.js @@ -0,0 +1,14 @@ +/* global startIframeTest */ + +jQuery( function() { + "use strict"; + + var elem = jQuery( "
" ); + var result = ""; + var i; + for ( i of elem ) { + result += i.nodeName; + } + + startIframeTest( result ); +} ); diff --git a/test/data/core/jquery-iterability-transpiled.html b/test/data/core/jquery-iterability-transpiled.html new file mode 100644 index 0000000..69ac182 --- /dev/null +++ b/test/data/core/jquery-iterability-transpiled.html @@ -0,0 +1,14 @@ + + + + + jQuery objects transpiled iterability test page + + + + + + +

jQuery objects transpiled iterability test page

+ + diff --git a/test/data/core/onready.html b/test/data/core/onready.html new file mode 100644 index 0000000..88ba94a --- /dev/null +++ b/test/data/core/onready.html @@ -0,0 +1,25 @@ + + + + + alias-masked DOM properties (#14074) + + + + + +
+ +
+ + + \ No newline at end of file diff --git a/test/data/csp-nonce-external.html b/test/data/csp-nonce-external.html new file mode 100644 index 0000000..8baa85c --- /dev/null +++ b/test/data/csp-nonce-external.html @@ -0,0 +1,13 @@ + + + + + CSP nonce via jQuery.globalEval Test Page + + + + + +

CSP nonce for external script Test Page

+ + diff --git a/test/data/csp-nonce-external.js b/test/data/csp-nonce-external.js new file mode 100644 index 0000000..efedd5a --- /dev/null +++ b/test/data/csp-nonce-external.js @@ -0,0 +1,5 @@ +/* global startIframeTest */ + +jQuery( function() { + $( "body" ).append( "" ); +} ); diff --git a/test/data/csp-nonce-globaleval.html b/test/data/csp-nonce-globaleval.html new file mode 100644 index 0000000..aa620c5 --- /dev/null +++ b/test/data/csp-nonce-globaleval.html @@ -0,0 +1,13 @@ + + + + + CSP nonce via jQuery.globalEval Test Page + + + + + +

CSP nonce via jQuery.globalEval Test Page

+ + diff --git a/test/data/csp-nonce-globaleval.js b/test/data/csp-nonce-globaleval.js new file mode 100644 index 0000000..23d549f --- /dev/null +++ b/test/data/csp-nonce-globaleval.js @@ -0,0 +1,5 @@ +/* global startIframeTest */ + +jQuery( function() { + $.globalEval( "startIframeTest()", { nonce: "jquery+hardcoded+nonce" } ); +} ); diff --git a/test/data/csp-nonce.html b/test/data/csp-nonce.html new file mode 100644 index 0000000..d35c33f --- /dev/null +++ b/test/data/csp-nonce.html @@ -0,0 +1,13 @@ + + + + + CSP nonce Test Page + + + + + +

CSP nonce Test Page

+ + diff --git a/test/data/csp-nonce.js b/test/data/csp-nonce.js new file mode 100644 index 0000000..507e87e --- /dev/null +++ b/test/data/csp-nonce.js @@ -0,0 +1,8 @@ +/* global startIframeTest */ + +jQuery( function() { + var script = document.createElement( "script" ); + script.setAttribute( "nonce", "jquery+hardcoded+nonce" ); + script.innerHTML = "startIframeTest()"; + $( document.head ).append( script ); +} ); diff --git a/test/data/csp.include.html b/test/data/csp.include.html new file mode 100644 index 0000000..17e2ef0 --- /dev/null +++ b/test/data/csp.include.html @@ -0,0 +1,14 @@ + + + + + CSP Test Page + + + + + + +

CSP Test Page

+ + diff --git a/test/data/css/cssWidthBeforeDocReady.html b/test/data/css/cssWidthBeforeDocReady.html new file mode 100644 index 0000000..4ebc2c9 --- /dev/null +++ b/test/data/css/cssWidthBeforeDocReady.html @@ -0,0 +1,22 @@ + + + + + + + +
+ + + + + diff --git a/test/data/css/cssWidthBrowserZoom.html b/test/data/css/cssWidthBrowserZoom.html new file mode 100644 index 0000000..133daf6 --- /dev/null +++ b/test/data/css/cssWidthBrowserZoom.html @@ -0,0 +1,30 @@ + + + + + + + +
+ + + + + diff --git a/test/data/dashboard.xml b/test/data/dashboard.xml new file mode 100644 index 0000000..5a6f561 --- /dev/null +++ b/test/data/dashboard.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/test/data/data/dataAttrs.html b/test/data/data/dataAttrs.html new file mode 100644 index 0000000..785150e --- /dev/null +++ b/test/data/data/dataAttrs.html @@ -0,0 +1,17 @@ + + + + + IE11 onpageshow strangeness (#14894) + + + + + + Test for #14894 + + diff --git a/test/data/dimensions/documentLarge.html b/test/data/dimensions/documentLarge.html new file mode 100644 index 0000000..9d6a729 --- /dev/null +++ b/test/data/dimensions/documentLarge.html @@ -0,0 +1,21 @@ + + + + + + + + +
+ + +
+ + diff --git a/test/data/event/focusElem.html b/test/data/event/focusElem.html new file mode 100644 index 0000000..1940e8b --- /dev/null +++ b/test/data/event/focusElem.html @@ -0,0 +1,17 @@ + + + + + .focus() (activeElement access #13393) + + + + + + + + + diff --git a/test/data/event/focusinCrossFrame.html b/test/data/event/focusinCrossFrame.html new file mode 100644 index 0000000..6dd187e --- /dev/null +++ b/test/data/event/focusinCrossFrame.html @@ -0,0 +1,19 @@ + + + + + focusin event cross-frame (#14180) + + + + + + + + + diff --git a/test/data/event/interactiveReady.html b/test/data/event/interactiveReady.html new file mode 100644 index 0000000..a80b794 --- /dev/null +++ b/test/data/event/interactiveReady.html @@ -0,0 +1,24 @@ + + + + +Test case for gh-2100 + + + + + + + + + +
+ + diff --git a/test/data/event/onbeforeunload.html b/test/data/event/onbeforeunload.html new file mode 100644 index 0000000..11ad196 --- /dev/null +++ b/test/data/event/onbeforeunload.html @@ -0,0 +1,20 @@ + + + + + diff --git a/test/data/event/promiseReady.html b/test/data/event/promiseReady.html new file mode 100644 index 0000000..c6e0862 --- /dev/null +++ b/test/data/event/promiseReady.html @@ -0,0 +1,18 @@ + + + + +Test case for jQuery ticket #11470 + + + + + + + + diff --git a/test/data/event/syncReady.html b/test/data/event/syncReady.html new file mode 100644 index 0000000..5aa5104 --- /dev/null +++ b/test/data/event/syncReady.html @@ -0,0 +1,24 @@ + + + + +Test case for jQuery ticket #10067 + + + + + + + + + +
+ + diff --git a/test/data/event/triggerunload.html b/test/data/event/triggerunload.html new file mode 100644 index 0000000..02be59d --- /dev/null +++ b/test/data/event/triggerunload.html @@ -0,0 +1,19 @@ + + + + + + diff --git a/test/data/frame.html b/test/data/frame.html new file mode 100644 index 0000000..98107be --- /dev/null +++ b/test/data/frame.html @@ -0,0 +1,9 @@ + + + frame + + + + + + diff --git a/test/data/iframe.html b/test/data/iframe.html new file mode 100644 index 0000000..ad646c4 --- /dev/null +++ b/test/data/iframe.html @@ -0,0 +1,8 @@ + + + iframe + + +
span text
+ + diff --git a/test/data/iframeTest.js b/test/data/iframeTest.js new file mode 100644 index 0000000..4db5683 --- /dev/null +++ b/test/data/iframeTest.js @@ -0,0 +1,7 @@ +window.startIframeTest = function() { + var args = Array.prototype.slice.call( arguments ); + + // Note: jQuery may be undefined if page did not load it + args.unshift( window.jQuery, window, document ); + window.parent.iframeCallback.apply( null, args ); +}; diff --git a/test/data/inner_module.js b/test/data/inner_module.js new file mode 100644 index 0000000..b13315a --- /dev/null +++ b/test/data/inner_module.js @@ -0,0 +1 @@ +QUnit.assert.ok( true, "evaluated: inner module with src" ); diff --git a/test/data/inner_nomodule.js b/test/data/inner_nomodule.js new file mode 100644 index 0000000..c37b4ed --- /dev/null +++ b/test/data/inner_nomodule.js @@ -0,0 +1 @@ +QUnit.assert.ok( !QUnit.moduleTypeSupported, "evaluated: inner nomodule script with src" ); diff --git a/test/data/jquery-1.9.1.js b/test/data/jquery-1.9.1.js new file mode 100644 index 0000000..80c97a2 --- /dev/null +++ b/test/data/jquery-1.9.1.js @@ -0,0 +1,9885 @@ +/*! + * jQuery JavaScript Library v1.9.1 + * http://jquery.com/ + * + * Includes Sizzle.js + * http://sizzlejs.com/ + * + * Copyright 2005, 2012 jQuery Foundation, Inc. and other contributors + * Released under the MIT license + * http://jquery.org/license + * + * Date: 2013-2-4 + */ +( function( window, undefined ) { + +// Can't do this because several apps including ASP.NET trace +// the stack via arguments.caller.callee and Firefox dies if +// you try to trace through "use strict" call chains. (#13335) +// Support: Firefox 18+ +//"use strict"; +var + + // The deferred used on DOM ready + readyList, + + // A central reference to the root jQuery(document) + rootjQuery, + + // Support: IE<9 + // For `typeof node.method` instead of `node.method !== undefined` + core_strundefined = typeof undefined, + + // Use the correct document accordingly with window argument (sandbox) + document = window.document, + location = window.location, + + // Map over jQuery in case of overwrite + _jQuery = window.jQuery, + + // Map over the $ in case of overwrite + _$ = window.$, + + // [[Class]] -> type pairs + class2type = {}, + + // List of deleted data cache ids, so we can reuse them + core_deletedIds = [], + + core_version = "1.9.1", + + // Save a reference to some core methods + core_concat = core_deletedIds.concat, + core_push = core_deletedIds.push, + core_slice = core_deletedIds.slice, + core_indexOf = core_deletedIds.indexOf, + core_toString = class2type.toString, + core_hasOwn = class2type.hasOwnProperty, + core_trim = core_version.trim, + + // Define a local copy of jQuery + jQuery = function( selector, context ) { + + // The jQuery object is actually just the init constructor 'enhanced' + return new jQuery.fn.init( selector, context, rootjQuery ); + }, + + // Used for matching numbers + core_pnum = /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source, + + // Used for splitting on whitespace + core_rnotwhite = /\S+/g, + + // Make sure we trim BOM and NBSP (here's looking at you, Safari 5.0 and IE) + rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, + + // A simple way to check for HTML strings + // Prioritize #id over to avoid XSS via location.hash (#9521) + // Strict HTML recognition (#11290: must start with <) + rquickExpr = /^(?:(<[\w\W]+>)[^>]*|#([\w-]*))$/, + + // Match a standalone tag + rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>|)$/, + + // JSON RegExp + rvalidchars = /^[\],:{}\s]*$/, + rvalidbraces = /(?:^|:|,)(?:\s*\[)+/g, + rvalidescape = /\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g, + rvalidtokens = /"[^"\\\r\n]*"|true|false|null|-?(?:\d+\.|)\d+(?:[eE][+-]?\d+|)/g, + + // Matches dashed string for camelizing + rmsPrefix = /^-ms-/, + rdashAlpha = /-([\da-z])/gi, + + // Used by jQuery.camelCase as callback to replace() + fcamelCase = function( all, letter ) { + return letter.toUpperCase(); + }, + + // The ready event handler + completed = function( event ) { + + // readyState === "complete" is good enough for us to call the dom ready in oldIE + if ( document.addEventListener || event.type === "load" || document.readyState === "complete" ) { + detach(); + jQuery.ready(); + } + }, + + // Clean-up method for dom ready events + detach = function() { + if ( document.addEventListener ) { + document.removeEventListener( "DOMContentLoaded", completed, false ); + window.removeEventListener( "load", completed, false ); + + } else { + document.detachEvent( "onreadystatechange", completed ); + window.detachEvent( "onload", completed ); + } + }; + +jQuery.fn = jQuery.prototype = { + + // The current version of jQuery being used + jquery: core_version, + + constructor: jQuery, + init: function( selector, context, rootjQuery ) { + var match, elem; + + // HANDLE: $(""), $(null), $(undefined), $(false) + if ( !selector ) { + return this; + } + + // Handle HTML strings + if ( typeof selector === "string" ) { + if ( selector.charAt( 0 ) === "<" && selector.charAt( selector.length - 1 ) === ">" && selector.length >= 3 ) { + + // Assume that strings that start and end with <> are HTML and skip the regex check + match = [ null, selector, null ]; + + } else { + match = rquickExpr.exec( selector ); + } + + // Match html or make sure no context is specified for #id + if ( match && ( match[ 1 ] || !context ) ) { + + // HANDLE: $(html) -> $(array) + if ( match[ 1 ] ) { + context = context instanceof jQuery ? context[ 0 ] : context; + + // scripts is true for back-compat + jQuery.merge( this, jQuery.parseHTML( + match[ 1 ], + context && context.nodeType ? context.ownerDocument || context : document, + true + ) ); + + // HANDLE: $(html, props) + if ( rsingleTag.test( match[ 1 ] ) && jQuery.isPlainObject( context ) ) { + for ( match in context ) { + + // Properties of context are called as methods if possible + if ( jQuery.isFunction( this[ match ] ) ) { + this[ match ]( context[ match ] ); + + // ...and otherwise set as attributes + } else { + this.attr( match, context[ match ] ); + } + } + } + + return this; + + // HANDLE: $(#id) + } else { + elem = document.getElementById( match[ 2 ] ); + + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document #6963 + if ( elem && elem.parentNode ) { + + // Handle the case where IE and Opera return items + // by name instead of ID + if ( elem.id !== match[ 2 ] ) { + return rootjQuery.find( selector ); + } + + // Otherwise, we inject the element directly into the jQuery object + this.length = 1; + this[ 0 ] = elem; + } + + this.context = document; + this.selector = selector; + return this; + } + + // HANDLE: $(expr, $(...)) + } else if ( !context || context.jquery ) { + return ( context || rootjQuery ).find( selector ); + + // HANDLE: $(expr, context) + // (which is just equivalent to: $(context).find(expr) + } else { + return this.constructor( context ).find( selector ); + } + + // HANDLE: $(DOMElement) + } else if ( selector.nodeType ) { + this.context = this[ 0 ] = selector; + this.length = 1; + return this; + + // HANDLE: $(function) + // Shortcut for document ready + } else if ( jQuery.isFunction( selector ) ) { + return rootjQuery.ready( selector ); + } + + if ( selector.selector !== undefined ) { + this.selector = selector.selector; + this.context = selector.context; + } + + return jQuery.makeArray( selector, this ); + }, + + // Start with an empty selector + selector: "", + + // The default length of a jQuery object is 0 + length: 0, + + // The number of elements contained in the matched element set + size: function() { + return this.length; + }, + + toArray: function() { + return core_slice.call( this ); + }, + + // Get the Nth element in the matched element set OR + // Get the whole matched element set as a clean array + get: function( num ) { + return num == null ? + + // Return a 'clean' array + this.toArray() : + + // Return just the object + ( num < 0 ? this[ this.length + num ] : this[ num ] ); + }, + + // Take an array of elements and push it onto the stack + // (returning the new matched element set) + pushStack: function( elems ) { + + // Build a new jQuery matched element set + var ret = jQuery.merge( this.constructor(), elems ); + + // Add the old object onto the stack (as a reference) + ret.prevObject = this; + ret.context = this.context; + + // Return the newly-formed element set + return ret; + }, + + // Execute a callback for every element in the matched set. + // (You can seed the arguments with an array of args, but this is + // only used internally.) + each: function( callback, args ) { + return jQuery.each( this, callback, args ); + }, + + ready: function( fn ) { + + // Add the callback + jQuery.ready.promise().done( fn ); + + return this; + }, + + slice: function() { + return this.pushStack( core_slice.apply( this, arguments ) ); + }, + + first: function() { + return this.eq( 0 ); + }, + + last: function() { + return this.eq( -1 ); + }, + + eq: function( i ) { + var len = this.length, + j = +i + ( i < 0 ? len : 0 ); + return this.pushStack( j >= 0 && j < len ? [ this[ j ] ] : [] ); + }, + + map: function( callback ) { + return this.pushStack( jQuery.map( this, function( elem, i ) { + return callback.call( elem, i, elem ); + } ) ); + }, + + end: function() { + return this.prevObject || this.constructor( null ); + }, + + // For internal use only. + // Behaves like an Array's method, not like a jQuery method. + push: core_push, + sort: [].sort, + splice: [].splice +}; + +// Give the init function the jQuery prototype for later instantiation +jQuery.fn.init.prototype = jQuery.fn; + +jQuery.extend = jQuery.fn.extend = function() { + var src, copyIsArray, copy, name, options, clone, + target = arguments[ 0 ] || {}, + i = 1, + length = arguments.length, + deep = false; + + // Handle a deep copy situation + if ( typeof target === "boolean" ) { + deep = target; + target = arguments[ 1 ] || {}; + + // skip the boolean and the target + i = 2; + } + + // Handle case when target is a string or something (possible in deep copy) + if ( typeof target !== "object" && !jQuery.isFunction( target ) ) { + target = {}; + } + + // extend jQuery itself if only one argument is passed + if ( length === i ) { + target = this; + --i; + } + + for ( ; i < length; i++ ) { + + // Only deal with non-null/undefined values + if ( ( options = arguments[ i ] ) != null ) { + + // Extend the base object + for ( name in options ) { + src = target[ name ]; + copy = options[ name ]; + + // Prevent never-ending loop + if ( target === copy ) { + continue; + } + + // Recurse if we're merging plain objects or arrays + if ( deep && copy && ( jQuery.isPlainObject( copy ) || ( copyIsArray = jQuery.isArray( copy ) ) ) ) { + if ( copyIsArray ) { + copyIsArray = false; + clone = src && jQuery.isArray( src ) ? src : []; + + } else { + clone = src && jQuery.isPlainObject( src ) ? src : {}; + } + + // Never move original objects, clone them + target[ name ] = jQuery.extend( deep, clone, copy ); + + // Don't bring in undefined values + } else if ( copy !== undefined ) { + target[ name ] = copy; + } + } + } + } + + // Return the modified object + return target; +}; + +jQuery.extend( { + noConflict: function( deep ) { + if ( window.$ === jQuery ) { + window.$ = _$; + } + + if ( deep && window.jQuery === jQuery ) { + window.jQuery = _jQuery; + } + + return jQuery; + }, + + // Is the DOM ready to be used? Set to true once it occurs. + isReady: false, + + // A counter to track how many items to wait for before + // the ready event fires. See #6781 + readyWait: 1, + + // Hold (or release) the ready event + holdReady: function( hold ) { + if ( hold ) { + jQuery.readyWait++; + } else { + jQuery.ready( true ); + } + }, + + // Handle when the DOM is ready + ready: function( wait ) { + + // Abort if there are pending holds or we're already ready + if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) { + return; + } + + // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443). + if ( !document.body ) { + return setTimeout( jQuery.ready ); + } + + // Remember that the DOM is ready + jQuery.isReady = true; + + // If a normal DOM Ready event fired, decrement, and wait if need be + if ( wait !== true && --jQuery.readyWait > 0 ) { + return; + } + + // If there are functions bound, to execute + readyList.resolveWith( document, [ jQuery ] ); + + // Trigger any bound ready events + if ( jQuery.fn.trigger ) { + jQuery( document ).trigger( "ready" ).off( "ready" ); + } + }, + + // See test/unit/core.js for details concerning isFunction. + // Since version 1.3, DOM methods and functions like alert + // aren't supported. They return false on IE (#2968). + isFunction: function( obj ) { + return jQuery.type( obj ) === "function"; + }, + + isArray: Array.isArray || function( obj ) { + return jQuery.type( obj ) === "array"; + }, + + isWindow: function( obj ) { + return obj != null && obj == obj.window; + }, + + isNumeric: function( obj ) { + return !isNaN( parseFloat( obj ) ) && isFinite( obj ); + }, + + type: function( obj ) { + if ( obj == null ) { + return String( obj ); + } + return typeof obj === "object" || typeof obj === "function" ? + class2type[ core_toString.call( obj ) ] || "object" : + typeof obj; + }, + + isPlainObject: function( obj ) { + + // Must be an Object. + // Because of IE, we also have to check the presence of the constructor property. + // Make sure that DOM nodes and window objects don't pass through, as well + if ( !obj || jQuery.type( obj ) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) { + return false; + } + + try { + + // Not own constructor property must be Object + if ( obj.constructor && + !core_hasOwn.call( obj, "constructor" ) && + !core_hasOwn.call( obj.constructor.prototype, "isPrototypeOf" ) ) { + return false; + } + } catch ( e ) { + + // IE8,9 Will throw exceptions on certain host objects #9897 + return false; + } + + // Own properties are enumerated firstly, so to speed up, + // if last one is own, then all properties are own. + + var key; + for ( key in obj ) {} + + return key === undefined || core_hasOwn.call( obj, key ); + }, + + isEmptyObject: function( obj ) { + var name; + for ( name in obj ) { + return false; + } + return true; + }, + + error: function( msg ) { + throw new Error( msg ); + }, + + // data: string of html + // context (optional): If specified, the fragment will be created in this context, defaults to document + // keepScripts (optional): If true, will include scripts passed in the html string + parseHTML: function( data, context, keepScripts ) { + if ( !data || typeof data !== "string" ) { + return null; + } + if ( typeof context === "boolean" ) { + keepScripts = context; + context = false; + } + context = context || document; + + var parsed = rsingleTag.exec( data ), + scripts = !keepScripts && []; + + // Single tag + if ( parsed ) { + return [ context.createElement( parsed[ 1 ] ) ]; + } + + parsed = jQuery.buildFragment( [ data ], context, scripts ); + if ( scripts ) { + jQuery( scripts ).remove(); + } + return jQuery.merge( [], parsed.childNodes ); + }, + + parseJSON: function( data ) { + + // Attempt to parse using the native JSON parser first + if ( window.JSON && window.JSON.parse ) { + return window.JSON.parse( data ); + } + + if ( data === null ) { + return data; + } + + if ( typeof data === "string" ) { + + // Make sure leading/trailing whitespace is removed (IE can't handle it) + data = jQuery.trim( data ); + + if ( data ) { + + // Make sure the incoming data is actual JSON + // Logic borrowed from http://json.org/json2.js + if ( rvalidchars.test( data.replace( rvalidescape, "@" ) + .replace( rvalidtokens, "]" ) + .replace( rvalidbraces, "" ) ) ) { + + return ( new Function( "return " + data ) )(); + } + } + } + + jQuery.error( "Invalid JSON: " + data ); + }, + + // Cross-browser xml parsing + parseXML: function( data ) { + var xml, tmp; + if ( !data || typeof data !== "string" ) { + return null; + } + try { + if ( window.DOMParser ) { // Standard + tmp = new DOMParser(); + xml = tmp.parseFromString( data, "text/xml" ); + } else { // IE + xml = new ActiveXObject( "Microsoft.XMLDOM" ); + xml.async = "false"; + xml.loadXML( data ); + } + } catch ( e ) { + xml = undefined; + } + if ( !xml || !xml.documentElement || xml.getElementsByTagName( "parsererror" ).length ) { + jQuery.error( "Invalid XML: " + data ); + } + return xml; + }, + + noop: function() {}, + + // Evaluates a script in a global context + // Workarounds based on findings by Jim Driscoll + // http://weblogs.java.net/blog/driscoll/archive/2009/09/08/eval-javascript-global-context + globalEval: function( data ) { + if ( data && jQuery.trim( data ) ) { + + // We use execScript on Internet Explorer + // We use an anonymous function so that context is window + // rather than jQuery in Firefox + ( window.execScript || function( data ) { + window[ "eval" ].call( window, data ); + } )( data ); + } + }, + + // Convert dashed to camelCase; used by the css and data modules + // Microsoft forgot to hump their vendor prefix (#9572) + camelCase: function( string ) { + return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); + }, + + nodeName: function( elem, name ) { + return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase(); + }, + + // args is for internal usage only + each: function( obj, callback, args ) { + var value, + i = 0, + length = obj.length, + isArray = isArraylike( obj ); + + if ( args ) { + if ( isArray ) { + for ( ; i < length; i++ ) { + value = callback.apply( obj[ i ], args ); + + if ( value === false ) { + break; + } + } + } else { + for ( i in obj ) { + value = callback.apply( obj[ i ], args ); + + if ( value === false ) { + break; + } + } + } + + // A special, fast, case for the most common use of each + } else { + if ( isArray ) { + for ( ; i < length; i++ ) { + value = callback.call( obj[ i ], i, obj[ i ] ); + + if ( value === false ) { + break; + } + } + } else { + for ( i in obj ) { + value = callback.call( obj[ i ], i, obj[ i ] ); + + if ( value === false ) { + break; + } + } + } + } + + return obj; + }, + + // Use native String.trim function wherever possible + trim: core_trim && !core_trim.call( "\uFEFF\xA0" ) ? + function( text ) { + return text == null ? + "" : + core_trim.call( text ); + } : + + // Otherwise use our own trimming functionality + function( text ) { + return text == null ? + "" : + ( text + "" ).replace( rtrim, "" ); + }, + + // results is for internal usage only + makeArray: function( arr, results ) { + var ret = results || []; + + if ( arr != null ) { + if ( isArraylike( Object( arr ) ) ) { + jQuery.merge( ret, + typeof arr === "string" ? + [ arr ] : arr + ); + } else { + core_push.call( ret, arr ); + } + } + + return ret; + }, + + inArray: function( elem, arr, i ) { + var len; + + if ( arr ) { + if ( core_indexOf ) { + return core_indexOf.call( arr, elem, i ); + } + + len = arr.length; + i = i ? i < 0 ? Math.max( 0, len + i ) : i : 0; + + for ( ; i < len; i++ ) { + + // Skip accessing in sparse arrays + if ( i in arr && arr[ i ] === elem ) { + return i; + } + } + } + + return -1; + }, + + merge: function( first, second ) { + var l = second.length, + i = first.length, + j = 0; + + if ( typeof l === "number" ) { + for ( ; j < l; j++ ) { + first[ i++ ] = second[ j ]; + } + } else { + while ( second[ j ] !== undefined ) { + first[ i++ ] = second[ j++ ]; + } + } + + first.length = i; + + return first; + }, + + grep: function( elems, callback, inv ) { + var retVal, + ret = [], + i = 0, + length = elems.length; + inv = !!inv; + + // Go through the array, only saving the items + // that pass the validator function + for ( ; i < length; i++ ) { + retVal = !!callback( elems[ i ], i ); + if ( inv !== retVal ) { + ret.push( elems[ i ] ); + } + } + + return ret; + }, + + // arg is for internal usage only + map: function( elems, callback, arg ) { + var value, + i = 0, + length = elems.length, + isArray = isArraylike( elems ), + ret = []; + + // Go through the array, translating each of the items to their + if ( isArray ) { + for ( ; i < length; i++ ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret[ ret.length ] = value; + } + } + + // Go through every key on the object, + } else { + for ( i in elems ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret[ ret.length ] = value; + } + } + } + + // Flatten any nested arrays + return core_concat.apply( [], ret ); + }, + + // A global GUID counter for objects + guid: 1, + + // Bind a function to a context, optionally partially applying any + // arguments. + proxy: function( fn, context ) { + var args, proxy, tmp; + + if ( typeof context === "string" ) { + tmp = fn[ context ]; + context = fn; + fn = tmp; + } + + // Quick check to determine if target is callable, in the spec + // this throws a TypeError, but we will just return undefined. + if ( !jQuery.isFunction( fn ) ) { + return undefined; + } + + // Simulated bind + args = core_slice.call( arguments, 2 ); + proxy = function() { + return fn.apply( context || this, args.concat( core_slice.call( arguments ) ) ); + }; + + // Set the guid of unique handler to the same of original handler, so it can be removed + proxy.guid = fn.guid = fn.guid || jQuery.guid++; + + return proxy; + }, + + // Multifunctional method to get and set values of a collection + // The value/s can optionally be executed if it's a function + access: function( elems, fn, key, value, chainable, emptyGet, raw ) { + var i = 0, + length = elems.length, + bulk = key == null; + + // Sets many values + if ( jQuery.type( key ) === "object" ) { + chainable = true; + for ( i in key ) { + jQuery.access( elems, fn, i, key[ i ], true, emptyGet, raw ); + } + + // Sets one value + } else if ( value !== undefined ) { + chainable = true; + + if ( !jQuery.isFunction( value ) ) { + raw = true; + } + + if ( bulk ) { + + // Bulk operations run against the entire set + if ( raw ) { + fn.call( elems, value ); + fn = null; + + // ...except when executing function values + } else { + bulk = fn; + fn = function( elem, key, value ) { + return bulk.call( jQuery( elem ), value ); + }; + } + } + + if ( fn ) { + for ( ; i < length; i++ ) { + fn( elems[ i ], key, raw ? value : value.call( elems[ i ], i, fn( elems[ i ], key ) ) ); + } + } + } + + return chainable ? + elems : + + // Gets + bulk ? + fn.call( elems ) : + length ? fn( elems[ 0 ], key ) : emptyGet; + }, + + now: function() { + return ( new Date() ).getTime(); + } +} ); + +jQuery.ready.promise = function( obj ) { + if ( !readyList ) { + + readyList = jQuery.Deferred(); + + // Catch cases where $(document).ready() is called after the browser event has already occurred. + // we once tried to use readyState "interactive" here, but it caused issues like the one + // discovered by ChrisS here: http://bugs.jquery.com/ticket/12282#comment:15 + if ( document.readyState === "complete" ) { + + // Handle it asynchronously to allow scripts the opportunity to delay ready + setTimeout( jQuery.ready ); + + // Standards-based browsers support DOMContentLoaded + } else if ( document.addEventListener ) { + + // Use the handy event callback + document.addEventListener( "DOMContentLoaded", completed, false ); + + // A fallback to window.onload, that will always work + window.addEventListener( "load", completed, false ); + + // If IE event model is used + } else { + + // Ensure firing before onload, maybe late but safe also for iframes + document.attachEvent( "onreadystatechange", completed ); + + // A fallback to window.onload, that will always work + window.attachEvent( "onload", completed ); + + // If IE and not a frame + // continually check to see if the document is ready + var top = false; + + try { + top = window.frameElement == null && document.documentElement; + } catch ( e ) {} + + if ( top && top.doScroll ) { + ( function doScrollCheck() { + if ( !jQuery.isReady ) { + + try { + + // Use the trick by Diego Perini + // http://javascript.nwbox.com/IEContentLoaded/ + top.doScroll( "left" ); + } catch ( e ) { + return setTimeout( doScrollCheck, 50 ); + } + + // detach all dom ready events + detach(); + + // and execute any waiting functions + jQuery.ready(); + } + } )(); + } + } + } + return readyList.promise( obj ); +}; + +// Populate the class2type map +jQuery.each( "Boolean Number String Function Array Date RegExp Object Error".split( " " ), function( i, name ) { + class2type[ "[object " + name + "]" ] = name.toLowerCase(); +} ); + +function isArraylike( obj ) { + var length = obj.length, + type = jQuery.type( obj ); + + if ( jQuery.isWindow( obj ) ) { + return false; + } + + if ( obj.nodeType === 1 && length ) { + return true; + } + + return type === "array" || type !== "function" && + ( length === 0 || + typeof length === "number" && length > 0 && ( length - 1 ) in obj ); +} + +// All jQuery objects should point back to these +rootjQuery = jQuery( document ); + +// String to Object options format cache +var optionsCache = {}; + +// Convert String-formatted options into Object-formatted ones and store in cache +function createOptions( options ) { + var object = optionsCache[ options ] = {}; + jQuery.each( options.match( core_rnotwhite ) || [], function( _, flag ) { + object[ flag ] = true; + } ); + return object; +} + +/* + * Create a callback list using the following parameters: + * + * options: an optional list of space-separated options that will change how + * the callback list behaves or a more traditional option object + * + * By default a callback list will act like an event callback list and can be + * "fired" multiple times. + * + * Possible options: + * + * once: will ensure the callback list can only be fired once (like a Deferred) + * + * memory: will keep track of previous values and will call any callback added + * after the list has been fired right away with the latest "memorized" + * values (like a Deferred) + * + * unique: will ensure a callback can only be added once (no duplicate in the list) + * + * stopOnFalse: interrupt callings when a callback returns false + * + */ +jQuery.Callbacks = function( options ) { + + // Convert options from String-formatted to Object-formatted if needed + // (we check in cache first) + options = typeof options === "string" ? + ( optionsCache[ options ] || createOptions( options ) ) : + jQuery.extend( {}, options ); + + var // Flag to know if list is currently firing + firing, + + // Last fire value (for non-forgettable lists) + memory, + + // Flag to know if list was already fired + fired, + + // End of the loop when firing + firingLength, + + // Index of currently firing callback (modified by remove if needed) + firingIndex, + + // First callback to fire (used internally by add and fireWith) + firingStart, + + // Actual callback list + list = [], + + // Stack of fire calls for repeatable lists + stack = !options.once && [], + + // Fire callbacks + fire = function( data ) { + memory = options.memory && data; + fired = true; + firingIndex = firingStart || 0; + firingStart = 0; + firingLength = list.length; + firing = true; + for ( ; list && firingIndex < firingLength; firingIndex++ ) { + if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) { + memory = false; // To prevent further calls using add + break; + } + } + firing = false; + if ( list ) { + if ( stack ) { + if ( stack.length ) { + fire( stack.shift() ); + } + } else if ( memory ) { + list = []; + } else { + self.disable(); + } + } + }, + + // Actual Callbacks object + self = { + + // Add a callback or a collection of callbacks to the list + add: function() { + if ( list ) { + + // First, we save the current length + var start = list.length; + ( function add( args ) { + jQuery.each( args, function( _, arg ) { + var type = jQuery.type( arg ); + if ( type === "function" ) { + if ( !options.unique || !self.has( arg ) ) { + list.push( arg ); + } + } else if ( arg && arg.length && type !== "string" ) { + + // Inspect recursively + add( arg ); + } + } ); + } )( arguments ); + + // Do we need to add the callbacks to the + // current firing batch? + if ( firing ) { + firingLength = list.length; + + // With memory, if we're not firing then + // we should call right away + } else if ( memory ) { + firingStart = start; + fire( memory ); + } + } + return this; + }, + + // Remove a callback from the list + remove: function() { + if ( list ) { + jQuery.each( arguments, function( _, arg ) { + var index; + while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) { + list.splice( index, 1 ); + + // Handle firing indexes + if ( firing ) { + if ( index <= firingLength ) { + firingLength--; + } + if ( index <= firingIndex ) { + firingIndex--; + } + } + } + } ); + } + return this; + }, + + // Check if a given callback is in the list. + // If no argument is given, return whether or not list has callbacks attached. + has: function( fn ) { + return fn ? jQuery.inArray( fn, list ) > -1 : !!( list && list.length ); + }, + + // Remove all callbacks from the list + empty: function() { + list = []; + return this; + }, + + // Have the list do nothing anymore + disable: function() { + list = stack = memory = undefined; + return this; + }, + + // Is it disabled? + disabled: function() { + return !list; + }, + + // Lock the list in its current state + lock: function() { + stack = undefined; + if ( !memory ) { + self.disable(); + } + return this; + }, + + // Is it locked? + locked: function() { + return !stack; + }, + + // Call all callbacks with the given context and arguments + fireWith: function( context, args ) { + args = args || []; + args = [ context, args.slice ? args.slice() : args ]; + if ( list && ( !fired || stack ) ) { + if ( firing ) { + stack.push( args ); + } else { + fire( args ); + } + } + return this; + }, + + // Call all the callbacks with the given arguments + fire: function() { + self.fireWith( this, arguments ); + return this; + }, + + // To know if the callbacks have already been called at least once + fired: function() { + return !!fired; + } + }; + + return self; +}; +jQuery.extend( { + + Deferred: function( func ) { + var tuples = [ + + // action, add listener, listener list, final state + [ "resolve", "done", jQuery.Callbacks( "once memory" ), "resolved" ], + [ "reject", "fail", jQuery.Callbacks( "once memory" ), "rejected" ], + [ "notify", "progress", jQuery.Callbacks( "memory" ) ] + ], + state = "pending", + promise = { + state: function() { + return state; + }, + always: function() { + deferred.done( arguments ).fail( arguments ); + return this; + }, + then: function( /* fnDone, fnFail, fnProgress */ ) { + var fns = arguments; + return jQuery.Deferred( function( newDefer ) { + jQuery.each( tuples, function( i, tuple ) { + var action = tuple[ 0 ], + fn = jQuery.isFunction( fns[ i ] ) && fns[ i ]; + + // deferred[ done | fail | progress ] for forwarding actions to newDefer + deferred[ tuple[ 1 ] ]( function() { + var returned = fn && fn.apply( this, arguments ); + if ( returned && jQuery.isFunction( returned.promise ) ) { + returned.promise() + .done( newDefer.resolve ) + .fail( newDefer.reject ) + .progress( newDefer.notify ); + } else { + newDefer[ action + "With" ]( this === promise ? newDefer.promise() : this, fn ? [ returned ] : arguments ); + } + } ); + } ); + fns = null; + } ).promise(); + }, + + // Get a promise for this deferred + // If obj is provided, the promise aspect is added to the object + promise: function( obj ) { + return obj != null ? jQuery.extend( obj, promise ) : promise; + } + }, + deferred = {}; + + // Keep pipe for back-compat + promise.pipe = promise.then; + + // Add list-specific methods + jQuery.each( tuples, function( i, tuple ) { + var list = tuple[ 2 ], + stateString = tuple[ 3 ]; + + // promise[ done | fail | progress ] = list.add + promise[ tuple[ 1 ] ] = list.add; + + // Handle state + if ( stateString ) { + list.add( function() { + + // state = [ resolved | rejected ] + state = stateString; + + // [ reject_list | resolve_list ].disable; progress_list.lock + }, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock ); + } + + // deferred[ resolve | reject | notify ] + deferred[ tuple[ 0 ] ] = function() { + deferred[ tuple[ 0 ] + "With" ]( this === deferred ? promise : this, arguments ); + return this; + }; + deferred[ tuple[ 0 ] + "With" ] = list.fireWith; + } ); + + // Make the deferred a promise + promise.promise( deferred ); + + // Call given func if any + if ( func ) { + func.call( deferred, deferred ); + } + + // All done! + return deferred; + }, + + // Deferred helper + when: function( subordinate /* , ..., subordinateN */ ) { + var i = 0, + resolveValues = core_slice.call( arguments ), + length = resolveValues.length, + + // the count of uncompleted subordinates + remaining = length !== 1 || ( subordinate && jQuery.isFunction( subordinate.promise ) ) ? length : 0, + + // the master Deferred. If resolveValues consist of only a single Deferred, just use that. + deferred = remaining === 1 ? subordinate : jQuery.Deferred(), + + // Update function for both resolve and progress values + updateFunc = function( i, contexts, values ) { + return function( value ) { + contexts[ i ] = this; + values[ i ] = arguments.length > 1 ? core_slice.call( arguments ) : value; + if ( values === progressValues ) { + deferred.notifyWith( contexts, values ); + } else if ( !( --remaining ) ) { + deferred.resolveWith( contexts, values ); + } + }; + }, + + progressValues, progressContexts, resolveContexts; + + // add listeners to Deferred subordinates; treat others as resolved + if ( length > 1 ) { + progressValues = new Array( length ); + progressContexts = new Array( length ); + resolveContexts = new Array( length ); + for ( ; i < length; i++ ) { + if ( resolveValues[ i ] && jQuery.isFunction( resolveValues[ i ].promise ) ) { + resolveValues[ i ].promise() + .done( updateFunc( i, resolveContexts, resolveValues ) ) + .fail( deferred.reject ) + .progress( updateFunc( i, progressContexts, progressValues ) ); + } else { + --remaining; + } + } + } + + // if we're not waiting on anything, resolve the master + if ( !remaining ) { + deferred.resolveWith( resolveContexts, resolveValues ); + } + + return deferred.promise(); + } +} ); +jQuery.support = ( function() { + + var support, all, a, + input, select, fragment, + opt, eventName, isSupported, i, + div = document.createElement( "div" ); + + // Setup + div.setAttribute( "className", "t" ); + div.innerHTML = "
a"; + + // Support tests won't run in some limited or non-browser environments + all = div.getElementsByTagName( "*" ); + a = div.getElementsByTagName( "a" )[ 0 ]; + if ( !all || !a || !all.length ) { + return {}; + } + + // First batch of tests + select = document.createElement( "select" ); + opt = select.appendChild( document.createElement( "option" ) ); + input = div.getElementsByTagName( "input" )[ 0 ]; + + a.style.cssText = "top:1px;float:left;opacity:.5"; + support = { + + // Test setAttribute on camelCase class. If it works, we need attrFixes when doing get/setAttribute (ie6/7) + getSetAttribute: div.className !== "t", + + // IE strips leading whitespace when .innerHTML is used + leadingWhitespace: div.firstChild.nodeType === 3, + + // Make sure that tbody elements aren't automatically inserted + // IE will insert them into empty tables + tbody: !div.getElementsByTagName( "tbody" ).length, + + // Make sure that link elements get serialized correctly by innerHTML + // This requires a wrapper element in IE + htmlSerialize: !!div.getElementsByTagName( "link" ).length, + + // Get the style information from getAttribute + // (IE uses .cssText instead) + style: /top/.test( a.getAttribute( "style" ) ), + + // Make sure that URLs aren't manipulated + // (IE normalizes it by default) + hrefNormalized: a.getAttribute( "href" ) === "/a", + + // Make sure that element opacity exists + // (IE uses filter instead) + // Use a regex to work around a WebKit issue. See #5145 + opacity: /^0.5/.test( a.style.opacity ), + + // Verify style float existence + // (IE uses styleFloat instead of cssFloat) + cssFloat: !!a.style.cssFloat, + + // Check the default checkbox/radio value ("" on WebKit; "on" elsewhere) + checkOn: !!input.value, + + // Make sure that a selected-by-default option has a working selected property. + // (WebKit defaults to false instead of true, IE too, if it's in an optgroup) + optSelected: opt.selected, + + // Tests for enctype support on a form (#6743) + enctype: !!document.createElement( "form" ).enctype, + + // Makes sure cloning an html5 element does not cause problems + // Where outerHTML is undefined, this still works + html5Clone: document.createElement( "nav" ).cloneNode( true ).outerHTML !== "<:nav>", + + // jQuery.support.boxModel DEPRECATED in 1.8 since we don't support Quirks Mode + boxModel: document.compatMode === "CSS1Compat", + + // Will be defined later + deleteExpando: true, + noCloneEvent: true, + inlineBlockNeedsLayout: false, + shrinkWrapBlocks: false, + reliableMarginRight: true, + boxSizingReliable: true, + pixelPosition: false + }; + + // Make sure checked status is properly cloned + input.checked = true; + support.noCloneChecked = input.cloneNode( true ).checked; + + // Make sure that the options inside disabled selects aren't marked as disabled + // (WebKit marks them as disabled) + select.disabled = true; + support.optDisabled = !opt.disabled; + + // Support: IE<9 + try { + delete div.test; + } catch ( e ) { + support.deleteExpando = false; + } + + // Check if we can trust getAttribute("value") + input = document.createElement( "input" ); + input.setAttribute( "value", "" ); + support.input = input.getAttribute( "value" ) === ""; + + // Check if an input maintains its value after becoming a radio + input.value = "t"; + input.setAttribute( "type", "radio" ); + support.radioValue = input.value === "t"; + + // #11217 - WebKit loses check when the name is after the checked attribute + input.setAttribute( "checked", "t" ); + input.setAttribute( "name", "t" ); + + fragment = document.createDocumentFragment(); + fragment.appendChild( input ); + + // Check if a disconnected checkbox will retain its checked + // value of true after appended to the DOM (IE6/7) + support.appendChecked = input.checked; + + // WebKit doesn't clone checked state correctly in fragments + support.checkClone = fragment.cloneNode( true ).cloneNode( true ).lastChild.checked; + + // Support: IE<9 + // Opera does not clone events (and typeof div.attachEvent === undefined). + // IE9-10 clones events bound via attachEvent, but they don't trigger with .click() + if ( div.attachEvent ) { + div.attachEvent( "onclick", function() { + support.noCloneEvent = false; + } ); + + div.cloneNode( true ).click(); + } + + // Support: IE<9 (lack submit/change bubble), Firefox 17+ (lack focusin event) + // Beware of CSP restrictions (https://developer.mozilla.org/en/Security/CSP), test/csp.php + for ( i in { submit: true, change: true, focusin: true } ) { + div.setAttribute( eventName = "on" + i, "t" ); + + support[ i + "Bubbles" ] = eventName in window || div.attributes[ eventName ].expando === false; + } + + div.style.backgroundClip = "content-box"; + div.cloneNode( true ).style.backgroundClip = ""; + support.clearCloneStyle = div.style.backgroundClip === "content-box"; + + // Run tests that need a body at doc ready + jQuery( function() { + var container, marginDiv, tds, + divReset = "padding:0;margin:0;border:0;display:block;box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;", + body = document.getElementsByTagName( "body" )[ 0 ]; + + if ( !body ) { + + // Return for frameset docs that don't have a body + return; + } + + container = document.createElement( "div" ); + container.style.cssText = "border:0;width:0;height:0;position:absolute;top:0;left:-9999px;margin-top:1px"; + + body.appendChild( container ).appendChild( div ); + + // Support: IE8 + // Check if table cells still have offsetWidth/Height when they are set + // to display:none and there are still other visible table cells in a + // table row; if so, offsetWidth/Height are not reliable for use when + // determining if an element has been hidden directly using + // display:none (it is still safe to use offsets if a parent element is + // hidden; don safety goggles and see bug #4512 for more information). + div.innerHTML = "
t
"; + tds = div.getElementsByTagName( "td" ); + tds[ 0 ].style.cssText = "padding:0;margin:0;border:0;display:none"; + isSupported = ( tds[ 0 ].offsetHeight === 0 ); + + tds[ 0 ].style.display = ""; + tds[ 1 ].style.display = "none"; + + // Support: IE8 + // Check if empty table cells still have offsetWidth/Height + support.reliableHiddenOffsets = isSupported && ( tds[ 0 ].offsetHeight === 0 ); + + // Check box-sizing and margin behavior + div.innerHTML = ""; + div.style.cssText = "box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%;"; + support.boxSizing = ( div.offsetWidth === 4 ); + support.doesNotIncludeMarginInBodyOffset = ( body.offsetTop !== 1 ); + + // Use window.getComputedStyle because jsdom on node.js will break without it. + if ( window.getComputedStyle ) { + support.pixelPosition = ( window.getComputedStyle( div, null ) || {} ).top !== "1%"; + support.boxSizingReliable = ( window.getComputedStyle( div, null ) || { width: "4px" } ).width === "4px"; + + // Check if div with explicit width and no margin-right incorrectly + // gets computed margin-right based on width of container. (#3333) + // Fails in WebKit before Feb 2011 nightlies + // WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right + marginDiv = div.appendChild( document.createElement( "div" ) ); + marginDiv.style.cssText = div.style.cssText = divReset; + marginDiv.style.marginRight = marginDiv.style.width = "0"; + div.style.width = "1px"; + + support.reliableMarginRight = + !parseFloat( ( window.getComputedStyle( marginDiv, null ) || {} ).marginRight ); + } + + if ( typeof div.style.zoom !== core_strundefined ) { + + // Support: IE<8 + // Check if natively block-level elements act like inline-block + // elements when setting their display to 'inline' and giving + // them layout + div.innerHTML = ""; + div.style.cssText = divReset + "width:1px;padding:1px;display:inline;zoom:1"; + support.inlineBlockNeedsLayout = ( div.offsetWidth === 3 ); + + // Support: IE6 + // Check if elements with layout shrink-wrap their children + div.style.display = "block"; + div.innerHTML = "
"; + div.firstChild.style.width = "5px"; + support.shrinkWrapBlocks = ( div.offsetWidth !== 3 ); + + if ( support.inlineBlockNeedsLayout ) { + + // Prevent IE 6 from affecting layout for positioned elements #11048 + // Prevent IE from shrinking the body in IE 7 mode #12869 + // Support: IE<8 + body.style.zoom = 1; + } + } + + body.removeChild( container ); + + // Null elements to avoid leaks in IE + container = div = tds = marginDiv = null; + } ); + + // Null elements to avoid leaks in IE + all = select = fragment = opt = a = input = null; + + return support; +} )(); + +var rbrace = /(?:\{[\s\S]*\}|\[[\s\S]*\])$/, + rmultiDash = /([A-Z])/g; + +function internalData( elem, name, data, pvt /* Internal Use Only */ ) { + if ( !jQuery.acceptData( elem ) ) { + return; + } + + var thisCache, ret, + internalKey = jQuery.expando, + getByName = typeof name === "string", + + // We have to handle DOM nodes and JS objects differently because IE6-7 + // can't GC object references properly across the DOM-JS boundary + isNode = elem.nodeType, + + // Only DOM nodes need the global jQuery cache; JS object data is + // attached directly to the object so GC can occur automatically + cache = isNode ? jQuery.cache : elem, + + // Only defining an ID for JS objects if its cache already exists allows + // the code to shortcut on the same path as a DOM node with no cache + id = isNode ? elem[ internalKey ] : elem[ internalKey ] && internalKey; + + // Avoid doing any more work than we need to when trying to get data on an + // object that has no data at all + if ( ( !id || !cache[ id ] || ( !pvt && !cache[ id ].data ) ) && getByName && data === undefined ) { + return; + } + + if ( !id ) { + + // Only DOM nodes need a new unique ID for each element since their data + // ends up in the global cache + if ( isNode ) { + elem[ internalKey ] = id = core_deletedIds.pop() || jQuery.guid++; + } else { + id = internalKey; + } + } + + if ( !cache[ id ] ) { + cache[ id ] = {}; + + // Avoids exposing jQuery metadata on plain JS objects when the object + // is serialized using JSON.stringify + if ( !isNode ) { + cache[ id ].toJSON = jQuery.noop; + } + } + + // An object can be passed to jQuery.data instead of a key/value pair; this gets + // shallow copied over onto the existing cache + if ( typeof name === "object" || typeof name === "function" ) { + if ( pvt ) { + cache[ id ] = jQuery.extend( cache[ id ], name ); + } else { + cache[ id ].data = jQuery.extend( cache[ id ].data, name ); + } + } + + thisCache = cache[ id ]; + + // jQuery data() is stored in a separate object inside the object's internal data + // cache in order to avoid key collisions between internal data and user-defined + // data. + if ( !pvt ) { + if ( !thisCache.data ) { + thisCache.data = {}; + } + + thisCache = thisCache.data; + } + + if ( data !== undefined ) { + thisCache[ jQuery.camelCase( name ) ] = data; + } + + // Check for both converted-to-camel and non-converted data property names + // If a data property was specified + if ( getByName ) { + + // First Try to find as-is property data + ret = thisCache[ name ]; + + // Test for null|undefined property data + if ( ret == null ) { + + // Try to find the camelCased property + ret = thisCache[ jQuery.camelCase( name ) ]; + } + } else { + ret = thisCache; + } + + return ret; +} + +function internalRemoveData( elem, name, pvt ) { + if ( !jQuery.acceptData( elem ) ) { + return; + } + + var i, l, thisCache, + isNode = elem.nodeType, + + // See jQuery.data for more information + cache = isNode ? jQuery.cache : elem, + id = isNode ? elem[ jQuery.expando ] : jQuery.expando; + + // If there is already no cache entry for this object, there is no + // purpose in continuing + if ( !cache[ id ] ) { + return; + } + + if ( name ) { + + thisCache = pvt ? cache[ id ] : cache[ id ].data; + + if ( thisCache ) { + + // Support array or space separated string names for data keys + if ( !jQuery.isArray( name ) ) { + + // try the string as a key before any manipulation + if ( name in thisCache ) { + name = [ name ]; + } else { + + // split the camel cased version by spaces unless a key with the spaces exists + name = jQuery.camelCase( name ); + if ( name in thisCache ) { + name = [ name ]; + } else { + name = name.split( " " ); + } + } + } else { + + // If "name" is an array of keys... + // When data is initially created, via ("key", "val") signature, + // keys will be converted to camelCase. + // Since there is no way to tell _how_ a key was added, remove + // both plain key and camelCase key. #12786 + // This will only penalize the array argument path. + name = name.concat( jQuery.map( name, jQuery.camelCase ) ); + } + + for ( i = 0, l = name.length; i < l; i++ ) { + delete thisCache[ name[ i ] ]; + } + + // If there is no data left in the cache, we want to continue + // and let the cache object itself get destroyed + if ( !( pvt ? isEmptyDataObject : jQuery.isEmptyObject )( thisCache ) ) { + return; + } + } + } + + // See jQuery.data for more information + if ( !pvt ) { + delete cache[ id ].data; + + // Don't destroy the parent cache unless the internal data object + // had been the only thing left in it + if ( !isEmptyDataObject( cache[ id ] ) ) { + return; + } + } + + // Destroy the cache + if ( isNode ) { + jQuery.cleanData( [ elem ], true ); + + // Use delete when supported for expandos or `cache` is not a window per isWindow (#10080) + } else if ( jQuery.support.deleteExpando || cache != cache.window ) { + delete cache[ id ]; + + // When all else fails, null + } else { + cache[ id ] = null; + } +} + +jQuery.extend( { + cache: {}, + + // Unique for each copy of jQuery on the page + // Non-digits removed to match rinlinejQuery + expando: "jQuery" + ( core_version + Math.random() ).replace( /\D/g, "" ), + + // The following elements throw uncatchable exceptions if you + // attempt to add expando properties to them. + noData: { + "embed": true, + + // Ban all objects except for Flash (which handle expandos) + "object": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000", + "applet": true + }, + + hasData: function( elem ) { + elem = elem.nodeType ? jQuery.cache[ elem[ jQuery.expando ] ] : elem[ jQuery.expando ]; + return !!elem && !isEmptyDataObject( elem ); + }, + + data: function( elem, name, data ) { + return internalData( elem, name, data ); + }, + + removeData: function( elem, name ) { + return internalRemoveData( elem, name ); + }, + + // For internal use only. + _data: function( elem, name, data ) { + return internalData( elem, name, data, true ); + }, + + _removeData: function( elem, name ) { + return internalRemoveData( elem, name, true ); + }, + + // A method for determining if a DOM node can handle the data expando + acceptData: function( elem ) { + + // Do not set data on non-element because it will not be cleared (#8335). + if ( elem.nodeType && elem.nodeType !== 1 && elem.nodeType !== 9 ) { + return false; + } + + var noData = elem.nodeName && jQuery.noData[ elem.nodeName.toLowerCase() ]; + + // nodes accept data unless otherwise specified; rejection can be conditional + return !noData || noData !== true && elem.getAttribute( "classid" ) === noData; + } +} ); + +jQuery.fn.extend( { + data: function( key, value ) { + var attrs, name, + elem = this[ 0 ], + i = 0, + data = null; + + // Gets all values + if ( key === undefined ) { + if ( this.length ) { + data = jQuery.data( elem ); + + if ( elem.nodeType === 1 && !jQuery._data( elem, "parsedAttrs" ) ) { + attrs = elem.attributes; + for ( ; i < attrs.length; i++ ) { + name = attrs[ i ].name; + + if ( !name.indexOf( "data-" ) ) { + name = jQuery.camelCase( name.slice( 5 ) ); + + dataAttr( elem, name, data[ name ] ); + } + } + jQuery._data( elem, "parsedAttrs", true ); + } + } + + return data; + } + + // Sets multiple values + if ( typeof key === "object" ) { + return this.each( function() { + jQuery.data( this, key ); + } ); + } + + return jQuery.access( this, function( value ) { + + if ( value === undefined ) { + + // Try to fetch any internally stored data first + return elem ? dataAttr( elem, key, jQuery.data( elem, key ) ) : null; + } + + this.each( function() { + jQuery.data( this, key, value ); + } ); + }, null, value, arguments.length > 1, null, true ); + }, + + removeData: function( key ) { + return this.each( function() { + jQuery.removeData( this, key ); + } ); + } +} ); + +function dataAttr( elem, key, data ) { + + // If nothing was found internally, try to fetch any + // data from the HTML5 data-* attribute + if ( data === undefined && elem.nodeType === 1 ) { + + var name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase(); + + data = elem.getAttribute( name ); + + if ( typeof data === "string" ) { + try { + data = data === "true" ? true : + data === "false" ? false : + data === "null" ? null : + + // Only convert to a number if it doesn't change the string + +data + "" === data ? +data : + rbrace.test( data ) ? jQuery.parseJSON( data ) : + data; + } catch ( e ) {} + + // Make sure we set the data so it isn't changed later + jQuery.data( elem, key, data ); + + } else { + data = undefined; + } + } + + return data; +} + +// checks a cache object for emptiness +function isEmptyDataObject( obj ) { + var name; + for ( name in obj ) { + + // if the public data object is empty, the private is still empty + if ( name === "data" && jQuery.isEmptyObject( obj[ name ] ) ) { + continue; + } + if ( name !== "toJSON" ) { + return false; + } + } + + return true; +} +jQuery.extend( { + queue: function( elem, type, data ) { + var queue; + + if ( elem ) { + type = ( type || "fx" ) + "queue"; + queue = jQuery._data( elem, type ); + + // Speed up dequeue by getting out quickly if this is just a lookup + if ( data ) { + if ( !queue || jQuery.isArray( data ) ) { + queue = jQuery._data( elem, type, jQuery.makeArray( data ) ); + } else { + queue.push( data ); + } + } + return queue || []; + } + }, + + dequeue: function( elem, type ) { + type = type || "fx"; + + var queue = jQuery.queue( elem, type ), + startLength = queue.length, + fn = queue.shift(), + hooks = jQuery._queueHooks( elem, type ), + next = function() { + jQuery.dequeue( elem, type ); + }; + + // If the fx queue is dequeued, always remove the progress sentinel + if ( fn === "inprogress" ) { + fn = queue.shift(); + startLength--; + } + + hooks.cur = fn; + if ( fn ) { + + // Add a progress sentinel to prevent the fx queue from being + // automatically dequeued + if ( type === "fx" ) { + queue.unshift( "inprogress" ); + } + + // clear up the last queue stop function + delete hooks.stop; + fn.call( elem, next, hooks ); + } + + if ( !startLength && hooks ) { + hooks.empty.fire(); + } + }, + + // not intended for public consumption - generates a queueHooks object, or returns the current one + _queueHooks: function( elem, type ) { + var key = type + "queueHooks"; + return jQuery._data( elem, key ) || jQuery._data( elem, key, { + empty: jQuery.Callbacks( "once memory" ).add( function() { + jQuery._removeData( elem, type + "queue" ); + jQuery._removeData( elem, key ); + } ) + } ); + } +} ); + +jQuery.fn.extend( { + queue: function( type, data ) { + var setter = 2; + + if ( typeof type !== "string" ) { + data = type; + type = "fx"; + setter--; + } + + if ( arguments.length < setter ) { + return jQuery.queue( this[ 0 ], type ); + } + + return data === undefined ? + this : + this.each( function() { + var queue = jQuery.queue( this, type, data ); + + // ensure a hooks for this queue + jQuery._queueHooks( this, type ); + + if ( type === "fx" && queue[ 0 ] !== "inprogress" ) { + jQuery.dequeue( this, type ); + } + } ); + }, + dequeue: function( type ) { + return this.each( function() { + jQuery.dequeue( this, type ); + } ); + }, + + // Based off of the plugin by Clint Helfers, with permission. + // http://blindsignals.com/index.php/2009/07/jquery-delay/ + delay: function( time, type ) { + time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time; + type = type || "fx"; + + return this.queue( type, function( next, hooks ) { + var timeout = setTimeout( next, time ); + hooks.stop = function() { + clearTimeout( timeout ); + }; + } ); + }, + clearQueue: function( type ) { + return this.queue( type || "fx", [] ); + }, + + // Get a promise resolved when queues of a certain type + // are emptied (fx is the type by default) + promise: function( type, obj ) { + var tmp, + count = 1, + defer = jQuery.Deferred(), + elements = this, + i = this.length, + resolve = function() { + if ( !( --count ) ) { + defer.resolveWith( elements, [ elements ] ); + } + }; + + if ( typeof type !== "string" ) { + obj = type; + type = undefined; + } + type = type || "fx"; + + while ( i-- ) { + tmp = jQuery._data( elements[ i ], type + "queueHooks" ); + if ( tmp && tmp.empty ) { + count++; + tmp.empty.add( resolve ); + } + } + resolve(); + return defer.promise( obj ); + } +} ); +var nodeHook, boolHook, + rclass = /[\t\r\n]/g, + rreturn = /\r/g, + rfocusable = /^(?:input|select|textarea|button|object)$/i, + rclickable = /^(?:a|area)$/i, + rboolean = /^(?:checked|selected|autofocus|autoplay|async|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped)$/i, + ruseDefault = /^(?:checked|selected)$/i, + getSetAttribute = jQuery.support.getSetAttribute, + getSetInput = jQuery.support.input; + +jQuery.fn.extend( { + attr: function( name, value ) { + return jQuery.access( this, jQuery.attr, name, value, arguments.length > 1 ); + }, + + removeAttr: function( name ) { + return this.each( function() { + jQuery.removeAttr( this, name ); + } ); + }, + + prop: function( name, value ) { + return jQuery.access( this, jQuery.prop, name, value, arguments.length > 1 ); + }, + + removeProp: function( name ) { + name = jQuery.propFix[ name ] || name; + return this.each( function() { + + // try/catch handles cases where IE balks (such as removing a property on window) + try { + this[ name ] = undefined; + delete this[ name ]; + } catch ( e ) {} + } ); + }, + + addClass: function( value ) { + var classes, elem, cur, clazz, j, + i = 0, + len = this.length, + proceed = typeof value === "string" && value; + + if ( jQuery.isFunction( value ) ) { + return this.each( function( j ) { + jQuery( this ).addClass( value.call( this, j, this.className ) ); + } ); + } + + if ( proceed ) { + + // The disjunction here is for better compressibility (see removeClass) + classes = ( value || "" ).match( core_rnotwhite ) || []; + + for ( ; i < len; i++ ) { + elem = this[ i ]; + cur = elem.nodeType === 1 && ( elem.className ? + ( " " + elem.className + " " ).replace( rclass, " " ) : + " " + ); + + if ( cur ) { + j = 0; + while ( ( clazz = classes[ j++ ] ) ) { + if ( cur.indexOf( " " + clazz + " " ) < 0 ) { + cur += clazz + " "; + } + } + elem.className = jQuery.trim( cur ); + + } + } + } + + return this; + }, + + removeClass: function( value ) { + var classes, elem, cur, clazz, j, + i = 0, + len = this.length, + proceed = arguments.length === 0 || typeof value === "string" && value; + + if ( jQuery.isFunction( value ) ) { + return this.each( function( j ) { + jQuery( this ).removeClass( value.call( this, j, this.className ) ); + } ); + } + if ( proceed ) { + classes = ( value || "" ).match( core_rnotwhite ) || []; + + for ( ; i < len; i++ ) { + elem = this[ i ]; + + // This expression is here for better compressibility (see addClass) + cur = elem.nodeType === 1 && ( elem.className ? + ( " " + elem.className + " " ).replace( rclass, " " ) : + "" + ); + + if ( cur ) { + j = 0; + while ( ( clazz = classes[ j++ ] ) ) { + + // Remove *all* instances + while ( cur.indexOf( " " + clazz + " " ) >= 0 ) { + cur = cur.replace( " " + clazz + " ", " " ); + } + } + elem.className = value ? jQuery.trim( cur ) : ""; + } + } + } + + return this; + }, + + toggleClass: function( value, stateVal ) { + var type = typeof value, + isBool = typeof stateVal === "boolean"; + + if ( jQuery.isFunction( value ) ) { + return this.each( function( i ) { + jQuery( this ).toggleClass( value.call( this, i, this.className, stateVal ), stateVal ); + } ); + } + + return this.each( function() { + if ( type === "string" ) { + + // toggle individual class names + var className, + i = 0, + self = jQuery( this ), + state = stateVal, + classNames = value.match( core_rnotwhite ) || []; + + while ( ( className = classNames[ i++ ] ) ) { + + // check each className given, space separated list + state = isBool ? state : !self.hasClass( className ); + self[ state ? "addClass" : "removeClass" ]( className ); + } + + // Toggle whole class name + } else if ( type === core_strundefined || type === "boolean" ) { + if ( this.className ) { + + // store className if set + jQuery._data( this, "__className__", this.className ); + } + + // If the element has a class name or if we're passed "false", + // then remove the whole classname (if there was one, the above saved it). + // Otherwise bring back whatever was previously saved (if anything), + // falling back to the empty string if nothing was stored. + this.className = this.className || value === false ? "" : jQuery._data( this, "__className__" ) || ""; + } + } ); + }, + + hasClass: function( selector ) { + var className = " " + selector + " ", + i = 0, + l = this.length; + for ( ; i < l; i++ ) { + if ( this[ i ].nodeType === 1 && ( " " + this[ i ].className + " " ).replace( rclass, " " ).indexOf( className ) >= 0 ) { + return true; + } + } + + return false; + }, + + val: function( value ) { + var ret, hooks, isFunction, + elem = this[ 0 ]; + + if ( !arguments.length ) { + if ( elem ) { + hooks = jQuery.valHooks[ elem.type ] || jQuery.valHooks[ elem.nodeName.toLowerCase() ]; + + if ( hooks && "get" in hooks && ( ret = hooks.get( elem, "value" ) ) !== undefined ) { + return ret; + } + + ret = elem.value; + + return typeof ret === "string" ? + + // handle most common string cases + ret.replace( rreturn, "" ) : + + // handle cases where value is null/undef or number + ret == null ? "" : ret; + } + + return; + } + + isFunction = jQuery.isFunction( value ); + + return this.each( function( i ) { + var val, + self = jQuery( this ); + + if ( this.nodeType !== 1 ) { + return; + } + + if ( isFunction ) { + val = value.call( this, i, self.val() ); + } else { + val = value; + } + + // Treat null/undefined as ""; convert numbers to string + if ( val == null ) { + val = ""; + } else if ( typeof val === "number" ) { + val += ""; + } else if ( jQuery.isArray( val ) ) { + val = jQuery.map( val, function( value ) { + return value == null ? "" : value + ""; + } ); + } + + hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ]; + + // If set returns undefined, fall back to normal setting + if ( !hooks || !( "set" in hooks ) || hooks.set( this, val, "value" ) === undefined ) { + this.value = val; + } + } ); + } +} ); + +jQuery.extend( { + valHooks: { + option: { + get: function( elem ) { + + // attributes.value is undefined in Blackberry 4.7 but + // uses .value. See #6932 + var val = elem.attributes.value; + return !val || val.specified ? elem.value : elem.text; + } + }, + select: { + get: function( elem ) { + var value, option, + options = elem.options, + index = elem.selectedIndex, + one = elem.type === "select-one" || index < 0, + values = one ? null : [], + max = one ? index + 1 : options.length, + i = index < 0 ? + max : + one ? index : 0; + + // Loop through all the selected options + for ( ; i < max; i++ ) { + option = options[ i ]; + + // oldIE doesn't update selected after form reset (#2551) + if ( ( option.selected || i === index ) && + + // Don't return options that are disabled or in a disabled optgroup + ( jQuery.support.optDisabled ? !option.disabled : option.getAttribute( "disabled" ) === null ) && + ( !option.parentNode.disabled || !jQuery.nodeName( option.parentNode, "optgroup" ) ) ) { + + // Get the specific value for the option + value = jQuery( option ).val(); + + // We don't need an array for one selects + if ( one ) { + return value; + } + + // Multi-Selects return an array + values.push( value ); + } + } + + return values; + }, + + set: function( elem, value ) { + var values = jQuery.makeArray( value ); + + jQuery( elem ).find( "option" ).each( function() { + this.selected = jQuery.inArray( jQuery( this ).val(), values ) >= 0; + } ); + + if ( !values.length ) { + elem.selectedIndex = -1; + } + return values; + } + } + }, + + attr: function( elem, name, value ) { + var hooks, notxml, ret, + nType = elem.nodeType; + + // don't get/set attributes on text, comment and attribute nodes + if ( !elem || nType === 3 || nType === 8 || nType === 2 ) { + return; + } + + // Fallback to prop when attributes are not supported + if ( typeof elem.getAttribute === core_strundefined ) { + return jQuery.prop( elem, name, value ); + } + + notxml = nType !== 1 || !jQuery.isXMLDoc( elem ); + + // All attributes are lowercase + // Grab necessary hook if one is defined + if ( notxml ) { + name = name.toLowerCase(); + hooks = jQuery.attrHooks[ name ] || ( rboolean.test( name ) ? boolHook : nodeHook ); + } + + if ( value !== undefined ) { + + if ( value === null ) { + jQuery.removeAttr( elem, name ); + + } else if ( hooks && notxml && "set" in hooks && ( ret = hooks.set( elem, value, name ) ) !== undefined ) { + return ret; + + } else { + elem.setAttribute( name, value + "" ); + return value; + } + + } else if ( hooks && notxml && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) { + return ret; + + } else { + + // In IE9+, Flash objects don't have .getAttribute (#12945) + // Support: IE9+ + if ( typeof elem.getAttribute !== core_strundefined ) { + ret = elem.getAttribute( name ); + } + + // Non-existent attributes return null, we normalize to undefined + return ret == null ? + undefined : + ret; + } + }, + + removeAttr: function( elem, value ) { + var name, propName, + i = 0, + attrNames = value && value.match( core_rnotwhite ); + + if ( attrNames && elem.nodeType === 1 ) { + while ( ( name = attrNames[ i++ ] ) ) { + propName = jQuery.propFix[ name ] || name; + + // Boolean attributes get special treatment (#10870) + if ( rboolean.test( name ) ) { + + // Set corresponding property to false for boolean attributes + // Also clear defaultChecked/defaultSelected (if appropriate) for IE<8 + if ( !getSetAttribute && ruseDefault.test( name ) ) { + elem[ jQuery.camelCase( "default-" + name ) ] = + elem[ propName ] = false; + } else { + elem[ propName ] = false; + } + + // See #9699 for explanation of this approach (setting first, then removal) + } else { + jQuery.attr( elem, name, "" ); + } + + elem.removeAttribute( getSetAttribute ? name : propName ); + } + } + }, + + attrHooks: { + type: { + set: function( elem, value ) { + if ( !jQuery.support.radioValue && value === "radio" && jQuery.nodeName( elem, "input" ) ) { + + // Setting the type on a radio button after the value resets the value in IE6-9 + // Reset value to default in case type is set after value during creation + var val = elem.value; + elem.setAttribute( "type", value ); + if ( val ) { + elem.value = val; + } + return value; + } + } + } + }, + + propFix: { + tabindex: "tabIndex", + readonly: "readOnly", + "for": "htmlFor", + "class": "className", + maxlength: "maxLength", + cellspacing: "cellSpacing", + cellpadding: "cellPadding", + rowspan: "rowSpan", + colspan: "colSpan", + usemap: "useMap", + frameborder: "frameBorder", + contenteditable: "contentEditable" + }, + + prop: function( elem, name, value ) { + var ret, hooks, notxml, + nType = elem.nodeType; + + // don't get/set properties on text, comment and attribute nodes + if ( !elem || nType === 3 || nType === 8 || nType === 2 ) { + return; + } + + notxml = nType !== 1 || !jQuery.isXMLDoc( elem ); + + if ( notxml ) { + + // Fix name and attach hooks + name = jQuery.propFix[ name ] || name; + hooks = jQuery.propHooks[ name ]; + } + + if ( value !== undefined ) { + if ( hooks && "set" in hooks && ( ret = hooks.set( elem, value, name ) ) !== undefined ) { + return ret; + + } else { + return ( elem[ name ] = value ); + } + + } else { + if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) { + return ret; + + } else { + return elem[ name ]; + } + } + }, + + propHooks: { + tabIndex: { + get: function( elem ) { + + // elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set + // http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ + var attributeNode = elem.getAttributeNode( "tabindex" ); + + return attributeNode && attributeNode.specified ? + parseInt( attributeNode.value, 10 ) : + rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ? + 0 : + undefined; + } + } + } +} ); + +// Hook for boolean attributes +boolHook = { + get: function( elem, name ) { + var + + // Use .prop to determine if this attribute is understood as boolean + prop = jQuery.prop( elem, name ), + + // Fetch it accordingly + attr = typeof prop === "boolean" && elem.getAttribute( name ), + detail = typeof prop === "boolean" ? + + getSetInput && getSetAttribute ? + attr != null : + + // oldIE fabricates an empty string for missing boolean attributes + // and conflates checked/selected into attroperties + ruseDefault.test( name ) ? + elem[ jQuery.camelCase( "default-" + name ) ] : + !!attr : + + // fetch an attribute node for properties not recognized as boolean + elem.getAttributeNode( name ); + + return detail && detail.value !== false ? + name.toLowerCase() : + undefined; + }, + set: function( elem, value, name ) { + if ( value === false ) { + + // Remove boolean attributes when set to false + jQuery.removeAttr( elem, name ); + } else if ( getSetInput && getSetAttribute || !ruseDefault.test( name ) ) { + + // IE<8 needs the *property* name + elem.setAttribute( !getSetAttribute && jQuery.propFix[ name ] || name, name ); + + // Use defaultChecked and defaultSelected for oldIE + } else { + elem[ jQuery.camelCase( "default-" + name ) ] = elem[ name ] = true; + } + + return name; + } +}; + +// fix oldIE value attroperty +if ( !getSetInput || !getSetAttribute ) { + jQuery.attrHooks.value = { + get: function( elem, name ) { + var ret = elem.getAttributeNode( name ); + return jQuery.nodeName( elem, "input" ) ? + + // Ignore the value *property* by using defaultValue + elem.defaultValue : + + ret && ret.specified ? ret.value : undefined; + }, + set: function( elem, value, name ) { + if ( jQuery.nodeName( elem, "input" ) ) { + + // Does not return so that setAttribute is also used + elem.defaultValue = value; + } else { + + // Use nodeHook if defined (#1954); otherwise setAttribute is fine + return nodeHook && nodeHook.set( elem, value, name ); + } + } + }; +} + +// IE6/7 do not support getting/setting some attributes with get/setAttribute +if ( !getSetAttribute ) { + + // Use this for any attribute in IE6/7 + // This fixes almost every IE6/7 issue + nodeHook = jQuery.valHooks.button = { + get: function( elem, name ) { + var ret = elem.getAttributeNode( name ); + return ret && ( name === "id" || name === "name" || name === "coords" ? ret.value !== "" : ret.specified ) ? + ret.value : + undefined; + }, + set: function( elem, value, name ) { + + // Set the existing or create a new attribute node + var ret = elem.getAttributeNode( name ); + if ( !ret ) { + elem.setAttributeNode( + ( ret = elem.ownerDocument.createAttribute( name ) ) + ); + } + + ret.value = value += ""; + + // Break association with cloned elements by also using setAttribute (#9646) + return name === "value" || value === elem.getAttribute( name ) ? + value : + undefined; + } + }; + + // Set contenteditable to false on removals(#10429) + // Setting to empty string throws an error as an invalid value + jQuery.attrHooks.contenteditable = { + get: nodeHook.get, + set: function( elem, value, name ) { + nodeHook.set( elem, value === "" ? false : value, name ); + } + }; + + // Set width and height to auto instead of 0 on empty string( Bug #8150 ) + // This is for removals + jQuery.each( [ "width", "height" ], function( i, name ) { + jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], { + set: function( elem, value ) { + if ( value === "" ) { + elem.setAttribute( name, "auto" ); + return value; + } + } + } ); + } ); +} + +// Some attributes require a special call on IE +// http://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx +if ( !jQuery.support.hrefNormalized ) { + jQuery.each( [ "href", "src", "width", "height" ], function( i, name ) { + jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], { + get: function( elem ) { + var ret = elem.getAttribute( name, 2 ); + return ret == null ? undefined : ret; + } + } ); + } ); + + // href/src property should get the full normalized URL (#10299/#12915) + jQuery.each( [ "href", "src" ], function( i, name ) { + jQuery.propHooks[ name ] = { + get: function( elem ) { + return elem.getAttribute( name, 4 ); + } + }; + } ); +} + +if ( !jQuery.support.style ) { + jQuery.attrHooks.style = { + get: function( elem ) { + + // Return undefined in the case of empty string + // Note: IE uppercases css property names, but if we were to .toLowerCase() + // .cssText, that would destroy case senstitivity in URL's, like in "background" + return elem.style.cssText || undefined; + }, + set: function( elem, value ) { + return ( elem.style.cssText = value + "" ); + } + }; +} + +// Safari mis-reports the default selected property of an option +// Accessing the parent's selectedIndex property fixes it +if ( !jQuery.support.optSelected ) { + jQuery.propHooks.selected = jQuery.extend( jQuery.propHooks.selected, { + get: function( elem ) { + var parent = elem.parentNode; + + if ( parent ) { + parent.selectedIndex; + + // Make sure that it also works with optgroups, see #5701 + if ( parent.parentNode ) { + parent.parentNode.selectedIndex; + } + } + return null; + } + } ); +} + +// IE6/7 call enctype encoding +if ( !jQuery.support.enctype ) { + jQuery.propFix.enctype = "encoding"; +} + +// Radios and checkboxes getter/setter +if ( !jQuery.support.checkOn ) { + jQuery.each( [ "radio", "checkbox" ], function() { + jQuery.valHooks[ this ] = { + get: function( elem ) { + + // Handle the case where in Webkit "" is returned instead of "on" if a value isn't specified + return elem.getAttribute( "value" ) === null ? "on" : elem.value; + } + }; + } ); +} +jQuery.each( [ "radio", "checkbox" ], function() { + jQuery.valHooks[ this ] = jQuery.extend( jQuery.valHooks[ this ], { + set: function( elem, value ) { + if ( jQuery.isArray( value ) ) { + return ( elem.checked = jQuery.inArray( jQuery( elem ).val(), value ) >= 0 ); + } + } + } ); +} ); +var rformElems = /^(?:input|select|textarea)$/i, + rkeyEvent = /^key/, + rmouseEvent = /^(?:mouse|contextmenu)|click/, + rfocusMorph = /^(?:focusinfocus|focusoutblur)$/, + rtypenamespace = /^([^.]*)(?:\.(.+)|)$/; + +function returnTrue() { + return true; +} + +function returnFalse() { + return false; +} + +/* + * Helper functions for managing events -- not part of the public interface. + * Props to Dean Edwards' addEvent library for many of the ideas. + */ +jQuery.event = { + + global: {}, + + add: function( elem, types, handler, data, selector ) { + var tmp, events, t, handleObjIn, + special, eventHandle, handleObj, + handlers, type, namespaces, origType, + elemData = jQuery._data( elem ); + + // Don't attach events to noData or text/comment nodes (but allow plain objects) + if ( !elemData ) { + return; + } + + // Caller can pass in an object of custom data in lieu of the handler + if ( handler.handler ) { + handleObjIn = handler; + handler = handleObjIn.handler; + selector = handleObjIn.selector; + } + + // Make sure that the handler has a unique ID, used to find/remove it later + if ( !handler.guid ) { + handler.guid = jQuery.guid++; + } + + // Init the element's event structure and main handler, if this is the first + if ( !( events = elemData.events ) ) { + events = elemData.events = {}; + } + if ( !( eventHandle = elemData.handle ) ) { + eventHandle = elemData.handle = function( e ) { + + // Discard the second event of a jQuery.event.trigger() and + // when an event is called after a page has unloaded + return typeof jQuery !== core_strundefined && ( !e || jQuery.event.triggered !== e.type ) ? + jQuery.event.dispatch.apply( eventHandle.elem, arguments ) : + undefined; + }; + + // Add elem as a property of the handle fn to prevent a memory leak with IE non-native events + eventHandle.elem = elem; + } + + // Handle multiple events separated by a space + // jQuery(...).bind("mouseover mouseout", fn); + types = ( types || "" ).match( core_rnotwhite ) || [ "" ]; + t = types.length; + while ( t-- ) { + tmp = rtypenamespace.exec( types[ t ] ) || []; + type = origType = tmp[ 1 ]; + namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); + + // If event changes its type, use the special event handlers for the changed type + special = jQuery.event.special[ type ] || {}; + + // If selector defined, determine special event api type, otherwise given type + type = ( selector ? special.delegateType : special.bindType ) || type; + + // Update special based on newly reset type + special = jQuery.event.special[ type ] || {}; + + // handleObj is passed to all event handlers + handleObj = jQuery.extend( { + type: type, + origType: origType, + data: data, + handler: handler, + guid: handler.guid, + selector: selector, + needsContext: selector && jQuery.expr.match.needsContext.test( selector ), + namespace: namespaces.join( "." ) + }, handleObjIn ); + + // Init the event handler queue if we're the first + if ( !( handlers = events[ type ] ) ) { + handlers = events[ type ] = []; + handlers.delegateCount = 0; + + // Only use addEventListener/attachEvent if the special events handler returns false + if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) { + + // Bind the global event handler to the element + if ( elem.addEventListener ) { + elem.addEventListener( type, eventHandle, false ); + + } else if ( elem.attachEvent ) { + elem.attachEvent( "on" + type, eventHandle ); + } + } + } + + if ( special.add ) { + special.add.call( elem, handleObj ); + + if ( !handleObj.handler.guid ) { + handleObj.handler.guid = handler.guid; + } + } + + // Add to the element's handler list, delegates in front + if ( selector ) { + handlers.splice( handlers.delegateCount++, 0, handleObj ); + } else { + handlers.push( handleObj ); + } + + // Keep track of which events have ever been used, for event optimization + jQuery.event.global[ type ] = true; + } + + // Nullify elem to prevent memory leaks in IE + elem = null; + }, + + // Detach an event or set of events from an element + remove: function( elem, types, handler, selector, mappedTypes ) { + var j, handleObj, tmp, + origCount, t, events, + special, handlers, type, + namespaces, origType, + elemData = jQuery.hasData( elem ) && jQuery._data( elem ); + + if ( !elemData || !( events = elemData.events ) ) { + return; + } + + // Once for each type.namespace in types; type may be omitted + types = ( types || "" ).match( core_rnotwhite ) || [ "" ]; + t = types.length; + while ( t-- ) { + tmp = rtypenamespace.exec( types[ t ] ) || []; + type = origType = tmp[ 1 ]; + namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); + + // Unbind all events (on this namespace, if provided) for the element + if ( !type ) { + for ( type in events ) { + jQuery.event.remove( elem, type + types[ t ], handler, selector, true ); + } + continue; + } + + special = jQuery.event.special[ type ] || {}; + type = ( selector ? special.delegateType : special.bindType ) || type; + handlers = events[ type ] || []; + tmp = tmp[ 2 ] && new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ); + + // Remove matching events + origCount = j = handlers.length; + while ( j-- ) { + handleObj = handlers[ j ]; + + if ( ( mappedTypes || origType === handleObj.origType ) && + ( !handler || handler.guid === handleObj.guid ) && + ( !tmp || tmp.test( handleObj.namespace ) ) && + ( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector ) ) { + handlers.splice( j, 1 ); + + if ( handleObj.selector ) { + handlers.delegateCount--; + } + if ( special.remove ) { + special.remove.call( elem, handleObj ); + } + } + } + + // Remove generic event handler if we removed something and no more handlers exist + // (avoids potential for endless recursion during removal of special event handlers) + if ( origCount && !handlers.length ) { + if ( !special.teardown || special.teardown.call( elem, namespaces, elemData.handle ) === false ) { + jQuery.removeEvent( elem, type, elemData.handle ); + } + + delete events[ type ]; + } + } + + // Remove the expando if it's no longer used + if ( jQuery.isEmptyObject( events ) ) { + delete elemData.handle; + + // removeData also checks for emptiness and clears the expando if empty + // so use it instead of delete + jQuery._removeData( elem, "events" ); + } + }, + + trigger: function( event, data, elem, onlyHandlers ) { + var handle, ontype, cur, + bubbleType, special, tmp, i, + eventPath = [ elem || document ], + type = core_hasOwn.call( event, "type" ) ? event.type : event, + namespaces = core_hasOwn.call( event, "namespace" ) ? event.namespace.split( "." ) : []; + + cur = tmp = elem = elem || document; + + // Don't do events on text and comment nodes + if ( elem.nodeType === 3 || elem.nodeType === 8 ) { + return; + } + + // focus/blur morphs to focusin/out; ensure we're not firing them right now + if ( rfocusMorph.test( type + jQuery.event.triggered ) ) { + return; + } + + if ( type.indexOf( "." ) >= 0 ) { + + // Namespaced trigger; create a regexp to match event type in handle() + namespaces = type.split( "." ); + type = namespaces.shift(); + namespaces.sort(); + } + ontype = type.indexOf( ":" ) < 0 && "on" + type; + + // Caller can pass in a jQuery.Event object, Object, or just an event type string + event = event[ jQuery.expando ] ? + event : + new jQuery.Event( type, typeof event === "object" && event ); + + event.isTrigger = true; + event.namespace = namespaces.join( "." ); + event.namespace_re = event.namespace ? + new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ) : + null; + + // Clean up the event in case it is being reused + event.result = undefined; + if ( !event.target ) { + event.target = elem; + } + + // Clone any incoming data and prepend the event, creating the handler arg list + data = data == null ? + [ event ] : + jQuery.makeArray( data, [ event ] ); + + // Allow special events to draw outside the lines + special = jQuery.event.special[ type ] || {}; + if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) { + return; + } + + // Determine event propagation path in advance, per W3C events spec (#9951) + // Bubble up to document, then to window; watch for a global ownerDocument var (#9724) + if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) { + + bubbleType = special.delegateType || type; + if ( !rfocusMorph.test( bubbleType + type ) ) { + cur = cur.parentNode; + } + for ( ; cur; cur = cur.parentNode ) { + eventPath.push( cur ); + tmp = cur; + } + + // Only add window if we got to document (e.g., not plain obj or detached DOM) + if ( tmp === ( elem.ownerDocument || document ) ) { + eventPath.push( tmp.defaultView || tmp.parentWindow || window ); + } + } + + // Fire handlers on the event path + i = 0; + while ( ( cur = eventPath[ i++ ] ) && !event.isPropagationStopped() ) { + + event.type = i > 1 ? + bubbleType : + special.bindType || type; + + // jQuery handler + handle = ( jQuery._data( cur, "events" ) || {} )[ event.type ] && jQuery._data( cur, "handle" ); + if ( handle ) { + handle.apply( cur, data ); + } + + // Native handler + handle = ontype && cur[ ontype ]; + if ( handle && jQuery.acceptData( cur ) && handle.apply && handle.apply( cur, data ) === false ) { + event.preventDefault(); + } + } + event.type = type; + + // If nobody prevented the default action, do it now + if ( !onlyHandlers && !event.isDefaultPrevented() ) { + + if ( ( !special._default || special._default.apply( elem.ownerDocument, data ) === false ) && + !( type === "click" && jQuery.nodeName( elem, "a" ) ) && jQuery.acceptData( elem ) ) { + + // Call a native DOM method on the target with the same name name as the event. + // Can't use an .isFunction() check here because IE6/7 fails that test. + // Don't do default actions on window, that's where global variables be (#6170) + if ( ontype && elem[ type ] && !jQuery.isWindow( elem ) ) { + + // Don't re-trigger an onFOO event when we call its FOO() method + tmp = elem[ ontype ]; + + if ( tmp ) { + elem[ ontype ] = null; + } + + // Prevent re-triggering of the same event, since we already bubbled it above + jQuery.event.triggered = type; + try { + elem[ type ](); + } catch ( e ) { + + // IE<9 dies on focus/blur to hidden element (#1486,#12518) + // only reproducible on winXP IE8 native, not IE9 in IE8 mode + } + jQuery.event.triggered = undefined; + + if ( tmp ) { + elem[ ontype ] = tmp; + } + } + } + } + + return event.result; + }, + + dispatch: function( event ) { + + // Make a writable jQuery.Event from the native event object + event = jQuery.event.fix( event ); + + var i, ret, handleObj, matched, j, + handlerQueue = [], + args = core_slice.call( arguments ), + handlers = ( jQuery._data( this, "events" ) || {} )[ event.type ] || [], + special = jQuery.event.special[ event.type ] || {}; + + // Use the fix-ed jQuery.Event rather than the (read-only) native event + args[ 0 ] = event; + event.delegateTarget = this; + + // Call the preDispatch hook for the mapped type, and let it bail if desired + if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) { + return; + } + + // Determine handlers + handlerQueue = jQuery.event.handlers.call( this, event, handlers ); + + // Run delegates first; they may want to stop propagation beneath us + i = 0; + while ( ( matched = handlerQueue[ i++ ] ) && !event.isPropagationStopped() ) { + event.currentTarget = matched.elem; + + j = 0; + while ( ( handleObj = matched.handlers[ j++ ] ) && !event.isImmediatePropagationStopped() ) { + + // Triggered event must either 1) have no namespace, or + // 2) have namespace(s) a subset or equal to those in the bound event (both can have no namespace). + if ( !event.namespace_re || event.namespace_re.test( handleObj.namespace ) ) { + + event.handleObj = handleObj; + event.data = handleObj.data; + + ret = ( ( jQuery.event.special[ handleObj.origType ] || {} ).handle || handleObj.handler ) + .apply( matched.elem, args ); + + if ( ret !== undefined ) { + if ( ( event.result = ret ) === false ) { + event.preventDefault(); + event.stopPropagation(); + } + } + } + } + } + + // Call the postDispatch hook for the mapped type + if ( special.postDispatch ) { + special.postDispatch.call( this, event ); + } + + return event.result; + }, + + handlers: function( event, handlers ) { + var sel, handleObj, matches, i, + handlerQueue = [], + delegateCount = handlers.delegateCount, + cur = event.target; + + // Find delegate handlers + // Black-hole SVG instance trees (#13180) + // Avoid non-left-click bubbling in Firefox (#3861) + if ( delegateCount && cur.nodeType && ( !event.button || event.type !== "click" ) ) { + + for ( ; cur != this; cur = cur.parentNode || this ) { + + // Don't check non-elements (#13208) + // Don't process clicks on disabled elements (#6911, #8165, #11382, #11764) + if ( cur.nodeType === 1 && ( cur.disabled !== true || event.type !== "click" ) ) { + matches = []; + for ( i = 0; i < delegateCount; i++ ) { + handleObj = handlers[ i ]; + + // Don't conflict with Object.prototype properties (#13203) + sel = handleObj.selector + " "; + + if ( matches[ sel ] === undefined ) { + matches[ sel ] = handleObj.needsContext ? + jQuery( sel, this ).index( cur ) >= 0 : + jQuery.find( sel, this, null, [ cur ] ).length; + } + if ( matches[ sel ] ) { + matches.push( handleObj ); + } + } + if ( matches.length ) { + handlerQueue.push( { elem: cur, handlers: matches } ); + } + } + } + } + + // Add the remaining (directly-bound) handlers + if ( delegateCount < handlers.length ) { + handlerQueue.push( { elem: this, handlers: handlers.slice( delegateCount ) } ); + } + + return handlerQueue; + }, + + fix: function( event ) { + if ( event[ jQuery.expando ] ) { + return event; + } + + // Create a writable copy of the event object and normalize some properties + var i, prop, copy, + type = event.type, + originalEvent = event, + fixHook = this.fixHooks[ type ]; + + if ( !fixHook ) { + this.fixHooks[ type ] = fixHook = + rmouseEvent.test( type ) ? this.mouseHooks : + rkeyEvent.test( type ) ? this.keyHooks : + {}; + } + copy = fixHook.props ? this.props.concat( fixHook.props ) : this.props; + + event = new jQuery.Event( originalEvent ); + + i = copy.length; + while ( i-- ) { + prop = copy[ i ]; + event[ prop ] = originalEvent[ prop ]; + } + + // Support: IE<9 + // Fix target property (#1925) + if ( !event.target ) { + event.target = originalEvent.srcElement || document; + } + + // Support: Chrome 23+, Safari? + // Target should not be a text node (#504, #13143) + if ( event.target.nodeType === 3 ) { + event.target = event.target.parentNode; + } + + // Support: IE<9 + // For mouse/key events, metaKey==false if it's undefined (#3368, #11328) + event.metaKey = !!event.metaKey; + + return fixHook.filter ? fixHook.filter( event, originalEvent ) : event; + }, + + // Includes some event props shared by KeyEvent and MouseEvent + props: "altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split( " " ), + + fixHooks: {}, + + keyHooks: { + props: "char charCode key keyCode".split( " " ), + filter: function( event, original ) { + + // Add which for key events + if ( event.which == null ) { + event.which = original.charCode != null ? original.charCode : original.keyCode; + } + + return event; + } + }, + + mouseHooks: { + props: "button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split( " " ), + filter: function( event, original ) { + var body, eventDoc, doc, + button = original.button, + fromElement = original.fromElement; + + // Calculate pageX/Y if missing and clientX/Y available + if ( event.pageX == null && original.clientX != null ) { + eventDoc = event.target.ownerDocument || document; + doc = eventDoc.documentElement; + body = eventDoc.body; + + event.pageX = original.clientX + ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - ( doc && doc.clientLeft || body && body.clientLeft || 0 ); + event.pageY = original.clientY + ( doc && doc.scrollTop || body && body.scrollTop || 0 ) - ( doc && doc.clientTop || body && body.clientTop || 0 ); + } + + // Add relatedTarget, if necessary + if ( !event.relatedTarget && fromElement ) { + event.relatedTarget = fromElement === event.target ? original.toElement : fromElement; + } + + // Add which for click: 1 === left; 2 === middle; 3 === right + // Note: button is not normalized, so don't use it + if ( !event.which && button !== undefined ) { + event.which = ( button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ) ); + } + + return event; + } + }, + + special: { + load: { + + // Prevent triggered image.load events from bubbling to window.load + noBubble: true + }, + click: { + + // For checkbox, fire native event so checked state will be right + trigger: function() { + if ( jQuery.nodeName( this, "input" ) && this.type === "checkbox" && this.click ) { + this.click(); + return false; + } + } + }, + focus: { + + // Fire native event if possible so blur/focus sequence is correct + trigger: function() { + if ( this !== document.activeElement && this.focus ) { + try { + this.focus(); + return false; + } catch ( e ) { + + // Support: IE<9 + // If we error on focus to hidden element (#1486, #12518), + // let .trigger() run the handlers + } + } + }, + delegateType: "focusin" + }, + blur: { + trigger: function() { + if ( this === document.activeElement && this.blur ) { + this.blur(); + return false; + } + }, + delegateType: "focusout" + }, + + beforeunload: { + postDispatch: function( event ) { + + // Even when returnValue equals to undefined Firefox will still show alert + if ( event.result !== undefined ) { + event.originalEvent.returnValue = event.result; + } + } + } + }, + + simulate: function( type, elem, event, bubble ) { + + // Piggyback on a donor event to simulate a different one. + // Fake originalEvent to avoid donor's stopPropagation, but if the + // simulated event prevents default then we do the same on the donor. + var e = jQuery.extend( + new jQuery.Event(), + event, + { type: type, + isSimulated: true, + originalEvent: {} + } + ); + if ( bubble ) { + jQuery.event.trigger( e, null, elem ); + } else { + jQuery.event.dispatch.call( elem, e ); + } + if ( e.isDefaultPrevented() ) { + event.preventDefault(); + } + } +}; + +jQuery.removeEvent = document.removeEventListener ? + function( elem, type, handle ) { + if ( elem.removeEventListener ) { + elem.removeEventListener( type, handle, false ); + } + } : + function( elem, type, handle ) { + var name = "on" + type; + + if ( elem.detachEvent ) { + + // #8545, #7054, preventing memory leaks for custom events in IE6-8 + // detachEvent needed property on element, by name of that event, to properly expose it to GC + if ( typeof elem[ name ] === core_strundefined ) { + elem[ name ] = null; + } + + elem.detachEvent( name, handle ); + } + }; + +jQuery.Event = function( src, props ) { + + // Allow instantiation without the 'new' keyword + if ( !( this instanceof jQuery.Event ) ) { + return new jQuery.Event( src, props ); + } + + // Event object + if ( src && src.type ) { + this.originalEvent = src; + this.type = src.type; + + // Events bubbling up the document may have been marked as prevented + // by a handler lower down the tree; reflect the correct value. + this.isDefaultPrevented = ( src.defaultPrevented || src.returnValue === false || + src.getPreventDefault && src.getPreventDefault() ) ? returnTrue : returnFalse; + + // Event type + } else { + this.type = src; + } + + // Put explicitly provided properties onto the event object + if ( props ) { + jQuery.extend( this, props ); + } + + // Create a timestamp if incoming event doesn't have one + this.timeStamp = src && src.timeStamp || jQuery.now(); + + // Mark it as fixed + this[ jQuery.expando ] = true; +}; + +// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding +// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html +jQuery.Event.prototype = { + isDefaultPrevented: returnFalse, + isPropagationStopped: returnFalse, + isImmediatePropagationStopped: returnFalse, + + preventDefault: function() { + var e = this.originalEvent; + + this.isDefaultPrevented = returnTrue; + if ( !e ) { + return; + } + + // If preventDefault exists, run it on the original event + if ( e.preventDefault ) { + e.preventDefault(); + + // Support: IE + // Otherwise set the returnValue property of the original event to false + } else { + e.returnValue = false; + } + }, + stopPropagation: function() { + var e = this.originalEvent; + + this.isPropagationStopped = returnTrue; + if ( !e ) { + return; + } + + // If stopPropagation exists, run it on the original event + if ( e.stopPropagation ) { + e.stopPropagation(); + } + + // Support: IE + // Set the cancelBubble property of the original event to true + e.cancelBubble = true; + }, + stopImmediatePropagation: function() { + this.isImmediatePropagationStopped = returnTrue; + this.stopPropagation(); + } +}; + +// Create mouseenter/leave events using mouseover/out and event-time checks +jQuery.each( { + mouseenter: "mouseover", + mouseleave: "mouseout" +}, function( orig, fix ) { + jQuery.event.special[ orig ] = { + delegateType: fix, + bindType: fix, + + handle: function( event ) { + var ret, + target = this, + related = event.relatedTarget, + handleObj = event.handleObj; + + // For mousenter/leave call the handler if related is outside the target. + // NB: No relatedTarget if the mouse left/entered the browser window + if ( !related || ( related !== target && !jQuery.contains( target, related ) ) ) { + event.type = handleObj.origType; + ret = handleObj.handler.apply( this, arguments ); + event.type = fix; + } + return ret; + } + }; +} ); + +// IE submit delegation +if ( !jQuery.support.submitBubbles ) { + + jQuery.event.special.submit = { + setup: function() { + + // Only need this for delegated form submit events + if ( jQuery.nodeName( this, "form" ) ) { + return false; + } + + // Lazy-add a submit handler when a descendant form may potentially be submitted + jQuery.event.add( this, "click._submit keypress._submit", function( e ) { + + // Node name check avoids a VML-related crash in IE (#9807) + var elem = e.target, + form = jQuery.nodeName( elem, "input" ) || jQuery.nodeName( elem, "button" ) ? elem.form : undefined; + if ( form && !jQuery._data( form, "submitBubbles" ) ) { + jQuery.event.add( form, "submit._submit", function( event ) { + event._submit_bubble = true; + } ); + jQuery._data( form, "submitBubbles", true ); + } + } ); + + // return undefined since we don't need an event listener + }, + + postDispatch: function( event ) { + + // If form was submitted by the user, bubble the event up the tree + if ( event._submit_bubble ) { + delete event._submit_bubble; + if ( this.parentNode && !event.isTrigger ) { + jQuery.event.simulate( "submit", this.parentNode, event, true ); + } + } + }, + + teardown: function() { + + // Only need this for delegated form submit events + if ( jQuery.nodeName( this, "form" ) ) { + return false; + } + + // Remove delegated handlers; cleanData eventually reaps submit handlers attached above + jQuery.event.remove( this, "._submit" ); + } + }; +} + +// IE change delegation and checkbox/radio fix +if ( !jQuery.support.changeBubbles ) { + + jQuery.event.special.change = { + + setup: function() { + + if ( rformElems.test( this.nodeName ) ) { + + // IE doesn't fire change on a check/radio until blur; trigger it on click + // after a propertychange. Eat the blur-change in special.change.handle. + // This still fires onchange a second time for check/radio after blur. + if ( this.type === "checkbox" || this.type === "radio" ) { + jQuery.event.add( this, "propertychange._change", function( event ) { + if ( event.originalEvent.propertyName === "checked" ) { + this._just_changed = true; + } + } ); + jQuery.event.add( this, "click._change", function( event ) { + if ( this._just_changed && !event.isTrigger ) { + this._just_changed = false; + } + + // Allow triggered, simulated change events (#11500) + jQuery.event.simulate( "change", this, event, true ); + } ); + } + return false; + } + + // Delegated event; lazy-add a change handler on descendant inputs + jQuery.event.add( this, "beforeactivate._change", function( e ) { + var elem = e.target; + + if ( rformElems.test( elem.nodeName ) && !jQuery._data( elem, "changeBubbles" ) ) { + jQuery.event.add( elem, "change._change", function( event ) { + if ( this.parentNode && !event.isSimulated && !event.isTrigger ) { + jQuery.event.simulate( "change", this.parentNode, event, true ); + } + } ); + jQuery._data( elem, "changeBubbles", true ); + } + } ); + }, + + handle: function( event ) { + var elem = event.target; + + // Swallow native change events from checkbox/radio, we already triggered them above + if ( this !== elem || event.isSimulated || event.isTrigger || ( elem.type !== "radio" && elem.type !== "checkbox" ) ) { + return event.handleObj.handler.apply( this, arguments ); + } + }, + + teardown: function() { + jQuery.event.remove( this, "._change" ); + + return !rformElems.test( this.nodeName ); + } + }; +} + +// Create "bubbling" focus and blur events +if ( !jQuery.support.focusinBubbles ) { + jQuery.each( { focus: "focusin", blur: "focusout" }, function( orig, fix ) { + + // Attach a single capturing handler while someone wants focusin/focusout + var attaches = 0, + handler = function( event ) { + jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ), true ); + }; + + jQuery.event.special[ fix ] = { + setup: function() { + if ( attaches++ === 0 ) { + document.addEventListener( orig, handler, true ); + } + }, + teardown: function() { + if ( --attaches === 0 ) { + document.removeEventListener( orig, handler, true ); + } + } + }; + } ); +} + +jQuery.fn.extend( { + + on: function( types, selector, data, fn, /*INTERNAL*/ one ) { + var type, origFn; + + // Types can be a map of types/handlers + if ( typeof types === "object" ) { + + // ( types-Object, selector, data ) + if ( typeof selector !== "string" ) { + + // ( types-Object, data ) + data = data || selector; + selector = undefined; + } + for ( type in types ) { + this.on( type, selector, data, types[ type ], one ); + } + return this; + } + + if ( data == null && fn == null ) { + + // ( types, fn ) + fn = selector; + data = selector = undefined; + } else if ( fn == null ) { + if ( typeof selector === "string" ) { + + // ( types, selector, fn ) + fn = data; + data = undefined; + } else { + + // ( types, data, fn ) + fn = data; + data = selector; + selector = undefined; + } + } + if ( fn === false ) { + fn = returnFalse; + } else if ( !fn ) { + return this; + } + + if ( one === 1 ) { + origFn = fn; + fn = function( event ) { + + // Can use an empty set, since event contains the info + jQuery().off( event ); + return origFn.apply( this, arguments ); + }; + + // Use same guid so caller can remove using origFn + fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); + } + return this.each( function() { + jQuery.event.add( this, types, fn, data, selector ); + } ); + }, + one: function( types, selector, data, fn ) { + return this.on( types, selector, data, fn, 1 ); + }, + off: function( types, selector, fn ) { + var handleObj, type; + if ( types && types.preventDefault && types.handleObj ) { + + // ( event ) dispatched jQuery.Event + handleObj = types.handleObj; + jQuery( types.delegateTarget ).off( + handleObj.namespace ? handleObj.origType + "." + handleObj.namespace : handleObj.origType, + handleObj.selector, + handleObj.handler + ); + return this; + } + if ( typeof types === "object" ) { + + // ( types-object [, selector] ) + for ( type in types ) { + this.off( type, selector, types[ type ] ); + } + return this; + } + if ( selector === false || typeof selector === "function" ) { + + // ( types [, fn] ) + fn = selector; + selector = undefined; + } + if ( fn === false ) { + fn = returnFalse; + } + return this.each( function() { + jQuery.event.remove( this, types, fn, selector ); + } ); + }, + + bind: function( types, data, fn ) { + return this.on( types, null, data, fn ); + }, + unbind: function( types, fn ) { + return this.off( types, null, fn ); + }, + + delegate: function( selector, types, data, fn ) { + return this.on( types, selector, data, fn ); + }, + undelegate: function( selector, types, fn ) { + + // ( namespace ) or ( selector, types [, fn] ) + return arguments.length === 1 ? this.off( selector, "**" ) : this.off( types, selector || "**", fn ); + }, + + trigger: function( type, data ) { + return this.each( function() { + jQuery.event.trigger( type, data, this ); + } ); + }, + triggerHandler: function( type, data ) { + var elem = this[ 0 ]; + if ( elem ) { + return jQuery.event.trigger( type, data, elem, true ); + } + } +} ); +/*! + * Sizzle CSS Selector Engine + * Copyright 2012 jQuery Foundation and other contributors + * Released under the MIT license + * http://sizzlejs.com/ + */ +( function( window, undefined ) { + +var i, + cachedruns, + Expr, + getText, + isXML, + compile, + hasDuplicate, + outermostContext, + + // Local document vars + setDocument, + document, + docElem, + documentIsXML, + rbuggyQSA, + rbuggyMatches, + matches, + contains, + sortOrder, + + // Instance-specific data + expando = "sizzle" + -( new Date() ), + preferredDoc = window.document, + support = {}, + dirruns = 0, + done = 0, + classCache = createCache(), + tokenCache = createCache(), + compilerCache = createCache(), + + // General-purpose constants + strundefined = typeof undefined, + MAX_NEGATIVE = 1 << 31, + + // Array methods + arr = [], + pop = arr.pop, + push = arr.push, + slice = arr.slice, + + // Use a stripped-down indexOf if we can't use a native one + indexOf = arr.indexOf || function( elem ) { + var i = 0, + len = this.length; + for ( ; i < len; i++ ) { + if ( this[ i ] === elem ) { + return i; + } + } + return -1; + }, + + // Regular expressions + + // Whitespace characters http://www.w3.org/TR/css3-selectors/#whitespace + whitespace = "[\\x20\\t\\r\\n\\f]", + + // http://www.w3.org/TR/css3-syntax/#characters + characterEncoding = "(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+", + + // Loosely modeled on CSS identifier characters + // An unquoted value should be a CSS identifier http://www.w3.org/TR/css3-selectors/#attribute-selectors + // Proper syntax: http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier + identifier = characterEncoding.replace( "w", "w#" ), + + // Acceptable operators http://www.w3.org/TR/selectors/#attribute-selectors + operators = "([*^$|!~]?=)", + attributes = "\\[" + whitespace + "*(" + characterEncoding + ")" + whitespace + + "*(?:" + operators + whitespace + "*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|(" + identifier + ")|)|)" + whitespace + "*\\]", + + // Prefer arguments quoted, + // then not containing pseudos/brackets, + // then attribute selectors/non-parenthetical expressions, + // then anything else + // These preferences are here to reduce the number of selectors + // needing tokenize in the PSEUDO preFilter + pseudos = ":(" + characterEncoding + ")(?:\\(((['\"])((?:\\\\.|[^\\\\])*?)\\3|((?:\\\\.|[^\\\\()[\\]]|" + attributes.replace( 3, 8 ) + ")*)|.*)\\)|)", + + // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter + rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ), + + rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ), + rcombinators = new RegExp( "^" + whitespace + "*([\\x20\\t\\r\\n\\f>+~])" + whitespace + "*" ), + rpseudo = new RegExp( pseudos ), + ridentifier = new RegExp( "^" + identifier + "$" ), + + matchExpr = { + "ID": new RegExp( "^#(" + characterEncoding + ")" ), + "CLASS": new RegExp( "^\\.(" + characterEncoding + ")" ), + "NAME": new RegExp( "^\\[name=['\"]?(" + characterEncoding + ")['\"]?\\]" ), + "TAG": new RegExp( "^(" + characterEncoding.replace( "w", "w*" ) + ")" ), + "ATTR": new RegExp( "^" + attributes ), + "PSEUDO": new RegExp( "^" + pseudos ), + "CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + whitespace + + "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace + + "*(\\d+)|))" + whitespace + "*\\)|)", "i" ), + + // For use in libraries implementing .is() + // We use this for POS matching in `select` + "needsContext": new RegExp( "^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + + whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" ) + }, + + rsibling = /[\x20\t\r\n\f]*[+~]/, + + rnative = /^[^{]+\{\s*\[native code/, + + // Easily-parseable/retrievable ID or TAG or CLASS selectors + rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/, + + rinputs = /^(?:input|select|textarea|button)$/i, + rheader = /^h\d$/i, + + rescape = /'|\\/g, + rattributeQuotes = /\=[\x20\t\r\n\f]*([^'"\]]*)[\x20\t\r\n\f]*\]/g, + + // CSS escapes http://www.w3.org/TR/CSS21/syndata.html#escaped-characters + runescape = /\\([\da-fA-F]{1,6}[\x20\t\r\n\f]?|.)/g, + funescape = function( _, escaped ) { + var high = "0x" + escaped - 0x10000; + + // NaN means non-codepoint + return high !== high ? + escaped : + + // BMP codepoint + high < 0 ? + String.fromCharCode( high + 0x10000 ) : + + // Supplemental Plane codepoint (surrogate pair) + String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 ); + }; + +// Use a stripped-down slice if we can't use a native one +try { + slice.call( preferredDoc.documentElement.childNodes, 0 )[ 0 ].nodeType; +} catch ( e ) { + slice = function( i ) { + var elem, + results = []; + while ( ( elem = this[ i++ ] ) ) { + results.push( elem ); + } + return results; + }; +} + +/** + * For feature detection + * @param {Function} fn The function to test for native support + */ +function isNative( fn ) { + return rnative.test( fn + "" ); +} + +/** + * Create key-value caches of limited size + * @returns {Function(string, Object)} Returns the Object data after storing it on itself with + * property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength) + * deleting the oldest entry + */ +function createCache() { + var cache, + keys = []; + + return ( cache = function( key, value ) { + + // Use (key + " ") to avoid collision with native prototype properties (see Issue #157) + if ( keys.push( key += " " ) > Expr.cacheLength ) { + + // Only keep the most recent entries + delete cache[ keys.shift() ]; + } + return ( cache[ key ] = value ); + } ); +} + +/** + * Mark a function for special use by Sizzle + * @param {Function} fn The function to mark + */ +function markFunction( fn ) { + fn[ expando ] = true; + return fn; +} + +/** + * Support testing using an element + * @param {Function} fn Passed the created div and expects a boolean result + */ +function assert( fn ) { + var div = document.createElement( "div" ); + + try { + return fn( div ); + } catch ( e ) { + return false; + } finally { + + // release memory in IE + div = null; + } +} + +function Sizzle( selector, context, results, seed ) { + var match, elem, m, nodeType, + + // QSA vars + i, groups, old, nid, newContext, newSelector; + + if ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) { + setDocument( context ); + } + + context = context || document; + results = results || []; + + if ( !selector || typeof selector !== "string" ) { + return results; + } + + if ( ( nodeType = context.nodeType ) !== 1 && nodeType !== 9 ) { + return []; + } + + if ( !documentIsXML && !seed ) { + + // Shortcuts + if ( ( match = rquickExpr.exec( selector ) ) ) { + + // Speed-up: Sizzle("#ID") + if ( ( m = match[ 1 ] ) ) { + if ( nodeType === 9 ) { + elem = context.getElementById( m ); + + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document #6963 + if ( elem && elem.parentNode ) { + + // Handle the case where IE, Opera, and Webkit return items + // by name instead of ID + if ( elem.id === m ) { + results.push( elem ); + return results; + } + } else { + return results; + } + } else { + + // Context is not a document + if ( context.ownerDocument && ( elem = context.ownerDocument.getElementById( m ) ) && + contains( context, elem ) && elem.id === m ) { + results.push( elem ); + return results; + } + } + + // Speed-up: Sizzle("TAG") + } else if ( match[ 2 ] ) { + push.apply( results, slice.call( context.getElementsByTagName( selector ), 0 ) ); + return results; + + // Speed-up: Sizzle(".CLASS") + } else if ( ( m = match[ 3 ] ) && support.getByClassName && context.getElementsByClassName ) { + push.apply( results, slice.call( context.getElementsByClassName( m ), 0 ) ); + return results; + } + } + + // QSA path + if ( support.qsa && !rbuggyQSA.test( selector ) ) { + old = true; + nid = expando; + newContext = context; + newSelector = nodeType === 9 && selector; + + // qSA works strangely on Element-rooted queries + // We can work around this by specifying an extra ID on the root + // and working up from there (Thanks to Andrew Dupont for the technique) + // IE 8 doesn't work on object elements + if ( nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) { + groups = tokenize( selector ); + + if ( ( old = context.getAttribute( "id" ) ) ) { + nid = old.replace( rescape, "\\$&" ); + } else { + context.setAttribute( "id", nid ); + } + nid = "[id='" + nid + "'] "; + + i = groups.length; + while ( i-- ) { + groups[ i ] = nid + toSelector( groups[ i ] ); + } + newContext = rsibling.test( selector ) && context.parentNode || context; + newSelector = groups.join( "," ); + } + + if ( newSelector ) { + try { + push.apply( results, slice.call( newContext.querySelectorAll( + newSelector + ), 0 ) ); + return results; + } catch ( qsaError ) { + } finally { + if ( !old ) { + context.removeAttribute( "id" ); + } + } + } + } + } + + // All others + return select( selector.replace( rtrim, "$1" ), context, results, seed ); +} + +/** + * Detect xml + * @param {Element|Object} elem An element or a document + */ +isXML = Sizzle.isXML = function( elem ) { + + // documentElement is verified for cases where it doesn't yet exist + // (such as loading iframes in IE - #4833) + var documentElement = elem && ( elem.ownerDocument || elem ).documentElement; + return documentElement ? documentElement.nodeName !== "HTML" : false; +}; + +/** + * Sets document-related variables once based on the current document + * @param {Element|Object} [doc] An element or document object to use to set the document + * @returns {Object} Returns the current document + */ +setDocument = Sizzle.setDocument = function( node ) { + var doc = node ? node.ownerDocument || node : preferredDoc; + + // If no document and documentElement is available, return + if ( doc === document || doc.nodeType !== 9 || !doc.documentElement ) { + return document; + } + + // Set our document + document = doc; + docElem = doc.documentElement; + + // Support tests + documentIsXML = isXML( doc ); + + // Check if getElementsByTagName("*") returns only elements + support.tagNameNoComments = assert( function( div ) { + div.appendChild( doc.createComment( "" ) ); + return !div.getElementsByTagName( "*" ).length; + } ); + + // Check if attributes should be retrieved by attribute nodes + support.attributes = assert( function( div ) { + div.innerHTML = ""; + var type = typeof div.lastChild.getAttribute( "multiple" ); + + // IE8 returns a string for some attributes even when not present + return type !== "boolean" && type !== "string"; + } ); + + // Check if getElementsByClassName can be trusted + support.getByClassName = assert( function( div ) { + + // Opera can't find a second classname (in 9.6) + div.innerHTML = ""; + if ( !div.getElementsByClassName || !div.getElementsByClassName( "e" ).length ) { + return false; + } + + // Safari 3.2 caches class attributes and doesn't catch changes + div.lastChild.className = "e"; + return div.getElementsByClassName( "e" ).length === 2; + } ); + + // Check if getElementById returns elements by name + // Check if getElementsByName privileges form controls or returns elements by ID + support.getByName = assert( function( div ) { + + // Inject content + div.id = expando + 0; + div.innerHTML = "
"; + docElem.insertBefore( div, docElem.firstChild ); + + // Test + var pass = doc.getElementsByName && + + // buggy browsers will return fewer than the correct 2 + doc.getElementsByName( expando ).length === 2 + + + // buggy browsers will return more than the correct 0 + doc.getElementsByName( expando + 0 ).length; + support.getIdNotName = !doc.getElementById( expando ); + + // Cleanup + docElem.removeChild( div ); + + return pass; + } ); + + // IE6/7 return modified attributes + Expr.attrHandle = assert( function( div ) { + div.innerHTML = ""; + return div.firstChild && typeof div.firstChild.getAttribute !== strundefined && + div.firstChild.getAttribute( "href" ) === "#"; + } ) ? + {} : + { + "href": function( elem ) { + return elem.getAttribute( "href", 2 ); + }, + "type": function( elem ) { + return elem.getAttribute( "type" ); + } + }; + + // ID find and filter + if ( support.getIdNotName ) { + Expr.find[ "ID" ] = function( id, context ) { + if ( typeof context.getElementById !== strundefined && !documentIsXML ) { + var m = context.getElementById( id ); + + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document #6963 + return m && m.parentNode ? [ m ] : []; + } + }; + Expr.filter[ "ID" ] = function( id ) { + var attrId = id.replace( runescape, funescape ); + return function( elem ) { + return elem.getAttribute( "id" ) === attrId; + }; + }; + } else { + Expr.find[ "ID" ] = function( id, context ) { + if ( typeof context.getElementById !== strundefined && !documentIsXML ) { + var m = context.getElementById( id ); + + return m ? + m.id === id || typeof m.getAttributeNode !== strundefined && m.getAttributeNode( "id" ).value === id ? + [ m ] : + undefined : + []; + } + }; + Expr.filter[ "ID" ] = function( id ) { + var attrId = id.replace( runescape, funescape ); + return function( elem ) { + var node = typeof elem.getAttributeNode !== strundefined && elem.getAttributeNode( "id" ); + return node && node.value === attrId; + }; + }; + } + + // Tag + Expr.find[ "TAG" ] = support.tagNameNoComments ? + function( tag, context ) { + if ( typeof context.getElementsByTagName !== strundefined ) { + return context.getElementsByTagName( tag ); + } + } : + function( tag, context ) { + var elem, + tmp = [], + i = 0, + results = context.getElementsByTagName( tag ); + + // Filter out possible comments + if ( tag === "*" ) { + while ( ( elem = results[ i++ ] ) ) { + if ( elem.nodeType === 1 ) { + tmp.push( elem ); + } + } + + return tmp; + } + return results; + }; + + // Name + Expr.find[ "NAME" ] = support.getByName && function( tag, context ) { + if ( typeof context.getElementsByName !== strundefined ) { + return context.getElementsByName( name ); + } + }; + + // Class + Expr.find[ "CLASS" ] = support.getByClassName && function( className, context ) { + if ( typeof context.getElementsByClassName !== strundefined && !documentIsXML ) { + return context.getElementsByClassName( className ); + } + }; + + // QSA and matchesSelector support + + // matchesSelector(:active) reports false when true (IE9/Opera 11.5) + rbuggyMatches = []; + + // qSa(:focus) reports false when true (Chrome 21), + // no need to also add to buggyMatches since matches checks buggyQSA + // A support test would require too much code (would include document ready) + rbuggyQSA = [ ":focus" ]; + + if ( ( support.qsa = isNative( doc.querySelectorAll ) ) ) { + + // Build QSA regex + // Regex strategy adopted from Diego Perini + assert( function( div ) { + + // Select is set to empty string on purpose + // This is to test IE's treatment of not explictly + // setting a boolean content attribute, + // since its presence should be enough + // http://bugs.jquery.com/ticket/12359 + div.innerHTML = ""; + + // IE8 - Some boolean attributes are not treated correctly + if ( !div.querySelectorAll( "[selected]" ).length ) { + rbuggyQSA.push( "\\[" + whitespace + "*(?:checked|disabled|ismap|multiple|readonly|selected|value)" ); + } + + // Webkit/Opera - :checked should return selected option elements + // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + // IE8 throws error here and will not see later tests + if ( !div.querySelectorAll( ":checked" ).length ) { + rbuggyQSA.push( ":checked" ); + } + } ); + + assert( function( div ) { + + // Opera 10-12/IE8 - ^= $= *= and empty values + // Should not select anything + div.innerHTML = ""; + if ( div.querySelectorAll( "[i^='']" ).length ) { + rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:\"\"|'')" ); + } + + // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled) + // IE8 throws error here and will not see later tests + if ( !div.querySelectorAll( ":enabled" ).length ) { + rbuggyQSA.push( ":enabled", ":disabled" ); + } + + // Opera 10-11 does not throw on post-comma invalid pseudos + div.querySelectorAll( "*,:x" ); + rbuggyQSA.push( ",.*:" ); + } ); + } + + if ( ( support.matchesSelector = isNative( ( matches = docElem.matchesSelector || + docElem.mozMatchesSelector || + docElem.webkitMatchesSelector || + docElem.oMatchesSelector || + docElem.msMatchesSelector ) ) ) ) { + + assert( function( div ) { + + // Check to see if it's possible to do matchesSelector + // on a disconnected node (IE 9) + support.disconnectedMatch = matches.call( div, "div" ); + + // This should fail with an exception + // Gecko does not error, returns false instead + matches.call( div, "[s!='']:x" ); + rbuggyMatches.push( "!=", pseudos ); + } ); + } + + rbuggyQSA = new RegExp( rbuggyQSA.join( "|" ) ); + rbuggyMatches = new RegExp( rbuggyMatches.join( "|" ) ); + + // Element contains another + // Purposefully does not implement inclusive descendent + // As in, an element does not contain itself + contains = isNative( docElem.contains ) || docElem.compareDocumentPosition ? + function( a, b ) { + var adown = a.nodeType === 9 ? a.documentElement : a, + bup = b && b.parentNode; + return a === bup || !!( bup && bup.nodeType === 1 && ( + adown.contains ? + adown.contains( bup ) : + a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 + ) ); + } : + function( a, b ) { + if ( b ) { + while ( ( b = b.parentNode ) ) { + if ( b === a ) { + return true; + } + } + } + return false; + }; + + // Document order sorting + sortOrder = docElem.compareDocumentPosition ? + function( a, b ) { + var compare; + + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + if ( ( compare = b.compareDocumentPosition && a.compareDocumentPosition && a.compareDocumentPosition( b ) ) ) { + if ( compare & 1 || a.parentNode && a.parentNode.nodeType === 11 ) { + if ( a === doc || contains( preferredDoc, a ) ) { + return -1; + } + if ( b === doc || contains( preferredDoc, b ) ) { + return 1; + } + return 0; + } + return compare & 4 ? -1 : 1; + } + + return a.compareDocumentPosition ? -1 : 1; + } : + function( a, b ) { + var cur, + i = 0, + aup = a.parentNode, + bup = b.parentNode, + ap = [ a ], + bp = [ b ]; + + // Exit early if the nodes are identical + if ( a === b ) { + hasDuplicate = true; + return 0; + + // Parentless nodes are either documents or disconnected + } else if ( !aup || !bup ) { + return a === doc ? -1 : + b === doc ? 1 : + aup ? -1 : + bup ? 1 : + 0; + + // If the nodes are siblings, we can do a quick check + } else if ( aup === bup ) { + return siblingCheck( a, b ); + } + + // Otherwise we need full lists of their ancestors for comparison + cur = a; + while ( ( cur = cur.parentNode ) ) { + ap.unshift( cur ); + } + cur = b; + while ( ( cur = cur.parentNode ) ) { + bp.unshift( cur ); + } + + // Walk down the tree looking for a discrepancy + while ( ap[ i ] === bp[ i ] ) { + i++; + } + + return i ? + + // Do a sibling check if the nodes have a common ancestor + siblingCheck( ap[ i ], bp[ i ] ) : + + // Otherwise nodes in our document sort first + ap[ i ] === preferredDoc ? -1 : + bp[ i ] === preferredDoc ? 1 : + 0; + }; + + // Always assume the presence of duplicates if sort doesn't + // pass them to our comparison function (as in Google Chrome). + hasDuplicate = false; + [ 0, 0 ].sort( sortOrder ); + support.detectDuplicates = hasDuplicate; + + return document; +}; + +Sizzle.matches = function( expr, elements ) { + return Sizzle( expr, null, null, elements ); +}; + +Sizzle.matchesSelector = function( elem, expr ) { + + // Set document vars if needed + if ( ( elem.ownerDocument || elem ) !== document ) { + setDocument( elem ); + } + + // Make sure that attribute selectors are quoted + expr = expr.replace( rattributeQuotes, "='$1']" ); + + // rbuggyQSA always contains :focus, so no need for an existence check + if ( support.matchesSelector && !documentIsXML && ( !rbuggyMatches || !rbuggyMatches.test( expr ) ) && !rbuggyQSA.test( expr ) ) { + try { + var ret = matches.call( elem, expr ); + + // IE 9's matchesSelector returns false on disconnected nodes + if ( ret || support.disconnectedMatch || + + // As well, disconnected nodes are said to be in a document + // fragment in IE 9 + elem.document && elem.document.nodeType !== 11 ) { + return ret; + } + } catch ( e ) {} + } + + return Sizzle( expr, document, null, [ elem ] ).length > 0; +}; + +Sizzle.contains = function( context, elem ) { + + // Set document vars if needed + if ( ( context.ownerDocument || context ) !== document ) { + setDocument( context ); + } + return contains( context, elem ); +}; + +Sizzle.attr = function( elem, name ) { + var val; + + // Set document vars if needed + if ( ( elem.ownerDocument || elem ) !== document ) { + setDocument( elem ); + } + + if ( !documentIsXML ) { + name = name.toLowerCase(); + } + if ( ( val = Expr.attrHandle[ name ] ) ) { + return val( elem ); + } + if ( documentIsXML || support.attributes ) { + return elem.getAttribute( name ); + } + return ( ( val = elem.getAttributeNode( name ) ) || elem.getAttribute( name ) ) && elem[ name ] === true ? + name : + val && val.specified ? val.value : null; +}; + +Sizzle.error = function( msg ) { + throw new Error( "Syntax error, unrecognized expression: " + msg ); +}; + +// Document sorting and removing duplicates +Sizzle.uniqueSort = function( results ) { + var elem, + duplicates = [], + i = 1, + j = 0; + + // Unless we *know* we can detect duplicates, assume their presence + hasDuplicate = !support.detectDuplicates; + results.sort( sortOrder ); + + if ( hasDuplicate ) { + for ( ; ( elem = results[ i ] ); i++ ) { + if ( elem === results[ i - 1 ] ) { + j = duplicates.push( i ); + } + } + while ( j-- ) { + results.splice( duplicates[ j ], 1 ); + } + } + + return results; +}; + +function siblingCheck( a, b ) { + var cur = b && a, + diff = cur && ( ~b.sourceIndex || MAX_NEGATIVE ) - ( ~a.sourceIndex || MAX_NEGATIVE ); + + // Use IE sourceIndex if available on both nodes + if ( diff ) { + return diff; + } + + // Check if b follows a + if ( cur ) { + while ( ( cur = cur.nextSibling ) ) { + if ( cur === b ) { + return -1; + } + } + } + + return a ? 1 : -1; +} + +// Returns a function to use in pseudos for input types +function createInputPseudo( type ) { + return function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && elem.type === type; + }; +} + +// Returns a function to use in pseudos for buttons +function createButtonPseudo( type ) { + return function( elem ) { + var name = elem.nodeName.toLowerCase(); + return ( name === "input" || name === "button" ) && elem.type === type; + }; +} + +// Returns a function to use in pseudos for positionals +function createPositionalPseudo( fn ) { + return markFunction( function( argument ) { + argument = +argument; + return markFunction( function( seed, matches ) { + var j, + matchIndexes = fn( [], seed.length, argument ), + i = matchIndexes.length; + + // Match elements found at the specified indexes + while ( i-- ) { + if ( seed[ ( j = matchIndexes[ i ] ) ] ) { + seed[ j ] = !( matches[ j ] = seed[ j ] ); + } + } + } ); + } ); +} + +/** + * Utility function for retrieving the text value of an array of DOM nodes + * @param {Array|Element} elem + */ +getText = Sizzle.getText = function( elem ) { + var node, + ret = "", + i = 0, + nodeType = elem.nodeType; + + if ( !nodeType ) { + + // If no nodeType, this is expected to be an array + for ( ; ( node = elem[ i ] ); i++ ) { + + // Do not traverse comment nodes + ret += getText( node ); + } + } else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { + + // Use textContent for elements + // innerText usage removed for consistency of new lines (see #11153) + if ( typeof elem.textContent === "string" ) { + return elem.textContent; + } else { + + // Traverse its children + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + ret += getText( elem ); + } + } + } else if ( nodeType === 3 || nodeType === 4 ) { + return elem.nodeValue; + } + + // Do not include comment or processing instruction nodes + + return ret; +}; + +Expr = Sizzle.selectors = { + + // Can be adjusted by the user + cacheLength: 50, + + createPseudo: markFunction, + + match: matchExpr, + + find: {}, + + relative: { + ">": { dir: "parentNode", first: true }, + " ": { dir: "parentNode" }, + "+": { dir: "previousSibling", first: true }, + "~": { dir: "previousSibling" } + }, + + preFilter: { + "ATTR": function( match ) { + match[ 1 ] = match[ 1 ].replace( runescape, funescape ); + + // Move the given value to match[3] whether quoted or unquoted + match[ 3 ] = ( match[ 4 ] || match[ 5 ] || "" ).replace( runescape, funescape ); + + if ( match[ 2 ] === "~=" ) { + match[ 3 ] = " " + match[ 3 ] + " "; + } + + return match.slice( 0, 4 ); + }, + + "CHILD": function( match ) { + /* matches from matchExpr["CHILD"] + 1 type (only|nth|...) + 2 what (child|of-type) + 3 argument (even|odd|\d*|\d*n([+-]\d+)?|...) + 4 xn-component of xn+y argument ([+-]?\d*n|) + 5 sign of xn-component + 6 x of xn-component + 7 sign of y-component + 8 y of y-component + */ + match[ 1 ] = match[ 1 ].toLowerCase(); + + if ( match[ 1 ].slice( 0, 3 ) === "nth" ) { + + // nth-* requires argument + if ( !match[ 3 ] ) { + Sizzle.error( match[ 0 ] ); + } + + // numeric x and y parameters for Expr.filter.CHILD + // remember that false/true cast respectively to 0/1 + match[ 4 ] = +( match[ 4 ] ? match[ 5 ] + ( match[ 6 ] || 1 ) : 2 * ( match[ 3 ] === "even" || match[ 3 ] === "odd" ) ); + match[ 5 ] = +( ( match[ 7 ] + match[ 8 ] ) || match[ 3 ] === "odd" ); + + // other types prohibit arguments + } else if ( match[ 3 ] ) { + Sizzle.error( match[ 0 ] ); + } + + return match; + }, + + "PSEUDO": function( match ) { + var excess, + unquoted = !match[ 5 ] && match[ 2 ]; + + if ( matchExpr[ "CHILD" ].test( match[ 0 ] ) ) { + return null; + } + + // Accept quoted arguments as-is + if ( match[ 4 ] ) { + match[ 2 ] = match[ 4 ]; + + // Strip excess characters from unquoted arguments + } else if ( unquoted && rpseudo.test( unquoted ) && + + // Get excess from tokenize (recursively) + ( excess = tokenize( unquoted, true ) ) && + + // advance to the next closing parenthesis + ( excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length ) ) { + + // excess is a negative index + match[ 0 ] = match[ 0 ].slice( 0, excess ); + match[ 2 ] = unquoted.slice( 0, excess ); + } + + // Return only captures needed by the pseudo filter method (type and argument) + return match.slice( 0, 3 ); + } + }, + + filter: { + + "TAG": function( nodeName ) { + if ( nodeName === "*" ) { + return function() { return true; }; + } + + nodeName = nodeName.replace( runescape, funescape ).toLowerCase(); + return function( elem ) { + return elem.nodeName && elem.nodeName.toLowerCase() === nodeName; + }; + }, + + "CLASS": function( className ) { + var pattern = classCache[ className + " " ]; + + return pattern || + ( pattern = new RegExp( "(^|" + whitespace + ")" + className + "(" + whitespace + "|$)" ) ) && + classCache( className, function( elem ) { + return pattern.test( elem.className || ( typeof elem.getAttribute !== strundefined && elem.getAttribute( "class" ) ) || "" ); + } ); + }, + + "ATTR": function( name, operator, check ) { + return function( elem ) { + var result = Sizzle.attr( elem, name ); + + if ( result == null ) { + return operator === "!="; + } + if ( !operator ) { + return true; + } + + result += ""; + + return operator === "=" ? result === check : + operator === "!=" ? result !== check : + operator === "^=" ? check && result.indexOf( check ) === 0 : + operator === "*=" ? check && result.indexOf( check ) > -1 : + operator === "$=" ? check && result.slice( -check.length ) === check : + operator === "~=" ? ( " " + result + " " ).indexOf( check ) > -1 : + operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" : + false; + }; + }, + + "CHILD": function( type, what, argument, first, last ) { + var simple = type.slice( 0, 3 ) !== "nth", + forward = type.slice( -4 ) !== "last", + ofType = what === "of-type"; + + return first === 1 && last === 0 ? + + // Shortcut for :nth-*(n) + function( elem ) { + return !!elem.parentNode; + } : + + function( elem, context, xml ) { + var cache, outerCache, node, diff, nodeIndex, start, + dir = simple !== forward ? "nextSibling" : "previousSibling", + parent = elem.parentNode, + name = ofType && elem.nodeName.toLowerCase(), + useCache = !xml && !ofType; + + if ( parent ) { + + // :(first|last|only)-(child|of-type) + if ( simple ) { + while ( dir ) { + node = elem; + while ( ( node = node[ dir ] ) ) { + if ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) { + return false; + } + } + + // Reverse direction for :only-* (if we haven't yet done so) + start = dir = type === "only" && !start && "nextSibling"; + } + return true; + } + + start = [ forward ? parent.firstChild : parent.lastChild ]; + + // non-xml :nth-child(...) stores cache data on `parent` + if ( forward && useCache ) { + + // Seek `elem` from a previously-cached index + outerCache = parent[ expando ] || ( parent[ expando ] = {} ); + cache = outerCache[ type ] || []; + nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; + diff = cache[ 0 ] === dirruns && cache[ 2 ]; + node = nodeIndex && parent.childNodes[ nodeIndex ]; + + while ( ( node = ++nodeIndex && node && node[ dir ] || + + // Fallback to seeking `elem` from the start + ( diff = nodeIndex = 0 ) || start.pop() ) ) { + + // When found, cache indexes on `parent` and break + if ( node.nodeType === 1 && ++diff && node === elem ) { + outerCache[ type ] = [ dirruns, nodeIndex, diff ]; + break; + } + } + + // Use previously-cached element index if available + } else if ( useCache && ( cache = ( elem[ expando ] || ( elem[ expando ] = {} ) )[ type ] ) && cache[ 0 ] === dirruns ) { + diff = cache[ 1 ]; + + // xml :nth-child(...) or :nth-last-child(...) or :nth(-last)?-of-type(...) + } else { + + // Use the same loop as above to seek `elem` from the start + while ( ( node = ++nodeIndex && node && node[ dir ] || + ( diff = nodeIndex = 0 ) || start.pop() ) ) { + + if ( ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) && ++diff ) { + + // Cache the index of each encountered element + if ( useCache ) { + ( node[ expando ] || ( node[ expando ] = {} ) )[ type ] = [ dirruns, diff ]; + } + + if ( node === elem ) { + break; + } + } + } + } + + // Incorporate the offset, then check against cycle size + diff -= last; + return diff === first || ( diff % first === 0 && diff / first >= 0 ); + } + }; + }, + + "PSEUDO": function( pseudo, argument ) { + + // pseudo-class names are case-insensitive + // http://www.w3.org/TR/selectors/#pseudo-classes + // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters + // Remember that setFilters inherits from pseudos + var args, + fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] || + Sizzle.error( "unsupported pseudo: " + pseudo ); + + // The user may use createPseudo to indicate that + // arguments are needed to create the filter function + // just as Sizzle does + if ( fn[ expando ] ) { + return fn( argument ); + } + + // But maintain support for old signatures + if ( fn.length > 1 ) { + args = [ pseudo, pseudo, "", argument ]; + return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ? + markFunction( function( seed, matches ) { + var idx, + matched = fn( seed, argument ), + i = matched.length; + while ( i-- ) { + idx = indexOf.call( seed, matched[ i ] ); + seed[ idx ] = !( matches[ idx ] = matched[ i ] ); + } + } ) : + function( elem ) { + return fn( elem, 0, args ); + }; + } + + return fn; + } + }, + + pseudos: { + + // Potentially complex pseudos + "not": markFunction( function( selector ) { + + // Trim the selector passed to compile + // to avoid treating leading and trailing + // spaces as combinators + var input = [], + results = [], + matcher = compile( selector.replace( rtrim, "$1" ) ); + + return matcher[ expando ] ? + markFunction( function( seed, matches, context, xml ) { + var elem, + unmatched = matcher( seed, null, xml, [] ), + i = seed.length; + + // Match elements unmatched by `matcher` + while ( i-- ) { + if ( ( elem = unmatched[ i ] ) ) { + seed[ i ] = !( matches[ i ] = elem ); + } + } + } ) : + function( elem, context, xml ) { + input[ 0 ] = elem; + matcher( input, null, xml, results ); + return !results.pop(); + }; + } ), + + "has": markFunction( function( selector ) { + return function( elem ) { + return Sizzle( selector, elem ).length > 0; + }; + } ), + + "contains": markFunction( function( text ) { + return function( elem ) { + return ( elem.textContent || elem.innerText || getText( elem ) ).indexOf( text ) > -1; + }; + } ), + + // "Whether an element is represented by a :lang() selector + // is based solely on the element's language value + // being equal to the identifier C, + // or beginning with the identifier C immediately followed by "-". + // The matching of C against the element's language value is performed case-insensitively. + // The identifier C does not have to be a valid language name." + // http://www.w3.org/TR/selectors/#lang-pseudo + "lang": markFunction( function( lang ) { + + // lang value must be a valid identifider + if ( !ridentifier.test( lang || "" ) ) { + Sizzle.error( "unsupported lang: " + lang ); + } + lang = lang.replace( runescape, funescape ).toLowerCase(); + return function( elem ) { + var elemLang; + do { + if ( ( elemLang = documentIsXML ? + elem.getAttribute( "xml:lang" ) || elem.getAttribute( "lang" ) : + elem.lang ) ) { + + elemLang = elemLang.toLowerCase(); + return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0; + } + } while ( ( elem = elem.parentNode ) && elem.nodeType === 1 ); + return false; + }; + } ), + + // Miscellaneous + "target": function( elem ) { + var hash = window.location && window.location.hash; + return hash && hash.slice( 1 ) === elem.id; + }, + + "root": function( elem ) { + return elem === docElem; + }, + + "focus": function( elem ) { + return elem === document.activeElement && ( !document.hasFocus || document.hasFocus() ) && !!( elem.type || elem.href || ~elem.tabIndex ); + }, + + // Boolean properties + "enabled": function( elem ) { + return elem.disabled === false; + }, + + "disabled": function( elem ) { + return elem.disabled === true; + }, + + "checked": function( elem ) { + + // In CSS3, :checked should return both checked and selected elements + // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + var nodeName = elem.nodeName.toLowerCase(); + return ( nodeName === "input" && !!elem.checked ) || ( nodeName === "option" && !!elem.selected ); + }, + + "selected": function( elem ) { + + // Accessing this property makes selected-by-default + // options in Safari work properly + if ( elem.parentNode ) { + elem.parentNode.selectedIndex; + } + + return elem.selected === true; + }, + + // Contents + "empty": function( elem ) { + + // http://www.w3.org/TR/selectors/#empty-pseudo + // :empty is only affected by element nodes and content nodes(including text(3), cdata(4)), + // not comment, processing instructions, or others + // Thanks to Diego Perini for the nodeName shortcut + // Greater than "@" means alpha characters (specifically not starting with "#" or "?") + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + if ( elem.nodeName > "@" || elem.nodeType === 3 || elem.nodeType === 4 ) { + return false; + } + } + return true; + }, + + "parent": function( elem ) { + return !Expr.pseudos[ "empty" ]( elem ); + }, + + // Element/input types + "header": function( elem ) { + return rheader.test( elem.nodeName ); + }, + + "input": function( elem ) { + return rinputs.test( elem.nodeName ); + }, + + "button": function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && elem.type === "button" || name === "button"; + }, + + "text": function( elem ) { + var attr; + + // IE6 and 7 will map elem.type to 'text' for new HTML5 types (search, etc) + // use getAttribute instead to test this case + return elem.nodeName.toLowerCase() === "input" && + elem.type === "text" && + ( ( attr = elem.getAttribute( "type" ) ) == null || attr.toLowerCase() === elem.type ); + }, + + // Position-in-collection + "first": createPositionalPseudo( function() { + return [ 0 ]; + } ), + + "last": createPositionalPseudo( function( matchIndexes, length ) { + return [ length - 1 ]; + } ), + + "eq": createPositionalPseudo( function( matchIndexes, length, argument ) { + return [ argument < 0 ? argument + length : argument ]; + } ), + + "even": createPositionalPseudo( function( matchIndexes, length ) { + var i = 0; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + } ), + + "odd": createPositionalPseudo( function( matchIndexes, length ) { + var i = 1; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + } ), + + "lt": createPositionalPseudo( function( matchIndexes, length, argument ) { + var i = argument < 0 ? argument + length : argument; + for ( ; --i >= 0; ) { + matchIndexes.push( i ); + } + return matchIndexes; + } ), + + "gt": createPositionalPseudo( function( matchIndexes, length, argument ) { + var i = argument < 0 ? argument + length : argument; + for ( ; ++i < length; ) { + matchIndexes.push( i ); + } + return matchIndexes; + } ) + } +}; + +// Add button/input type pseudos +for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) { + Expr.pseudos[ i ] = createInputPseudo( i ); +} +for ( i in { submit: true, reset: true } ) { + Expr.pseudos[ i ] = createButtonPseudo( i ); +} + +function tokenize( selector, parseOnly ) { + var matched, match, tokens, type, + soFar, groups, preFilters, + cached = tokenCache[ selector + " " ]; + + if ( cached ) { + return parseOnly ? 0 : cached.slice( 0 ); + } + + soFar = selector; + groups = []; + preFilters = Expr.preFilter; + + while ( soFar ) { + + // Comma and first run + if ( !matched || ( match = rcomma.exec( soFar ) ) ) { + if ( match ) { + + // Don't consume trailing commas as valid + soFar = soFar.slice( match[ 0 ].length ) || soFar; + } + groups.push( tokens = [] ); + } + + matched = false; + + // Combinators + if ( ( match = rcombinators.exec( soFar ) ) ) { + matched = match.shift(); + tokens.push( { + value: matched, + + // Cast descendant combinators to space + type: match[ 0 ].replace( rtrim, " " ) + } ); + soFar = soFar.slice( matched.length ); + } + + // Filters + for ( type in Expr.filter ) { + if ( ( match = matchExpr[ type ].exec( soFar ) ) && ( !preFilters[ type ] || + ( match = preFilters[ type ]( match ) ) ) ) { + matched = match.shift(); + tokens.push( { + value: matched, + type: type, + matches: match + } ); + soFar = soFar.slice( matched.length ); + } + } + + if ( !matched ) { + break; + } + } + + // Return the length of the invalid excess + // if we're just parsing + // Otherwise, throw an error or return tokens + return parseOnly ? + soFar.length : + soFar ? + Sizzle.error( selector ) : + + // Cache the tokens + tokenCache( selector, groups ).slice( 0 ); +} + +function toSelector( tokens ) { + var i = 0, + len = tokens.length, + selector = ""; + for ( ; i < len; i++ ) { + selector += tokens[ i ].value; + } + return selector; +} + +function addCombinator( matcher, combinator, base ) { + var dir = combinator.dir, + checkNonElements = base && dir === "parentNode", + doneName = done++; + + return combinator.first ? + + // Check against closest ancestor/preceding element + function( elem, context, xml ) { + while ( ( elem = elem[ dir ] ) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + return matcher( elem, context, xml ); + } + } + } : + + // Check against all ancestor/preceding elements + function( elem, context, xml ) { + var data, cache, outerCache, + dirkey = dirruns + " " + doneName; + + // We can't set arbitrary data on XML nodes, so they don't benefit from dir caching + if ( xml ) { + while ( ( elem = elem[ dir ] ) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + if ( matcher( elem, context, xml ) ) { + return true; + } + } + } + } else { + while ( ( elem = elem[ dir ] ) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + outerCache = elem[ expando ] || ( elem[ expando ] = {} ); + if ( ( cache = outerCache[ dir ] ) && cache[ 0 ] === dirkey ) { + if ( ( data = cache[ 1 ] ) === true || data === cachedruns ) { + return data === true; + } + } else { + cache = outerCache[ dir ] = [ dirkey ]; + cache[ 1 ] = matcher( elem, context, xml ) || cachedruns; + if ( cache[ 1 ] === true ) { + return true; + } + } + } + } + } + }; +} + +function elementMatcher( matchers ) { + return matchers.length > 1 ? + function( elem, context, xml ) { + var i = matchers.length; + while ( i-- ) { + if ( !matchers[ i ]( elem, context, xml ) ) { + return false; + } + } + return true; + } : + matchers[ 0 ]; +} + +function condense( unmatched, map, filter, context, xml ) { + var elem, + newUnmatched = [], + i = 0, + len = unmatched.length, + mapped = map != null; + + for ( ; i < len; i++ ) { + if ( ( elem = unmatched[ i ] ) ) { + if ( !filter || filter( elem, context, xml ) ) { + newUnmatched.push( elem ); + if ( mapped ) { + map.push( i ); + } + } + } + } + + return newUnmatched; +} + +function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) { + if ( postFilter && !postFilter[ expando ] ) { + postFilter = setMatcher( postFilter ); + } + if ( postFinder && !postFinder[ expando ] ) { + postFinder = setMatcher( postFinder, postSelector ); + } + return markFunction( function( seed, results, context, xml ) { + var temp, i, elem, + preMap = [], + postMap = [], + preexisting = results.length, + + // Get initial elements from seed or context + elems = seed || multipleContexts( selector || "*", context.nodeType ? [ context ] : context, [] ), + + // Prefilter to get matcher input, preserving a map for seed-results synchronization + matcherIn = preFilter && ( seed || !selector ) ? + condense( elems, preMap, preFilter, context, xml ) : + elems, + + matcherOut = matcher ? + + // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results, + postFinder || ( seed ? preFilter : preexisting || postFilter ) ? + + // ...intermediate processing is necessary + [] : + + // ...otherwise use results directly + results : + matcherIn; + + // Find primary matches + if ( matcher ) { + matcher( matcherIn, matcherOut, context, xml ); + } + + // Apply postFilter + if ( postFilter ) { + temp = condense( matcherOut, postMap ); + postFilter( temp, [], context, xml ); + + // Un-match failing elements by moving them back to matcherIn + i = temp.length; + while ( i-- ) { + if ( ( elem = temp[ i ] ) ) { + matcherOut[ postMap[ i ] ] = !( matcherIn[ postMap[ i ] ] = elem ); + } + } + } + + if ( seed ) { + if ( postFinder || preFilter ) { + if ( postFinder ) { + + // Get the final matcherOut by condensing this intermediate into postFinder contexts + temp = []; + i = matcherOut.length; + while ( i-- ) { + if ( ( elem = matcherOut[ i ] ) ) { + + // Restore matcherIn since elem is not yet a final match + temp.push( ( matcherIn[ i ] = elem ) ); + } + } + postFinder( null, ( matcherOut = [] ), temp, xml ); + } + + // Move matched elements from seed to results to keep them synchronized + i = matcherOut.length; + while ( i-- ) { + if ( ( elem = matcherOut[ i ] ) && + ( temp = postFinder ? indexOf.call( seed, elem ) : preMap[ i ] ) > -1 ) { + + seed[ temp ] = !( results[ temp ] = elem ); + } + } + } + + // Add elements to results, through postFinder if defined + } else { + matcherOut = condense( + matcherOut === results ? + matcherOut.splice( preexisting, matcherOut.length ) : + matcherOut + ); + if ( postFinder ) { + postFinder( null, results, matcherOut, xml ); + } else { + push.apply( results, matcherOut ); + } + } + } ); +} + +function matcherFromTokens( tokens ) { + var checkContext, matcher, j, + len = tokens.length, + leadingRelative = Expr.relative[ tokens[ 0 ].type ], + implicitRelative = leadingRelative || Expr.relative[ " " ], + i = leadingRelative ? 1 : 0, + + // The foundational matcher ensures that elements are reachable from top-level context(s) + matchContext = addCombinator( function( elem ) { + return elem === checkContext; + }, implicitRelative, true ), + matchAnyContext = addCombinator( function( elem ) { + return indexOf.call( checkContext, elem ) > -1; + }, implicitRelative, true ), + matchers = [ function( elem, context, xml ) { + return ( !leadingRelative && ( xml || context !== outermostContext ) ) || ( + ( checkContext = context ).nodeType ? + matchContext( elem, context, xml ) : + matchAnyContext( elem, context, xml ) ); + } ]; + + for ( ; i < len; i++ ) { + if ( ( matcher = Expr.relative[ tokens[ i ].type ] ) ) { + matchers = [ addCombinator( elementMatcher( matchers ), matcher ) ]; + } else { + matcher = Expr.filter[ tokens[ i ].type ].apply( null, tokens[ i ].matches ); + + // Return special upon seeing a positional matcher + if ( matcher[ expando ] ) { + + // Find the next relative operator (if any) for proper handling + j = ++i; + for ( ; j < len; j++ ) { + if ( Expr.relative[ tokens[ j ].type ] ) { + break; + } + } + return setMatcher( + i > 1 && elementMatcher( matchers ), + i > 1 && toSelector( tokens.slice( 0, i - 1 ) ).replace( rtrim, "$1" ), + matcher, + i < j && matcherFromTokens( tokens.slice( i, j ) ), + j < len && matcherFromTokens( ( tokens = tokens.slice( j ) ) ), + j < len && toSelector( tokens ) + ); + } + matchers.push( matcher ); + } + } + + return elementMatcher( matchers ); +} + +function matcherFromGroupMatchers( elementMatchers, setMatchers ) { + + // A counter to specify which element is currently being matched + var matcherCachedRuns = 0, + bySet = setMatchers.length > 0, + byElement = elementMatchers.length > 0, + superMatcher = function( seed, context, xml, results, expandContext ) { + var elem, j, matcher, + setMatched = [], + matchedCount = 0, + i = "0", + unmatched = seed && [], + outermost = expandContext != null, + contextBackup = outermostContext, + + // We must always have either seed elements or context + elems = seed || byElement && Expr.find[ "TAG" ]( "*", expandContext && context.parentNode || context ), + + // Use integer dirruns iff this is the outermost matcher + dirrunsUnique = ( dirruns += contextBackup == null ? 1 : Math.random() || 0.1 ); + + if ( outermost ) { + outermostContext = context !== document && context; + cachedruns = matcherCachedRuns; + } + + // Add elements passing elementMatchers directly to results + // Keep `i` a string if there are no elements so `matchedCount` will be "00" below + for ( ; ( elem = elems[ i ] ) != null; i++ ) { + if ( byElement && elem ) { + j = 0; + while ( ( matcher = elementMatchers[ j++ ] ) ) { + if ( matcher( elem, context, xml ) ) { + results.push( elem ); + break; + } + } + if ( outermost ) { + dirruns = dirrunsUnique; + cachedruns = ++matcherCachedRuns; + } + } + + // Track unmatched elements for set filters + if ( bySet ) { + + // They will have gone through all possible matchers + if ( ( elem = !matcher && elem ) ) { + matchedCount--; + } + + // Lengthen the array for every element, matched or not + if ( seed ) { + unmatched.push( elem ); + } + } + } + + // Apply set filters to unmatched elements + matchedCount += i; + if ( bySet && i !== matchedCount ) { + j = 0; + while ( ( matcher = setMatchers[ j++ ] ) ) { + matcher( unmatched, setMatched, context, xml ); + } + + if ( seed ) { + + // Reintegrate element matches to eliminate the need for sorting + if ( matchedCount > 0 ) { + while ( i-- ) { + if ( !( unmatched[ i ] || setMatched[ i ] ) ) { + setMatched[ i ] = pop.call( results ); + } + } + } + + // Discard index placeholder values to get only actual matches + setMatched = condense( setMatched ); + } + + // Add matches to results + push.apply( results, setMatched ); + + // Seedless set matches succeeding multiple successful matchers stipulate sorting + if ( outermost && !seed && setMatched.length > 0 && + ( matchedCount + setMatchers.length ) > 1 ) { + + Sizzle.uniqueSort( results ); + } + } + + // Override manipulation of globals by nested matchers + if ( outermost ) { + dirruns = dirrunsUnique; + outermostContext = contextBackup; + } + + return unmatched; + }; + + return bySet ? + markFunction( superMatcher ) : + superMatcher; +} + +compile = Sizzle.compile = function( selector, group /* Internal Use Only */ ) { + var i, + setMatchers = [], + elementMatchers = [], + cached = compilerCache[ selector + " " ]; + + if ( !cached ) { + + // Generate a function of recursive functions that can be used to check each element + if ( !group ) { + group = tokenize( selector ); + } + i = group.length; + while ( i-- ) { + cached = matcherFromTokens( group[ i ] ); + if ( cached[ expando ] ) { + setMatchers.push( cached ); + } else { + elementMatchers.push( cached ); + } + } + + // Cache the compiled function + cached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) ); + } + return cached; +}; + +function multipleContexts( selector, contexts, results ) { + var i = 0, + len = contexts.length; + for ( ; i < len; i++ ) { + Sizzle( selector, contexts[ i ], results ); + } + return results; +} + +function select( selector, context, results, seed ) { + var i, tokens, token, type, find, + match = tokenize( selector ); + + if ( !seed ) { + + // Try to minimize operations if there is only one group + if ( match.length === 1 ) { + + // Take a shortcut and set the context if the root selector is an ID + tokens = match[ 0 ] = match[ 0 ].slice( 0 ); + if ( tokens.length > 2 && ( token = tokens[ 0 ] ).type === "ID" && + context.nodeType === 9 && !documentIsXML && + Expr.relative[ tokens[ 1 ].type ] ) { + + context = Expr.find[ "ID" ]( token.matches[ 0 ].replace( runescape, funescape ), context )[ 0 ]; + if ( !context ) { + return results; + } + + selector = selector.slice( tokens.shift().value.length ); + } + + // Fetch a seed set for right-to-left matching + i = matchExpr[ "needsContext" ].test( selector ) ? 0 : tokens.length; + while ( i-- ) { + token = tokens[ i ]; + + // Abort if we hit a combinator + if ( Expr.relative[ ( type = token.type ) ] ) { + break; + } + if ( ( find = Expr.find[ type ] ) ) { + + // Search, expanding context for leading sibling combinators + if ( ( seed = find( + token.matches[ 0 ].replace( runescape, funescape ), + rsibling.test( tokens[ 0 ].type ) && context.parentNode || context + ) ) ) { + + // If seed is empty or no tokens remain, we can return early + tokens.splice( i, 1 ); + selector = seed.length && toSelector( tokens ); + if ( !selector ) { + push.apply( results, slice.call( seed, 0 ) ); + return results; + } + + break; + } + } + } + } + } + + // Compile and execute a filtering function + // Provide `match` to avoid retokenization if we modified the selector above + compile( selector, match )( + seed, + context, + documentIsXML, + results, + rsibling.test( selector ) + ); + return results; +} + +// Deprecated +Expr.pseudos[ "nth" ] = Expr.pseudos[ "eq" ]; + +// Easy API for creating new setFilters +function setFilters() {} +Expr.filters = setFilters.prototype = Expr.pseudos; +Expr.setFilters = new setFilters(); + +// Initialize with the default document +setDocument(); + +// Override sizzle attribute retrieval +Sizzle.attr = jQuery.attr; +jQuery.find = Sizzle; +jQuery.expr = Sizzle.selectors; +jQuery.expr[ ":" ] = jQuery.expr.pseudos; +jQuery.unique = Sizzle.uniqueSort; +jQuery.text = Sizzle.getText; +jQuery.isXMLDoc = Sizzle.isXML; +jQuery.contains = Sizzle.contains; + +} )( window ); +var runtil = /Until$/, + rparentsprev = /^(?:parents|prev(?:Until|All))/, + isSimple = /^.[^:#\[\.,]*$/, + rneedsContext = jQuery.expr.match.needsContext, + + // methods guaranteed to produce a unique set when starting from a unique set + guaranteedUnique = { + children: true, + contents: true, + next: true, + prev: true + }; + +jQuery.fn.extend( { + find: function( selector ) { + var i, ret, self, + len = this.length; + + if ( typeof selector !== "string" ) { + self = this; + return this.pushStack( jQuery( selector ).filter( function() { + for ( i = 0; i < len; i++ ) { + if ( jQuery.contains( self[ i ], this ) ) { + return true; + } + } + } ) ); + } + + ret = []; + for ( i = 0; i < len; i++ ) { + jQuery.find( selector, this[ i ], ret ); + } + + // Needed because $( selector, context ) becomes $( context ).find( selector ) + ret = this.pushStack( len > 1 ? jQuery.unique( ret ) : ret ); + ret.selector = ( this.selector ? this.selector + " " : "" ) + selector; + return ret; + }, + + has: function( target ) { + var i, + targets = jQuery( target, this ), + len = targets.length; + + return this.filter( function() { + for ( i = 0; i < len; i++ ) { + if ( jQuery.contains( this, targets[ i ] ) ) { + return true; + } + } + } ); + }, + + not: function( selector ) { + return this.pushStack( winnow( this, selector, false ) ); + }, + + filter: function( selector ) { + return this.pushStack( winnow( this, selector, true ) ); + }, + + is: function( selector ) { + return !!selector && ( + typeof selector === "string" ? + + // If this is a positional/relative selector, check membership in the returned set + // so $("p:first").is("p:last") won't return true for a doc with two "p". + rneedsContext.test( selector ) ? + jQuery( selector, this.context ).index( this[ 0 ] ) >= 0 : + jQuery.filter( selector, this ).length > 0 : + this.filter( selector ).length > 0 ); + }, + + closest: function( selectors, context ) { + var cur, + i = 0, + l = this.length, + ret = [], + pos = rneedsContext.test( selectors ) || typeof selectors !== "string" ? + jQuery( selectors, context || this.context ) : + 0; + + for ( ; i < l; i++ ) { + cur = this[ i ]; + + while ( cur && cur.ownerDocument && cur !== context && cur.nodeType !== 11 ) { + if ( pos ? pos.index( cur ) > -1 : jQuery.find.matchesSelector( cur, selectors ) ) { + ret.push( cur ); + break; + } + cur = cur.parentNode; + } + } + + return this.pushStack( ret.length > 1 ? jQuery.unique( ret ) : ret ); + }, + + // Determine the position of an element within + // the matched set of elements + index: function( elem ) { + + // No argument, return index in parent + if ( !elem ) { + return ( this[ 0 ] && this[ 0 ].parentNode ) ? this.first().prevAll().length : -1; + } + + // index in selector + if ( typeof elem === "string" ) { + return jQuery.inArray( this[ 0 ], jQuery( elem ) ); + } + + // Locate the position of the desired element + return jQuery.inArray( + + // If it receives a jQuery object, the first element is used + elem.jquery ? elem[ 0 ] : elem, this ); + }, + + add: function( selector, context ) { + var set = typeof selector === "string" ? + jQuery( selector, context ) : + jQuery.makeArray( selector && selector.nodeType ? [ selector ] : selector ), + all = jQuery.merge( this.get(), set ); + + return this.pushStack( jQuery.unique( all ) ); + }, + + addBack: function( selector ) { + return this.add( selector == null ? + this.prevObject : this.prevObject.filter( selector ) + ); + } +} ); + +jQuery.fn.andSelf = jQuery.fn.addBack; + +function sibling( cur, dir ) { + do { + cur = cur[ dir ]; + } while ( cur && cur.nodeType !== 1 ); + + return cur; +} + +jQuery.each( { + parent: function( elem ) { + var parent = elem.parentNode; + return parent && parent.nodeType !== 11 ? parent : null; + }, + parents: function( elem ) { + return jQuery.dir( elem, "parentNode" ); + }, + parentsUntil: function( elem, i, until ) { + return jQuery.dir( elem, "parentNode", until ); + }, + next: function( elem ) { + return sibling( elem, "nextSibling" ); + }, + prev: function( elem ) { + return sibling( elem, "previousSibling" ); + }, + nextAll: function( elem ) { + return jQuery.dir( elem, "nextSibling" ); + }, + prevAll: function( elem ) { + return jQuery.dir( elem, "previousSibling" ); + }, + nextUntil: function( elem, i, until ) { + return jQuery.dir( elem, "nextSibling", until ); + }, + prevUntil: function( elem, i, until ) { + return jQuery.dir( elem, "previousSibling", until ); + }, + siblings: function( elem ) { + return jQuery.sibling( ( elem.parentNode || {} ).firstChild, elem ); + }, + children: function( elem ) { + return jQuery.sibling( elem.firstChild ); + }, + contents: function( elem ) { + return jQuery.nodeName( elem, "iframe" ) ? + elem.contentDocument || elem.contentWindow.document : + jQuery.merge( [], elem.childNodes ); + } +}, function( name, fn ) { + jQuery.fn[ name ] = function( until, selector ) { + var ret = jQuery.map( this, fn, until ); + + if ( !runtil.test( name ) ) { + selector = until; + } + + if ( selector && typeof selector === "string" ) { + ret = jQuery.filter( selector, ret ); + } + + ret = this.length > 1 && !guaranteedUnique[ name ] ? jQuery.unique( ret ) : ret; + + if ( this.length > 1 && rparentsprev.test( name ) ) { + ret = ret.reverse(); + } + + return this.pushStack( ret ); + }; +} ); + +jQuery.extend( { + filter: function( expr, elems, not ) { + if ( not ) { + expr = ":not(" + expr + ")"; + } + + return elems.length === 1 ? + jQuery.find.matchesSelector( elems[ 0 ], expr ) ? [ elems[ 0 ] ] : [] : + jQuery.find.matches( expr, elems ); + }, + + dir: function( elem, dir, until ) { + var matched = [], + cur = elem[ dir ]; + + while ( cur && cur.nodeType !== 9 && ( until === undefined || cur.nodeType !== 1 || !jQuery( cur ).is( until ) ) ) { + if ( cur.nodeType === 1 ) { + matched.push( cur ); + } + cur = cur[ dir ]; + } + return matched; + }, + + sibling: function( n, elem ) { + var r = []; + + for ( ; n; n = n.nextSibling ) { + if ( n.nodeType === 1 && n !== elem ) { + r.push( n ); + } + } + + return r; + } +} ); + +// Implement the identical functionality for filter and not +function winnow( elements, qualifier, keep ) { + + // Can't pass null or undefined to indexOf in Firefox 4 + // Set to 0 to skip string check + qualifier = qualifier || 0; + + if ( jQuery.isFunction( qualifier ) ) { + return jQuery.grep( elements, function( elem, i ) { + var retVal = !!qualifier.call( elem, i, elem ); + return retVal === keep; + } ); + + } else if ( qualifier.nodeType ) { + return jQuery.grep( elements, function( elem ) { + return ( elem === qualifier ) === keep; + } ); + + } else if ( typeof qualifier === "string" ) { + var filtered = jQuery.grep( elements, function( elem ) { + return elem.nodeType === 1; + } ); + + if ( isSimple.test( qualifier ) ) { + return jQuery.filter( qualifier, filtered, !keep ); + } else { + qualifier = jQuery.filter( qualifier, filtered ); + } + } + + return jQuery.grep( elements, function( elem ) { + return ( jQuery.inArray( elem, qualifier ) >= 0 ) === keep; + } ); +} +function createSafeFragment( document ) { + var list = nodeNames.split( "|" ), + safeFrag = document.createDocumentFragment(); + + if ( safeFrag.createElement ) { + while ( list.length ) { + safeFrag.createElement( + list.pop() + ); + } + } + return safeFrag; +} + +var nodeNames = "abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|" + + "header|hgroup|mark|meter|nav|output|progress|section|summary|time|video", + rinlinejQuery = / jQuery\d+="(?:null|\d+)"/g, + rnoshimcache = new RegExp( "<(?:" + nodeNames + ")[\\s/>]", "i" ), + rleadingWhitespace = /^\s+/, + rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi, + rtagName = /<([\w:]+)/, + rtbody = /\s*$/g, + + // We have to close these tags to support XHTML (#13200) + wrapMap = { + option: [ 1, "" ], + legend: [ 1, "
", "
" ], + area: [ 1, "", "" ], + param: [ 1, "", "" ], + thead: [ 1, "", "
" ], + tr: [ 2, "", "
" ], + col: [ 2, "", "
" ], + td: [ 3, "", "
" ], + + // IE6-8 can't serialize link, script, style, or any html5 (NoScope) tags, + // unless wrapped in a div with non-breaking characters in front of it. + _default: jQuery.support.htmlSerialize ? [ 0, "", "" ] : [ 1, "X
", "
" ] + }, + safeFragment = createSafeFragment( document ), + fragmentDiv = safeFragment.appendChild( document.createElement( "div" ) ); + +wrapMap.optgroup = wrapMap.option; +wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; +wrapMap.th = wrapMap.td; + +jQuery.fn.extend( { + text: function( value ) { + return jQuery.access( this, function( value ) { + return value === undefined ? + jQuery.text( this ) : + this.empty().append( ( this[ 0 ] && this[ 0 ].ownerDocument || document ).createTextNode( value ) ); + }, null, value, arguments.length ); + }, + + wrapAll: function( html ) { + if ( jQuery.isFunction( html ) ) { + return this.each( function( i ) { + jQuery( this ).wrapAll( html.call( this, i ) ); + } ); + } + + if ( this[ 0 ] ) { + + // The elements to wrap the target around + var wrap = jQuery( html, this[ 0 ].ownerDocument ).eq( 0 ).clone( true ); + + if ( this[ 0 ].parentNode ) { + wrap.insertBefore( this[ 0 ] ); + } + + wrap.map( function() { + var elem = this; + + while ( elem.firstChild && elem.firstChild.nodeType === 1 ) { + elem = elem.firstChild; + } + + return elem; + } ).append( this ); + } + + return this; + }, + + wrapInner: function( html ) { + if ( jQuery.isFunction( html ) ) { + return this.each( function( i ) { + jQuery( this ).wrapInner( html.call( this, i ) ); + } ); + } + + return this.each( function() { + var self = jQuery( this ), + contents = self.contents(); + + if ( contents.length ) { + contents.wrapAll( html ); + + } else { + self.append( html ); + } + } ); + }, + + wrap: function( html ) { + var isFunction = jQuery.isFunction( html ); + + return this.each( function( i ) { + jQuery( this ).wrapAll( isFunction ? html.call( this, i ) : html ); + } ); + }, + + unwrap: function() { + return this.parent().each( function() { + if ( !jQuery.nodeName( this, "body" ) ) { + jQuery( this ).replaceWith( this.childNodes ); + } + } ).end(); + }, + + append: function() { + return this.domManip( arguments, true, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + this.appendChild( elem ); + } + } ); + }, + + prepend: function() { + return this.domManip( arguments, true, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + this.insertBefore( elem, this.firstChild ); + } + } ); + }, + + before: function() { + return this.domManip( arguments, false, function( elem ) { + if ( this.parentNode ) { + this.parentNode.insertBefore( elem, this ); + } + } ); + }, + + after: function() { + return this.domManip( arguments, false, function( elem ) { + if ( this.parentNode ) { + this.parentNode.insertBefore( elem, this.nextSibling ); + } + } ); + }, + + // keepData is for internal use only--do not document + remove: function( selector, keepData ) { + var elem, + i = 0; + + for ( ; ( elem = this[ i ] ) != null; i++ ) { + if ( !selector || jQuery.filter( selector, [ elem ] ).length > 0 ) { + if ( !keepData && elem.nodeType === 1 ) { + jQuery.cleanData( getAll( elem ) ); + } + + if ( elem.parentNode ) { + if ( keepData && jQuery.contains( elem.ownerDocument, elem ) ) { + setGlobalEval( getAll( elem, "script" ) ); + } + elem.parentNode.removeChild( elem ); + } + } + } + + return this; + }, + + empty: function() { + var elem, + i = 0; + + for ( ; ( elem = this[ i ] ) != null; i++ ) { + + // Remove element nodes and prevent memory leaks + if ( elem.nodeType === 1 ) { + jQuery.cleanData( getAll( elem, false ) ); + } + + // Remove any remaining nodes + while ( elem.firstChild ) { + elem.removeChild( elem.firstChild ); + } + + // If this is a select, ensure that it displays empty (#12336) + // Support: IE<9 + if ( elem.options && jQuery.nodeName( elem, "select" ) ) { + elem.options.length = 0; + } + } + + return this; + }, + + clone: function( dataAndEvents, deepDataAndEvents ) { + dataAndEvents = dataAndEvents == null ? false : dataAndEvents; + deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents; + + return this.map( function() { + return jQuery.clone( this, dataAndEvents, deepDataAndEvents ); + } ); + }, + + html: function( value ) { + return jQuery.access( this, function( value ) { + var elem = this[ 0 ] || {}, + i = 0, + l = this.length; + + if ( value === undefined ) { + return elem.nodeType === 1 ? + elem.innerHTML.replace( rinlinejQuery, "" ) : + undefined; + } + + // See if we can take a shortcut and just use innerHTML + if ( typeof value === "string" && !rnoInnerhtml.test( value ) && + ( jQuery.support.htmlSerialize || !rnoshimcache.test( value ) ) && + ( jQuery.support.leadingWhitespace || !rleadingWhitespace.test( value ) ) && + !wrapMap[ ( rtagName.exec( value ) || [ "", "" ] )[ 1 ].toLowerCase() ] ) { + + value = value.replace( rxhtmlTag, "<$1>" ); + + try { + for ( ; i < l; i++ ) { + + // Remove element nodes and prevent memory leaks + elem = this[ i ] || {}; + if ( elem.nodeType === 1 ) { + jQuery.cleanData( getAll( elem, false ) ); + elem.innerHTML = value; + } + } + + elem = 0; + + // If using innerHTML throws an exception, use the fallback method + } catch ( e ) {} + } + + if ( elem ) { + this.empty().append( value ); + } + }, null, value, arguments.length ); + }, + + replaceWith: function( value ) { + var isFunc = jQuery.isFunction( value ); + + // Make sure that the elements are removed from the DOM before they are inserted + // this can help fix replacing a parent with child elements + if ( !isFunc && typeof value !== "string" ) { + value = jQuery( value ).not( this ).detach(); + } + + return this.domManip( [ value ], true, function( elem ) { + var next = this.nextSibling, + parent = this.parentNode; + + if ( parent ) { + jQuery( this ).remove(); + parent.insertBefore( elem, next ); + } + } ); + }, + + detach: function( selector ) { + return this.remove( selector, true ); + }, + + domManip: function( args, table, callback ) { + + // Flatten any nested arrays + args = core_concat.apply( [], args ); + + var first, node, hasScripts, + scripts, doc, fragment, + i = 0, + l = this.length, + set = this, + iNoClone = l - 1, + value = args[ 0 ], + isFunction = jQuery.isFunction( value ); + + // We can't cloneNode fragments that contain checked, in WebKit + if ( isFunction || !( l <= 1 || typeof value !== "string" || jQuery.support.checkClone || !rchecked.test( value ) ) ) { + return this.each( function( index ) { + var self = set.eq( index ); + if ( isFunction ) { + args[ 0 ] = value.call( this, index, table ? self.html() : undefined ); + } + self.domManip( args, table, callback ); + } ); + } + + if ( l ) { + fragment = jQuery.buildFragment( args, this[ 0 ].ownerDocument, false, this ); + first = fragment.firstChild; + + if ( fragment.childNodes.length === 1 ) { + fragment = first; + } + + if ( first ) { + table = table && jQuery.nodeName( first, "tr" ); + scripts = jQuery.map( getAll( fragment, "script" ), disableScript ); + hasScripts = scripts.length; + + // Use the original fragment for the last item instead of the first because it can end up + // being emptied incorrectly in certain situations (#8070). + for ( ; i < l; i++ ) { + node = fragment; + + if ( i !== iNoClone ) { + node = jQuery.clone( node, true, true ); + + // Keep references to cloned scripts for later restoration + if ( hasScripts ) { + jQuery.merge( scripts, getAll( node, "script" ) ); + } + } + + callback.call( + table && jQuery.nodeName( this[ i ], "table" ) ? + findOrAppend( this[ i ], "tbody" ) : + this[ i ], + node, + i + ); + } + + if ( hasScripts ) { + doc = scripts[ scripts.length - 1 ].ownerDocument; + + // Reenable scripts + jQuery.map( scripts, restoreScript ); + + // Evaluate executable scripts on first document insertion + for ( i = 0; i < hasScripts; i++ ) { + node = scripts[ i ]; + if ( rscriptType.test( node.type || "" ) && + !jQuery._data( node, "globalEval" ) && jQuery.contains( doc, node ) ) { + + if ( node.src ) { + + // Hope ajax is available... + jQuery.ajax( { + url: node.src, + type: "GET", + dataType: "script", + async: false, + global: false, + "throws": true + } ); + } else { + jQuery.globalEval( ( node.text || node.textContent || node.innerHTML || "" ).replace( rcleanScript, "" ) ); + } + } + } + } + + // Fix #11809: Avoid leaking memory + fragment = first = null; + } + } + + return this; + } +} ); + +function findOrAppend( elem, tag ) { + return elem.getElementsByTagName( tag )[ 0 ] || elem.appendChild( elem.ownerDocument.createElement( tag ) ); +} + +// Replace/restore the type attribute of script elements for safe DOM manipulation +function disableScript( elem ) { + var attr = elem.getAttributeNode( "type" ); + elem.type = ( attr && attr.specified ) + "/" + elem.type; + return elem; +} +function restoreScript( elem ) { + var match = rscriptTypeMasked.exec( elem.type ); + if ( match ) { + elem.type = match[ 1 ]; + } else { + elem.removeAttribute( "type" ); + } + return elem; +} + +// Mark scripts as having already been evaluated +function setGlobalEval( elems, refElements ) { + var elem, + i = 0; + for ( ; ( elem = elems[ i ] ) != null; i++ ) { + jQuery._data( elem, "globalEval", !refElements || jQuery._data( refElements[ i ], "globalEval" ) ); + } +} + +function cloneCopyEvent( src, dest ) { + + if ( dest.nodeType !== 1 || !jQuery.hasData( src ) ) { + return; + } + + var type, i, l, + oldData = jQuery._data( src ), + curData = jQuery._data( dest, oldData ), + events = oldData.events; + + if ( events ) { + delete curData.handle; + curData.events = {}; + + for ( type in events ) { + for ( i = 0, l = events[ type ].length; i < l; i++ ) { + jQuery.event.add( dest, type, events[ type ][ i ] ); + } + } + } + + // make the cloned public data object a copy from the original + if ( curData.data ) { + curData.data = jQuery.extend( {}, curData.data ); + } +} + +function fixCloneNodeIssues( src, dest ) { + var nodeName, e, data; + + // We do not need to do anything for non-Elements + if ( dest.nodeType !== 1 ) { + return; + } + + nodeName = dest.nodeName.toLowerCase(); + + // IE6-8 copies events bound via attachEvent when using cloneNode. + if ( !jQuery.support.noCloneEvent && dest[ jQuery.expando ] ) { + data = jQuery._data( dest ); + + for ( e in data.events ) { + jQuery.removeEvent( dest, e, data.handle ); + } + + // Event data gets referenced instead of copied if the expando gets copied too + dest.removeAttribute( jQuery.expando ); + } + + // IE blanks contents when cloning scripts, and tries to evaluate newly-set text + if ( nodeName === "script" && dest.text !== src.text ) { + disableScript( dest ).text = src.text; + restoreScript( dest ); + + // IE6-10 improperly clones children of object elements using classid. + // IE10 throws NoModificationAllowedError if parent is null, #12132. + } else if ( nodeName === "object" ) { + if ( dest.parentNode ) { + dest.outerHTML = src.outerHTML; + } + + // This path appears unavoidable for IE9. When cloning an object + // element in IE9, the outerHTML strategy above is not sufficient. + // If the src has innerHTML and the destination does not, + // copy the src.innerHTML into the dest.innerHTML. #10324 + if ( jQuery.support.html5Clone && ( src.innerHTML && !jQuery.trim( dest.innerHTML ) ) ) { + dest.innerHTML = src.innerHTML; + } + + } else if ( nodeName === "input" && manipulation_rcheckableType.test( src.type ) ) { + + // IE6-8 fails to persist the checked state of a cloned checkbox + // or radio button. Worse, IE6-7 fail to give the cloned element + // a checked appearance if the defaultChecked value isn't also set + + dest.defaultChecked = dest.checked = src.checked; + + // IE6-7 get confused and end up setting the value of a cloned + // checkbox/radio button to an empty string instead of "on" + if ( dest.value !== src.value ) { + dest.value = src.value; + } + + // IE6-8 fails to return the selected option to the default selected + // state when cloning options + } else if ( nodeName === "option" ) { + dest.defaultSelected = dest.selected = src.defaultSelected; + + // IE6-8 fails to set the defaultValue to the correct value when + // cloning other types of input fields + } else if ( nodeName === "input" || nodeName === "textarea" ) { + dest.defaultValue = src.defaultValue; + } +} + +jQuery.each( { + appendTo: "append", + prependTo: "prepend", + insertBefore: "before", + insertAfter: "after", + replaceAll: "replaceWith" +}, function( name, original ) { + jQuery.fn[ name ] = function( selector ) { + var elems, + i = 0, + ret = [], + insert = jQuery( selector ), + last = insert.length - 1; + + for ( ; i <= last; i++ ) { + elems = i === last ? this : this.clone( true ); + jQuery( insert[ i ] )[ original ]( elems ); + + // Modern browsers can apply jQuery collections as arrays, but oldIE needs a .get() + core_push.apply( ret, elems.get() ); + } + + return this.pushStack( ret ); + }; +} ); + +function getAll( context, tag ) { + var elems, elem, + i = 0, + found = typeof context.getElementsByTagName !== core_strundefined ? context.getElementsByTagName( tag || "*" ) : + typeof context.querySelectorAll !== core_strundefined ? context.querySelectorAll( tag || "*" ) : + undefined; + + if ( !found ) { + for ( found = [], elems = context.childNodes || context; ( elem = elems[ i ] ) != null; i++ ) { + if ( !tag || jQuery.nodeName( elem, tag ) ) { + found.push( elem ); + } else { + jQuery.merge( found, getAll( elem, tag ) ); + } + } + } + + return tag === undefined || tag && jQuery.nodeName( context, tag ) ? + jQuery.merge( [ context ], found ) : + found; +} + +// Used in buildFragment, fixes the defaultChecked property +function fixDefaultChecked( elem ) { + if ( manipulation_rcheckableType.test( elem.type ) ) { + elem.defaultChecked = elem.checked; + } +} + +jQuery.extend( { + clone: function( elem, dataAndEvents, deepDataAndEvents ) { + var destElements, node, clone, i, srcElements, + inPage = jQuery.contains( elem.ownerDocument, elem ); + + if ( jQuery.support.html5Clone || jQuery.isXMLDoc( elem ) || !rnoshimcache.test( "<" + elem.nodeName + ">" ) ) { + clone = elem.cloneNode( true ); + + // IE<=8 does not properly clone detached, unknown element nodes + } else { + fragmentDiv.innerHTML = elem.outerHTML; + fragmentDiv.removeChild( clone = fragmentDiv.firstChild ); + } + + if ( ( !jQuery.support.noCloneEvent || !jQuery.support.noCloneChecked ) && + ( elem.nodeType === 1 || elem.nodeType === 11 ) && !jQuery.isXMLDoc( elem ) ) { + + // We eschew Sizzle here for performance reasons: http://jsperf.com/getall-vs-sizzle/2 + destElements = getAll( clone ); + srcElements = getAll( elem ); + + // Fix all IE cloning issues + for ( i = 0; ( node = srcElements[ i ] ) != null; ++i ) { + + // Ensure that the destination node is not null; Fixes #9587 + if ( destElements[ i ] ) { + fixCloneNodeIssues( node, destElements[ i ] ); + } + } + } + + // Copy the events from the original to the clone + if ( dataAndEvents ) { + if ( deepDataAndEvents ) { + srcElements = srcElements || getAll( elem ); + destElements = destElements || getAll( clone ); + + for ( i = 0; ( node = srcElements[ i ] ) != null; i++ ) { + cloneCopyEvent( node, destElements[ i ] ); + } + } else { + cloneCopyEvent( elem, clone ); + } + } + + // Preserve script evaluation history + destElements = getAll( clone, "script" ); + if ( destElements.length > 0 ) { + setGlobalEval( destElements, !inPage && getAll( elem, "script" ) ); + } + + destElements = srcElements = node = null; + + // Return the cloned set + return clone; + }, + + buildFragment: function( elems, context, scripts, selection ) { + var j, elem, contains, + tmp, tag, tbody, wrap, + l = elems.length, + + // Ensure a safe fragment + safe = createSafeFragment( context ), + + nodes = [], + i = 0; + + for ( ; i < l; i++ ) { + elem = elems[ i ]; + + if ( elem || elem === 0 ) { + + // Add nodes directly + if ( jQuery.type( elem ) === "object" ) { + jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem ); + + // Convert non-html into a text node + } else if ( !rhtml.test( elem ) ) { + nodes.push( context.createTextNode( elem ) ); + + // Convert html into DOM nodes + } else { + tmp = tmp || safe.appendChild( context.createElement( "div" ) ); + + // Deserialize a standard representation + tag = ( rtagName.exec( elem ) || [ "", "" ] )[ 1 ].toLowerCase(); + wrap = wrapMap[ tag ] || wrapMap._default; + + tmp.innerHTML = wrap[ 1 ] + elem.replace( rxhtmlTag, "<$1>" ) + wrap[ 2 ]; + + // Descend through wrappers to the right content + j = wrap[ 0 ]; + while ( j-- ) { + tmp = tmp.lastChild; + } + + // Manually add leading whitespace removed by IE + if ( !jQuery.support.leadingWhitespace && rleadingWhitespace.test( elem ) ) { + nodes.push( context.createTextNode( rleadingWhitespace.exec( elem )[ 0 ] ) ); + } + + // Remove IE's autoinserted from table fragments + if ( !jQuery.support.tbody ) { + + // String was a , *may* have spurious + elem = tag === "table" && !rtbody.test( elem ) ? + tmp.firstChild : + + // String was a bare or + wrap[ 1 ] === "
" && !rtbody.test( elem ) ? + tmp : + 0; + + j = elem && elem.childNodes.length; + while ( j-- ) { + if ( jQuery.nodeName( ( tbody = elem.childNodes[ j ] ), "tbody" ) && !tbody.childNodes.length ) { + elem.removeChild( tbody ); + } + } + } + + jQuery.merge( nodes, tmp.childNodes ); + + // Fix #12392 for WebKit and IE > 9 + tmp.textContent = ""; + + // Fix #12392 for oldIE + while ( tmp.firstChild ) { + tmp.removeChild( tmp.firstChild ); + } + + // Remember the top-level container for proper cleanup + tmp = safe.lastChild; + } + } + } + + // Fix #11356: Clear elements from fragment + if ( tmp ) { + safe.removeChild( tmp ); + } + + // Reset defaultChecked for any radios and checkboxes + // about to be appended to the DOM in IE 6/7 (#8060) + if ( !jQuery.support.appendChecked ) { + jQuery.grep( getAll( nodes, "input" ), fixDefaultChecked ); + } + + i = 0; + while ( ( elem = nodes[ i++ ] ) ) { + + // #4087 - If origin and destination elements are the same, and this is + // that element, do not do anything + if ( selection && jQuery.inArray( elem, selection ) !== -1 ) { + continue; + } + + contains = jQuery.contains( elem.ownerDocument, elem ); + + // Append to fragment + tmp = getAll( safe.appendChild( elem ), "script" ); + + // Preserve script evaluation history + if ( contains ) { + setGlobalEval( tmp ); + } + + // Capture executables + if ( scripts ) { + j = 0; + while ( ( elem = tmp[ j++ ] ) ) { + if ( rscriptType.test( elem.type || "" ) ) { + scripts.push( elem ); + } + } + } + } + + tmp = null; + + return safe; + }, + + cleanData: function( elems, /* internal */ acceptData ) { + var elem, type, id, data, + i = 0, + internalKey = jQuery.expando, + cache = jQuery.cache, + deleteExpando = jQuery.support.deleteExpando, + special = jQuery.event.special; + + for ( ; ( elem = elems[ i ] ) != null; i++ ) { + + if ( acceptData || jQuery.acceptData( elem ) ) { + + id = elem[ internalKey ]; + data = id && cache[ id ]; + + if ( data ) { + if ( data.events ) { + for ( type in data.events ) { + if ( special[ type ] ) { + jQuery.event.remove( elem, type ); + + // This is a shortcut to avoid jQuery.event.remove's overhead + } else { + jQuery.removeEvent( elem, type, data.handle ); + } + } + } + + // Remove cache only if it was not already removed by jQuery.event.remove + if ( cache[ id ] ) { + + delete cache[ id ]; + + // IE does not allow us to delete expando properties from nodes, + // nor does it have a removeAttribute function on Document nodes; + // we must handle all of these cases + if ( deleteExpando ) { + delete elem[ internalKey ]; + + } else if ( typeof elem.removeAttribute !== core_strundefined ) { + elem.removeAttribute( internalKey ); + + } else { + elem[ internalKey ] = null; + } + + core_deletedIds.push( id ); + } + } + } + } + } +} ); +var iframe, getStyles, curCSS, + ralpha = /alpha\([^)]*\)/i, + ropacity = /opacity\s*=\s*([^)]*)/, + rposition = /^(top|right|bottom|left)$/, + + // swappable if display is none or starts with table except "table", "table-cell", or "table-caption" + // see here for display values: https://developer.mozilla.org/en-US/docs/CSS/display + rdisplayswap = /^(none|table(?!-c[ea]).+)/, + rmargin = /^margin/, + rnumsplit = new RegExp( "^(" + core_pnum + ")(.*)$", "i" ), + rnumnonpx = new RegExp( "^(" + core_pnum + ")(?!px)[a-z%]+$", "i" ), + rrelNum = new RegExp( "^([+-])=(" + core_pnum + ")", "i" ), + elemdisplay = { BODY: "block" }, + + cssShow = { position: "absolute", visibility: "hidden", display: "block" }, + cssNormalTransform = { + letterSpacing: 0, + fontWeight: 400 + }, + + cssExpand = [ "Top", "Right", "Bottom", "Left" ], + cssPrefixes = [ "Webkit", "O", "Moz", "ms" ]; + +// return a css property mapped to a potentially vendor prefixed property +function vendorPropName( style, name ) { + + // shortcut for names that are not vendor prefixed + if ( name in style ) { + return name; + } + + // check for vendor prefixed names + var capName = name.charAt( 0 ).toUpperCase() + name.slice( 1 ), + origName = name, + i = cssPrefixes.length; + + while ( i-- ) { + name = cssPrefixes[ i ] + capName; + if ( name in style ) { + return name; + } + } + + return origName; +} + +function isHidden( elem, el ) { + + // isHidden might be called from jQuery#filter function; + // in that case, element will be second argument + elem = el || elem; + return jQuery.css( elem, "display" ) === "none" || !jQuery.contains( elem.ownerDocument, elem ); +} + +function showHide( elements, show ) { + var display, elem, hidden, + values = [], + index = 0, + length = elements.length; + + for ( ; index < length; index++ ) { + elem = elements[ index ]; + if ( !elem.style ) { + continue; + } + + values[ index ] = jQuery._data( elem, "olddisplay" ); + display = elem.style.display; + if ( show ) { + + // Reset the inline display of this element to learn if it is + // being hidden by cascaded rules or not + if ( !values[ index ] && display === "none" ) { + elem.style.display = ""; + } + + // Set elements which have been overridden with display: none + // in a stylesheet to whatever the default browser style is + // for such an element + if ( elem.style.display === "" && isHidden( elem ) ) { + values[ index ] = jQuery._data( elem, "olddisplay", css_defaultDisplay( elem.nodeName ) ); + } + } else { + + if ( !values[ index ] ) { + hidden = isHidden( elem ); + + if ( display && display !== "none" || !hidden ) { + jQuery._data( elem, "olddisplay", hidden ? display : jQuery.css( elem, "display" ) ); + } + } + } + } + + // Set the display of most of the elements in a second loop + // to avoid the constant reflow + for ( index = 0; index < length; index++ ) { + elem = elements[ index ]; + if ( !elem.style ) { + continue; + } + if ( !show || elem.style.display === "none" || elem.style.display === "" ) { + elem.style.display = show ? values[ index ] || "" : "none"; + } + } + + return elements; +} + +jQuery.fn.extend( { + css: function( name, value ) { + return jQuery.access( this, function( elem, name, value ) { + var len, styles, + map = {}, + i = 0; + + if ( jQuery.isArray( name ) ) { + styles = getStyles( elem ); + len = name.length; + + for ( ; i < len; i++ ) { + map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles ); + } + + return map; + } + + return value !== undefined ? + jQuery.style( elem, name, value ) : + jQuery.css( elem, name ); + }, name, value, arguments.length > 1 ); + }, + show: function() { + return showHide( this, true ); + }, + hide: function() { + return showHide( this ); + }, + toggle: function( state ) { + var bool = typeof state === "boolean"; + + return this.each( function() { + if ( bool ? state : isHidden( this ) ) { + jQuery( this ).show(); + } else { + jQuery( this ).hide(); + } + } ); + } +} ); + +jQuery.extend( { + + // Add in style property hooks for overriding the default + // behavior of getting and setting a style property + cssHooks: { + opacity: { + get: function( elem, computed ) { + if ( computed ) { + + // We should always get a number back from opacity + var ret = curCSS( elem, "opacity" ); + return ret === "" ? "1" : ret; + } + } + } + }, + + // Exclude the following css properties to add px + cssNumber: { + "columnCount": true, + "fillOpacity": true, + "fontWeight": true, + "lineHeight": true, + "opacity": true, + "orphans": true, + "widows": true, + "zIndex": true, + "zoom": true + }, + + // Add in properties whose names you wish to fix before + // setting or getting the value + cssProps: { + + // normalize float css property + "float": jQuery.support.cssFloat ? "cssFloat" : "styleFloat" + }, + + // Get and set the style property on a DOM Node + style: function( elem, name, value, extra ) { + + // Don't set styles on text and comment nodes + if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) { + return; + } + + // Make sure that we're working with the right name + var ret, type, hooks, + origName = jQuery.camelCase( name ), + style = elem.style; + + name = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName( style, origName ) ); + + // gets hook for the prefixed version + // followed by the unprefixed version + hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; + + // Check if we're setting a value + if ( value !== undefined ) { + type = typeof value; + + // convert relative number strings (+= or -=) to relative numbers. #7345 + if ( type === "string" && ( ret = rrelNum.exec( value ) ) ) { + value = ( ret[ 1 ] + 1 ) * ret[ 2 ] + parseFloat( jQuery.css( elem, name ) ); + + // Fixes bug #9237 + type = "number"; + } + + // Make sure that NaN and null values aren't set. See: #7116 + if ( value == null || type === "number" && isNaN( value ) ) { + return; + } + + // If a number was passed in, add 'px' to the (except for certain CSS properties) + if ( type === "number" && !jQuery.cssNumber[ origName ] ) { + value += "px"; + } + + // Fixes #8908, it can be done more correctly by specifing setters in cssHooks, + // but it would mean to define eight (for every problematic property) identical functions + if ( !jQuery.support.clearCloneStyle && value === "" && name.indexOf( "background" ) === 0 ) { + style[ name ] = "inherit"; + } + + // If a hook was provided, use that value, otherwise just set the specified value + if ( !hooks || !( "set" in hooks ) || ( value = hooks.set( elem, value, extra ) ) !== undefined ) { + + // Wrapped to prevent IE from throwing errors when 'invalid' values are provided + // Fixes bug #5509 + try { + style[ name ] = value; + } catch ( e ) {} + } + + } else { + + // If a hook was provided get the non-computed value from there + if ( hooks && "get" in hooks && ( ret = hooks.get( elem, false, extra ) ) !== undefined ) { + return ret; + } + + // Otherwise just get the value from the style object + return style[ name ]; + } + }, + + css: function( elem, name, extra, styles ) { + var num, val, hooks, + origName = jQuery.camelCase( name ); + + // Make sure that we're working with the right name + name = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName( elem.style, origName ) ); + + // gets hook for the prefixed version + // followed by the unprefixed version + hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; + + // If a hook was provided get the computed value from there + if ( hooks && "get" in hooks ) { + val = hooks.get( elem, true, extra ); + } + + // Otherwise, if a way to get the computed value exists, use that + if ( val === undefined ) { + val = curCSS( elem, name, styles ); + } + + //convert "normal" to computed value + if ( val === "normal" && name in cssNormalTransform ) { + val = cssNormalTransform[ name ]; + } + + // Return, converting to number if forced or a qualifier was provided and val looks numeric + if ( extra === "" || extra ) { + num = parseFloat( val ); + return extra === true || jQuery.isNumeric( num ) ? num || 0 : val; + } + return val; + }, + + // A method for quickly swapping in/out CSS properties to get correct calculations + swap: function( elem, options, callback, args ) { + var ret, name, + old = {}; + + // Remember the old values, and insert the new ones + for ( name in options ) { + old[ name ] = elem.style[ name ]; + elem.style[ name ] = options[ name ]; + } + + ret = callback.apply( elem, args || [] ); + + // Revert the old values + for ( name in options ) { + elem.style[ name ] = old[ name ]; + } + + return ret; + } +} ); + +// NOTE: we've included the "window" in window.getComputedStyle +// because jsdom on node.js will break without it. +if ( window.getComputedStyle ) { + getStyles = function( elem ) { + return window.getComputedStyle( elem, null ); + }; + + curCSS = function( elem, name, _computed ) { + var width, minWidth, maxWidth, + computed = _computed || getStyles( elem ), + + // getPropertyValue is only needed for .css('filter') in IE9, see #12537 + ret = computed ? computed.getPropertyValue( name ) || computed[ name ] : undefined, + style = elem.style; + + if ( computed ) { + + if ( ret === "" && !jQuery.contains( elem.ownerDocument, elem ) ) { + ret = jQuery.style( elem, name ); + } + + // A tribute to the "awesome hack by Dean Edwards" + // Chrome < 17 and Safari 5.0 uses "computed value" instead of "used value" for margin-right + // Safari 5.1.7 (at least) returns percentage for a larger set of values, but width seems to be reliably pixels + // this is against the CSSOM draft spec: http://dev.w3.org/csswg/cssom/#resolved-values + if ( rnumnonpx.test( ret ) && rmargin.test( name ) ) { + + // Remember the original values + width = style.width; + minWidth = style.minWidth; + maxWidth = style.maxWidth; + + // Put in the new values to get a computed value out + style.minWidth = style.maxWidth = style.width = ret; + ret = computed.width; + + // Revert the changed values + style.width = width; + style.minWidth = minWidth; + style.maxWidth = maxWidth; + } + } + + return ret; + }; +} else if ( document.documentElement.currentStyle ) { + getStyles = function( elem ) { + return elem.currentStyle; + }; + + curCSS = function( elem, name, _computed ) { + var left, rs, rsLeft, + computed = _computed || getStyles( elem ), + ret = computed ? computed[ name ] : undefined, + style = elem.style; + + // Avoid setting ret to empty string here + // so we don't default to auto + if ( ret == null && style && style[ name ] ) { + ret = style[ name ]; + } + + // From the awesome hack by Dean Edwards + // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291 + + // If we're not dealing with a regular pixel number + // but a number that has a weird ending, we need to convert it to pixels + // but not position css attributes, as those are proportional to the parent element instead + // and we can't measure the parent instead because it might trigger a "stacking dolls" problem + if ( rnumnonpx.test( ret ) && !rposition.test( name ) ) { + + // Remember the original values + left = style.left; + rs = elem.runtimeStyle; + rsLeft = rs && rs.left; + + // Put in the new values to get a computed value out + if ( rsLeft ) { + rs.left = elem.currentStyle.left; + } + style.left = name === "fontSize" ? "1em" : ret; + ret = style.pixelLeft + "px"; + + // Revert the changed values + style.left = left; + if ( rsLeft ) { + rs.left = rsLeft; + } + } + + return ret === "" ? "auto" : ret; + }; +} + +function setPositiveNumber( elem, value, subtract ) { + var matches = rnumsplit.exec( value ); + return matches ? + + // Guard against undefined "subtract", e.g., when used as in cssHooks + Math.max( 0, matches[ 1 ] - ( subtract || 0 ) ) + ( matches[ 2 ] || "px" ) : + value; +} + +function augmentWidthOrHeight( elem, name, extra, isBorderBox, styles ) { + var i = extra === ( isBorderBox ? "border" : "content" ) ? + + // If we already have the right measurement, avoid augmentation + 4 : + + // Otherwise initialize for horizontal or vertical properties + name === "width" ? 1 : 0, + + val = 0; + + for ( ; i < 4; i += 2 ) { + + // both box models exclude margin, so add it if we want it + if ( extra === "margin" ) { + val += jQuery.css( elem, extra + cssExpand[ i ], true, styles ); + } + + if ( isBorderBox ) { + + // border-box includes padding, so remove it if we want content + if ( extra === "content" ) { + val -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); + } + + // at this point, extra isn't border nor margin, so remove border + if ( extra !== "margin" ) { + val -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + } + } else { + + // at this point, extra isn't content, so add padding + val += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); + + // at this point, extra isn't content nor padding, so add border + if ( extra !== "padding" ) { + val += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + } + } + } + + return val; +} + +function getWidthOrHeight( elem, name, extra ) { + + // Start with offset property, which is equivalent to the border-box value + var valueIsBorderBox = true, + val = name === "width" ? elem.offsetWidth : elem.offsetHeight, + styles = getStyles( elem ), + isBorderBox = jQuery.support.boxSizing && jQuery.css( elem, "boxSizing", false, styles ) === "border-box"; + + // some non-html elements return undefined for offsetWidth, so check for null/undefined + // svg - https://bugzilla.mozilla.org/show_bug.cgi?id=649285 + // MathML - https://bugzilla.mozilla.org/show_bug.cgi?id=491668 + if ( val <= 0 || val == null ) { + + // Fall back to computed then uncomputed css if necessary + val = curCSS( elem, name, styles ); + if ( val < 0 || val == null ) { + val = elem.style[ name ]; + } + + // Computed unit is not pixels. Stop here and return. + if ( rnumnonpx.test( val ) ) { + return val; + } + + // we need the check for style in case a browser which returns unreliable values + // for getComputedStyle silently falls back to the reliable elem.style + valueIsBorderBox = isBorderBox && ( jQuery.support.boxSizingReliable || val === elem.style[ name ] ); + + // Normalize "", auto, and prepare for extra + val = parseFloat( val ) || 0; + } + + // use the active box-sizing model to add/subtract irrelevant styles + return ( val + + augmentWidthOrHeight( + elem, + name, + extra || ( isBorderBox ? "border" : "content" ), + valueIsBorderBox, + styles + ) + ) + "px"; +} + +// Try to determine the default display value of an element +function css_defaultDisplay( nodeName ) { + var doc = document, + display = elemdisplay[ nodeName ]; + + if ( !display ) { + display = actualDisplay( nodeName, doc ); + + // If the simple way fails, read from inside an iframe + if ( display === "none" || !display ) { + + // Use the already-created iframe if possible + iframe = ( iframe || + jQuery( " + + + + +
+ +
+ + + + +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ +
+
hi there
+
+
+
+
+
+
+
+
+ +
+
    +
  1. Rice
  2. +
  3. Beans
  4. +
  5. Blinis
  6. +
  7. Tofu
  8. +
+ +
I'm hungry. I should...
+ ...Eat lots of food... | + ...Eat a little food... | + ...Eat no food... + ...Eat a burger... + ...Eat some funyuns... + ...Eat some funyuns... + + + + + + +
+ +
+ + +
+ +
+ 1 + 2 + + + + + + + + +
+
+
+
fadeIn
fadeIn
+
fadeOut
fadeOut
+ +
show
show
+
hide
hide
+
hide
hide
+ +
togglein
togglein
+
toggleout
toggleout
+
toggleout
toggleout
+ +
slideUp
slideUp
+
slideDown
slideDown
+
slideUp
slideUp
+ +
slideToggleIn
slideToggleIn
+
slideToggleOut
slideToggleOut
+ +
fadeToggleIn
fadeToggleIn
+
fadeToggleOut
fadeToggleOut
+ +
fadeTo
fadeTo
+
+ +
+ +
diff --git a/test/data/readywait.html b/test/data/readywait.html new file mode 100644 index 0000000..d7de0b0 --- /dev/null +++ b/test/data/readywait.html @@ -0,0 +1,76 @@ + + + + + + jQuery.holdReady Test + + + + + + + + + + +

+ jQuery.holdReady Test +

+

+ This is a test page for jQuery.readyWait and jQuery.holdReady, + see + #6781 + and + #8803. +

+

+ Test for jQuery.holdReady, which can be used + by plugins and other scripts to indicate something + important to the page is still loading and needs + to block the DOM ready callbacks that are registered + with jQuery. +

+

+ Script loaders are the most likely kind of script + to use jQuery.holdReady, but it could be used by + other things like a script that loads a CSS file + and wants to pause the DOM ready callbacks. +

+

+ Expected Result: The text + It Worked! + appears below after about 2 seconds. +

+

+ If there is an error in the console, + or the text does not show up, then the test failed. +

+
+ + diff --git a/test/data/selector/html5_selector.html b/test/data/selector/html5_selector.html new file mode 100644 index 0000000..35c583c --- /dev/null +++ b/test/data/selector/html5_selector.html @@ -0,0 +1,116 @@ + + + + + jQuery selector - attributes + + + + + + + + + + +
+ +
+ + +
+ + + +
+ + + + + +
    + +
    + +
    + + + + + + + + + + + + + + + +
    +
    Term
    This is the first definition in compact format.
    +
    Term
    This is the second definition in compact format.
    +
    + + + + Scrolling text (non-standard) + + diff --git a/test/data/selector/sizzle_cache.html b/test/data/selector/sizzle_cache.html new file mode 100644 index 0000000..513390c --- /dev/null +++ b/test/data/selector/sizzle_cache.html @@ -0,0 +1,25 @@ + + + + + jQuery selector - sizzle cache + + + + + + + + + + + diff --git a/test/data/support/bodyBackground.html b/test/data/support/bodyBackground.html new file mode 100644 index 0000000..d95f39d --- /dev/null +++ b/test/data/support/bodyBackground.html @@ -0,0 +1,29 @@ + + + + + + + +
    + + + +
    + + + diff --git a/test/data/support/csp.js b/test/data/support/csp.js new file mode 100644 index 0000000..4d55bda --- /dev/null +++ b/test/data/support/csp.js @@ -0,0 +1,3 @@ +jQuery( function() { + startIframeTest( getComputedSupport( jQuery.support ) ); +} ); diff --git a/test/data/support/csp.log b/test/data/support/csp.log new file mode 100755 index 0000000..e69de29 diff --git a/test/data/support/getComputedSupport.js b/test/data/support/getComputedSupport.js new file mode 100644 index 0000000..ce3f118 --- /dev/null +++ b/test/data/support/getComputedSupport.js @@ -0,0 +1,14 @@ +function getComputedSupport( support ) { + var prop, + result = {}; + + for ( prop in support ) { + if ( typeof support[ prop ] === "function" ) { + result[ prop ] = support[ prop ](); + } else { + result[ prop ] = support[ prop ]; + } + } + + return result; +} diff --git a/test/data/test.include.html b/test/data/test.include.html new file mode 100644 index 0000000..ea75074 --- /dev/null +++ b/test/data/test.include.html @@ -0,0 +1,7 @@ +html text
    + + +blabla diff --git a/test/data/test2.html b/test/data/test2.html new file mode 100644 index 0000000..c7fff22 --- /dev/null +++ b/test/data/test2.html @@ -0,0 +1,5 @@ + diff --git a/test/data/test3.html b/test/data/test3.html new file mode 100644 index 0000000..446d5c1 --- /dev/null +++ b/test/data/test3.html @@ -0,0 +1,5 @@ +
    This is a user
    +
    This is a user
    +
    This is a teacher
    +
    This is a superuser
    +
    This is a superuser with non-HTML whitespace
    diff --git a/test/data/testinit-jsdom.js b/test/data/testinit-jsdom.js new file mode 100644 index 0000000..bedf093 --- /dev/null +++ b/test/data/testinit-jsdom.js @@ -0,0 +1,54 @@ +"use strict"; + +// Support: jsdom 13.2+ +// jsdom implements a throwing `window.scrollTo`. +QUnit.config.scrolltop = false; + +const FILEPATH = "/test/data/testinit-jsdom.js"; +const activeScript = document.currentScript; +const parentUrl = activeScript && activeScript.src ? + activeScript.src.replace( /[?#].*/, "" ) + FILEPATH.replace( /[^/]+/g, ".." ) + "/" : + "../"; +const supportjQuery = this.jQuery; + +// baseURL is intentionally set to "data/" instead of "". +// This is not just for convenience (since most files are in data/) +// but also to ensure that urls without prefix fail. +// Otherwise it's easy to write tests that pass on test/index.html +// but fail in Karma runner (where the baseURL is different). +const baseURL = parentUrl + "test/data/"; + +// Setup global variables before loading jQuery for testing .noConflict() +supportjQuery.noConflict( true ); +window.originaljQuery = this.jQuery = undefined; +window.original$ = this.$ = "replaced"; + +/** + * Add random number to url to stop caching + * + * Also prefixes with baseURL automatically. + * + * @example url("index.html") + * @result "data/index.html?10538358428943" + * + * @example url("mock.php?foo=bar") + * @result "data/mock.php?foo=bar&10538358345554" + */ +function url( value ) { + return baseURL + value + ( /\?/.test( value ) ? "&" : "?" ) + + new Date().getTime() + "" + parseInt( Math.random() * 100000, 10 ); +} + +// The file-loading part of testinit.js#loadTests is handled by +// jsdom Karma config; here we just need to trigger relevant APIs. +this.loadTests = function() { + + // Delay the initialization until after all the files are loaded + // as in the JSDOM case we load them via Karma (see Gruntfile.js) + // instead of directly in testinit.js. + window.addEventListener( "load", function() { + window.__karma__.start(); + jQuery.noConflict(); + QUnit.start(); + } ); +}; diff --git a/test/data/testinit.js b/test/data/testinit.js new file mode 100644 index 0000000..b27ac4c --- /dev/null +++ b/test/data/testinit.js @@ -0,0 +1,400 @@ +/* eslint no-multi-str: "off" */ + +var FILEPATH = "/test/data/testinit.js", + activeScript = [].slice.call( document.getElementsByTagName( "script" ), -1 )[ 0 ], + parentUrl = activeScript && activeScript.src ? + activeScript.src.replace( /[?#].*/, "" ) + FILEPATH.replace( /[^/]+/g, ".." ) + "/" : + "../", + + // baseURL is intentionally set to "data/" instead of "". + // This is not just for convenience (since most files are in data/) + // but also to ensure that urls without prefix fail. + // Otherwise it's easy to write tests that pass on test/index.html + // but fail in Karma runner (where the baseURL is different). + baseURL = parentUrl + "test/data/", + supportjQuery = this.jQuery, + + // see RFC 2606 + externalHost = "example.com"; + +this.hasPHP = true; +this.isLocal = window.location.protocol === "file:"; + +// Setup global variables before loading jQuery for testing .noConflict() +supportjQuery.noConflict( true ); +window.originaljQuery = this.jQuery = undefined; +window.original$ = this.$ = "replaced"; + +/** + * Returns an array of elements with the given IDs + * @example q( "main", "foo", "bar" ) + * @result [
    , , ] + */ +this.q = function() { + var r = [], + i = 0; + + for ( ; i < arguments.length; i++ ) { + r.push( document.getElementById( arguments[ i ] ) ); + } + return r; +}; + +/** + * Asserts that a select matches the given IDs + * @param {String} message - Assertion name + * @param {String} selector - Sizzle selector + * @param {String} expectedIds - Array of ids to construct what is expected + * @param {(String|Node)=document} context - Selector context + * @example match("Check for something", "p", ["foo", "bar"]); + */ +function match( message, selector, expectedIds, context, assert ) { + var f = jQuery( selector, context ).get(), + s = "", + i = 0; + + for ( ; i < f.length; i++ ) { + s += ( s && "," ) + "\"" + f[ i ].id + "\""; + } + + assert.deepEqual( f, q.apply( q, expectedIds ), message + " (" + selector + ")" ); +} + +/** + * Asserts that a select matches the given IDs. + * The select is not bound by a context. + * @param {String} message - Assertion name + * @param {String} selector - Sizzle selector + * @param {String} expectedIds - Array of ids to construct what is expected + * @example t("Check for something", "p", ["foo", "bar"]); + */ +QUnit.assert.t = function( message, selector, expectedIds ) { + match( message, selector, expectedIds, undefined, QUnit.assert ); +}; + +/** + * Asserts that a select matches the given IDs. + * The select is performed within the `#qunit-fixture` context. + * @param {String} message - Assertion name + * @param {String} selector - Sizzle selector + * @param {String} expectedIds - Array of ids to construct what is expected + * @example selectInFixture("Check for something", "p", ["foo", "bar"]); + */ +QUnit.assert.selectInFixture = function( message, selector, expectedIds ) { + match( message, selector, expectedIds, "#qunit-fixture", QUnit.assert ); +}; + +this.createDashboardXML = function() { + var string = " \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + "; + + return jQuery.parseXML( string ); +}; + +this.createWithFriesXML = function() { + var string = " \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + 1 \ + \ + \ + \ + \ + foo \ + \ + \ + \ + \ + \ + \ + "; + + return jQuery.parseXML( string.replace( /\{\{\s*externalHost\s*\}\}/g, externalHost ) ); +}; + +this.createXMLFragment = function() { + var xml, frag; + if ( window.ActiveXObject ) { + xml = new window.ActiveXObject( "msxml2.domdocument" ); + } else { + xml = document.implementation.createDocument( "", "", null ); + } + + if ( xml ) { + frag = xml.createElement( "data" ); + } + + return frag; +}; + +window.fireNative = document.createEvent ? + function( node, type ) { + var event = document.createEvent( "HTMLEvents" ); + + event.initEvent( type, true, true ); + node.dispatchEvent( event ); + } : + function( node, type ) { + node.fireEvent( "on" + type, document.createEventObject() ); + }; + +/** + * Add random number to url to stop caching + * + * Also prefixes with baseURL automatically. + * + * @example url("index.html") + * @result "data/index.html?10538358428943" + * + * @example url("mock.php?foo=bar") + * @result "data/mock.php?foo=bar&10538358345554" + */ +function url( value ) { + return baseURL + value + ( /\?/.test( value ) ? "&" : "?" ) + + new Date().getTime() + "" + parseInt( Math.random() * 100000, 10 ); +} + +// Ajax testing helper +this.ajaxTest = function( title, expect, options ) { + QUnit.test( title, function( assert ) { + assert.expect( expect ); + var requestOptions; + + if ( typeof options === "function" ) { + options = options( assert ); + } + options = options || []; + requestOptions = options.requests || options.request || options; + if ( !Array.isArray( requestOptions ) ) { + requestOptions = [ requestOptions ]; + } + + var done = assert.async(); + + if ( options.setup ) { + options.setup(); + } + + var completed = false, + remaining = requestOptions.length, + complete = function() { + if ( !completed && --remaining === 0 ) { + completed = true; + delete ajaxTest.abort; + if ( options.teardown ) { + options.teardown(); + } + + // Make sure all events will be called before done() + setTimeout( done ); + } + }, + requests = jQuery.map( requestOptions, function( options ) { + var request = ( options.create || jQuery.ajax )( options ), + callIfDefined = function( deferType, optionType ) { + var handler = options[ deferType ] || !!options[ optionType ]; + return function( _, status ) { + if ( !completed ) { + if ( !handler ) { + assert.ok( false, "unexpected " + status ); + } else if ( typeof handler === "function" ) { + handler.apply( this, arguments ); + } + } + }; + }; + + if ( options.afterSend ) { + options.afterSend( request, assert ); + } + + return request + .done( callIfDefined( "done", "success" ) ) + .fail( callIfDefined( "fail", "error" ) ) + .always( complete ); + } ); + + ajaxTest.abort = function( reason ) { + if ( !completed ) { + completed = true; + delete ajaxTest.abort; + assert.ok( false, "aborted " + reason ); + jQuery.each( requests, function( i, request ) { + request.abort(); + } ); + } + }; + } ); +}; + +this.testIframe = function( title, fileName, func, wrapper ) { + if ( !wrapper ) { + wrapper = QUnit.test; + } + wrapper.call( QUnit, title, function( assert ) { + var done = assert.async(), + $iframe = supportjQuery( "" ) + .css( { position: "absolute", top: "0", left: "-600px", width: "500px" } ) + .attr( { id: "qunit-fixture-iframe", src: url( fileName ) } ); + + // Test iframes are expected to invoke this via startIframeTest (cf. iframeTest.js) + window.iframeCallback = function() { + var args = Array.prototype.slice.call( arguments ); + + args.unshift( assert ); + + setTimeout( function() { + var result; + + this.iframeCallback = undefined; + + result = func.apply( this, args ); + + function finish() { + func = function() {}; + $iframe.remove(); + done(); + } + + // Wait for promises returned by `func`. + if ( result && result.then ) { + result.then( finish ); + } else { + finish(); + } + } ); + }; + + // Attach iframe to the body for visibility-dependent code + // It will be removed by either the above code, or the testDone callback in testrunner.js + $iframe.prependTo( document.body ); + } ); +}; +this.iframeCallback = undefined; + +// Tests are always loaded async +// except when running tests in Karma (See Gruntfile) +if ( !window.__karma__ ) { + QUnit.config.autostart = false; +} + +// Leverage QUnit URL parsing to detect testSwarm environment and "basic" testing mode +QUnit.isSwarm = ( QUnit.urlParams.swarmURL + "" ).indexOf( "http" ) === 0; +QUnit.basicTests = ( QUnit.urlParams.module + "" ) === "basic"; + +// Async test for module script type support +function moduleTypeSupported() { + var script = document.createElement( "script" ); + script.type = "module"; + script.text = "QUnit.moduleTypeSupported = true"; + document.head.appendChild( script ).parentNode.removeChild( script ); +} +moduleTypeSupported(); + +this.loadTests = function() { + + // QUnit.config is populated from QUnit.urlParams but only at the beginning + // of the test run. We need to read both. + var amd = QUnit.config.amd || QUnit.urlParams.amd; + + // Directly load tests that need evaluation before DOMContentLoaded. + if ( !amd || document.readyState === "loading" ) { + document.write( " + + + +

    Delegate Tests (x)

    + + + + + + + + + + + + + + + + + +
    + Controls: + + + + + + + +
    + +
    + + +
    + +
    + +
    + + +
    + + + + + + + +
    +
    +
    +
    +
    +

    NOTE: Only IE supports propertychange, beforeactivate, beforedeactivate; buttons do not support change events.

    + +

    Submit Tests

    + + + + + + + + + + + + + + + +
    + Submit each: + +
    + +
    +
    +
    + +
    +
    +
    + +
    +
    $(document).bind('submit')
    Results:TEXTPASSWORDBUTTONDOCUMENT
    + +
    + + + + diff --git a/test/hovertest.html b/test/hovertest.html new file mode 100644 index 0000000..8e45784 --- /dev/null +++ b/test/hovertest.html @@ -0,0 +1,158 @@ + + +Hover tests + + + + +

    Hover (mouse{over,out,enter,leave}) Tests

    +

    Be sure to try moving the mouse out of the browser via the left side on each test.

    +
    + +
    +
    + + .hover() in/out: 0 / 0 +
    +
    + Mouse over here should NOT trigger the counter. +
    +
    +
    +
    + + Live enter/leave: 0 / 0 +
    +
    + Mouse over here should NOT trigger the counter. +
    +
    +
    +
    + + Delegated enter/leave: 0 / 0 +
    +
    + Mouse over here should NOT trigger the counter. +
    +
    + +
    +
    + + Bind over/out: 0 / 0 +
    +
    + Mouse over here SHOULD trigger the counter. +
    +
    +
    +
    + + Live over/out: 0 / 0 +
    +
    + Mouse over here SHOULD trigger the counter. +
    +
    +
    +
    + + Delegated over/out: 0 / 0 +
    +
    + Mouse over here SHOULD trigger the counter. +
    +
    + +
    + + + + diff --git a/test/index.html b/test/index.html new file mode 100644 index 0000000..3c4ba58 --- /dev/null +++ b/test/index.html @@ -0,0 +1,54 @@ + + + + + jQuery Test Suite + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + +
    + + + diff --git a/test/integration/data/gh-1764-fullscreen-iframe.css b/test/integration/data/gh-1764-fullscreen-iframe.css new file mode 100644 index 0000000..ba4b4fa --- /dev/null +++ b/test/integration/data/gh-1764-fullscreen-iframe.css @@ -0,0 +1,18 @@ +.result { + font-size: 24px; + margin: 0.5em 0; + width: 700px; + height: 56px; +} + +.error { + background-color: red; +} + +.warn { + background-color: yellow; +} + +.success { + background-color: lightgreen; +} diff --git a/test/integration/data/gh-1764-fullscreen-iframe.html b/test/integration/data/gh-1764-fullscreen-iframe.html new file mode 100644 index 0000000..bed56b8 --- /dev/null +++ b/test/integration/data/gh-1764-fullscreen-iframe.html @@ -0,0 +1,21 @@ + + + + + + Test for gh-1764 - test iframe + + + + +
    +
    + +
    + + + + + diff --git a/test/integration/data/gh-1764-fullscreen.js b/test/integration/data/gh-1764-fullscreen.js new file mode 100644 index 0000000..b2bb4cd --- /dev/null +++ b/test/integration/data/gh-1764-fullscreen.js @@ -0,0 +1,99 @@ +/* exported bootstrapFrom */ + +// `mode` may be "iframe" or not specified. +function bootstrapFrom( mainSelector, mode ) { + if ( mode === "iframe" && window.parent === window ) { + jQuery( mainSelector + " .result" ) + .attr( "class", "result warn" ) + .text( "This test should be run in an iframe. Open ../gh-1764-fullscreen.html." ); + jQuery( mainSelector + " .toggle-fullscreen" ).remove(); + return; + } + + var fullscreenSupported = document.exitFullscreen || + document.exitFullscreen || + document.msExitFullscreen || + document.mozCancelFullScreen || + document.webkitExitFullscreen; + + function isFullscreen() { + return !!( document.fullscreenElement || + document.mozFullScreenElement || + document.webkitFullscreenElement || + document.msFullscreenElement ); + } + + function requestFullscreen( element ) { + if ( !isFullscreen() ) { + if ( element.requestFullscreen ) { + element.requestFullscreen(); + } else if ( element.msRequestFullscreen ) { + element.msRequestFullscreen(); + } else if ( element.mozRequestFullScreen ) { + element.mozRequestFullScreen(); + } else if ( element.webkitRequestFullscreen ) { + element.webkitRequestFullscreen(); + } + } + } + + function exitFullscreen() { + if ( document.exitFullscreen ) { + document.exitFullscreen(); + } else if ( document.msExitFullscreen ) { + document.msExitFullscreen(); + } else if ( document.mozCancelFullScreen ) { + document.mozCancelFullScreen(); + } else if ( document.webkitExitFullscreen ) { + document.webkitExitFullscreen(); + } + } + + function runTest() { + var dimensions; + if ( !fullscreenSupported ) { + jQuery( mainSelector + " .result" ) + .attr( "class", "result success" ) + .text( "Fullscreen mode is not supported in this browser. Test not run." ); + } else if ( !isFullscreen() ) { + jQuery( mainSelector + " .result" ) + .attr( "class", "result warn" ) + .text( "Enable fullscreen mode to fire the test." ); + } else { + dimensions = jQuery( mainSelector + " .result" ).css( [ "width", "height" ] ); + dimensions.width = parseFloat( dimensions.width ).toFixed( 3 ); + dimensions.height = parseFloat( dimensions.height ).toFixed( 3 ); + if ( dimensions.width === "700.000" && dimensions.height === "56.000" ) { + jQuery( mainSelector + " .result" ) + .attr( "class", "result success" ) + .text( "Dimensions in fullscreen mode are computed correctly." ); + } else { + jQuery( mainSelector + " .result" ) + .attr( "class", "result error" ) + .html( "Incorrect dimensions; " + + "expected: { width: '700.000', height: '56.000' };
    " + + "got: { width: '" + dimensions.width + "', height: '" + + dimensions.height + "' }." ); + } + } + } + + function toggleFullscreen() { + if ( isFullscreen() ) { + exitFullscreen(); + } else { + requestFullscreen( jQuery( mainSelector + " .container" )[ 0 ] ); + } + } + + $( mainSelector + " .toggle-fullscreen" ).on( "click", toggleFullscreen ); + + $( document ).on( [ + "webkitfullscreenchange", + "mozfullscreenchange", + "fullscreenchange", + "MSFullscreenChange" + ].join( " " ), runTest ); + + runTest(); +} diff --git a/test/integration/gh-1764-fullscreen.html b/test/integration/gh-1764-fullscreen.html new file mode 100644 index 0000000..1cba659 --- /dev/null +++ b/test/integration/gh-1764-fullscreen.html @@ -0,0 +1,34 @@ + + + + + + Test for gh-1764 + + + + + +
    +
    + +
    + + + + + + diff --git a/test/integration/gh-2343-ie-radio-click.html b/test/integration/gh-2343-ie-radio-click.html new file mode 100644 index 0000000..4bc6956 --- /dev/null +++ b/test/integration/gh-2343-ie-radio-click.html @@ -0,0 +1,33 @@ + + + + + + Test for gh-2343 (IE11) + + + + + + +

    Test for gh-2343 (IE11)

    +

    +Instructions: In IE11, click on or focus the first radio button. +Then use the left/right arrow keys to select the other radios. +You should see events logged in the results below. +

    +
    + 0 + 1 + 2 +
    +
    + + + diff --git a/test/jquery.js b/test/jquery.js new file mode 100644 index 0000000..bb4ae6f --- /dev/null +++ b/test/jquery.js @@ -0,0 +1,73 @@ +// Use the right jQuery source on the test page (and iframes) +( function() { + /* global loadTests: false */ + + var config, src, + FILEPATH = "/test/jquery.js", + activeScript = [].slice.call( document.getElementsByTagName( "script" ), -1 )[ 0 ], + parentUrl = activeScript && activeScript.src ? + activeScript.src.replace( /[?#].*/, "" ) + FILEPATH.replace( /[^/]+/g, ".." ) + "/" : + "../", + QUnit = window.QUnit, + require = window.require; + + function getQUnitConfig() { + var config = Object.create( null ); + + // Default to unminified jQuery for directly-opened iframes + if ( !QUnit ) { + config.dev = true; + } else { + + // QUnit.config is populated from QUnit.urlParams but only at the beginning + // of the test run. We need to read both. + QUnit.config.urlConfig.forEach( function( entry ) { + config[ entry.id ] = QUnit.config[ entry.id ] != null ? + QUnit.config[ entry.id ] : + QUnit.urlParams[ entry.id ]; + } ); + } + + return config; + } + + // Define configuration parameters controlling how jQuery is loaded + if ( QUnit ) { + QUnit.config.urlConfig.push( { + id: "amd", + label: "Load with AMD", + tooltip: "Load the AMD jQuery file (and its dependencies)" + }, { + id: "dev", + label: "Load unminified", + tooltip: "Load the development (unminified) jQuery file" + } ); + } + + config = getQUnitConfig(); + + src = config.dev ? + "dist/jquery.js" : + "dist/jquery.min.js"; + + // Honor AMD loading on the main window (detected by seeing QUnit on it). + // This doesn't apply to iframes because they synchronously expect jQuery to be there. + if ( config.amd && QUnit ) { + require.config( { + baseUrl: parentUrl + } ); + src = "src/jquery"; + + // Include tests if specified + if ( typeof loadTests !== "undefined" ) { + require( [ src ], loadTests ); + } else { + require( [ src ] ); + } + + // Otherwise, load synchronously + } else { + document.write( " + + %SCRIPTS% + + + + + + diff --git a/test/karma.debug.html b/test/karma.debug.html new file mode 100644 index 0000000..39acd9c --- /dev/null +++ b/test/karma.debug.html @@ -0,0 +1,45 @@ + + + +%X_UA_COMPATIBLE% + DEBUG + + + + + + +
    + + + + +
    + + + + + + + %SCRIPTS% + + + + + + diff --git a/test/localfile.html b/test/localfile.html new file mode 100644 index 0000000..5a79bfe --- /dev/null +++ b/test/localfile.html @@ -0,0 +1,75 @@ + + + + + jQuery Local File Test + + + + + +

    jQuery Local File Test

    +

    + Introduction +

    +
      +
    • + Access this file using the "file:" protocol, +
    • +
    • + two green "OK" strings must appear below, +
    • +
    • + Empty local files will issue errors, it's a known limitation. +
    • +
    +

    + Results +

    +
      +
    • + Success: + + +
    • +
    • + Error: + + +
    • +
    +

    + Logs: +

    +
      +
    + + diff --git a/test/middleware-mockserver.js b/test/middleware-mockserver.js new file mode 100644 index 0000000..36216ec --- /dev/null +++ b/test/middleware-mockserver.js @@ -0,0 +1,310 @@ +/* eslint-env node */ +var url = require( "url" ); +var fs = require( "fs" ); +var getRawBody = require( "raw-body" ); + +var cspLog = ""; +/** + * Keep in sync with /test/mock.php + */ +var mocks = { + contentType: function( req, resp ) { + resp.writeHead( 200, { + "content-type": req.query.contentType + } ); + resp.end( req.query.response ); + }, + wait: function( req, resp ) { + var wait = Number( req.query.wait ) * 1000; + setTimeout( function() { + if ( req.query.script ) { + resp.writeHead( 200, { "content-type": "text/javascript" } ); + } else { + resp.writeHead( 200, { "content-type": "text/html" } ); + resp.end( "ERROR " ); + } + }, wait ); + }, + name: function( req, resp, next ) { + resp.writeHead( 200 ); + if ( req.query.name === "foo" ) { + resp.end( "bar" ); + return; + } + getBody( req ).then( function( body ) { + if ( body === "name=peter" ) { + resp.end( "pan" ); + } else { + resp.end( "ERROR" ); + } + }, next ); + }, + xml: function( req, resp, next ) { + var content = "5-23"; + resp.writeHead( 200, { "content-type": "text/xml" } ); + + if ( req.query.cal === "5-2" ) { + resp.end( content ); + return; + } + getBody( req ).then( function( body ) { + if ( body === "cal=5-2" ) { + resp.end( content ); + } else { + resp.end( "ERROR" ); + } + }, next ); + }, + atom: function( req, resp, next ) { + resp.writeHead( 200, { "content-type": "atom+xml" } ); + resp.end( "" ); + }, + script: function( req, resp ) { + if ( req.query.header === "ecma" ) { + resp.writeHead( 200, { "content-type": "application/ecmascript" } ); + } else if ( "header" in req.query ) { + resp.writeHead( 200, { "content-type": "text/javascript" } ); + } else { + resp.writeHead( 200, { "content-type": "text/html" } ); + } + resp.end( "QUnit.assert.ok( true, \"mock executed\" );" ); + }, + testbar: function( req, resp ) { + resp.writeHead( 200 ); + resp.end( + "this.testBar = 'bar'; " + + "jQuery('#ap').html('bar'); " + + "QUnit.assert.ok( true, 'mock executed');" + ); + }, + json: function( req, resp ) { + if ( req.query.header ) { + resp.writeHead( 200, { "content-type": "application/json" } ); + } + if ( req.query.array ) { + resp.end( JSON.stringify( + [ { name: "John", age: 21 }, { name: "Peter", age: 25 } ] + ) ); + } else { + resp.end( JSON.stringify( + { data: { lang: "en", length: 25 } } + ) ); + } + }, + jsonp: function( req, resp, next ) { + var callback; + if ( Array.isArray( req.query.callback ) ) { + callback = Promise.resolve( req.query.callback[ req.query.callback.length - 1 ] ); + } else if ( req.query.callback ) { + callback = Promise.resolve( req.query.callback ); + } else if ( req.method === "GET" ) { + callback = Promise.resolve( req.url.match( /^.+\/([^\/?]+)\?.+$/ )[ 1 ] ); + } else { + callback = getBody( req ).then( function( body ) { + return body.trim().replace( "callback=", "" ); + } ); + } + var json = req.query.array ? + JSON.stringify( + [ { name: "John", age: 21 }, { name: "Peter", age: 25 } ] + ) : + JSON.stringify( + { data: { lang: "en", length: 25 } } + ); + callback.then( function( cb ) { + resp.end( cb + "(" + json + ")" ); + }, next ); + }, + xmlOverJsonp: function( req, resp ) { + var callback = req.query.callback; + var body = fs.readFileSync( __dirname + "/data/with_fries.xml" ).toString(); + resp.writeHead( 200 ); + resp.end( callback + "(" + JSON.stringify( body ) + ")\n" ); + }, + error: function( req, resp ) { + if ( req.query.json ) { + resp.writeHead( 400, { "content-type": "application/json" } ); + resp.end( "{ \"code\": 40, \"message\": \"Bad Request\" }" ); + } else { + resp.writeHead( 400 ); + resp.end( "plain text message" ); + } + }, + headers: function( req, resp ) { + resp.writeHead( 200, { + "Sample-Header": "Hello World", + "Empty-Header": "", + "Sample-Header2": "Hello World 2", + "List-Header": "Item 1", + "list-header": "Item 2", + "constructor": "prototype collision (constructor)" + } ); + req.query.keys.split( "|" ).forEach( function( key ) { + if ( req.headers[ key.toLowerCase() ] ) { + resp.write( key + ": " + req.headers[ key.toLowerCase() ] + "\n" ); + } + } ); + resp.end(); + }, + echoData: function( req, resp, next ) { + getBody( req ).then( function( body ) { + resp.end( body ); + }, next ); + }, + echoQuery: function( req, resp ) { + resp.end( req.parsed.search.slice( 1 ) ); + }, + echoMethod: function( req, resp ) { + resp.end( req.method ); + }, + echoHtml: function( req, resp, next ) { + resp.writeHead( 200, { "Content-Type": "text/html" } ); + resp.write( "
    " + req.method + "
    " ); + resp.write( "
    " + req.parsed.search.slice( 1 ) + "
    " ); + getBody( req ).then( function( body ) { + resp.write( "
    " + body + "
    " ); + resp.end( body ); + }, next ); + }, + etag: function( req, resp ) { + var hash = Number( req.query.ts ).toString( 36 ); + var etag = "W/\"" + hash + "\""; + if ( req.headers[ "if-none-match" ] === etag ) { + resp.writeHead( 304 ); + resp.end(); + return; + } + resp.writeHead( 200, { + "Etag": etag + } ); + resp.end(); + }, + ims: function( req, resp, next ) { + var ts = req.query.ts; + if ( req.headers[ "if-modified-since" ] === ts ) { + resp.writeHead( 304 ); + resp.end(); + return; + } + resp.writeHead( 200, { + "Last-Modified": ts + } ); + resp.end(); + }, + status: function( req, resp, next ) { + resp.writeHead( Number( req.query.code ) ); + resp.end(); + }, + testHTML: function( req, resp ) { + resp.writeHead( 200, { "Content-Type": "text/html" } ); + var body = fs.readFileSync( __dirname + "/data/test.include.html" ).toString(); + body = body.replace( /{{baseURL}}/g, req.query.baseURL ); + resp.end( body ); + }, + cspFrame: function( req, resp ) { + resp.writeHead( 200, { + "Content-Type": "text/html", + "Content-Security-Policy": "default-src 'self'; report-uri /base/test/data/mock.php?action=cspLog" + } ); + var body = fs.readFileSync( __dirname + "/data/csp.include.html" ).toString(); + resp.end( body ); + }, + cspNonce: function( req, resp ) { + var testParam = req.query.test ? "-" + req.query.test : ""; + resp.writeHead( 200, { + "Content-Type": "text/html", + "Content-Security-Policy": "script-src 'nonce-jquery+hardcoded+nonce'; report-uri /base/test/data/mock.php?action=cspLog" + } ); + var body = fs.readFileSync( + __dirname + "/data/csp-nonce" + testParam + ".html" ).toString(); + resp.end( body ); + }, + cspLog: function( req, resp ) { + cspLog = "error"; + resp.writeHead( 200 ); + resp.end(); + }, + cspClean: function( req, resp ) { + cspLog = ""; + resp.writeHead( 200 ); + resp.end(); + }, + errorWithScript: function( req, resp ) { + if ( req.query.withScriptContentType ) { + resp.writeHead( 404, { "Content-Type": "application/javascript" } ); + } else { + resp.writeHead( 404 ); + } + if ( req.query.callback ) { + resp.end( req.query.callback + "( {\"status\": 404, \"msg\": \"Not Found\"} )" ); + } else { + resp.end( "QUnit.assert.ok( false, \"Mock return erroneously executed\" );" ); + } + } +}; +var handlers = { + "test/data/mock.php": function( req, resp, next ) { + if ( !mocks[ req.query.action ] ) { + resp.writeHead( 400 ); + resp.end( "Invalid action query.\n" ); + console.log( "Invalid action query:", req.method, req.url ); + return; + } + mocks[ req.query.action ]( req, resp, next ); + }, + "test/data/support/csp.log": function( req, resp ) { + resp.writeHead( 200 ); + resp.end( cspLog ); + }, + "test/data/404.txt": function( req, resp ) { + resp.writeHead( 404 ); + resp.end( "" ); + } +}; + +/** + * Connect-compatible middleware factory for mocking server responses. + * Used by Ajax unit tests when run via Karma. + * + * Despite Karma using Express, it uses Connect to deal with custom middleware, + * which passes the raw Node Request and Response objects instead of the + * Express versions of these (e.g. no req.path, req.query, resp.set). + */ +function MockserverMiddlewareFactory() { + /** + * @param {http.IncomingMessage} req + * @param {http.ServerResponse} resp + * @param {Function} next Continue request handling + */ + return function( req, resp, next ) { + var parsed = url.parse( req.url, /* parseQuery */ true ), + path = parsed.pathname.replace( /^\/base\//, "" ), + query = parsed.query, + subReq = Object.assign( Object.create( req ), { + query: query, + parsed: parsed + } ); + + if ( /^test\/data\/mock.php\//.test( path ) ) { + // Support REST-like Apache PathInfo + path = "test\/data\/mock.php"; + } + + if ( !handlers[ path ] ) { + next(); + return; + } + + handlers[ path ]( subReq, resp, next ); + }; +} + +function getBody( req ) { + return req.method !== "POST" ? + Promise.resolve( "" ) : + getRawBody( req, { + encoding: true + } ); +} + +module.exports = MockserverMiddlewareFactory; diff --git a/test/networkerror.html b/test/networkerror.html new file mode 100644 index 0000000..f666ee0 --- /dev/null +++ b/test/networkerror.html @@ -0,0 +1,84 @@ + + + + + + jQuery Network Error Test for Firefox + + + + + + +

    + jQuery Network Error Test for Firefox +

    +
    + This is a test page for + + #8135 + + which was reported in Firefox when accessing properties + of an XMLHttpRequest object after a network error occurred. +
    +
    Take the following steps:
    +
      +
    1. + make sure you accessed this page through a web server, +
    2. +
    3. + stop the web server, +
    4. +
    5. + open the console, +
    6. +
    7. + click this + + , +
    8. +
    9. + wait for both requests to fail. +
    10. +
    +
    + Test passes if you get two log lines: +
      +
    • + the first starting with "abort", +
    • +
    • + the second starting with "complete", +
    • +
    +
    +
    + Test fails if the browser notifies an exception. +
    + + diff --git a/test/node_smoke_tests/.eslintrc.json b/test/node_smoke_tests/.eslintrc.json new file mode 100644 index 0000000..a1bd6ec --- /dev/null +++ b/test/node_smoke_tests/.eslintrc.json @@ -0,0 +1,13 @@ +{ + "root": true, + + "extends": "../../.eslintrc-node.json", + + "parserOptions": { + "ecmaVersion": 6 + }, + + "env": { + "es6": true + } +} diff --git a/test/node_smoke_tests/document_missing.js b/test/node_smoke_tests/document_missing.js new file mode 100644 index 0000000..0f6a3f7 --- /dev/null +++ b/test/node_smoke_tests/document_missing.js @@ -0,0 +1,11 @@ +"use strict"; + +const assert = require( "assert" ); +const ensureGlobalNotCreated = require( "./lib/ensure_global_not_created" ); +const jQueryFactory = require( "../../dist/jquery.js" ); + +assert.throws( () => { + jQueryFactory( {} ); +}, /jQuery requires a window with a document/ ); + +ensureGlobalNotCreated( module.exports ); diff --git a/test/node_smoke_tests/document_passed.js b/test/node_smoke_tests/document_passed.js new file mode 100644 index 0000000..b1154d3 --- /dev/null +++ b/test/node_smoke_tests/document_passed.js @@ -0,0 +1,12 @@ +"use strict"; + +const { JSDOM } = require( "jsdom" ); + +const { window } = new JSDOM( "" ); + +const ensureJQuery = require( "./lib/ensure_jquery" ); +const ensureGlobalNotCreated = require( "./lib/ensure_global_not_created" ); +const jQuery = require( "../../dist/jquery.js" )( window ); + +ensureJQuery( jQuery ); +ensureGlobalNotCreated( module.exports ); diff --git a/test/node_smoke_tests/document_present_originally.js b/test/node_smoke_tests/document_present_originally.js new file mode 100644 index 0000000..89b0c7b --- /dev/null +++ b/test/node_smoke_tests/document_present_originally.js @@ -0,0 +1,15 @@ +"use strict"; + +const { JSDOM } = require( "jsdom" ); + +const { window } = new JSDOM( "" ); + +// Pretend the window is a global. +global.window = window; + +const ensureJQuery = require( "./lib/ensure_jquery" ); +const ensureGlobalNotCreated = require( "./lib/ensure_global_not_created" ); +const jQuery = require( "../../dist/jquery.js" ); + +ensureJQuery( jQuery ); +ensureGlobalNotCreated( module.exports, window ); diff --git a/test/node_smoke_tests/iterable_with_native_symbol.js b/test/node_smoke_tests/iterable_with_native_symbol.js new file mode 100644 index 0000000..3376ebd --- /dev/null +++ b/test/node_smoke_tests/iterable_with_native_symbol.js @@ -0,0 +1,8 @@ +"use strict"; + +if ( typeof Symbol === "undefined" ) { + console.log( "Symbols not supported, skipping the test..." ); + process.exit(); +} + +require( "./lib/ensure_iterability_es6" )(); diff --git a/test/node_smoke_tests/lib/ensure_global_not_created.js b/test/node_smoke_tests/lib/ensure_global_not_created.js new file mode 100644 index 0000000..95db622 --- /dev/null +++ b/test/node_smoke_tests/lib/ensure_global_not_created.js @@ -0,0 +1,15 @@ +"use strict"; + +const assert = require( "assert" ); + +// Ensure the jQuery property on global/window/module.exports/etc. was not +// created in a CommonJS environment. +// `global` is always checked in addition to passed parameters. +const ensureGlobalNotCreated = ( ...args ) => { + [ ...args, global ].forEach( function( object ) { + assert.strictEqual( object.jQuery, undefined, + "A jQuery global was created in a CommonJS environment." ); + } ); +}; + +module.exports = ensureGlobalNotCreated; diff --git a/test/node_smoke_tests/lib/ensure_iterability_es6.js b/test/node_smoke_tests/lib/ensure_iterability_es6.js new file mode 100644 index 0000000..a948f19 --- /dev/null +++ b/test/node_smoke_tests/lib/ensure_iterability_es6.js @@ -0,0 +1,25 @@ +"use strict"; + +const assert = require( "assert" ); + +const ensureIterability = () => { + const { JSDOM } = require( "jsdom" ); + + const { window } = new JSDOM( "" ); + + let i; + const ensureJQuery = require( "./ensure_jquery" ); + const jQuery = require( "../../../dist/jquery.js" )( window ); + const elem = jQuery( "
    " ); + let result = ""; + + ensureJQuery( jQuery ); + + for ( i of elem ) { + result += i.nodeName; + } + + assert.strictEqual( result, "DIVSPANA", "for-of works on jQuery objects" ); +}; + +module.exports = ensureIterability; diff --git a/test/node_smoke_tests/lib/ensure_jquery.js b/test/node_smoke_tests/lib/ensure_jquery.js new file mode 100644 index 0000000..5b7c064 --- /dev/null +++ b/test/node_smoke_tests/lib/ensure_jquery.js @@ -0,0 +1,11 @@ +"use strict"; + +const assert = require( "assert" ); + +// Check if the object we got is the jQuery object by invoking a basic API. +const ensureJQuery = ( jQuery ) => { + assert( /^jQuery/.test( jQuery.expando ), + "jQuery.expando was not detected, the jQuery bootstrap process has failed" ); +}; + +module.exports = ensureJQuery; diff --git a/test/promises_aplus_adapters/.eslintrc.json b/test/promises_aplus_adapters/.eslintrc.json new file mode 100644 index 0000000..f961645 --- /dev/null +++ b/test/promises_aplus_adapters/.eslintrc.json @@ -0,0 +1,5 @@ +{ + "root": true, + + "extends": "../../.eslintrc-node.json" +} diff --git a/test/promises_aplus_adapters/deferred.js b/test/promises_aplus_adapters/deferred.js new file mode 100644 index 0000000..5e3ffe2 --- /dev/null +++ b/test/promises_aplus_adapters/deferred.js @@ -0,0 +1,17 @@ +"use strict"; + +const { JSDOM } = require( "jsdom" ); + +const { window } = new JSDOM( "" ); + +const jQuery = require( "../../" )( window ); + +module.exports.deferred = () => { + const deferred = jQuery.Deferred(); + + return { + promise: deferred.promise(), + resolve: deferred.resolve.bind( deferred ), + reject: deferred.reject.bind( deferred ) + }; +}; diff --git a/test/promises_aplus_adapters/when.js b/test/promises_aplus_adapters/when.js new file mode 100644 index 0000000..3e945d4 --- /dev/null +++ b/test/promises_aplus_adapters/when.js @@ -0,0 +1,45 @@ +"use strict"; + +const { JSDOM } = require( "jsdom" ); + +const { window } = new JSDOM( "" ); + +const jQuery = require( "../../" )( window ); + +module.exports.deferred = () => { + let adopted, promised; + + return { + resolve: function() { + if ( !adopted ) { + adopted = jQuery.when.apply( jQuery, arguments ); + if ( promised ) { + adopted.then( promised.resolve, promised.reject ); + } + } + return adopted; + }, + reject: function( value ) { + if ( !adopted ) { + adopted = jQuery.when( jQuery.Deferred().reject( value ) ); + if ( promised ) { + adopted.then( promised.resolve, promised.reject ); + } + } + return adopted; + }, + + // A manually-constructed thenable that works even if calls precede resolve/reject + promise: { + then: function() { + if ( !adopted ) { + if ( !promised ) { + promised = jQuery.Deferred(); + } + return promised.then.apply( promised, arguments ); + } + return adopted.then.apply( adopted, arguments ); + } + } + }; +}; diff --git a/test/unit/ajax.js b/test/unit/ajax.js new file mode 100644 index 0000000..bcb2346 --- /dev/null +++ b/test/unit/ajax.js @@ -0,0 +1,2943 @@ +QUnit.module( "ajax", { + afterEach: function() { + jQuery( document ).off( "ajaxStart ajaxStop ajaxSend ajaxComplete ajaxError ajaxSuccess" ); + moduleTeardown.apply( this, arguments ); + } +} ); + +( function() { + QUnit.test( "Unit Testing Environment", function( assert ) { + assert.expect( 2 ); + + assert.ok( hasPHP, "Running in an environment with PHP support. The AJAX tests only run if the environment supports PHP!" ); + assert.ok( !isLocal, "Unit tests are not ran from file:// (especially in Chrome. If you must test from file:// with Chrome, run it with the --allow-file-access-from-files flag!)" ); + } ); + + if ( !jQuery.ajax || ( isLocal && !hasPHP ) ) { + return; + } + + function addGlobalEvents( expected, assert ) { + return function() { + expected = expected || ""; + jQuery( document ).on( "ajaxStart ajaxStop ajaxSend ajaxComplete ajaxError ajaxSuccess", function( e ) { + assert.ok( expected.indexOf( e.type ) !== -1, e.type ); + } ); + }; + } + +//----------- jQuery.ajax() + + testIframe( + "XMLHttpRequest - Attempt to block tests because of dangling XHR requests (IE)", + "ajax/unreleasedXHR.html", + function( assert ) { + assert.expect( 1 ); + assert.ok( true, "done" ); + } + ); + + ajaxTest( "jQuery.ajax() - success callbacks", 8, function( assert ) { + return { + setup: addGlobalEvents( "ajaxStart ajaxStop ajaxSend ajaxComplete ajaxSuccess", assert ), + url: url( "name.html" ), + beforeSend: function() { + assert.ok( true, "beforeSend" ); + }, + success: function() { + assert.ok( true, "success" ); + }, + complete: function() { + assert.ok( true, "complete" ); + } + }; + } ); + + ajaxTest( "jQuery.ajax() - success callbacks - (url, options) syntax", 8, function( assert ) { + return { + setup: addGlobalEvents( "ajaxStart ajaxStop ajaxSend ajaxComplete ajaxSuccess", assert ), + create: function( options ) { + return jQuery.ajax( url( "name.html" ), options ); + }, + beforeSend: function() { + assert.ok( true, "beforeSend" ); + }, + success: function() { + assert.ok( true, "success" ); + }, + complete: function() { + assert.ok( true, "complete" ); + } + }; + } ); + + ajaxTest( "jQuery.ajax() - execute js for crossOrigin when dataType option is provided", 3, + function( assert ) { + return { + create: function( options ) { + options.crossDomain = true; + options.dataType = "script"; + return jQuery.ajax( url( "mock.php?action=script&header=ecma" ), options ); + }, + success: function() { + assert.ok( true, "success" ); + }, + complete: function() { + assert.ok( true, "complete" ); + } + }; + } + ); + + ajaxTest( "jQuery.ajax() - custom attributes for script tag", 4, + function( assert ) { + return { + create: function( options ) { + var xhr; + options.dataType = "script"; + options.scriptAttrs = { id: "jquery-ajax-test", async: "async" }; + xhr = jQuery.ajax( url( "mock.php?action=script" ), options ); + assert.equal( jQuery( "#jquery-ajax-test" ).attr( "async" ), "async", "attr value" ); + return xhr; + }, + success: function() { + assert.ok( true, "success" ); + }, + complete: function() { + assert.ok( true, "complete" ); + } + }; + } + ); + + ajaxTest( "jQuery.ajax() - do not execute js (crossOrigin)", 2, function( assert ) { + return { + create: function( options ) { + options.crossDomain = true; + return jQuery.ajax( url( "mock.php?action=script&header" ), options ); + }, + success: function() { + assert.ok( true, "success" ); + }, + fail: function() { + if ( jQuery.support.cors === false ) { + assert.ok( true, "fail" ); + } + }, + complete: function() { + assert.ok( true, "complete" ); + } + }; + } ); + + ajaxTest( "jQuery.ajax() - success callbacks (late binding)", 8, function( assert ) { + return { + setup: addGlobalEvents( "ajaxStart ajaxStop ajaxSend ajaxComplete ajaxSuccess", assert ), + url: url( "name.html" ), + beforeSend: function() { + assert.ok( true, "beforeSend" ); + }, + success: true, + afterSend: function( request ) { + request.always( function() { + assert.ok( true, "complete" ); + } ).done( function() { + assert.ok( true, "success" ); + } ).fail( function() { + assert.ok( false, "error" ); + } ); + } + }; + } ); + + ajaxTest( "jQuery.ajax() - success callbacks (oncomplete binding)", 8, function( assert ) { + return { + setup: addGlobalEvents( "ajaxStart ajaxStop ajaxSend ajaxComplete ajaxSuccess", assert ), + url: url( "name.html" ), + beforeSend: function() { + assert.ok( true, "beforeSend" ); + }, + success: true, + complete: function( xhr ) { + xhr.always( function() { + assert.ok( true, "complete" ); + } ).done( function() { + assert.ok( true, "success" ); + } ).fail( function() { + assert.ok( false, "error" ); + } ); + } + }; + } ); + + ajaxTest( "jQuery.ajax() - error callbacks", 8, function( assert ) { + return { + setup: addGlobalEvents( "ajaxStart ajaxStop ajaxSend ajaxComplete ajaxError", assert ), + url: url( "mock.php?action=wait&wait=5" ), + beforeSend: function() { + assert.ok( true, "beforeSend" ); + }, + afterSend: function( request ) { + request.abort(); + }, + error: function() { + assert.ok( true, "error" ); + }, + complete: function() { + assert.ok( true, "complete" ); + } + }; + } ); + + ajaxTest( "jQuery.ajax() - textStatus and errorThrown values", 4, function( assert ) { + return [ { + url: url( "mock.php?action=wait&wait=5" ), + error: function( _, textStatus, errorThrown ) { + assert.strictEqual( textStatus, "abort", "textStatus is 'abort' for abort" ); + assert.strictEqual( errorThrown, "abort", "errorThrown is 'abort' for abort" ); + }, + afterSend: function( request ) { + request.abort(); + } + }, + { + url: url( "mock.php?action=wait&wait=5" ), + error: function( _, textStatus, errorThrown ) { + assert.strictEqual( textStatus, "mystatus", "textStatus is 'mystatus' for abort('mystatus')" ); + assert.strictEqual( errorThrown, "mystatus", "errorThrown is 'mystatus' for abort('mystatus')" ); + }, + afterSend: function( request ) { + request.abort( "mystatus" ); + } + } ]; + } ); + + ajaxTest( "jQuery.ajax() - responseText on error", 1, function( assert ) { + return { + url: url( "mock.php?action=error" ), + error: function( xhr ) { + assert.strictEqual( xhr.responseText, "plain text message", "Test jqXHR.responseText is filled for HTTP errors" ); + } + }; + } ); + + QUnit.test( "jQuery.ajax() - retry with jQuery.ajax( this )", function( assert ) { + assert.expect( 2 ); + var previousUrl, + firstTime = true, + done = assert.async(); + jQuery.ajax( { + url: url( "mock.php?action=error" ), + error: function() { + if ( firstTime ) { + firstTime = false; + jQuery.ajax( this ); + } else { + assert.ok( true, "Test retrying with jQuery.ajax(this) works" ); + jQuery.ajax( { + url: url( "mock.php?action=error&x=2" ), + beforeSend: function() { + if ( !previousUrl ) { + previousUrl = this.url; + } else { + assert.strictEqual( this.url, previousUrl, "url parameters are not re-appended" ); + done(); + return false; + } + }, + error: function() { + jQuery.ajax( this ); + } + } ); + } + } + } ); + } ); + + ajaxTest( "jQuery.ajax() - headers", 8, function( assert ) { + return { + setup: function() { + jQuery( document ).on( "ajaxSend", function( evt, xhr ) { + xhr.setRequestHeader( "ajax-send", "test" ); + } ); + }, + url: url( "mock.php?action=headers&keys=siMPle|SometHing-elsE|OthEr|Nullable|undefined|Empty|ajax-send" ), + headers: { + "siMPle": "value", + "SometHing-elsE": "other value", + "OthEr": "something else", + "Nullable": null, + "undefined": undefined + + // Support: IE 9 - 11, Edge 12 - 14 only + // Not all browsers allow empty-string headers + //"Empty": "" + }, + success: function( data, _, xhr ) { + var i, emptyHeader, + isAndroid = /android 4\.[0-3]/i.test( navigator.userAgent ), + requestHeaders = jQuery.extend( this.headers, { + "ajax-send": "test" + } ), + tmp = []; + for ( i in requestHeaders ) { + tmp.push( i, ": ", requestHeaders[ i ] + "", "\n" ); + } + tmp = tmp.join( "" ); + + assert.strictEqual( data, tmp, "Headers were sent" ); + assert.strictEqual( xhr.getResponseHeader( "Sample-Header" ), "Hello World", "Sample header received" ); + assert.ok( data.indexOf( "undefined" ) < 0, "Undefined header value was not sent" ); + + emptyHeader = xhr.getResponseHeader( "Empty-Header" ); + if ( emptyHeader === null ) { + assert.ok( true, "Firefox doesn't support empty headers" ); + } else { + assert.strictEqual( emptyHeader, "", "Empty header received" ); + } + assert.strictEqual( xhr.getResponseHeader( "Sample-Header2" ), "Hello World 2", "Second sample header received" ); + + if ( isAndroid ) { + // Support: Android 4.0-4.3 only + // Android Browser only returns the last value for each header + // so there's no way for jQuery get all parts. + assert.ok( true, "Android doesn't support repeated header names" ); + } else { + assert.strictEqual( xhr.getResponseHeader( "List-Header" ), "Item 1, Item 2", "List header received" ); + } + + if ( isAndroid && QUnit.isSwarm ) { + // Support: Android 4.0-4.3 on BrowserStack only + // Android Browser versions provided by BrowserStack fail this test + // while locally fired emulators don't, even when they connect + // to TestSwarm. Just skip the test there to avoid a red build. + assert.ok( true, "BrowserStack's Android fails the \"prototype collision (constructor)\" test" ); + } else { + assert.strictEqual( xhr.getResponseHeader( "constructor" ), "prototype collision (constructor)", "constructor header received" ); + } + assert.strictEqual( xhr.getResponseHeader( "__proto__" ), null, "Undefined __proto__ header not received" ); + } + }; + } ); + + ajaxTest( "jQuery.ajax() - Accept header", 1, function( assert ) { + return { + url: url( "mock.php?action=headers&keys=accept" ), + headers: { + Accept: "very wrong accept value" + }, + beforeSend: function( xhr ) { + xhr.setRequestHeader( "Accept", "*/*" ); + }, + success: function( data ) { + assert.strictEqual( data, "accept: */*\n", "Test Accept header is set to last value provided" ); + } + }; + } ); + + ajaxTest( "jQuery.ajax() - contentType", 2, function( assert ) { + return [ + { + url: url( "mock.php?action=headers&keys=content-type" ), + contentType: "test", + success: function( data ) { + assert.strictEqual( data, "content-type: test\n", "Test content-type is sent when options.contentType is set" ); + } + }, + { + url: url( "mock.php?action=headers&keys=content-type" ), + contentType: false, + success: function( data ) { + + // Some server/interpreter combinations always supply a Content-Type to scripts + data = data || "content-type: \n"; + assert.strictEqual( data, "content-type: \n", "Test content-type is not set when options.contentType===false" ); + } + } + ]; + } ); + + ajaxTest( "jQuery.ajax() - protocol-less urls", 1, function( assert ) { + return { + url: "//somedomain.com", + beforeSend: function( xhr, settings ) { + assert.equal( settings.url, location.protocol + "//somedomain.com", "Make sure that the protocol is added." ); + return false; + }, + error: true + }; + } ); + + ajaxTest( "jQuery.ajax() - URL fragment component preservation", 4, function( assert ) { + return [ + { + url: baseURL + "name.html#foo", + beforeSend: function( xhr, settings ) { + assert.equal( settings.url, baseURL + "name.html#foo", + "hash preserved for request with no query component." ); + return false; + }, + error: true + }, + { + url: baseURL + "name.html?abc#foo", + beforeSend: function( xhr, settings ) { + assert.equal( settings.url, baseURL + "name.html?abc#foo", + "hash preserved for request with query component." ); + return false; + }, + error: true + }, + { + url: baseURL + "name.html?abc#foo", + data: { + "test": 123 + }, + beforeSend: function( xhr, settings ) { + assert.equal( settings.url, baseURL + "name.html?abc&test=123#foo", + "hash preserved for request with query component and data." ); + return false; + }, + error: true + }, + { + url: baseURL + "name.html?abc#brownies", + data: { + "devo": "hat" + }, + cache: false, + beforeSend: function( xhr, settings ) { + // Clear the cache-buster param value + var url = settings.url.replace( /_=[^&#]+/, "_=" ); + assert.equal( url, baseURL + "name.html?abc&devo=hat&_=#brownies", + "hash preserved for cache-busting request with query component and data." ); + return false; + }, + error: true + } + ]; + } ); + + ajaxTest( "jQuery.ajax() - traditional param encoding", 4, function( assert ) { + return [ + { + url: "/", + traditional: true, + data: { + "devo": "hat", + "answer": 42, + "quux": "a space" + }, + beforeSend: function( xhr, settings ) { + assert.equal( settings.url, "/?devo=hat&answer=42&quux=a%20space", "Simple case" ); + return false; + }, + error: true + }, + { + url: "/", + traditional: true, + data: { + "a": [ 1, 2, 3 ], + "b[]": [ "b1", "b2" ] + }, + beforeSend: function( xhr, settings ) { + assert.equal( settings.url, "/?a=1&a=2&a=3&b%5B%5D=b1&b%5B%5D=b2", "Arrays" ); + return false; + }, + error: true + }, + { + url: "/", + traditional: true, + data: { + "a": [ [ 1, 2 ], [ 3, 4 ], 5 ] + }, + beforeSend: function( xhr, settings ) { + assert.equal( settings.url, "/?a=1%2C2&a=3%2C4&a=5", "Nested arrays" ); + return false; + }, + error: true + }, + { + url: "/", + traditional: true, + data: { + "a": [ "w", [ [ "x", "y" ], "z" ] ] + }, + cache: false, + beforeSend: function( xhr, settings ) { + var url = settings.url.replace( /\d{3,}/, "" ); + assert.equal( url, "/?a=w&a=x%2Cy%2Cz&_=", "Cache-buster" ); + return false; + }, + error: true + } + ]; + } ); + + ajaxTest( "jQuery.ajax() - cross-domain detection", 8, function( assert ) { + function request( url, title, crossDomainOrOptions ) { + return jQuery.extend( { + dataType: "jsonp", + url: url, + beforeSend: function( _, s ) { + assert.ok( crossDomainOrOptions === false ? !s.crossDomain : s.crossDomain, title ); + return false; + }, + error: true + }, crossDomainOrOptions ); + } + + var loc = document.location, + samePort = loc.port || ( loc.protocol === "http:" ? 80 : 443 ), + otherPort = loc.port === 666 ? 667 : 666, + otherProtocol = loc.protocol === "http:" ? "https:" : "http:"; + + return [ + request( + loc.protocol + "//" + loc.hostname + ":" + samePort, + "Test matching ports are not detected as cross-domain", + false + ), + request( + otherProtocol + "//" + loc.host, + "Test different protocols are detected as cross-domain" + ), + request( + "app:/path", + "Adobe AIR app:/ URL detected as cross-domain" + ), + request( + loc.protocol + "//example.invalid:" + ( loc.port || 80 ), + "Test different hostnames are detected as cross-domain" + ), + request( + loc.protocol + "//" + loc.hostname + ":" + otherPort, + "Test different ports are detected as cross-domain" + ), + request( + "about:blank", + "Test about:blank is detected as cross-domain" + ), + request( + loc.protocol + "//" + loc.host, + "Test forced crossDomain is detected as cross-domain", + { + crossDomain: true + } + ), + request( + " http://otherdomain.com", + "Cross-domain url with leading space is detected as cross-domain" + ) + ]; + } ); + + ajaxTest( "jQuery.ajax() - abort", 9, function( assert ) { + return { + setup: addGlobalEvents( "ajaxStart ajaxStop ajaxSend ajaxError ajaxComplete", assert ), + url: url( "mock.php?action=wait&wait=5" ), + beforeSend: function() { + assert.ok( true, "beforeSend" ); + }, + afterSend: function( xhr ) { + assert.strictEqual( xhr.readyState, 1, "XHR readyState indicates successful dispatch" ); + xhr.abort(); + assert.strictEqual( xhr.readyState, 0, "XHR readyState indicates successful abortion" ); + }, + error: true, + complete: function() { + assert.ok( true, "complete" ); + } + }; + } ); + + if ( !/android 4\.0/i.test( navigator.userAgent ) ) { + ajaxTest( "jQuery.ajax() - native abort", 2, function( assert ) { + return { + url: url( "mock.php?action=wait&wait=1" ), + xhr: function() { + var xhr = new window.XMLHttpRequest(); + setTimeout( function() { + xhr.abort(); + }, 100 ); + return xhr; + }, + error: function( xhr, msg ) { + assert.strictEqual( msg, "error", "Native abort triggers error callback" ); + }, + complete: function() { + assert.ok( true, "complete" ); + } + }; + } ); + } + + // Support: Android <= 4.0 - 4.3 only + // Android 4.0-4.3 does not have ontimeout on an xhr + if ( "ontimeout" in new window.XMLHttpRequest() ) { + ajaxTest( "jQuery.ajax() - native timeout", 2, function( assert ) { + return { + url: url( "mock.php?action=wait&wait=1" ), + xhr: function() { + var xhr = new window.XMLHttpRequest(); + xhr.timeout = 1; + return xhr; + }, + error: function( xhr, msg ) { + assert.strictEqual( msg, "error", "Native timeout triggers error callback" ); + }, + complete: function() { + assert.ok( true, "complete" ); + } + }; + } ); + } + + ajaxTest( "jQuery.ajax() - events with context", 12, function( assert ) { + var context = document.createElement( "div" ); + + function event( e ) { + assert.equal( this, context, e.type ); + } + + function callback( msg ) { + return function() { + assert.equal( this, context, "context is preserved on callback " + msg ); + }; + } + + return { + setup: function() { + jQuery( context ).appendTo( "#foo" ) + .on( "ajaxSend", event ) + .on( "ajaxComplete", event ) + .on( "ajaxError", event ) + .on( "ajaxSuccess", event ); + }, + requests: [ { + url: url( "name.html" ), + context: context, + beforeSend: callback( "beforeSend" ), + success: callback( "success" ), + complete: callback( "complete" ) + }, { + url: url( "404.txt" ), + context: context, + beforeSend: callback( "beforeSend" ), + error: callback( "error" ), + complete: callback( "complete" ) + } ] + }; + } ); + + ajaxTest( "jQuery.ajax() - events without context", 3, function( assert ) { + function nocallback( msg ) { + return function() { + assert.equal( typeof this.url, "string", "context is settings on callback " + msg ); + }; + } + return { + url: url( "404.txt" ), + beforeSend: nocallback( "beforeSend" ), + error: nocallback( "error" ), + complete: nocallback( "complete" ) + }; + } ); + + ajaxTest( "#15118 - jQuery.ajax() - function without jQuery.event", 1, function( assert ) { + var holder; + return { + url: url( "mock.php?action=json" ), + setup: function() { + holder = jQuery.event; + delete jQuery.event; + }, + complete: function() { + assert.ok( true, "Call can be made without jQuery.event" ); + jQuery.event = holder; + }, + success: true + }; + } ); + + ajaxTest( "#15160 - jQuery.ajax() - request manually aborted in ajaxSend", 3, function( assert ) { + return { + setup: function() { + jQuery( document ).on( "ajaxSend", function( e, jqXHR ) { + jqXHR.abort(); + } ); + + jQuery( document ).on( "ajaxError ajaxComplete", function( e, jqXHR ) { + assert.equal( jqXHR.statusText, "abort", "jqXHR.statusText equals abort on global ajaxComplete and ajaxError events" ); + } ); + }, + url: url( "name.html" ), + error: true, + complete: function() { + assert.ok( true, "complete" ); + } + }; + } ); + + ajaxTest( "jQuery.ajax() - context modification", 1, function( assert ) { + return { + url: url( "name.html" ), + context: {}, + beforeSend: function() { + this.test = "foo"; + }, + afterSend: function() { + assert.strictEqual( this.context.test, "foo", "Make sure the original object is maintained." ); + }, + success: true + }; + } ); + + ajaxTest( "jQuery.ajax() - context modification through ajaxSetup", 3, function( assert ) { + var obj = {}; + return { + setup: function() { + jQuery.ajaxSetup( { + context: obj + } ); + assert.strictEqual( jQuery.ajaxSettings.context, obj, "Make sure the context is properly set in ajaxSettings." ); + }, + requests: [ { + url: url( "name.html" ), + success: function() { + assert.strictEqual( this, obj, "Make sure the original object is maintained." ); + } + }, { + url: url( "name.html" ), + context: {}, + success: function() { + assert.ok( this !== obj, "Make sure overriding context is possible." ); + } + } ] + }; + } ); + + ajaxTest( "jQuery.ajax() - disabled globals", 3, function( assert ) { + return { + setup: addGlobalEvents( "", assert ), + global: false, + url: url( "name.html" ), + beforeSend: function() { + assert.ok( true, "beforeSend" ); + }, + success: function() { + assert.ok( true, "success" ); + }, + complete: function() { + assert.ok( true, "complete" ); + } + }; + } ); + + ajaxTest( "jQuery.ajax() - xml: non-namespace elements inside namespaced elements", 3, function( assert ) { + return { + url: url( "with_fries.xml" ), + dataType: "xml", + success: function( resp ) { + assert.equal( jQuery( "properties", resp ).length, 1, "properties in responseXML" ); + assert.equal( jQuery( "jsconf", resp ).length, 1, "jsconf in responseXML" ); + assert.equal( jQuery( "thing", resp ).length, 2, "things in responseXML" ); + } + }; + } ); + + ajaxTest( "jQuery.ajax() - xml: non-namespace elements inside namespaced elements (over JSONP)", 3, function( assert ) { + return { + url: url( "mock.php?action=xmlOverJsonp" ), + dataType: "jsonp xml", + success: function( resp ) { + assert.equal( jQuery( "properties", resp ).length, 1, "properties in responseXML" ); + assert.equal( jQuery( "jsconf", resp ).length, 1, "jsconf in responseXML" ); + assert.equal( jQuery( "thing", resp ).length, 2, "things in responseXML" ); + } + }; + } ); + + ajaxTest( "jQuery.ajax() - HEAD requests", 2, function( assert ) { + return [ + { + url: url( "name.html" ), + type: "HEAD", + success: function( data, status, xhr ) { + assert.ok( /Date/i.test( xhr.getAllResponseHeaders() ), "No Date in HEAD response" ); + } + }, + { + url: url( "name.html" ), + data: { + "whip_it": "good" + }, + type: "HEAD", + success: function( data, status, xhr ) { + assert.ok( /Date/i.test( xhr.getAllResponseHeaders() ), "No Date in HEAD response with data" ); + } + } + ]; + } ); + + ajaxTest( "jQuery.ajax() - beforeSend", 1, function( assert ) { + return { + url: url( "name.html" ), + beforeSend: function() { + this.check = true; + }, + success: function() { + assert.ok( this.check, "check beforeSend was executed" ); + } + }; + } ); + + ajaxTest( "jQuery.ajax() - beforeSend, cancel request manually", 2, function( assert ) { + return { + create: function() { + return jQuery.ajax( { + url: url( "name.html" ), + beforeSend: function( xhr ) { + assert.ok( true, "beforeSend got called, canceling" ); + xhr.abort(); + }, + success: function() { + assert.ok( false, "request didn't get canceled" ); + }, + complete: function() { + assert.ok( false, "request didn't get canceled" ); + }, + error: function() { + assert.ok( false, "request didn't get canceled" ); + } + } ); + }, + fail: function( _, reason ) { + assert.strictEqual( reason, "canceled", "canceled request must fail with 'canceled' status text" ); + } + }; + } ); + + ajaxTest( "jQuery.ajax() - dataType html", 5, function( assert ) { + return { + setup: function() { + Globals.register( "testFoo" ); + Globals.register( "testBar" ); + }, + dataType: "html", + url: url( "mock.php?action=testHTML&baseURL=" + baseURL ), + success: function( data ) { + assert.ok( data.match( /^html text/ ), "Check content for datatype html" ); + jQuery( "#ap" ).html( data ); + assert.strictEqual( window[ "testFoo" ], "foo", "Check if script was evaluated for datatype html" ); + assert.strictEqual( window[ "testBar" ], "bar", "Check if script src was evaluated for datatype html" ); + } + }; + } ); + + ajaxTest( "jQuery.ajax() - do execute scripts if JSONP from unsuccessful responses", 1, function( assert ) { + var testMsg = "Unsuccessful JSONP requests should have a JSON body"; + return { + dataType: "jsonp", + url: url( "mock.php?action=errorWithScript" ), + // error is the significant assertion + error: function( xhr ) { + var expected = { "status": 404, "msg": "Not Found" }; + assert.deepEqual( xhr.responseJSON, expected, testMsg ); + } + }; + } ); + + ajaxTest( "jQuery.ajax() - do not execute scripts from unsuccessful responses (gh-4250)", 11, function( assert ) { + var globalEval = jQuery.globalEval; + + var failConverters = { + "text script": function() { + assert.ok( false, "No converter for unsuccessful response" ); + } + }; + + function request( title, options ) { + var testMsg = title + ": expected file missing status"; + return jQuery.extend( { + beforeSend: function() { + jQuery.globalEval = function() { + assert.ok( false, "Should not eval" ); + }; + }, + complete: function() { + jQuery.globalEval = globalEval; + }, + // error is the significant assertion + error: function( xhr ) { + assert.strictEqual( xhr.status, 404, testMsg ); + }, + success: function() { + assert.ok( false, "Unanticipated success" ); + } + }, options ); + } + + return [ + request( + "HTML reply", + { + url: url( "404.txt" ) + } + ), + request( + "HTML reply with dataType", + { + dataType: "script", + url: url( "404.txt" ) + } + ), + request( + "script reply", + { + url: url( "mock.php?action=errorWithScript&withScriptContentType" ) + } + ), + request( + "non-script reply", + { + url: url( "mock.php?action=errorWithScript" ) + } + ), + request( + "script reply with dataType", + { + dataType: "script", + url: url( "mock.php?action=errorWithScript&withScriptContentType" ) + } + ), + request( + "non-script reply with dataType", + { + dataType: "script", + url: url( "mock.php?action=errorWithScript" ) + } + ), + request( + "script reply with converter", + { + converters: failConverters, + url: url( "mock.php?action=errorWithScript&withScriptContentType" ) + } + ), + request( + "non-script reply with converter", + { + converters: failConverters, + url: url( "mock.php?action=errorWithScript" ) + } + ), + request( + "script reply with converter and dataType", + { + converters: failConverters, + dataType: "script", + url: url( "mock.php?action=errorWithScript&withScriptContentType" ) + } + ), + request( + "non-script reply with converter and dataType", + { + converters: failConverters, + dataType: "script", + url: url( "mock.php?action=errorWithScript" ) + } + ), + request( + "JSONP reply with dataType", + { + dataType: "jsonp", + url: url( "mock.php?action=errorWithScript" ), + beforeSend: function() { + jQuery.globalEval = function( response ) { + assert.ok( /"status": 404, "msg": "Not Found"/.test( response ), "Error object returned" ); + }; + } + } + ) + ]; + } ); + + ajaxTest( "jQuery.ajax() - synchronous request", 1, function( assert ) { + return { + url: url( "json_obj.js" ), + dataType: "text", + async: false, + success: true, + afterSend: function( xhr ) { + assert.ok( /^\{ "data"/.test( xhr.responseText ), "check returned text" ); + } + }; + } ); + + ajaxTest( "jQuery.ajax() - synchronous request with callbacks", 2, function( assert ) { + return { + url: url( "json_obj.js" ), + async: false, + dataType: "text", + success: true, + afterSend: function( xhr ) { + var result; + xhr.done( function( data ) { + assert.ok( true, "success callback executed" ); + result = data; + } ); + assert.ok( /^\{ "data"/.test( result ), "check returned text" ); + } + }; + } ); + + QUnit.test( "jQuery.ajax(), jQuery.get[Script|JSON](), jQuery.post(), pass-through request object", function( assert ) { + assert.expect( 8 ); + var done = assert.async(); + var target = "name.html", + successCount = 0, + errorCount = 0, + errorEx = "", + success = function() { + successCount++; + }; + jQuery( document ).on( "ajaxError.passthru", function( e, xml ) { + errorCount++; + errorEx += ": " + xml.status; + } ); + jQuery( document ).one( "ajaxStop", function() { + assert.equal( successCount, 5, "Check all ajax calls successful" ); + assert.equal( errorCount, 0, "Check no ajax errors (status" + errorEx + ")" ); + jQuery( document ).off( "ajaxError.passthru" ); + done(); + } ); + Globals.register( "testBar" ); + + assert.ok( jQuery.get( url( target ), success ), "get" ); + assert.ok( jQuery.post( url( target ), success ), "post" ); + assert.ok( jQuery.getScript( url( "mock.php?action=testbar" ), success ), "script" ); + assert.ok( jQuery.getJSON( url( "json_obj.js" ), success ), "json" ); + assert.ok( jQuery.ajax( { + url: url( target ), + success: success + } ), "generic" ); + } ); + + ajaxTest( "jQuery.ajax() - cache", 28, function( assert ) { + var re = /_=(.*?)(&|$)/g, + rootUrl = baseURL + "text.txt"; + + function request( url, title ) { + return { + url: url, + cache: false, + beforeSend: function() { + var parameter, tmp; + + // URL sanity check + assert.equal( this.url.indexOf( rootUrl ), 0, "root url not mangled: " + this.url ); + assert.equal( /\&.*\?/.test( this.url ), false, "parameter delimiters in order" ); + + while ( ( tmp = re.exec( this.url ) ) ) { + assert.strictEqual( parameter, undefined, title + ": only one 'no-cache' parameter" ); + parameter = tmp[ 1 ]; + assert.notStrictEqual( parameter, "tobereplaced555", title + ": parameter (if it was there) was replaced" ); + } + return false; + }, + error: true + }; + } + + return [ + request( + rootUrl, + "no query" + ), + request( + rootUrl + "?", + "empty query" + ), + request( + rootUrl + "?pizza=true", + "1 parameter" + ), + request( + rootUrl + "?_=tobereplaced555", + "_= parameter" + ), + request( + rootUrl + "?pizza=true&_=tobereplaced555", + "1 parameter and _=" + ), + request( + rootUrl + "?_=tobereplaced555&tv=false", + "_= and 1 parameter" + ), + request( + rootUrl + "?name=David&_=tobereplaced555&washere=true", + "2 parameters surrounding _=" + ) + ]; + } ); + + jQuery.each( [ " - Same Domain", " - Cross Domain" ], function( crossDomain, label ) { + + ajaxTest( "jQuery.ajax() - JSONP - Query String (?n)" + label, 4, function( assert ) { + return [ + { + url: baseURL + "mock.php?action=jsonp&callback=?", + dataType: "jsonp", + crossDomain: crossDomain, + success: function( data ) { + assert.ok( data.data, "JSON results returned (GET, url callback)" ); + } + }, + { + url: baseURL + "mock.php?action=jsonp&callback=??", + dataType: "jsonp", + crossDomain: crossDomain, + success: function( data ) { + assert.ok( data.data, "JSON results returned (GET, url context-free callback)" ); + } + }, + { + url: baseURL + "mock.php/???action=jsonp", + dataType: "jsonp", + crossDomain: crossDomain, + success: function( data ) { + assert.ok( data.data, "JSON results returned (GET, REST-like)" ); + } + }, + { + url: baseURL + "mock.php/???action=jsonp&array=1", + dataType: "jsonp", + crossDomain: crossDomain, + success: function( data ) { + assert.ok( Array.isArray( data ), "JSON results returned (GET, REST-like with param)" ); + } + } + ]; + } ); + + ajaxTest( "jQuery.ajax() - JSONP - Explicit callback param" + label, 10, function( assert ) { + return { + setup: function() { + Globals.register( "functionToCleanUp" ); + Globals.register( "XXX" ); + Globals.register( "jsonpResults" ); + window[ "jsonpResults" ] = function( data ) { + assert.ok( data[ "data" ], "JSON results returned (GET, custom callback function)" ); + }; + }, + requests: [ { + url: baseURL + "mock.php?action=jsonp", + dataType: "jsonp", + crossDomain: crossDomain, + jsonp: "callback", + success: function( data ) { + assert.ok( data[ "data" ], "JSON results returned (GET, data obj callback)" ); + } + }, { + url: baseURL + "mock.php?action=jsonp", + dataType: "jsonp", + crossDomain: crossDomain, + jsonpCallback: "jsonpResults", + success: function( data ) { + assert.strictEqual( + typeof window[ "jsonpResults" ], + "function", + "should not rewrite original function" + ); + assert.ok( data.data, "JSON results returned (GET, custom callback name)" ); + } + }, { + url: baseURL + "mock.php?action=jsonp", + dataType: "jsonp", + crossDomain: crossDomain, + jsonpCallback: "functionToCleanUp", + success: function( data ) { + assert.ok( data[ "data" ], "JSON results returned (GET, custom callback name to be cleaned up)" ); + assert.strictEqual( window[ "functionToCleanUp" ], true, "Callback was removed (GET, custom callback name to be cleaned up)" ); + var xhr; + jQuery.ajax( { + url: baseURL + "mock.php?action=jsonp", + dataType: "jsonp", + crossDomain: crossDomain, + jsonpCallback: "functionToCleanUp", + beforeSend: function( jqXHR ) { + xhr = jqXHR; + return false; + } + } ); + xhr.fail( function() { + assert.ok( true, "Ajax error JSON (GET, custom callback name to be cleaned up)" ); + assert.strictEqual( window[ "functionToCleanUp" ], true, "Callback was removed after early abort (GET, custom callback name to be cleaned up)" ); + } ); + } + }, { + url: baseURL + "mock.php?action=jsonp&callback=XXX", + dataType: "jsonp", + jsonp: false, + jsonpCallback: "XXX", + crossDomain: crossDomain, + beforeSend: function() { + assert.ok( /action=jsonp&callback=XXX&_=\d+$/.test( this.url ), "The URL wasn't messed with (GET, custom callback name with no url manipulation)" ); + }, + success: function( data ) { + assert.ok( data[ "data" ], "JSON results returned (GET, custom callback name with no url manipulation)" ); + } + } ] + }; + } ); + + ajaxTest( "jQuery.ajax() - JSONP - Callback in data" + label, 2, function( assert ) { + return [ + { + url: baseURL + "mock.php?action=jsonp", + dataType: "jsonp", + crossDomain: crossDomain, + data: "callback=?", + success: function( data ) { + assert.ok( data.data, "JSON results returned (GET, data callback)" ); + } + }, + { + url: baseURL + "mock.php?action=jsonp", + dataType: "jsonp", + crossDomain: crossDomain, + data: "callback=??", + success: function( data ) { + assert.ok( data.data, "JSON results returned (GET, data context-free callback)" ); + } + } + ]; + } ); + + ajaxTest( "jQuery.ajax() - JSONP - POST" + label, 3, function( assert ) { + return [ + { + type: "POST", + url: baseURL + "mock.php?action=jsonp", + dataType: "jsonp", + crossDomain: crossDomain, + success: function( data ) { + assert.ok( data[ "data" ], "JSON results returned (POST, no callback)" ); + } + }, + { + type: "POST", + url: baseURL + "mock.php?action=jsonp", + data: "callback=?", + dataType: "jsonp", + crossDomain: crossDomain, + success: function( data ) { + assert.ok( data[ "data" ], "JSON results returned (POST, data callback)" ); + } + }, + { + type: "POST", + url: baseURL + "mock.php?action=jsonp", + jsonp: "callback", + dataType: "jsonp", + crossDomain: crossDomain, + success: function( data ) { + assert.ok( data[ "data" ], "JSON results returned (POST, data obj callback)" ); + } + } + ]; + } ); + + ajaxTest( "jQuery.ajax() - JSONP" + label, 3, function( assert ) { + return [ + { + url: baseURL + "mock.php?action=jsonp", + dataType: "jsonp", + crossDomain: crossDomain, + success: function( data ) { + assert.ok( data.data, "JSON results returned (GET, no callback)" ); + } + }, + { + create: function( options ) { + var request = jQuery.ajax( options ), + promise = request.then( function( data ) { + assert.ok( data.data, "first request: JSON results returned (GET, no callback)" ); + request = jQuery.ajax( this ).done( function( data ) { + assert.ok( data.data, "this re-used: JSON results returned (GET, no callback)" ); + } ); + promise.abort = request.abort; + return request; + } ); + promise.abort = request.abort; + return promise; + }, + url: baseURL + "mock.php?action=jsonp", + dataType: "jsonp", + crossDomain: crossDomain, + success: true + } + ]; + } ); + + } ); + + ajaxTest( "jQuery.ajax() - script, Remote", 2, function( assert ) { + return { + setup: function() { + Globals.register( "testBar" ); + }, + url: url( "mock.php?action=testbar" ), + dataType: "script", + success: function() { + assert.strictEqual( window[ "testBar" ], "bar", "Script results returned (GET, no callback)" ); + } + }; + } ); + + ajaxTest( "jQuery.ajax() - script, Remote with POST", 3, function( assert ) { + return { + setup: function() { + Globals.register( "testBar" ); + }, + url: url( "mock.php?action=testbar" ), + type: "POST", + dataType: "script", + success: function( data, status ) { + assert.strictEqual( window[ "testBar" ], "bar", "Script results returned (POST, no callback)" ); + assert.strictEqual( status, "success", "Script results returned (POST, no callback)" ); + } + }; + } ); + + ajaxTest( "jQuery.ajax() - script, Remote with scheme-less URL", 2, function( assert ) { + return { + setup: function() { + Globals.register( "testBar" ); + }, + url: url( "mock.php?action=testbar" ), + dataType: "script", + success: function() { + assert.strictEqual( window[ "testBar" ], "bar", "Script results returned (GET, no callback)" ); + } + }; + } ); + + ajaxTest( "jQuery.ajax() - malformed JSON", 2, function( assert ) { + return { + url: baseURL + "badjson.js", + dataType: "json", + error: function( xhr, msg, detailedMsg ) { + assert.strictEqual( msg, "parsererror", "A parse error occurred." ); + assert.ok( /(invalid|error|exception)/i.test( detailedMsg ), "Detailed parsererror message provided" ); + } + }; + } ); + + ajaxTest( "jQuery.ajax() - script by content-type", 2, function() { + return [ + { + url: baseURL + "mock.php?action=script", + data: { + "header": "script" + }, + success: true + }, + { + url: baseURL + "mock.php?action=script", + data: { + "header": "ecma" + }, + success: true + } + ]; + } ); + + ajaxTest( "jQuery.ajax() - JSON by content-type", 5, function( assert ) { + return { + url: baseURL + "mock.php?action=json", + data: { + "header": "json", + "array": "1" + }, + success: function( json ) { + assert.ok( json.length >= 2, "Check length" ); + assert.strictEqual( json[ 0 ][ "name" ], "John", "Check JSON: first, name" ); + assert.strictEqual( json[ 0 ][ "age" ], 21, "Check JSON: first, age" ); + assert.strictEqual( json[ 1 ][ "name" ], "Peter", "Check JSON: second, name" ); + assert.strictEqual( json[ 1 ][ "age" ], 25, "Check JSON: second, age" ); + } + }; + } ); + + ajaxTest( "jQuery.ajax() - JSON by content-type disabled with options", 6, function( assert ) { + return { + url: url( "mock.php?action=json" ), + data: { + "header": "json", + "array": "1" + }, + contents: { + "json": false + }, + success: function( text ) { + assert.strictEqual( typeof text, "string", "json wasn't auto-determined" ); + var json = JSON.parse( text ); + assert.ok( json.length >= 2, "Check length" ); + assert.strictEqual( json[ 0 ][ "name" ], "John", "Check JSON: first, name" ); + assert.strictEqual( json[ 0 ][ "age" ], 21, "Check JSON: first, age" ); + assert.strictEqual( json[ 1 ][ "name" ], "Peter", "Check JSON: second, name" ); + assert.strictEqual( json[ 1 ][ "age" ], 25, "Check JSON: second, age" ); + } + }; + } ); + + ajaxTest( "jQuery.ajax() - simple get", 1, function( assert ) { + return { + type: "GET", + url: url( "mock.php?action=name&name=foo" ), + success: function( msg ) { + assert.strictEqual( msg, "bar", "Check for GET" ); + } + }; + } ); + + ajaxTest( "jQuery.ajax() - simple post", 1, function( assert ) { + return { + type: "POST", + url: url( "mock.php?action=name" ), + data: "name=peter", + success: function( msg ) { + assert.strictEqual( msg, "pan", "Check for POST" ); + } + }; + } ); + + ajaxTest( "jQuery.ajax() - data option - empty bodies for non-GET requests", 1, function( assert ) { + return { + url: baseURL + "mock.php?action=echoData", + data: undefined, + type: "post", + success: function( result ) { + assert.strictEqual( result, "" ); + } + }; + } ); + + ajaxTest( "jQuery.ajax() - data - x-www-form-urlencoded (gh-2658)", 1, function( assert ) { + return { + url: "bogus.html", + data: { devo: "A Beautiful World" }, + type: "post", + beforeSend: function( _, s ) { + assert.strictEqual( s.data, "devo=A+Beautiful+World", "data is '+'-encoded" ); + return false; + }, + error: true + }; + } ); + + ajaxTest( "jQuery.ajax() - data - text/plain (gh-2658)", 1, function( assert ) { + return { + url: "bogus.html", + data: { devo: "A Beautiful World" }, + type: "post", + contentType: "text/plain", + beforeSend: function( _, s ) { + assert.strictEqual( s.data, "devo=A%20Beautiful%20World", "data is %20-encoded" ); + return false; + }, + error: true + }; + } ); + + ajaxTest( "jQuery.ajax() - don't escape %20 with contentType override (gh-4119)", 1, function( assert ) { + return { + url: "bogus.html", + contentType: "application/x-www-form-urlencoded", + headers: { "content-type": "application/json" }, + method: "post", + dataType: "json", + data: "{\"val\":\"%20\"}", + beforeSend: function( _, s ) { + assert.strictEqual( s.data, "{\"val\":\"%20\"}", "data is not %20-encoded" ); + return false; + }, + error: true + }; + } ); + + ajaxTest( "jQuery.ajax() - escape %20 with contentType override (gh-4119)", 1, function( assert ) { + return { + url: "bogus.html", + contentType: "application/json", + headers: { "content-type": "application/x-www-form-urlencoded" }, + method: "post", + dataType: "json", + data: "{\"val\":\"%20\"}", + beforeSend: function( _, s ) { + assert.strictEqual( s.data, "{\"val\":\"+\"}", "data is %20-encoded" ); + return false; + }, + error: true + }; + } ); + + ajaxTest( "jQuery.ajax() - override contentType with header (gh-4119)", 1, function( assert ) { + return { + url: "bogus.html", + contentType: "application/json", + headers: { "content-type": "application/x-www-form-urlencoded" }, + beforeSend: function( _, s ) { + assert.strictEqual( s.contentType, "application/x-www-form-urlencoded", + "contentType is overwritten" ); + return false; + }, + error: true + }; + } ); + + ajaxTest( "jQuery.ajax() - data - no processing POST", 1, function( assert ) { + return { + url: "bogus.html", + data: { devo: "A Beautiful World" }, + type: "post", + contentType: "x-special-sauce", + processData: false, + beforeSend: function( _, s ) { + assert.deepEqual( s.data, { devo: "A Beautiful World" }, "data is not processed" ); + return false; + }, + error: true + }; + } ); + + ajaxTest( "jQuery.ajax() - data - no processing GET", 1, function( assert ) { + return { + url: "bogus.html", + data: { devo: "A Beautiful World" }, + type: "get", + contentType: "x-something-else", + processData: false, + beforeSend: function( _, s ) { + assert.deepEqual( s.data, { devo: "A Beautiful World" }, "data is not processed" ); + return false; + }, + error: true + }; + } ); + + ajaxTest( "jQuery.ajax() - data - process string with GET", 2, function( assert ) { + return { + url: "bogus.html", + data: "a=1&b=2", + type: "get", + contentType: "x-something-else", + processData: false, + beforeSend: function( _, s ) { + assert.equal( s.url, "bogus.html?a=1&b=2", "added data to url" ); + assert.equal( s.data, undefined, "removed data from settings" ); + return false; + }, + error: true + }; + } ); + + var ifModifiedNow = new Date(); + + jQuery.each( + /* jQuery.each arguments start */ + { + " (cache)": true, + " (no cache)": false + }, + function( label, cache ) { + jQuery.each( + { + "If-Modified-Since": "mock.php?action=ims", + "Etag": "mock.php?action=etag" + }, + function( type, url ) { + url = baseURL + url + "&ts=" + ifModifiedNow++; + QUnit.test( "jQuery.ajax() - " + type + " support" + label, function( assert ) { + assert.expect( 4 ); + var done = assert.async(); + jQuery.ajax( { + url: url, + ifModified: true, + cache: cache, + success: function( _, status ) { + assert.strictEqual( status, "success", "Initial status is 'success'" ); + jQuery.ajax( { + url: url, + ifModified: true, + cache: cache, + success: function( data, status, jqXHR ) { + assert.strictEqual( status, "notmodified", "Following status is 'notmodified'" ); + assert.strictEqual( jqXHR.status, 304, "XHR status is 304" ); + assert.equal( data, null, "no response body is given" ); + }, + complete: function() { + done(); + } + } ); + } + } ); + } ); + } + ); + } + /* jQuery.each arguments end */ + ); + + ajaxTest( "jQuery.ajax() - failing cross-domain (non-existing)", 1, function( assert ) { + return { + + // see RFC 2606 + url: "http://example.invalid", + error: function( xhr, _, e ) { + assert.ok( true, "file not found: " + xhr.status + " => " + e ); + } + }; + } ); + + ajaxTest( "jQuery.ajax() - failing cross-domain", 1, function( assert ) { + return { + url: "http://" + externalHost, + error: function( xhr, _, e ) { + assert.ok( true, "access denied: " + xhr.status + " => " + e ); + } + }; + } ); + + ajaxTest( "jQuery.ajax() - atom+xml", 1, function( assert ) { + return { + url: url( "mock.php?action=atom" ), + success: function() { + assert.ok( true, "success" ); + } + }; + } ); + + QUnit.test( "jQuery.ajax() - statusText", function( assert ) { + assert.expect( 3 ); + var done = assert.async(); + jQuery.ajax( url( "mock.php?action=status&code=200&text=Hello" ) ).done( function( _, statusText, jqXHR ) { + assert.strictEqual( statusText, "success", "callback status text ok for success" ); + assert.ok( jqXHR.statusText === "Hello" || jqXHR.statusText === "OK", "jqXHR status text ok for success (" + jqXHR.statusText + ")" ); + jQuery.ajax( url( "mock.php?action=status&code=404&text=World" ) ).fail( function( jqXHR, statusText ) { + assert.strictEqual( statusText, "error", "callback status text ok for error" ); + done(); + } ); + } ); + } ); + + QUnit.test( "jQuery.ajax() - statusCode", function( assert ) { + assert.expect( 20 ); + var done = assert.async(), + count = 12; + + function countComplete() { + if ( !--count ) { + done(); + } + } + + function createStatusCodes( name, isSuccess ) { + name = "Test " + name + " " + ( isSuccess ? "success" : "error" ); + return { + 200: function() { + assert.ok( isSuccess, name ); + }, + 404: function() { + assert.ok( !isSuccess, name ); + } + }; + } + + jQuery.each( + /* jQuery.each arguments start */ + { + "name.html": true, + "404.txt": false + }, + function( uri, isSuccess ) { + jQuery.ajax( url( uri ), { + statusCode: createStatusCodes( "in options", isSuccess ), + complete: countComplete + } ); + + jQuery.ajax( url( uri ), { + complete: countComplete + } ).statusCode( createStatusCodes( "immediately with method", isSuccess ) ); + + jQuery.ajax( url( uri ), { + complete: function( jqXHR ) { + jqXHR.statusCode( createStatusCodes( "on complete", isSuccess ) ); + countComplete(); + } + } ); + + jQuery.ajax( url( uri ), { + complete: function( jqXHR ) { + setTimeout( function() { + jqXHR.statusCode( createStatusCodes( "very late binding", isSuccess ) ); + countComplete(); + }, 100 ); + } + } ); + + jQuery.ajax( url( uri ), { + statusCode: createStatusCodes( "all (options)", isSuccess ), + complete: function( jqXHR ) { + jqXHR.statusCode( createStatusCodes( "all (on complete)", isSuccess ) ); + setTimeout( function() { + jqXHR.statusCode( createStatusCodes( "all (very late binding)", isSuccess ) ); + countComplete(); + }, 100 ); + } + } ).statusCode( createStatusCodes( "all (immediately with method)", isSuccess ) ); + + var testString = ""; + + jQuery.ajax( url( uri ), { + success: function( a, b, jqXHR ) { + assert.ok( isSuccess, "success" ); + var statusCode = {}; + statusCode[ jqXHR.status ] = function() { + testString += "B"; + }; + jqXHR.statusCode( statusCode ); + testString += "A"; + }, + error: function( jqXHR ) { + assert.ok( !isSuccess, "error" ); + var statusCode = {}; + statusCode[ jqXHR.status ] = function() { + testString += "B"; + }; + jqXHR.statusCode( statusCode ); + testString += "A"; + }, + complete: function() { + assert.strictEqual( + testString, + "AB", + "Test statusCode callbacks are ordered like " + ( isSuccess ? "success" : "error" ) + " callbacks" + ); + countComplete(); + } + } ); + + } + /* jQuery.each arguments end*/ + ); + } ); + + ajaxTest( "jQuery.ajax() - transitive conversions", 8, function( assert ) { + return [ + { + url: url( "mock.php?action=json" ), + converters: { + "json myJson": function( data ) { + assert.ok( true, "converter called" ); + return data; + } + }, + dataType: "myJson", + success: function() { + assert.ok( true, "Transitive conversion worked" ); + assert.strictEqual( this.dataTypes[ 0 ], "text", "response was retrieved as text" ); + assert.strictEqual( this.dataTypes[ 1 ], "myjson", "request expected myjson dataType" ); + } + }, + { + url: url( "mock.php?action=json" ), + converters: { + "json myJson": function( data ) { + assert.ok( true, "converter called (*)" ); + return data; + } + }, + contents: false, /* headers are wrong so we ignore them */ + dataType: "* myJson", + success: function() { + assert.ok( true, "Transitive conversion worked (*)" ); + assert.strictEqual( this.dataTypes[ 0 ], "text", "response was retrieved as text (*)" ); + assert.strictEqual( this.dataTypes[ 1 ], "myjson", "request expected myjson dataType (*)" ); + } + } + ]; + } ); + + ajaxTest( "jQuery.ajax() - overrideMimeType", 2, function( assert ) { + return [ + { + url: url( "mock.php?action=json" ), + beforeSend: function( xhr ) { + xhr.overrideMimeType( "application/json" ); + }, + success: function( json ) { + assert.ok( json.data, "Mimetype overridden using beforeSend" ); + } + }, + { + url: url( "mock.php?action=json" ), + mimeType: "application/json", + success: function( json ) { + assert.ok( json.data, "Mimetype overridden using mimeType option" ); + } + } + ]; + } ); + + ajaxTest( "jQuery.ajax() - empty json gets to error callback instead of success callback.", 1, function( assert ) { + return { + url: url( "mock.php?action=echoData" ), + error: function( _, __, error ) { + assert.equal( typeof error === "object", true, "Didn't get back error object for empty json response" ); + }, + dataType: "json" + }; + } ); + + ajaxTest( "#2688 - jQuery.ajax() - beforeSend, cancel request", 2, function( assert ) { + return { + create: function() { + return jQuery.ajax( { + url: url( "name.html" ), + beforeSend: function() { + assert.ok( true, "beforeSend got called, canceling" ); + return false; + }, + success: function() { + assert.ok( false, "request didn't get canceled" ); + }, + complete: function() { + assert.ok( false, "request didn't get canceled" ); + }, + error: function() { + assert.ok( false, "request didn't get canceled" ); + } + } ); + }, + fail: function( _, reason ) { + assert.strictEqual( reason, "canceled", "canceled request must fail with 'canceled' status text" ); + } + }; + } ); + + ajaxTest( "#2806 - jQuery.ajax() - data option - evaluate function values", 1, function( assert ) { + return { + url: baseURL + "mock.php?action=echoQuery", + data: { + key: function() { + return "value"; + } + }, + success: function( result ) { + assert.strictEqual( result, "action=echoQuery&key=value" ); + } + }; + } ); + + QUnit.test( "#7531 - jQuery.ajax() - Location object as url", function( assert ) { + assert.expect( 1 ); + + var xhr, + success = false; + try { + xhr = jQuery.ajax( { + url: window.location + } ); + success = true; + xhr.abort(); + } catch ( e ) { + + } + assert.ok( success, "document.location did not generate exception" ); + } ); + + jQuery.each( [ " - Same Domain", " - Cross Domain" ], function( crossDomain, label ) { + ajaxTest( "#7578 - jQuery.ajax() - JSONP - default for cache option" + label, 1, function( assert ) { + return { + url: baseURL + "mock.php?action=jsonp", + dataType: "jsonp", + crossDomain: crossDomain, + beforeSend: function() { + assert.strictEqual( this.cache, false, "cache must be false on JSON request" ); + return false; + }, + error: true + }; + } ); + } ); + + ajaxTest( "#8107 - jQuery.ajax() - multiple method signatures introduced in 1.5", 4, function( assert ) { + return [ + { + create: function() { + return jQuery.ajax(); + }, + done: function() { + assert.ok( true, "With no arguments" ); + } + }, + { + create: function() { + return jQuery.ajax( baseURL + "name.html" ); + }, + done: function() { + assert.ok( true, "With only string URL argument" ); + } + }, + { + create: function() { + return jQuery.ajax( baseURL + "name.html", {} ); + }, + done: function() { + assert.ok( true, "With string URL param and map" ); + } + }, + { + create: function( options ) { + return jQuery.ajax( options ); + }, + url: baseURL + "name.html", + success: function() { + assert.ok( true, "With only map" ); + } + } + ]; + } ); + + jQuery.each( [ " - Same Domain", " - Cross Domain" ], function( crossDomain, label ) { + ajaxTest( "#8205 - jQuery.ajax() - JSONP - re-use callbacks name" + label, 4, function( assert ) { + return { + url: baseURL + "mock.php?action=jsonp", + dataType: "jsonp", + crossDomain: crossDomain, + beforeSend: function( jqXHR, s ) { + s.callback = s.jsonpCallback; + + assert.ok( this.callback in window, "JSONP callback name is in the window" ); + }, + success: function() { + var previous = this; + + assert.strictEqual( + previous.jsonpCallback, + undefined, + "jsonpCallback option is set back to default in callbacks" + ); + + assert.ok( + !( this.callback in window ), + "JSONP callback name was removed from the window" + ); + + jQuery.ajax( { + url: baseURL + "mock.php?action=jsonp", + dataType: "jsonp", + crossDomain: crossDomain, + beforeSend: function() { + assert.strictEqual( this.jsonpCallback, previous.callback, "JSONP callback name is re-used" ); + return false; + } + } ); + } + }; + } ); + } ); + + QUnit.test( "#9887 - jQuery.ajax() - Context with circular references (#9887)", function( assert ) { + assert.expect( 2 ); + + var success = false, + context = {}; + context.field = context; + try { + jQuery.ajax( "non-existing", { + context: context, + beforeSend: function() { + assert.ok( this === context, "context was not deep extended" ); + return false; + } + } ); + success = true; + } catch ( e ) { + console.log( e ); + } + assert.ok( success, "context with circular reference did not generate an exception" ); + } ); + + jQuery.each( [ "as argument", "in settings object" ], function( inSetting, title ) { + + function request( assert, url, test ) { + return { + create: function() { + return jQuery.ajax( inSetting ? { url: url } : url ); + }, + done: function() { + assert.ok( true, ( test || url ) + " " + title ); + } + }; + } + + ajaxTest( "#10093 - jQuery.ajax() - falsy url " + title, 4, function( assert ) { + return [ + request( assert, "", "empty string" ), + request( assert, false ), + request( assert, null ), + request( assert, undefined ) + ]; + } ); + } ); + + ajaxTest( "#11151 - jQuery.ajax() - parse error body", 2, function( assert ) { + return { + url: url( "mock.php?action=error&json=1" ), + dataFilter: function( string ) { + assert.ok( false, "dataFilter called" ); + return string; + }, + error: function( jqXHR ) { + assert.strictEqual( jqXHR.responseText, "{ \"code\": 40, \"message\": \"Bad Request\" }", "Error body properly set" ); + assert.deepEqual( jqXHR.responseJSON, { code: 40, message: "Bad Request" }, "Error body properly parsed" ); + } + }; + } ); + + ajaxTest( "#11426 - jQuery.ajax() - loading binary data shouldn't throw an exception in IE", 1, function( assert ) { + return { + url: url( "1x1.jpg" ), + success: function( data ) { + assert.ok( data === undefined || /JFIF/.test( data ), "success callback reached" ); + } + }; + } ); + +if ( typeof window.ArrayBuffer === "undefined" || typeof new XMLHttpRequest().responseType !== "string" ) { + + QUnit.skip( "No ArrayBuffer support in XHR", jQuery.noop ); +} else { + + // No built-in support for binary data, but it's easy to add via a prefilter + jQuery.ajaxPrefilter( "arraybuffer", function( s ) { + s.xhrFields = { responseType: "arraybuffer" }; + s.responseFields.arraybuffer = "response"; + s.converters[ "binary arraybuffer" ] = true; + } ); + + ajaxTest( "gh-2498 - jQuery.ajax() - binary data shouldn't throw an exception", 2, function( assert ) { + return { + url: url( "1x1.jpg" ), + dataType: "arraybuffer", + success: function( data, s, jqxhr ) { + assert.ok( data instanceof window.ArrayBuffer, "correct data type" ); + assert.ok( jqxhr.response instanceof window.ArrayBuffer, "data in jQXHR" ); + } + }; + } ); +} + + QUnit.test( "#11743 - jQuery.ajax() - script, throws exception", function( assert ) { + assert.expect( 1 ); + var done = assert.async(); + var onerror = window.onerror; + window.onerror = function() { + assert.ok( true, "Exception thrown" ); + window.onerror = onerror; + done(); + }; + jQuery.ajax( { + url: baseURL + "badjson.js", + dataType: "script", + throws: true + } ); + } ); + + jQuery.each( [ "method", "type" ], function( _, globalOption ) { + function request( assert, option ) { + var options = { + url: url( "mock.php?action=echoData" ), + data: "hello", + success: function( msg ) { + assert.strictEqual( msg, "hello", "Check for POST (no override)" ); + } + }; + if ( option ) { + options[ option ] = "GET"; + options.success = function( msg ) { + assert.strictEqual( msg, "", "Check for no POST (overriding with " + option + ")" ); + }; + } + return options; + } + + ajaxTest( + "#12004 - jQuery.ajax() - method is an alias of type - " + + globalOption + " set globally", 3, + function( assert ) { + return { + setup: function() { + var options = {}; + options[ globalOption ] = "POST"; + jQuery.ajaxSetup( options ); + }, + requests: [ + request( assert, "type" ), + request( assert, "method" ), + request( assert ) + ] + }; + } + ); + } ); + + ajaxTest( "#13276 - jQuery.ajax() - compatibility between XML documents from ajax requests and parsed string", 1, function( assert ) { + return { + url: baseURL + "dashboard.xml", + dataType: "xml", + success: function( ajaxXML ) { + var parsedXML = jQuery( jQuery.parseXML( "blibli" ) ).find( "tab" ); + ajaxXML = jQuery( ajaxXML ); + try { + ajaxXML.find( "infowindowtab" ).append( parsedXML ); + } catch ( e ) { + assert.strictEqual( e, undefined, "error" ); + return; + } + assert.strictEqual( ajaxXML.find( "tab" ).length, 3, "Parsed node was added properly" ); + } + }; + } ); + + ajaxTest( "#13292 - jQuery.ajax() - converter is bypassed for 204 requests", 3, function( assert ) { + return { + url: baseURL + "mock.php?action=status&code=204&text=No+Content", + dataType: "testing", + converters: { + "* testing": function() { + throw "converter was called"; + } + }, + success: function( data, status, jqXHR ) { + assert.strictEqual( jqXHR.status, 204, "status code is 204" ); + assert.strictEqual( status, "nocontent", "status text is 'nocontent'" ); + assert.strictEqual( data, undefined, "data is undefined" ); + }, + error: function( _, status, error ) { + assert.ok( false, "error" ); + assert.strictEqual( status, "parsererror", "Parser Error" ); + assert.strictEqual( error, "converter was called", "Converter was called" ); + } + }; + } ); + + ajaxTest( "#13388 - jQuery.ajax() - responseXML", 3, function( assert ) { + return { + url: url( "with_fries.xml" ), + dataType: "xml", + success: function( resp, _, jqXHR ) { + assert.notStrictEqual( resp, undefined, "XML document exists" ); + assert.ok( "responseXML" in jqXHR, "jqXHR.responseXML exists" ); + assert.strictEqual( resp, jqXHR.responseXML, "jqXHR.responseXML is set correctly" ); + } + }; + } ); + + ajaxTest( "#13922 - jQuery.ajax() - converter is bypassed for HEAD requests", 3, function( assert ) { + return { + url: baseURL + "mock.php?action=json", + method: "HEAD", + data: { + header: "yes" + }, + converters: { + "text json": function() { + throw "converter was called"; + } + }, + success: function( data, status ) { + assert.ok( true, "success" ); + assert.strictEqual( status, "nocontent", "data is undefined" ); + assert.strictEqual( data, undefined, "data is undefined" ); + }, + error: function( _, status, error ) { + assert.ok( false, "error" ); + assert.strictEqual( status, "parsererror", "Parser Error" ); + assert.strictEqual( error, "converter was called", "Converter was called" ); + } + }; + } ); + + // Chrome 78 dropped support for synchronous XHR requests inside of + // beforeunload, unload, pagehide, and visibilitychange event handlers. + // See https://bugs.chromium.org/p/chromium/issues/detail?id=952452 + // Safari 13 did similar changes. The below check will catch them both. + // Edge Legacy fakes Chrome which fakes Safari in their user agents so we need + // to exclude Edge specifically here so that the test continues to run there. + if ( !/safari/i.test( navigator.userAgent ) || /edge\//i.test( navigator.userAgent ) ) { + testIframe( + "#14379 - jQuery.ajax() on unload", + "ajax/onunload.html", + function( assert, jQuery, window, document, status ) { + assert.expect( 1 ); + assert.strictEqual( status, "success", "Request completed" ); + } + ); + } + + ajaxTest( "#14683 - jQuery.ajax() - Exceptions thrown synchronously by xhr.send should be caught", 4, function( assert ) { + return [ { + url: baseURL + "mock.php?action=echoData", + method: "POST", + data: { + toString: function() { + throw "Can't parse"; + } + }, + processData: false, + done: function( data ) { + assert.ok( false, "done: " + data ); + }, + fail: function( jqXHR, status, error ) { + assert.ok( true, "exception caught: " + error ); + assert.strictEqual( jqXHR.status, 0, "proper status code" ); + assert.strictEqual( status, "error", "proper status" ); + } + }, { + url: "http://" + externalHost + ":80q", + done: function( data ) { + assert.ok( false, "done: " + data ); + }, + fail: function( _, status, error ) { + assert.ok( true, "fail: " + status + " - " + error ); + } + } ]; + } ); + + ajaxTest( "gh-2587 - when content-type not xml, but looks like one", 1, function( assert ) { + return { + url: url( "mock.php?action=contentType" ), + data: { + contentType: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + "response": "" + }, + success: function( result ) { + assert.strictEqual( + typeof result, + "string", + "Should handle it as a string, not xml" + ); + } + }; + } ); + + ajaxTest( "gh-2587 - when content-type not xml, but looks like one", 1, function( assert ) { + return { + url: url( "mock.php?action=contentType" ), + data: { + contentType: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + "response": "" + }, + success: function( result ) { + assert.strictEqual( + typeof result, + "string", + "Should handle it as a string, not xml" + ); + } + }; + } ); + + ajaxTest( "gh-2587 - when content-type not json, but looks like one", 1, function( assert ) { + return { + url: url( "mock.php?action=contentType" ), + data: { + contentType: "test/jsontest", + "response": JSON.stringify( { test: "test" } ) + }, + success: function( result ) { + assert.strictEqual( + typeof result, + "string", + "Should handle it as a string, not json" + ); + } + }; + } ); + + ajaxTest( "gh-2587 - when content-type not html, but looks like one", 1, function( assert ) { + return { + url: url( "mock.php?action=contentType" ), + data: { + contentType: "test/htmltest", + "response": "

    test

    " + }, + success: function( result ) { + assert.strictEqual( + typeof result, + "string", + "Should handle it as a string, not html" + ); + } + }; + } ); + + ajaxTest( "gh-2587 - when content-type not javascript, but looks like one", 1, function( assert ) { + return { + url: url( "mock.php?action=contentType" ), + data: { + contentType: "test/testjavascript", + "response": "alert(1)" + }, + success: function( result ) { + assert.strictEqual( + typeof result, + "string", + "Should handle it as a string, not javascript" + ); + } + }; + } ); + + ajaxTest( "gh-2587 - when content-type not ecmascript, but looks like one", 1, function( assert ) { + return { + url: url( "mock.php?action=contentType" ), + data: { + contentType: "test/testjavascript", + "response": "alert(1)" + }, + success: function( result ) { + assert.strictEqual( + typeof result, + "string", + "Should handle it as a string, not ecmascript" + ); + } + }; + } ); + +//----------- jQuery.ajaxPrefilter() + + ajaxTest( "jQuery.ajaxPrefilter() - abort", 1, function( assert ) { + return { + dataType: "prefix", + setup: function() { + + // Ensure prefix does not throw an error + jQuery.ajaxPrefilter( "+prefix", function( options, _, jqXHR ) { + if ( options.abortInPrefilter ) { + jqXHR.abort(); + } + } ); + }, + abortInPrefilter: true, + error: function() { + assert.ok( false, "error callback called" ); + }, + fail: function( _, reason ) { + assert.strictEqual( reason, "canceled", "Request aborted by the prefilter must fail with 'canceled' status text" ); + } + }; + } ); + +//----------- jQuery.ajaxSetup() + + QUnit.test( "jQuery.ajaxSetup()", function( assert ) { + assert.expect( 1 ); + var done = assert.async(); + jQuery.ajaxSetup( { + url: url( "mock.php?action=name&name=foo" ), + success: function( msg ) { + assert.strictEqual( msg, "bar", "Check for GET" ); + done(); + } + } ); + jQuery.ajax(); + } ); + + QUnit.test( "jQuery.ajaxSetup({ timeout: Number }) - with global timeout", function( assert ) { + assert.expect( 2 ); + var done = assert.async(); + var passed = 0, + pass = function() { + assert.ok( passed++ < 2, "Error callback executed" ); + if ( passed === 2 ) { + jQuery( document ).off( "ajaxError.setupTest" ); + done(); + } + }, + fail = function( a, b ) { + assert.ok( false, "Check for timeout failed " + a + " " + b ); + done(); + }; + + jQuery( document ).on( "ajaxError.setupTest", pass ); + + jQuery.ajaxSetup( { + timeout: 1000 + } ); + + jQuery.ajax( { + type: "GET", + url: url( "mock.php?action=wait&wait=5" ), + error: pass, + success: fail + } ); + } ); + + QUnit.test( "jQuery.ajaxSetup({ timeout: Number }) with localtimeout", function( assert ) { + assert.expect( 1 ); + var done = assert.async(); + jQuery.ajaxSetup( { + timeout: 50 + } ); + jQuery.ajax( { + type: "GET", + timeout: 15000, + url: url( "mock.php?action=wait&wait=1" ), + error: function() { + assert.ok( false, "Check for local timeout failed" ); + done(); + }, + success: function() { + assert.ok( true, "Check for local timeout" ); + done(); + } + } ); + } ); + +//----------- jQuery.domManip() + + QUnit.test( "#11264 - jQuery.domManip() - no side effect because of ajaxSetup or global events", function( assert ) { + assert.expect( 1 ); + + jQuery.ajaxSetup( { + type: "POST" + } ); + + jQuery( document ).on( "ajaxStart ajaxStop", function() { + assert.ok( false, "Global event triggered" ); + } ); + + jQuery( "#qunit-fixture" ).append( "" ); + + jQuery( document ).off( "ajaxStart ajaxStop" ); + } ); + + QUnit.test( + "jQuery#load() - always use GET method even if it overrided through ajaxSetup (#11264)", + function( assert ) { + assert.expect( 1 ); + var done = assert.async(); + + jQuery.ajaxSetup( { + type: "POST" + } ); + + jQuery( "#qunit-fixture" ).load( baseURL + "mock.php?action=echoMethod", function( method ) { + assert.equal( method, "GET" ); + done(); + } ); + } + ); + + QUnit.test( + "jQuery#load() - should resolve with correct context", + function( assert ) { + assert.expect( 2 ); + var done = assert.async(); + var ps = jQuery( "

    " ); + var i = 0; + + ps.appendTo( "#qunit-fixture" ); + + ps.load( baseURL + "mock.php?action=echoMethod", function() { + assert.strictEqual( this, ps[ i++ ] ); + + if ( i === 2 ) { + done(); + } + } ); + } + ); + + QUnit.test( + "#11402 - jQuery.domManip() - script in comments are properly evaluated", + function( assert ) { + assert.expect( 2 ); + jQuery( "#qunit-fixture" ).load( baseURL + "cleanScript.html", assert.async() ); + } + ); + +//----------- jQuery.get() + + QUnit.test( "jQuery.get( String, Hash, Function ) - parse xml and use text() on nodes", function( assert ) { + assert.expect( 2 ); + var done = assert.async(); + jQuery.get( url( "dashboard.xml" ), function( xml ) { + var content = []; + jQuery( "tab", xml ).each( function() { + content.push( jQuery( this ).text() ); + } ); + assert.strictEqual( content[ 0 ], "blabla", "Check first tab" ); + assert.strictEqual( content[ 1 ], "blublu", "Check second tab" ); + done(); + } ); + } ); + + QUnit.test( "#8277 - jQuery.get( String, Function ) - data in ajaxSettings", function( assert ) { + assert.expect( 1 ); + var done = assert.async(); + jQuery.ajaxSetup( { + data: "helloworld" + } ); + jQuery.get( url( "mock.php?action=echoQuery" ), function( data ) { + assert.ok( /helloworld$/.test( data ), "Data from ajaxSettings was used" ); + done(); + } ); + } ); + +//----------- jQuery.getJSON() + + QUnit.test( "jQuery.getJSON( String, Hash, Function ) - JSON array", function( assert ) { + assert.expect( 5 ); + var done = assert.async(); + jQuery.getJSON( + url( "mock.php?action=json" ), + { + "array": "1" + }, + function( json ) { + assert.ok( json.length >= 2, "Check length" ); + assert.strictEqual( json[ 0 ][ "name" ], "John", "Check JSON: first, name" ); + assert.strictEqual( json[ 0 ][ "age" ], 21, "Check JSON: first, age" ); + assert.strictEqual( json[ 1 ][ "name" ], "Peter", "Check JSON: second, name" ); + assert.strictEqual( json[ 1 ][ "age" ], 25, "Check JSON: second, age" ); + done(); + } + ); + } ); + + QUnit.test( "jQuery.getJSON( String, Function ) - JSON object", function( assert ) { + assert.expect( 2 ); + var done = assert.async(); + jQuery.getJSON( url( "mock.php?action=json" ), function( json ) { + if ( json && json[ "data" ] ) { + assert.strictEqual( json[ "data" ][ "lang" ], "en", "Check JSON: lang" ); + assert.strictEqual( json[ "data" ].length, 25, "Check JSON: length" ); + done(); + } + } ); + } ); + + QUnit.test( "jQuery.getJSON( String, Function ) - JSON object with absolute url to local content", function( assert ) { + assert.expect( 2 ); + var done = assert.async(); + var absoluteUrl = url( "mock.php?action=json" ); + + // Make a relative URL absolute relative to the document location + if ( !/^[a-z][a-z0-9+.-]*:/i.test( absoluteUrl ) ) { + + // An absolute path replaces everything after the host + if ( absoluteUrl.charAt( 0 ) === "/" ) { + absoluteUrl = window.location.href.replace( /(:\/*[^/]*).*$/, "$1" ) + absoluteUrl; + + // A relative path replaces the last slash-separated path segment + } else { + absoluteUrl = window.location.href.replace( /[^/]*$/, "" ) + absoluteUrl; + } + } + + jQuery.getJSON( absoluteUrl, function( json ) { + assert.strictEqual( json.data.lang, "en", "Check JSON: lang" ); + assert.strictEqual( json.data.length, 25, "Check JSON: length" ); + done(); + } ); + } ); + +//----------- jQuery.getScript() + + QUnit.test( "jQuery.getScript( String, Function ) - with callback", + function( assert ) { + assert.expect( 2 ); + var done = assert.async(); + + Globals.register( "testBar" ); + jQuery.getScript( url( "mock.php?action=testbar" ), function() { + assert.strictEqual( window[ "testBar" ], "bar", "Check if script was evaluated" ); + done(); + } ); + } + ); + + QUnit.test( "jQuery.getScript( String, Function ) - no callback", function( assert ) { + assert.expect( 1 ); + Globals.register( "testBar" ); + jQuery.getScript( url( "mock.php?action=testbar" ) ).done( assert.async() ); + } ); + + QUnit.test( "#8082 - jQuery.getScript( String, Function ) - source as responseText", function( assert ) { + assert.expect( 2 ); + var done = assert.async(); + + Globals.register( "testBar" ); + jQuery.getScript( url( "mock.php?action=testbar" ), function( data, _, jqXHR ) { + assert.strictEqual( data, jqXHR.responseText, "Same-domain script requests returns the source of the script" ); + done(); + } ); + } ); + + QUnit.test( "jQuery.getScript( Object ) - with callback", function( assert ) { + assert.expect( 2 ); + var done = assert.async(); + + Globals.register( "testBar" ); + jQuery.getScript( { + url: url( "mock.php?action=testbar" ), + success: function() { + assert.strictEqual( window[ "testBar" ], "bar", "Check if script was evaluated" ); + done(); + } + } ); + } ); + + QUnit.test( "jQuery.getScript( Object ) - no callback", function( assert ) { + assert.expect( 1 ); + Globals.register( "testBar" ); + jQuery.getScript( { url: url( "mock.php?action=testbar" ) } ).done( assert.async() ); + } ); + +// //----------- jQuery.fn.load() + + // check if load can be called with only url + QUnit.test( "jQuery.fn.load( String )", function( assert ) { + assert.expect( 2 ); + jQuery.ajaxSetup( { + beforeSend: function() { + assert.strictEqual( this.type, "GET", "no data means GET request" ); + } + } ); + jQuery( "#first" ).load( baseURL + "name.html", assert.async() ); + } ); + + QUnit.test( "jQuery.fn.load() - 404 error callbacks", function( assert ) { + assert.expect( 6 ); + var done = assert.async(); + + addGlobalEvents( "ajaxStart ajaxStop ajaxSend ajaxComplete ajaxError", assert )(); + jQuery( document ).on( "ajaxStop", done ); + jQuery( "
    " ).load( baseURL + "404.txt", function() { + assert.ok( true, "complete" ); + } ); + } ); + + // check if load can be called with url and null data + QUnit.test( "jQuery.fn.load( String, null )", function( assert ) { + assert.expect( 2 ); + jQuery.ajaxSetup( { + beforeSend: function() { + assert.strictEqual( this.type, "GET", "no data means GET request" ); + } + } ); + jQuery( "#first" ).load( baseURL + "name.html", null, assert.async() ); + } ); + + // check if load can be called with url and undefined data + QUnit.test( "jQuery.fn.load( String, undefined )", function( assert ) { + assert.expect( 2 ); + jQuery.ajaxSetup( { + beforeSend: function() { + assert.strictEqual( this.type, "GET", "no data means GET request" ); + } + } ); + jQuery( "#first" ).load( baseURL + "name.html", undefined, assert.async() ); + } ); + + // check if load can be called with only url + QUnit.test( "jQuery.fn.load( URL_SELECTOR )", function( assert ) { + assert.expect( 1 ); + var done = assert.async(); + jQuery( "#first" ).load( baseURL + "test3.html div.user", function() { + assert.strictEqual( jQuery( this ).children( "div" ).length, 2, "Verify that specific elements were injected" ); + done(); + } ); + } ); + + // Selector should be trimmed to avoid leading spaces (#14773) + QUnit.test( "jQuery.fn.load( URL_SELECTOR with spaces )", function( assert ) { + assert.expect( 1 ); + var done = assert.async(); + jQuery( "#first" ).load( baseURL + "test3.html #superuser ", function() { + assert.strictEqual( jQuery( this ).children( "div" ).length, 1, "Verify that specific elements were injected" ); + done(); + } ); + } ); + + // Selector should be trimmed to avoid leading spaces (#14773) + // Selector should include any valid non-HTML whitespace (#3003) + QUnit.test( "jQuery.fn.load( URL_SELECTOR with non-HTML whitespace(#3003) )", function( assert ) { + assert.expect( 1 ); + var done = assert.async(); + jQuery( "#first" ).load( baseURL + "test3.html #whitespace\\\\xA0 ", function() { + assert.strictEqual( jQuery( this ).children( "div" ).length, 1, "Verify that specific elements were injected" ); + done(); + } ); + } ); + + QUnit.test( "jQuery.fn.load( String, Function ) - simple: inject text into DOM", function( assert ) { + assert.expect( 2 ); + var done = assert.async(); + jQuery( "#first" ).load( url( "name.html" ), function() { + assert.ok( /^ERROR/.test( jQuery( "#first" ).text() ), "Check if content was injected into the DOM" ); + done(); + } ); + } ); + + QUnit.test( "jQuery.fn.load( String, Function ) - check scripts", function( assert ) { + assert.expect( 7 ); + var done = assert.async(); + var verifyEvaluation = function() { + assert.strictEqual( window[ "testBar" ], "bar", "Check if script src was evaluated after load" ); + assert.strictEqual( jQuery( "#ap" ).html(), "bar", "Check if script evaluation has modified DOM" ); + done(); + }; + + Globals.register( "testFoo" ); + Globals.register( "testBar" ); + + jQuery( "#first" ).load( url( "mock.php?action=testHTML&baseURL=" + baseURL ), function() { + assert.ok( jQuery( "#first" ).html().match( /^html text/ ), "Check content after loading html" ); + assert.strictEqual( jQuery( "#foo" ).html(), "foo", "Check if script evaluation has modified DOM" ); + assert.strictEqual( window[ "testFoo" ], "foo", "Check if script was evaluated after load" ); + setTimeout( verifyEvaluation, 600 ); + } ); + } ); + + QUnit.test( "jQuery.fn.load( String, Function ) - check file with only a script tag", function( assert ) { + assert.expect( 3 ); + var done = assert.async(); + Globals.register( "testFoo" ); + + jQuery( "#first" ).load( url( "test2.html" ), function() { + assert.strictEqual( jQuery( "#foo" ).html(), "foo", "Check if script evaluation has modified DOM" ); + assert.strictEqual( window[ "testFoo" ], "foo", "Check if script was evaluated after load" ); + done(); + } ); + } ); + + QUnit.test( "jQuery.fn.load( String, Function ) - dataFilter in ajaxSettings", function( assert ) { + assert.expect( 2 ); + var done = assert.async(); + jQuery.ajaxSetup( { + dataFilter: function() { + return "Hello World"; + } + } ); + jQuery( "
    " ).load( url( "name.html" ), function( responseText ) { + assert.strictEqual( jQuery( this ).html(), "Hello World", "Test div was filled with filtered data" ); + assert.strictEqual( responseText, "Hello World", "Test callback receives filtered data" ); + done(); + } ); + } ); + + QUnit.test( "jQuery.fn.load( String, Object, Function )", function( assert ) { + assert.expect( 2 ); + var done = assert.async(); + jQuery( "
    " ).load( url( "mock.php?action=echoHtml" ), { + "bar": "ok" + }, function() { + var $node = jQuery( this ); + assert.strictEqual( $node.find( "#method" ).text(), "POST", "Check method" ); + assert.strictEqual( $node.find( "#data" ).text(), "bar=ok", "Check if data is passed correctly" ); + done(); + } ); + } ); + + QUnit.test( "jQuery.fn.load( String, String, Function )", function( assert ) { + assert.expect( 2 ); + var done = assert.async(); + + jQuery( "
    " ).load( url( "mock.php?action=echoHtml" ), "foo=3&bar=ok", function() { + var $node = jQuery( this ); + assert.strictEqual( $node.find( "#method" ).text(), "GET", "Check method" ); + assert.ok( $node.find( "#query" ).text().match( /foo=3&bar=ok/ ), "Check if a string of data is passed correctly" ); + done(); + } ); + } ); + + QUnit.test( "jQuery.fn.load() - callbacks get the correct parameters", function( assert ) { + assert.expect( 8 ); + var completeArgs = {}, + done = assert.async(); + + jQuery.ajaxSetup( { + success: function( _, status, jqXHR ) { + completeArgs[ this.url ] = [ jqXHR.responseText, status, jqXHR ]; + }, + error: function( jqXHR, status ) { + completeArgs[ this.url ] = [ jqXHR.responseText, status, jqXHR ]; + } + } ); + + jQuery.when.apply( + jQuery, + jQuery.map( [ + { + type: "success", + url: baseURL + "mock.php?action=echoQuery&arg=pop" + }, + { + type: "error", + url: baseURL + "404.txt" + } + ], + function( options ) { + return jQuery.Deferred( function( defer ) { + jQuery( "#foo" ).load( options.url, function() { + var args = arguments; + assert.strictEqual( completeArgs[ options.url ].length, args.length, "same number of arguments (" + options.type + ")" ); + jQuery.each( completeArgs[ options.url ], function( i, value ) { + assert.strictEqual( args[ i ], value, "argument #" + i + " is the same (" + options.type + ")" ); + } ); + defer.resolve(); + } ); + } ); + } ) + ).always( done ); + } ); + + QUnit.test( "#2046 - jQuery.fn.load( String, Function ) with ajaxSetup on dataType json", function( assert ) { + assert.expect( 1 ); + var done = assert.async(); + + jQuery.ajaxSetup( { + dataType: "json" + } ); + jQuery( document ).on( "ajaxComplete", function( e, xml, s ) { + assert.strictEqual( s.dataType, "html", "Verify the load() dataType was html" ); + jQuery( document ).off( "ajaxComplete" ); + done(); + } ); + jQuery( "#first" ).load( baseURL + "test3.html" ); + } ); + + QUnit.test( "#10524 - jQuery.fn.load() - data specified in ajaxSettings is merged in", function( assert ) { + assert.expect( 1 ); + var done = assert.async(); + + var data = { + "baz": 1 + }; + jQuery.ajaxSetup( { + data: { + "foo": "bar" + } + } ); + jQuery( "#foo" ).load( baseURL + "mock.php?action=echoQuery", data ); + jQuery( document ).on( "ajaxComplete", function( event, jqXHR, options ) { + assert.ok( ~options.data.indexOf( "foo=bar" ), "Data from ajaxSettings was used" ); + done(); + } ); + } ); + +// //----------- jQuery.post() + + QUnit.test( "jQuery.post() - data", function( assert ) { + assert.expect( 3 ); + var done = assert.async(); + + jQuery.when( + jQuery.post( + url( "mock.php?action=xml" ), + { + cal: "5-2" + }, + function( xml ) { + jQuery( "math", xml ).each( function() { + assert.strictEqual( jQuery( "calculation", this ).text(), "5-2", "Check for XML" ); + assert.strictEqual( jQuery( "result", this ).text(), "3", "Check for XML" ); + } ); + } + ), + jQuery.ajax( { + url: url( "mock.php?action=echoData" ), + type: "POST", + data: { + "test": { + "length": 7, + "foo": "bar" + } + }, + success: function( data ) { + assert.strictEqual( data, "test%5Blength%5D=7&test%5Bfoo%5D=bar", "Check if a sub-object with a length param is serialized correctly" ); + } + } ) + ).always( done ); + } ); + + QUnit.test( "jQuery.post( String, Hash, Function ) - simple with xml", function( assert ) { + assert.expect( 4 ); + var done = assert.async(); + + jQuery.when( + jQuery.post( + url( "mock.php?action=xml" ), + { + cal: "5-2" + }, + function( xml ) { + jQuery( "math", xml ).each( function() { + assert.strictEqual( jQuery( "calculation", this ).text(), "5-2", "Check for XML" ); + assert.strictEqual( jQuery( "result", this ).text(), "3", "Check for XML" ); + } ); + } + ), + jQuery.post( url( "mock.php?action=xml&cal=5-2" ), {}, function( xml ) { + jQuery( "math", xml ).each( function() { + assert.strictEqual( jQuery( "calculation", this ).text(), "5-2", "Check for XML" ); + assert.strictEqual( jQuery( "result", this ).text(), "3", "Check for XML" ); + } ); + } ) + ).always( function() { + done(); + } ); + } ); + + QUnit.test( "jQuery[get|post]( options ) - simple with xml", function( assert ) { + assert.expect( 2 ); + var done = assert.async(); + + jQuery.when.apply( jQuery, + jQuery.map( [ "get", "post" ], function( method ) { + return jQuery[ method ]( { + url: url( "mock.php?action=xml" ), + data: { + cal: "5-2" + }, + success: function( xml ) { + jQuery( "math", xml ).each( function() { + assert.strictEqual( jQuery( "result", this ).text(), "3", "Check for XML" ); + } ); + } + } ); + } ) + ).always( function() { + done(); + } ); + } ); + +//----------- jQuery.active + + QUnit.test( "jQuery.active", function( assert ) { + assert.expect( 1 ); + assert.ok( jQuery.active === 0, "ajax active counter should be zero: " + jQuery.active ); + } ); + +} )(); diff --git a/test/unit/animation.js b/test/unit/animation.js new file mode 100644 index 0000000..7a51add --- /dev/null +++ b/test/unit/animation.js @@ -0,0 +1,261 @@ +( function() { + +// Can't test what ain't there +if ( !jQuery.fx ) { + return; +} + +var oldRaf = window.requestAnimationFrame, + defaultPrefilter = jQuery.Animation.prefilters[ 0 ], + defaultTweener = jQuery.Animation.tweeners[ "*" ][ 0 ], + startTime = 505877050; + +// This module tests jQuery.Animation and the corresponding 1.8+ effects APIs +QUnit.module( "animation", { + beforeEach: function() { + window.requestAnimationFrame = null; + this.sandbox = sinon.sandbox.create(); + this.clock = this.sandbox.useFakeTimers( startTime ); + this._oldInterval = jQuery.fx.interval; + jQuery.fx.step = {}; + jQuery.fx.interval = 10; + jQuery.Animation.prefilters = [ defaultPrefilter ]; + jQuery.Animation.tweeners = { "*": [ defaultTweener ] }; + }, + afterEach: function() { + this.sandbox.restore(); + jQuery.fx.stop(); + jQuery.fx.interval = this._oldInterval; + window.requestAnimationFrame = oldRaf; + return moduleTeardown.apply( this, arguments ); + } +} ); + +QUnit.test( "Animation( subject, props, opts ) - shape", function( assert ) { + assert.expect( 20 ); + + var subject = { test: 0 }, + props = { test: 1 }, + opts = { queue: "fx", duration: 100 }, + animation = jQuery.Animation( subject, props, opts ); + + assert.equal( + animation.elem, + subject, + ".elem is set to the exact object passed" + ); + assert.equal( + animation.originalOptions, + opts, + ".originalOptions is set to options passed" + ); + assert.equal( + animation.originalProperties, + props, + ".originalProperties is set to props passed" + ); + + assert.notEqual( animation.props, props, ".props is not the original however" ); + assert.deepEqual( animation.props, props, ".props is a copy of the original" ); + + assert.deepEqual( animation.opts, { + duration: 100, + queue: "fx", + specialEasing: { test: undefined }, + easing: jQuery.easing._default + }, ".options is filled with default easing and specialEasing" ); + + assert.equal( animation.startTime, startTime, "startTime was set" ); + assert.equal( animation.duration, 100, ".duration is set" ); + + assert.equal( animation.tweens.length, 1, ".tweens has one Tween" ); + assert.equal( typeof animation.tweens[ 0 ].run, "function", "which has a .run function" ); + + assert.equal( typeof animation.createTween, "function", ".createTween is a function" ); + assert.equal( typeof animation.stop, "function", ".stop is a function" ); + + assert.equal( typeof animation.done, "function", ".done is a function" ); + assert.equal( typeof animation.fail, "function", ".fail is a function" ); + assert.equal( typeof animation.always, "function", ".always is a function" ); + assert.equal( typeof animation.progress, "function", ".progress is a function" ); + + assert.equal( jQuery.timers.length, 1, "Added a timers function" ); + assert.equal( jQuery.timers[ 0 ].elem, subject, "...with .elem as the subject" ); + assert.equal( jQuery.timers[ 0 ].anim, animation, "...with .anim as the animation" ); + assert.equal( jQuery.timers[ 0 ].queue, opts.queue, "...with .queue" ); + + // Cleanup after ourselves by ticking to the end + this.clock.tick( 100 ); +} ); + +QUnit.test( "Animation.prefilter( fn ) - calls prefilter after defaultPrefilter", + function( assert ) { + assert.expect( 1 ); + + var prefilter = this.sandbox.stub(), + defaultSpy = this.sandbox.spy( jQuery.Animation.prefilters, 0 ); + + jQuery.Animation.prefilter( prefilter ); + + jQuery.Animation( {}, {}, {} ); + assert.ok( prefilter.calledAfter( defaultSpy ), "our prefilter called after" ); + } +); + +QUnit.test( "Animation.prefilter( fn, true ) - calls prefilter before defaultPrefilter", + function( assert ) { + assert.expect( 1 ); + + var prefilter = this.sandbox.stub(), + defaultSpy = this.sandbox.spy( jQuery.Animation.prefilters, 0 ); + + jQuery.Animation.prefilter( prefilter, true ); + + jQuery.Animation( {}, {}, {} ); + assert.ok( prefilter.calledBefore( defaultSpy ), "our prefilter called before" ); + } +); + +QUnit.test( "Animation.prefilter - prefilter return hooks", function( assert ) { + assert.expect( 34 ); + + var animation, realAnimation, element, + sandbox = this.sandbox, + ourAnimation = { stop: this.sandbox.spy() }, + target = { height: 50 }, + props = { height: 100 }, + opts = { duration: 100 }, + prefilter = this.sandbox.spy( function() { + realAnimation = this; + sandbox.spy( realAnimation, "createTween" ); + + assert.deepEqual( realAnimation.originalProperties, props, "originalProperties" ); + assert.equal( arguments[ 0 ], this.elem, "first param elem" ); + assert.equal( arguments[ 1 ], this.props, "second param props" ); + assert.equal( arguments[ 2 ], this.opts, "third param opts" ); + return ourAnimation; + } ), + defaultSpy = sandbox.spy( jQuery.Animation.prefilters, 0 ), + queueSpy = sandbox.spy( function( next ) { + next(); + } ), + TweenSpy = sandbox.spy( jQuery, "Tween" ); + + jQuery.Animation.prefilter( prefilter, true ); + + sandbox.stub( jQuery.fx, "timer" ); + + animation = jQuery.Animation( target, props, opts ); + + assert.equal( prefilter.callCount, 1, "Called prefilter" ); + + assert.equal( + defaultSpy.callCount, + 0, + "Returning something from a prefilter caused remaining prefilters to not run" + ); + assert.equal( jQuery.fx.timer.callCount, 0, "Returning something never queues a timer" ); + assert.equal( + animation, + ourAnimation, + "Returning something returned it from jQuery.Animation" + ); + assert.equal( + realAnimation.createTween.callCount, + 0, + "Returning something never creates tweens" + ); + assert.equal( TweenSpy.callCount, 0, "Returning something never creates tweens" ); + + // Test overridden usage on queues: + prefilter.reset(); + element = jQuery( "
    " ) + .css( "height", 50 ) + .animate( props, 100 ) + .queue( queueSpy ) + .animate( props, 100 ) + .queue( queueSpy ) + .animate( props, 100 ) + .queue( queueSpy ); + + assert.equal( prefilter.callCount, 1, "Called prefilter" ); + assert.equal( queueSpy.callCount, 0, "Next function in queue not called" ); + + realAnimation.opts.complete.call( realAnimation.elem ); + assert.equal( queueSpy.callCount, 1, "Next function in queue called after complete" ); + + assert.equal( prefilter.callCount, 2, "Called prefilter again - animation #2" ); + assert.equal( ourAnimation.stop.callCount, 0, ".stop() on our animation hasn't been called" ); + + element.stop(); + assert.equal( ourAnimation.stop.callCount, 1, ".stop() called ourAnimation.stop()" ); + assert.ok( + !ourAnimation.stop.args[ 0 ][ 0 ], + ".stop( falsy ) (undefined or false are both valid)" + ); + + assert.equal( queueSpy.callCount, 2, "Next queue function called" ); + assert.ok( queueSpy.calledAfter( ourAnimation.stop ), "After our animation was told to stop" ); + + // ourAnimation.stop.reset(); + assert.equal( prefilter.callCount, 3, "Got the next animation" ); + + ourAnimation.stop.reset(); + + // do not clear queue, gotoEnd + element.stop( false, true ); + assert.ok( ourAnimation.stop.calledWith( true ), ".stop(true) calls .stop(true)" ); + assert.ok( queueSpy.calledAfter( ourAnimation.stop ), + "and the next queue function ran after we were told" ); +} ); + +QUnit.test( "Animation.tweener( fn ) - unshifts a * tweener", function( assert ) { + assert.expect( 2 ); + var starTweeners = jQuery.Animation.tweeners[ "*" ]; + + jQuery.Animation.tweener( jQuery.noop ); + assert.equal( starTweeners.length, 2 ); + assert.deepEqual( starTweeners, [ jQuery.noop, defaultTweener ] ); +} ); + +QUnit.test( "Animation.tweener( 'prop', fn ) - unshifts a 'prop' tweener", function( assert ) { + assert.expect( 4 ); + var tweeners = jQuery.Animation.tweeners, + fn = function() {}; + + jQuery.Animation.tweener( "prop", jQuery.noop ); + assert.equal( tweeners.prop.length, 1 ); + assert.deepEqual( tweeners.prop, [ jQuery.noop ] ); + + jQuery.Animation.tweener( "prop", fn ); + assert.equal( tweeners.prop.length, 2 ); + assert.deepEqual( tweeners.prop, [ fn, jQuery.noop ] ); +} ); + +QUnit.test( + "Animation.tweener( 'list of props', fn ) - unshifts a tweener to each prop", + function( assert ) { + assert.expect( 2 ); + var tweeners = jQuery.Animation.tweeners, + fn = function() {}; + + jQuery.Animation.tweener( "list of props", jQuery.noop ); + assert.deepEqual( tweeners, { + list: [ jQuery.noop ], + of: [ jQuery.noop ], + props: [ jQuery.noop ], + "*": [ defaultTweener ] + } ); + + // Test with extra whitespaces + jQuery.Animation.tweener( " list\t of \tprops\n*", fn ); + assert.deepEqual( tweeners, { + list: [ fn, jQuery.noop ], + of: [ fn, jQuery.noop ], + props: [ fn, jQuery.noop ], + "*": [ fn, defaultTweener ] + } ); + } +); + +} )(); diff --git a/test/unit/attributes.js b/test/unit/attributes.js new file mode 100644 index 0000000..9d24ff1 --- /dev/null +++ b/test/unit/attributes.js @@ -0,0 +1,1753 @@ +QUnit.module( "attributes", { + afterEach: moduleTeardown +} ); + +function bareObj( value ) { + return value; +} + +function functionReturningObj( value ) { + return function() { + return value; + }; +} + +function arrayFromString( value ) { + return value ? value.split( " " ) : []; +} + +/* + ======== local reference ======= + bareObj and functionReturningObj can be used to test passing functions to setters + See testVal below for an example + + bareObj( value ); + This function returns whatever value is passed in + + functionReturningObj( value ); + Returns a function that returns the value +*/ + +QUnit.test( "jQuery.propFix integrity test", function( assert ) { + assert.expect( 1 ); + + // This must be maintained and equal jQuery.attrFix when appropriate + // Ensure that accidental or erroneous property + // overwrites don't occur + // This is simply for better code coverage and future proofing. + var props = { + "tabindex": "tabIndex", + "readonly": "readOnly", + "for": "htmlFor", + "class": "className", + "maxlength": "maxLength", + "cellspacing": "cellSpacing", + "cellpadding": "cellPadding", + "rowspan": "rowSpan", + "colspan": "colSpan", + "usemap": "useMap", + "frameborder": "frameBorder", + "contenteditable": "contentEditable" + }; + + assert.deepEqual( props, jQuery.propFix, "jQuery.propFix passes integrity check" ); +} ); + +QUnit.test( "attr(String)", function( assert ) { + assert.expect( 50 ); + + var extras, body, $body, + select, optgroup, option, $img, styleElem, + $button, $form, $a; + + assert.equal( jQuery( "#text1" ).attr( "type" ), "text", "Check for type attribute" ); + assert.equal( jQuery( "#radio1" ).attr( "type" ), "radio", "Check for type attribute" ); + assert.equal( jQuery( "#check1" ).attr( "type" ), "checkbox", "Check for type attribute" ); + assert.equal( jQuery( "#simon1" ).attr( "rel" ), "bookmark", "Check for rel attribute" ); + assert.equal( jQuery( "#google" ).attr( "title" ), "Google!", "Check for title attribute" ); + assert.equal( jQuery( "#mark" ).attr( "hreflang" ), "en", "Check for hreflang attribute" ); + assert.equal( jQuery( "#en" ).attr( "lang" ), "en", "Check for lang attribute" ); + assert.equal( jQuery( "#simon" ).attr( "class" ), "blog link", "Check for class attribute" ); + assert.equal( jQuery( "#name" ).attr( "name" ), "name", "Check for name attribute" ); + assert.equal( jQuery( "#text1" ).attr( "name" ), "action", "Check for name attribute" ); + assert.ok( jQuery( "#form" ).attr( "action" ).indexOf( "formaction" ) >= 0, "Check for action attribute" ); + assert.equal( jQuery( "#text1" ).attr( "value", "t" ).attr( "value" ), "t", "Check setting the value attribute" ); + assert.equal( jQuery( "#text1" ).attr( "value", "" ).attr( "value" ), "", "Check setting the value attribute to empty string" ); + assert.equal( jQuery( "
    " ).attr( "value" ), "t", "Check setting custom attr named 'value' on a div" ); + assert.equal( jQuery( "#form" ).attr( "blah", "blah" ).attr( "blah" ), "blah", "Set non-existent attribute on a form" ); + assert.equal( jQuery( "#foo" ).attr( "height" ), undefined, "Non existent height attribute should return undefined" ); + + // [7472] & [3113] (form contains an input with name="action" or name="id") + extras = jQuery( "" ).appendTo( "#testForm" ); + assert.equal( jQuery( "#form" ).attr( "action", "newformaction" ).attr( "action" ), "newformaction", "Check that action attribute was changed" ); + assert.equal( jQuery( "#testForm" ).attr( "target" ), undefined, "Retrieving target does not equal the input with name=target" ); + assert.equal( jQuery( "#testForm" ).attr( "target", "newTarget" ).attr( "target" ), "newTarget", "Set target successfully on a form" ); + assert.equal( jQuery( "#testForm" ).removeAttr( "id" ).attr( "id" ), undefined, "Retrieving id does not equal the input with name=id after id is removed [#7472]" ); + + // Bug #3685 (form contains input with name="name") + assert.equal( jQuery( "#testForm" ).attr( "name" ), undefined, "Retrieving name does not retrieve input with name=name" ); + extras.remove(); + + assert.equal( jQuery( "#text1" ).attr( "maxlength" ), "30", "Check for maxlength attribute" ); + assert.equal( jQuery( "#text1" ).attr( "maxLength" ), "30", "Check for maxLength attribute" ); + assert.equal( jQuery( "#area1" ).attr( "maxLength" ), "30", "Check for maxLength attribute" ); + + // using innerHTML in IE causes href attribute to be serialized to the full path + jQuery( "" ).attr( { + "id": "tAnchor5", + "href": "#5" + } ).appendTo( "#qunit-fixture" ); + assert.equal( jQuery( "#tAnchor5" ).attr( "href" ), "#5", "Check for non-absolute href (an anchor)" ); + jQuery( "" ).appendTo( "#qunit-fixture" ); + assert.equal( jQuery( "#tAnchor5" ).prop( "href" ), jQuery( "#tAnchor6" ).prop( "href" ), "Check for absolute href prop on an anchor" ); + + jQuery( "" ).appendTo( "#qunit-fixture" ); + assert.equal( jQuery( "#tAnchor5" ).prop( "href" ), jQuery( "#scriptSrc" ).prop( "src" ), "Check for absolute src prop on a script" ); + + // list attribute is readonly by default in browsers that support it + jQuery( "#list-test" ).attr( "list", "datalist" ); + assert.equal( jQuery( "#list-test" ).attr( "list" ), "datalist", "Check setting list attribute" ); + + // Related to [5574] and [5683] + body = document.body; + $body = jQuery( body ); + + assert.strictEqual( $body.attr( "foo" ), undefined, "Make sure that a non existent attribute returns undefined" ); + + body.setAttribute( "foo", "baz" ); + assert.equal( $body.attr( "foo" ), "baz", "Make sure the dom attribute is retrieved when no expando is found" ); + + $body.attr( "foo", "cool" ); + assert.equal( $body.attr( "foo" ), "cool", "Make sure that setting works well when both expando and dom attribute are available" ); + + body.removeAttribute( "foo" ); // Cleanup + + select = document.createElement( "select" ); + optgroup = document.createElement( "optgroup" ); + option = document.createElement( "option" ); + + optgroup.appendChild( option ); + select.appendChild( optgroup ); + + assert.equal( jQuery( option ).prop( "selected" ), true, "Make sure that a single option is selected, even when in an optgroup." ); + + $img = jQuery( "" ).appendTo( "body" ); + assert.equal( $img.attr( "width" ), "215", "Retrieve width attribute on an element with display:none." ); + assert.equal( $img.attr( "height" ), "53", "Retrieve height attribute on an element with display:none." ); + + // Check for style support + styleElem = jQuery( "
    " ).appendTo( "#qunit-fixture" ).css( { + background: "url(UPPERlower.gif)" + } ); + assert.ok( !!~styleElem.attr( "style" ).indexOf( "UPPERlower.gif" ), "Check style attribute getter" ); + assert.ok( !!~styleElem.attr( "style", "position:absolute;" ).attr( "style" ).indexOf( "absolute" ), "Check style setter" ); + + // Check value on button element (#1954) + $button = jQuery( "" ).insertAfter( "#button" ); + assert.strictEqual( $button.attr( "value" ), undefined, "Absence of value attribute on a button" ); + assert.equal( $button.attr( "value", "foobar" ).attr( "value" ), "foobar", "Value attribute on a button does not return innerHTML" ); + assert.equal( $button.attr( "value", "baz" ).html(), "text", "Setting the value attribute does not change innerHTML" ); + + // Attributes with a colon on a table element (#1591) + assert.equal( jQuery( "#table" ).attr( "test:attrib" ), undefined, "Retrieving a non-existent attribute on a table with a colon does not throw an error." ); + assert.equal( jQuery( "#table" ).attr( "test:attrib", "foobar" ).attr( "test:attrib" ), "foobar", "Setting an attribute on a table with a colon does not throw an error." ); + + $form = jQuery( "
    " ).appendTo( "#qunit-fixture" ); + assert.equal( $form.attr( "class" ), "something", "Retrieve the class attribute on a form." ); + + $a = jQuery( "Click" ).appendTo( "#qunit-fixture" ); + assert.equal( $a.attr( "onclick" ), "something()", "Retrieve ^on attribute without anonymous function wrapper." ); + + assert.ok( jQuery( "
    " ).attr( "doesntexist" ) === undefined, "Make sure undefined is returned when no attribute is found." ); + assert.ok( jQuery( "
    " ).attr( "title" ) === undefined, "Make sure undefined is returned when no attribute is found." ); + assert.equal( jQuery( "
    " ).attr( "title", "something" ).attr( "title" ), "something", "Set the title attribute." ); + assert.ok( jQuery().attr( "doesntexist" ) === undefined, "Make sure undefined is returned when no element is there." ); + assert.equal( jQuery( "
    " ).attr( "value" ), undefined, "An unset value on a div returns undefined." ); + assert.strictEqual( jQuery( "" ).attr( "value" ), undefined, "An unset value on a select returns undefined." ); + + $form = jQuery( "#form" ).attr( "enctype", "multipart/form-data" ); + assert.equal( $form.prop( "enctype" ), "multipart/form-data", "Set the enctype of a form (encoding in IE6/7 #6743)" ); + +} ); + +QUnit.test( "attr(String) on cloned elements, #9646", function( assert ) { + assert.expect( 4 ); + + var div, + input = jQuery( "" ); + + input.attr( "name" ); + + assert.strictEqual( input.clone( true ).attr( "name", "test" )[ 0 ].name, "test", "Name attribute should be changed on cloned element" ); + + div = jQuery( "
    " ); + div.attr( "id" ); + + assert.strictEqual( div.clone( true ).attr( "id", "test" )[ 0 ].id, "test", "Id attribute should be changed on cloned element" ); + + input = jQuery( "" ); + input.attr( "value" ); + + assert.strictEqual( input.clone( true ).attr( "value", "test" )[ 0 ].value, "test", "Value attribute should be changed on cloned element" ); + + assert.strictEqual( input.clone( true ).attr( "value", 42 )[ 0 ].value, "42", "Value attribute should be changed on cloned element" ); +} ); + +QUnit.test( "attr(String) in XML Files", function( assert ) { + assert.expect( 3 ); + var xml = createDashboardXML(); + assert.equal( jQuery( "locations", xml ).attr( "class" ), "foo", "Check class attribute in XML document" ); + assert.equal( jQuery( "location", xml ).attr( "for" ), "bar", "Check for attribute in XML document" ); + assert.equal( jQuery( "location", xml ).attr( "checked" ), "different", "Check that hooks are not attached in XML document" ); +} ); + +QUnit.test( "attr(String, Function)", function( assert ) { + assert.expect( 2 ); + + assert.equal( + jQuery( "#text1" ).attr( "value", function() { + return this.id; + } ).attr( "value" ), + "text1", + "Set value from id" + ); + + assert.equal( + jQuery( "#text1" ).attr( "title", function( i ) { + return i; + } ).attr( "title" ), + "0", + "Set value with an index" + ); +} ); + +QUnit.test( "attr(Hash)", function( assert ) { + assert.expect( 3 ); + var pass = true; + + jQuery( "#qunit-fixture div" ).attr( { + "foo": "baz", + "zoo": "ping" + } ).each( function() { + if ( this.getAttribute( "foo" ) !== "baz" && this.getAttribute( "zoo" ) !== "ping" ) { + pass = false; + } + } ); + + assert.ok( pass, "Set Multiple Attributes" ); + + assert.equal( + jQuery( "#text1" ).attr( { + "value": function() { + return this[ "id" ]; + } } ).attr( "value" ), + "text1", + "Set attribute to computed value #1" + ); + + assert.equal( + jQuery( "#text1" ).attr( { + "title": function( i ) { + return i; + } + } ).attr( "title" ), + "0", + "Set attribute to computed value #2" + ); +} ); + +QUnit.test( "attr(String, Object)", function( assert ) { + assert.expect( 71 ); + + var $input, $text, $details, + attributeNode, commentNode, textNode, obj, + table, td, j, type, + check, thrown, button, $radio, $radios, $svg, + div = jQuery( "#qunit-fixture div" ).attr( "foo", "bar" ), + i = 0, + fail = false; + + for ( ; i < div.length; i++ ) { + if ( div[ i ].getAttribute( "foo" ) !== "bar" ) { + fail = i; + break; + } + } + + assert.equal( fail, false, "Set Attribute, the #" + fail + " element didn't get the attribute 'foo'" ); + + assert.ok( + jQuery( "#foo" ).attr( { + "width": null + } ), + "Try to set an attribute to nothing" + ); + + jQuery( "#name" ).attr( "name", "something" ); + assert.equal( jQuery( "#name" ).attr( "name" ), "something", "Set name attribute" ); + jQuery( "#name" ).attr( "name", null ); + assert.equal( jQuery( "#name" ).attr( "name" ), undefined, "Remove name attribute" ); + + $input = jQuery( "", { + name: "something", + id: "specified" + } ); + assert.equal( $input.attr( "name" ), "something", "Check element creation gets/sets the name attribute." ); + assert.equal( $input.attr( "id" ), "specified", "Check element creation gets/sets the id attribute." ); + + // As of fixing #11115, we only guarantee boolean property update for checked and selected + $input = jQuery( "" ).attr( "checked", true ); + assert.equal( $input.prop( "checked" ), true, "Setting checked updates property (verified by .prop)" ); + assert.equal( $input[ 0 ].checked, true, "Setting checked updates property (verified by native property)" ); + $input = jQuery( "" ).attr( "selected", true ); + assert.equal( $input.prop( "selected" ), true, "Setting selected updates property (verified by .prop)" ); + assert.equal( $input[ 0 ].selected, true, "Setting selected updates property (verified by native property)" ); + + $input = jQuery( "#check2" ); + $input.prop( "checked", true ).prop( "checked", false ).attr( "checked", true ); + assert.equal( $input.attr( "checked" ), "checked", "Set checked (verified by .attr)" ); + $input.prop( "checked", false ).prop( "checked", true ).attr( "checked", false ); + assert.equal( $input.attr( "checked" ), undefined, "Remove checked (verified by .attr)" ); + + $input = jQuery( "#text1" ).prop( "readOnly", true ).prop( "readOnly", false ).attr( "readonly", true ); + assert.equal( $input.attr( "readonly" ), "readonly", "Set readonly (verified by .attr)" ); + $input.prop( "readOnly", false ).prop( "readOnly", true ).attr( "readonly", false ); + assert.equal( $input.attr( "readonly" ), undefined, "Remove readonly (verified by .attr)" ); + + $input = jQuery( "#check2" ).attr( "checked", true ).attr( "checked", false ).prop( "checked", true ); + assert.equal( $input[ 0 ].checked, true, "Set checked property (verified by native property)" ); + assert.equal( $input.prop( "checked" ), true, "Set checked property (verified by .prop)" ); + assert.equal( $input.attr( "checked" ), undefined, "Setting checked property doesn't affect checked attribute" ); + $input.attr( "checked", false ).attr( "checked", true ).prop( "checked", false ); + assert.equal( $input[ 0 ].checked, false, "Clear checked property (verified by native property)" ); + assert.equal( $input.prop( "checked" ), false, "Clear checked property (verified by .prop)" ); + assert.equal( $input.attr( "checked" ), "checked", "Clearing checked property doesn't affect checked attribute" ); + + $input = jQuery( "#check2" ).attr( "checked", false ).attr( "checked", "checked" ); + assert.equal( $input.attr( "checked" ), "checked", "Set checked to 'checked' (verified by .attr)" ); + + $radios = jQuery( "#checkedtest" ).find( "input[type='radio']" ); + $radios.eq( 1 ).trigger( "click" ); + assert.equal( $radios.eq( 1 ).prop( "checked" ), true, "Second radio was checked when clicked" ); + assert.equal( $radios.eq( 0 ).attr( "checked" ), "checked", "First radio is still [checked]" ); + + $input = jQuery( "#text1" ).attr( "readonly", false ).prop( "readOnly", true ); + assert.equal( $input[ 0 ].readOnly, true, "Set readonly property (verified by native property)" ); + assert.equal( $input.prop( "readOnly" ), true, "Set readonly property (verified by .prop)" ); + $input.attr( "readonly", true ).prop( "readOnly", false ); + assert.equal( $input[ 0 ].readOnly, false, "Clear readonly property (verified by native property)" ); + assert.equal( $input.prop( "readOnly" ), false, "Clear readonly property (verified by .prop)" ); + + $input = jQuery( "#name" ).attr( "maxlength", "5" ); + assert.equal( $input[ 0 ].maxLength, 5, "Set maxlength (verified by native property)" ); + $input.attr( "maxLength", "10" ); + assert.equal( $input[ 0 ].maxLength, 10, "Set maxlength (verified by native property)" ); + + // HTML5 boolean attributes + $text = jQuery( "#text1" ).attr( { + "autofocus": true, + "required": true + } ); + assert.equal( $text.attr( "autofocus" ), "autofocus", "Reading autofocus attribute yields 'autofocus'" ); + assert.equal( $text.attr( "autofocus", false ).attr( "autofocus" ), undefined, "Setting autofocus to false removes it" ); + assert.equal( $text.attr( "required" ), "required", "Reading required attribute yields 'required'" ); + assert.equal( $text.attr( "required", false ).attr( "required" ), undefined, "Setting required attribute to false removes it" ); + + $details = jQuery( "
    " ).appendTo( "#qunit-fixture" ); + assert.equal( $details.attr( "open" ), "open", "open attribute presence indicates true" ); + assert.equal( $details.attr( "open", false ).attr( "open" ), undefined, "Setting open attribute to false removes it" ); + + $text.attr( "data-something", true ); + assert.equal( $text.attr( "data-something" ), "true", "Set data attributes" ); + assert.equal( $text.data( "something" ), true, "Setting data attributes are not affected by boolean settings" ); + $text.attr( "data-another", false ); + assert.equal( $text.attr( "data-another" ), "false", "Set data attributes" ); + assert.equal( $text.data( "another" ), false, "Setting data attributes are not affected by boolean settings" ); + assert.equal( $text.attr( "aria-disabled", false ).attr( "aria-disabled" ), "false", "Setting aria attributes are not affected by boolean settings" ); + $text.removeData( "something" ).removeData( "another" ).removeAttr( "aria-disabled" ); + + jQuery( "#foo" ).attr( "contenteditable", true ); + assert.equal( jQuery( "#foo" ).attr( "contenteditable" ), "true", "Enumerated attributes are set properly" ); + + attributeNode = document.createAttribute( "irrelevant" ); + commentNode = document.createComment( "some comment" ); + textNode = document.createTextNode( "some text" ); + obj = {}; + + jQuery.each( [ commentNode, textNode, attributeNode ], function( i, elem ) { + var $elem = jQuery( elem ); + $elem.attr( "nonexisting", "foo" ); + assert.strictEqual( $elem.attr( "nonexisting" ), undefined, "attr(name, value) works correctly on comment and text nodes (bug #7500)." ); + } ); + + jQuery.each( [ window, document, obj, "#firstp" ], function( i, elem ) { + var oldVal = elem.nonexisting, + $elem = jQuery( elem ); + assert.strictEqual( $elem.attr( "nonexisting" ), undefined, "attr works correctly for non existing attributes (bug #7500)." ); + assert.equal( $elem.attr( "nonexisting", "foo" ).attr( "nonexisting" ), "foo", "attr falls back to prop on unsupported arguments" ); + elem.nonexisting = oldVal; + } ); + + // Register the property on the window for the previous assertion so it will be clean up + Globals.register( "nonexisting" ); + + table = jQuery( "#table" ).append( "cellcellcellcellcell" ); + td = table.find( "td" ).eq( 0 ); + td.attr( "rowspan", "2" ); + assert.equal( td[ 0 ][ "rowSpan" ], 2, "Check rowspan is correctly set" ); + td.attr( "colspan", "2" ); + assert.equal( td[ 0 ][ "colSpan" ], 2, "Check colspan is correctly set" ); + table.attr( "cellspacing", "2" ); + assert.equal( table[ 0 ][ "cellSpacing" ], "2", "Check cellspacing is correctly set" ); + + assert.equal( jQuery( "#area1" ).attr( "value" ), undefined, "Value attribute is distinct from value property." ); + + // for #1070 + jQuery( "#name" ).attr( "someAttr", "0" ); + assert.equal( jQuery( "#name" ).attr( "someAttr" ), "0", "Set attribute to a string of '0'" ); + jQuery( "#name" ).attr( "someAttr", 0 ); + assert.equal( jQuery( "#name" ).attr( "someAttr" ), "0", "Set attribute to the number 0" ); + jQuery( "#name" ).attr( "someAttr", 1 ); + assert.equal( jQuery( "#name" ).attr( "someAttr" ), "1", "Set attribute to the number 1" ); + + // using contents will get comments regular, text, and comment nodes + j = jQuery( "#nonnodes" ).contents(); + + j.attr( "name", "attrvalue" ); + assert.equal( j.attr( "name" ), "attrvalue", "Check node,textnode,comment for attr" ); + j.removeAttr( "name" ); + + // Type + type = jQuery( "#check2" ).attr( "type" ); + try { + jQuery( "#check2" ).attr( "type", "hidden" ); + assert.ok( true, "No exception thrown on input type change" ); + } catch ( e ) { + assert.ok( true, "Exception thrown on input type change: " + e ); + } + + check = document.createElement( "input" ); + thrown = true; + try { + jQuery( check ).attr( "type", "checkbox" ); + } catch ( e ) { + thrown = false; + } + assert.ok( thrown, "Exception thrown when trying to change type property" ); + assert.equal( "checkbox", jQuery( check ).attr( "type" ), "Verify that you can change the type of an input element that isn't in the DOM" ); + + check = jQuery( "" ); + thrown = true; + try { + check.attr( "type", "checkbox" ); + } catch ( e ) { + thrown = false; + } + assert.ok( thrown, "Exception thrown when trying to change type property" ); + assert.equal( "checkbox", check.attr( "type" ), "Verify that you can change the type of an input element that isn't in the DOM" ); + + button = jQuery( "#button" ); + try { + button.attr( "type", "submit" ); + assert.ok( true, "No exception thrown on button type change" ); + } catch ( e ) { + assert.ok( true, "Exception thrown on button type change: " + e ); + } + + $radio = jQuery( "", { + "value": "sup", + // Use uppercase here to ensure the type + // attrHook is still used + "TYPE": "radio" + } ).appendTo( "#testForm" ); + assert.equal( $radio.val(), "sup", "Value is not reset when type is set after value on a radio" ); + + // Setting attributes on svg elements (bug #3116) + $svg = jQuery( + "" + + + "" + + "" + ).appendTo( "body" ); + assert.equal( $svg.attr( "cx", 100 ).attr( "cx" ), "100", "Set attribute on svg element" ); + $svg.remove(); + + // undefined values are chainable + jQuery( "#name" ).attr( "maxlength", "5" ).removeAttr( "nonexisting" ); + assert.equal( typeof jQuery( "#name" ).attr( "maxlength", undefined ), "object", ".attr('attribute', undefined) is chainable (#5571)" ); + assert.equal( jQuery( "#name" ).attr( "maxlength", undefined ).attr( "maxlength" ), "5", ".attr('attribute', undefined) does not change value (#5571)" ); + assert.equal( jQuery( "#name" ).attr( "nonexisting", undefined ).attr( "nonexisting" ), undefined, ".attr('attribute', undefined) does not create attribute (#5571)" ); +} ); + +QUnit.test( "attr(non-ASCII)", function( assert ) { + assert.expect( 2 ); + + var $div = jQuery( "
    " ).appendTo( "#qunit-fixture" ); + + assert.equal( $div.attr( "Ω" ), "omega", ".attr() exclusively lowercases characters in the range A-Z (gh-2730)" ); + assert.equal( $div.attr( "AØC" ), "alpha", ".attr() exclusively lowercases characters in the range A-Z (gh-2730)" ); +} ); + +QUnit.test( "attr - extending the boolean attrHandle", function( assert ) { + assert.expect( 1 ); + var called = false, + origAttrHandleHadChecked = "checked" in jQuery.expr.attrHandle, + origAttrHandleChecked = jQuery.expr.attrHandle.checked, + _handle = origAttrHandleChecked || $.noop; + jQuery.expr.attrHandle.checked = function() { + called = true; + _handle.apply( this, arguments ); + }; + jQuery( "#qunit-fixture input" ).attr( "checked" ); + called = false; + jQuery( "#qunit-fixture input" ).attr( "checked" ); + assert.ok( called, "The boolean attrHandle does not drop custom attrHandles" ); + + if ( origAttrHandleHadChecked ) { + jQuery.expr.attrHandle.checked = origAttrHandleChecked; + } else { + delete jQuery.expr.attrHandle.checked; + } + +} ); + +QUnit.test( "attr(String, Object) - Loaded via XML document", function( assert ) { + assert.expect( 2 ); + var xml = createDashboardXML(), + titles = []; + jQuery( "tab", xml ).each( function() { + titles.push( jQuery( this ).attr( "title" ) ); + } ); + assert.equal( titles[ 0 ], "Location", "attr() in XML context: Check first title" ); + assert.equal( titles[ 1 ], "Users", "attr() in XML context: Check second title" ); +} ); + +QUnit.test( "attr(String, Object) - Loaded via XML fragment", function( assert ) { + assert.expect( 2 ); + var frag = createXMLFragment(), + $frag = jQuery( frag ); + + $frag.attr( "test", "some value" ); + assert.equal( $frag.attr( "test" ), "some value", "set attribute" ); + $frag.attr( "test", null ); + assert.equal( $frag.attr( "test" ), undefined, "remove attribute" ); +} ); + +QUnit.test( "attr('tabindex')", function( assert ) { + assert.expect( 8 ); + + // elements not natively tabbable + assert.equal( jQuery( "#listWithTabIndex" ).attr( "tabindex" ), "5", "not natively tabbable, with tabindex set to 0" ); + assert.equal( jQuery( "#divWithNoTabIndex" ).attr( "tabindex" ), undefined, "not natively tabbable, no tabindex set" ); + + // anchor with href + assert.equal( jQuery( "#linkWithNoTabIndex" ).attr( "tabindex" ), undefined, "anchor with href, no tabindex set" ); + assert.equal( jQuery( "#linkWithTabIndex" ).attr( "tabindex" ), "2", "anchor with href, tabindex set to 2" ); + assert.equal( jQuery( "#linkWithNegativeTabIndex" ).attr( "tabindex" ), "-1", "anchor with href, tabindex set to -1" ); + + // anchor without href + assert.equal( jQuery( "#linkWithNoHrefWithNoTabIndex" ).attr( "tabindex" ), undefined, "anchor without href, no tabindex set" ); + assert.equal( jQuery( "#linkWithNoHrefWithTabIndex" ).attr( "tabindex" ), "1", "anchor without href, tabindex set to 2" ); + assert.equal( jQuery( "#linkWithNoHrefWithNegativeTabIndex" ).attr( "tabindex" ), "-1", "anchor without href, no tabindex set" ); +} ); + +QUnit.test( "attr('tabindex', value)", function( assert ) { + assert.expect( 9 ); + + var element = jQuery( "#divWithNoTabIndex" ); + assert.equal( element.attr( "tabindex" ), undefined, "start with no tabindex" ); + + // set a positive string + element.attr( "tabindex", "1" ); + assert.equal( element.attr( "tabindex" ), "1", "set tabindex to 1 (string)" ); + + // set a zero string + element.attr( "tabindex", "0" ); + assert.equal( element.attr( "tabindex" ), "0", "set tabindex to 0 (string)" ); + + // set a negative string + element.attr( "tabindex", "-1" ); + assert.equal( element.attr( "tabindex" ), "-1", "set tabindex to -1 (string)" ); + + // set a positive number + element.attr( "tabindex", 1 ); + assert.equal( element.attr( "tabindex" ), "1", "set tabindex to 1 (number)" ); + + // set a zero number + element.attr( "tabindex", 0 ); + assert.equal( element.attr( "tabindex" ), "0", "set tabindex to 0 (number)" ); + + // set a negative number + element.attr( "tabindex", -1 ); + assert.equal( element.attr( "tabindex" ), "-1", "set tabindex to -1 (number)" ); + + element = jQuery( "#linkWithTabIndex" ); + assert.equal( element.attr( "tabindex" ), "2", "start with tabindex 2" ); + + element.attr( "tabindex", -1 ); + assert.equal( element.attr( "tabindex" ), "-1", "set negative tabindex" ); +} ); + +QUnit.test( "removeAttr(String)", function( assert ) { + assert.expect( 12 ); + var $first; + + assert.equal( jQuery( "
    " ).removeAttr( "class" ).attr( "class" ), undefined, "remove class" ); + assert.equal( jQuery( "#form" ).removeAttr( "id" ).attr( "id" ), undefined, "Remove id" ); + assert.equal( jQuery( "#foo" ).attr( "style", "position:absolute;" ).removeAttr( "style" ).attr( "style" ), undefined, "Check removing style attribute" ); + assert.equal( jQuery( "#form" ).attr( "style", "position:absolute;" ).removeAttr( "style" ).attr( "style" ), undefined, "Check removing style attribute on a form" ); + assert.equal( jQuery( "
    " ).appendTo( "#foo" ).removeAttr( "style" ).prop( "style" ).cssText, "", "Check removing style attribute (#9699 Webkit)" ); + assert.equal( jQuery( "#fx-test-group" ).attr( "height", "3px" ).removeAttr( "height" ).get( 0 ).style.height, "1px", "Removing height attribute has no effect on height set with style attribute" ); + + jQuery( "#check1" ).removeAttr( "checked" ).prop( "checked", true ).removeAttr( "checked" ); + assert.equal( document.getElementById( "check1" ).checked, true, "removeAttr should not set checked to false, since the checked attribute does NOT mirror the checked property" ); + jQuery( "#text1" ).prop( "readOnly", true ).removeAttr( "readonly" ); + assert.equal( document.getElementById( "text1" ).readOnly, false, "removeAttr sets boolean properties to false" ); + + jQuery( "#option2c" ).removeAttr( "selected" ); + assert.equal( jQuery( "#option2d" ).attr( "selected" ), "selected", "Removing `selected` from an option that is not selected does not remove selected from the currently selected option (#10870)" ); + + try { + $first = jQuery( "#first" ).attr( "contenteditable", "true" ).removeAttr( "contenteditable" ); + assert.equal( $first.attr( "contenteditable" ), undefined, "Remove the contenteditable attribute" ); + } catch ( e ) { + assert.ok( false, "Removing contenteditable threw an error (#10429)" ); + } + + $first = jQuery( "
    " ); + assert.equal( $first.attr( "Case" ), "mixed", "case of attribute doesn't matter" ); + $first.removeAttr( "Case" ); + assert.equal( $first.attr( "Case" ), undefined, "mixed-case attribute was removed" ); +} ); + +QUnit.test( "removeAttr(String) in XML", function( assert ) { + assert.expect( 7 ); + var xml = createDashboardXML(), + iwt = jQuery( "infowindowtab", xml ); + + assert.equal( iwt.attr( "normal" ), "ab", "Check initial value" ); + iwt.removeAttr( "Normal" ); + assert.equal( iwt.attr( "normal" ), "ab", "Should still be there" ); + iwt.removeAttr( "normal" ); + assert.equal( iwt.attr( "normal" ), undefined, "Removed" ); + + assert.equal( iwt.attr( "mixedCase" ), "yes", "Check initial value" ); + assert.equal( iwt.attr( "mixedcase" ), undefined, "toLowerCase not work good" ); + iwt.removeAttr( "mixedcase" ); + assert.equal( iwt.attr( "mixedCase" ), "yes", "Should still be there" ); + iwt.removeAttr( "mixedCase" ); + assert.equal( iwt.attr( "mixedCase" ), undefined, "Removed" ); +} ); + +QUnit.test( "removeAttr(Multi String, variable space width)", function( assert ) { + assert.expect( 8 ); + + var div = jQuery( "
    " ), + tests = { + id: "a", + alt: "b", + title: "c", + rel: "d" + }; + + jQuery.each( tests, function( key, val ) { + assert.equal( div.attr( key ), val, "Attribute `" + key + "` exists, and has a value of `" + val + "`" ); + } ); + + div.removeAttr( "id alt title rel " ); + + jQuery.each( tests, function( key ) { + assert.equal( div.attr( key ), undefined, "Attribute `" + key + "` was removed" ); + } ); +} ); + +QUnit.test( "removeAttr(Multi String, non-HTML whitespace is valid in attribute names (gh-3003)", function( assert ) { + assert.expect( 8 ); + + var div = jQuery( "
    " ); + var tests = { + id: "a", + "data-\xA0": "b", + title: "c", + rel: "d" + }; + + jQuery.each( tests, function( key, val ) { + assert.equal( div.attr( key ), val, "Attribute \"" + key + "\" exists, and has a value of \"" + val + "\"" ); + } ); + + div.removeAttr( "id data-\xA0 title rel " ); + + jQuery.each( tests, function( key ) { + assert.equal( div.attr( key ), undefined, "Attribute \"" + key + "\" was removed" ); + } ); +} ); + +QUnit.test( "prop(String, Object)", function( assert ) { + + assert.expect( 17 ); + + assert.equal( jQuery( "#text1" ).prop( "value" ), "Test", "Check for value attribute" ); + assert.equal( jQuery( "#text1" ).prop( "value", "Test2" ).prop( "defaultValue" ), "Test", "Check for defaultValue attribute" ); + assert.equal( jQuery( "#select2" ).prop( "selectedIndex" ), 3, "Check for selectedIndex attribute" ); + assert.equal( jQuery( "#foo" ).prop( "nodeName" ).toUpperCase(), "DIV", "Check for nodeName attribute" ); + assert.equal( jQuery( "#foo" ).prop( "tagName" ).toUpperCase(), "DIV", "Check for tagName attribute" ); + assert.equal( jQuery( "" ).prop( "selected" ), false, "Check selected attribute on disconnected element." ); + + assert.equal( jQuery( "#listWithTabIndex" ).prop( "tabindex" ), 5, "Check retrieving tabindex" ); + jQuery( "#text1" ).prop( "readonly", true ); + assert.equal( document.getElementById( "text1" ).readOnly, true, "Check setting readOnly property with 'readonly'" ); + assert.equal( jQuery( "#label-for" ).prop( "for" ), "action", "Check retrieving htmlFor" ); + jQuery( "#text1" ).prop( "class", "test" ); + assert.equal( document.getElementById( "text1" ).className, "test", "Check setting className with 'class'" ); + assert.equal( jQuery( "#text1" ).prop( "maxlength" ), 30, "Check retrieving maxLength" ); + jQuery( "#table" ).prop( "cellspacing", 1 ); + assert.equal( jQuery( "#table" ).prop( "cellSpacing" ), "1", "Check setting and retrieving cellSpacing" ); + jQuery( "#table" ).prop( "cellpadding", 1 ); + assert.equal( jQuery( "#table" ).prop( "cellPadding" ), "1", "Check setting and retrieving cellPadding" ); + jQuery( "#table" ).prop( "rowspan", 1 ); + assert.equal( jQuery( "#table" ).prop( "rowSpan" ), 1, "Check setting and retrieving rowSpan" ); + jQuery( "#table" ).prop( "colspan", 1 ); + assert.equal( jQuery( "#table" ).prop( "colSpan" ), 1, "Check setting and retrieving colSpan" ); + jQuery( "#table" ).prop( "usemap", 1 ); + assert.equal( jQuery( "#table" ).prop( "useMap" ), 1, "Check setting and retrieving useMap" ); + jQuery( "#table" ).prop( "frameborder", 1 ); + assert.equal( jQuery( "#table" ).prop( "frameBorder" ), 1, "Check setting and retrieving frameBorder" ); +} ); + +QUnit.test( "prop(String, Object) on null/undefined", function( assert ) { + + assert.expect( 14 ); + + var select, optgroup, option, attributeNode, commentNode, textNode, obj, $form, + body = document.body, + $body = jQuery( body ); + + assert.ok( $body.prop( "nextSibling" ) === null, "Make sure a null expando returns null" ); + body[ "foo" ] = "bar"; + assert.equal( $body.prop( "foo" ), "bar", "Make sure the expando is preferred over the dom attribute" ); + body[ "foo" ] = undefined; + assert.ok( $body.prop( "foo" ) === undefined, "Make sure the expando is preferred over the dom attribute, even if undefined" ); + + select = document.createElement( "select" ); + optgroup = document.createElement( "optgroup" ); + option = document.createElement( "option" ); + + optgroup.appendChild( option ); + select.appendChild( optgroup ); + + assert.equal( jQuery( option ).prop( "selected" ), true, "Make sure that a single option is selected, even when in an optgroup." ); + assert.equal( jQuery( document ).prop( "nodeName" ), "#document", "prop works correctly on document nodes (bug #7451)." ); + + attributeNode = document.createAttribute( "irrelevant" ); + commentNode = document.createComment( "some comment" ); + textNode = document.createTextNode( "some text" ); + obj = {}; + jQuery.each( [ document, attributeNode, commentNode, textNode, obj, "#firstp" ], function( i, ele ) { + assert.strictEqual( jQuery( ele ).prop( "nonexisting" ), undefined, "prop works correctly for non existing attributes (bug #7500)." ); + } ); + + obj = {}; + jQuery.each( [ document, obj ], function( i, ele ) { + var $ele = jQuery( ele ); + $ele.prop( "nonexisting", "foo" ); + assert.equal( $ele.prop( "nonexisting" ), "foo", "prop(name, value) works correctly for non existing attributes (bug #7500)." ); + } ); + jQuery( document ).removeProp( "nonexisting" ); + + $form = jQuery( "#form" ).prop( "enctype", "multipart/form-data" ); + assert.equal( $form.prop( "enctype" ), "multipart/form-data", "Set the enctype of a form (encoding in IE6/7 #6743)" ); +} ); + +QUnit.test( "prop('tabindex')", function( assert ) { + assert.expect( 11 ); + + // inputs without tabIndex attribute + assert.equal( jQuery( "#inputWithoutTabIndex" ).prop( "tabindex" ), 0, "input without tabindex" ); + assert.equal( jQuery( "#buttonWithoutTabIndex" ).prop( "tabindex" ), 0, "button without tabindex" ); + assert.equal( jQuery( "#textareaWithoutTabIndex" ).prop( "tabindex" ), 0, "textarea without tabindex" ); + + // elements not natively tabbable + assert.equal( jQuery( "#listWithTabIndex" ).prop( "tabindex" ), 5, "not natively tabbable, with tabindex set to 0" ); + assert.equal( jQuery( "#divWithNoTabIndex" ).prop( "tabindex" ), -1, "not natively tabbable, no tabindex set" ); + + // anchor with href + assert.equal( jQuery( "#linkWithNoTabIndex" ).prop( "tabindex" ), 0, "anchor with href, no tabindex set" ); + assert.equal( jQuery( "#linkWithTabIndex" ).prop( "tabindex" ), 2, "anchor with href, tabindex set to 2" ); + assert.equal( jQuery( "#linkWithNegativeTabIndex" ).prop( "tabindex" ), -1, "anchor with href, tabindex set to -1" ); + + // anchor without href + assert.equal( jQuery( "#linkWithNoHrefWithNoTabIndex" ).prop( "tabindex" ), -1, "anchor without href, no tabindex set" ); + assert.equal( jQuery( "#linkWithNoHrefWithTabIndex" ).prop( "tabindex" ), 1, "anchor without href, tabindex set to 2" ); + assert.equal( jQuery( "#linkWithNoHrefWithNegativeTabIndex" ).prop( "tabindex" ), -1, "anchor without href, no tabindex set" ); +} ); + +QUnit.test( "image.prop( 'tabIndex' )", function( assert ) { + assert.expect( 1 ); + var image = jQuery( "" ) + .appendTo( "#qunit-fixture" ); + assert.equal( image.prop( "tabIndex" ), -1, "tabIndex on image" ); +} ); + +QUnit.test( "prop('tabindex', value)", function( assert ) { + assert.expect( 10 ); + + var clone, + element = jQuery( "#divWithNoTabIndex" ); + + assert.equal( element.prop( "tabindex" ), -1, "start with no tabindex" ); + + // set a positive string + element.prop( "tabindex", "1" ); + assert.equal( element.prop( "tabindex" ), 1, "set tabindex to 1 (string)" ); + + // set a zero string + element.prop( "tabindex", "0" ); + assert.equal( element.prop( "tabindex" ), 0, "set tabindex to 0 (string)" ); + + // set a negative string + element.prop( "tabindex", "-1" ); + assert.equal( element.prop( "tabindex" ), -1, "set tabindex to -1 (string)" ); + + // set a positive number + element.prop( "tabindex", 1 ); + assert.equal( element.prop( "tabindex" ), 1, "set tabindex to 1 (number)" ); + + // set a zero number + element.prop( "tabindex", 0 ); + assert.equal( element.prop( "tabindex" ), 0, "set tabindex to 0 (number)" ); + + // set a negative number + element.prop( "tabindex", -1 ); + assert.equal( element.prop( "tabindex" ), -1, "set tabindex to -1 (number)" ); + + element = jQuery( "#linkWithTabIndex" ); + assert.equal( element.prop( "tabindex" ), 2, "start with tabindex 2" ); + + element.prop( "tabindex", -1 ); + assert.equal( element.prop( "tabindex" ), -1, "set negative tabindex" ); + + clone = element.clone(); + clone.prop( "tabindex", 1 ); + assert.equal( clone[ 0 ].getAttribute( "tabindex" ), "1", "set tabindex on cloned element" ); +} ); + +QUnit.test( "option.prop('selected', true) affects select.selectedIndex (gh-2732)", function( assert ) { + assert.expect( 2 ); + + function addOptions( $elem ) { + return $elem.append( + jQuery( "" ).val( "a" ).text( "One" ), + jQuery( "" ).val( "b" ).text( "Two" ), + jQuery( "" ).val( "c" ).text( "Three" ) + ) + .find( "[value=a]" ).prop( "selected", true ).end() + .find( "[value=c]" ).prop( "selected", true ).end(); + } + + var $optgroup, + $select = jQuery( "" ); + + // Check select with options + addOptions( $select ).appendTo( "#qunit-fixture" ); + $select.find( "[value=b]" ).prop( "selected", true ); + assert.equal( $select[ 0 ].selectedIndex, 1, "Setting option selected affects selectedIndex" ); + + $select.empty(); + + // Check select with optgroup + $optgroup = jQuery( "" ); + addOptions( $optgroup ).appendTo( $select ); + $select.find( "[value=b]" ).prop( "selected", true ); + + assert.equal( $select[ 0 ].selectedIndex, 1, "Setting option in optgroup selected affects selectedIndex" ); +} ); + +QUnit.test( "removeProp(String)", function( assert ) { + assert.expect( 6 ); + var attributeNode = document.createAttribute( "irrelevant" ), + commentNode = document.createComment( "some comment" ), + textNode = document.createTextNode( "some text" ), + obj = {}; + + assert.strictEqual( + jQuery( "#firstp" ).prop( "nonexisting", "foo" ).removeProp( "nonexisting" )[ 0 ][ "nonexisting" ], + undefined, + "removeprop works correctly on DOM element nodes" + ); + + jQuery.each( [ document, obj ], function( i, ele ) { + var $ele = jQuery( ele ); + $ele.prop( "nonexisting", "foo" ).removeProp( "nonexisting" ); + assert.strictEqual( ele[ "nonexisting" ], undefined, "removeProp works correctly on non DOM element nodes (bug #7500)." ); + } ); + jQuery.each( [ commentNode, textNode, attributeNode ], function( i, ele ) { + var $ele = jQuery( ele ); + $ele.prop( "nonexisting", "foo" ).removeProp( "nonexisting" ); + assert.strictEqual( ele[ "nonexisting" ], undefined, "removeProp works correctly on non DOM element nodes (bug #7500)." ); + } ); +} ); + +QUnit.test( "val() after modification", function( assert ) { + + assert.expect( 1 ); + + document.getElementById( "text1" ).value = "bla"; + assert.equal( jQuery( "#text1" ).val(), "bla", "Check for modified value of input element" ); +} ); + +QUnit.test( "val()", function( assert ) { + + assert.expect( 20 + ( jQuery.fn.serialize ? 6 : 0 ) ); + + var checks, $button; + assert.equal( jQuery( "#text1" ).val(), "Test", "Check for value of input element" ); + + // ticket #1714 this caused a JS error in IE + assert.equal( jQuery( "#first" ).val(), "", "Check a paragraph element to see if it has a value" ); + assert.ok( jQuery( [] ).val() === undefined, "Check an empty jQuery object will return undefined from val" ); + + assert.equal( jQuery( "#select2" ).val(), "3", "Call val() on a single='single' select" ); + + assert.deepEqual( jQuery( "#select3" ).val(), [ "1", "2" ], "Call val() on a multiple='multiple' select" ); + + assert.equal( jQuery( "#option3c" ).val(), "2", "Call val() on a option element with value" ); + + assert.equal( jQuery( "#option3a" ).val(), "", "Call val() on a option element with empty value" ); + + assert.equal( jQuery( "#option3e" ).val(), "no value", "Call val() on a option element with no value attribute" ); + + assert.equal( jQuery( "#option3a" ).val(), "", "Call val() on a option element with no value attribute" ); + + jQuery( "#select3" ).val( "" ); + assert.deepEqual( jQuery( "#select3" ).val(), [ "" ], "Call val() on a multiple='multiple' select" ); + + assert.deepEqual( jQuery( "#select4" ).val(), [], "Call val() on multiple='multiple' select with all disabled options" ); + + jQuery( "#select4 optgroup" ).add( "#select4 > [disabled]" ).attr( "disabled", false ); + assert.deepEqual( jQuery( "#select4" ).val(), [ "2", "3" ], "Call val() on multiple='multiple' select with some disabled options" ); + + jQuery( "#select4" ).attr( "disabled", true ); + assert.deepEqual( jQuery( "#select4" ).val(), [ "2", "3" ], "Call val() on disabled multiple='multiple' select" ); + + assert.equal( jQuery( "#select5" ).val(), "3", "Check value on ambiguous select." ); + + jQuery( "#select5" ).val( 1 ); + assert.equal( jQuery( "#select5" ).val(), "1", "Check value on ambiguous select." ); + + jQuery( "#select5" ).val( 3 ); + assert.equal( jQuery( "#select5" ).val(), "3", "Check value on ambiguous select." ); + + assert.strictEqual( + jQuery( "" ).val(), + null, + "Select-one with only option disabled (#12584)" + ); + + if ( jQuery.fn.serialize ) { + checks = jQuery( "" ).appendTo( "#form" ); + + assert.deepEqual( checks.serialize(), "", "Get unchecked values." ); + + assert.equal( checks.eq( 3 ).val(), "on", "Make sure a value of 'on' is provided if none is specified." ); + + checks.val( [ "2" ] ); + assert.deepEqual( checks.serialize(), "test=2", "Get a single checked value." ); + + checks.val( [ "1", "" ] ); + assert.deepEqual( checks.serialize(), "test=1&test=", "Get multiple checked values." ); + + checks.val( [ "", "2" ] ); + assert.deepEqual( checks.serialize(), "test=2&test=", "Get multiple checked values." ); + + checks.val( [ "1", "on" ] ); + assert.deepEqual( checks.serialize(), "test=1&test=on", "Get multiple checked values." ); + + checks.remove(); + } + + $button = jQuery( "" ).insertAfter( "#button" ); + assert.equal( $button.val(), "foobar", "Value retrieval on a button does not return innerHTML" ); + assert.equal( $button.val( "baz" ).html(), "text", "Setting the value does not change innerHTML" ); + + assert.equal( jQuery( "" ).val( "test" ).attr( "value" ), "test", "Setting value sets the value attribute" ); +} ); + +QUnit.test( "val() with non-matching values on dropdown list", function( assert ) { + assert.expect( 3 ); + + jQuery( "#select5" ).val( "" ); + assert.equal( jQuery( "#select5" ).val(), null, "Non-matching set on select-one" ); + + var select6 = jQuery( "" ).appendTo( "#form" ); + jQuery( select6 ).val( "nothing" ); + assert.deepEqual( jQuery( select6 ).val(), [], "Non-matching set (single value) on select-multiple" ); + + jQuery( select6 ).val( [ "nothing1", "nothing2" ] ); + assert.deepEqual( jQuery( select6 ).val(), [], "Non-matching set (array of values) on select-multiple" ); + + select6.remove(); +} ); + +if ( "value" in document.createElement( "meter" ) && + "value" in document.createElement( "progress" ) ) { + + QUnit.test( "val() respects numbers without exception (Bug #9319)", function( assert ) { + + assert.expect( 4 ); + + var $meter = jQuery( "" ), + $progress = jQuery( "" ); + + try { + assert.equal( typeof $meter.val(), "number", "meter, returns a number and does not throw exception" ); + assert.equal( $meter.val(), $meter[ 0 ].value, "meter, api matches host and does not throw exception" ); + + assert.equal( typeof $progress.val(), "number", "progress, returns a number and does not throw exception" ); + assert.equal( $progress.val(), $progress[ 0 ].value, "progress, api matches host and does not throw exception" ); + + } catch ( e ) {} + + $meter.remove(); + $progress.remove(); + } ); +} + +var testVal = function( valueObj, assert ) { + assert.expect( 9 ); + + jQuery( "#text1" ).val( valueObj( "test" ) ); + assert.equal( document.getElementById( "text1" ).value, "test", "Check for modified (via val(String)) value of input element" ); + + jQuery( "#text1" ).val( valueObj( undefined ) ); + assert.equal( document.getElementById( "text1" ).value, "", "Check for modified (via val(undefined)) value of input element" ); + + jQuery( "#text1" ).val( valueObj( 67 ) ); + assert.equal( document.getElementById( "text1" ).value, "67", "Check for modified (via val(Number)) value of input element" ); + + jQuery( "#text1" ).val( valueObj( null ) ); + assert.equal( document.getElementById( "text1" ).value, "", "Check for modified (via val(null)) value of input element" ); + + var j, + $select = jQuery( "" ), + $select1 = jQuery( "#select1" ); + + $select1.val( valueObj( "3" ) ); + assert.equal( $select1.val(), "3", "Check for modified (via val(String)) value of select element" ); + + $select1.val( valueObj( 2 ) ); + assert.equal( $select1.val(), "2", "Check for modified (via val(Number)) value of select element" ); + + $select1.append( "" ); + $select1.val( valueObj( 4 ) ); + assert.equal( $select1.val(), "4", "Should be possible to set the val() to a newly created option" ); + + // using contents will get comments regular, text, and comment nodes + j = jQuery( "#nonnodes" ).contents(); + j.val( valueObj( "asdf" ) ); + assert.equal( j.val(), "asdf", "Check node,textnode,comment with val()" ); + j.removeAttr( "value" ); + + $select.val( valueObj( [ "1", "2" ] ) ); + assert.deepEqual( $select.val(), [ "1", "2" ], "Should set array of values" ); +}; + +QUnit.test( "val(String/Number)", function( assert ) { + testVal( bareObj, assert ); +} ); + +QUnit.test( "val(Function)", function( assert ) { + testVal( functionReturningObj, assert ); +} ); + +QUnit.test( "val(Array of Numbers) (Bug #7123)", function( assert ) { + assert.expect( 4 ); + jQuery( "#form" ).append( "" ); + var elements = jQuery( "#form input[name=arrayTest]" ).val( [ 1, 2 ] ); + assert.ok( elements[ 0 ].checked, "First element was checked" ); + assert.ok( elements[ 1 ].checked, "Second element was checked" ); + assert.ok( !elements[ 2 ].checked, "Third element was unchecked" ); + assert.ok( !elements[ 3 ].checked, "Fourth element remained unchecked" ); + + elements.remove(); +} ); + +QUnit.test( "val(Function) with incoming value", function( assert ) { + assert.expect( 10 ); + + var oldVal = jQuery( "#text1" ).val(); + + jQuery( "#text1" ).val( function( i, val ) { + assert.equal( val, oldVal, "Make sure the incoming value is correct." ); + return "test"; + } ); + + assert.equal( document.getElementById( "text1" ).value, "test", "Check for modified (via val(String)) value of input element" ); + + oldVal = jQuery( "#text1" ).val(); + + jQuery( "#text1" ).val( function( i, val ) { + assert.equal( val, oldVal, "Make sure the incoming value is correct." ); + return 67; + } ); + + assert.equal( document.getElementById( "text1" ).value, "67", "Check for modified (via val(Number)) value of input element" ); + + oldVal = jQuery( "#select1" ).val(); + + jQuery( "#select1" ).val( function( i, val ) { + assert.equal( val, oldVal, "Make sure the incoming value is correct." ); + return "3"; + } ); + + assert.equal( jQuery( "#select1" ).val(), "3", "Check for modified (via val(String)) value of select element" ); + + oldVal = jQuery( "#select1" ).val(); + + jQuery( "#select1" ).val( function( i, val ) { + assert.equal( val, oldVal, "Make sure the incoming value is correct." ); + return 2; + } ); + + assert.equal( jQuery( "#select1" ).val(), "2", "Check for modified (via val(Number)) value of select element" ); + + jQuery( "#select1" ).append( "" ); + + oldVal = jQuery( "#select1" ).val(); + + jQuery( "#select1" ).val( function( i, val ) { + assert.equal( val, oldVal, "Make sure the incoming value is correct." ); + return 4; + } ); + + assert.equal( jQuery( "#select1" ).val(), "4", "Should be possible to set the val() to a newly created option" ); +} ); + +// testing if a form.reset() breaks a subsequent call to a select element's .val() (in IE only) +QUnit.test( "val(select) after form.reset() (Bug #2551)", function( assert ) { + assert.expect( 3 ); + + jQuery( "
    " ).appendTo( "#qunit-fixture" ); + + jQuery( "#kkk" ).val( "gf" ); + + document[ "kk" ].reset(); + + assert.equal( jQuery( "#kkk" )[ 0 ].value, "cf", "Check value of select after form reset." ); + assert.equal( jQuery( "#kkk" ).val(), "cf", "Check value of select after form reset." ); + + // re-verify the multi-select is not broken (after form.reset) by our fix for single-select + assert.deepEqual( jQuery( "#select3" ).val(), [ "1", "2" ], "Call val() on a multiple='multiple' select" ); + + jQuery( "#kk" ).remove(); +} ); + +QUnit.test( "select.val(space characters) (gh-2978)", function( assert ) { + assert.expect( 37 ); + + var $select = jQuery( "" ).appendTo( "#qunit-fixture" ), + spaces = { + "\\t": { + html: " ", + val: "\t" + }, + "\\n": { + html: " ", + val: "\n" + }, + "\\r": { + html: " ", + val: "\r" + }, + "\\f": "\f", + "space": " ", + "\\u00a0": "\u00a0", + "\\u1680": "\u1680" + }, + html = ""; + jQuery.each( spaces, function( key, obj ) { + var value = obj.html || obj; + html += ""; + html += ""; + html += ""; + } ); + $select.html( html ); + + jQuery.each( spaces, function( key, obj ) { + var val = obj.val || obj; + $select.val( "attr" + val ); + assert.equal( $select.val(), "attr" + val, "Value ending with space character (" + key + ") selected (attr)" ); + + $select.val( "at" + val + "tr" ); + assert.equal( $select.val(), "at" + val + "tr", "Value with space character (" + key + ") in the middle selected (attr)" ); + + $select.val( val + "attr" ); + assert.equal( $select.val(), val + "attr", "Value starting with space character (" + key + ") selected (attr)" ); + } ); + + jQuery.each( spaces, function( key, obj ) { + var value = obj.html || obj, + val = obj.val || obj; + html = ""; + html += ""; + html += ""; + html += ""; + $select.html( html ); + + + if ( /^\\u/.test( key ) ) { + $select.val( val + "text" ); + assert.equal( $select.val(), val + "text", "Value with non-HTML space character at beginning is not stripped (" + key + ") selected (" + key + "text)" ); + $select.val( "te" + val + "xt" ); + assert.equal( $select.val(), "te" + val + "xt", "Value with non-space whitespace character (" + key + ") in the middle selected (text)" ); + $select.val( "text" + val ); + assert.equal( $select.val(), "text" + val, "Value with non-HTML space character at end is not stripped (" + key + ") selected (text" + key + ")" ); + } else { + $select.val( "text" ); + assert.equal( $select.val(), "text", "Value with HTML space character at beginning or end is stripped (" + key + ") selected (text)" ); + $select.val( "te xt" ); + assert.equal( $select.val(), "te xt", "Value with space character (" + key + ") in the middle selected (text)" ); + } + } ); +} ); + +var testAddClass = function( valueObj, assert ) { + assert.expect( 9 ); + + var pass, j, i, + div = jQuery( "#qunit-fixture div" ); + div.addClass( valueObj( "test" ) ); + pass = true; + for ( i = 0; i < div.length; i++ ) { + if ( !~div.get( i ).className.indexOf( "test" ) ) { + pass = false; + } + } + assert.ok( pass, "Add Class" ); + + // using contents will get regular, text, and comment nodes + j = jQuery( "#nonnodes" ).contents(); + j.addClass( valueObj( "asdf" ) ); + assert.ok( j.hasClass( "asdf" ), "Check node,textnode,comment for addClass" ); + + div = jQuery( "
    " ); + + div.addClass( valueObj( "test" ) ); + assert.equal( div.attr( "class" ), "test", "Make sure there's no extra whitespace." ); + + div.attr( "class", " foo" ); + div.addClass( valueObj( "test" ) ); + assert.equal( div.attr( "class" ), "foo test", "Make sure there's no extra whitespace." ); + + div.attr( "class", "foo" ); + div.addClass( valueObj( "bar baz" ) ); + assert.equal( div.attr( "class" ), "foo bar baz", "Make sure there isn't too much trimming." ); + + div.removeClass(); + div.addClass( valueObj( "foo" ) ).addClass( valueObj( "foo" ) ); + assert.equal( div.attr( "class" ), "foo", "Do not add the same class twice in separate calls." ); + + div.addClass( valueObj( "fo" ) ); + assert.equal( div.attr( "class" ), "foo fo", "Adding a similar class does not get interrupted." ); + div.removeClass().addClass( "wrap2" ); + assert.ok( div.addClass( "wrap" ).hasClass( "wrap" ), "Can add similarly named classes" ); + + div.removeClass(); + div.addClass( valueObj( "bar bar" ) ); + assert.equal( div.attr( "class" ), "bar", "Do not add the same class twice in the same call." ); +}; + +QUnit.test( "addClass(String)", function( assert ) { + testAddClass( bareObj, assert ); +} ); + +QUnit.test( "addClass(Function)", function( assert ) { + testAddClass( functionReturningObj, assert ); +} ); + +QUnit.test( "addClass(Array)", function( assert ) { + testAddClass( arrayFromString, assert ); +} ); + +QUnit.test( "addClass(Function) with incoming value", function( assert ) { + assert.expect( 52 ); + var pass, i, + div = jQuery( "#qunit-fixture div" ), + old = div.map( function() { + return jQuery( this ).attr( "class" ) || ""; + } ); + + div.addClass( function( i, val ) { + if ( this.id !== "_firebugConsole" ) { + assert.equal( val, old[ i ], "Make sure the incoming value is correct." ); + return "test"; + } + } ); + + pass = true; + for ( i = 0; i < div.length; i++ ) { + if ( div.get( i ).className.indexOf( "test" ) === -1 ) { + pass = false; + } + } + assert.ok( pass, "Add Class" ); +} ); + +var testRemoveClass = function( valueObj, assert ) { + assert.expect( 8 ); + + var $set = jQuery( "#qunit-fixture div" ), + div = document.createElement( "div" ); + + $set.addClass( "test" ).removeClass( valueObj( "test" ) ); + + assert.ok( !$set.is( ".test" ), "Remove Class" ); + + $set.addClass( "test" ).addClass( "foo" ).addClass( "bar" ); + $set.removeClass( valueObj( "test" ) ).removeClass( valueObj( "bar" ) ).removeClass( valueObj( "foo" ) ); + + assert.ok( !$set.is( ".test,.bar,.foo" ), "Remove multiple classes" ); + + // Make sure that a null value doesn't cause problems + $set.eq( 0 ).addClass( "expected" ).removeClass( valueObj( null ) ); + assert.ok( $set.eq( 0 ).is( ".expected" ), "Null value passed to removeClass" ); + + $set.eq( 0 ).addClass( "expected" ).removeClass( valueObj( "" ) ); + assert.ok( $set.eq( 0 ).is( ".expected" ), "Empty string passed to removeClass" ); + + // using contents will get regular, text, and comment nodes + $set = jQuery( "#nonnodes" ).contents(); + $set.removeClass( valueObj( "asdf" ) ); + assert.ok( !$set.hasClass( "asdf" ), "Check node,textnode,comment for removeClass" ); + + jQuery( div ).removeClass( valueObj( "foo" ) ); + assert.strictEqual( jQuery( div ).attr( "class" ), undefined, "removeClass doesn't create a class attribute" ); + + div.className = " test foo "; + + jQuery( div ).removeClass( valueObj( "foo" ) ); + assert.equal( div.className, "test", "Make sure remaining className is trimmed." ); + + div.className = " test "; + + jQuery( div ).removeClass( valueObj( "test" ) ); + assert.equal( div.className, "", "Make sure there is nothing left after everything is removed." ); +}; + +QUnit.test( "removeClass(String) - simple", function( assert ) { + testRemoveClass( bareObj, assert ); +} ); + +QUnit.test( "removeClass(Function) - simple", function( assert ) { + testRemoveClass( functionReturningObj, assert ); +} ); + +QUnit.test( "removeClass(Array) - simple", function( assert ) { + testRemoveClass( arrayFromString, assert ); +} ); + +QUnit.test( "removeClass(Function) with incoming value", function( assert ) { + assert.expect( 52 ); + + var $divs = jQuery( "#qunit-fixture div" ).addClass( "test" ), old = $divs.map( function() { + return jQuery( this ).attr( "class" ); + } ); + + $divs.removeClass( function( i, val ) { + if ( this.id !== "_firebugConsole" ) { + assert.equal( val, old[ i ], "Make sure the incoming value is correct." ); + return "test"; + } + } ); + + assert.ok( !$divs.is( ".test" ), "Remove Class" ); +} ); + +QUnit.test( "removeClass() removes duplicates", function( assert ) { + assert.expect( 1 ); + + var $div = jQuery( jQuery.parseHTML( "
    " ) ); + + $div.removeClass( "x" ); + + assert.ok( !$div.hasClass( "x" ), "Element with multiple same classes does not escape the wrath of removeClass()" ); +} ); + +QUnit.test( "removeClass(undefined) is a no-op", function( assert ) { + assert.expect( 1 ); + + var $div = jQuery( "
    " ); + $div.removeClass( undefined ); + + assert.ok( $div.hasClass( "base" ) && $div.hasClass( "second" ), "Element still has classes after removeClass(undefined)" ); +} ); + +var testToggleClass = function( valueObj, assert ) { + assert.expect( 19 ); + + var e = jQuery( "#firstp" ); + assert.ok( !e.is( ".test" ), "Assert class not present" ); + e.toggleClass( valueObj( "test" ) ); + assert.ok( e.is( ".test" ), "Assert class present" ); + e.toggleClass( valueObj( "test" ) ); + assert.ok( !e.is( ".test" ), "Assert class not present" ); + + // class name with a boolean + e.toggleClass( valueObj( "test" ), false ); + assert.ok( !e.is( ".test" ), "Assert class not present" ); + e.toggleClass( valueObj( "test" ), false ); + assert.ok( !e.is( ".test" ), "Assert class still not present" ); + e.toggleClass( valueObj( "test" ), true ); + assert.ok( e.is( ".test" ), "Assert class present" ); + e.toggleClass( valueObj( "test" ), true ); + assert.ok( e.is( ".test" ), "Assert class still present" ); + e.toggleClass( valueObj( "test" ), false ); + assert.ok( !e.is( ".test" ), "Assert class not present" ); + + // multiple class names + e.addClass( "testA testB" ); + assert.ok( e.is( ".testA.testB" ), "Assert 2 different classes present" ); + e.toggleClass( valueObj( "testB testC" ) ); + assert.ok( ( e.is( ".testA.testC" ) && !e.is( ".testB" ) ), "Assert 1 class added, 1 class removed, and 1 class kept" ); + e.toggleClass( valueObj( "testA testC" ) ); + assert.ok( ( !e.is( ".testA" ) && !e.is( ".testB" ) && !e.is( ".testC" ) ), "Assert no class present" ); + + // toggleClass storage + e.toggleClass( true ); + assert.ok( e[ 0 ].className === "", "Assert class is empty (data was empty)" ); + e.addClass( "testD testE" ); + assert.ok( e.is( ".testD.testE" ), "Assert class present" ); + e.toggleClass(); + assert.ok( !e.is( ".testD.testE" ), "Assert class not present" ); + assert.ok( jQuery._data( e[ 0 ], "__className__" ) === "testD testE", "Assert data was stored" ); + e.toggleClass(); + assert.ok( e.is( ".testD.testE" ), "Assert class present (restored from data)" ); + e.toggleClass( false ); + assert.ok( !e.is( ".testD.testE" ), "Assert class not present" ); + e.toggleClass( true ); + assert.ok( e.is( ".testD.testE" ), "Assert class present (restored from data)" ); + e.toggleClass(); + e.toggleClass( false ); + e.toggleClass(); + assert.ok( e.is( ".testD.testE" ), "Assert class present (restored from data)" ); +}; + +QUnit.test( "toggleClass(String|boolean|undefined[, boolean])", function( assert ) { + testToggleClass( bareObj, assert ); +} ); + +QUnit.test( "toggleClass(Function[, boolean])", function( assert ) { + testToggleClass( functionReturningObj, assert ); +} ); + +QUnit.test( "toggleClass(Array[, boolean])", function( assert ) { + testToggleClass( arrayFromString, assert ); +} ); + +QUnit.test( "toggleClass(Function[, boolean]) with incoming value", function( assert ) { + assert.expect( 14 ); + + var e = jQuery( "#firstp" ), + old = e.attr( "class" ) || ""; + + assert.ok( !e.is( ".test" ), "Assert class not present" ); + + e.toggleClass( function( i, val ) { + assert.equal( old, val, "Make sure the incoming value is correct." ); + return "test"; + } ); + assert.ok( e.is( ".test" ), "Assert class present" ); + + old = e.attr( "class" ); + + e.toggleClass( function( i, val ) { + assert.equal( old, val, "Make sure the incoming value is correct." ); + return "test"; + } ); + assert.ok( !e.is( ".test" ), "Assert class not present" ); + + old = e.attr( "class" ) || ""; + + // class name with a boolean + e.toggleClass( function( i, val, state ) { + assert.equal( old, val, "Make sure the incoming value is correct." ); + assert.equal( state, false, "Make sure that the state is passed in." ); + return "test"; + }, false ); + assert.ok( !e.is( ".test" ), "Assert class not present" ); + + old = e.attr( "class" ) || ""; + + e.toggleClass( function( i, val, state ) { + assert.equal( old, val, "Make sure the incoming value is correct." ); + assert.equal( state, true, "Make sure that the state is passed in." ); + return "test"; + }, true ); + assert.ok( e.is( ".test" ), "Assert class present" ); + + old = e.attr( "class" ); + + e.toggleClass( function( i, val, state ) { + assert.equal( old, val, "Make sure the incoming value is correct." ); + assert.equal( state, false, "Make sure that the state is passed in." ); + return "test"; + }, false ); + assert.ok( !e.is( ".test" ), "Assert class not present" ); +} ); + +QUnit.test( "addClass, removeClass, hasClass", function( assert ) { + assert.expect( 17 ); + + var jq = jQuery( "

    Hi

    " ), x = jq[ 0 ]; + + jq.addClass( "hi" ); + assert.equal( x.className, "hi", "Check single added class" ); + + jq.addClass( "foo bar" ); + assert.equal( x.className, "hi foo bar", "Check more added classes" ); + + jq.removeClass(); + assert.equal( x.className, "", "Remove all classes" ); + + jq.addClass( "hi foo bar" ); + jq.removeClass( "foo" ); + assert.equal( x.className, "hi bar", "Check removal of one class" ); + + assert.ok( jq.hasClass( "hi" ), "Check has1" ); + assert.ok( jq.hasClass( "bar" ), "Check has2" ); + + jq = jQuery( "

    " ); + + assert.ok( jq.hasClass( "class1" ), "Check hasClass with line feed" ); + assert.ok( jq.is( ".class1" ), "Check is with line feed" ); + assert.ok( jq.hasClass( "class2" ), "Check hasClass with tab" ); + assert.ok( jq.is( ".class2" ), "Check is with tab" ); + assert.ok( jq.hasClass( "cla.ss3" ), "Check hasClass with dot" ); + assert.ok( jq.hasClass( "class4" ), "Check hasClass with carriage return" ); + assert.ok( jq.is( ".class4" ), "Check is with carriage return" ); + + jq.removeClass( "class2" ); + assert.ok( jq.hasClass( "class2" ) === false, "Check the class has been properly removed" ); + jq.removeClass( "cla" ); + assert.ok( jq.hasClass( "cla.ss3" ), "Check the dotted class has not been removed" ); + jq.removeClass( "cla.ss3" ); + assert.ok( jq.hasClass( "cla.ss3" ) === false, "Check the dotted class has been removed" ); + jq.removeClass( "class4" ); + assert.ok( jq.hasClass( "class4" ) === false, "Check the class has been properly removed" ); +} ); + +QUnit.test( "addClass, removeClass, hasClass on many elements", function( assert ) { + assert.expect( 19 ); + + var elem = jQuery( "

    p0

    p1

    p2

    " ); + + elem.addClass( "hi" ); + assert.equal( elem[ 0 ].className, "hi", "Check single added class" ); + assert.equal( elem[ 1 ].className, "hi", "Check single added class" ); + assert.equal( elem[ 2 ].className, "hi", "Check single added class" ); + + elem.addClass( "foo bar" ); + assert.equal( elem[ 0 ].className, "hi foo bar", "Check more added classes" ); + assert.equal( elem[ 1 ].className, "hi foo bar", "Check more added classes" ); + assert.equal( elem[ 2 ].className, "hi foo bar", "Check more added classes" ); + + elem.removeClass(); + assert.equal( elem[ 0 ].className, "", "Remove all classes" ); + assert.equal( elem[ 1 ].className, "", "Remove all classes" ); + assert.equal( elem[ 2 ].className, "", "Remove all classes" ); + + elem.addClass( "hi foo bar" ); + elem.removeClass( "foo" ); + assert.equal( elem[ 0 ].className, "hi bar", "Check removal of one class" ); + assert.equal( elem[ 1 ].className, "hi bar", "Check removal of one class" ); + assert.equal( elem[ 2 ].className, "hi bar", "Check removal of one class" ); + + assert.ok( elem.hasClass( "hi" ), "Check has1" ); + assert.ok( elem.hasClass( "bar" ), "Check has2" ); + + assert.ok( jQuery( "

    p0

    p1

    p2

    " ).hasClass( "hi" ), + "Did find a class in the first element" ); + assert.ok( jQuery( "

    p0

    p1

    p2

    " ).hasClass( "hi" ), + "Did find a class in the second element" ); + assert.ok( jQuery( "

    p0

    p1

    p2

    " ).hasClass( "hi" ), + "Did find a class in the last element" ); + + assert.ok( jQuery( "

    p0

    p1

    p2

    " ).hasClass( "hi" ), + "Did find a class when present in all elements" ); + + assert.ok( !jQuery( "

    p0

    p1

    p2

    " ).hasClass( "hi" ), + "Did not find a class when not present" ); +} ); + +QUnit.test( "addClass, removeClass, hasClass on many elements - Array", function( assert ) { + assert.expect( 16 ); + + var elem = jQuery( "

    p0

    p1

    p2

    " ); + + elem.addClass( [ "hi" ] ); + assert.equal( elem[ 0 ].className, "hi", "Check single added class" ); + assert.equal( elem[ 1 ].className, "hi", "Check single added class" ); + assert.equal( elem[ 2 ].className, "hi", "Check single added class" ); + + elem.addClass( [ "foo", "bar" ] ); + assert.equal( elem[ 0 ].className, "hi foo bar", "Check more added classes" ); + assert.equal( elem[ 1 ].className, "hi foo bar", "Check more added classes" ); + assert.equal( elem[ 2 ].className, "hi foo bar", "Check more added classes" ); + + elem.removeClass(); + assert.equal( elem[ 0 ].className, "", "Remove all classes" ); + assert.equal( elem[ 1 ].className, "", "Remove all classes" ); + assert.equal( elem[ 2 ].className, "", "Remove all classes" ); + + elem.addClass( [ "hi", "foo", "bar", "baz" ] ); + elem.removeClass( [ "foo" ] ); + assert.equal( elem[ 0 ].className, "hi bar baz", "Check removal of one class" ); + assert.equal( elem[ 1 ].className, "hi bar baz", "Check removal of one class" ); + assert.equal( elem[ 2 ].className, "hi bar baz", "Check removal of one class" ); + + elem.removeClass( [ "bar baz" ] ); + assert.equal( elem[ 0 ].className, "hi", "Check removal of two classes" ); + assert.equal( elem[ 1 ].className, "hi", "Check removal of two classes" ); + assert.equal( elem[ 2 ].className, "hi", "Check removal of two classes" ); + + assert.ok( elem.hasClass( "hi" ), "Check has1" ); +} ); + +QUnit.test( "addClass, removeClass, hasClass on elements with classes with non-HTML whitespace (gh-3072, gh-3003)", function( assert ) { + assert.expect( 9 ); + + var $elem = jQuery( "
    " ); + + function testMatches() { + assert.ok( $elem.is( ".\\A0 test" ), "Element matches with collapsed space" ); + assert.ok( $elem.is( ".\\A0test" ), "Element matches with non-breaking space" ); + assert.ok( $elem.hasClass( "\xA0test" ), "Element has class with non-breaking space" ); + } + + testMatches(); + $elem.addClass( "foo" ); + testMatches(); + $elem.removeClass( "foo" ); + testMatches(); +} ); + +QUnit.test( "contents().hasClass() returns correct values", function( assert ) { + assert.expect( 2 ); + + var $div = jQuery( "
    text
    " ), + $contents = $div.contents(); + + assert.ok( $contents.hasClass( "foo" ), "Found 'foo' in $contents" ); + assert.ok( !$contents.hasClass( "undefined" ), "Did not find 'undefined' in $contents (correctly)" ); +} ); + +QUnit.test( "hasClass correctly interprets non-space separators (#13835)", function( assert ) { + assert.expect( 4 ); + + var + map = { + tab: " ", + "line-feed": " ", + "form-feed": " ", + "carriage-return": " " + }, + classes = jQuery.map( map, function( separator, label ) { + return " " + separator + label + separator + " "; + } ), + $div = jQuery( "
    " ); + + jQuery.each( map, function( label ) { + assert.ok( $div.hasClass( label ), label.replace( "-", " " ) ); + } ); +} ); + +QUnit.test( "coords returns correct values in IE6/IE7, see #10828", function( assert ) { + assert.expect( 1 ); + + var area, + map = jQuery( "" ); + + area = map.html( "a" ).find( "area" ); + assert.equal( area.attr( "coords" ), "0,0,0,0", "did not retrieve coords correctly" ); +} ); + +QUnit.test( "should not throw at $(option).val() (#14686)", function( assert ) { + assert.expect( 1 ); + + try { + jQuery( "" ).val(); + assert.ok( true ); + } catch ( _ ) { + assert.ok( false ); + } +} ); + +QUnit.test( "option value not trimmed when setting via parent select", function( assert ) { + assert.expect( 1 ); + assert.equal( jQuery( "" ).val( "2" ).val(), "2" ); +} ); + +QUnit.test( "Insignificant white space returned for $(option).val() (#14858, gh-2978)", function( assert ) { + assert.expect( 16 ); + + var val = jQuery( "" ).val(); + assert.equal( val.length, 0, "Empty option should have no value" ); + + jQuery.each( [ " ", "\n", "\t", "\f", "\r" ], function( i, character ) { + var val = jQuery( "" ).val(); + assert.equal( val.length, 0, "insignificant white-space returned for value" ); + + val = jQuery( "" ).val(); + assert.equal( val.length, 4, "insignificant white-space returned for value" ); + + val = jQuery( "" ).val(); + assert.equal( val, "te st", "Whitespace is collapsed in values" ); + } ); +} ); + +QUnit.test( "SVG class manipulation (gh-2199)", function( assert ) { + assert.expect( 12 ); + + function createSVGElement( nodeName ) { + return document.createElementNS( "http://www.w3.org/2000/svg", nodeName ); + } + + jQuery.each( [ + "svg", + "rect", + "g" + ], function() { + var elem = jQuery( createSVGElement( this ) ); + + elem.addClass( "awesome" ); + assert.ok( elem.hasClass( "awesome" ), "SVG element (" + this + ") has added class" ); + + elem.removeClass( "awesome" ); + assert.ok( !elem.hasClass( "awesome" ), "SVG element (" + this + ") removes the class" ); + + elem.toggleClass( "awesome" ); + assert.ok( elem.hasClass( "awesome" ), "SVG element (" + this + ") toggles the class on" ); + + elem.toggleClass( "awesome" ); + assert.ok( !elem.hasClass( "awesome" ), "SVG element (" + this + ") toggles the class off" ); + } ); +} ); + +QUnit.test( "non-lowercase boolean attribute getters should not crash", function( assert ) { + assert.expect( 3 ); + + var elem = jQuery( "" ); + + jQuery.each( { + checked: "Checked", + required: "requiRed", + autofocus: "AUTOFOCUS" + }, function( lowercased, original ) { + try { + assert.strictEqual( elem.attr( original ), lowercased, + "The '" + this + "' attribute getter should return the lowercased name" ); + } catch ( e ) { + assert.ok( false, "The '" + this + "' attribute getter threw" ); + } + } ); +} ); diff --git a/test/unit/basic.js b/test/unit/basic.js new file mode 100644 index 0000000..aadf36f --- /dev/null +++ b/test/unit/basic.js @@ -0,0 +1,281 @@ +QUnit.module( "basic", { afterEach: moduleTeardown } ); + +if ( jQuery.ajax ) { +QUnit.test( "ajax", function( assert ) { + assert.expect( 4 ); + + var done = assert.async( 3 ); + + jQuery.ajax( { + type: "GET", + url: url( "mock.php?action=name&name=foo" ), + success: function( msg ) { + assert.strictEqual( msg, "bar", "Check for GET" ); + done(); + } + } ); + + jQuery.ajax( { + type: "POST", + url: url( "mock.php?action=name" ), + data: "name=peter", + success: function( msg ) { + assert.strictEqual( msg, "pan", "Check for POST" ); + done(); + } + } ); + + jQuery( "#first" ).load( url( "name.html" ), function() { + assert.ok( /^ERROR/.test( jQuery( "#first" ).text() ), + "Check if content was injected into the DOM" ); + done(); + } ); +} ); +} + +QUnit.test( "attributes", function( assert ) { + assert.expect( 6 ); + + var a = jQuery( "" ).appendTo( "#qunit-fixture" ), + input = jQuery( "" ).appendTo( "#qunit-fixture" ); + + assert.strictEqual( a.attr( "foo", "bar" ).attr( "foo" ), "bar", ".attr getter/setter" ); + assert.strictEqual( a.removeAttr( "foo" ).attr( "foo" ), undefined, ".removeAttr" ); + assert.strictEqual( a.prop( "href", "#5" ).prop( "href" ), + location.href.replace( /\#.*$/, "" ) + "#5", + ".prop getter/setter" ); + + a.addClass( "abc def ghj" ).removeClass( "def ghj" ); + assert.strictEqual( a.hasClass( "abc" ), true, ".(add|remove|has)Class, class present" ); + assert.strictEqual( a.hasClass( "def" ), false, ".(add|remove|has)Class, class missing" ); + + assert.strictEqual( input.val( "xyz" ).val(), "xyz", ".val getter/setter" ); +} ); + +if ( jQuery.css ) { +QUnit.test( "css", function( assert ) { + assert.expect( 1 ); + + var div = jQuery( "
    " ).appendTo( "#qunit-fixture" ); + + assert.strictEqual( div.css( "width", "50px" ).css( "width" ), "50px", ".css getter/setter" ); +} ); +} + +if ( jQuery.fn.show && jQuery.fn.hide ) { +QUnit.test( "show/hide", function( assert ) { + assert.expect( 2 ); + + var div = jQuery( "
    " ).appendTo( "#qunit-fixture" ); + + div.hide(); + assert.strictEqual( div.css( "display" ), "none", "div hidden" ); + div.show(); + assert.strictEqual( div.css( "display" ), "block", "div shown" ); +} ); +} + +QUnit.test( "core", function( assert ) { + assert.expect( 17 ); + + var elem = jQuery( "
    " ); + + assert.strictEqual( elem.length, 2, "Correct number of elements" ); + + assert.ok( jQuery.isPlainObject( { "a": 2 } ), "jQuery.isPlainObject(object)" ); + assert.ok( !jQuery.isPlainObject( "foo" ), "jQuery.isPlainObject(String)" ); + + assert.ok( jQuery.isXMLDoc( jQuery.parseXML( + "" + ) ), "jQuery.isXMLDoc" ); + + assert.strictEqual( jQuery.inArray( 3, [ "a", 6, false, 3, {} ] ), 3, "jQuery.inArray - true" ); + assert.strictEqual( + jQuery.inArray( 3, [ "a", 6, false, "3", {} ] ), + -1, + "jQuery.inArray - false" + ); + + assert.strictEqual( elem.get( 1 ), elem[ 1 ], ".get" ); + assert.strictEqual( elem.first()[ 0 ], elem[ 0 ], ".first" ); + assert.strictEqual( elem.last()[ 0 ], elem[ 1 ], ".last" ); + + assert.deepEqual( jQuery.map( [ "a", "b", "c" ], function( v, k ) { + return k + v; + } ), [ "0a", "1b", "2c" ], "jQuery.map" ); + + assert.deepEqual( jQuery.merge( [ 1, 2 ], [ "a", "b" ] ), [ 1, 2, "a", "b" ], "jQuery.merge" ); + + assert.deepEqual( jQuery.grep( [ 1, 2, 3 ], function( value ) { + return value % 2 !== 0; + } ), [ 1, 3 ], "jQuery.grep" ); + + assert.deepEqual( jQuery.extend( { a: 2 }, { b: 3 } ), { a: 2, b: 3 }, "jQuery.extend" ); + + jQuery.each( [ 0, 2 ], function( k, v ) { + assert.strictEqual( k * 2, v, "jQuery.each" ); + } ); + + assert.deepEqual( jQuery.makeArray( { 0: "a", 1: "b", 2: "c", length: 3 } ), + [ "a", "b", "c" ], "jQuery.makeArray" ); + + assert.strictEqual( jQuery.parseHTML( "
    " ).length, + 2, "jQuery.parseHTML" ); +} ); + +QUnit.test( "data", function( assert ) { + assert.expect( 4 ); + + var elem = jQuery( "
    " ).appendTo( "#qunit-fixture" ); + + assert.ok( !jQuery.hasData( elem[ 0 ] ), "jQuery.hasData - false" ); + assert.strictEqual( elem.data( "a", "b" ).data( "a" ), "b", ".data getter/setter" ); + assert.strictEqual( elem.data( "c" ), "d", ".data from data-* attributes" ); + assert.ok( jQuery.hasData( elem[ 0 ] ), "jQuery.hasData - true" ); +} ); + +QUnit.test( "dimensions", function( assert ) { + assert.expect( 3 ); + + var elem = jQuery( + "
    " + ).appendTo( "#qunit-fixture" ); + + assert.strictEqual( elem.width( 50 ).width(), 50, ".width getter/setter" ); + assert.strictEqual( elem.innerWidth(), 64, ".innerWidth getter" ); + assert.strictEqual( elem.outerWidth(), 68, ".outerWidth getter" ); +} ); + +QUnit.test( "event", function( assert ) { + assert.expect( 1 ); + + var elem = jQuery( "
    " ).appendTo( "#qunit-fixture" ); + + elem + .on( "click", function() { + assert.ok( false, "click should not fire" ); + } ) + .off( "click" ) + .trigger( "click" ) + .on( "click", function() { + assert.ok( true, "click should fire" ); + } ) + .trigger( "click" ); +} ); + +QUnit.test( "manipulation", function( assert ) { + assert.expect( 5 ); + + var child, + elem1 = jQuery( "
    " ).appendTo( "#qunit-fixture" ), + elem2 = jQuery( "
    " ).appendTo( "#qunit-fixture" ); + + assert.strictEqual( elem1.text( "foo" ).text(), "foo", ".html getter/setter" ); + + assert.strictEqual( + elem1.html( "" ).html(), + "", + ".html getter/setter" + ); + + assert.strictEqual( elem1.append( elem2 )[ 0 ].childNodes[ 1 ], elem2[ 0 ], ".append" ); + assert.strictEqual( elem1.prepend( elem2 )[ 0 ].childNodes[ 0 ], elem2[ 0 ], ".prepend" ); + + child = elem1.find( "span" ); + child.after( "" ); + child.before( "" ); + + assert.strictEqual( + elem1.html(), + "
    ", + ".after/.before" + ); +} ); + +// Support: jsdom 13.2+ +// jsdom returns 0 for offset-related properties +QUnit[ /jsdom\//.test( navigator.userAgent ) ? "skip" : "test" ]( "offset", function( assert ) { + assert.expect( 3 ); + + var parent = jQuery( "
    " ).appendTo( "#qunit-fixture" ), + elem = jQuery( "
    " ).appendTo( parent ); + + assert.strictEqual( elem.offset().top, 25, ".offset getter" ); + assert.strictEqual( elem.position().top, 5, ".position getter" ); + assert.strictEqual( elem.offsetParent()[ 0 ], parent[ 0 ], ".offsetParent" ); +} ); + +QUnit.test( "selector", function( assert ) { + assert.expect( 2 ); + + var elem = jQuery( "
    " ) + .appendTo( "#qunit-fixture" ); + + assert.strictEqual( elem.find( ".a a" ).length, 0, ".find - no result" ); + assert.strictEqual( elem.find( "span.b a" )[ 0 ].nodeName, "A", ".find - one result" ); +} ); + +QUnit.test( "serialize", function( assert ) { + assert.expect( 2 ); + + var params = { "someName": [ 1, 2, 3 ], "regularThing": "blah" }; + assert.strictEqual( jQuery.param( params ), + "someName%5B%5D=1&someName%5B%5D=2&someName%5B%5D=3®ularThing=blah", + "jQuery.param" ); + + assert.strictEqual( jQuery( "#form" ).serialize(), + "action=Test&radio2=on&check=on&hidden=&foo%5Bbar%5D=&name=name&search=search" + + "&select1=&select2=3&select3=1&select3=2&select5=3", + "form serialization as query string" ); +} ); + +QUnit.test( "traversing", function( assert ) { + assert.expect( 12 ); + + var elem = jQuery( "
    foo
    " ) + .appendTo( "#qunit-fixture" ); + + assert.strictEqual( elem.find( "em" ).parent()[ 0 ].nodeName, "B", ".parent" ); + assert.strictEqual( elem.find( "em" ).parents()[ 1 ].nodeName, "A", ".parents" ); + assert.strictEqual( elem.find( "em" ).parentsUntil( "div" ).length, 2, ".parentsUntil" ); + assert.strictEqual( elem.find( "i" ).next()[ 0 ].nodeName, "SPAN", ".next" ); + assert.strictEqual( elem.find( "i" ).prev()[ 0 ].nodeName, "A", ".prev" ); + assert.strictEqual( elem.find( "a" ).nextAll()[ 1 ].nodeName, "SPAN", ".nextAll" ); + assert.strictEqual( elem.find( "span" ).prevAll()[ 1 ].nodeName, "A", ".prevAll" ); + assert.strictEqual( elem.find( "a" ).nextUntil( "span" ).length, 1, ".nextUntil" ); + assert.strictEqual( elem.find( "span" ).prevUntil( "a" ).length, 1, ".prevUntil" ); + assert.strictEqual( elem.find( "i" ).siblings().length, 2, ".siblings" ); + assert.strictEqual( elem.children()[ 2 ].nodeName, "SPAN", ".children" ); + assert.strictEqual( elem.contents()[ 3 ].nodeType, 3, ".contents" ); +} ); + +QUnit.test( "wrap", function( assert ) { + assert.expect( 3 ); + + var elem = jQuery( "
    " ); + + elem.find( "b" ).wrap( "" ); + + assert.strictEqual( + elem.html(), + "", + ".wrap" + ); + + elem.find( "span" ).wrapInner( "" ); + + assert.strictEqual( + elem.html(), + "", + ".wrapInner" + ); + + elem.find( "a" ).wrapAll( "" ); + + assert.strictEqual( + elem.html(), + "", + ".wrapAll" + ); + +} ); diff --git a/test/unit/callbacks.js b/test/unit/callbacks.js new file mode 100644 index 0000000..c1a6eac --- /dev/null +++ b/test/unit/callbacks.js @@ -0,0 +1,397 @@ +QUnit.module( "callbacks", { + afterEach: moduleTeardown +} ); + +( function() { + +if ( !jQuery.Callbacks ) { + return; +} + +( function() { + +var output, + addToOutput = function( string ) { + return function() { + output += string; + }; + }, + outputA = addToOutput( "A" ), + outputB = addToOutput( "B" ), + outputC = addToOutput( "C" ), + tests = { + "": "XABC X XABCABCC X XBB X XABA X XX", + "once": "XABC X X X X X XABA X XX", + "memory": "XABC XABC XABCABCCC XA XBB XB XABA XC XX", + "unique": "XABC X XABCA X XBB X XAB X X", + "stopOnFalse": "XABC X XABCABCC X XBB X XA X XX", + "once memory": "XABC XABC X XA X XA XABA XC XX", + "once unique": "XABC X X X X X XAB X X", + "once stopOnFalse": "XABC X X X X X XA X XX", + "memory unique": "XABC XA XABCA XA XBB XB XAB XC X", + "memory stopOnFalse": "XABC XABC XABCABCCC XA XBB XB XA X XX", + "unique stopOnFalse": "XABC X XABCA X XBB X XA X X" + }, + filters = { + "no filter": undefined, + "filter": function( fn ) { + return function() { + return fn.apply( this, arguments ); + }; + } + }; + + function showFlags( flags ) { + if ( typeof flags === "string" ) { + return "'" + flags + "'"; + } + var output = [], key; + for ( key in flags ) { + output.push( "'" + key + "': " + flags[ key ] ); + } + return "{ " + output.join( ", " ) + " }"; + } + +jQuery.each( tests, function( strFlags, resultString ) { + + var objectFlags = {}; + + jQuery.each( strFlags.split( " " ), function() { + if ( this.length ) { + objectFlags[ this ] = true; + } + } ); + + jQuery.each( filters, function( filterLabel ) { + + jQuery.each( { + "string": strFlags, + "object": objectFlags + }, function( flagsTypes, flags ) { + + QUnit.test( "jQuery.Callbacks( " + showFlags( flags ) + " ) - " + filterLabel, function( assert ) { + + assert.expect( 29 ); + + var cblist, + results = resultString.split( /\s+/ ); + + // Basic binding and firing + output = "X"; + cblist = jQuery.Callbacks( flags ); + assert.strictEqual( cblist.locked(), false, ".locked() initially false" ); + assert.strictEqual( cblist.disabled(), false, ".disabled() initially false" ); + assert.strictEqual( cblist.fired(), false, ".fired() initially false" ); + cblist.add( function( str ) { + output += str; + } ); + assert.strictEqual( cblist.fired(), false, ".fired() still false after .add" ); + cblist.fire( "A" ); + assert.strictEqual( output, "XA", "Basic binding and firing" ); + assert.strictEqual( cblist.fired(), true, ".fired() detects firing" ); + output = "X"; + cblist.disable(); + cblist.add( function( str ) { + output += str; + } ); + assert.strictEqual( output, "X", "Adding a callback after disabling" ); + cblist.fire( "A" ); + assert.strictEqual( output, "X", "Firing after disabling" ); + assert.strictEqual( cblist.disabled(), true, ".disabled() becomes true" ); + assert.strictEqual( cblist.locked(), true, "disabling locks" ); + + // Emptying while firing (#13517) + cblist = jQuery.Callbacks( flags ); + cblist.add( cblist.empty ); + cblist.add( function() { + assert.ok( false, "not emptied" ); + } ); + cblist.fire(); + + // Disabling while firing + cblist = jQuery.Callbacks( flags ); + cblist.add( cblist.disable ); + cblist.add( function() { + assert.ok( false, "not disabled" ); + } ); + cblist.fire(); + + // Basic binding and firing (context, arguments) + output = "X"; + cblist = jQuery.Callbacks( flags ); + cblist.add( function() { + assert.equal( this, window, "Basic binding and firing (context)" ); + output += Array.prototype.join.call( arguments, "" ); + } ); + cblist.fireWith( window, [ "A", "B" ] ); + assert.strictEqual( output, "XAB", "Basic binding and firing (arguments)" ); + + // fireWith with no arguments + output = ""; + cblist = jQuery.Callbacks( flags ); + cblist.add( function() { + assert.equal( this, window, "fireWith with no arguments (context is window)" ); + assert.strictEqual( arguments.length, 0, "fireWith with no arguments (no arguments)" ); + } ); + cblist.fireWith(); + + // Basic binding, removing and firing + output = "X"; + cblist = jQuery.Callbacks( flags ); + cblist.add( outputA, outputB, outputC ); + cblist.remove( outputB, outputC ); + cblist.fire(); + assert.strictEqual( output, "XA", "Basic binding, removing and firing" ); + + // Empty + output = "X"; + cblist = jQuery.Callbacks( flags ); + cblist.add( outputA ); + cblist.add( outputB ); + cblist.add( outputC ); + cblist.empty(); + cblist.fire(); + assert.strictEqual( output, "X", "Empty" ); + + // Locking + output = "X"; + cblist = jQuery.Callbacks( flags ); + cblist.add( function( str ) { + output += str; + } ); + cblist.lock(); + cblist.add( function( str ) { + output += str; + } ); + cblist.fire( "A" ); + cblist.add( function( str ) { + output += str; + } ); + assert.strictEqual( output, "X", "Lock early" ); + assert.strictEqual( cblist.locked(), true, "Locking reflected in accessor" ); + + // Locking while firing (gh-1990) + output = "X"; + cblist = jQuery.Callbacks( flags ); + cblist.add( cblist.lock ); + cblist.add( function( str ) { + output += str; + } ); + cblist.fire( "A" ); + assert.strictEqual( output, "XA", "Locking doesn't abort execution (gh-1990)" ); + + // Ordering + output = "X"; + cblist = jQuery.Callbacks( flags ); + cblist.add( function() { + cblist.add( outputC ); + outputA(); + }, outputB ); + cblist.fire(); + assert.strictEqual( output, results.shift(), "Proper ordering" ); + + // Add and fire again + output = "X"; + cblist.add( function() { + cblist.add( outputC ); + outputA(); + }, outputB ); + assert.strictEqual( output, results.shift(), "Add after fire" ); + + output = "X"; + cblist.fire(); + assert.strictEqual( output, results.shift(), "Fire again" ); + + // Multiple fire + output = "X"; + cblist = jQuery.Callbacks( flags ); + cblist.add( function( str ) { + output += str; + } ); + cblist.fire( "A" ); + assert.strictEqual( output, "XA", "Multiple fire (first fire)" ); + output = "X"; + cblist.add( function( str ) { + output += str; + } ); + assert.strictEqual( output, results.shift(), "Multiple fire (first new callback)" ); + output = "X"; + cblist.fire( "B" ); + assert.strictEqual( output, results.shift(), "Multiple fire (second fire)" ); + output = "X"; + cblist.add( function( str ) { + output += str; + } ); + assert.strictEqual( output, results.shift(), "Multiple fire (second new callback)" ); + + // Return false + output = "X"; + cblist = jQuery.Callbacks( flags ); + cblist.add( outputA, function() { return false; }, outputB ); + cblist.add( outputA ); + cblist.fire(); + assert.strictEqual( output, results.shift(), "Callback returning false" ); + + // Add another callback (to control lists with memory do not fire anymore) + output = "X"; + cblist.add( outputC ); + assert.strictEqual( output, results.shift(), "Adding a callback after one returned false" ); + + // Callbacks are not iterated + output = ""; + function handler() { + output += "X"; + } + handler.method = function() { + output += "!"; + }; + cblist = jQuery.Callbacks( flags ); + cblist.add( handler ); + cblist.add( handler ); + cblist.fire(); + assert.strictEqual( output, results.shift(), "No callback iteration" ); + } ); + } ); + } ); +} ); + +} )(); + +QUnit.test( "jQuery.Callbacks( options ) - options are copied", function( assert ) { + + assert.expect( 1 ); + + var options = { + "unique": true + }, + cb = jQuery.Callbacks( options ), + count = 0, + fn = function() { + assert.ok( !( count++ ), "called once" ); + }; + options[ "unique" ] = false; + cb.add( fn, fn ); + cb.fire(); +} ); + +QUnit.test( "jQuery.Callbacks.fireWith - arguments are copied", function( assert ) { + + assert.expect( 1 ); + + var cb = jQuery.Callbacks( "memory" ), + args = [ "hello" ]; + + cb.fireWith( null, args ); + args[ 0 ] = "world"; + + cb.add( function( hello ) { + assert.strictEqual( hello, "hello", "arguments are copied internally" ); + } ); +} ); + +QUnit.test( "jQuery.Callbacks.remove - should remove all instances", function( assert ) { + + assert.expect( 1 ); + + var cb = jQuery.Callbacks(); + + function fn() { + assert.ok( false, "function wasn't removed" ); + } + + cb.add( fn, fn, function() { + assert.ok( true, "end of test" ); + } ).remove( fn ).fire(); +} ); + +QUnit.test( "jQuery.Callbacks.has", function( assert ) { + + assert.expect( 13 ); + + var cb = jQuery.Callbacks(); + function getA() { + return "A"; + } + function getB() { + return "B"; + } + function getC() { + return "C"; + } + cb.add( getA, getB, getC ); + assert.strictEqual( cb.has(), true, "No arguments to .has() returns whether callback function(s) are attached or not" ); + assert.strictEqual( cb.has( getA ), true, "Check if a specific callback function is in the Callbacks list" ); + + cb.remove( getB ); + assert.strictEqual( cb.has( getB ), false, "Remove a specific callback function and make sure its no longer there" ); + assert.strictEqual( cb.has( getA ), true, "Remove a specific callback function and make sure other callback function is still there" ); + + cb.empty(); + assert.strictEqual( cb.has(), false, "Empty list and make sure there are no callback function(s)" ); + assert.strictEqual( cb.has( getA ), false, "Check for a specific function in an empty() list" ); + + cb.add( getA, getB, function() { + assert.strictEqual( cb.has(), true, "Check if list has callback function(s) from within a callback function" ); + assert.strictEqual( cb.has( getA ), true, "Check if list has a specific callback from within a callback function" ); + } ).fire(); + + assert.strictEqual( cb.has(), true, "Callbacks list has callback function(s) after firing" ); + + cb.disable(); + assert.strictEqual( cb.has(), false, "disabled() list has no callback functions (returns false)" ); + assert.strictEqual( cb.has( getA ), false, "Check for a specific function in a disabled() list" ); + + cb = jQuery.Callbacks( "unique" ); + cb.add( getA ); + cb.add( getA ); + assert.strictEqual( cb.has(), true, "Check if unique list has callback function(s) attached" ); + cb.lock(); + assert.strictEqual( cb.has(), false, "locked() list is empty and returns false" ); +} ); + +QUnit.test( "jQuery.Callbacks() - adding a string doesn't cause a stack overflow", function( assert ) { + + assert.expect( 1 ); + + jQuery.Callbacks().add( "hello world" ); + + assert.ok( true, "no stack overflow" ); +} ); + +QUnit.test( "jQuery.Callbacks() - disabled callback doesn't fire (gh-1790)", function( assert ) { + + assert.expect( 1 ); + + var cb = jQuery.Callbacks(), + fired = false, + shot = function() { fired = true; }; + + cb.disable(); + cb.empty(); + cb.add( shot ); + cb.fire(); + assert.ok( !fired, "Disabled callback function didn't fire" ); +} ); + +QUnit.test( "jQuery.Callbacks() - list with memory stays locked (gh-3469)", function( assert ) { + + assert.expect( 3 ); + + var cb = jQuery.Callbacks( "memory" ), + fired = 0, + count1 = function() { fired += 1; }, + count2 = function() { fired += 10; }; + + cb.add( count1 ); + cb.fire(); + assert.equal( fired, 1, "Pre-lock() fire" ); + + cb.lock(); + cb.add( count2 ); + assert.equal( fired, 11, "Post-lock() add" ); + + cb.fire(); + assert.equal( fired, 11, "Post-lock() fire ignored" ); +} ); + +} )(); diff --git a/test/unit/core.js b/test/unit/core.js new file mode 100644 index 0000000..e45e493 --- /dev/null +++ b/test/unit/core.js @@ -0,0 +1,1573 @@ +QUnit.module( "core", { + beforeEach: function() { + this.sandbox = sinon.sandbox.create(); + }, + afterEach: function() { + this.sandbox.restore(); + return moduleTeardown.apply( this, arguments ); + } +} ); + +QUnit.test( "Basic requirements", function( assert ) { + assert.expect( 7 ); + assert.ok( Array.prototype.push, "Array.push()" ); + assert.ok( Function.prototype.apply, "Function.apply()" ); + assert.ok( document.getElementById, "getElementById" ); + assert.ok( document.getElementsByTagName, "getElementsByTagName" ); + assert.ok( RegExp, "RegExp" ); + assert.ok( jQuery, "jQuery" ); + assert.ok( $, "$" ); +} ); + +QUnit.test( "jQuery()", function( assert ) { + + var elem, i, + obj = jQuery( "div" ), + code = jQuery( "" ), + img = jQuery( "" ), + div = jQuery( "

    " ), + exec = false, + expected = 23, + attrObj = { + "text": "test", + "class": "test2", + "id": "test3" + }; + + // The $(html, props) signature can stealth-call any $.fn method, check for a + // few here but beware of modular builds where these methods may be excluded. + if ( jQuery.fn.click ) { + expected++; + attrObj[ "click" ] = function() { assert.ok( exec, "Click executed." ); }; + } + if ( jQuery.fn.width ) { + expected++; + attrObj[ "width" ] = 10; + } + if ( jQuery.fn.offset ) { + expected++; + attrObj[ "offset" ] = { "top": 1, "left": 1 }; + } + if ( jQuery.fn.css ) { + expected += 2; + attrObj[ "css" ] = { "paddingLeft": 1, "paddingRight": 1 }; + } + if ( jQuery.fn.attr ) { + expected++; + attrObj.attr = { "desired": "very" }; + } + + assert.expect( expected ); + + // Basic constructor's behavior + assert.equal( jQuery().length, 0, "jQuery() === jQuery([])" ); + assert.equal( jQuery( undefined ).length, 0, "jQuery(undefined) === jQuery([])" ); + assert.equal( jQuery( null ).length, 0, "jQuery(null) === jQuery([])" ); + assert.equal( jQuery( "" ).length, 0, "jQuery('') === jQuery([])" ); + assert.deepEqual( jQuery( obj ).get(), obj.get(), "jQuery(jQueryObj) == jQueryObj" ); + + // Invalid #id goes to Sizzle which will throw an error (gh-1682) + try { + jQuery( "#" ); + } catch ( e ) { + assert.ok( true, "Threw an error on #id with no id" ); + } + + // can actually yield more than one, when iframes are included, the window is an array as well + assert.equal( jQuery( window ).length, 1, "Correct number of elements generated for jQuery(window)" ); + +/* + // disabled since this test was doing nothing. i tried to fix it but i'm not sure + // what the expected behavior should even be. FF returns "\n" for the text node + // make sure this is handled + var crlfContainer = jQuery('

    \r\n

    '); + var x = crlfContainer.contents().get(0).nodeValue; + assert.equal( x, what???, "Check for \\r and \\n in jQuery()" ); +*/ + + /* // Disabled until we add this functionality in + var pass = true; + try { + jQuery("
    Testing
    ").appendTo(document.getElementById("iframe").contentDocument.body); + } catch(e){ + pass = false; + } + assert.ok( pass, "jQuery('<tag>') needs optional document parameter to ease cross-frame DOM wrangling, see #968" );*/ + + assert.equal( code.length, 1, "Correct number of elements generated for code" ); + assert.equal( code.parent().length, 0, "Make sure that the generated HTML has no parent." ); + + assert.equal( img.length, 1, "Correct number of elements generated for img" ); + assert.equal( img.parent().length, 0, "Make sure that the generated HTML has no parent." ); + + assert.equal( div.length, 4, "Correct number of elements generated for div hr code b" ); + assert.equal( div.parent().length, 0, "Make sure that the generated HTML has no parent." ); + + assert.equal( jQuery( [ 1, 2, 3 ] ).get( 1 ), 2, "Test passing an array to the factory" ); + + assert.equal( jQuery( document.body ).get( 0 ), jQuery( "body" ).get( 0 ), "Test passing an html node to the factory" ); + + elem = jQuery( " hello" )[ 0 ]; + assert.equal( elem.nodeName.toLowerCase(), "em", "leading space" ); + + elem = jQuery( "\n\nworld" )[ 0 ]; + assert.equal( elem.nodeName.toLowerCase(), "em", "leading newlines" ); + + elem = jQuery( "
    ", attrObj ); + + if ( jQuery.fn.width ) { + assert.equal( elem[ 0 ].style.width, "10px", "jQuery() quick setter width" ); + } + + if ( jQuery.fn.offset ) { + assert.equal( elem[ 0 ].style.top, "1px", "jQuery() quick setter offset" ); + } + + if ( jQuery.fn.css ) { + assert.equal( elem[ 0 ].style.paddingLeft, "1px", "jQuery quick setter css" ); + assert.equal( elem[ 0 ].style.paddingRight, "1px", "jQuery quick setter css" ); + } + + if ( jQuery.fn.attr ) { + assert.equal( elem[ 0 ].getAttribute( "desired" ), "very", "jQuery quick setter attr" ); + } + + assert.equal( elem[ 0 ].childNodes.length, 1, "jQuery quick setter text" ); + assert.equal( elem[ 0 ].firstChild.nodeValue, "test", "jQuery quick setter text" ); + assert.equal( elem[ 0 ].className, "test2", "jQuery() quick setter class" ); + assert.equal( elem[ 0 ].id, "test3", "jQuery() quick setter id" ); + + exec = true; + elem.trigger( "click" ); + + // manually clean up detached elements + elem.remove(); + + for ( i = 0; i < 3; ++i ) { + elem = jQuery( "" ); + } + assert.equal( elem[ 0 ].defaultValue, "TEST", "Ensure cached nodes are cloned properly (Bug #6655)" ); + + elem = jQuery( "", {} ); + assert.strictEqual( elem[ 0 ].ownerDocument, document, + "Empty attributes object is not interpreted as a document (trac-8950)" ); +} ); + +QUnit[ jQuery.find.compile ? "test" : "skip" ]( "jQuery(selector, context)", function( assert ) { + assert.expect( 3 ); + assert.deepEqual( jQuery( "div p", "#qunit-fixture" ).get(), q( "sndp", "en", "sap" ), "Basic selector with string as context" ); + assert.deepEqual( jQuery( "div p", q( "qunit-fixture" )[ 0 ] ).get(), q( "sndp", "en", "sap" ), "Basic selector with element as context" ); + assert.deepEqual( jQuery( "div p", jQuery( "#qunit-fixture" ) ).get(), q( "sndp", "en", "sap" ), "Basic selector with jQuery object as context" ); +} ); + +QUnit.test( "globalEval", function( assert ) { + assert.expect( 3 ); + Globals.register( "globalEvalTest" ); + + jQuery.globalEval( "globalEvalTest = 1;" ); + assert.equal( window.globalEvalTest, 1, "Test variable assignments are global" ); + + jQuery.globalEval( "var globalEvalTest = 2;" ); + assert.equal( window.globalEvalTest, 2, "Test variable declarations are global" ); + + jQuery.globalEval( "this.globalEvalTest = 3;" ); + assert.equal( window.globalEvalTest, 3, "Test context (this) is the window object" ); +} ); + +QUnit.test( "globalEval with 'use strict'", function( assert ) { + assert.expect( 1 ); + Globals.register( "strictEvalTest" ); + + jQuery.globalEval( "'use strict'; var strictEvalTest = 1;" ); + assert.equal( window.strictEvalTest, 1, "Test variable declarations are global (strict mode)" ); +} ); + +QUnit.test( "globalEval execution after script injection (#7862)", function( assert ) { + assert.expect( 1 ); + + var now, + script = document.createElement( "script" ); + + script.src = baseURL + "mock.php?action=wait&wait=2&script=1"; + + now = Date.now(); + document.body.appendChild( script ); + + jQuery.globalEval( "var strictEvalTest = " + Date.now() + ";" ); + assert.ok( window.strictEvalTest - now < 500, "Code executed synchronously" ); +} ); + +testIframe( + "globalEval with custom document context", + "core/globaleval-context.html", + function( assert, framejQuery, frameWindow, frameDocument ) { + assert.expect( 2 ); + + jQuery.globalEval( "window.scriptTest = true;", {}, frameDocument ); + assert.ok( !window.scriptTest, "script executed in iframe context" ); + assert.ok( frameWindow.scriptTest, "script executed in iframe context" ); + } +); + + +QUnit.test( "noConflict", function( assert ) { + assert.expect( 7 ); + + var $$ = jQuery; + + assert.strictEqual( jQuery, jQuery.noConflict(), "noConflict returned the jQuery object" ); + assert.strictEqual( window[ "jQuery" ], $$, "Make sure jQuery wasn't touched." ); + assert.strictEqual( window[ "$" ], original$, "Make sure $ was reverted." ); + + jQuery = $ = $$; + + assert.strictEqual( jQuery.noConflict( true ), $$, "noConflict returned the jQuery object" ); + assert.strictEqual( window[ "jQuery" ], originaljQuery, "Make sure jQuery was reverted." ); + assert.strictEqual( window[ "$" ], original$, "Make sure $ was reverted." ); + assert.ok( $$().pushStack( [] ), "Make sure that jQuery still works." ); + + window[ "jQuery" ] = jQuery = $$; +} ); + +QUnit.test( "isPlainObject", function( assert ) { + var done = assert.async(); + + assert.expect( 23 ); + + var pass, iframe, doc, parentObj, childObj, deep, + fn = function() {}; + + // The use case that we want to match + assert.ok( jQuery.isPlainObject( {} ), "{}" ); + assert.ok( jQuery.isPlainObject( new window.Object() ), "new Object" ); + assert.ok( jQuery.isPlainObject( { constructor: fn } ), + "plain object with constructor property" ); + assert.ok( jQuery.isPlainObject( { constructor: "foo" } ), + "plain object with primitive constructor property" ); + + parentObj = {}; + childObj = Object.create( parentObj ); + assert.ok( !jQuery.isPlainObject( childObj ), "Object.create({})" ); + parentObj.foo = "bar"; + assert.ok( !jQuery.isPlainObject( childObj ), "Object.create({...})" ); + childObj.bar = "foo"; + assert.ok( !jQuery.isPlainObject( childObj ), "extend(Object.create({...}), ...)" ); + + // Not objects shouldn't be matched + assert.ok( !jQuery.isPlainObject( "" ), "string" ); + assert.ok( !jQuery.isPlainObject( 0 ) && !jQuery.isPlainObject( 1 ), "number" ); + assert.ok( !jQuery.isPlainObject( true ) && !jQuery.isPlainObject( false ), "boolean" ); + assert.ok( !jQuery.isPlainObject( null ), "null" ); + assert.ok( !jQuery.isPlainObject( undefined ), "undefined" ); + + // Arrays shouldn't be matched + assert.ok( !jQuery.isPlainObject( [] ), "array" ); + + // Instantiated objects shouldn't be matched + assert.ok( !jQuery.isPlainObject( new Date() ), "new Date" ); + + // Functions shouldn't be matched + assert.ok( !jQuery.isPlainObject( fn ), "fn" ); + + // Again, instantiated objects shouldn't be matched + assert.ok( !jQuery.isPlainObject( new fn() ), "new fn (no methods)" ); + + // Makes the function a little more realistic + // (and harder to detect, incidentally) + fn.prototype[ "someMethod" ] = function() {}; + + // Again, instantiated objects shouldn't be matched + assert.ok( !jQuery.isPlainObject( new fn() ), "new fn" ); + + // Instantiated objects with primitive constructors shouldn't be matched + fn.prototype.constructor = "foo"; + assert.ok( !jQuery.isPlainObject( new fn() ), "new fn with primitive constructor" ); + + // Deep object + deep = { "foo": { "baz": true }, "foo2": document }; + assert.ok( jQuery.isPlainObject( deep ), "Object with objects is still plain" ); + + // DOM Element + assert.ok( !jQuery.isPlainObject( document.createElement( "div" ) ), "DOM Element" ); + + // Window + assert.ok( !jQuery.isPlainObject( window ), "window" ); + + pass = false; + try { + jQuery.isPlainObject( window.location ); + pass = true; + } catch ( e ) {} + assert.ok( pass, "Does not throw exceptions on host objects" ); + + // Objects from other windows should be matched + Globals.register( "iframeDone" ); + window.iframeDone = function( otherObject, detail ) { + window.iframeDone = undefined; + iframe.parentNode.removeChild( iframe ); + assert.ok( jQuery.isPlainObject( new otherObject() ), "new otherObject" + ( detail ? " - " + detail : "" ) ); + done(); + }; + + try { + iframe = jQuery( "#qunit-fixture" )[ 0 ].appendChild( document.createElement( "iframe" ) ); + doc = iframe.contentDocument || iframe.contentWindow.document; + doc.open(); + doc.write( "" ); + doc.close(); + } catch ( e ) { + window.iframeDone( Object, "iframes not supported" ); + } +} ); + +QUnit[ typeof Symbol === "function" ? "test" : "skip" ]( "isPlainObject(Symbol)", function( assert ) { + assert.expect( 2 ); + + assert.equal( jQuery.isPlainObject( Symbol() ), false, "Symbol" ); + assert.equal( jQuery.isPlainObject( Object( Symbol() ) ), false, "Symbol inside an object" ); +} ); + +QUnit.test( "isPlainObject(localStorage)", function( assert ) { + assert.expect( 1 ); + + assert.equal( jQuery.isPlainObject( localStorage ), false ); +} ); + +QUnit[ "assign" in Object ? "test" : "skip" ]( "isPlainObject(Object.assign(...))", + function( assert ) { + assert.expect( 1 ); + + var parentObj = { foo: "bar" }; + var childObj = Object.assign( Object.create( parentObj ), { bar: "foo" } ); + + assert.ok( !jQuery.isPlainObject( childObj ), "isPlainObject(Object.assign(...))" ); + } +); + +QUnit.test( "isXMLDoc - HTML", function( assert ) { + assert.expect( 4 ); + + assert.ok( !jQuery.isXMLDoc( document ), "HTML document" ); + assert.ok( !jQuery.isXMLDoc( document.documentElement ), "HTML documentElement" ); + assert.ok( !jQuery.isXMLDoc( document.body ), "HTML Body Element" ); + + var body, + iframe = document.createElement( "iframe" ); + document.body.appendChild( iframe ); + + try { + body = jQuery( iframe ).contents()[ 0 ]; + + try { + assert.ok( !jQuery.isXMLDoc( body ), "Iframe body element" ); + } catch ( e ) { + assert.ok( false, "Iframe body element exception" ); + } + + } catch ( e ) { + assert.ok( true, "Iframe body element - iframe not working correctly" ); + } + + document.body.removeChild( iframe ); +} ); + +QUnit.test( "isXMLDoc - embedded SVG", function( assert ) { + assert.expect( 6 ); + + var htmlTree = jQuery( "
    " + + "" + + "" + + "" + + "
    " + )[ 0 ]; + + assert.strictEqual( jQuery.isXMLDoc( htmlTree ), false, "disconnected div element" ); + assert.strictEqual( jQuery.isXMLDoc( htmlTree.firstChild ), true, + "disconnected HTML-embedded SVG root element" ); + + assert.strictEqual( jQuery.isXMLDoc( htmlTree.firstChild.firstChild ), true, + "disconnected HTML-embedded SVG child element" ); + + document.getElementById( "qunit-fixture" ).appendChild( htmlTree ); + assert.strictEqual( jQuery.isXMLDoc( htmlTree ), false, "connected div element" ); + assert.strictEqual( jQuery.isXMLDoc( htmlTree.firstChild ), true, + "connected HTML-embedded SVG root element" ); + + assert.strictEqual( jQuery.isXMLDoc( htmlTree.firstChild.firstChild ), true, + "disconnected HTML-embedded SVG child element" ); +} ); + +QUnit.test( "isXMLDoc - XML", function( assert ) { + assert.expect( 8 ); + + var xml = createDashboardXML(); + var svg = jQuery.parseXML( + "" + + "" + ); + assert.ok( jQuery.isXMLDoc( xml ), "XML document" ); + assert.ok( jQuery.isXMLDoc( xml.documentElement ), "XML documentElement" ); + assert.ok( jQuery.isXMLDoc( xml.documentElement.firstChild ), "XML child element" ); + assert.ok( jQuery.isXMLDoc( jQuery( "tab", xml )[ 0 ] ), "XML tab Element" ); + + assert.ok( jQuery.isXMLDoc( svg ), "SVG document" ); + assert.ok( jQuery.isXMLDoc( svg.documentElement ), "SVG documentElement" ); + assert.ok( jQuery.isXMLDoc( svg.documentElement.firstChild ), "SVG child element" ); + assert.ok( jQuery.isXMLDoc( jQuery( "desc", svg )[ 0 ] ), "XML desc Element" ); +} ); + +QUnit.test( "XSS via location.hash", function( assert ) { + var done = assert.async(); + assert.expect( 1 ); + + jQuery[ "_check9521" ] = function( x ) { + assert.ok( x, "script called from #id-like selector with inline handler" ); + jQuery( "#check9521" ).remove(); + delete jQuery[ "_check9521" ]; + done(); + }; + try { + + // This throws an error because it's processed like an id + jQuery( "#" ).appendTo( "#qunit-fixture" ); + } catch ( err ) { + jQuery[ "_check9521" ]( true ); + } +} ); + +QUnit.test( "jQuery('html')", function( assert ) { + assert.expect( 18 ); + + var s, div, j; + + jQuery[ "foo" ] = false; + s = jQuery( "" )[ 0 ]; + assert.ok( s, "Creating a script" ); + assert.ok( !jQuery[ "foo" ], "Make sure the script wasn't executed prematurely" ); + jQuery( "body" ).append( "" ); + assert.ok( jQuery[ "foo" ], "Executing a script's contents in the right context" ); + + // Test multi-line HTML + div = jQuery( "
    \r\nsome text\n

    some p

    \nmore text\r\n
    " )[ 0 ]; + assert.equal( div.nodeName.toUpperCase(), "DIV", "Make sure we're getting a div." ); + assert.equal( div.firstChild.nodeType, 3, "Text node." ); + assert.equal( div.lastChild.nodeType, 3, "Text node." ); + assert.equal( div.childNodes[ 1 ].nodeType, 1, "Paragraph." ); + assert.equal( div.childNodes[ 1 ].firstChild.nodeType, 3, "Paragraph text." ); + + assert.ok( jQuery( "" )[ 0 ], "Creating a link" ); + + assert.ok( !jQuery( "" )[ 0 ].parentNode, "Create a script" ); + + assert.ok( jQuery( "" ).attr( "type", "hidden" ), "Create an input and set the type." ); + + j = jQuery( "hi there " ); + assert.ok( j.length >= 2, "Check node,textnode,comment creation (some browsers delete comments)" ); + + assert.ok( !jQuery( "" )[ 0 ].selected, "Make sure that options are auto-selected #2050" ); + + assert.ok( jQuery( "
    " )[ 0 ], "Create a div with closing tag." ); + assert.ok( jQuery( "
    " )[ 0 ], "Create a table with closing tag." ); + + assert.equal( jQuery( "element[attribute='
    ']" ).length, 0, + "When html is within brackets, do not recognize as html." ); + + //equal( jQuery( "element[attribute=
    ]" ).length, 0, + // "When html is within brackets, do not recognize as html." ); + if ( jQuery.find.compile ) { + assert.equal( jQuery( "element:not(
    )" ).length, 0, + "When html is within parens, do not recognize as html." ); + } else { + assert.ok( "skip", "Complex :not not supported in selector-native" ); + } + assert.equal( jQuery( "\\" ).length, 0, "Ignore escaped html characters" ); +} ); + +QUnit.test( "jQuery(element with non-alphanumeric name)", function( assert ) { + assert.expect( 36 ); + + jQuery.each( [ "-", ":" ], function( i, symbol ) { + jQuery.each( [ "thead", "tbody", "tfoot", "colgroup", "caption", "tr", "th", "td" ], + function( j, tag ) { + var tagName = tag + symbol + "test"; + var el = jQuery( "<" + tagName + ">" ); + assert.ok( el[ 0 ], "Create a " + tagName + " element" ); + assert.ok( el[ 0 ].nodeName === tagName.toUpperCase(), + tagName + " element has expected node name" ); + } + ); + + var tagName = [ "tr", "multiple", "symbol" ].join( symbol ); + var el = jQuery( "<" + tagName + ">" ); + assert.ok( el[ 0 ], "Create a " + tagName + " element" ); + assert.ok( el[ 0 ].nodeName === tagName.toUpperCase(), + tagName + " element has expected node name" ); + } ); +} ); + +QUnit.test( "jQuery('massive html #7990')", function( assert ) { + assert.expect( 3 ); + + var i, + li = "
  1. very very very very large html string
  2. ", + html = [ "
      " ]; + + for ( i = 0; i < 30000; i += 1 ) { + html[ html.length ] = li; + } + html[ html.length ] = "
    "; + html = jQuery( html.join( "" ) )[ 0 ]; + assert.equal( html.nodeName.toLowerCase(), "ul" ); + assert.equal( html.firstChild.nodeName.toLowerCase(), "li" ); + assert.equal( html.childNodes.length, 30000 ); +} ); + +QUnit.test( "jQuery('html', context)", function( assert ) { + assert.expect( 1 ); + + var $div = jQuery( "
    " )[ 0 ], + $span = jQuery( "", $div ); + assert.equal( $span.length, 1, "verify a span created with a div context works, #1763" ); +} ); + +QUnit.test( "jQuery(selector, xml).text(str) - loaded via xml document", function( assert ) { + assert.expect( 2 ); + + var xml = createDashboardXML(), + + // tests for #1419 where ie was a problem + tab = jQuery( "tab", xml ).eq( 0 ); + assert.equal( tab.text(), "blabla", "verify initial text correct" ); + tab.text( "newtext" ); + assert.equal( tab.text(), "newtext", "verify new text correct" ); +} ); + +QUnit.test( "end()", function( assert ) { + assert.expect( 3 ); + assert.equal( "Yahoo", jQuery( "#yahoo" ).parent().end().text(), "check for end" ); + assert.ok( jQuery( "#yahoo" ).end(), "check for end with nothing to end" ); + + var x = jQuery( "#yahoo" ); + x.parent(); + assert.equal( "Yahoo", jQuery( "#yahoo" ).text(), "check for non-destructive behavior" ); +} ); + +QUnit.test( "length", function( assert ) { + assert.expect( 1 ); + assert.equal( jQuery( "#qunit-fixture p" ).length, 6, "Get Number of Elements Found" ); +} ); + +QUnit.test( "get()", function( assert ) { + assert.expect( 1 ); + assert.deepEqual( jQuery( "#qunit-fixture p" ).get(), q( "firstp", "ap", "sndp", "en", "sap", "first" ), "Get All Elements" ); +} ); + +QUnit.test( "toArray()", function( assert ) { + assert.expect( 1 ); + assert.deepEqual( jQuery( "#qunit-fixture p" ).toArray(), + q( "firstp", "ap", "sndp", "en", "sap", "first" ), + "Convert jQuery object to an Array" ); +} ); + +QUnit.test( "inArray()", function( assert ) { + assert.expect( 19 ); + + var selections = { + p: q( "firstp", "sap", "ap", "first" ), + em: q( "siblingnext", "siblingfirst" ), + div: q( "qunit-testrunner-toolbar", "nothiddendiv", "nothiddendivchild", "foo" ), + a: q( "mark", "groups", "google", "simon1" ), + empty: [] + }, + tests = { + p: { elem: jQuery( "#ap" )[ 0 ], index: 2 }, + em: { elem: jQuery( "#siblingfirst" )[ 0 ], index: 1 }, + div: { elem: jQuery( "#nothiddendiv" )[ 0 ], index: 1 }, + a: { elem: jQuery( "#simon1" )[ 0 ], index: 3 } + }, + falseTests = { + p: jQuery( "#liveSpan1" )[ 0 ], + em: jQuery( "#nothiddendiv" )[ 0 ], + empty: "" + }; + + jQuery.each( tests, function( key, obj ) { + assert.equal( jQuery.inArray( obj.elem, selections[ key ] ), obj.index, "elem is in the array of selections of its tag" ); + + // Third argument (fromIndex) + assert.equal( !!~jQuery.inArray( obj.elem, selections[ key ], 5 ), false, "elem is NOT in the array of selections given a starting index greater than its position" ); + assert.equal( !!~jQuery.inArray( obj.elem, selections[ key ], 1 ), true, "elem is in the array of selections given a starting index less than or equal to its position" ); + assert.equal( !!~jQuery.inArray( obj.elem, selections[ key ], -3 ), true, "elem is in the array of selections given a negative index" ); + } ); + + jQuery.each( falseTests, function( key, elem ) { + assert.equal( !!~jQuery.inArray( elem, selections[ key ] ), false, "elem is NOT in the array of selections" ); + } ); + +} ); + +QUnit.test( "get(Number)", function( assert ) { + assert.expect( 2 ); + assert.equal( jQuery( "#qunit-fixture p" ).get( 0 ), document.getElementById( "firstp" ), "Get A Single Element" ); + assert.strictEqual( jQuery( "#firstp" ).get( 1 ), undefined, "Try get with index larger elements count" ); +} ); + +QUnit.test( "get(-Number)", function( assert ) { + assert.expect( 2 ); + assert.equal( jQuery( "p" ).get( -1 ), document.getElementById( "first" ), "Get a single element with negative index" ); + assert.strictEqual( jQuery( "#firstp" ).get( -2 ), undefined, "Try get with index negative index larger then elements count" ); +} ); + +QUnit.test( "each(Function)", function( assert ) { + assert.expect( 1 ); + var div, pass, i; + + div = jQuery( "div" ); + div.each( function() {this.foo = "zoo";} ); + pass = true; + for ( i = 0; i < div.length; i++ ) { + if ( div.get( i ).foo !== "zoo" ) { + pass = false; + } + } + assert.ok( pass, "Execute a function, Relative" ); +} ); + +QUnit.test( "slice()", function( assert ) { + assert.expect( 7 ); + + var $links = jQuery( "#ap a" ); + + assert.deepEqual( $links.slice( 1, 2 ).get(), q( "groups" ), "slice(1,2)" ); + assert.deepEqual( $links.slice( 1 ).get(), q( "groups", "anchor1", "mark" ), "slice(1)" ); + assert.deepEqual( $links.slice( 0, 3 ).get(), q( "google", "groups", "anchor1" ), "slice(0,3)" ); + assert.deepEqual( $links.slice( -1 ).get(), q( "mark" ), "slice(-1)" ); + + assert.deepEqual( $links.eq( 1 ).get(), q( "groups" ), "eq(1)" ); + assert.deepEqual( $links.eq( "2" ).get(), q( "anchor1" ), "eq('2')" ); + assert.deepEqual( $links.eq( -1 ).get(), q( "mark" ), "eq(-1)" ); +} ); + +QUnit.test( "first()/last()", function( assert ) { + assert.expect( 4 ); + + var $links = jQuery( "#ap a" ), $none = jQuery( "asdf" ); + + assert.deepEqual( $links.first().get(), q( "google" ), "first()" ); + assert.deepEqual( $links.last().get(), q( "mark" ), "last()" ); + + assert.deepEqual( $none.first().get(), [], "first() none" ); + assert.deepEqual( $none.last().get(), [], "last() none" ); +} ); + +QUnit.test( "even()/odd()", function( assert ) { + assert.expect( 4 ); + + var $links = jQuery( "#ap a" ), $none = jQuery( "asdf" ); + + assert.deepEqual( $links.even().get(), q( "google", "anchor1" ), "even()" ); + assert.deepEqual( $links.odd().get(), q( "groups", "mark" ), "odd()" ); + + assert.deepEqual( $none.even().get(), [], "even() none" ); + assert.deepEqual( $none.odd().get(), [], "odd() none" ); +} ); + +QUnit.test( "map()", function( assert ) { + assert.expect( 2 ); + + assert.deepEqual( + jQuery( "#ap" ).map( function() { + return jQuery( this ).find( "a" ).get(); + } ).get(), + q( "google", "groups", "anchor1", "mark" ), + "Array Map" + ); + + assert.deepEqual( + jQuery( "#ap > a" ).map( function() { + return this.parentNode; + } ).get(), + q( "ap", "ap", "ap" ), + "Single Map" + ); +} ); + +QUnit.test( "jQuery.map", function( assert ) { + assert.expect( 28 ); + + var i, label, result, callback; + + result = jQuery.map( [ 3, 4, 5 ], function( v, k ) { + return k; + } ); + assert.equal( result.join( "" ), "012", "Map the keys from an array" ); + + result = jQuery.map( [ 3, 4, 5 ], function( v ) { + return v; + } ); + assert.equal( result.join( "" ), "345", "Map the values from an array" ); + + result = jQuery.map( { a: 1, b: 2 }, function( v, k ) { + return k; + } ); + assert.equal( result.join( "" ), "ab", "Map the keys from an object" ); + + result = jQuery.map( { a: 1, b: 2 }, function( v ) { + return v; + } ); + assert.equal( result.join( "" ), "12", "Map the values from an object" ); + + result = jQuery.map( [ "a", undefined, null, "b" ], function( v ) { + return v; + } ); + assert.equal( result.join( "" ), "ab", "Array iteration does not include undefined/null results" ); + + result = jQuery.map( { a: "a", b: undefined, c: null, d: "b" }, function( v ) { + return v; + } ); + assert.equal( result.join( "" ), "ab", "Object iteration does not include undefined/null results" ); + + result = { + Zero: function() {}, + One: function( a ) { a = a; }, + Two: function( a, b ) { a = a; b = b; } + }; + callback = function( v, k ) { + assert.equal( k, "foo", label + "-argument function treated like object" ); + }; + for ( i in result ) { + label = i; + result[ i ].foo = "bar"; + jQuery.map( result[ i ], callback ); + } + + result = { + "undefined": undefined, + "null": null, + "false": false, + "true": true, + "empty string": "", + "nonempty string": "string", + "string \"0\"": "0", + "negative": -1, + "excess": 1 + }; + callback = function( v, k ) { + assert.equal( k, "length", "Object with " + label + " length treated like object" ); + }; + for ( i in result ) { + label = i; + jQuery.map( { length: result[ i ] }, callback ); + } + + result = { + "sparse Array": Array( 4 ), + "length: 1 plain object": { length: 1, "0": true }, + "length: 2 plain object": { length: 2, "0": true, "1": true }, + NodeList: document.getElementsByTagName( "html" ) + }; + callback = function( v, k ) { + if ( result[ label ] ) { + delete result[ label ]; + assert.equal( k, "0", label + " treated like array" ); + } + }; + for ( i in result ) { + label = i; + jQuery.map( result[ i ], callback ); + } + + result = false; + jQuery.map( { length: 0 }, function() { + result = true; + } ); + assert.ok( !result, "length: 0 plain object treated like array" ); + + result = false; + jQuery.map( document.getElementsByTagName( "asdf" ), function() { + result = true; + } ); + assert.ok( !result, "empty NodeList treated like array" ); + + result = jQuery.map( Array( 4 ), function( v, k ) { + return k % 2 ? k : [ k, k, k ]; + } ); + assert.equal( result.join( "" ), "00012223", "Array results flattened (#2616)" ); + + result = jQuery.map( [ [ [ 1, 2 ], 3 ], 4 ], function( v, k ) { + return v; + } ); + assert.equal( result.length, 3, "Array flatten only one level down" ); + assert.ok( Array.isArray( result[ 0 ] ), "Array flatten only one level down" ); + + // Support: IE 9 - 11+, Edge 18+, Android Browser 4.0 - 4.3 only, iOS 7 - 11 only, + // Safari 11 only, Firefox <= 61 only + // Skip the test in browsers without Array#flat. + if ( Array.prototype.flat ) { + result = jQuery.map( Array( 300000 ), function( v, k ) { + return k; + } ); + assert.equal( result.length, 300000, "Able to map 300000 records without any problems (#4320)" ); + } else { + assert.ok( "skip", "Array#flat doesn't supported on all browsers" ); + } +} ); + +QUnit.test( "jQuery.merge()", function( assert ) { + assert.expect( 10 ); + + assert.deepEqual( + jQuery.merge( [], [] ), + [], + "Empty arrays" + ); + + assert.deepEqual( + jQuery.merge( [ 1 ], [ 2 ] ), + [ 1, 2 ], + "Basic (single-element)" + ); + assert.deepEqual( + jQuery.merge( [ 1, 2 ], [ 3, 4 ] ), + [ 1, 2, 3, 4 ], + "Basic (multiple-element)" + ); + + assert.deepEqual( + jQuery.merge( [ 1, 2 ], [] ), + [ 1, 2 ], + "Second empty" + ); + assert.deepEqual( + jQuery.merge( [], [ 1, 2 ] ), + [ 1, 2 ], + "First empty" + ); + + // Fixed at [5998], #3641 + assert.deepEqual( + jQuery.merge( [ -2, -1 ], [ 0, 1, 2 ] ), + [ -2, -1, 0, 1, 2 ], + "Second array including a zero (falsy)" + ); + + // After fixing #5527 + assert.deepEqual( + jQuery.merge( [], [ null, undefined ] ), + [ null, undefined ], + "Second array including null and undefined values" + ); + assert.deepEqual( + jQuery.merge( { length: 0 }, [ 1, 2 ] ), + { length: 2, 0: 1, 1: 2 }, + "First array like" + ); + assert.deepEqual( + jQuery.merge( [ 1, 2 ], { length: 1, 0: 3 } ), + [ 1, 2, 3 ], + "Second array like" + ); + + assert.deepEqual( + jQuery.merge( [], document.getElementById( "lengthtest" ).getElementsByTagName( "input" ) ), + [ document.getElementById( "length" ), document.getElementById( "idTest" ) ], + "Second NodeList" + ); +} ); + +QUnit.test( "jQuery.grep()", function( assert ) { + assert.expect( 8 ); + + var searchCriterion = function( value ) { + return value % 2 === 0; + }; + + assert.deepEqual( jQuery.grep( [], searchCriterion ), [], "Empty array" ); + assert.deepEqual( jQuery.grep( new Array( 4 ), searchCriterion ), [], "Sparse array" ); + + assert.deepEqual( + jQuery.grep( [ 1, 2, 3, 4, 5, 6 ], searchCriterion ), + [ 2, 4, 6 ], + "Satisfying elements present" + ); + assert.deepEqual( + jQuery.grep( [ 1, 3, 5, 7 ], searchCriterion ), + [], + "Satisfying elements absent" + ); + + assert.deepEqual( + jQuery.grep( [ 1, 2, 3, 4, 5, 6 ], searchCriterion, true ), + [ 1, 3, 5 ], + "Satisfying elements present and grep inverted" + ); + assert.deepEqual( + jQuery.grep( [ 1, 3, 5, 7 ], searchCriterion, true ), + [ 1, 3, 5, 7 ], + "Satisfying elements absent and grep inverted" + ); + + assert.deepEqual( + jQuery.grep( [ 1, 2, 3, 4, 5, 6 ], searchCriterion, false ), + [ 2, 4, 6 ], + "Satisfying elements present but grep explicitly uninverted" + ); + assert.deepEqual( + jQuery.grep( [ 1, 3, 5, 7 ], searchCriterion, false ), + [], + "Satisfying elements absent and grep explicitly uninverted" + ); +} ); + +QUnit.test( "jQuery.grep(Array-like)", function( assert ) { + assert.expect( 7 ); + + var searchCriterion = function( value ) { + return value % 2 === 0; + }; + + assert.deepEqual( jQuery.grep( { length: 0 }, searchCriterion ), [], "Empty array-like" ); + + assert.deepEqual( + jQuery.grep( { 0: 1, 1: 2, 2: 3, 3: 4, 4: 5, 5: 6, length: 6 }, searchCriterion ), + [ 2, 4, 6 ], + "Satisfying elements present and array-like object used" + ); + assert.deepEqual( + jQuery.grep( { 0: 1, 1: 3, 2: 5, 3: 7, length: 4 }, searchCriterion ), + [], + "Satisfying elements absent and Array-like object used" + ); + + assert.deepEqual( + jQuery.grep( { 0: 1, 1: 2, 2: 3, 3: 4, 4: 5, 5: 6, length: 6 }, searchCriterion, true ), + [ 1, 3, 5 ], + "Satisfying elements present, array-like object used, and grep inverted" + ); + assert.deepEqual( + jQuery.grep( { 0: 1, 1: 3, 2: 5, 3: 7, length: 4 }, searchCriterion, true ), + [ 1, 3, 5, 7 ], + "Satisfying elements absent, array-like object used, and grep inverted" + ); + + assert.deepEqual( + jQuery.grep( { 0: 1, 1: 2, 2: 3, 3: 4, 4: 5, 5: 6, length: 6 }, searchCriterion, false ), + [ 2, 4, 6 ], + "Satisfying elements present, Array-like object used, but grep explicitly uninverted" + ); + assert.deepEqual( + jQuery.grep( { 0: 1, 1: 3, 2: 5, 3: 7, length: 4 }, searchCriterion, false ), + [], + "Satisfying elements absent, Array-like object used, and grep explicitly uninverted" + ); +} ); + +QUnit.test( "jQuery.extend(Object, Object)", function( assert ) { + assert.expect( 28 ); + + var empty, optionsWithLength, optionsWithDate, myKlass, + customObject, optionsWithCustomObject, MyNumber, ret, + nullUndef, target, recursive, obj, + defaults, defaultsCopy, options1, options1Copy, options2, options2Copy, merged2, + settings = { "xnumber1": 5, "xnumber2": 7, "xstring1": "peter", "xstring2": "pan" }, + options = { "xnumber2": 1, "xstring2": "x", "xxx": "newstring" }, + optionsCopy = { "xnumber2": 1, "xstring2": "x", "xxx": "newstring" }, + merged = { "xnumber1": 5, "xnumber2": 1, "xstring1": "peter", "xstring2": "x", "xxx": "newstring" }, + deep1 = { "foo": { "bar": true } }, + deep2 = { "foo": { "baz": true }, "foo2": document }, + deep2copy = { "foo": { "baz": true }, "foo2": document }, + deepmerged = { "foo": { "bar": true, "baz": true }, "foo2": document }, + arr = [ 1, 2, 3 ], + nestedarray = { "arr": arr }; + + jQuery.extend( settings, options ); + assert.deepEqual( settings, merged, "Check if extended: settings must be extended" ); + assert.deepEqual( options, optionsCopy, "Check if not modified: options must not be modified" ); + + jQuery.extend( settings, null, options ); + assert.deepEqual( settings, merged, "Check if extended: settings must be extended" ); + assert.deepEqual( options, optionsCopy, "Check if not modified: options must not be modified" ); + + jQuery.extend( true, deep1, deep2 ); + assert.deepEqual( deep1[ "foo" ], deepmerged[ "foo" ], "Check if foo: settings must be extended" ); + assert.deepEqual( deep2[ "foo" ], deep2copy[ "foo" ], "Check if not deep2: options must not be modified" ); + assert.equal( deep1[ "foo2" ], document, "Make sure that a deep clone was not attempted on the document" ); + + assert.ok( jQuery.extend( true, {}, nestedarray )[ "arr" ] !== arr, "Deep extend of object must clone child array" ); + + // #5991 + assert.ok( Array.isArray( jQuery.extend( true, { "arr": {} }, nestedarray )[ "arr" ] ), "Cloned array have to be an Array" ); + assert.ok( jQuery.isPlainObject( jQuery.extend( true, { "arr": arr }, { "arr": {} } )[ "arr" ] ), "Cloned object have to be an plain object" ); + + empty = {}; + optionsWithLength = { "foo": { "length": -1 } }; + jQuery.extend( true, empty, optionsWithLength ); + assert.deepEqual( empty[ "foo" ], optionsWithLength[ "foo" ], "The length property must copy correctly" ); + + empty = {}; + optionsWithDate = { "foo": { "date": new Date() } }; + jQuery.extend( true, empty, optionsWithDate ); + assert.deepEqual( empty[ "foo" ], optionsWithDate[ "foo" ], "Dates copy correctly" ); + + /** @constructor */ + myKlass = function() {}; + customObject = new myKlass(); + optionsWithCustomObject = { "foo": { "date": customObject } }; + empty = {}; + jQuery.extend( true, empty, optionsWithCustomObject ); + assert.ok( empty[ "foo" ] && empty[ "foo" ][ "date" ] === customObject, "Custom objects copy correctly (no methods)" ); + + // Makes the class a little more realistic + myKlass.prototype = { "someMethod": function() {} }; + empty = {}; + jQuery.extend( true, empty, optionsWithCustomObject ); + assert.ok( empty[ "foo" ] && empty[ "foo" ][ "date" ] === customObject, "Custom objects copy correctly" ); + + MyNumber = Number; + + ret = jQuery.extend( true, { "foo": 4 }, { "foo": new MyNumber( 5 ) } ); + assert.ok( parseInt( ret.foo, 10 ) === 5, "Wrapped numbers copy correctly" ); + + nullUndef = jQuery.extend( {}, options, { "xnumber2": null } ); + assert.ok( nullUndef[ "xnumber2" ] === null, "Check to make sure null values are copied" ); + + nullUndef = jQuery.extend( {}, options, { "xnumber2": undefined } ); + assert.ok( nullUndef[ "xnumber2" ] === options[ "xnumber2" ], "Check to make sure undefined values are not copied" ); + + nullUndef = jQuery.extend( {}, options, { "xnumber0": null } ); + assert.ok( nullUndef[ "xnumber0" ] === null, "Check to make sure null values are inserted" ); + + target = {}; + recursive = { foo:target, bar:5 }; + jQuery.extend( true, target, recursive ); + assert.deepEqual( target, { bar:5 }, "Check to make sure a recursive obj doesn't go never-ending loop by not copying it over" ); + + ret = jQuery.extend( true, { foo: [] }, { foo: [ 0 ] } ); // 1907 + assert.equal( ret.foo.length, 1, "Check to make sure a value with coercion 'false' copies over when necessary to fix #1907" ); + + ret = jQuery.extend( true, { foo: "1,2,3" }, { foo: [ 1, 2, 3 ] } ); + assert.ok( typeof ret.foo !== "string", "Check to make sure values equal with coercion (but not actually equal) overwrite correctly" ); + + ret = jQuery.extend( true, { foo:"bar" }, { foo:null } ); + assert.ok( typeof ret.foo !== "undefined", "Make sure a null value doesn't crash with deep extend, for #1908" ); + + obj = { foo:null }; + jQuery.extend( true, obj, { foo:"notnull" } ); + assert.equal( obj.foo, "notnull", "Make sure a null value can be overwritten" ); + + function func() {} + jQuery.extend( func, { key: "value" } ); + assert.equal( func.key, "value", "Verify a function can be extended" ); + + defaults = { xnumber1: 5, xnumber2: 7, xstring1: "peter", xstring2: "pan" }; + defaultsCopy = { xnumber1: 5, xnumber2: 7, xstring1: "peter", xstring2: "pan" }; + options1 = { xnumber2: 1, xstring2: "x" }; + options1Copy = { xnumber2: 1, xstring2: "x" }; + options2 = { xstring2: "xx", xxx: "newstringx" }; + options2Copy = { xstring2: "xx", xxx: "newstringx" }; + merged2 = { xnumber1: 5, xnumber2: 1, xstring1: "peter", xstring2: "xx", xxx: "newstringx" }; + + settings = jQuery.extend( {}, defaults, options1, options2 ); + assert.deepEqual( settings, merged2, "Check if extended: settings must be extended" ); + assert.deepEqual( defaults, defaultsCopy, "Check if not modified: options1 must not be modified" ); + assert.deepEqual( options1, options1Copy, "Check if not modified: options1 must not be modified" ); + assert.deepEqual( options2, options2Copy, "Check if not modified: options2 must not be modified" ); +} ); + +QUnit.test( "jQuery.extend(Object, Object {created with \"defineProperties\"})", function( assert ) { + assert.expect( 2 ); + + var definedObj = Object.defineProperties( {}, { + "enumerableProp": { + get: function() { + return true; + }, + enumerable: true + }, + "nonenumerableProp": { + get: function() { + return true; + } + } + } ), + accessorObj = {}; + + jQuery.extend( accessorObj, definedObj ); + assert.equal( accessorObj.enumerableProp, true, "Verify that getters are transferred" ); + assert.equal( accessorObj.nonenumerableProp, undefined, "Verify that non-enumerable getters are ignored" ); +} ); + +QUnit.test( "jQuery.extend(true,{},{a:[], o:{}}); deep copy with array, followed by object", function( assert ) { + assert.expect( 2 ); + + var result, initial = { + + // This will make "copyIsArray" true + array: [ 1, 2, 3, 4 ], + + // If "copyIsArray" doesn't get reset to false, the check + // will evaluate true and enter the array copy block + // instead of the object copy block. Since the ternary in the + // "copyIsArray" block will evaluate to false + // (check if operating on an array with ), this will be + // replaced by an empty array. + object: {} + }; + + result = jQuery.extend( true, {}, initial ); + + assert.deepEqual( result, initial, "The [result] and [initial] have equal shape and values" ); + assert.ok( !Array.isArray( result.object ), "result.object wasn't paved with an empty array" ); +} ); + +QUnit.test( "jQuery.extend( true, ... ) Object.prototype pollution", function( assert ) { + assert.expect( 1 ); + + jQuery.extend( true, {}, JSON.parse( "{\"__proto__\": {\"devMode\": true}}" ) ); + assert.ok( !( "devMode" in {} ), "Object.prototype not polluted" ); +} ); + +QUnit.test( "jQuery.each(Object,Function)", function( assert ) { + assert.expect( 23 ); + + var i, label, seen, callback; + + seen = {}; + jQuery.each( [ 3, 4, 5 ], function( k, v ) { + seen[ k ] = v; + } ); + assert.deepEqual( seen, { "0": 3, "1": 4, "2": 5 }, "Array iteration" ); + + seen = {}; + jQuery.each( { name: "name", lang: "lang" }, function( k, v ) { + seen[ k ] = v; + } ); + assert.deepEqual( seen, { name: "name", lang: "lang" }, "Object iteration" ); + + seen = []; + jQuery.each( [ 1, 2, 3 ], function( k, v ) { + seen.push( v ); + if ( k === 1 ) { + return false; + } + } ); + assert.deepEqual( seen, [ 1, 2 ], "Broken array iteration" ); + + seen = []; + jQuery.each( { "a": 1, "b": 2, "c": 3 }, function( k, v ) { + seen.push( v ); + return false; + } ); + assert.deepEqual( seen, [ 1 ], "Broken object iteration" ); + + seen = { + Zero: function() {}, + One: function( a ) { a = a; }, + Two: function( a, b ) { a = a; b = b; } + }; + callback = function( k ) { + assert.equal( k, "foo", label + "-argument function treated like object" ); + }; + for ( i in seen ) { + label = i; + seen[ i ].foo = "bar"; + jQuery.each( seen[ i ], callback ); + } + + seen = { + "undefined": undefined, + "null": null, + "false": false, + "true": true, + "empty string": "", + "nonempty string": "string", + "string \"0\"": "0", + "negative": -1, + "excess": 1 + }; + callback = function( k ) { + assert.equal( k, "length", "Object with " + label + " length treated like object" ); + }; + for ( i in seen ) { + label = i; + jQuery.each( { length: seen[ i ] }, callback ); + } + + seen = { + "sparse Array": Array( 4 ), + "length: 1 plain object": { length: 1, "0": true }, + "length: 2 plain object": { length: 2, "0": true, "1": true }, + NodeList: document.getElementsByTagName( "html" ) + }; + callback = function( k ) { + if ( seen[ label ] ) { + delete seen[ label ]; + assert.equal( k, "0", label + " treated like array" ); + return false; + } + }; + for ( i in seen ) { + label = i; + jQuery.each( seen[ i ], callback ); + } + + seen = false; + jQuery.each( { length: 0 }, function() { + seen = true; + } ); + assert.ok( !seen, "length: 0 plain object treated like array" ); + + seen = false; + jQuery.each( document.getElementsByTagName( "asdf" ), function() { + seen = true; + } ); + assert.ok( !seen, "empty NodeList treated like array" ); + + i = 0; + jQuery.each( document.styleSheets, function() { + i++; + } ); + assert.equal( i, document.styleSheets.length, "Iteration over document.styleSheets" ); +} ); + +QUnit.test( "jQuery.each/map(undefined/null,Function)", function( assert ) { + assert.expect( 1 ); + + try { + jQuery.each( undefined, jQuery.noop ); + jQuery.each( null, jQuery.noop ); + jQuery.map( undefined, jQuery.noop ); + jQuery.map( null, jQuery.noop ); + assert.ok( true, "jQuery.each/map( undefined/null, function() {} );" ); + } catch ( e ) { + assert.ok( false, "each/map must accept null and undefined values" ); + } +} ); + +QUnit.test( "JIT compilation does not interfere with length retrieval (gh-2145)", function( assert ) { + assert.expect( 4 ); + + var i; + + // Trigger JIT compilation of jQuery.each – and therefore isArraylike – in iOS. + // Convince JSC to use one of its optimizing compilers + // by providing code which can be LICM'd into nothing. + for ( i = 0; i < 1000; i++ ) { + jQuery.each( [] ); + } + + i = 0; + jQuery.each( { 1: "1", 2: "2", 3: "3" }, function( index ) { + assert.equal( ++i, index, "Iteration over object with solely " + + "numeric indices (gh-2145 JIT iOS 8 bug)" ); + } ); + assert.equal( i, 3, "Iteration over object with solely " + + "numeric indices (gh-2145 JIT iOS 8 bug)" ); +} ); + +QUnit.test( "jQuery.makeArray", function( assert ) { + assert.expect( 15 ); + + assert.equal( jQuery.makeArray( jQuery( "html>*" ) )[ 0 ].nodeName.toUpperCase(), "HEAD", "Pass makeArray a jQuery object" ); + + assert.equal( jQuery.makeArray( document.getElementsByName( "PWD" ) ).slice( 0, 1 )[ 0 ].name, "PWD", "Pass makeArray a nodelist" ); + + assert.equal( ( function() { return jQuery.makeArray( arguments ); } )( 1, 2 ).join( "" ), "12", "Pass makeArray an arguments array" ); + + assert.equal( jQuery.makeArray( [ 1, 2, 3 ] ).join( "" ), "123", "Pass makeArray a real array" ); + + assert.equal( jQuery.makeArray().length, 0, "Pass nothing to makeArray and expect an empty array" ); + + assert.equal( jQuery.makeArray( 0 )[ 0 ], 0, "Pass makeArray a number" ); + + assert.equal( jQuery.makeArray( "foo" )[ 0 ], "foo", "Pass makeArray a string" ); + + assert.equal( jQuery.makeArray( true )[ 0 ].constructor, Boolean, "Pass makeArray a boolean" ); + + assert.equal( jQuery.makeArray( document.createElement( "div" ) )[ 0 ].nodeName.toUpperCase(), "DIV", "Pass makeArray a single node" ); + + assert.equal( jQuery.makeArray( { length:2, 0:"a", 1:"b" } ).join( "" ), "ab", "Pass makeArray an array like map (with length)" ); + + assert.ok( !!jQuery.makeArray( document.documentElement.childNodes ).slice( 0, 1 )[ 0 ].nodeName, "Pass makeArray a childNodes array" ); + + // function, is tricky as it has length + assert.equal( jQuery.makeArray( function() { return 1;} )[ 0 ](), 1, "Pass makeArray a function" ); + + //window, also has length + assert.equal( jQuery.makeArray( window )[ 0 ], window, "Pass makeArray the window" ); + + assert.equal( jQuery.makeArray( /a/ )[ 0 ].constructor, RegExp, "Pass makeArray a regex" ); + + // Some nodes inherit traits of nodelists + assert.ok( jQuery.makeArray( document.getElementById( "form" ) ).length >= 13, + "Pass makeArray a form (treat as elements)" ); +} ); + +QUnit.test( "jQuery.inArray", function( assert ) { + assert.expect( 3 ); + + assert.equal( jQuery.inArray( 0, false ), -1, "Search in 'false' as array returns -1 and doesn't throw exception" ); + + assert.equal( jQuery.inArray( 0, null ), -1, "Search in 'null' as array returns -1 and doesn't throw exception" ); + + assert.equal( jQuery.inArray( 0, undefined ), -1, "Search in 'undefined' as array returns -1 and doesn't throw exception" ); +} ); + +QUnit.test( "jQuery.isEmptyObject", function( assert ) { + assert.expect( 2 ); + + assert.equal( true, jQuery.isEmptyObject( {} ), "isEmptyObject on empty object literal" ); + assert.equal( false, jQuery.isEmptyObject( { a:1 } ), "isEmptyObject on non-empty object literal" ); + + // What about this ? + // equal(true, jQuery.isEmptyObject(null), "isEmptyObject on null" ); +} ); + +QUnit.test( "jQuery.parseHTML", function( assert ) { + assert.expect( 23 ); + + var html, nodes; + + assert.deepEqual( jQuery.parseHTML(), [], "Without arguments" ); + assert.deepEqual( jQuery.parseHTML( undefined ), [], "Undefined" ); + assert.deepEqual( jQuery.parseHTML( null ), [], "Null" ); + assert.deepEqual( jQuery.parseHTML( false ), [], "Boolean false" ); + assert.deepEqual( jQuery.parseHTML( 0 ), [], "Zero" ); + assert.deepEqual( jQuery.parseHTML( true ), [], "Boolean true" ); + assert.deepEqual( jQuery.parseHTML( 42 ), [], "Positive number" ); + assert.deepEqual( jQuery.parseHTML( "" ), [], "Empty string" ); + assert.throws( function() { + jQuery.parseHTML( "
    ", document.getElementById( "form" ) ); + }, "Passing an element as the context raises an exception (context should be a document)" ); + + nodes = jQuery.parseHTML( jQuery( "body" )[ 0 ].innerHTML ); + assert.ok( nodes.length > 4, "Parse a large html string" ); + assert.ok( Array.isArray( nodes ), "parseHTML returns an array rather than a nodelist" ); + + html = ""; + assert.equal( jQuery.parseHTML( html ).length, 0, "Ignore scripts by default" ); + assert.equal( jQuery.parseHTML( html, true )[ 0 ].nodeName.toLowerCase(), "script", "Preserve scripts when requested" ); + + html += "
    "; + assert.equal( jQuery.parseHTML( html )[ 0 ].nodeName.toLowerCase(), "div", "Preserve non-script nodes" ); + assert.equal( jQuery.parseHTML( html, true )[ 0 ].nodeName.toLowerCase(), "script", "Preserve script position" ); + + assert.equal( jQuery.parseHTML( "text" )[ 0 ].nodeType, 3, "Parsing text returns a text node" ); + assert.equal( jQuery.parseHTML( "\t
    " )[ 0 ].nodeValue, "\t", "Preserve leading whitespace" ); + + assert.equal( jQuery.parseHTML( "
    " )[ 0 ].nodeType, 3, "Leading spaces are treated as text nodes (#11290)" ); + + html = jQuery.parseHTML( "
    test div
    " ); + + assert.equal( html[ 0 ].parentNode.nodeType, 11, "parentNode should be documentFragment" ); + assert.equal( html[ 0 ].innerHTML, "test div", "Content should be preserved" ); + + assert.equal( jQuery.parseHTML( "" ).length, 1, "Incorrect html-strings should not break anything" ); + assert.equal( jQuery.parseHTML( "" )[ 1 ].parentNode.nodeType, 11, + "parentNode should be documentFragment for wrapMap (variable in manipulation module) elements too" ); + assert.ok( jQuery.parseHTML( "<#if>

    This is a test.

    <#/if>" ) || true, "Garbage input should not cause error" ); +} ); + +QUnit.test( "jQuery.parseHTML() - gh-2965", function( assert ) { + assert.expect( 1 ); + + var html = "", + href = jQuery.parseHTML( html )[ 0 ].href; + + assert.ok( /\/example\.html$/.test( href ), "href is not lost after parsing anchor" ); +} ); + +if ( jQuery.support.createHTMLDocument ) { + QUnit.test( "jQuery.parseHTML", function( assert ) { + var done = assert.async(); + assert.expect( 1 ); + + Globals.register( "parseHTMLError" ); + + jQuery.globalEval( "parseHTMLError = false;" ); + jQuery.parseHTML( "" ); + + window.setTimeout( function() { + assert.equal( window.parseHTMLError, false, "onerror eventhandler has not been called." ); + done(); + }, 2000 ); + } ); +} + +QUnit.test( "jQuery.parseXML", function( assert ) { + assert.expect( 8 ); + + var xml, tmp; + try { + xml = jQuery.parseXML( "

    A well-formed xml string

    " ); + tmp = xml.getElementsByTagName( "p" )[ 0 ]; + assert.ok( !!tmp, "

    present in document" ); + tmp = tmp.getElementsByTagName( "b" )[ 0 ]; + assert.ok( !!tmp, " present in document" ); + assert.strictEqual( tmp.childNodes[ 0 ].nodeValue, "well-formed", " text is as expected" ); + } catch ( e ) { + assert.strictEqual( e, undefined, "unexpected error" ); + } + try { + xml = jQuery.parseXML( "

    Not a <well-formed xml string

    " ); + assert.ok( false, "invalid XML not detected" ); + } catch ( e ) { + assert.ok( e.message.indexOf( "Invalid XML:" ) === 0, "invalid XML detected" ); + } + try { + xml = jQuery.parseXML( "" ); + assert.strictEqual( xml, null, "empty string => null document" ); + xml = jQuery.parseXML(); + assert.strictEqual( xml, null, "undefined string => null document" ); + xml = jQuery.parseXML( null ); + assert.strictEqual( xml, null, "null string => null document" ); + xml = jQuery.parseXML( true ); + assert.strictEqual( xml, null, "non-string => null document" ); + } catch ( e ) { + assert.ok( false, "empty input throws exception" ); + } +} ); + +// Support: IE 11+, Edge 12 - 18 only +// IE throws an error when parsing invalid XML instead of reporting the error +// in a `parsererror` element, IE & Legacy Edge don't report errors in a parsererror +// element; skip the test there. +QUnit[ + document.documentMode || /edge\//i.test( navigator.userAgent ) ? + "skip" : + "test" +]( "jQuery.parseXML - error reporting", function( assert ) { + assert.expect( 2 ); + + var errorArg, lineMatch, line, columnMatch, column; + + sinon.stub( jQuery, "error" ); + + jQuery.parseXML( "

    Not a <well-formed xml string

    " ); + errorArg = jQuery.error.firstCall.args[ 0 ].toLowerCase(); + console.log( "errorArg", errorArg ); + + lineMatch = errorArg.match( /line\s*(?:number)?\s*(\d+)/ ); + line = lineMatch && lineMatch[ 1 ]; + columnMatch = errorArg.match( /column\s*(\d+)/ ); + column = columnMatch && columnMatch[ 1 ]; + + assert.strictEqual( line, "1", "reports error line" ); + assert.strictEqual( column, "11", "reports error column" ); +} ); + +testIframe( + "Conditional compilation compatibility (#13274)", + "core/cc_on.html", + function( assert, jQuery, window, document, cc_on, errors ) { + assert.expect( 3 ); + assert.ok( true, "JScript conditional compilation " + ( cc_on ? "supported" : "not supported" ) ); + assert.deepEqual( errors, [], "No errors" ); + assert.ok( jQuery(), "jQuery executes" ); + } +); + +// iOS7 doesn't fire the load event if the long-loading iframe gets its source reset to about:blank. +// This makes this test fail but it doesn't seem to cause any real-life problems so blacklisting +// this test there is preferred to complicating the hard-to-test core/ready code further. +if ( !/iphone os 7_/i.test( navigator.userAgent ) ) { + testIframe( + "document ready when jQuery loaded asynchronously (#13655)", + "core/dynamic_ready.html", + function( assert, jQuery, window, document, ready ) { + assert.expect( 1 ); + assert.equal( true, ready, "document ready correctly fired when jQuery is loaded after DOMContentLoaded" ); + } + ); +} + +testIframe( + "Tolerating alias-masked DOM properties (#14074)", + "core/aliased.html", + function( assert, jQuery, window, document, errors ) { + assert.expect( 1 ); + assert.deepEqual( errors, [], "jQuery loaded" ); + } +); + +testIframe( + "Don't call window.onready (#14802)", + "core/onready.html", + function( assert, jQuery, window, document, error ) { + assert.expect( 1 ); + assert.equal( error, false, "no call to user-defined onready" ); + } +); + +QUnit.test( "Iterability of jQuery objects (gh-1693)", function( assert ) { + assert.expect( 1 ); + + var i, elem, result; + + if ( typeof Symbol === "function" ) { + + elem = jQuery( "
    " ); + result = ""; + + try { + eval( "for ( i of elem ) { result += i.nodeName; }" ); + } catch ( e ) {} + assert.equal( result, "DIVSPANA", "for-of works on jQuery objects" ); + } else { + assert.ok( true, "The browser doesn't support Symbols" ); + } +} ); + +testIframe( + "Iterability of jQuery objects with Symbol polyfill (gh-1693)", + "core/jquery-iterability-transpiled.html", + function( assert, jQuery, window, document, testString ) { + assert.expect( 1 ); + + assert.strictEqual( testString, "DIVSPANA", + "for-of works on jQuery objects with Symbol polyfilled" ); + } +); + +QUnit[ jQuery.Deferred ? "test" : "skip" ]( "jQuery.readyException (original)", function( assert ) { + assert.expect( 1 ); + + var message; + + this.sandbox.stub( window, "setTimeout", function( fn ) { + try { + fn(); + } catch ( error ) { + message = error.message; + } + } ); + + jQuery( function() { + throw new Error( "Error in jQuery ready" ); + } ); + assert.strictEqual( + message, + "Error in jQuery ready", + "The error should have been thrown in a timeout" + ); +} ); + +QUnit[ jQuery.Deferred ? "test" : "skip" ]( "jQuery.readyException (custom)", function( assert ) { + assert.expect( 1 ); + + var done = assert.async(); + + this.sandbox.stub( jQuery, "readyException", function( error ) { + assert.strictEqual( + error.message, + "Error in jQuery ready", + "The custom jQuery.readyException should have been called" + ); + done(); + } ); + + jQuery( function() { + throw new Error( "Error in jQuery ready" ); + } ); +} ); diff --git a/test/unit/css.js b/test/unit/css.js new file mode 100644 index 0000000..c6a17a7 --- /dev/null +++ b/test/unit/css.js @@ -0,0 +1,1837 @@ +if ( jQuery.css ) { + +QUnit.module( "css", { afterEach: moduleTeardown } ); + +QUnit.test( "css(String|Hash)", function( assert ) { + assert.expect( 42 ); + + assert.equal( jQuery( "#qunit-fixture" ).css( "display" ), "block", "Check for css property \"display\"" ); + + var $child, div, div2, width, height, child, prctval, checkval, old; + + $child = jQuery( "#nothiddendivchild" ).css( { "width": "20%", "height": "20%" } ); + assert.notEqual( $child.css( "width" ), "20px", "Retrieving a width percentage on the child of a hidden div returns percentage" ); + assert.notEqual( $child.css( "height" ), "20px", "Retrieving a height percentage on the child of a hidden div returns percentage" ); + + div = jQuery( "
    " ); + + // These should be "auto" (or some better value) + // temporarily provide "0px" for backwards compat + assert.equal( div.css( "width" ), "0px", "Width on disconnected node." ); + assert.equal( div.css( "height" ), "0px", "Height on disconnected node." ); + + div.css( { "width": 4, "height": 4 } ); + + assert.equal( div.css( "width" ), "4px", "Width on disconnected node." ); + assert.equal( div.css( "height" ), "4px", "Height on disconnected node." ); + + div2 = jQuery( "
    " ).appendTo( "body" ); + + assert.equal( div2.find( "input" ).css( "height" ), "20px", "Height on hidden input." ); + assert.equal( div2.find( "textarea" ).css( "height" ), "20px", "Height on hidden textarea." ); + assert.equal( div2.find( "div" ).css( "height" ), "20px", "Height on hidden div." ); + + div2.remove(); + + // handle negative numbers by setting to zero #11604 + jQuery( "#nothiddendiv" ).css( { "width": 1, "height": 1 } ); + + width = parseFloat( jQuery( "#nothiddendiv" ).css( "width" ) ); + height = parseFloat( jQuery( "#nothiddendiv" ).css( "height" ) ); + jQuery( "#nothiddendiv" ).css( { "overflow":"hidden", "width": -1, "height": -1 } ); + assert.equal( parseFloat( jQuery( "#nothiddendiv" ).css( "width" ) ), 0, "Test negative width set to 0" ); + assert.equal( parseFloat( jQuery( "#nothiddendiv" ).css( "height" ) ), 0, "Test negative height set to 0" ); + + assert.equal( jQuery( "
    " ).css( "display" ), "none", "Styles on disconnected nodes" ); + + jQuery( "#floatTest" ).css( { "float": "right" } ); + assert.equal( jQuery( "#floatTest" ).css( "float" ), "right", "Modified CSS float using \"float\": Assert float is right" ); + jQuery( "#floatTest" ).css( { "font-size": "30px" } ); + assert.equal( jQuery( "#floatTest" ).css( "font-size" ), "30px", "Modified CSS font-size: Assert font-size is 30px" ); + jQuery.each( "0,0.25,0.5,0.75,1".split( "," ), function( i, n ) { + jQuery( "#foo" ).css( { "opacity": n } ); + + assert.equal( jQuery( "#foo" ).css( "opacity" ), parseFloat( n ), "Assert opacity is " + parseFloat( n ) + " as a String" ); + jQuery( "#foo" ).css( { "opacity": parseFloat( n ) } ); + assert.equal( jQuery( "#foo" ).css( "opacity" ), parseFloat( n ), "Assert opacity is " + parseFloat( n ) + " as a Number" ); + } ); + jQuery( "#foo" ).css( { "opacity": "" } ); + assert.equal( jQuery( "#foo" ).css( "opacity" ), "1", "Assert opacity is 1 when set to an empty String" ); + + assert.equal( jQuery( "#empty" ).css( "opacity" ), "0", "Assert opacity is accessible" ); + jQuery( "#empty" ).css( { "opacity": "1" } ); + assert.equal( jQuery( "#empty" ).css( "opacity" ), "1", "Assert opacity is taken from style attribute when set" ); + + div = jQuery( "#nothiddendiv" ); + child = jQuery( "#nothiddendivchild" ); + + assert.equal( parseInt( div.css( "fontSize" ), 10 ), 16, "Verify fontSize px set." ); + assert.equal( parseInt( div.css( "font-size" ), 10 ), 16, "Verify fontSize px set." ); + assert.equal( parseInt( child.css( "fontSize" ), 10 ), 16, "Verify fontSize px set." ); + assert.equal( parseInt( child.css( "font-size" ), 10 ), 16, "Verify fontSize px set." ); + + child.css( "height", "100%" ); + assert.equal( child[ 0 ].style.height, "100%", "Make sure the height is being set correctly." ); + + child.attr( "class", "em" ); + assert.equal( parseInt( child.css( "fontSize" ), 10 ), 32, "Verify fontSize em set." ); + + // Have to verify this as the result depends upon the browser's CSS + // support for font-size percentages + child.attr( "class", "prct" ); + prctval = parseInt( child.css( "fontSize" ), 10 ); + checkval = 0; + if ( prctval === 16 || prctval === 24 ) { + checkval = prctval; + } + + assert.equal( prctval, checkval, "Verify fontSize % set." ); + + assert.equal( typeof child.css( "width" ), "string", "Make sure that a string width is returned from css('width')." ); + + old = child[ 0 ].style.height; + + // Test NaN + child.css( "height", parseFloat( "zoo" ) ); + assert.equal( child[ 0 ].style.height, old, "Make sure height isn't changed on NaN." ); + + // Test null + child.css( "height", null ); + assert.equal( child[ 0 ].style.height, old, "Make sure height isn't changed on null." ); + + old = child[ 0 ].style.fontSize; + + // Test NaN + child.css( "font-size", parseFloat( "zoo" ) ); + assert.equal( child[ 0 ].style.fontSize, old, "Make sure font-size isn't changed on NaN." ); + + // Test null + child.css( "font-size", null ); + assert.equal( child[ 0 ].style.fontSize, old, "Make sure font-size isn't changed on null." ); + + assert.strictEqual( child.css( "x-fake" ), undefined, "Make sure undefined is returned from css(nonexistent)." ); + + div = jQuery( "
    " ).css( { position: "absolute", "z-index": 1000 } ).appendTo( "#qunit-fixture" ); + assert.strictEqual( div.css( "z-index" ), "1000", + "Make sure that a string z-index is returned from css('z-index') (#14432)." ); +} ); + +QUnit.test( "css() explicit and relative values", function( assert ) { + assert.expect( 29 ); + + var $elem = jQuery( "#nothiddendiv" ); + + $elem.css( { "width": 1, "height": 1, "paddingLeft": "1px", "opacity": 1 } ); + assert.equal( $elem.css( "width" ), "1px", "Initial css set or width/height works (hash)" ); + assert.equal( $elem.css( "paddingLeft" ), "1px", "Initial css set of paddingLeft works (hash)" ); + assert.equal( $elem.css( "opacity" ), "1", "Initial css set of opacity works (hash)" ); + + $elem.css( { width: "+=9" } ); + assert.equal( $elem.css( "width" ), "10px", "'+=9' on width (hash)" ); + + $elem.css( { "width": "-=9" } ); + assert.equal( $elem.css( "width" ), "1px", "'-=9' on width (hash)" ); + + $elem.css( { "width": "+=9px" } ); + assert.equal( $elem.css( "width" ), "10px", "'+=9px' on width (hash)" ); + + $elem.css( { "width": "-=9px" } ); + assert.equal( $elem.css( "width" ), "1px", "'-=9px' on width (hash)" ); + + $elem.css( "width", "+=9" ); + assert.equal( $elem.css( "width" ), "10px", "'+=9' on width (params)" ); + + $elem.css( "width", "-=9" ); + assert.equal( $elem.css( "width" ), "1px", "'-=9' on width (params)" ); + + $elem.css( "width", "+=9px" ); + assert.equal( $elem.css( "width" ), "10px", "'+=9px' on width (params)" ); + + $elem.css( "width", "-=9px" ); + assert.equal( $elem.css( "width" ), "1px", "'-=9px' on width (params)" ); + + $elem.css( "width", "-=-9px" ); + assert.equal( $elem.css( "width" ), "10px", "'-=-9px' on width (params)" ); + + $elem.css( "width", "+=-9px" ); + assert.equal( $elem.css( "width" ), "1px", "'+=-9px' on width (params)" ); + + $elem.css( { "paddingLeft": "+=4" } ); + assert.equal( $elem.css( "paddingLeft" ), "5px", "'+=4' on paddingLeft (hash)" ); + + $elem.css( { "paddingLeft": "-=4" } ); + assert.equal( $elem.css( "paddingLeft" ), "1px", "'-=4' on paddingLeft (hash)" ); + + $elem.css( { "paddingLeft": "+=4px" } ); + assert.equal( $elem.css( "paddingLeft" ), "5px", "'+=4px' on paddingLeft (hash)" ); + + $elem.css( { "paddingLeft": "-=4px" } ); + assert.equal( $elem.css( "paddingLeft" ), "1px", "'-=4px' on paddingLeft (hash)" ); + + $elem.css( { "padding-left": "+=4" } ); + assert.equal( $elem.css( "paddingLeft" ), "5px", "'+=4' on padding-left (hash)" ); + + $elem.css( { "padding-left": "-=4" } ); + assert.equal( $elem.css( "paddingLeft" ), "1px", "'-=4' on padding-left (hash)" ); + + $elem.css( { "padding-left": "+=4px" } ); + assert.equal( $elem.css( "paddingLeft" ), "5px", "'+=4px' on padding-left (hash)" ); + + $elem.css( { "padding-left": "-=4px" } ); + assert.equal( $elem.css( "paddingLeft" ), "1px", "'-=4px' on padding-left (hash)" ); + + $elem.css( "paddingLeft", "+=4" ); + assert.equal( $elem.css( "paddingLeft" ), "5px", "'+=4' on paddingLeft (params)" ); + + $elem.css( "paddingLeft", "-=4" ); + assert.equal( $elem.css( "paddingLeft" ), "1px", "'-=4' on paddingLeft (params)" ); + + $elem.css( "padding-left", "+=4px" ); + assert.equal( $elem.css( "paddingLeft" ), "5px", "'+=4px' on padding-left (params)" ); + + $elem.css( "padding-left", "-=4px" ); + assert.equal( $elem.css( "paddingLeft" ), "1px", "'-=4px' on padding-left (params)" ); + + $elem.css( { "opacity": "-=0.5" } ); + assert.equal( $elem.css( "opacity" ), "0.5", "'-=0.5' on opacity (hash)" ); + + $elem.css( { "opacity": "+=0.5" } ); + assert.equal( $elem.css( "opacity" ), "1", "'+=0.5' on opacity (hash)" ); + + $elem.css( "opacity", "-=0.5" ); + assert.equal( $elem.css( "opacity" ), "0.5", "'-=0.5' on opacity (params)" ); + + $elem.css( "opacity", "+=0.5" ); + assert.equal( $elem.css( "opacity" ), "1", "'+=0.5' on opacity (params)" ); +} ); + +QUnit.test( "css() non-px relative values (gh-1711)", function( assert ) { + assert.expect( 17 ); + + var cssCurrent, + units = {}, + $child = jQuery( "#nothiddendivchild" ), + add = function( prop, val, unit ) { + var difference, + adjustment = ( val < 0 ? "-=" : "+=" ) + Math.abs( val ) + unit, + message = prop + ": " + adjustment, + cssOld = cssCurrent, + expected = cssOld + val * units[ prop ][ unit ]; + + // Apply change + $child.css( prop, adjustment ); + cssCurrent = parseFloat( $child.css( prop ) ); + message += " (actual " + round( cssCurrent, 2 ) + "px, expected " + + round( expected, 2 ) + "px)"; + + // Require a difference of no more than one pixel + difference = Math.abs( cssCurrent - expected ); + assert.ok( difference <= 1, message ); + }, + getUnits = function( prop ) { + units[ prop ] = { + "px": 1, + "em": parseFloat( $child.css( prop, "100em" ).css( prop ) ) / 100, + "pt": parseFloat( $child.css( prop, "100pt" ).css( prop ) ) / 100, + "pc": parseFloat( $child.css( prop, "100pc" ).css( prop ) ) / 100, + "cm": parseFloat( $child.css( prop, "100cm" ).css( prop ) ) / 100, + "mm": parseFloat( $child.css( prop, "100mm" ).css( prop ) ) / 100, + "%": parseFloat( $child.css( prop, "500%" ).css( prop ) ) / 500 + }; + }, + round = function( num, fractionDigits ) { + var base = Math.pow( 10, fractionDigits ); + return Math.round( num * base ) / base; + }; + + jQuery( "#nothiddendiv" ).css( { height: 1, padding: 0, width: 400 } ); + $child.css( { height: 1, padding: 0 } ); + + getUnits( "width" ); + cssCurrent = parseFloat( $child.css( "width", "50%" ).css( "width" ) ); + add( "width", 25, "%" ); + add( "width", -50, "%" ); + add( "width", 10, "em" ); + add( "width", 10, "pt" ); + add( "width", -2.3, "pt" ); + add( "width", 5, "pc" ); + add( "width", -5, "em" ); + add( "width", +2, "cm" ); + add( "width", -15, "mm" ); + add( "width", 21, "px" ); + + getUnits( "lineHeight" ); + cssCurrent = parseFloat( $child.css( "lineHeight", "1em" ).css( "lineHeight" ) ); + add( "lineHeight", 50, "%" ); + add( "lineHeight", 2, "em" ); + add( "lineHeight", -10, "px" ); + add( "lineHeight", 20, "pt" ); + add( "lineHeight", 30, "pc" ); + add( "lineHeight", 1, "cm" ); + add( "lineHeight", -44, "mm" ); +} ); + +QUnit.test( "css() mismatched relative values with bounded styles (gh-2144)", function( assert ) { + assert.expect( 1 ); + + var right, + $container = jQuery( "
    " ) + .css( { position: "absolute", width: "400px", fontSize: "4px" } ) + .appendTo( "#qunit-fixture" ), + $el = jQuery( "
    " ) + .css( { position: "absolute", left: "50%", right: "50%" } ) + .appendTo( $container ); + + $el.css( "right", "-=25em" ); + assert.equal( Math.round( parseFloat( $el.css( "right" ) ) ), 100, + "Constraints do not interfere with unit conversion" ); +} ); + +QUnit.test( "css(String, Object)", function( assert ) { + assert.expect( 19 ); + var j, div, display, ret, success; + + jQuery( "#floatTest" ).css( "float", "left" ); + assert.equal( jQuery( "#floatTest" ).css( "float" ), "left", "Modified CSS float using \"float\": Assert float is left" ); + jQuery( "#floatTest" ).css( "font-size", "20px" ); + assert.equal( jQuery( "#floatTest" ).css( "font-size" ), "20px", "Modified CSS font-size: Assert font-size is 20px" ); + + jQuery.each( "0,0.25,0.5,0.75,1".split( "," ), function( i, n ) { + jQuery( "#foo" ).css( "opacity", n ); + assert.equal( jQuery( "#foo" ).css( "opacity" ), parseFloat( n ), "Assert opacity is " + parseFloat( n ) + " as a String" ); + jQuery( "#foo" ).css( "opacity", parseFloat( n ) ); + assert.equal( jQuery( "#foo" ).css( "opacity" ), parseFloat( n ), "Assert opacity is " + parseFloat( n ) + " as a Number" ); + } ); + jQuery( "#foo" ).css( "opacity", "" ); + assert.equal( jQuery( "#foo" ).css( "opacity" ), "1", "Assert opacity is 1 when set to an empty String" ); + + // using contents will get comments regular, text, and comment nodes + j = jQuery( "#nonnodes" ).contents(); + j.css( "overflow", "visible" ); + assert.equal( j.css( "overflow" ), "visible", "Check node,textnode,comment css works" ); + assert.equal( jQuery( "#t2037 .hidden" ).css( "display" ), "none", "Make sure browser thinks it is hidden" ); + + div = jQuery( "#nothiddendiv" ); + display = div.css( "display" ); + ret = div.css( "display", undefined ); + + assert.equal( ret, div, "Make sure setting undefined returns the original set." ); + assert.equal( div.css( "display" ), display, "Make sure that the display wasn't changed." ); + + success = true; + try { + jQuery( "#foo" ).css( "backgroundColor", "rgba(0, 0, 0, 0.1)" ); + } + catch ( e ) { + success = false; + } + assert.ok( success, "Setting RGBA values does not throw Error (#5509)" ); + + jQuery( "#foo" ).css( "font", "7px/21px sans-serif" ); + assert.strictEqual( jQuery( "#foo" ).css( "line-height" ), "21px", + "Set font shorthand property (#14759)" ); +} ); + +QUnit.test( "css(String, Object) with negative values", function( assert ) { + assert.expect( 4 ); + + jQuery( "#nothiddendiv" ).css( "margin-top", "-10px" ); + jQuery( "#nothiddendiv" ).css( "margin-left", "-10px" ); + assert.equal( jQuery( "#nothiddendiv" ).css( "margin-top" ), "-10px", "Ensure negative top margins work." ); + assert.equal( jQuery( "#nothiddendiv" ).css( "margin-left" ), "-10px", "Ensure negative left margins work." ); + + jQuery( "#nothiddendiv" ).css( "position", "absolute" ); + jQuery( "#nothiddendiv" ).css( "top", "-20px" ); + jQuery( "#nothiddendiv" ).css( "left", "-20px" ); + assert.equal( jQuery( "#nothiddendiv" ).css( "top" ), "-20px", "Ensure negative top values work." ); + assert.equal( jQuery( "#nothiddendiv" ).css( "left" ), "-20px", "Ensure negative left values work." ); +} ); + +QUnit.test( "css(Array)", function( assert ) { + assert.expect( 2 ); + + var expectedMany = { + "overflow": "visible", + "width": "16px" + }, + expectedSingle = { + "width": "16px" + }, + elem = jQuery( "
    " ).appendTo( "#qunit-fixture" ); + + assert.deepEqual( elem.css( expectedMany ).css( [ "overflow", "width" ] ), expectedMany, "Getting multiple element array" ); + assert.deepEqual( elem.css( expectedSingle ).css( [ "width" ] ), expectedSingle, "Getting single element array" ); +} ); + +QUnit.test( "css(String, Function)", function( assert ) { + assert.expect( 3 ); + + var index, + sizes = [ "10px", "20px", "30px" ]; + + jQuery( "
    " + + "
    " + + "
    " ) + .appendTo( "body" ); + + index = 0; + + jQuery( "#cssFunctionTest div" ).css( "font-size", function() { + var size = sizes[ index ]; + index++; + return size; + } ); + + index = 0; + + jQuery( "#cssFunctionTest div" ).each( function() { + var computedSize = jQuery( this ).css( "font-size" ), + expectedSize = sizes[ index ]; + assert.equal( computedSize, expectedSize, "Div #" + index + " should be " + expectedSize ); + index++; + } ); + + jQuery( "#cssFunctionTest" ).remove(); +} ); + +QUnit.test( "css(String, Function) with incoming value", function( assert ) { + assert.expect( 3 ); + + var index, + sizes = [ "10px", "20px", "30px" ]; + + jQuery( "
    " + + "
    " + + "
    " ) + .appendTo( "body" ); + + index = 0; + + jQuery( "#cssFunctionTest div" ).css( "font-size", function() { + var size = sizes[ index ]; + index++; + return size; + } ); + + index = 0; + + jQuery( "#cssFunctionTest div" ).css( "font-size", function( i, computedSize ) { + var expectedSize = sizes[ index ]; + assert.equal( computedSize, expectedSize, "Div #" + index + " should be " + expectedSize ); + index++; + return computedSize; + } ); + + jQuery( "#cssFunctionTest" ).remove(); +} ); + +QUnit.test( "css(Object) where values are Functions", function( assert ) { + assert.expect( 3 ); + + var index, + sizes = [ "10px", "20px", "30px" ]; + + jQuery( "
    " + + "
    " + + "
    " ) + .appendTo( "body" ); + + index = 0; + + jQuery( "#cssFunctionTest div" ).css( { "fontSize": function() { + var size = sizes[ index ]; + index++; + return size; + } } ); + + index = 0; + + jQuery( "#cssFunctionTest div" ).each( function() { + var computedSize = jQuery( this ).css( "font-size" ), + expectedSize = sizes[ index ]; + assert.equal( computedSize, expectedSize, "Div #" + index + " should be " + expectedSize ); + index++; + } ); + + jQuery( "#cssFunctionTest" ).remove(); +} ); + +QUnit.test( "css(Object) where values are Functions with incoming values", function( assert ) { + assert.expect( 3 ); + + var index, + sizes = [ "10px", "20px", "30px" ]; + + jQuery( "
    " + + "
    " + + "
    " ) + .appendTo( "body" ); + + index = 0; + + jQuery( "#cssFunctionTest div" ).css( { "fontSize": function() { + var size = sizes[ index ]; + index++; + return size; + } } ); + + index = 0; + + jQuery( "#cssFunctionTest div" ).css( { "font-size": function( i, computedSize ) { + var expectedSize = sizes[ index ]; + assert.equal( computedSize, expectedSize, "Div #" + index + " should be " + expectedSize ); + index++; + return computedSize; + } } ); + + jQuery( "#cssFunctionTest" ).remove(); +} ); + +// .show(), .hide(), can be excluded from the build +if ( jQuery.fn.show && jQuery.fn.hide ) { + +QUnit.test( "show()", function( assert ) { + + assert.expect( 18 ); + + var hiddendiv, div, pass, test; + hiddendiv = jQuery( "div.hidden" ); + + assert.equal( jQuery.css( hiddendiv[ 0 ], "display" ), "none", "hiddendiv is display: none" ); + + hiddendiv.css( "display", "block" ); + assert.equal( jQuery.css( hiddendiv[ 0 ], "display" ), "block", "hiddendiv is display: block" ); + + hiddendiv.show(); + assert.equal( jQuery.css( hiddendiv[ 0 ], "display" ), "block", "hiddendiv is display: block" ); + + hiddendiv.css( "display", "" ); + + pass = true; + div = jQuery( "#qunit-fixture div" ); + div.show().each( function() { + if ( this.style.display === "none" ) { + pass = false; + } + } ); + assert.ok( pass, "Show" ); + + jQuery( + "
    " + + "

    " + + "
    " + + "
    " + ).appendTo( "#qunit-fixture" ).find( "*" ).css( "display", "none" ); + + test = { + "div": "block", + "p": "block", + "a": "inline", + "code": "inline", + "pre": "block", + "span": "inline", + "table": "table", + "thead": "table-header-group", + "tbody": "table-row-group", + "tr": "table-row", + "th": "table-cell", + "td": "table-cell", + "ul": "block", + "li": "list-item" + }; + + jQuery.each( test, function( selector, expected ) { + var elem = jQuery( selector, "#show-tests" ).show(); + assert.equal( elem.css( "display" ), expected, "Show using correct display type for " + selector ); + } ); + + // Make sure that showing or hiding a text node doesn't cause an error + jQuery( "
    test
    text test" ).show().remove(); + jQuery( "
    test
    text test" ).hide().remove(); +} ); + +QUnit.test( "show/hide detached nodes", function( assert ) { + assert.expect( 19 ); + + var div, span, tr; + + div = jQuery( "
    " ).hide(); + assert.equal( div.css( "display" ), "none", "hide() updates inline style of a detached div" ); + div.appendTo( "#qunit-fixture" ); + assert.equal( div.css( "display" ), "none", + "A hidden-while-detached div is hidden after attachment" ); + div.show(); + assert.equal( div.css( "display" ), "block", + "A hidden-while-detached div can be shown after attachment" ); + + div = jQuery( "