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
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 0000000..9e343f3 Binary files /dev/null and b/img/logo.png differ 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, zero configuration setup for any Node.js project and minimal configuration for Browser-based projects.
+Tests can be run anywhere; Node, your browser, even inside a Web Worker. Test your code where it runs.
+Flexible APIs for custom assertions, runners, and reporters mean you can extend QUnit to fit your needs.
+v2.10.1 (changelog)
+ +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 + + + + + +- 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. -
- -- 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. -
- -- 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.
-
- 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. -
- -- 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. -
- -- QUnit provides a number of built-in assertions. Let's look at three of those: -
- -
- 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" );
-});
-
-
- 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.
-
- 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.
-
- Occasionally, callback assertions in the code might not be called at all, causing the test to fail silently. -
-- 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" );
-});
-
-
-
- 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.
-
- 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.
-
- 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();
- });
-});
-
-
- - 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. -
-
- 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" );
-});
-
-
- 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: -
-- 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.
-
- 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.
-
- 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. -
- -- 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. -
- -
- 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").
-
- 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.
-
- 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" );
-});
-
-
-
- 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" );
-});
-
-
-
- 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 });
).
-
- 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. -
-- 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.
-
- 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. -
-- 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! -
- -- 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: -
- - -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
-QUnit is available from the jQuery CDN hosted by MaxCDN. - -
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.
-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
.
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.
- -QUnit follows the Node Long-term Support (LTS) Schedule. Support is provided for Current, Active, and Maintenance releases.
- -- 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. -
-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.
- -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.
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.
- -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)
-
- 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 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.
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.
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.”
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.
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.
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.
- -These plugins provide custom assertions or a complete new interface to use QUnit.
-setupOnce
and teardownOnce
to module's config.These libraries can be used along with QUnit to mock Ajax requests, timers and more.
-These plugins make it easier to integrate QUnit in various testing setups.
-iframe
Alternative themes for the HTML Reporter. Load their CSS file instead of the default one.
-The following plugins provide a myriad of ways to modify, extend, and enhance QUnit itself as well as the developer experience of using QUnit.
+ +{{ plugin.description }}
+blah blah blah...
- - Posted - - January 28th, 2008 - - - by - -blah blah blah...
- - Posted - - January 28th, 2008 - - - by - -blah blah blah...
- - Posted - - January 27th, 2008 - - - by - -blah blah blah...
- - Posted - - January 26th, 2008 - - - by - -blah blah blah...
- - Posted - - January 25th, 2008 - - - by - -blah blah blah...
- - Posted - - January 24th, 2008 - - - by - - -blah blah blah...
- - Posted - - January 14th, 2008 - - - by - -blah blah blah...
- - Posted - - January 4th, 2008 - - - by - -blah blah blah...
- - Posted - - December 15th, 2008 - - - by - -blah blah blah...
- - Posted - - January 28th, 2008 - - - by - -blah blah blah...
- - Posted - - January 27th, 2008 - - - by - -blah blah blah...
- - Posted - - January 26th, 2008 - - - by - -blah blah blah...
- - Posted - - January 25th, 2008 - - - by - -blah blah blah...
- - Posted - - January 24th, 2008 - - - by - - -blah blah blah...
- - Posted - - January 14th, 2008 - - - by - -blah blah blah...
- - Posted - - January 4th, 2008 - - - by - -blah blah blah...
- - Posted - - December 15th, 2008 - - - by - -blah blah blah...
- - Posted - January 28th, 2008 - - by - -blah blah blah...
- - Posted - January 27th, 2008 - - by - -blah blah blah...
- - Posted - January 28th, 2008 - - by - -blah blah blah...
- - Posted - January 27th, 2008 - - by - -blah blah blah...
- - Posted - - January 28th, 2008 - - - by - -blah blah blah...
- - Posted - - January 27th, 2008 - - - by - -blah blah blah...
- - Posted - - January 26th, 2008 - - - by - -blah blah blah...
- - Posted - - January 25th, 2008 - - - by - -blah blah blah...
- - Posted - - January 24th, 2008 - - - by - - -blah blah blah...
- - Posted - - January 14th, 2008 - - - by - -blah blah blah...
- - Posted - - January 4th, 2008 - - - by - -blah blah blah...
- - Posted - - December 15th, 2008 - - - by - -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 hooksbefore
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.