From 2cdec4937c0b947f6c01714cfe4f6cd16061b46c Mon Sep 17 00:00:00 2001 From: Trent Willis Date: Mon, 9 Apr 2018 17:19:14 -0700 Subject: [PATCH] Initial commit of Jelyll website Ref https://github.com/qunitjs/qunitjs.com/issues/151. --- .gitignore | 5 +- CONTRIBUTING.md | 5 - Gemfile | 2 + Gemfile.lock | 250 +++++++++ Gruntfile.js | 30 -- README.md | 8 +- _config.yml | 39 ++ _data/plugins.json | 19 + _layouts/home.html | 17 + _layouts/page.html | 13 + _layouts/wrapper.html | 69 +++ _sass/highlight.scss | 75 +++ about.md | 79 +++ config-sample.json | 5 - css/styles.scss | 329 ++++++++++++ guides.md | 19 + img/logo-text.svg | 1 + img/logo-with-colored-text.svg | 1 + img/logo-with-text.svg | 1 + img/logo.png | Bin 0 -> 9121 bytes img/logo.svg | 1 + index.md | 101 ++++ intro.md | 146 ++++++ package.json | 30 -- pages/about.md | 56 -- pages/cookbook.html | 477 ------------------ pages/index.html | 115 ----- pages/intro.html | 194 ------- pages/plugins.html | 71 --- plugins.md | 17 + redirects.json | 3 - ...ookbook-1-basics.html => example-add.html} | 11 +- resources/example-cookbook-noglobals.html | 19 - resources/example-index.html | 29 +- resources/intro/0-ugly.html | 59 --- resources/intro/1-mangled.html | 139 ----- resources/intro/2-getting-somewhere.html | 119 ----- resources/intro/3-first-test.html | 35 -- resources/intro/4-qunit-test.html | 28 - resources/intro/4a-green.png | Bin 28231 -> 0 bytes resources/intro/4b-qunit-test.html | 28 - resources/intro/4b-red.png | Bin 50633 -> 0 bytes resources/intro/5-qunit-test-refactored.html | 28 - resources/intro/6-qunit-dom.html | 75 --- resources/intro/7-qunit-dom-refactored.html | 71 --- resources/intro/8-endstate.html | 110 ---- resources/intro/prettydate.js | 21 - resources/intro/prettydate2.js | 36 -- resources/tests.js | 3 - update-plugins.sh | 2 + ...grade-guide-2.x.md => upgrade-guide-2.x.md | 103 ++-- 51 files changed, 1266 insertions(+), 1828 deletions(-) delete mode 100644 CONTRIBUTING.md create mode 100644 Gemfile create mode 100644 Gemfile.lock delete mode 100644 Gruntfile.js create mode 100644 _config.yml create mode 100644 _data/plugins.json create mode 100644 _layouts/home.html create mode 100644 _layouts/page.html create mode 100644 _layouts/wrapper.html create mode 100644 _sass/highlight.scss create mode 100644 about.md delete mode 100644 config-sample.json create mode 100644 css/styles.scss create mode 100644 guides.md create mode 100644 img/logo-text.svg create mode 100644 img/logo-with-colored-text.svg create mode 100644 img/logo-with-text.svg create mode 100644 img/logo.png create mode 100644 img/logo.svg create mode 100644 index.md create mode 100644 intro.md delete mode 100644 package.json delete mode 100644 pages/about.md delete mode 100644 pages/cookbook.html delete mode 100644 pages/index.html delete mode 100644 pages/intro.html delete mode 100644 pages/plugins.html create mode 100644 plugins.md delete mode 100644 redirects.json rename resources/{example-cookbook-1-basics.html => example-add.html} (54%) delete mode 100644 resources/example-cookbook-noglobals.html delete mode 100644 resources/intro/0-ugly.html delete mode 100644 resources/intro/1-mangled.html delete mode 100644 resources/intro/2-getting-somewhere.html delete mode 100644 resources/intro/3-first-test.html delete mode 100644 resources/intro/4-qunit-test.html delete mode 100644 resources/intro/4a-green.png delete mode 100644 resources/intro/4b-qunit-test.html delete mode 100644 resources/intro/4b-red.png delete mode 100644 resources/intro/5-qunit-test-refactored.html delete mode 100644 resources/intro/6-qunit-dom.html delete mode 100644 resources/intro/7-qunit-dom-refactored.html delete mode 100644 resources/intro/8-endstate.html delete mode 100644 resources/intro/prettydate.js delete mode 100644 resources/intro/prettydate2.js delete mode 100644 resources/tests.js create mode 100755 update-plugins.sh rename pages/upgrade-guide-2.x.md => upgrade-guide-2.x.md (85%) diff --git a/.gitignore b/.gitignore index 633f1bd..300bee5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,2 @@ -/dist/ -/node_modules/ -config.js* +/.sass-cache/ +/_site/ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index 85e7a6f..0000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,5 +0,0 @@ -Welcome! Thanks for your interest in contributing to qunitjs.com. You're **almost** in the right place. More information on how to contribute to this and all other jQuery Foundation projects is over at [contribute.jquery.org](http://contribute.jquery.org). You'll definitely want to take a look at the articles on contributing [to our websites](http://contribute.jquery.org/web-sites/). - -You may also want to take a look at our [commit & pull request guide](http://contribute.jquery.org/commits-and-pull-requests/) and [style guides](http://contribute.jquery.org/style-guide/) for instructions on how to maintain your fork and submit your code. Before we can merge any pull request, we'll also need you to sign our [contributor license agreement](http://contribute.jquery.org/cla). - -You can find us on [IRC](http://irc.jquery.org), specifically in [#jquery-content](irc://irc.freenode.net/#jquery-content) should you have any questions. If you've never contributed to open source before, we've put together [a short guide with tips, tricks, and ideas on getting started](http://contribute.jquery.org/open-source/). diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..37f5eaa --- /dev/null +++ b/Gemfile @@ -0,0 +1,2 @@ +source 'https://rubygems.org' +gem 'github-pages', group: :jekyll_plugins diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 0000000..fa07042 --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,250 @@ +GEM + remote: https://rubygems.org/ + specs: + activesupport (4.2.9) + i18n (~> 0.7) + minitest (~> 5.1) + thread_safe (~> 0.3, >= 0.3.4) + tzinfo (~> 1.1) + addressable (2.5.2) + public_suffix (>= 2.0.2, < 4.0) + coffee-script (2.4.1) + coffee-script-source + execjs + coffee-script-source (1.11.1) + colorator (1.1.0) + commonmarker (0.17.9) + ruby-enum (~> 0.5) + concurrent-ruby (1.0.5) + em-websocket (0.5.1) + eventmachine (>= 0.12.9) + http_parser.rb (~> 0.6.0) + ethon (0.11.0) + ffi (>= 1.3.0) + eventmachine (1.2.5) + execjs (2.7.0) + faraday (0.14.0) + multipart-post (>= 1.2, < 3) + ffi (1.9.23) + forwardable-extended (2.6.0) + gemoji (3.0.0) + github-pages (180) + activesupport (= 4.2.9) + github-pages-health-check (= 1.4.0) + jekyll (= 3.7.3) + jekyll-avatar (= 0.5.0) + jekyll-coffeescript (= 1.1.1) + jekyll-commonmark-ghpages (= 0.1.5) + jekyll-default-layout (= 0.1.4) + jekyll-feed (= 0.9.3) + jekyll-gist (= 1.5.0) + jekyll-github-metadata (= 2.9.4) + jekyll-mentions (= 1.3.0) + jekyll-optional-front-matter (= 0.3.0) + jekyll-paginate (= 1.1.0) + jekyll-readme-index (= 0.2.0) + jekyll-redirect-from (= 0.13.0) + jekyll-relative-links (= 0.5.3) + jekyll-remote-theme (= 0.2.3) + jekyll-sass-converter (= 1.5.2) + jekyll-seo-tag (= 2.4.0) + jekyll-sitemap (= 1.2.0) + jekyll-swiss (= 0.4.0) + jekyll-theme-architect (= 0.1.0) + jekyll-theme-cayman (= 0.1.0) + jekyll-theme-dinky (= 0.1.0) + jekyll-theme-hacker (= 0.1.0) + jekyll-theme-leap-day (= 0.1.0) + jekyll-theme-merlot (= 0.1.0) + jekyll-theme-midnight (= 0.1.0) + jekyll-theme-minimal (= 0.1.0) + jekyll-theme-modernist (= 0.1.0) + jekyll-theme-primer (= 0.5.2) + jekyll-theme-slate (= 0.1.0) + jekyll-theme-tactile (= 0.1.0) + jekyll-theme-time-machine (= 0.1.0) + jekyll-titles-from-headings (= 0.5.1) + jemoji (= 0.9.0) + kramdown (= 1.16.2) + liquid (= 4.0.0) + listen (= 3.1.5) + mercenary (~> 0.3) + minima (= 2.4.0) + nokogiri (>= 1.8.1, < 2.0) + rouge (= 2.2.1) + terminal-table (~> 1.4) + github-pages-health-check (1.4.0) + addressable (~> 2.3) + net-dns (~> 0.8) + octokit (~> 4.0) + public_suffix (~> 2.0) + typhoeus (~> 1.3) + html-pipeline (2.7.1) + activesupport (>= 2) + nokogiri (>= 1.4) + http_parser.rb (0.6.0) + i18n (0.9.5) + concurrent-ruby (~> 1.0) + jekyll (3.7.3) + addressable (~> 2.4) + colorator (~> 1.0) + em-websocket (~> 0.5) + i18n (~> 0.7) + jekyll-sass-converter (~> 1.0) + jekyll-watch (~> 2.0) + kramdown (~> 1.14) + liquid (~> 4.0) + mercenary (~> 0.3.3) + pathutil (~> 0.9) + rouge (>= 1.7, < 4) + safe_yaml (~> 1.0) + jekyll-avatar (0.5.0) + jekyll (~> 3.0) + jekyll-coffeescript (1.1.1) + coffee-script (~> 2.2) + coffee-script-source (~> 1.11.1) + jekyll-commonmark (1.2.0) + commonmarker (~> 0.14) + jekyll (>= 3.0, < 4.0) + jekyll-commonmark-ghpages (0.1.5) + commonmarker (~> 0.17.6) + jekyll-commonmark (~> 1) + rouge (~> 2) + jekyll-default-layout (0.1.4) + jekyll (~> 3.0) + jekyll-feed (0.9.3) + jekyll (~> 3.3) + jekyll-gist (1.5.0) + octokit (~> 4.2) + jekyll-github-metadata (2.9.4) + jekyll (~> 3.1) + octokit (~> 4.0, != 4.4.0) + jekyll-mentions (1.3.0) + activesupport (~> 4.0) + html-pipeline (~> 2.3) + jekyll (~> 3.0) + jekyll-optional-front-matter (0.3.0) + jekyll (~> 3.0) + jekyll-paginate (1.1.0) + jekyll-readme-index (0.2.0) + jekyll (~> 3.0) + jekyll-redirect-from (0.13.0) + jekyll (~> 3.3) + jekyll-relative-links (0.5.3) + jekyll (~> 3.3) + jekyll-remote-theme (0.2.3) + jekyll (~> 3.5) + rubyzip (>= 1.2.1, < 3.0) + typhoeus (>= 0.7, < 2.0) + jekyll-sass-converter (1.5.2) + sass (~> 3.4) + jekyll-seo-tag (2.4.0) + jekyll (~> 3.3) + jekyll-sitemap (1.2.0) + jekyll (~> 3.3) + jekyll-swiss (0.4.0) + jekyll-theme-architect (0.1.0) + jekyll (~> 3.5) + jekyll-seo-tag (~> 2.0) + jekyll-theme-cayman (0.1.0) + jekyll (~> 3.5) + jekyll-seo-tag (~> 2.0) + jekyll-theme-dinky (0.1.0) + jekyll (~> 3.5) + jekyll-seo-tag (~> 2.0) + jekyll-theme-hacker (0.1.0) + jekyll (~> 3.5) + jekyll-seo-tag (~> 2.0) + jekyll-theme-leap-day (0.1.0) + jekyll (~> 3.5) + jekyll-seo-tag (~> 2.0) + jekyll-theme-merlot (0.1.0) + jekyll (~> 3.5) + jekyll-seo-tag (~> 2.0) + jekyll-theme-midnight (0.1.0) + jekyll (~> 3.5) + jekyll-seo-tag (~> 2.0) + jekyll-theme-minimal (0.1.0) + jekyll (~> 3.5) + jekyll-seo-tag (~> 2.0) + jekyll-theme-modernist (0.1.0) + jekyll (~> 3.5) + jekyll-seo-tag (~> 2.0) + jekyll-theme-primer (0.5.2) + jekyll (~> 3.5) + jekyll-github-metadata (~> 2.9) + jekyll-seo-tag (~> 2.2) + jekyll-theme-slate (0.1.0) + jekyll (~> 3.5) + jekyll-seo-tag (~> 2.0) + jekyll-theme-tactile (0.1.0) + jekyll (~> 3.5) + jekyll-seo-tag (~> 2.0) + jekyll-theme-time-machine (0.1.0) + jekyll (~> 3.5) + jekyll-seo-tag (~> 2.0) + jekyll-titles-from-headings (0.5.1) + jekyll (~> 3.3) + jekyll-watch (2.0.0) + listen (~> 3.0) + jemoji (0.9.0) + activesupport (~> 4.0, >= 4.2.9) + gemoji (~> 3.0) + html-pipeline (~> 2.2) + jekyll (~> 3.0) + kramdown (1.16.2) + liquid (4.0.0) + listen (3.1.5) + rb-fsevent (~> 0.9, >= 0.9.4) + rb-inotify (~> 0.9, >= 0.9.7) + ruby_dep (~> 1.2) + mercenary (0.3.6) + mini_portile2 (2.3.0) + minima (2.4.0) + jekyll (~> 3.5) + jekyll-feed (~> 0.9) + jekyll-seo-tag (~> 2.1) + minitest (5.11.3) + multipart-post (2.0.0) + net-dns (0.8.0) + nokogiri (1.8.2) + mini_portile2 (~> 2.3.0) + octokit (4.8.0) + sawyer (~> 0.8.0, >= 0.5.3) + pathutil (0.16.1) + forwardable-extended (~> 2.6) + public_suffix (2.0.5) + rb-fsevent (0.10.3) + rb-inotify (0.9.10) + ffi (>= 0.5.0, < 2) + rouge (2.2.1) + ruby-enum (0.7.2) + i18n + ruby_dep (1.5.0) + rubyzip (1.2.1) + safe_yaml (1.0.4) + sass (3.5.6) + sass-listen (~> 4.0.0) + sass-listen (4.0.0) + rb-fsevent (~> 0.9, >= 0.9.4) + rb-inotify (~> 0.9, >= 0.9.7) + sawyer (0.8.1) + addressable (>= 2.3.5, < 2.6) + faraday (~> 0.8, < 1.0) + terminal-table (1.8.0) + unicode-display_width (~> 1.1, >= 1.1.1) + thread_safe (0.3.6) + typhoeus (1.3.0) + ethon (>= 0.9.0) + tzinfo (1.2.5) + thread_safe (~> 0.1) + unicode-display_width (1.3.0) + +PLATFORMS + ruby + +DEPENDENCIES + github-pages + +BUNDLED WITH + 1.16.1 diff --git a/Gruntfile.js b/Gruntfile.js deleted file mode 100644 index 06dfbfb..0000000 --- a/Gruntfile.js +++ /dev/null @@ -1,30 +0,0 @@ -module.exports = function( grunt ) { - -grunt.loadNpmTasks( "grunt-jquery-content" ); - -grunt.initConfig({ - "build-posts": { - page: "pages/**" - }, - "build-resources": { - all: "resources/**" - }, - wordpress: (function() { - var config = require( "./config" ); - config.dir = "dist/wordpress"; - return config; - })() -}); - -// Inserts markup to put ToC in sidebar -grunt.registerTask( "generate-columns", function() { - var upgradeGuide = "dist/wordpress/posts/page/upgrade-guide-2.x.html", - content = grunt.file.read( upgradeGuide ) - .replace( /(<\/script>)/, "$1\n
" ) - .replace( /(\n
\n$1" ); - grunt.file.write( upgradeGuide, content ); -}); - -grunt.registerTask( "build", [ "build-posts", "generate-columns", "build-resources" ] ); - -}; diff --git a/README.md b/README.md index e7fa5cf..30e3b1d 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,9 @@ # qunitjs.com -## Building and Deploying +This repository houses the content and code for the [qunitjs.com](https://qunitjs.com/) website. -To build and deploy your changes for previewing in a [`jquery-wp-content`](https://github.com/jquery/jquery-wp-content) instance, follow the [workflow instructions](http://contribute.jquery.org/web-sites/#workflow) from our documentation on [contributing to jQuery Foundation web sites](http://contribute.jquery.org/web-sites/). +## Development + +1. Install [Ruby](https://www.ruby-lang.org/) and [Bundler](http://bundler.io/). +2. Install project dependencies by running `bundle install`. +3. Serve the website with `bundle exec jekyll serve` diff --git a/_config.yml b/_config.yml new file mode 100644 index 0000000..0ee0f36 --- /dev/null +++ b/_config.yml @@ -0,0 +1,39 @@ +# Welcome to Jekyll! +# +# This config file is meant for settings that affect your whole blog, values +# which you are expected to set up once and rarely edit after that. If you find +# yourself editing this file very often, consider using Jekyll's data files +# feature for the data you need to update frequently. +# +# For technical reasons, this file is *NOT* reloaded automatically when you use +# 'bundle exec jekyll serve'. If you change this file, please restart the server process. + +# Site settings +# These are used to personalize your new site. If you look in the HTML files, +# you will see them accessed via {{ site.title }}, {{ site.email }}, and so on. +# You can create any custom variable you would like, and they will be accessible +# in the templates via {{ site.myvariable }}. +title: QUnit +# email: your-email@domain.com +description: > # this means to ignore newlines until "baseurl:" + The powerful, easy-to-use JavaScript testing framework +baseurl: "" # the subpath of your site, e.g. /blog +url: "https://qunitjs.com" # the base hostname & protocol for your site, e.g. http://example.com +twitter_username: qunitjs +github_username: qunitjs + +highlighter: rouge +markdown: kramdown +kramdown: + input: GFM + toc_levels: "1,2" + +exclude: + - Gemfile + - Gemfile.lock + - README.md + - CONTRIBUTING.md + - CNAME + - vendor + +permalink: title diff --git a/_data/plugins.json b/_data/plugins.json new file mode 100644 index 0000000..fe16dcf --- /dev/null +++ b/_data/plugins.json @@ -0,0 +1,19 @@ +[{"name":"qunit-reporter-junit","description":"Produce JUnit-style XML test reports with QUnit.","maintainers":[{"username":"jamesmgreene","email":"james.m.greene@gmail.com"}],"keywords":["qunit","qunit-plugin","qunit-reporter","junit","xunit","ci","jenkins"],"version":"1.1.1","date":"2016-10-16T01:23:43.596Z"} +,{"name":"qunit-assert-html","description":"QUnit HTML assertion addon","maintainers":[{"username":"jamesmgreene","email":"james.m.greene@gmail.com"}],"keywords":["qunit-plugin","qunit-assert","qunit","assert","HTML","assertHTMLEquals","DOM"],"version":"1.1.0","date":"2016-10-03T02:49:50.045Z"} +,{"name":"qunit-composite","description":"Run multiple QUnit test suite pages.","maintainers":[{"username":"jamesmgreene","email":"james.m.greene@gmail.com"}],"keywords":["qunit","qunit-plugin","composite","multiple","suites","pages"],"version":"2.0.0","date":"2016-10-02T01:26:01.185Z"} +,{"name":"qunit-assert-step","description":"A QUnit plugin for asserting the proper sequence in which the code should execute.","maintainers":[{"username":"jamesmgreene","email":"james.m.greene@gmail.com"}],"keywords":["qunit-plugin","qunit-assert","qunit","assert","order","step"],"version":"1.1.1","date":"2016-10-03T03:46:41.981Z"} +,{"name":"qunit-assert-close","description":"A QUnit plugin for asserting that a number is approximately equal (or not) to an expected number, within a given tolerance.","maintainers":[{"username":"jamesmgreene","email":"james.m.greene@gmail.com"}],"keywords":["qunit-plugin","qunit-assert","qunit","assert","close","close-enough","roughlyEquals","closeTo","approximate"],"version":"2.1.2","date":"2016-10-02T02:25:51.041Z"} +,{"name":"qunit-theme-ninja","description":"A QUnit theme for ninjas.","maintainers":[{"username":"krinkle","email":"krinklemail@gmail.com"}],"keywords":["qunit","qunit-plugin","qunit-theme"],"version":"1.1.0","date":"2014-04-23T16:35:36.572Z"} +,{"name":"qunit-assert-canvas","description":"A QUnit plugin for asserting individual pixel values within a Canvas element.","maintainers":[{"username":"jamesmgreene","email":"james.m.greene@gmail.com"}],"keywords":["qunit-plugin","qunit-assert","qunit","assert","canvas","drawImage","image","draw","render"],"version":"1.1.0","date":"2016-10-02T02:25:02.752Z"} +,{"name":"qunit-assert-classes","description":"A set of assertions for checking thats an element has ( or does not have ) any number of classes. The check is order independent and gives a diff of the expected classes.","maintainers":[{"username":"arschmitz","email":"arschmitz@gmail.com"}],"keywords":["qunit-plugin","qunit-assert","qunit","assert","classes"],"version":"1.0.2","date":"2015-04-29T19:09:20.175Z"} +,{"name":"qunit-pending","description":"A QUnit plugin to add the \"pending\" test syntax to QUnit, a la Mocha.","maintainers":[{"username":"jamesmgreene","email":"james.m.greene@gmail.com"}],"keywords":["qunit-plugin","qunit-test","qunit","test","unit test","pending","skip","skipped","not implemented yet","stub","future"],"version":"1.0.0","date":"2016-10-02T01:55:55.632Z"} +,{"name":"qunit-reporter-lcov","description":"Produce lcov test reports with QUnit","maintainers":[{"username":"piranna","email":"piranna@gmail.com"}],"keywords":["qunit","qunit-plugin","qunit-reporter","lcov"],"version":"1.0.2","date":"2015-03-15T20:31:35.403Z"} +,{"name":"qunit-assert-compare","description":"A QUnit plugin for asserting numerical comparisons like greater than, less than, etc.","maintainers":[{"username":"jamesmgreene","email":"james.m.greene@gmail.com"}],"keywords":["qunit-plugin","qunit-assert","assert","qunit","less","lesser","lessThan","less than","lesserThan","lesser than","greater","greaterThan","greater than","lessThanOrEqual","less than or equal","lesserThanOrEqual","lesser than or equal","greaterThanOrEqual","greater than or equal","equal","notEqual","not equal","compare","comparison","numerical comparison","lt","lte","gt","gte","eq","neq"],"version":"1.1.0","date":"2016-10-02T03:13:17.701Z"} +,{"name":"qunit-theme-gabe","description":"A QUnit theme by Gabe Hendry.","maintainers":[{"username":"krinkle","email":"krinklemail@gmail.com"}],"keywords":["qunit","qunit-plugin","qunit-theme"],"version":"1.0.1","date":"2013-05-06T23:33:24.707Z"} +,{"name":"qunit-theme-nv","description":"A QUnit theme.","maintainers":[{"username":"krinkle","email":"krinklemail@gmail.com"}],"keywords":["qunit","qunit-plugin","qunit-theme"],"version":"1.0.1","date":"2013-05-07T00:46:25.641Z"} +,{"name":"qunit-dark","description":"A kind of dark theme for qunit","maintainers":[{"username":"royriojas","email":"royriojas@gmail.com"}],"keywords":["qunit","qunit-plugin","qunit-theme","qunit theme dark"],"version":"0.1.5","date":"2014-03-20T17:47:37.864Z"} +,{"name":"qunit-assert-nodes","description":"A QUnit plugin providing a convenient API for batch acceptance assertions","maintainers":[{"username":"dsheiko","email":"web.sbelarus@gmail.com"}],"keywords":["jquery","qunit-plugin","qunit-assert","qunit","assert","order"],"version":"0.0.3","date":"2013-06-25T07:56:50.394Z"} +,{"name":"qunit-special-blend","description":"A QUnit plugin/hack/wrapper which allows the user to designiate single tests or modules to run.","maintainers":[{"username":"jarrodctaylor","email":"jarrod.c.taylor@gmail.com"}],"keywords":["qunit-plugin","qunit","unit testing","test case"],"version":"1.1.1","date":"2014-11-23T01:26:37.775Z"} +,{"name":"karma-qunit-special-blend","description":"A QUnit plugin/hack/wrapper which allows the user to designiate single tests or modules to run.","maintainers":[{"username":"jarrodctaylor","email":"jarrod.c.taylor@gmail.com"}],"keywords":["karma-plugin","qunit-plugin","qunit","unit testing","test case"],"version":"1.1.1","date":"2014-11-23T01:14:02.286Z"} +,{"name":"qunit-theme-burce","description":"A QUnit theme by Bryce Dorn.","maintainers":[{"username":"brycedorn","email":"brycedorn@gmail.com"}],"keywords":["qunit","burce","qunit-plugin","qunit-theme"],"version":"1.0.0","date":"2014-06-09T22:03:07.465Z"} +] diff --git a/_layouts/home.html b/_layouts/home.html new file mode 100644 index 0000000..720c533 --- /dev/null +++ b/_layouts/home.html @@ -0,0 +1,17 @@ +--- +layout: wrapper +--- + +
+
+

