From 1b43addc3c31f9d589b46e255a66e92d13d4ee05 Mon Sep 17 00:00:00 2001 From: Timo Tijhof Date: Wed, 12 Jun 2024 21:45:35 +0100 Subject: [PATCH] Docs: New guide for Browser Runner and HTML Reporter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This restores the first half of the Cookbook that Jörn originally wrote on the original qunitjs.com website. We removed this during the redesign in 2020, but we hadn't found a new place for everything. https://github.com/qunitjs/qunitjs.com/issues/151 https://github.com/qunitjs/qunitjs.com/blob/v2.10.2/pages/cookbook.html * Update config doc pages to reflect whether something is part of Browser Runner (active in any browser environment), or the HTML Reporter. These have been separated now per https://github.com/qunitjs/qunit/issues/1118, as QUnit.reporters.html, and deals purely with what happens inside the optional `div[id=qunit]` element. * Move browser-related content from intro.md to the new browser.md. We should probably do the same for Node.js/CLI as well, and focus this only on env-agnostic getting started. This would be easier if the example use export/import ESM instead of having to assume either module.exports (Node.js) or globals (browser). Co-authored-by: Jörn Zaefferer Fixes https://github.com/qunitjs/qunit/issues/1748. Fixes https://github.com/qunitjs/qunit/issues/1747. --- .eslintrc.json | 16 +++ README.md | 4 +- docs/_data/sitenav.yml | 6 +- docs/api/callbacks/QUnit.on.md | 1 + docs/api/config/altertitle.md | 4 +- docs/api/config/collapse.md | 2 +- docs/api/config/filter.md | 11 +- docs/api/config/fixture.md | 164 +++++++++++++++++++++++++++++- docs/api/config/hidepassed.md | 12 ++- docs/api/config/moduleId.md | 8 +- docs/api/config/noglobals.md | 8 +- docs/api/config/notrycatch.md | 11 +- docs/api/config/scrolltop.md | 4 +- docs/api/config/seed.md | 10 +- docs/api/config/testId.md | 8 +- docs/api/index.md | 2 +- docs/browser.md | 161 +++++++++++++++++++++++++++++ docs/index.md | 6 +- docs/intro.md | 97 ++---------------- docs/resources/calc.js | 42 ++++++++ docs/resources/calc.test.js | 25 +++++ docs/resources/example-add.html | 17 ++-- docs/resources/example-index.html | 16 +-- docs/resources/game.js | 83 +++++++++++++++ docs/resources/game.test.js | 60 +++++++++++ 25 files changed, 646 insertions(+), 132 deletions(-) create mode 100644 docs/browser.md create mode 100644 docs/resources/calc.js create mode 100644 docs/resources/calc.test.js create mode 100644 docs/resources/game.js create mode 100644 docs/resources/game.test.js diff --git a/.eslintrc.json b/.eslintrc.json index 595264c4c..585cee981 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -191,6 +191,22 @@ "sourceType": "module" } }, + { + "files": [ + "docs/resources/**/*.html", + "docs/resources/**/*.js" + ], + "parserOptions": { + "ecmaVersion": 2018 + }, + "env": { + "es2017": true, + "browser": true + }, + "globals": { + "QUnit": "readonly" + } + }, { "files": ["**/*.md"], "processor": "markdown/markdown" diff --git a/README.md b/README.md index 69ee35375..85b6f2ceb 100644 --- a/README.md +++ b/README.md @@ -18,8 +18,8 @@ project and has since evolved to test any client-side or server-side JavaScript * [API documentation](https://qunitjs.com/api/) * [Guides how-to](https://qunitjs.com/guides/) -* [Browser support](https://qunitjs.com/intro/#browser-support) -* [Integrations](https://qunitjs.com/intro/#integrations) +* [Browser support](https://qunitjs.com/browser/#browser-support) +* [Browser automation](https://qunitjs.com/browser/#integrations) ## Support diff --git a/docs/_data/sitenav.yml b/docs/_data/sitenav.yml index 8062055fe..380e99f9d 100644 --- a/docs/_data/sitenav.yml +++ b/docs/_data/sitenav.yml @@ -3,8 +3,12 @@ sub: - name: Getting Started href: /intro/ + - name: Browser Runner + href: /browser/ - name: Command-line Interface href: /cli/ + - name: Download + href: /intro/#download - name: Plugins href: /plugins/ - name: QUnit 2.0 Upgrade Guide @@ -17,7 +21,7 @@ - name: Who's using QUnit? href: /projects/ - name: Browser Support - href: /intro/#browser-support + href: /browser/#browser-support - name: About QUnit href: /about/ - name: Brand Guidelines diff --git a/docs/api/callbacks/QUnit.on.md b/docs/api/callbacks/QUnit.on.md index be347f1c6..027613d6c 100644 --- a/docs/api/callbacks/QUnit.on.md +++ b/docs/api/callbacks/QUnit.on.md @@ -4,6 +4,7 @@ title: QUnit.on() excerpt: Register an event listener callback. groups: - callbacks + - extension redirect_from: - "/callbacks/QUnit.on/" version_added: "2.2.0" diff --git a/docs/api/config/altertitle.md b/docs/api/config/altertitle.md index d90e7f93f..1100e9006 100644 --- a/docs/api/config/altertitle.md +++ b/docs/api/config/altertitle.md @@ -1,7 +1,7 @@ --- layout: page-api title: QUnit.config.altertitle -excerpt: Insert a success or failure symbol in the document title (HTML Reporter). +excerpt: Insert a success or failure symbol in the document title. groups: - config redirect_from: @@ -9,7 +9,7 @@ redirect_from: version_added: "1.0.0" --- -In the HTML Reporter, whether to insert a success or failure symbol in the document title. +In browser environments, whether to insert a success or failure symbol in the document title. diff --git a/docs/api/config/collapse.md b/docs/api/config/collapse.md index d8d88d670..4fa0534e4 100644 --- a/docs/api/config/collapse.md +++ b/docs/api/config/collapse.md @@ -22,6 +22,6 @@ In the HTML Reporter, collapse the details of failing tests after the first one.
-By default, QUnit's HTML Reporter collapses consecutive failing tests showing only the details for the first failed test. The other tests can be expanded manually with a single click on the test title. +By default, the [HTML Reporter](../../browser.md) collapses consecutive failing tests showing only the details for the first failed test. The other tests can be expanded manually with a single click on the test title. Set this option to `false` to expand the details for all failing tests. diff --git a/docs/api/config/filter.md b/docs/api/config/filter.md index 5a646653b..fdcb366d7 100644 --- a/docs/api/config/filter.md +++ b/docs/api/config/filter.md @@ -24,7 +24,7 @@ Select tests to run based on a substring or pattern match.