QUnit

+

The powerful, easy-to-use JavaScript testing framework.

+ + Get Started + View the Docs +
+
+ +
+ {{ content }} +
diff --git a/_layouts/page.html b/_layouts/page.html new file mode 100644 index 0000000..e79ff80 --- /dev/null +++ b/_layouts/page.html @@ -0,0 +1,13 @@ +--- +layout: wrapper +--- + +
+
+

{{ page.title }}

+
+
+ +
+ {{ content }} +
diff --git a/_layouts/wrapper.html b/_layouts/wrapper.html new file mode 100644 index 0000000..8694039 --- /dev/null +++ b/_layouts/wrapper.html @@ -0,0 +1,69 @@ + + + + + + + {% if page.title %}{{ page.title }} | {% endif %}QUnit + + + + + + + + + + + + + + {{ content }} + + + + diff --git a/_sass/highlight.scss b/_sass/highlight.scss new file mode 100644 index 0000000..51f1ece --- /dev/null +++ b/_sass/highlight.scss @@ -0,0 +1,75 @@ +pre.highlight { + border: 1px solid #ddd; + border-left: 4px solid $color-brand; + border-radius: 4px; + margin: 0; + margin-bottom: $size-spacing; + padding: 0.5rem 1rem; + font-size: 95%; + line-height: 140%; +} +.highlight code { + line-height: 150%; + background: transparent; +} +.highlight .hll { background-color: #ffffcc } +.highlight .c { color: #999988; font-style: italic } /* Comment */ +.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */ +.highlight .k { color: #000000; font-weight: bold } /* Keyword */ +.highlight .o { color: #000000; font-weight: bold } /* Operator */ +.highlight .cm { color: #999988; font-style: italic } /* Comment.Multiline */ +.highlight .cp { color: #999999; font-weight: bold; font-style: italic } /* Comment.Preproc */ +.highlight .c1 { color: #999988; font-style: italic } /* Comment.Single */ +.highlight .cs { color: #999999; font-weight: bold; font-style: italic } /* Comment.Special */ +.highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */ +.highlight .ge { color: #000000; font-style: italic } /* Generic.Emph */ +.highlight .gr { color: #aa0000 } /* Generic.Error */ +.highlight .gh { color: #999999 } /* Generic.Heading */ +.highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */ +.highlight .go { color: #888888 } /* Generic.Output */ +.highlight .gp { color: #555555 } /* Generic.Prompt */ +.highlight .gs { font-weight: bold } /* Generic.Strong */ +.highlight .gu { color: #aaaaaa } /* Generic.Subheading */ +.highlight .gt { color: #aa0000 } /* Generic.Traceback */ +.highlight .kc { color: #000000; font-weight: bold } /* Keyword.Constant */ +.highlight .kd { color: #000000; font-weight: bold } /* Keyword.Declaration */ +.highlight .kn { color: #000000; font-weight: bold } /* Keyword.Namespace */ +.highlight .kp { color: #000000; font-weight: bold } /* Keyword.Pseudo */ +.highlight .kr { color: #000000; font-weight: bold } /* Keyword.Reserved */ +.highlight .kt { color: #445588; font-weight: bold } /* Keyword.Type */ +.highlight .m { color: #009999 } /* Literal.Number */ +.highlight .s { color: #d01040 } /* Literal.String */ +.highlight .na { color: #008080 } /* Name.Attribute */ +.highlight .nb { color: #0086B3 } /* Name.Builtin */ +.highlight .nc { color: #445588; font-weight: bold } /* Name.Class */ +.highlight .no { color: #008080 } /* Name.Constant */ +.highlight .nd { color: #3c5d5d; font-weight: bold } /* Name.Decorator */ +.highlight .ni { color: #800080 } /* Name.Entity */ +.highlight .ne { color: #990000; font-weight: bold } /* Name.Exception */ +.highlight .nf { color: #990000; font-weight: bold } /* Name.Function */ +.highlight .nl { color: #990000; font-weight: bold } /* Name.Label */ +.highlight .nn { color: #555555 } /* Name.Namespace */ +.highlight .nt { color: #000080 } /* Name.Tag */ +.highlight .nv { color: #008080 } /* Name.Variable */ +.highlight .ow { color: #000000; font-weight: bold } /* Operator.Word */ +.highlight .w { color: #bbbbbb } /* Text.Whitespace */ +.highlight .mf { color: #009999 } /* Literal.Number.Float */ +.highlight .mh { color: #009999 } /* Literal.Number.Hex */ +.highlight .mi { color: #009999 } /* Literal.Number.Integer */ +.highlight .mo { color: #009999 } /* Literal.Number.Oct */ +.highlight .sb { color: #d01040 } /* Literal.String.Backtick */ +.highlight .sc { color: #d01040 } /* Literal.String.Char */ +.highlight .sd { color: #d01040 } /* Literal.String.Doc */ +.highlight .s2 { color: #d01040 } /* Literal.String.Double */ +.highlight .se { color: #d01040 } /* Literal.String.Escape */ +.highlight .sh { color: #d01040 } /* Literal.String.Heredoc */ +.highlight .si { color: #d01040 } /* Literal.String.Interpol */ +.highlight .sx { color: #d01040 } /* Literal.String.Other */ +.highlight .sr { color: #009926 } /* Literal.String.Regex */ +.highlight .s1 { color: #d01040 } /* Literal.String.Single */ +.highlight .ss { color: #990073 } /* Literal.String.Symbol */ +.highlight .bp { color: #999999 } /* Name.Builtin.Pseudo */ +.highlight .vc { color: #008080 } /* Name.Variable.Class */ +.highlight .vg { color: #008080 } /* Name.Variable.Global */ +.highlight .vi { color: #008080 } /* Name.Variable.Instance */ +.highlight .il { color: #009999 } /* Literal.Number.Integer.Long */ diff --git a/about.md b/about.md new file mode 100644 index 0000000..90d5ec0 --- /dev/null +++ b/about.md @@ -0,0 +1,79 @@ +--- +layout: page +title: About +--- + +

QUnit is a powerful, easy-to-use JavaScript testing framework. It was originally developed for the jQuery project but has since evolved to be a dependency of many modern JavaScript libraries and applications, including being the default testing framework for the Ember.js ecosystem.

+ +## Philosophy + +QUnit's philosophy as a test framework boils down to three primary tenants: _Easy_, _Universal_, and _Extensible_. + +### Easy + +QUnit should be easy-to-use from start to finish. Setting up your first test with should be super simple, requiring as little overhead as possible. Then, as you're developing, when a test or assertion fails, QUnit should provide feedback to you as fast as possible, with enough detail to quickly figure out the underlying issue. And it should do so without interrupting or corrupting other tests. + +Additionally, QUnit should be fast to make it easy for developers to have confidence that putting their tests on their critical path won't slow them down. + +### Univeral + +QUnit should be universally applicable for testing JavaScript code and support many different environments. JavaScript can run in the browser, in worker threads, and on the server, and so should QUnit so that you can test your code in the same environment where it will be running; the environment where you need to have confidence it works. + +### Extensible + +QUnit should be opinionated with a lean API to support being easy-to-use, but it should also be highly extensible. There are many different approaches to testing and many different types of tests that users may want to write, and while we can not support all of these out of the box, we can support APIs to enable the community to extend QUnit to meet their needs. + +## Community + +QUnit is free, open source and always will be, but this wouldn't be possible without a first-class team of volunteers and community of users. If you're interested in getting plugged into the community, here are some ways to get started: + +* Join [the official chat channel on Gitter](https://gitter.im/qunitjs/qunit). +* Follow [@qunitjs on Twitter](https://twitter.com/qunitjs) for announcements. + +Furthermore, if you'd like to contribute... + +* Features or bug fixes, you can find [the source code on GitHub](https://github.com/qunitjs/qunit). +* Updates to this website, you can find [this website on GitHub](https://github.com/qunitjs/qunitjs.com). +* Updates to the API documentation, you can find it in [the "docs" directory of the main repo on GitHub](https://github.com/qunitjs/qunit/tree/master/docs). + +## Team + +Between API design, feature implementation, ticket triage, bug fixing, and everything else, there’s a lot of work that goes into QUnit, and all of it is done by volunteers. While we value all of our contributors, there are a few who contribute frequently, provide high-level direction for the project, and are responsible for its overall maintenance, and we recognize them below. + +For a full list of contributors, see the [authors list](https://github.com/qunitjs/qunit/blob/master/AUTHORS.txt). + +### [Trent Willis](https://twitter.com/trentmwillis) - Project Lead + +Trent is a Senior UI Engineer at [Netflix](https://www.netflix.com) in beautiful Los Gatos, CA. He has been contributing to QUnit since 2015 and became the project lead in early 2017. + +### [Leo Balter](https://twitter.com/leobalter) + +Leo is a software engineer at [Bocoup](https://bocoup.com/) based in Boston, MA. He represents the JSFoundation at TC39, the technical committee that designs the language specification for JS, and maintains the official spec tests at [test262](http://github.com/tc39/test262/). He has been contributing to QUnit since 2013 and was a project lead from 2015 to early-2017. + +### [Richard Gibson](https://twitter.com/gibson042) + +Richard is an architect at [Dyn](http://dyn.com/) in New Hampshire, USA. He has been contributing to jQuery Foundation projects since 2011 (QUnit since 2012) and can be spotted on a large handful of open source repositories. + +### [Kevin Partington](https://github.com/platinumazure) + +Kevin is a software engineer based out of Minnesota, USA. He has contributed to QUnit since 2015. He is also heavily involved in the ESLint project and actively maintains an [ESLint plugin](https://github.com/platinumazure/eslint-plugin-qunit) for linting QUnit tests. + +### [Timo Tijhof](https://timotijhof.net/) + +Timo is a senior engineer at [Wikimedia Foundation](https://www.wikimedia.org/) where he is on the [Architecture Committee](https://www.mediawiki.org/wiki/Architecture_committee), the technical committee that governs the integrity and stability of Wikimedia software projects. He has been contributing to jQuery Foundation projects since 2011 and joined the QUnit Team in 2012. + +### [Jörn Zaefferer](http://bassistance.de/) + +Jörn is a freelance web developer, consultant, and trainer, residing in Cologne, Germany. Jörn evolved jQuery’s test suite into QUnit and was project lead until mid-2015. He created and maintains a number of popular plugins. As a jQuery UI development lead, he focuses on the development of new plugins, widgets, and utilities. + +### Previous Team Members + +* [James M. Greene](http://greene.io/) +* [John Resig](http://ejohn.org/) +* [Scott González](http://nemikor.com/) + +## History + +QUnit was originally developed by John Resig as part of [jQuery](https://jquery.com/). In 2008 it got its own home, name, and API documentation, allowing others to use it for their unit testing as well. At the time it still depended on jQuery. A rewrite in 2009 fixed that and QUnit has been an independent project ever since. + +QUnit's assertion methods originally followed the [CommonJS Unit Testing](https://wiki.commonjs.org/wiki/Unit_Testing/1.0) specification (which was to some degree influenced by QUnit) but have since been expanded to include a wider variety of assertions. diff --git a/config-sample.json b/config-sample.json deleted file mode 100644 index d6992c6..0000000 --- a/config-sample.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "url": "vagrant.qunitjs.com", - "username": "admin", - "password": "secret" -} diff --git a/css/styles.scss b/css/styles.scss new file mode 100644 index 0000000..a4226c1 --- /dev/null +++ b/css/styles.scss @@ -0,0 +1,329 @@ +--- +--- + +$size-1: 1rem; +$size-2: 1.333rem; +$size-3: 1.777rem; +$size-4: 2.369rem; +$size-5: 3.157rem; +$size-spacing: $size-2; + +$color-white: #fff; +$color-off-white: #eee; +$color-brand: #9c3493; +$color-accent: #390F39; +$color-black: #333; + +@import "highlight"; + +* { + box-sizing: border-box; +} + +body { + color: $color-black; + font-family: BlinkMacSystemFont, -apple-system, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", "Helvetica", "Arial", sans-serif; + line-height: 1.5; + margin: 0; +} + +h1, h2, h3, h4, h5, h6 { + color: $color-black; + font-weight: bold; + margin: 0; + margin-bottom: $size-spacing; +} +h1 { font-size: $size-5; } +h2 { + color: $color-brand; + font-size: $size-4; + + a { + color: $color-black; + + &:hover { + color: $color-brand; + } + } +} +h3 { font-size: $size-3; } +h4 { font-size: $size-2; } +h5 { font-size: $size-1; } +h6 { font-size: $size-1; } + +p { + margin: 0; + margin-bottom: $size-spacing; +} + +a { + color: $color-brand; + text-decoration: none; + + &:active, + &:hover, + &:focus { + color: $color-accent; + } +} + +code { + font-family: Monaco, Consolas, "Andale Mono", "DejaVu Sans Mono", monospace; + font-size: 95%; + background: rgba($color-brand, 0.2); + padding: 0 0.25em; + border-radius: 3px; +} + +hr { + margin: $size-5 0; + border: none; + border-top: 1px solid #ddd; +} + +iframe { + width:100%; + border: 1px solid #ddd; + border-radius: 3px; +} + +.main { + padding: ($size-spacing * 2) 0 $size-spacing; +} + +.wrapper { + max-width: 65rem; + margin: 0 auto; +} + +.lead { + font-size: $size-2; +} + +.button { + background-color: #fff; + border: 1px solid #fff; + border-radius: 4px; + color: #9c3493; + display: inline-block; + margin: 0.5rem; + padding: 0.5rem 1rem; + text-decoration: none; + transition: box-shadow 0.3s; + + &.secondary { + background-color: transparent; + color: white; + font-weight: 100; + } + + &:hover { + box-shadow: 0 10px 20px rgba(0,0,0,0.19), 0 6px 6px rgba(0,0,0,0.23); + } +} + +/* Site Header */ + +.site-header { + box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24); +} + +.site-header-wrapper { + display: flex; + justify-content: space-between; +} + +.site-logo { + color: $color-brand; + display: flex; + align-items: center; + font-size: $size-3; + font-weight: bold; + padding: $size-1 0; + text-decoration: none; + transition: color 0.3s; + + img { + height: $size-4; + margin-right: $size-spacing / 2; + } +} + +.site-logo:hover { + color: #390F39; +} + +/* Site Navigation */ + +.site-nav-list { + height: 100%; + list-style: none; + margin: 0; + padding: 0; +} + +.site-nav-item { + display: inline-block; + height: 100%; +} + +.site-nav-item:hover { + background-color: #efefef; +} + +.site-nav-link { + align-items: center; + color: #333333; + display: flex; + height: 100%; + padding: 0 0.5rem; + text-decoration: none; +} + +.site-nav-link.has-sub-list::after { + content: '▼'; + font-size: 0.75rem; + margin-left: 0.5rem; +} + +.site-nav-link:hover { + color: #9c3493; +} + +.site-nav-sub-list { + display: none; + position: absolute; + box-shadow: 0 10px 20px rgba(0,0,0,0.19), 0 6px 6px rgba(0,0,0,0.23); + background: #efefef; + padding: 0.5rem 1rem; + margin: 0; + list-style: none; + border-radius: 0 0 3px 3px; +} + +.site-nav-item:hover .site-nav-sub-list { + display: block; +} + +.site-sub-nav-link { + color: #333; + display: block; + margin: 0.5rem 0; + text-decoration: none; +} + +.site-sub-nav-link:hover { + color: $color-brand; +} + +/* Home Page Hero */ + +.hero { + background-color: $color-brand; + background-image: linear-gradient(-45deg, $color-brand, $color-accent); + padding: ($size-4 * 2) 0; + text-align: center; +} + +.hero-title img { + height: 1em; +} + +.hero-title, +.hero-description { + color: $color-white; + margin: 0; +} + +.hero-description { + font-weight: 100; +} + +.hero-button { + font-size: $size-2; + margin-top: $size-4; +} + +/* Home Page Highlights */ + +#home { + h2, h3 { + text-align: center; + font-weight: 300; + } +} + +.highlights { + display: grid; + grid-gap: $size-4; + grid-template-columns: 1fr 1fr 1fr; + text-align: center; +} + +.example-results { + display: grid; + grid-template-columns: 1fr 1fr; + grid-gap: $size-4; +} + +.example-result iframe, +.example-result pre { + height: 360px; +} + +#current-release { + margin-bottom: 0; +} + +.current-release { + font-size: $size-2; + text-align: center; +} + +/* Plugins Page */ + +#plugins { + list-style: none; + padding: 0; + display: grid; + grid-template-columns: 1fr 1fr; + grid-gap: $size-spacing; + margin: $size-spacing 0; +} + +.plugin { + border-bottom: 1px solid $color-off-white; + + h3 { + margin-bottom: 0; + } +} + +/* Site Footer */ + +.site-footer { + border-top: 1px solid $color-off-white; + margin-top: $size-4; + padding: $size-4 0; +} + +.site-footer .wrapper { + display: flex; + justify-content: space-between; +} + +.external-links a { + margin: 0 0.5rem; +} + +.cta { + text-align: center; + font-size: 1.777rem; +} + +.cta .button { + background-color: $color-brand; + border-color: $color-brand; + color: white; + font-weight: 100; +} diff --git a/guides.md b/guides.md new file mode 100644 index 0000000..b1bfc53 --- /dev/null +++ b/guides.md @@ -0,0 +1,19 @@ +--- +layout: page +title: Guides +--- + +

The following guides are here to help you as a user of QUnit. They cover a range of topics, beginning with how to get started with QUnit and continuing up through extensions to QUnit and how you can upgrade between major versions.

+ +## [Getting Started](./intro) + +A guided introduction to using QUnit and writing unit tests. If you've never used QUnit before, or need a refresher on the basics, this is the section for you. + +## [Plugins](./plugins) + +A curated list of plugins developed by the QUnit community. If you're looking for a different API, more assertions, or even different reporter themes, you'll find them here. + +## [2.x Upgrade Guide](./upgrade-guide-2.x) + +A reference for upgrading your QUnit 1.x test suite to QUnit 2.x. + diff --git a/img/logo-text.svg b/img/logo-text.svg new file mode 100644 index 0000000..47e4183 --- /dev/null +++ b/img/logo-text.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/img/logo-with-colored-text.svg b/img/logo-with-colored-text.svg new file mode 100644 index 0000000..56230f4 --- /dev/null +++ b/img/logo-with-colored-text.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/img/logo-with-text.svg b/img/logo-with-text.svg new file mode 100644 index 0000000..ac9ae07 --- /dev/null +++ b/img/logo-with-text.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/img/logo.png b/img/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..9e343f3a5f1635dbb5b3714002fc7bbd6308b769 GIT binary patch literal 9121 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 VKrds>=(_*3^|Xz&YBU~4{|_QGPW=D? literal 0 HcmV?d00001 diff --git a/img/logo.svg b/img/logo.svg new file mode 100644 index 0000000..8c15c21 --- /dev/null +++ b/img/logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/index.md b/index.md new file mode 100644 index 0000000..2cda517 --- /dev/null +++ b/index.md @@ -0,0 +1,101 @@ +--- +# You don't need to edit this file, it's empty on purpose. +# Edit theme's home layout instead if you wanna make some changes +# See: https://jekyllrb.com/docs/themes/#overriding-theme-defaults +layout: home +--- + +
+
+

Easy

+

Easy, zero configuration setup for any Node.js project and minimal configuration for Browser-based projects.

+
+ +
+

Universal

+

Tests can be run anywhere; Node, your browser, even inside a Web Worker. Test your code where it runs.

+
+ +
+

Extensible

+

Flexible APIs for custom assertions, runners, and reporters mean you can extend QUnit to fit your needs.

+
+
+ +--- + +## A Quick Example + +```js +const add = (a, b) => a + b; +QUnit.module('add', function() { + QUnit.test('should add two numbers', function(assert) { + assert.equal(add(1, 1), 2, '1 + 1 = 2'); + }); +}); +``` + +
+ +
+ +### Browser Result + + + +
+ +
+ +### CLI Result + +``` +TAP version 13 +ok 1 add > should add two numbers +1..1 +# pass 1 +# skip 0 +# todo 0 +# fail 0 +``` + +
+ +
+ +--- + +## Current Release + +

v2.10.1 (changelog)

+ +
+ +
+ +### Release Downloads + +These are the officially supported download channels for QUnit releases: + +* npm: `npm install --save-dev qunit` +* Yarn: `yarn add --dev qunit` +* Bower: `bower install --save-dev qunit` +* CDN: [`qunit-2.10.1.js`](https://code.jquery.com/qunit/qunit-2.10.1.js) & [`qunit-2.10.1.css`](https://code.jquery.com/qunit/qunit-2.10.1.css) + +
+ +
+ +### Pre-release Downloads + +These downloads give you access to the latest features and bug fixes to QUnit that have not been released yet: + +* CDN: [`qunit-git.js`](https://code.jquery.com/qunit/qunit-git.js) & [`qunit-git.css`](https://code.jquery.com/qunit/qunit-git.css) + +
+ +
+ +--- + +

What are you waiting for? Get started!

diff --git a/intro.md b/intro.md new file mode 100644 index 0000000..c196659 --- /dev/null +++ b/intro.md @@ -0,0 +1,146 @@ +--- +layout: page +title: Getting Started +--- + +

The following guide will get you up-and-running with QUnit in either [Node](#in-node) or [the Browser](#in-the-browser) in just a few minutes.

+ +## In Node + +Getting started with QUnit in Node is quick and easy. First, install QUnit inside your Node package using `npm`: + +```bash +npm install --save-dev qunit +``` + +Or `yarn`: + +```bash +yarn add --dev qunit +``` + +Then, let's start writing tests! We'll start with a function that adds two numbers. Create a file `add.js` with the following contents: + +```js +const add = (a, b) => a + b; +module.exports = add; +``` + +Next, create a file for your test at `test/add.js` and include the following: + +```js +const add = require('../add'); +QUnit.module('add', function() { + QUnit.test('should add two numbers', function(assert) { + assert.equal(add(1, 1), 2, '1 + 1 = 2'); + }); +}); +``` + +This defines a test module for the function and then a single test that verifies the result of adding two numbers together. + +To run the test, we'll want to add a script to your `package.json` so that you don't need to install QUnit globally (though you can if you prefer): + +```json +{ + "scripts": { + "test": "qunit" + } +} +``` + +Then, you can run: + +```bash +npm run test +``` + +And QUnit will print out: + +```bash +TAP version 13 +ok 1 add > should add two numbers +1..1 +# pass 1 +# skip 0 +# todo 0 +# fail 0 +``` + +Congrats! You just wrote and executed your first QUnit test! + +Next, you should try writing a test for some of your own code and then check out the [API documentation](https://api.qunitjs.com) or run `qunit --help` to discover more of QUnit's features. + +### Support Policy + +QUnit follows the Node Long-term Support (LTS) Schedule and provides support for Current, Active LTS, and Maintenance LTS releases. + +### Package Name Prior to 2.4.1 + +Prior to version 2.4.1, QUnit was published under the package name `qunitjs` on NPM. If you wish to install an older version of QUnit on Node, you will want to use the `qunitjs` package. The `qunit` package prior to version 2.4.1 is an alternative CLI that is now published as `node-qunit`. + +--- + +## In the Browser + +When getting started with QUnit in the browser, you have a couple options. You can install files locally from: + +* npm: `npm install --save-dev qunit`, +* yarn: `yarn add --dev qunit`, or +* bower: `bower install --save-dev qunit` + +Or, you can load the files from the [jQuery CDN](https://code.jquery.com/qunit/) which is hosted by [MaxCDN](https://www.maxcdn.com/). Since it's simpler, we'll load the files from the CDN. + +Start by creating a new HTML file called `tests.html` and include the following markup: + +```html + + + + + + Test Suite + + + +
+
+ + + +``` + +That's all the markup you need to start writing tests. Let's add a test for a hypothetical `add` function that adds two numbers together: + +```html + +``` + +This code defines a test module for the `add` function and then a single test verifying the result of adding two numbers. + +If you open this up in the browser you'll see the following: + + + +A detailed report of the tests that run and their assertions, as well as a bunch of options for filtering and re-running the tests. + +Congrats! You just wrote and executed your first QUnit test! + +Next, you should try writing a test for some of your own code and then check out the [API documentation](https://api.qunitjs.com) to discover more of QUnit's features. + +### Support Policy + +QUnit currently supports the same browsers as jQuery 3.x. For legacy browser support, including Internet Explorer versions lower than IE9, please use the 1.x series of QUnit. + +--- + +## Further Reading + +* [Introdution to JavaScript Unit Testing](https://coding.smashingmagazine.com/2012/06/introduction-to-javascript-unit-testing/) diff --git a/package.json b/package.json deleted file mode 100644 index de741d3..0000000 --- a/package.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "name": "qunitjs.com", - "title": "QUnit Website", - "description": "Website for QUnit", - "version": "2.10.2", - "homepage": "https://qunitjs.com", - "author": { - "name": "jQuery Foundation and other contributors" - }, - "repository": { - "type": "git", - "url": "git://github.com/qunitjs/qunitjs.com.git" - }, - "bugs": { - "url": "https://github.com/qunitjs/qunitjs.com/issues" - }, - "licenses": [ - { - "type": "MIT", - "url": "https://github.com/qunitjs/qunitjs.com/blob/master/LICENSE.txt" - } - ], - "dependencies": { - "grunt": "1.1.0", - "grunt-jquery-content": "3.0.3" - }, - "devDependencies": { - "grunt-cli": "1.3.2" - } -} diff --git a/pages/about.md b/pages/about.md deleted file mode 100644 index 1299dad..0000000 --- a/pages/about.md +++ /dev/null @@ -1,56 +0,0 @@ - - -QUnit is a powerful, easy-to-use JavaScript unit testing framework. It's used by jQuery, jQuery UI, jQuery Mobile and many other projects and is capable of testing any generic JavaScript code, including itself! - -## Vision and Goals - -### Simple setup - -Setting up the first unit test with QUnit should be super simple, requiring as little setup as possible. - -### Immediate, detailed feedback - -When a test or assertion fails, QUnit should provide feedback to the developer as fast as possible, with enough detail to quickly figure out the underlying issue. And doing so without interrupting the test, as the other assertions still run. - -### Also... - -* Performance: QUnit is not only easy, but fast. -* Opinionated and lean API, but extensible. -* Compatible: It works on many different environments. - -## QUnit Team - -There's a lot of work that goes into making QUnit. Between API design, implementation, ticket triage, bug fixing, developer relations, infrastructure, and everything else, most of the work is done by volunteers. We'd like to recognize the most prominent contributors below, for a full list of all contributors, see the [authors list](https://github.com/qunitjs/qunit/blob/master/AUTHORS.txt). - -### [Trent Willis](http://pretty-okay.com) - Project Lead - -Trent is a software engineer at LinkedIn based out of Sunnyvale, CA. He has been contributing to QUnit since 2015 and actively works on a number of test-related projects in the Ember.js ecosystem. - -### [Leo Balter](https://twitter.com/leobalter) - -Leo is a software engineer at Bocoup based in Boston, MA. Leo contributes to QUnit since 2013 and was a project lead from 2015 to early-2017. He represents the JSFoundation at TC39, the technical committee that designs the language specification for JS, and maintains the official spec tests at [test262](http://github.com/tc39/test262/). - -### [Richard Gibson](https://twitter.com/gibson042) - -Richard is an architect at [Dyn](http://dyn.com/) in New Hampshire, USA. He has been contributing to jQuery Foundation projects since 2011 (QUnit since 2012), and can be spotted on a large handful of open source repositories. - -### [Jörn Zaefferer](http://bassistance.de/) - -Jörn is a freelance web developer, consultant and trainer, residing in Cologne, Germany. Jörn evolved jQuery’s testsuite into QUnit and was project lead until mid-2015. He created and maintains a number of popular plugins. As a jQuery UI development lead, he focuses on the development of new plugins, widgets and utilities. - -### [Timo Tijhof](https://timotijhof.net/) - -Timo is a senior engineer at [Wikimedia Foundation](https://www.wikimedia.org/) where he is on the [Architecture Committee](https://www.mediawiki.org/wiki/Architecture_committee), the technical committee that governs the integrity and stability of Wikimedia software projects. He has been contributing to jQuery Foundation projects since 2011, and joined the QUnit Team in 2012. - -### [Kevin Partington](https://github.com/platinumazure) - -Kevin is a software engineer based out of Minnesota, USA. He has contributed to QUnit since 2015. He is also heavily involved in the ESLint project and actively maintains an [ESLint plugin](https://github.com/platinumazure/eslint-plugin-qunit) for linting QUnit tests. - -## Contributors (Past & Present) - -### [James M. Greene](http://greene.io/) -### [John Resig](http://ejohn.org/) -### [Scott González](http://nemikor.com/) diff --git a/pages/cookbook.html b/pages/cookbook.html deleted file mode 100644 index 696dffe..0000000 --- a/pages/cookbook.html +++ /dev/null @@ -1,477 +0,0 @@ - - - - -
-

Introduction

-

- Automated testing of software is an essential tool in development. Unit tests are the basic building blocks for automated tests: each component, the unit, of software is accompanied by a test that can be run by a test runner over and over again without any human interaction. In other words, you can write a test once and run it as often as necessary without any additional cost. -

-

- In addition to the benefits of good test coverage, testing can also drive the design of software, known as test-driven design, where a test is written before an implementation. You start writing a very simple test, verify that it fails (because the code to be tested doesn't exist yet), and then write the necessary implementation until the test passes. Once that happens, you extend the test to cover more of the desired functionality and implement again. By repeating those steps, the resulting code looks usually much different from what you'd get by starting with the implementation. -

-

- Unit testing in JavaScript isn't much different from in other programming languages. You need a small framework that provides a test runner, as well as some utilities to write the actual tests. -

- -

Automating Unit Testing

- -

Problem

-

- You want to automate testing your applications and frameworks, maybe even benefit from test-driven design. Writing your own testing framework may be tempting, but it involves a lot of work to cover all the details and special requirements of testing JavaScript code in various browsers. -

- -

Solution

-

- While there are other unit testing frameworks for JavaScript, you've decided to check out QUnit. QUnit is jQuery's unit test framework and is used by a wide variety of projects. -

-

- To use QUnit, you only need to include two QUnit files on your HTML page. QUnit consists of qunit.js, the test runner and testing framework, and qunit.css, which styles the test suite page to display test results: -

-
@partial(resources/example-cookbook-1-basics.html)
-

Opening this file in a browser gives the result shown below:

-

- -

-

- The only markup necessary in the <body> element is a <div> with id="qunit-fixture". This is required for all QUnit tests, even when the element itself is empty. This provides the fixture for tests, which will be explained in the section called "Keeping Tests Atomic". -

-

- The interesting part is the <script> element following the qunit.js include. It consists of a call to the test function, with two arguments: the name of the test as a string, which is later used to display the test results, and a function. The function contains the actual testing code, which involves one or more assertions. The example uses one assertion, equal(), which is explained in detail in Asserting Results. -

-

- Note that there is no document-ready block. The test runner handles that: calling QUnit.test() just adds the test to a queue, and its execution is deferred and controlled by the test runner. -

- - -

Discussion

-

- The header of the test suite displays the page title, a green bar when all tests passed (a red bar when at least one test failed), a bar with a few checkboxes to filter test results and a blue bar with the navigator.userAgent string (handy for screenshots of test results in different browsers). -

-

- Of the checkboxes, "Hide passed tests" is useful when a lot of tests ran and only a few failed. Checking the checkbox will hide everything that passed, making it easier to focus on the tests that failed (see also the Efficient Development section below). -

-

- Checking "Check for Globals" causes QUnit to make a list of all properties on the window object, before and after each test, then checking for differences. If properties are added or removed, the test will fail, listing the difference. This helps to make sure your test code and code under test doesn't accidentally export any global properties. -

-

- The "No try-catch" checkbox tells QUnit to run your test outside of a try-catch block. When your test throws an exception, the testrunner will die, unable to continue running, but you'll get a "native" exception, which can help tremendously debugging old browsers with bad debugging support like Internet Explorer 6 (JavaScript sucks at rethrowing exceptions). -

-

- Below the header is a summary, showing the total time it took to run all tests as well as the overall number of total and failed assertions. While tests are still running, it will show which test is currently being executed. -

-

- The actual contents of the page are the test results. Each entry in the numbered list starts with the name of the test followed by, in parentheses, the number of failed, passed, and total assertions. Clicking the entry will show the results of each assertion, usually with details about expected and actual results. The "Rerun" link at the end will run that test on its own. -

- -

Asserting Results

- -

Problem

-

- Essential elements of any unit test are assertions. The author of the test needs to express the results expected and have the unit testing framework compare them to the actual values that an implementation produces. -

- -

Solution

-

- QUnit provides a number of built-in assertions. Let's look at three of those: -

- -

ok( truthy [, message ] )

-

- The most basic one is ok(), which requires just one argument. If the argument evaluates to true, the assertion passes; otherwise, it fails. In addition, it accepts a string to display as a message in the test results: -

-

-QUnit.test( "ok test", function( assert ) {
-	assert.ok( true, "true succeeds" );
-	assert.ok( "non-empty", "non-empty string succeeds" );
-
-	assert.ok( false, "false fails" );
-	assert.ok( 0, "0 fails" );
-	assert.ok( NaN, "NaN fails" );
-	assert.ok( "", "empty string fails" );
-	assert.ok( null, "null fails" );
-	assert.ok( undefined, "undefined fails" );
-});
- -

equal( actual, expected [, message ] )

-

- The equal assertion uses the simple comparison operator (==) to compare the actual and expected arguments. When they are equal, the assertion passes; otherwise, it fails. When it fails, both actual and expected values are displayed in the test result, in addition to a given message: -

- -

-QUnit.test( "equal test", function( assert ) {
-	assert.equal( 0, 0, "Zero, Zero; equal succeeds" );
-	assert.equal( "", 0, "Empty, Zero; equal succeeds" );
-	assert.equal( "", "", "Empty, Empty; equal succeeds" );
-	assert.equal( 0, false, "Zero, false; equal succeeds" );
-
-	assert.equal( "three", 3, "Three, 3; equal fails" );
-	assert.equal( null, false, "null, false; equal fails" );
-});
-
-

- Compared to ok(), equal() makes it much easier to debug tests that failed, because it's obvious which value caused the test to fail. -

-

- When you need a strict comparison (===), use strictEqual() instead. -

- -

deepEqual( actual, expected [, message ] )

-

- The deepEqual() assertion can be used just like equal() and is a better choice in most cases. Instead of the simple comparison operator (==), it uses the more accurate comparison operator (===). That way, - undefined doesn't equal null, 0, or the empty string (""). It also compares the content of objects so that {key: value} is equal to {key: value}, even when comparing two objects with distinct identities.

deepEqual() also handles NaN, dates, regular expressions, arrays, and functions, while equal() would just check the object identity: -

-

-QUnit.test( "deepEqual test", function( assert ) {
-	var obj = { foo: "bar" };
-
-	assert.deepEqual( obj, { foo: "bar" }, "Two objects can be the same in value" );
-});
-
-

- In case you want to explicitly not compare the content of two values, equal() can still be used. In general, deepEqual() is the better choice. -

- - -

Synchronous Callbacks

- -

Problem

-

- Occasionally, callback assertions in the code might not be called at all, causing the test to fail silently. -

-

Solution

-

- QUnit provides a special assertion to define the number of assertions a test contains. If the test completes without the expected number of assertions, it will fail. -

-

- In order to indicate the expected number of assertion, call assert.expect() at the start of a test, with the number of expected assertions as the only argument: -

-

-QUnit.test( "a test", function( assert ) {
-	assert.expect( 2 );
-
-	function calc( x, operation ) {
-		return operation( x );
-	}
-
-	var result = calc( 2, function( x ) {
-		assert.ok( true, "calc() calls operation function" );
-		return x * x;
-	});
-
-	assert.equal( result, 4, "2 square equals 4" );
-});
-
- -

- Practical Example: -

- -

-QUnit.test( "a test", function( assert ) {
-	assert.expect( 1 );
-
-	var $body = $( "body" );
-
-	$body.on( "click", function() {
-		assert.ok( true, "body was clicked!" );
-	});
-
-	$body.trigger( "click" );
-});
-
- -

Discussion

-

- assert.expect() provides the most value when actually testing callbacks. When all code is running in the scope of the test function, - assert.expect() provides no additional value—any error preventing assertions to run would cause the test to fail anyway, because the test runner catches the error and fails the unit. -

- -

Asynchronous Callbacks

- -

Problem

-

- While assert.expect() is useful to test synchronous callbacks (see the section called "Synchronous Callbacks"), it can fall short for asynchronous callbacks. Asynchronous callbacks conflict with the way the test runner queues and executes tests. When code under test starts a timeout or interval or an AJAX request, the test runner will just continue running the rest of the test, as well as other tests following it, instead of waiting for the result of the asynchronous operation. -

-

Solution

-

- For every asynchronous operation in your QUnit.test() callback, use assert.async(), which returns a "done" function that should be called when the operation has completed. -

-

-QUnit.test( "asynchronous test: async input focus", function( assert ) {
-	var done = assert.async();
-	var input = $( "#test-input" ).focus();
-	setTimeout(function() {
-		assert.equal( document.activeElement, input[0], "Input was focused" );
-		done();
-	});
-});
-
- -

Testing User Actions

- -

Problem

-

- Code that relies on actions initiated by the user can't be tested by just calling a function. Usually an anonymous function is bound to an element's event, e.g., a click, which has to be simulated. -

-

Solution

-

- You can trigger the event using jQuery's trigger() method and test that the expected behavior occurred. If you don't want the native browser events to be triggered, you can use triggerHandler() to just execute the bound event handlers. This is useful when testing a click event on a link, where trigger() would cause the browser to change the location, which is hardly desired behavior in a test. -

-

- Let's assume we have a simple key logger that we want to test: -

- -

-function KeyLogger( target ) {
-	this.target = target;
-	this.log = [];
-
-	var that = this;
-	this.target.off( "keydown" ).on( "keydown", function( event ) {
-		that.log.push( event.keyCode );
-	});
-}
-
-

- We can manually trigger a keypress event to see whether the - logger is working: -

- -

-QUnit.test( "keylogger api behavior", function( assert ) {
-	var doc = $( document ),
-		keys = new KeyLogger( doc );
-
-	// Trigger the key event
-	doc.trigger( $.Event( "keydown", { keyCode: 9 } ) );
-
-	// Verify expected behavior
-	assert.deepEqual( keys.log, [ 9 ], "correct key was logged" );
-});
- -

Discussion

-

- If your event handler doesn't rely on any specific properties of the event, you can just call .trigger( eventType ). However, if your event handler does rely on specific properties of the event, you will need to create an event object using $.Event with the necessary properties, as shown previously.

It's also important to trigger all relevant events for complex behaviors such as dragging, which is comprised of mousedown, at least one mousemove, and a mouseup. Keep in mind that even some events that seem simple are actually compound; e.g., a click is really a mousedown, mouseup, and then click. Whether you actually need to trigger all three of these depends on the code under test. Triggering a click works for most cases. -

-

- If thats not enough, you have a few framework options that help simulating user events: -

-
    -
  • syn "is a synthetic event library that pretty much handles typing, clicking, moving, and dragging exactly how a real user would perform those actions". Used by FuncUnit, which is based on QUnit, for functional testing of web applications.
  • -
  • JSRobot - "A testing utility for web-apps that can generate real keystrokes rather than simply simulating the JavaScript event firing. This allows the keystroke to trigger the built-in browser behaviour which isn't otherwise possible."
  • -
  • DOH Robot "provides an API that enables testers to automate their UI tests using real, cross-platform, system-level input events". This gets you the closest to "real" browser events, but uses Java applets for that.
  • -
  • keyvent.js - "Keyboard events simulator."
  • -
- -

Keeping Tests Atomic

- -

Problem

-

- When tests are lumped together, it's possible to have tests that should pass but fail or tests that should fail but pass. This is a result of a test having invalid results because of side effects of a previous test: -

- -

-QUnit.test( "2 asserts", function( assert ) {
-	var fixture = $( "#qunit-fixture" );
-
-	fixture.append( "<div>hello!</div>" );
-	assert.equal( $( "div", fixture ).length, 1, "div added successfully!" );
-
-	fixture.append( "<span>hello!</span>" );
-	assert.equal( $( "span", fixture ).length, 1, "span added successfully!" );
-});
-
- -

- The first append() adds a <div> that the second equal() doesn't take into account. -

- -

Solution

-

- Use the QUnit.test() method to keep tests atomic, being careful to keep each assertion clean of any possible side effects. You should only rely on the fixture markup, inside the #qunit-fixture element. Modifying and relying on anything else can have side effects: -

-

-
-QUnit.test( "Appends a div", function( assert ) {
-	var fixture = $( "#qunit-fixture" );
-
-	fixture.append( "<div>hello!</div>" );
-	assert.equal( $( "div", fixture ).length, 1, "div added successfully!" );
-});
-
-QUnit.test( "Appends a span", function( assert ) {
-	var fixture = $( "#qunit-fixture" );
-
-	fixture.append("<span>hello!</span>" );
-	assert.equal( $( "span", fixture ).length, 1, "span added successfully!" );
-});
-
-

- QUnit will reset the elements inside the #qunit-fixture element after each test, removing any events that may have existed. As long as you use elements only within this fixture, you don't have to manually clean up after your tests to keep them atomic. -

- -

Discussion

-

- In addition to the #qunit-fixture fixture element and the filters explained in the section called "Efficient Development", QUnit also offers a ?noglobals flag. Consider the following test: -

-

-QUnit.test( "global pollution", function( assert ) {
-	window.pollute = true;
-	assert.ok( pollute, "nasty pollution" );
-});
-
-

- In a normal test run, this passes as a valid result. Running the ok() test with the noglobals flag will cause the test to fail, because QUnit detected that it polluted the window object. -

-

- There is no need to use this flag all the time, but it can be handy to detect global namespace pollution that may be problematic in combination with third-party libraries. And it helps to detect bugs in tests caused by side effects. -

- -

Grouping Tests

- -

Problem

-

- You've split up all of your tests to keep them atomic and free of side effects, but you want to keep them logically organized and be able to run a specific group of tests on their own. -

- -

Solution

-

- You can use the QUnit.module() function to group tests together: -

-

-QUnit.module( "group a" );
-QUnit.test( "a basic test example", function( assert ) {
-	assert.ok( true, "this test is fine" );
-});
-QUnit.test( "a basic test example 2", function( assert ) {
-	assert.ok( true, "this test is fine" );
-});
-
-QUnit.module( "group b" );
-QUnit.test( "a basic test example 3", function( assert ) {
-	assert.ok( true, "this test is fine" );
-});
-QUnit.test( "a basic test example 4", function( assert ) {
-	assert.ok( true, "this test is fine" );
-});
-

- All tests that occur after a call to QUnit.module() will be grouped into that module. The test names will all be preceded by the module name in the test results. You can then use that module name to select tests to run (see the section called "Efficient Development"). -

- -

Discussion

-

- In addition to grouping tests, QUnit.module() can be used to extract common code from tests within that module. The QUnit.module() function takes an optional second parameter to define functions to run before and after each test within the module: -

-

-QUnit.module( "module", {
-	beforeEach: function( assert ) {
-		assert.ok( true, "one extra assert per test" );
-	}, afterEach: function( assert ) {
-		assert.ok( true, "and one extra assert after each test" );
-	}
-});
-QUnit.test( "test with beforeEach and afterEach", function( assert ) {
-	assert.expect( 2 );
-});
-

- You can specify both beforeEach and afterEach properties together, or just one of them. -

-

- Calling QUnit.module() again without the additional argument will simply reset any beforeEach/afterEach functions defined by another module previously. -

- -

Custom Assertions

- -

Problem

-

- You have several tests that duplicate logic for asserting some expectation. This repetitive code lessens the readability of your tests and increases the surface for bugs. -

-

-QUnit.test( "retrieving object keys", function( assert ) {
-	var objectKeys = keys( { a: 1, b: 2 } );
-	assert.ok( objectKeys.indexOf("a") > -1, "Object keys" );
-	assert.ok( objectKeys.indexOf("b") > -1, "Object keys" );
-
-	var arrayKeys = keys( [1, 2] );
-	assert.ok( arrayKeys.indexOf("1") > -1, "Array keys" );
-	assert.ok( arrayKeys.indexOf("2") > -1, "Array keys" );
-});
-
- -

Solution

-

- Define a function to encapsulate the expectation in a reusable unit. Invoke this.pushResult within the body to notify QUnit that an assertion has taken place. -

-

-QUnit.assert.contains = function( needle, haystack, message ) {
-	var actual = haystack.indexOf(needle) > -1;
-	this.pushResult({
-		result: actual,
-		actual,
-		expected: needle,
-		message
-	});
-};
-QUnit.test("retrieving object keys", function( assert ) {
-	var objectKeys = keys( { a: 1, b: 2 } );
-	assert.contains( "a", objectKeys, "Object keys" );
-	assert.contains( "b", objectKeys, "Object keys" );
-
-	var arrayKeys = keys( [1, 2] );
-	assert.contains( "1", arrayKeys, "Array keys" );
-	assert.contains( "2", arrayKeys, "Array keys" );
-});
-
- -

Discussion

-

- Custom assertions can help make test suites more readable and more maintainable. These are simply functions that invoke this.pushResult with an object containing the assertion data; this is how QUnit detects that an assertion has taken place and tracks the result of that assertion. -

-

- It is a good practice to define this function as a method on the global QUnit.assert object. This helps communicate the purpose of the function to other developers. You may accomplish this by directly assigning a new property on the object (i.e. QUnit.assert.myAssertion = myAssertion;) or using QUnit.extend (i.e. QUnit.extend(QUnit.assert, { myAssertion: myAssertion });). -

- -

Efficient Development

- -

Problem

-

- Once your testsuite takes longer than a few seconds to run, you want to avoid wasting a lot of time just waiting for test results to come in. -

-

Solution

-

- QUnit has a bunch of features built in to make up for that. The most interesting ones require just a single click to activate. Toggle the "Hide passed tests" checkbox at the top, and QUnit will only show you tests that failed. That alone doesn't make a difference in speed, but already helps focusing on failing tests. -

-

- It gets more interesting if you take another QUnit feature into account, which is enabled by default and usually not noticable. Whenever a test fails, QUnit stores the name of that test in sessionStorage. The next time you run a testsuite, that failing test will run before all other tests. The output order isn't affected, only the execution order. In combination with the "Hide passed tests" checkbox you will then get to see the failing test, if it still fails, at the top, as soon as possible. -

- -

Discussion

-

- The automatic reordering happens by default. It implies that your tests need to be atomic, as discussed previously. If your tests aren't, you'll see random non-deterministic errors. Fixing that is usually the right approach. If you're really desperate, you can set QUnit.config.reorder = false. -

-

- In addition to the automatic reordering, there are a few manual options available. You can rerun any test by clicking the "Rerun" link next to that test. That will add a "testNumber=N" parameter to the query string, where "N" is the number of the test you clicked. You can then reload the page to keep running just that test, or use the browser's back button to go back to running all tests. -

-

- Running all tests within a module works pretty much the same way, except that you choose the module to run using the select at the top right. It'll set a "module=N" query string, where "N" is the encoded name of the module, for example "?module=testEnvironment%20with%20object". -

-

- *This page was first published, under a non-exclusive license, as a chapter in the jQuery Cookbook, authored by Scott González and Jörn Zaefferer. As QUnit changed since the book was printed, this version is more up-to-date. -

-
diff --git a/pages/index.html b/pages/index.html deleted file mode 100644 index 87cd573..0000000 --- a/pages/index.html +++ /dev/null @@ -1,115 +0,0 @@ - - -
-

What is QUnit?

-

- QUnit is a powerful, easy-to-use JavaScript unit testing framework. It's used by the jQuery, jQuery UI and jQuery Mobile projects and is capable of testing any generic JavaScript code, including itself! -

- -

Getting Started

- -

In The Browser

- -

- A minimal QUnit test setup: -

-
@partial(resources/example-index.html)
- -

- The contents of tests.js: -

-

-QUnit.test( "hello test", function( assert ) {
-	assert.ok( 1 == "1", "Passed!" );
-});
-
- -

- The result: -

- - -
- -

In Node

- -

Install QUnit globally so you can use the CLI:

-
$ npm install -g qunit
- -

Create test files in a test directory and then simply run:

-
$ qunit
- -

And you should see some output like:

-
TAP version 13
-ok 1 Module > Test #1
-ok 2 Module > Test #2
-1..2
-# pass 2
-# skip 0
-# todo 0
-# fail 0
- -

And that is it! While QUnit defaults to looking for test files in test, you can also put them anywhere and then specify file paths or glob expressions:

-
$ qunit 'tests/*-test.js'
- -

To view the additional supported options, simply run:

-
$ qunit --help
-
- -
-
-

Download

-

QUnit is available from the jQuery CDN hosted by MaxCDN. - -

Current Release - v2.10.1

- -

To test the latest features and bug fixes to QUnit, a version automatically generated from the latest commit to the QUnit Git repository is also available for use.

- - -

Package Name Prior to 2.4.1

-

Prior to version 2.4.1, QUnit was published under the name qunitjs on NPM. If you wish to install an older version of QUnit on Node, you will want to use the qunitjs package. The qunit package prior to 2.4.1 is an alternative CLI that is now published as node-qunit.

-
- -

Browser Support

-

QUnit currently supports the same browsers as jQuery 3.x.

-

For legacy browser support, including Internet Explorer versions lower than IE9, please use the 1.x series of QUnit.

- -

Node Support

-

QUnit follows the Node Long-term Support (LTS) Schedule. Support is provided for Current, Active, and Maintenance releases.

- -

Learn More

- - -

Get Involved

- - -

History

-

- QUnit was originally developed by John Resig as part of jQuery. In 2008 it got its own home, name and API documentation, allowing others to use it for their unit testing as well. At the time it still depended on jQuery. A rewrite in 2009 fixed that, and now QUnit runs completely standalone. -

-

- QUnit's assertion methods follow the CommonJS Unit Testing specification, which was to some degree influenced by QUnit. -

-
diff --git a/pages/intro.html b/pages/intro.html deleted file mode 100644 index 4b45ebf..0000000 --- a/pages/intro.html +++ /dev/null @@ -1,194 +0,0 @@ - - - - -
-

You probably know that testing is good, but the first hurdle to overcome when trying to write unit tests for client-side code is the lack of any actual units; JavaScript code is written for each page of a website or each module of an application and is closely intermixed with back-end logic and related HTML. In the worst case, the code is completely mixed with HTML, as inline events handlers.

- -

This is likely the case when no JavaScript library for some DOM abstraction is being used; writing inline event handlers is much easier than using the DOM APIs to bind those events. More and more developers are picking up a library such as jQuery to handle the DOM abstraction, allowing them to move those inline events to distinct scripts, either on the same page or even in a separate JavaScript file. However, putting the code into separate files doesn’t mean that it is ready to be tested as a unit.

- -

What is a unit anyway? In the best case, it is a pure function that you can deal with in some way — a function that always gives you the same result for a given input. This makes unit testing pretty easy, but most of the time you need to deal with side effects, which here means DOM manipulations. It’s still useful to figure out which units we can structure our code into and to build unit tests accordingly.

- -

Building Unit Tests

- -

With that in mind, we can obviously say that starting with unit testing is much easier when starting something from scratch. But that’s not what this article is about. This article is to help you with the harder problem: extracting existing code and testing the important parts, potentially uncovering and fixing bugs in the code.

- -

The process of extracting code and putting it into a different form, without modifying its current behavior, is called refactoring. Refactoring is an excellent method of improving the code design of a program; and because any change could actually modify the behaviour of the program, it is safest to do when unit tests are in place.

- -

This chicken-and-egg problem means that to add tests to existing code, you have to take the risk of breaking things. So, until you have solid coverage with unit tests, you need to continue manually testing to minimize that risk.

- -

That should be enough theory for now. Let’s look at a practical example, testing some JavaScript code that is currently mixed in with and connected to a page. The code looks for links with title attributes, using those titles to display when something was posted, as a relative time value, like “5 days ago”:

- -
@partial(resources/intro/0-ugly.html)
- -

If you ran that example, you’d see a problem: none of the dates get replaced. The code works, though. It loops through all anchors on the page and checks for a title property on each. If there is one, it passes it to the prettyDate function. If prettyDate returns a result, it updates the innerHTML of the link with the result.

- -

Make Things Testable

- -

The problem is that for any date older then 31 days, prettyDate just returns undefined (implicitly, with a single return statement), leaving the text of the anchor as is. So, to see what’s supposed to happen, we can hardcode a “current” date:

- -
@partial(resources/intro/1-mangled.html)
- - - -

Now, the links should say “2 hours ago,” “Yesterday” and so on. That’s something, but still not an actual testable unit. So, without changing the code further, all we can do is try to test the resulting DOM changes. Even if that did work, any small change to the markup would likely break the test, resulting in a really bad cost-benefit ratio for a test like that.

- -

Refactoring, Stage 0

- -

Instead, let’s refactor the code just enough to have something that we can unit test.

- -

We need to make two changes for this to happen: pass the current date to the prettyDate function as an argument, instead of having it just use new Date, and extract the function to a separate file so that we can include the code on a separate page for unit tests.

- -
@partial(resources/intro/2-getting-somewhere.html)
- -

Here’s the contents of prettydate.js:

- -
@partial(resources/intro/prettydate.js)
- - - -

Now that we have something to test, let’s write some actual unit tests:

- -
@partial(resources/intro/3-first-test.html)
- -
    -
  • Run this example. (Make sure to enable a console such as Firebug or Chrome’s Web Inspector.)
  • -
- -

This will create an ad-hoc testing framework, using only the console for output. It has no dependencies to the DOM at all, so you could just as well run it in a non-browser JavaScript environment, such as Node.js or Rhino, by extracting the code in the script tag to its own file.

- -

If a test fails, it will output the expected and actual result for that test. In the end, it will output a test summary with the total, failed and passed number of tests.

- -

If all tests have passed, like they should here, you would see the following in the console:

- -
-

Of 6 tests, 0 failed, 6 passed.

-
- -

To see what a failed assertion looks like, we can change something to break it:

- -
-

Expected 2 day ago, but was 2 days ago.

- -

Of 6 tests, 1 failed, 5 passed.

-
- -

While this ad-hoc approach is interesting as a proof of concept (you really can write a test runner in just a few lines of code), it’s much more practical to use an existing unit testing framework that provides better output and more infrastructure for writing and organizing tests.

- -

The QUnit JavaScript Test Suite

- -

The choice of framework is mostly a matter of taste. For the rest of this article, we’ll use QUnit (pronounced “q-unit”), because its style of describing tests is close to that of our ad-hoc test framework.

- -
@partial(resources/intro/4-qunit-test.html)
- - - -

Three sections are worth a closer look here. Along with the usual HTML boilerplate, we have three included files: two files for QUnit (qunit.css and qunit.js) and the previous prettydate.js.

- -

Then, there’s another script block with the actual tests. The test method is called once, passing a string as the first argument (naming the test) and passing a function as the second argument (which will run the actual code for this test). This code then defines the now variable, which gets reused below, then calls the equal method a few times with varying arguments. The equal method is one of several assertions that QUnit provides through the first parameter in the callback function of the test block. The first argument is the result of a call to prettyDate, with the now variable as the first argument and a date string as the second. The second argument to equal is the expected result. If the two arguments to equal are the same value, then the assertion will pass; otherwise, it will fail.

- -

Finally, in the body element is some QUnit-specific markup. These elements are optional. If present, QUnit will use them to output the test results.

- -

The result is this:

- -

- -

With a failed test, the result would look something like this:

- -

- -

Because the test contains a failing assertion, QUnit doesn’t collapse the results for that test, and we can see immediately what went wrong. Along with the output of the expected and actual values, we get a diff between the two, which can be useful for comparing larger strings. Here, it’s pretty obvious what went wrong.

- -

Refactoring, Stage 1

- -

The assertions are currently somewhat incomplete because we aren’t yet testing the n weeks ago variant. Before adding it, we should consider refactoring the test code. Currently, we are calling prettyDate for each assertion and passing the now argument. We could easily refactor this into a custom assertion method:

- -

-	QUnit.test("prettydate basics", function( assert ) {
-		function date(then, expected) {
-			assert.equal(prettyDate("2008/01/28 22:25:00", then), expected);
-		}
-		date("2008/01/28 22:24:30", "just now");
-		date("2008/01/28 22:23:30", "1 minute ago");
-		date("2008/01/28 21:23:30", "1 hour ago");
-		date("2008/01/27 22:23:30", "Yesterday");
-		date("2008/01/26 22:23:30", "2 days ago");
-		date("2007/01/26 22:23:30", undefined);
-	});
-	
- - - -

Here we’ve extracted the call to prettyDate into the date function, inlining the now variable into the function. We end up with just the relevant data for each assertion, making it easier to read, while the underlying abstraction remains pretty obvious.

- -

Testing The DOM manipulation

- -

Now that the prettyDate function is tested well enough, let’s shift our focus back to the initial example. Along with the prettyDate function, it also selected some DOM elements and updated them, within the window load event handler. Applying the same principles as before, we should be able to refactor that code and test it. In addition, we’ll introduce a module for these two functions, to avoid cluttering the global namespace and to be able to give these individual functions more meaningful names.

- -
@partial(resources/intro/6-qunit-dom.html)
- -

Here’s the contents of prettydate2.js:

- -
@partial(resources/intro/prettydate2.js)
- - - -

The new prettyDate.update function is an extract of the initial example, but with the now argument to pass through to prettyDate.format. The QUnit-based test for that function starts by selecting all a elements within the #qunit-fixture element. In the updated markup in the body element, the <div id="qunit-fixture">…</div> is new. It contains an extract of the markup from our initial example, enough to write useful tests against. By putting it in the #qunit-fixture element, we don’t have to worry about DOM changes from one test affecting other tests, because QUnit will automatically reset the markup after each test.

- -

Let’s look at the first test for prettyDate.update. After selecting those anchors, two assertions verify that these have their initial text values. Afterwards, prettyDate.update is called, passing along a fixed date (the same as in previous tests). Afterwards, two more assertions are run, now verifying that the innerHTML property of these elements have the correctly formatted date, “2 hours ago” and “Yesterday.”

- -

Refactoring, Stage 2

- -

The next test, prettyDate.update, one day later, does nearly the same thing, except that it passes a different date to prettyDate.update and, therefore, expects different results for the two links. Let’s see if we can refactor these tests to remove the duplication.

- -
@partial(resources/intro/7-qunit-dom-refactored.html)
- - - -

Here we have a new function called domtest, which encapsulates the logic of the two previous calls to test, introducing arguments for the test name, the date string and the two expected strings. It then gets called twice.

- -

Back To The Start

- -

With that in place, let’s go back to our initial example and see what that looks like now, after the refactoring.

- -
@partial(resources/intro/8-endstate.html)
- - - -

For a non-static example, we’d remove the argument to prettyDate.update. All in all, the refactoring is a huge improvement over the first example. And thanks to the prettyDate module that we introduced, we can add even more functionality without clobbering the global namespace.

- -

Conclusion

- -

Testing JavaScript code is not just a matter of using some test runner and writing a few tests; it usually requires some heavy structural changes when applied to code that has been tested only manually before. We’ve walked through an example of how to change the code structure of an existing module to run some tests using an ad-hoc testing framework, then replacing that with a more full-featured framework to get useful visual results.

- -

QUnit itself has a lot more to offer, with specific support for testing asynchronous code such as timeouts, AJAX and events. Its visual test runner helps to debug code by making it easy to rerun specific tests and by providing stack traces for failed assertions and caught exceptions. For further reading, check out the QUnit Cookbook.

-

Originally published on Smashing Magazine, June 2012

-
diff --git a/pages/plugins.html b/pages/plugins.html deleted file mode 100644 index f1903dd..0000000 --- a/pages/plugins.html +++ /dev/null @@ -1,71 +0,0 @@ - - -

API Extensions

-

These plugins provide custom assertions or a complete new interface to use QUnit.

-
    -
  • SpecIt A QUnit-based API for bdd-style testing
  • -
  • Pavlov - extends QUnit with a rich, higher-level, Behavioral API -
  • Parameterize - A QUnit plugin For Running Parameterized Tests
  • -
  • HTML - A QUnit plugin for assertion methods to compare two HTML strings for equality after a rigorous normalization process.
  • -
  • Step - A QUnit plugin for asserting the proper sequence in which the code should execute.
  • -
  • Close - A QUnit plugin for asserting that a number is approximately equal (or not) to an expected number, within a given tolerance.
  • -
  • Canvas - A QUnit plugin for asserting individual pixel values within a Canvas element.
  • -
  • qunit-promises - A QUnit plugin for asserting results of promises.
  • -
  • qunit-once - A QUnit plugin that adds setupOnce and teardownOnce to module's config.
  • -
  • qunit-inject - A QUnit plugin that allows dependency injection from a module's - config object into individual unit tests
  • -
  • qunit-helpful - A QUnit plugin that shows the failed expression in assertion message.
  • -
  • Classes - A QUnit plugin for asserting if an element has, or lacks, any number of classes.
  • -
  • Compare - A QUnit plugin for asserting numerical comparisons like greater than, less than, etc.
  • -
  • qunit-fixture - A QUnit plugin that easy use fixture.
  • -
  • qunit-dom - A QUnit plugin providing assertions on DOM elements.
  • -
- -

Mocking Tools

-

These libraries can be used along with QUnit to mock Ajax requests, timers and more.

- - -

Integration

-

These plugins make it easier to integrate QUnit in various testing setups.

- - -

Themes

-

Alternative themes for the HTML Reporter. Load their CSS file instead of the default one.

- - - diff --git a/plugins.md b/plugins.md new file mode 100644 index 0000000..413093b --- /dev/null +++ b/plugins.md @@ -0,0 +1,17 @@ +--- +layout: page +title: Plugins +--- + +

The following plugins provide a myriad of ways to modify, extend, and enhance QUnit itself as well as the developer experience of using QUnit.

+ +
    + {% for plugin in site.data.plugins %} +
  • +

    {{ plugin.name }}

    +

    {{ plugin.description }}

    +
  • + {% endfor %} +
+ +_Note: This list is automatically generated from npm packages using the [**qunit-plugin** keyword](https://www.npmjs.com/search?q=keywords:qunit-plugin) and is updated when the website is deployed._ diff --git a/redirects.json b/redirects.json deleted file mode 100644 index 65dbf6f..0000000 --- a/redirects.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "/addons/": "/plugins/" -} diff --git a/resources/example-cookbook-1-basics.html b/resources/example-add.html similarity index 54% rename from resources/example-cookbook-1-basics.html rename to resources/example-add.html index d3e3d85..b17b5bc 100644 --- a/resources/example-cookbook-1-basics.html +++ b/resources/example-add.html @@ -2,7 +2,8 @@ - QUnit basic example + + QUnit Example @@ -10,9 +11,11 @@
diff --git a/resources/example-cookbook-noglobals.html b/resources/example-cookbook-noglobals.html deleted file mode 100644 index 7c273c6..0000000 --- a/resources/example-cookbook-noglobals.html +++ /dev/null @@ -1,19 +0,0 @@ - - - - - QUnit Cookbook - Keeping Tests Atomic, testing for globals - - - -
-
- - - - diff --git a/resources/example-index.html b/resources/example-index.html index 38928ea..2784d89 100644 --- a/resources/example-index.html +++ b/resources/example-index.html @@ -1,15 +1,22 @@ - - - - QUnit Example - - - -
-
- - + + + + Test Suite + + + +
+
+ + diff --git a/resources/intro/0-ugly.html b/resources/intro/0-ugly.html deleted file mode 100644 index 05a4c00..0000000 --- a/resources/intro/0-ugly.html +++ /dev/null @@ -1,59 +0,0 @@ - - - - - Mangled date examples - - - - - - - - \ No newline at end of file diff --git a/resources/intro/1-mangled.html b/resources/intro/1-mangled.html deleted file mode 100644 index 371a79a..0000000 --- a/resources/intro/1-mangled.html +++ /dev/null @@ -1,139 +0,0 @@ - - - - - Mangled date examples - - - - - - - - \ No newline at end of file diff --git a/resources/intro/2-getting-somewhere.html b/resources/intro/2-getting-somewhere.html deleted file mode 100644 index aa56ea6..0000000 --- a/resources/intro/2-getting-somewhere.html +++ /dev/null @@ -1,119 +0,0 @@ - - - - - Refactored date examples - - - - - - - - - \ No newline at end of file diff --git a/resources/intro/3-first-test.html b/resources/intro/3-first-test.html deleted file mode 100644 index f923900..0000000 --- a/resources/intro/3-first-test.html +++ /dev/null @@ -1,35 +0,0 @@ - - - - - Refactored date examples - - - - - - - \ No newline at end of file diff --git a/resources/intro/4-qunit-test.html b/resources/intro/4-qunit-test.html deleted file mode 100644 index 4229f9f..0000000 --- a/resources/intro/4-qunit-test.html +++ /dev/null @@ -1,28 +0,0 @@ - - - - - Refactored date examples - - - - - - - - - -
- - - diff --git a/resources/intro/4a-green.png b/resources/intro/4a-green.png deleted file mode 100644 index 754d75bfa63233140c72eec18e94309756483bf2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 28231 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 Zw7f|xARwE79}ovfiz|p#eKz{`e*ogATonKS diff --git a/resources/intro/4b-qunit-test.html b/resources/intro/4b-qunit-test.html deleted file mode 100644 index 97e1e8b..0000000 --- a/resources/intro/4b-qunit-test.html +++ /dev/null @@ -1,28 +0,0 @@ - - - - - Refactored date examples - - - - - - - - - -
- - - diff --git a/resources/intro/4b-red.png b/resources/intro/4b-red.png deleted file mode 100644 index ce55294bcf8bef95c6449a296027ef4aa4b537c4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 50633 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�X0cm71o7csLe&? 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%0D7~~Or5m4OjVWrA0sw=0 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 RAG`vP6qOUH68ho)e*wY%byNTV diff --git a/resources/intro/5-qunit-test-refactored.html b/resources/intro/5-qunit-test-refactored.html deleted file mode 100644 index 0d23b17..0000000 --- a/resources/intro/5-qunit-test-refactored.html +++ /dev/null @@ -1,28 +0,0 @@ - - - - - Refactored date examples - - - - - - - -
- - - diff --git a/resources/intro/6-qunit-dom.html b/resources/intro/6-qunit-dom.html deleted file mode 100644 index 510c4fa..0000000 --- a/resources/intro/6-qunit-dom.html +++ /dev/null @@ -1,75 +0,0 @@ - - - - - Refactored date examples - - - - - - - -
-
- - - -
- - - diff --git a/resources/intro/7-qunit-dom-refactored.html b/resources/intro/7-qunit-dom-refactored.html deleted file mode 100644 index 2ee3ef5..0000000 --- a/resources/intro/7-qunit-dom-refactored.html +++ /dev/null @@ -1,71 +0,0 @@ - - - - - Refactored date examples - - - - - - - -
-
- - - -
- - - diff --git a/resources/intro/8-endstate.html b/resources/intro/8-endstate.html deleted file mode 100644 index d4996e8..0000000 --- a/resources/intro/8-endstate.html +++ /dev/null @@ -1,110 +0,0 @@ - - - - - Final date examples - - - - - - - - - \ No newline at end of file diff --git a/resources/intro/prettydate.js b/resources/intro/prettydate.js deleted file mode 100644 index f761d42..0000000 --- a/resources/intro/prettydate.js +++ /dev/null @@ -1,21 +0,0 @@ -function prettyDate(now, time){ - var date = new Date(time || ""), - diff = (((new Date(now)).getTime() - date.getTime()) / 1000), - day_diff = Math.floor(diff / 86400); - - if ( isNaN(day_diff) || day_diff < 0 || day_diff >= 31 ) - return; - - return day_diff == 0 && ( - diff < 60 && "just now" || - diff < 120 && "1 minute ago" || - diff < 3600 && Math.floor( diff / 60 ) + - " minutes ago" || - diff < 7200 && "1 hour ago" || - diff < 86400 && Math.floor( diff / 3600 ) + - " hours ago") || - day_diff == 1 && "Yesterday" || - day_diff < 7 && day_diff + " days ago" || - day_diff < 31 && Math.ceil( day_diff / 7 ) + - " weeks ago"; -} diff --git a/resources/intro/prettydate2.js b/resources/intro/prettydate2.js deleted file mode 100644 index 80a24f4..0000000 --- a/resources/intro/prettydate2.js +++ /dev/null @@ -1,36 +0,0 @@ -var prettyDate = { - format: function(now, time){ - var date = new Date(time || ""), - diff = (((new Date(now)).getTime() - date.getTime()) / 1000), - day_diff = Math.floor(diff / 86400); - - if ( isNaN(day_diff) || day_diff < 0 || day_diff >= 31 ) - return; - - return day_diff === 0 && ( - diff < 60 && "just now" || - diff < 120 && "1 minute ago" || - diff < 3600 && Math.floor( diff / 60 ) + - " minutes ago" || - diff < 7200 && "1 hour ago" || - diff < 86400 && Math.floor( diff / 3600 ) + - " hours ago") || - day_diff === 1 && "Yesterday" || - day_diff < 7 && day_diff + " days ago" || - day_diff < 31 && Math.ceil( day_diff / 7 ) + - " weeks ago"; - }, - - update: function(now) { - var links = document.getElementsByTagName("a"); - for ( var i = 0; i < links.length; i++ ) { - if ( links[i].title ) { - var date = prettyDate.format(now, links[i].title); - if ( date ) { - links[i].innerHTML = date; - } - } - } - } -}; - diff --git a/resources/tests.js b/resources/tests.js deleted file mode 100644 index d979b97..0000000 --- a/resources/tests.js +++ /dev/null @@ -1,3 +0,0 @@ -QUnit.test( "hello test", function( assert ) { - assert.ok( 1 == "1", "Passed!" ); -}); \ No newline at end of file diff --git a/update-plugins.sh b/update-plugins.sh new file mode 100755 index 0000000..21abc6b --- /dev/null +++ b/update-plugins.sh @@ -0,0 +1,2 @@ +#!/bin/sh +npm search keywords:qunit-plugin --json > _data/plugins.json diff --git a/pages/upgrade-guide-2.x.md b/upgrade-guide-2.x.md similarity index 85% rename from pages/upgrade-guide-2.x.md rename to upgrade-guide-2.x.md index 8241493..34e1dbc 100644 --- a/pages/upgrade-guide-2.x.md +++ b/upgrade-guide-2.x.md @@ -1,21 +1,21 @@ - +--- +layout: page +title: 2.x Upgrade Guide +--- ## Overview -This guide will assist in upgrading from QUnit 1.x to QUnit 2.x. All breaking changes are listed below, explaining how to upgrade your code to work with QUnit 2.x. +This guide will assist you in upgrading from QUnit 1.x to QUnit 2.x. All breaking changes are listed below with an explanation of how to upgrade your code to work with QUnit 2.x. -

The [qunit-migrate](https://github.com/apsdehal/qunit-migrate) project can help you automate the transtions to QUnit 2.x.

+

The [qunit-migrate](https://github.com/apsdehal/qunit-migrate) project can help you automate the transition to QUnit 2.x.

-Note that almost all the new APIs of QUnit 2.0.0 are already usable in QUnit 1.23.1, allowing you to migrate step by step. The only exception is the new module hooks before and after. +Note that almost all the new APIs of QUnit 2.0.0 are already usable in QUnit 1.23.1, allowing you to migrate step-by-step. The only exception is the new module hooks `before` and `after`. -QUnit 2.0.x will include a migration layer that throws descriptive errors for all deprecated methods ("Global 'test()' method is removed, use 'QUnit.test() instead"), to help you migrate to the new APIs. QUnit 2.1+ will remove that layer, causing failures that will be more difficult to debug ("ReferenceError: test is not defined"). +QUnit 2.0.x will include a migration layer that throws descriptive errors for all deprecated methods (`"Global 'test()' method is removed, use 'QUnit.test() instead"`), to help you migrate to the new APIs. QUnit 2.1+ will remove that layer, causing failures that will be more difficult to debug (`"ReferenceError: test is not defined"`). ## Removed globals -QUnit no longer exposes multiple global variables. The only global variable still exposed is `QUnit`. Use [`QUnit.module()`](http://api.qunitjs.com/QUnit.module/) and [`QUnit.test()`](http://api.qunitjs.com/QUnit.test/) to define your testsuite, and use the [`assert`](http://api.qunitjs.com/QUnit.assert/) argument in test callbacks to write assertions. +QUnit no longer exposes multiple global variables. The only global variable still exposed is `QUnit`. Use [`QUnit.module()`](http://api.qunitjs.com/QUnit.module/) and [`QUnit.test()`](http://api.qunitjs.com/QUnit.test/) to define your test suite, and use the [`assert`](http://api.qunitjs.com/QUnit.assert/) argument in test callbacks to write assertions. The global `stop()` and `start()` methods are gone, replaced by [`assert.async()`](http://api.qunitjs.com/async/), which returns a callback. Execute this callback when your test is done. @@ -37,7 +37,7 @@ QUnit.module( "router" ); ### Replace `test()` with `QUnit.test()` -The global function `test()` is gone, use [`QUnit.test()`](http://api.qunitjs.com/QUnit.test/) instead. +The global function `test()` is gone. Use [`QUnit.test()`](http://api.qunitjs.com/QUnit.test/) instead. Before: @@ -51,18 +51,19 @@ After: QUnit.test( "defaults to home" ); ``` -### Replace `asyncTest()` with `QUnit.test()` and `assert.async()` +### Replace `stop()` and `start()` with `assert.async()` -The global function `asyncTest()` is gone. Use [`QUnit.test()`](http://api.qunitjs.com/QUnit.test/) and [`assert.async()`](http://api.qunitjs.com/async/) instead. +The global functions `stop()` and `start()` are gone. Use [`assert.async()`](http://api.qunitjs.com/async/) instead, which returns a "done" function that should be called when the asynchronous operation has completed. Before: ```js -asyncTest( "navigates to new page (async)", function( assert ) { +QUnit.test( "navigates to new page (async)", function( assert ) { + stop(); router.navigate(function( newPage ) { assert.equal( newPage.id, 1 ); start(); - }) + }); }); ``` @@ -74,23 +75,22 @@ QUnit.test( "navigates to new page (async)", function( assert ) { router.navigate(function( newPage ) { assert.equal( newPage.id, 1 ); done(); - }) + }); }); ``` -### Replace `stop()` and `start()` with `assert.async()` +### Replace `asyncTest()` with `QUnit.test()` and `assert.async()` -The global functions `stop()` and `start()` are gone. Use [`assert.async()`](http://api.qunitjs.com/async/) instead, which returns a "done" function that should be called when the asynchronous operation has completed. +The global function `asyncTest()` is gone. Use [`QUnit.test()`](http://api.qunitjs.com/QUnit.test/) and [`assert.async()`](http://api.qunitjs.com/async/) instead. Before: ```js -QUnit.test( "navigates to new page (async)", function( assert ) { - stop(); +asyncTest( "navigates to new page (async)", function( assert ) { router.navigate(function( newPage ) { assert.equal( newPage.id, 1 ); start(); - }) + }); }); ``` @@ -102,7 +102,7 @@ QUnit.test( "navigates to new page (async)", function( assert ) { router.navigate(function( newPage ) { assert.equal( newPage.id, 1 ); done(); - }) + }); }); ``` @@ -136,7 +136,7 @@ QUnit.test( "refresh (sync)", function( assert ) { All global assertions, like `equal()` and `deepEqual()` are gone. Use `assert` instead, like [`assert.equal()`](http://api.qunitjs.com/equal/) or [`assert.deepEqual()`](http://api.qunitjs.com/deepEqual/). -Here are all assertion methods affected by this change in alphabetic order: [`deepEqual()`](http://api.qunitjs.com/deepEqual/), [`equal()`](http://api.qunitjs.com/equal/), [`notDeepEqual()`](http://api.qunitjs.com/notDeepEqual/), [`notEqual()`](http://api.qunitjs.com/notEqual/), [`notPropEqual()`](http://api.qunitjs.com/notPropEqual/), [`notStrictEqual()`](http://api.qunitjs.com/notStrictEqual/), [`ok()`](http://api.qunitjs.com/ok/), [`propEqual()`](http://api.qunitjs.com/propEqual/), [`strictEqual()`](http://api.qunitjs.com/strictEqual/), [`throws()`](http://api.qunitjs.com/throws/). +Here are all assertion methods affected by this change in alphabetic order: [`deepEqual()`](http://api.qunitjs.com/deepEqual/), [`equal()`](http://api.qunitjs.com/equal/), [`notDeepEqual()`](http://api.qunitjs.com/notDeepEqual/), [`notEqual()`](http://api.qunitjs.com/notEqual/), [`notPropEqual()`](http://api.qunitjs.com/notPropEqual/), [`notStrictEqual()`](http://api.qunitjs.com/notStrictEqual/), [`ok()`](http://api.qunitjs.com/ok/), [`propEqual()`](http://api.qunitjs.com/propEqual/), [`strictEqual()`](http://api.qunitjs.com/strictEqual/), and [`throws()`](http://api.qunitjs.com/throws/). Before: @@ -195,42 +195,15 @@ QUnit.module( "router", { }); ``` - -## Replace `assert.throws( block, string, message )` with `assert.throws( block, regexp, message )` - -The overload of `assert.throws()` which expected a block, error string, and assertion message has been removed and will now throw an exception. Use a regular expression instead. - -Before: - -```js -QUnit.test( "throws", function( assert ) { - assert.throws( function() { - throw new Error( "This is an error" ); - }, "This is an error", "An error should have been thrown" ); -}); -``` - -After: - -```js -QUnit.test( "throws", function( assert ) { - assert.throws( function() { - throw new Error( "This is an error" ); - }, /^This is an error$/, "An error should have been thrown" ); -}); -``` - -Note that in the two-argument overload `assert.throws( block, string )`, the string argument has always been interpreted as an assertion message instead of an expected value. You do not need to change any of these assertions. Of course, you should use the `assert.throws( block, regexp, message )` form anyway to make your assertions more precise. - ## Removed and modified QUnit methods and properties A few methods and properties in the `QUnit` namespace have been modified or removed. ### Replace `QUnit.log = callback` with `QUnit.log( callback )` for all reporting callbacks -For several versions of QUnit before 2.0, custom reporters could be registered by calling the appropiate methods with a callback function. If your code still uses the old approach of overwriting a property on the `QUnit` object, replace that by calling the method instead. +For several versions of QUnit before 2.0, custom reporters could be registered by calling the appropriate methods with a callback function. If your code still uses the old approach of overwriting a property on the `QUnit` object, replace that by calling the method instead. -This applies to all reporting callbacks, specifically: [`begin`](http://api.qunitjs.com/QUnit.begin/), [`done`](http://api.qunitjs.com/QUnit.done/), [`log`](http://api.qunitjs.com/QUnit.log/), [`moduleDone`](http://api.qunitjs.com/QUnit.moduleDone/), [`moduleStart`](http://api.qunitjs.com/QUnit.moduleStart/), [`testDone`](http://api.qunitjs.com/QUnit.testDone/), [`testStart`](http://api.qunitjs.com/QUnit.testStart/). +This applies to all reporting callbacks, specifically: [`begin`](http://api.qunitjs.com/QUnit.begin/), [`done`](http://api.qunitjs.com/QUnit.done/), [`log`](http://api.qunitjs.com/QUnit.log/), [`moduleDone`](http://api.qunitjs.com/QUnit.moduleDone/), [`moduleStart`](http://api.qunitjs.com/QUnit.moduleStart/), [`testDone`](http://api.qunitjs.com/QUnit.testDone/), and [`testStart`](http://api.qunitjs.com/QUnit.testStart/). Before: @@ -352,3 +325,31 @@ QUnit.test( "addition", function( assert ) { assert.equal( add( 1, 2 ), 3 ); }); ``` + +## Miscellaneous + +### Replace `assert.throws( block, string, message )` with `assert.throws( block, regexp, message )` + +The overload of `assert.throws()` which expected a block, error string, and assertion message has been removed and will now throw an exception. Use a regular expression instead. + +Before: + +```js +QUnit.test( "throws", function( assert ) { + assert.throws( function() { + throw new Error( "This is an error" ); + }, "This is an error", "An error should have been thrown" ); +}); +``` + +After: + +```js +QUnit.test( "throws", function( assert ) { + assert.throws( function() { + throw new Error( "This is an error" ); + }, /^This is an error$/, "An error should have been thrown" ); +}); +``` + +Note that in the two-argument overload `assert.throws( block, string )`, the string argument has always been interpreted as an assertion message instead of an expected value. You do not need to change any of these assertions. Of course, you should use the `assert.throws( block, regexp, message )` form anyway to make your assertions more precise.