-This option is available as [CLI option](../../cli.md), as control in the [HTML Reporter](../../intro.md#in-the-browser), and supported as URL query parameter. +This option is available as [CLI option](../../cli.md), as control in the [HTML Reporter](../../browser.md), and supported as URL query parameter.

@@ -78,3 +78,12 @@ The below matches `foo`, `foo > sub`, and `foo.sub`, but skips `bar`, `bar.foo`, ```js QUnit.config.filter = '/^foo/'; ``` + +### URL parameter + +``` +?filter=foo +?filter=!foo +?filter=/foo/ +?filter=!/foo/ +``` diff --git a/docs/api/config/fixture.md b/docs/api/config/fixture.md index b1cf7f3d8..e94353414 100644 --- a/docs/api/config/fixture.md +++ b/docs/api/config/fixture.md @@ -1,7 +1,7 @@ --- layout: page-api title: QUnit.config.fixture -excerpt: HTML content to render in the fixture container at the start of each test (HTML Reporter). +excerpt: HTML content to render before each test. groups: - config redirect_from: @@ -9,7 +9,7 @@ redirect_from: version_added: "1.0.0" --- -In the HTML Reporter, this HTML content will be rendered in the fixture container at the start of each test. +In browser environments, this HTML will be rendered in the `#qunit-fixture` element before each test. @@ -22,6 +22,164 @@ In the HTML Reporter, this HTML content will be rendered in the fixture containe
-By default QUnit will observe the initial content of the `#qunit-fixture` element, and use that as the fixture content for all tests. Use this option to configure the fixture content through JavaScript instead. +QUnit automatically resets the contents of `
`. This gives every test a fresh start, and automatically cleans up any additions or other changes from your tests. As long as you append or insert your elements inside the fixture, you will never have to manually clean up after your tests to keep them atomic. + +By starting with an empty fixture in your test HTML file, you effectively give each test a clean start, as QUnit will automatically remove anything that was added there before the next test begins. + +If many of your tests require the same markup, you can also set it inside the fixture ahead of time. This reduces duplication between tests. QUnit guruantees that each test will start with a fresh copy of the original fixture, undoing any changes that happened during any previous tests. + +You can define a custom fixture in one of two ways: + +* In HTML: By writing it as HTML inside the `
` element. +* In JavaScript: Set `QUnit.config.fixture` to a string from an [inline or bootstrap script](./index.md). + +By default, if `QUnit.config.fixture` is not set, QUnit will look for the `#qunit-fixture` element when the test run begins, and assign its contents to `QUnit.config.fixture`. The initially observed content is then restored before each test. To disable QUnit's fixture resetting behaviour, set the option to `null`. + +## Example + +### Automatic teardown + +Starting with an empty fixture. Any additions are automatically removed. + +```html + +
+
+ + + +``` +```js +// test/example.js + +QUnit.test('example [first]', function (assert) { + const fixture = document.querySelector('#qunit-fixture'); + + const resultA = document.querySelectorAll('#first'); + assert.strictEqual(resultA.length, 0, 'initial'); + + const div = document.createElement('div'); + div.id = 'first'; + fixture.append(div); + + const resultB = document.querySelectorAll('#first'); + assert.strictEqual(resultB.length, 1, 'after append'); +}); + +QUnit.test('example [second]', function (assert) { + const fixture = document.querySelector('#qunit-fixture'); + + // The previous elements were automatically detached. + const result = document.querySelectorAll('#first'); + assert.strictEqual(result.length, 0, 'initial is back to zero'); +}); +``` + + +### qunit-fixture HTML + +```html + +
+
+

+ Marty McFly: Listen, you got a back door to this place?
+ Bartender: Yeah, it's in the back. +

+
+ +``` + +```js +function findText (element, tagName) { + let ret = ''; + for (const emNode of element.querySelectorAll(tagName)) { + ret += emNode.textContent + ' '; + } + return ret.trim() || null; +} + +QUnit.test('findText [strong]', function (assert) { + const fixture = document.querySelector('#qunit-fixture'); + + assert.strictEqual( + findText(fixture, 'strong'), + 'Marty McFly Bartender', + 'initial' + ); + + fixture.querySelector('.bar').remove(); + + assert.strictEqual( + findText(fixture, 'strong'), + 'Marty McFly', + 'removed bar' + ); +}); + +QUnit.test('findText [code]', function (assert) { + const fixture = document.querySelector('#qunit-fixture'); + fixture.innerHTML = '

You can add two numbers.

'; + + assert.strictEqual( + findText(fixture, 'code'), + 'add' + ); + + assert.strictEqual( + findText(fixture, 'strong'), + null + ); +}); + +// The first test removed and its child. +// The second test replaced the fixture entirely. +// The fixture is clean and restored before each test starts. +QUnit.test('findText [em]', function (assert) { + const fixture = document.querySelector('#qunit-fixture'); + + assert.strictEqual( + findText(fixture, 'em'), + 'back', + 'initial content was restored' + ); +}); +``` + +### dynamic qunit-fixture + +```html + +
+
+ + + + +``` +```js +// test/bootstrap.js + +QUnit.config.fixture = '

Hi there, stranger!

'; + +// test/example.js + +QUnit.test('example [first]', function (assert) { + const fixture = document.querySelector('#qunit-fixture'); + + assert.strictEqual(fixture.textContent, 'Hi there, stranger!'); + + fixture.querySelector('strong').remove(); + + assert.strictEqual(fixture.textContent, 'Hi , stranger!'); +}); + +QUnit.test('example [second]', function (assert) { + const fixture = document.querySelector('#qunit-fixture'); + + // The fixture starts fresh on each test! + assert.strictEqual(fixture.textContent, 'Hi there, stranger!'); +}); +``` diff --git a/docs/api/config/hidepassed.md b/docs/api/config/hidepassed.md index a58767bf9..fcc5a10fe 100644 --- a/docs/api/config/hidepassed.md +++ b/docs/api/config/hidepassed.md @@ -22,6 +22,14 @@ In the HTML Reporter, hide results of passed tests. -

This option can also be controlled via the [HTML Reporter](../../intro.md#in-the-browser).

+

-By default, the HTML Reporter will list (in collapsed form) the names of all passed tests. Enable this option, to only list failing tests. +This option can also be controlled via the [HTML Reporter](../../browser.md). + +

+ +By default, the HTML Reporter will list both passing and failing tests. Passing tests are by default collapsed to display only their name. Enable `hidepassed` to hide passing tests completely, and show only failing tests in the list. + +## See also + +* [QUnit.config.collapse](./collapse.md) diff --git a/docs/api/config/moduleId.md b/docs/api/config/moduleId.md index bcabfb5e0..aa01486e2 100644 --- a/docs/api/config/moduleId.md +++ b/docs/api/config/moduleId.md @@ -9,7 +9,7 @@ redirect_from: version_added: "1.23.0" --- -In the HTML Reporter, select one or more modules to run by their internal ID. +Used by the HTML Reporter, this selects one or more modules by their internal ID to run exclusively. @@ -22,7 +22,11 @@ In the HTML Reporter, select one or more modules to run by their internal ID.
-

This option can be controlled via the [HTML Reporter](../../intro.md#in-the-browser) interface.

+

+ +This option can be controlled via the [HTML Reporter](../../browser.md) interface. + +

Specify modules by their internally hashed identifier for a given module. You can specify one or multiple modules to run. This option powers the multi-select dropdown menu in the HTML Reporter. diff --git a/docs/api/config/noglobals.md b/docs/api/config/noglobals.md index 1309eeba4..eca57dfc2 100644 --- a/docs/api/config/noglobals.md +++ b/docs/api/config/noglobals.md @@ -24,4 +24,10 @@ Check the global object after each test and report new properties as failures. Enable this option to let QUnit keep track of which global variables and properties exist on the global object (e.g. `window` in browsers). When new global properties are found, they will result in test failures to you make sure your application and your tests are not leaking any state. -

This option can also be controlled via the [HTML Reporter](../../intro.md#in-the-browser).

+This helps you make sure the code under test doesn't accidentally leak or declare any global variables. + +

+ +This option can also be controlled via the [HTML Reporter](../../browser.md). + +

diff --git a/docs/api/config/notrycatch.md b/docs/api/config/notrycatch.md index d4e9a658e..38a932979 100644 --- a/docs/api/config/notrycatch.md +++ b/docs/api/config/notrycatch.md @@ -22,8 +22,13 @@ Disable handling of uncaught exceptions during tests. -

This option can also be controlled via the [HTML Reporter](../../intro.md#in-the-browser) interface, and is supported as URL query parameter.

+

-By default, QUnit handles uncaught exceptions during test execution and reports them as test failures. This lets other tests continue running and allows reporters to summarise results. +This option can also be controlled via the [HTML Reporter](../../browser.md) interface, and is supported as URL query parameter. + +

+ +By default, QUnit handles uncaught errors during test execution and reports them as test failures. This allows reporters to reliably summarise results. + +Enabling this flag will disable this error handling, allowing your error to become a "native" uncaught exception and thus interrupt QUnit. This can sometimes ease debugging through a browser's developer tools, such as when dealing with breakpoints, or source maps. -Enabling this flag will disable this error handling, allowing you to more easily debug uncaught exceptions through developer tools. diff --git a/docs/api/config/scrolltop.md b/docs/api/config/scrolltop.md index 0022fddfb..2b96cd64b 100644 --- a/docs/api/config/scrolltop.md +++ b/docs/api/config/scrolltop.md @@ -1,7 +1,7 @@ --- layout: page-api title: QUnit.config.scrolltop -excerpt: Scroll to the top of the page after the test run (HTML Reporter). +excerpt: Scroll to the top of the page after the test run. groups: - config redirect_from: @@ -9,7 +9,7 @@ redirect_from: version_added: "1.14.0" --- -In the HTML Reporter, ensure the browser is scrolled to the top of the page when the tests are done. +In browser environments, scrop to the top of the page after the tests are done. diff --git a/docs/api/config/seed.md b/docs/api/config/seed.md index cb2875716..0221c2511 100644 --- a/docs/api/config/seed.md +++ b/docs/api/config/seed.md @@ -22,7 +22,11 @@ Enable randomized ordering of tests.
-

This option is also available as [CLI option](../../cli.md), and as URL query parameter in the browser.

+

+ +This option is also available as [CLI option](../../cli.md), and as URL query parameter in the [browser](../../browser.md). + +

When set to boolean true, or a string, QUnit will run tests in a [seeded-random order](https://en.wikipedia.org/wiki/Random_seed). @@ -31,3 +35,7 @@ The provided string will be used as the seed in a pseudo-random number generator Randomly ordering your tests can help identify non-atomic tests which either depend on a previous test or are leaking state to subsequent tests. If `seed` is boolean true (or set as URL query parameter without a value), then QUnit will generate on-demand a new random value to use as seed. You can then read the seed at runtime from the configuration value, and use it to reproduce the same test sequence later. + +## See also + +* [QUnit.config.reorder](./reorder.md) diff --git a/docs/api/config/testId.md b/docs/api/config/testId.md index 2db6dbbce..d69470484 100644 --- a/docs/api/config/testId.md +++ b/docs/api/config/testId.md @@ -9,7 +9,7 @@ redirect_from: version_added: "1.16.0" --- -In the HTML Reporter, select one or more tests to run by their internal ID. +Used by the HTML Reporter, select one or more tests to run by their internal ID. @@ -22,7 +22,11 @@ In the HTML Reporter, select one or more tests to run by their internal ID.
-

This option can be controlled via the [HTML Reporter](../../intro.md#in-the-browser) interface.

+

+ +This option can be controlled via the [HTML Reporter](../../browser.md) interface. + +

This property allows QUnit to run specific tests by their internally hashed identifier. You can specify one or multiple tests to run. This option powers the "Rerun" button in the HTML Reporter. diff --git a/docs/api/index.md b/docs/api/index.md index fb7a91fe9..7d7015c73 100644 --- a/docs/api/index.md +++ b/docs/api/index.md @@ -10,7 +10,7 @@ redirect_from: If you're new to QUnit, check out [Getting Started](../intro.md)! -QUnit is a powerful, easy-to-use JavaScript unit test suite. QUnit has no dependencies and supports Node.js, SpiderMonkey, and all [major browsers](../intro.md#browser-support). +QUnit is a powerful, easy-to-use JavaScript unit test suite. QUnit has no dependencies and supports Node.js, SpiderMonkey, and all [major browsers](../browser.md#browser-support). diff --git a/docs/browser.md b/docs/browser.md new file mode 100644 index 000000000..cc2a76ff8 --- /dev/null +++ b/docs/browser.md @@ -0,0 +1,161 @@ +--- +layout: page +title: Browser Runner +excerpt: Test JavaScript code in a real browser. +amethyst: + toc: true +redirect_from: + - "/cookbook/" +--- + +

+ +Test JavaScript code in a real browser with QUnit. + +

+ +## Getting started + +QUnit releases are standalone and require no runtime dependencies for use in the browser. To get started, all you need is an HTML file that loads two files: `qunit.js` and `qunit.css`. + + +```html + + + +QUnit + + +
+
+ + + + +``` + +This is typically saved as `/test.html` or `/test/index.html` in your project. + +The above example loads QUnit from the jQuery CDN. For improved experience during local or offline development, it is recommended to [install or download QUnit](intro.md#download) within your project. Some [integrations](#integrations), like Web Test Runner and Karma, can auto-create the HTML from a list of JS files or glob pattern. + +Let's add the following script, which tests an "add" function that adds two numbers together: + +```html + +``` + +Open your HTML file in a browser to find a detailed report. Live example ([open in a new tab](./resources/example-add.html){:target="_blank"}): + + + +Congrats! You just executed your first QUnit test! + +## Fixture + +Don't worry about DOM changes from one test affecting other tests, because QUnit will automatically reset the markup after each test. As long as you append or insert your elements inside the fixture, you will never have to manually clean up after your tests. + +This helps keep your tests them atomic! + +Find examples and learn more at [`QUnit.config.fixture`](./api/config/fixture.md). + +## Integrations + +### Linting + +The [eslint-plugin-qunit](https://github.com/platinumazure/eslint-plugin-qunit) package has a variety of rules available for enforcing best testing practices as well as detecting broken tests. + +### Browser automation + +The following integrations can be used to automate the running of browser tests with QUnit: + +* [grunt-contrib-qunit](https://github.com/gruntjs/grunt-contrib-qunit) for [Grunt task runner](https://gruntjs.com/) (test Headless Chrome). +* [testem](https://github.com/testem/testem) (test any local browser, including headless) +* [wdio-qunit-service](https://webdriver.io/docs/wdio-qunit-service/) (test any local, headless, or cloud browser; via WDIO selenium driver) +* [Web Test Runner](https://modern-web.dev/docs/test-runner/overview/) with [web-test-runner-qunit](https://github.com/brandonaaron/web-test-runner-qunit) (test any local, headless, or cloud browser) +* [Karma](https://karma-runner.github.io/latest/index.html) with [karma-qunit](https://github.com/karma-runner/karma-qunit) (test any local browser or cloud). +* [node-qunit-puppeteer](https://github.com/ameshkov/node-qunit-puppeteer) (test Headless Chrome). +* [StealJS](https://stealjs.com/) with [steal-qunit](https://stealjs.com/docs/steal-qunit.html) via [Testee](https://www.npmjs.com/package/testee) (test any local browser or cloud). +* [testcafe](https://github.com/DevExpress/testcafe) (test any local browser or cloud). + +Example projects: + +* [Krinkle/example-node-and-browser-qunit-ci](https://github.com/Krinkle/example-node-and-browser-qunit-ci/): Run QUnit tests locally and in CI, on Headless Firefox and Chrome (using Karma), and with Node.js.
Also demonstrates code coverage, and testing of isomorphic JavaScript projects. + +## URL parameters + +* [hidepassed](./api/config/hidepassed.md) +* [noglobals](./api/config/noglobals.md) +* [notrycatch](./api/config/notrycatch.md) +* [filter](./api/config/filter.md) +* [module](./api/config/module.md) +* [moduleId](./api/config/moduleId.md) +* [testId](./api/config/testId.md) +* [seed](./api/config/seed.md) + +## Markup + +QUnit requires no HTML markup in order to run tests. The above "Getting started" example is recommended for new projects, but you can customize this as-needed. + +To display test results, the only markup necessary is a `
` with `id="qunit"`. Without this, the tests will run with the [HTML Reporter](#html-reporter) disabled. + +[Browser automations](#integrations) that run tests for you from the command-line, might enable other reporters or event listeners instead. For example, they might use a TAP reporter, or [`QUnit.on()`](./callbacks/QUnit.on.md) to automatically extract results in a machine-readable way, and use it to set the build status of a continuous integration job (CI). + +## HTML Reporter + +The header of the report displays: +* the page title. +* a green bar when all tests passed, or a red bar if one or more tests failed. +* the `navigator.userAgent` string, handy when comparing results captured from different browsers. + +### Toolbar + +[Hide passed tests](./api/config/hidepassed.md) is useful when a lot of tests ran and only a few failed. Ticking the checkbox will show only the tests that failed.. + +[Check for globals](./api/config/noglobals.md) detects if a test creates or removes global variable is. QUnit keeps a list of properties found on the `window` object. If properties were added or removed, the test fails. + +[No try-catch](./api/config/notrycatch.md) instructs QUnit to run your tests without a try-catch block. In this mode, if your test throws an error it will interrupt QUnit, which may ease debugging. + +### Filter + +Enter a search phrase to re-run only tests that match the phrase. It performs a case-insensitive substring match by default, on both the module name and test name. You can use regular expressions, and/or invert the match to exclude tests instead. + +Read [filter](./api/config/filter.md) for examples. + +### Module selector + +To quickly re-run one or more modules of tests, select them from the module selector and press "Apply". + +You can use the input field to quickly find a module, even if you have many modules. The input field performs [fuzzy matching](https://github.com/farzher/fuzzysort), so don't worry about getting it exactly right! `baor game` and `boar dame` finds "board game". This is similar to how "Quick Open" works in modern text editors. + +When selecting a parent module, which contains [nested modules](./api/QUnit/module.md), the nested modules and their tests will also be run. + +### Test results + +Each result is displayed in a numbered list. After the name of the test, in parentheses, are the number of failed, passed, and total assertions. Click the entry to show the results of each assertion, with details about expected and actual results (for failed asssertions). + +The "Rerun" link at the end will run that test on its own. + +## Browser support + +QUnit currently supports the following browsers: + +* Internet Explorer: 9+ +* Edge: 15+ (both legacy MSEdge and Chromium-based) +* Firefox: 45+ +* Safari: 9+ +* Opera: 36+ +* Chrome: 58+ +* Android: 4.3+ +* iOS: 7+ (Mobile Safari) + +For older browsers, such as Internet Explorer 6-8, Opera 12+, or Safari 5+, please use QUnit 1.x, which you can download from the [release archives](https://releases.jquery.com/qunit/). diff --git a/docs/index.md b/docs/index.md index b641afb43..6e3a3a4f9 100644 --- a/docs/index.md +++ b/docs/index.md @@ -35,8 +35,8 @@ function add (a, b) { return a + b; } -QUnit.module('add', hooks => { - QUnit.test('two numbers', assert => { +QUnit.module('add', (hooks) => { + QUnit.test('two numbers', (assert) => { assert.equal(add(1, 2), 3); }); }); @@ -76,7 +76,7 @@ ok 1 add > two numbers

v2.21.0 (changelog)

-These are the officially supported [release channels](intro.md#release-channels) for QUnit: +These are the official [release channels](intro.md#download) for QUnit: * Download: [`qunit-2.21.0.js`](https://code.jquery.com/qunit/qunit-2.21.0.js) and [`qunit-2.21.0.css`](https://code.jquery.com/qunit/qunit-2.21.0.css) * npm: `npm install --save-dev qunit` diff --git a/docs/intro.md b/docs/intro.md index 91173d967..d05520750 100644 --- a/docs/intro.md +++ b/docs/intro.md @@ -5,16 +5,15 @@ amethyst: toc: true redirect_from: - "/guides/" - - "/cookbook/" ---

-This tutorial gets you up-and-running with QUnit in Node.js or [in the browser](#in-the-browser). +This tutorial gets you up-and-running with QUnit in Node.js or [in the browser](./browser.md).

-QUnit has no dependencies and supports Node.js, SpiderMonkey, and all [major browsers](#browser-support). +QUnit has no dependencies and supports Node.js, SpiderMonkey, and all [major browsers](./browser.md#browser-support). ## In Node.js @@ -44,7 +43,7 @@ const add = require('../add.js'); QUnit.module('add'); -QUnit.test('two numbers', assert => { +QUnit.test('two numbers', (assert) => { assert.equal(add(1, 2), 3); }); ``` @@ -91,99 +90,23 @@ Prior to QUnit 2.4.1, the npm package was published under the name "qunit**js**" The 0.x and 1.x versions of the "qunit" package on npm holds an alternative CLI that is now published as [node-qunit](https://github.com/qunitjs/node-qunit). -### Linting - -The [eslint-plugin-qunit](https://github.com/platinumazure/eslint-plugin-qunit) package has a variety of rules available for enforcing best testing practices as well as detecting broken tests. - --- -## In the Browser - -To get started with QUnit in the browser, create a simple HTML file called `test.html` and include the following markup: - -```html - - -Test Suite - - -
-
- - -``` - -That's all the markup you need to start writing tests. Note that this loads the library from the [jQuery CDN](https://code.jquery.com/qunit/). - -See also [Integrations & Downloads](#integrations) for integration you can use to automate browser testing. These usually also manage the HTML file for you. - -Let's add the following script, which tests an example `add()` function for adding two numbers together: - -```html - -``` - -This code defines a test module for the `add()` function and verifies the result of adding two numbers. +## Linting -If you open this up in the browser you'll find a detailed report of the tests that ran and their assertions, as well as various options for filtering and re-running individual tests to help during development. Like so: - - - -Congrats! You just wrote and executed your first QUnit test! - -Check out the [API documentation](./api/index.md) to learn more about the QUnit APIs for organising tests and making assertions. - -### Browser support - -QUnit currently supports the following browsers: - -* Internet Explorer: 9+ -* Edge: 15+ (both legacy MSEdge and Chromium-based) -* Firefox: 45+ -* Safari: 9+ -* Opera: 36+ -* Chrome: 58+ -* Android: 4.3+ -* iOS: 7+ (Mobile Safari) - -For older browsers, such as Internet Explorer 6-8, Opera 12+, or Safari 5+, please use the 1.x series of QUnit. - -### Integrations - -The following integrations can be used to automate the running of browser tests with QUnit: - -* [grunt-contrib-qunit](https://github.com/gruntjs/grunt-contrib-qunit) for [Grunt task runner](https://gruntjs.com/) (test Headless Chrome). -* [testem](https://github.com/testem/testem) (test any local browser, including headless) -* [wdio-qunit-service](https://webdriver.io/docs/wdio-qunit-service/) (test any local, headless, or cloud browser; via WDIO selenium driver) -* [Web Test Runner](https://modern-web.dev/docs/test-runner/overview/) with [web-test-runner-qunit](https://github.com/brandonaaron/web-test-runner-qunit) (test any local, headless, or cloud browser) -* [Karma](https://karma-runner.github.io/latest/index.html) with [karma-qunit](https://github.com/karma-runner/karma-qunit) (test any local browser or cloud). -* [node-qunit-puppeteer](https://github.com/ameshkov/node-qunit-puppeteer) (test Headless Chrome). -* [StealJS](https://stealjs.com/) with [steal-qunit](https://stealjs.com/docs/steal-qunit.html) via [Testee](https://www.npmjs.com/package/testee) (test any local browser or cloud). -* [testcafe](https://github.com/DevExpress/testcafe) (test any local browser or cloud). - -Example projects: +The [eslint-plugin-qunit](https://github.com/platinumazure/eslint-plugin-qunit) package has a variety of rules available for enforcing best testing practices as well as detecting broken tests. -* [Krinkle/example-node-and-browser-qunit-ci](https://github.com/Krinkle/example-node-and-browser-qunit-ci/): Run QUnit tests locally and in CI, on Headless Firefox and Chrome (using Karma), and with Node.js.
Also demonstrates code coverage, and testing of isomorphic JavaScript projects. +--- -### Release channels +## Download -These are the officially supported download channels for QUnit releases: +These are the official release channels for QUnit releases: * Download: QUnit has no runtime dependencies for browser use. You can save the [`qunit-2.21.0.js`](https://code.jquery.com/qunit/qunit-2.21.0.js) and [`qunit-2.21.0.css`](https://code.jquery.com/qunit/qunit-2.21.0.css) files directly from the [jQuery CDN](https://code.jquery.com/qunit/). - Or download them via the terminal: + Or download them via the terminal, and save them in your Git repository. ```bash curl -o qunit.css 'https://code.jquery.com/qunit/qunit-2.21.0.css' @@ -205,7 +128,7 @@ These are the officially supported download channels for QUnit releases: You can then reference `node_modules/qunit/qunit/qunit.css` and `node_modules/qunit/qunit/qunit.js` in your HTML. - If your project uses a custom npm frontend that locates packages elsewhere, you may need to generate the HTML dynamically and use [`require.resolve()`](https://nodejs.org/api/modules.html#modules_require_resolve_request_options) to locate `qunit/qunit/qunit.js` and `qunit/qunit/qunit.css`. Alternatively, use one of the [Integrations](#integrations) such as karma-qunit which do all of that for you. + If your project uses a custom npm frontend that locates packages elsewhere, you may need to generate the HTML dynamically and use [`require.resolve()`](https://nodejs.org/api/modules.html#modules_require_resolve_request_options) to locate `qunit/qunit/qunit.js` and `qunit/qunit/qunit.css`. Alternatively, use one of the [Integrations](./browser.md#integrations) such as karma-qunit which can do this for you. * Bower: diff --git a/docs/resources/calc.js b/docs/resources/calc.js new file mode 100644 index 000000000..da6da2bee --- /dev/null +++ b/docs/resources/calc.js @@ -0,0 +1,42 @@ +/** + * calc.js - An example project to demonstrate QUnit. + * + * @author Timo Tijhof, 2022 + * @license 0BSD + * @license Public domain + */ + +/** + * @param {number} a + * @param {number} b + * @return {number} + */ +export function add (a, b) { + return a + b; +} + +/** + * @param {number} a + * @param {number} b + * @return {number} + */ +export function substract (a, b) { + return a - b; +} + +/** + * @param {number} a + * @param {number} b + * @return {number} + */ +export function multiply (a, b) { + return a * b; +} + +/** + * @param {number} x + * @return {number} + */ +export function square (x) { + return x * x; +} diff --git a/docs/resources/calc.test.js b/docs/resources/calc.test.js new file mode 100644 index 000000000..12b2014a2 --- /dev/null +++ b/docs/resources/calc.test.js @@ -0,0 +1,25 @@ +import * as calc from './calc.js'; + +QUnit.module('calc', () => { + QUnit.test('add', (assert) => { + assert.equal(calc.add(1, 2), 3); + assert.equal(calc.add(2, 3), 5); + assert.equal(calc.add(5, -1), 4, 'negative'); + }); + + QUnit.test('substract', (assert) => { + assert.equal(calc.substract(3, 2), 1); + assert.equal(calc.substract(5, 3), 2); + assert.equal(calc.substract(5, -1), 6, 'negative'); + }); + + QUnit.test('multiply', (assert) => { + assert.equal(calc.multiply(7, 2), 14); + }); + + QUnit.test('square', (assert) => { + assert.equal(calc.square(5), 25); + assert.equal(calc.square(7), 49); + assert.equal(calc.square(-8), 64, 'negative'); + }); +}); diff --git a/docs/resources/example-add.html b/docs/resources/example-add.html index b7490faed..c828dd1ad 100644 --- a/docs/resources/example-add.html +++ b/docs/resources/example-add.html @@ -1,4 +1,5 @@ + QUnit @@ -7,14 +8,16 @@
+ diff --git a/docs/resources/example-index.html b/docs/resources/example-index.html index afb5ddbae..71afc7832 100644 --- a/docs/resources/example-index.html +++ b/docs/resources/example-index.html @@ -1,20 +1,14 @@ + QUnit
+ - + + - + diff --git a/docs/resources/game.js b/docs/resources/game.js new file mode 100644 index 000000000..bbf86e42e --- /dev/null +++ b/docs/resources/game.js @@ -0,0 +1,83 @@ +/** + * game.js - An example project to demonstrate QUnit. + * + * @author Timo Tijhof, 2022 + * @license 0BSD + * @license Public domain + */ + +export class Robot {} + +export class BoardObjectError extends Error {} +export class BoardMoveError extends Error {} + +export class Coord { + constructor (x = 0, y = 0) { + this.x = x; + this.y = y; + } + + clone () { + return new Coord(this.x, this.y); + } +} + +export class Board { + constructor (width, height) { + this.xMax = width - 1; + this.yMax = height - 1; + this.objects = new Map(); + } + + add (object) { + if (this.objects.has(object)) { + throw new BoardObjectError('Object already on board'); + } + this.objects.set(object, new Coord()); + } + + find (object) { + const pos = this.objects.get(object); + return pos ? pos.clone() : false; + } + + moveUp (object) { + const pos = this.objects.get(object); + if (!pos) { + throw new BoardObjectError('Object not on board'); + } + const newPos = pos.clone(); + newPos.y = (newPos.y === 0) ? this.yMax : (newPos.y - 1); + this.objects.set(object, this.normalizeCoord(newPos)); + } + + moveDown (object) { + const pos = this.objects.get(object); + if (!pos) { + throw new BoardObjectError('Object not on board'); + } + const newPos = pos.clone(); + newPos.y = (newPos.y === this.yMax) ? 0 : (newPos.y + 1); + this.objects.set(object, newPos); + } + + moveLeft (object) { + const pos = this.objects.get(object); + if (!pos) { + throw new BoardObjectError('Object not on board'); + } + const newPos = pos.clone(); + newPos.x = (newPos.x === 0) ? this.xMax : (newPos.x - 1); + this.objects.set(object, newPos); + } + + moveRight (object) { + const pos = this.objects.get(object); + if (!pos) { + throw new BoardObjectError('Object not on board'); + } + const newPos = pos.clone(); + newPos.x = (newPos.x === this.xMax) ? 0 : (newPos.x + 1); + this.objects.set(object, newPos); + } +} diff --git a/docs/resources/game.test.js b/docs/resources/game.test.js new file mode 100644 index 000000000..27d7849c4 --- /dev/null +++ b/docs/resources/game.test.js @@ -0,0 +1,60 @@ +import * as game from './game.js'; + +QUnit.module('game', (hooks) => { + let board; + + hooks.beforeEach(() => { + board = new game.Board(3, 3); + }); + + QUnit.module('Coord', () => { + QUnit.test('constructor', (assert) => { + const pos1 = new game.Coord(); + assert.propContains(pos1, { x: 0, y: 0 }, 'defaults'); + + const pos2 = new game.Coord(5, 7); + assert.propContains(pos2, { x: 5, y: 7 }, 'parameters'); + }); + }); + + QUnit.module('Board', () => { + QUnit.test('add', (assert) => { + const alice = new game.Robot(); + board.add(alice); + assert.propContains(board.find(alice), { x: 0, y: 0 }, 'add alice'); + + const bob = new game.Robot(); + board.add(bob); + assert.propContains(board.find(bob), { x: 0, y: 0 }, 'add bob'); + + assert.throws(() => { + board.add(bob); + }, game.BoardObjectError, 'add second bob'); + }); + + QUnit.test('find [absent]', (assert) => { + const bob = new game.Robot(); + assert.false(board.find(bob)); + }); + + QUnit.test('move', (assert) => { + const alice = new game.Robot(); + board.add(alice); + + board.moveRight(alice); + assert.propContains(board.find(alice), { x: 1, y: 0 }, 'move'); + + board.moveRight(alice); + assert.propContains(board.find(alice), { x: 2, y: 0 }, 'move again'); + + board.moveRight(alice); + assert.propContains(board.find(alice), { x: 0, y: 0 }, 'teleport back'); + + const bob = new game.Robot(); + + assert.throws(() => { + board.moveRight(bob); + }, board.BoardObjectError, 'not on board'); + }); + }); +});