From 0f6ec7c0b6303f988e9cde20a771239f4a88a796 Mon Sep 17 00:00:00 2001 From: Timo Tijhof Date: Mon, 28 Mar 2022 14:44:38 +0100 Subject: [PATCH] Build: lint MD and JSON, adopt semistandard and eslint-plugin-config Major overhaul: * Lint and enforce style in code examples in Markdown docs. * Lint and style JSON files. * Consolidate all ESLint configs into a central file. * Statically check and enforce ES5 environment and browser compat. We already have this confidence through our CI tests, but having it via eslin-plugin-config makes this accessible during development for 99% of cases, which makes development quicker and more helpful during pull requests as well. * Explicitly don't allow use of global setTimeout/clearTimeout, or any other runtime-specific APIs except when explicitly declared as safe to do so. This is to ensure portability for the main code and test suite. * Move from jquery preset to semistandard preset. I don't have a strong preference for the style, but the semistandard preset is more actively maintained and contains a broader set of rules that also help with code quality rather than just coding style. Having said that, I do use semistandard in other repos I maintain, which helps reduce context switching a bit for me. --- .editorconfig | 7 +- .eslintrc.base.js | 25 + .eslintrc.json | 266 +- .gitignore | 2 +- Gruntfile.js | 316 +- bin/qunit.js | 64 +- build/auth-cdn-commit.js | 130 +- build/coverage-bridge.js | 38 +- build/dist-replace.js | 41 +- build/prep-release.js | 233 +- build/reproducible-builds.js | 329 +- build/review-package.js | 124 +- build/set-release.js | 85 +- build/tasks/test-on-node.js | 197 +- build/utils.js | 134 +- build/watch.js | 215 +- docs/QUnit/hooks.md | 6 +- docs/QUnit/module.md | 240 +- docs/QUnit/start.md | 2 +- docs/QUnit/test.each.md | 60 +- docs/QUnit/test.md | 32 +- docs/QUnit/test.only.md | 16 +- docs/QUnit/test.skip.md | 12 +- docs/QUnit/test.todo.md | 8 +- docs/assert/async.md | 36 +- docs/assert/deepEqual.md | 24 +- docs/assert/equal.md | 16 +- docs/assert/expect.md | 14 +- docs/assert/false.md | 18 +- docs/assert/notDeepEqual.md | 6 +- docs/assert/notEqual.md | 12 +- docs/assert/notOk.md | 20 +- docs/assert/notPropContains.md | 32 +- docs/assert/notPropEqual.md | 13 +- docs/assert/notStrictEqual.md | 6 +- docs/assert/ok.md | 20 +- docs/assert/propContains.md | 32 +- docs/assert/propEqual.md | 17 +- docs/assert/pushResult.md | 29 +- docs/assert/rejects.md | 45 +- docs/assert/step.md | 8 +- docs/assert/strictEqual.md | 4 +- docs/assert/throws.md | 47 +- docs/assert/timeout.md | 28 +- docs/assert/true.md | 18 +- docs/assert/verifySteps.md | 87 +- docs/callbacks/QUnit.begin.md | 23 +- docs/callbacks/QUnit.done.md | 12 +- docs/callbacks/QUnit.log.md | 16 +- docs/callbacks/QUnit.moduleDone.md | 4 +- docs/callbacks/QUnit.moduleStart.md | 4 +- docs/callbacks/QUnit.on.md | 14 +- docs/callbacks/QUnit.testDone.md | 24 +- docs/callbacks/QUnit.testStart.md | 4 +- docs/config/autostart.md | 6 +- docs/config/current.md | 8 +- docs/config/index.md | 2 +- docs/config/urlConfig.md | 28 +- docs/extension/QUnit.dump.parse.md | 18 +- docs/extension/QUnit.extend.md | 14 +- docs/extension/QUnit.onUncaughtException.md | 12 +- docs/extension/QUnit.stack.md | 16 +- package-lock.json | 9397 ++++++++++++++++- package.json | 10 +- rollup.config.js | 72 +- src/assert.js | 912 +- src/cli/find-reporter.js | 106 +- src/cli/require-from-cwd.js | 6 +- src/cli/require-qunit.js | 89 +- src/cli/run.js | 435 +- src/cli/utils.js | 143 +- src/core.js | 347 +- src/core/config.js | 196 +- src/core/hooks.js | 20 +- src/core/logging.js | 98 +- src/core/on-uncaught-exception.js | 57 +- src/core/onerror.js | 26 +- src/core/processing-queue.js | 287 +- src/core/stacktrace.js | 72 +- src/core/utilities.js | 296 +- src/dump.js | 570 +- src/equiv.js | 661 +- src/events.js | 72 +- src/export.js | 73 +- src/globals.js | 129 +- src/html-reporter/diff.js | 2247 ++-- src/html-reporter/fixture.js | 100 +- src/html-reporter/html.js | 2241 ++-- src/html-reporter/reporter.js | 6 +- src/html-reporter/urlparams.js | 200 +- src/logger.js | 8 +- src/module.js | 300 +- src/promise.js | 2 +- src/qunit.js | 4 +- src/reporters.js | 8 +- src/reporters/ConsoleReporter.js | 77 +- src/reporters/TapReporter.js | 489 +- src/reports/suite.js | 226 +- src/reports/test.js | 194 +- src/test.js | 2032 ++-- test/.eslintrc.json | 32 - test/amd.html | 40 +- test/amd.js | 40 +- test/autostart.html | 56 +- test/autostart.js | 16 +- test/cli/.eslintrc.json | 9 - test/cli/ConsoleReporter.js | 85 +- test/cli/TapReporter.js | 525 +- test/cli/cli-main.js | 1494 ++- test/cli/cli-watch.js | 785 +- test/cli/custom-reporter.js | 112 +- test/cli/events.js | 458 +- .../fixtures/assert-expect/failing-expect.js | 8 +- .../fixtures/assert-expect/no-assertions.js | 4 +- .../fixtures/assert-expect/require-expects.js | 6 +- .../async-module-warning/promise-test.js | 12 +- .../cli/fixtures/async-module-warning/test.js | 14 +- .../cli/fixtures/bad-callbacks/begin-throw.js | 16 +- test/cli/fixtures/bad-callbacks/done-throw.js | 16 +- .../bad-callbacks/moduleDone-throw.js | 26 +- .../fixtures/bad-callbacks/testStart-throw.js | 26 +- test/cli/fixtures/callbacks-promises.js | 114 +- test/cli/fixtures/callbacks.js | 132 +- .../fixtures/config-filter-regex-exclude.js | 28 +- test/cli/fixtures/config-filter-regex.js | 34 +- test/cli/fixtures/config-filter-string.js | 22 +- test/cli/fixtures/config-module.js | 32 +- test/cli/fixtures/config-moduleId.js | 106 +- test/cli/fixtures/config-testId.js | 88 +- test/cli/fixtures/config-testTimeout.js | 12 +- test/cli/fixtures/done-after-timeout.js | 12 +- test/cli/fixtures/double.js | 16 +- test/cli/fixtures/drooling-done.js | 22 +- .../fixtures/drooling-extra-done-outside.js | 18 +- test/cli/fixtures/drooling-extra-done.js | 22 +- test/cli/fixtures/expected/tap-outputs.js | 117 +- .../fixtures/expected/watch-tap-outputs.js | 16 +- test/cli/fixtures/fail/failure.js | 16 +- test/cli/fixtures/fail/throws-match.js | 14 +- test/cli/fixtures/glob/a-test.js | 10 +- test/cli/fixtures/glob/not-a.js | 10 +- test/cli/fixtures/glob/sub/nested-test.js | 10 +- test/cli/fixtures/hanging-test/index.js | 6 +- test/cli/fixtures/hard-error-in-hook.js | 16 +- ...ard-error-in-test-with-no-async-handler.js | 14 +- test/cli/fixtures/hooks-global-context.js | 87 +- test/cli/fixtures/hooks-global-order.js | 107 +- .../fixtures/incorrect-hooks-warning/test.js | 18 +- test/cli/fixtures/memory-leak/.eslintrc.json | 5 - test/cli/fixtures/memory-leak/index.js | 147 +- test/cli/fixtures/module-nested.js | 52 +- test/cli/fixtures/noglobals/add-global.js | 8 +- test/cli/fixtures/noglobals/ignored.js | 8 +- test/cli/fixtures/noglobals/remove-global.js | 10 +- .../notrycatch/returns-rejection-in-hook.js | 25 +- .../fixtures/notrycatch/returns-rejection.js | 32 +- test/cli/fixtures/npm-reporter/index.js | 12 +- test/cli/fixtures/only/module-flat.js | 49 +- test/cli/fixtures/only/module-then-test.js | 26 +- test/cli/fixtures/only/module.js | 140 +- test/cli/fixtures/only/test.js | 24 +- test/cli/fixtures/single.js | 10 +- test/cli/fixtures/sourcemap/source.js | 16 +- test/cli/fixtures/test/first.js | 10 +- test/cli/fixtures/test/nested/second.js | 10 +- test/cli/fixtures/timeout/index.js | 24 +- test/cli/fixtures/too-many-done-calls.js | 12 +- test/cli/fixtures/unhandled-rejection.js | 26 +- test/cli/fixtures/zero-assertions.js | 14 +- test/cli/helpers/execute.js | 234 +- test/cli/require-qunit-test.js | 87 +- test/cli/structure.js | 159 +- test/cli/utils.js | 34 +- test/es2018/.eslintrc.json | 6 - test/es2018/async-functions.js | 60 +- test/es2018/esm.mjs | 14 +- test/es2018/rejects.js | 130 +- test/es2018/sum.mjs | 4 +- test/es2018/throws.js | 146 +- test/events-filters.html | 14 +- test/events-filters.js | 163 +- test/events-in-test.html | 14 +- test/events-in-test.js | 76 +- test/headless.html | 52 +- test/index.html | 48 +- test/integration/.eslintrc.json | 1 + test/logs.html | 14 +- test/logs.js | 660 +- test/main/assert-step.js | 293 +- test/main/assert-timeout.js | 236 +- test/main/assert.js | 1882 ++-- test/main/async.js | 660 +- test/main/deepEqual.js | 3992 ++++--- test/main/dump.js | 664 +- test/main/each.js | 91 +- test/main/modules.js | 844 +- test/main/onError.js | 121 +- test/main/onUncaughtException.js | 52 +- test/main/promise.js | 491 +- test/main/setTimeout.js | 40 +- test/main/stack.js | 22 +- test/main/test.js | 556 +- test/main/utilities.js | 175 +- test/module-skip.html | 20 +- test/module-skip.js | 110 +- test/module-todo.html | 20 +- test/module-todo.js | 108 +- test/mozjs.js | 90 +- test/node/.eslintrc.json | 6 - test/node/storage-1.js | 44 +- test/node/storage-2.js | 40 +- test/node/storage.js | 36 +- test/only-each.html | 14 +- test/only-each.js | 18 +- test/overload.html | 40 +- test/performance-mark.html | 2 +- test/performance-mark.js | 15 +- test/preconfigured.html | 28 +- test/preconfigured.js | 36 +- test/reorder.html | 12 +- test/reorder.js | 43 +- test/reorderError1.html | 88 +- test/reorderError1.js | 13 +- test/reorderError2.html | 82 +- test/reorderError2.js | 13 +- test/reporter-html/config-testId.html | 12 +- test/reporter-html/config-testId.js | 21 +- test/reporter-html/diff.js | 383 +- test/reporter-html/hidepassed.html | 12 +- test/reporter-html/hidepassed.js | 62 +- test/reporter-html/legacy-markup.html | 20 +- test/reporter-html/no-qunit-element.html | 34 +- test/reporter-html/reporter-html.js | 292 +- test/reporter-html/unhandled-rejection.js | 45 +- test/reporter-html/urlparams-filter.js | 48 +- .../window-onerror-preexisting-handler.html | 7 +- .../window-onerror-preexisting-handler.js | 116 +- test/reporter-html/window-onerror.js | 53 +- .../xhtml-escape-details-source.js | 16 +- test/reporter-urlparams.html | 20 +- test/reporter-urlparams.js | 67 +- test/sandboxed-iframe--contents.html | 22 +- test/sandboxed-iframe.html | 6 +- test/sandboxed-iframe.js | 56 +- test/seed.html | 2 +- test/seed.js | 38 +- test/startError.html | 12 +- test/startError.js | 48 +- test/urlparams-module.html | 12 +- test/urlparams-module.js | 23 +- test/urlparams-moduleId.html | 12 +- test/urlparams-moduleId.js | 57 +- test/urlparams-testId.html | 12 +- test/urlparams-testId.js | 34 +- test/webWorker-worker.js | 44 +- test/webWorker.html | 12 +- test/webWorker.js | 25 +- 257 files changed, 27390 insertions(+), 18212 deletions(-) create mode 100644 .eslintrc.base.js delete mode 100644 test/.eslintrc.json delete mode 100644 test/cli/.eslintrc.json delete mode 100644 test/cli/fixtures/memory-leak/.eslintrc.json delete mode 100644 test/es2018/.eslintrc.json delete mode 100644 test/node/.eslintrc.json diff --git a/.editorconfig b/.editorconfig index b566cfef4..f74499919 100644 --- a/.editorconfig +++ b/.editorconfig @@ -3,7 +3,8 @@ root = true [*] -indent_style = tab +indent_style = space +indent_size = 2 end_of_line = lf charset = utf-8 trim_trailing_whitespace = true @@ -13,7 +14,3 @@ insert_final_newline = true trim_trailing_whitespace = false [test/integration/nyc.js] trim_trailing_whitespace = false - -[*.{yml,yaml,md}] -indent_style = space -indent_size = 2 diff --git a/.eslintrc.base.js b/.eslintrc.base.js new file mode 100644 index 000000000..16ddb9bd6 --- /dev/null +++ b/.eslintrc.base.js @@ -0,0 +1,25 @@ +/* eslint-env node */ + +// This JS file exists to reset 'env' and 'globals', +// which ESLint doesn't have a built-in way to do otherwise. +// Because of this, we have to bypass semistandard. +const standard = require('eslint-config-standard'); + +const config = Object.assign(standard, { + env: {}, + globals: {}, + rules: Object.assign(standard.rules, { + // semistandard + semi: ['error', 'always'], + 'no-extra-semi': 'error', + // disablements + 'no-lone-blocks': 'off', + 'node/no-callback-literal': 'off', + 'prefer-const': 'off', + 'prefer-promise-reject-errors': 'off', + // extra + 'no-unused-vars': ['error', { args: 'all', argsIgnorePattern: '^_' }] + }) +}); + +module.exports = config; diff --git a/.eslintrc.json b/.eslintrc.json index 5ff1f7e07..8feb664f9 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,79 +1,199 @@ { - "root": true, - "env": { - "es6": true - }, - "extends": ["eslint:recommended", "jquery"], - "parserOptions": { + "root": true, + "extends": ".eslintrc.base.js", + "plugins": [ + "compat", + "html", + "json-es", + "markdown", + "qunit" + ], + "reportUnusedDisableDirectives": true, + "ignorePatterns": [ + "__codeorigin/**", + "coverage/**", + "docs/.jekyll-cache/**", + "docs/_site/**", + "lib/**", + "qunit/**", + "test/cli/fixtures/sourcemap/*.min.js", + "test/integration/*/**", + "temp/**" + ], + "overrides": [ + // Universal source code + // Written for compat with ES5 runtimes, + // ES6+ syntax is transpiled via Rollup. ES6+ APIs may not be used. + { + "files": ["src/**"], + "extends": ".eslintrc.base.js", + "parserOptions": { + "ecmaVersion": 6, "sourceType": "module" + }, + "globals": { + "clearTimeout": "off", + "setTimeout": "off" + }, + "rules": { + "no-console": "error", + "compat/compat": [ + "error", + "android >= 4.3, firefox >= 45, ie >= 9, safari >= 9.1, chrome >= 58" + ] + } + }, + // Universal test suites + // Strictly ES5 syntax for ES5 runtimes. + { + "files": ["test/**"], + "extends": [".eslintrc.base.js", "plugin:qunit/recommended"], + "parserOptions": { + "ecmaVersion": 5, + "sourceType": "script" + }, + "globals": { + "QUnit": "readonly" + }, + "rules": { + "compat/compat": [ + "error", + "android >= 4.3, firefox >= 45, ie >= 9, safari >= 9.1, chrome >= 58" + ], + "max-len": "off", + "no-throw-literal": "off", + "no-var": "off", + "prefer-regex-literals": "off", + "qunit/literal-compare-order": "off", + "qunit/no-arrow-tests": "off", + "qunit/no-assert-equal-boolean": "off", + "qunit/no-assert-logical-expression": "off", + "qunit/no-only": "off", + "qunit/require-expect": "off", + "qunit/resolve-async": "off" + } }, - "rules": { - "arrow-spacing": ["error", { "before": true, "after": true }], - "brace-style": ["error", "1tbs", { "allowSingleLine": true }], - "constructor-super": "error", - "indent": ["error", "tab", { - "CallExpression": { - "arguments": 1 - }, - "MemberExpression": 1, - "SwitchCase": 0, - "outerIIFEBody": 1 - }], + // Node.js-specific source code + { + "files": [ + "bin/**", + "build/**", + "src/cli/**", + "Gruntfile.js", + "rollup.config.js" + ], + "excludedFiles": [ + "build/coverage-bridge.js" + ], + "extends": [".eslintrc.base.js", "plugin:node/recommended"], + "parserOptions": { + "ecmaVersion": 2020, + "sourceType": "script" + }, + "env": { + "node": true + }, + "rules": { + "compat/compat": "off", "no-console": "off", - "no-const-assign": "error", - "no-constant-condition": ["error", { "checkLoops": false }], - "no-dupe-class-members": "error", - "no-duplicate-imports": "error", - "no-multi-spaces": ["error", { "ignoreEOLComments": true }], - "no-this-before-super": "error", - "no-useless-constructor": "error", - "no-useless-rename": "error", - "prefer-const": "error", - "rest-spread-spacing": ["error", "never"], - "wrap-iife": ["error", "any"] + "no-process-exit": "off", + "node/no-missing-require": "off", + "node/no-unpublished-require": "off" + } + }, + // Misc overrides + { + "files": ["test/cli/**"], + "env": { + "es2017": true, + "node": true + }, + "parserOptions": { + "ecmaVersion": 2018 + }, + "rules": { + "compat/compat": "off" + } + }, + { + "files": ["test/mozjs.js"], + "parserOptions": { + "ecmaVersion": 6 + }, + "rules": { + "compat/compat": "off" + } + }, + { + "files": ["test/es2018/**"], + "env": { + "es2017": true + }, + "parserOptions": { + "ecmaVersion": 2018 + }, + "rules": { + "compat/compat": "off" + } + }, + { + "files": ["test/node/**"], + "env": { + "es2017": true, + "node": true + }, + "parserOptions": { + "ecmaVersion": 2018 + }, + "rules": { + "compat/compat": "off" + } + }, + { + "files": ["test/**/*.html"], + "env": { + "browser": true + } + }, + { + "files": ["**/*.mjs"], + "parserOptions": { + "sourceType": "module" + } + }, + { + "files": ["**/*.md"], + "processor": "markdown/markdown" + }, + { + "files": ["**/*.md/**"], + "parserOptions": { + "ecmaFeatures": { + "impliedStrict": true + } + }, + "rules": { + // https://github.com/eslint/eslint/issues/15732 + "array-bracket-spacing": "off", + "no-labels": "off", + "no-undef": "off", + "no-unused-expressions": "off", + "no-unused-vars": "off", + "no-var": "off", + "strict": "off" + } }, - "ignorePatterns": [ - "__codeorigin/**", - "coverage/**", - "docs/_site/**", - "lib/**", - "qunit/**", - "test/integration/*/**", - "temp/**" - ], - "overrides": [ - { - "files": [ - "bin/*.js", - "build/**/*.js", - "src/cli/*.js", - "Gruntfile.js", - "rollup.config.js" - ], - "excludedFiles": [ - "build/coverage-bridge.js" - ], - "parserOptions": { - "sourceType": "script", - "ecmaVersion": 2020 - }, - "env": { - "node": true - }, - "extends": ["plugin:node/recommended"], - "rules": { - "no-process-exit": "off" - } - }, - { - "files": [ - "test/**/*.html" - ], - "extends": ["eslint:recommended", "jquery"], - "rules": { - "wrap-iife": ["error", "any"], - "indent": "off" - } - } - ] + { + "files": ["**/*.json"], + "extends": [ + "plugin:json-es/recommended" + ], + "parser": "eslint-plugin-json-es", + "rules": { + // https://github.com/zeitport/eslint-plugin-json-es/issues/35 + // "indent": ["error", 2] + "indent": "off" + } + } + ] } diff --git a/.gitignore b/.gitignore index 847f7d93f..2eac8aecb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,8 @@ /.nyc_output/ /__codeorigin/ /coverage/ -/docs/_site/ /docs/.jekyll-cache/ +/docs/_site/ /docs/Gemfile.lock /test/integration/*/package-lock.json /test/integration/*/node_modules/ diff --git a/Gruntfile.js b/Gruntfile.js index 001478d68..59c979f00 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -1,161 +1,161 @@ /* eslint-env node */ -const fs = require( "fs" ); -const path = require( "path" ); -const { preprocess } = require( "./build/dist-replace.js" ); - -var isCI = process.env.CI; - -module.exports = function( grunt ) { - var connectPort = Number( grunt.option( "connect-port" ) ) || 4000; - - grunt.loadNpmTasks( "grunt-contrib-connect" ); - grunt.loadNpmTasks( "grunt-contrib-copy" ); - grunt.loadNpmTasks( "grunt-contrib-qunit" ); - grunt.loadNpmTasks( "grunt-search" ); - - grunt.initConfig( { - connect: { - base: { - options: { - - // grunt-contrib-connect supports 'useAvailablePort' which - // automatically finds a suitable port, but we can't use that here because - // the grunt-contrib-qunit task needs to know the url ahead of time. - port: connectPort, - base: "." - } - } - }, - copy: { - options: { process: preprocess }, - - "src-css": { - src: "src/qunit.css", - dest: "qunit/qunit.css" - } - }, - search: { - options: { - - // Ensure that the only HTML entities used are those with a special status in XHTML - // and that any common singleton/empty HTML elements end with the XHTML-compliant - // "/>"rather than ">" - searchString: /(&(?!gt|lt|amp|quot)[A-Za-z0-9]+;|<(?:hr|HR|br|BR|input|INPUT)(?![^>]*\/>)(?:\s+[^>]*)?>)/g, - logFormat: "console", - failOnMatch: true - }, - xhtml: [ - "src/**/*.js" - ] - }, - qunit: { - all: { - options: { - timeout: 30000, - puppeteer: { - args: isCI ? - - // For CI - [ "--no-sandbox" ] : - - // For local development - // Allow Docker-based developer environmment to - // inject --no-sandbox as-needed for Chrome. - ( process.env.CHROMIUM_FLAGS || "" ).split( " " ) - }, - inject: [ - path.resolve( "./build/coverage-bridge.js" ), - require.resolve( "grunt-contrib-qunit/chrome/bridge" ) - ], - - // @HTML_FILES - urls: [ - "test/index.html", - - "test/amd.html", - "test/autostart.html", - "test/events-filters.html", - "test/events-in-test.html", - "test/headless.html", - "test/logs.html", - "test/module-skip.html", - "test/module-todo.html", - "test/only-each.html", - "test/overload.html", - "test/performance-mark.html", - "test/preconfigured.html", - "test/reorder.html", - "test/reorderError1.html", - "test/reorderError2.html", - "test/reporter-urlparams.html", - "test/sandboxed-iframe.html", - "test/seed.html", - "test/startError.html", - "test/urlparams-module.html", - "test/urlparams-moduleId.html", - "test/urlparams-testId.html", - "test/webWorker.html", - - "test/reporter-html/hidepassed.html?hidepassed=true", - "test/reporter-html/legacy-markup.html", - "test/reporter-html/no-qunit-element.html", - "test/reporter-html/config-testId.html", - "test/reporter-html/urlparams-filter.html", - "test/reporter-html/window-onerror-preexisting-handler.html", - "test/reporter-html/window-onerror.html", - "test/reporter-html/xhtml-escape-details-source.xhtml", - "test/reporter-html/xhtml-config-testId.xhtml" - - ].map( file => `http://localhost:${connectPort}/${file}` ) - } - } - }, - - "test-on-node": { - files: [ - - // Sync with test/index.html - "test/main/assert.js", - "test/main/assert-step.js", - "test/main/assert-timeout.js", - "test/main/async.js", - "test/main/deepEqual.js", - "test/main/dump.js", - "test/main/each.js", - "test/main/modules.js", - "test/main/onError.js", - "test/main/onUncaughtException.js", - "test/main/promise.js", - "test/main/setTimeout.js", - "test/main/stack.js", - "test/main/test.js", - "test/main/utilities.js", - - // Sync with test/*.html files that also make sense for Node.js - "test/events-in-test.js", - "test/logs.js", - "test/module-skip.js", - "test/module-todo.js", - - "test/node/storage-1.js", - "test/node/storage-2.js", - - "test/es2018/async-functions.js", - "test/es2018/rejects.js", - "test/es2018/throws.js" - ] - } - } ); - - grunt.event.on( "qunit.coverage", function( file, coverage ) { - var testName = file.split( "/test/" ).pop().replace( ".html", "" ).replace( /[/\\]/g, "--" ); - var reportPath = path.join( ".nyc_output", "browser--" + testName + ".json" ); - - fs.mkdirSync( path.dirname( reportPath ), { recursive: true } ); - fs.writeFileSync( reportPath, JSON.stringify( coverage ) + "\n" ); - } ); - - grunt.loadTasks( "build/tasks" ); - grunt.registerTask( "test", [ "search", "test-on-node", "connect:base", "qunit" ] ); +const fs = require('fs'); +const path = require('path'); +const { preprocess } = require('./build/dist-replace.js'); + +let isCI = process.env.CI; + +module.exports = function (grunt) { + let connectPort = Number(grunt.option('connect-port')) || 4000; + + grunt.loadNpmTasks('grunt-contrib-connect'); + grunt.loadNpmTasks('grunt-contrib-copy'); + grunt.loadNpmTasks('grunt-contrib-qunit'); + grunt.loadNpmTasks('grunt-search'); + + grunt.initConfig({ + connect: { + base: { + options: { + + // grunt-contrib-connect supports 'useAvailablePort' which + // automatically finds a suitable port, but we can't use that here because + // the grunt-contrib-qunit task needs to know the url ahead of time. + port: connectPort, + base: '.' + } + } + }, + copy: { + options: { process: preprocess }, + + 'src-css': { + src: 'src/qunit.css', + dest: 'qunit/qunit.css' + } + }, + search: { + options: { + + // Ensure that the only HTML entities used are those with a special status in XHTML + // and that any common singleton/empty HTML elements end with the XHTML-compliant + // "/>"rather than ">" + searchString: /(&(?!gt|lt|amp|quot)[A-Za-z0-9]+;|<(?:hr|HR|br|BR|input|INPUT)(?![^>]*\/>)(?:\s+[^>]*)?>)/g, + logFormat: 'console', + failOnMatch: true + }, + xhtml: [ + 'src/**/*.js' + ] + }, + qunit: { + all: { + options: { + timeout: 30000, + puppeteer: { + args: isCI + + // For CI + ? ['--no-sandbox'] + + // For local development + // Allow Docker-based developer environmment to + // inject --no-sandbox as-needed for Chrome. + : (process.env.CHROMIUM_FLAGS || '').split(' ') + }, + inject: [ + path.resolve('./build/coverage-bridge.js'), + require.resolve('grunt-contrib-qunit/chrome/bridge') + ], + + // @HTML_FILES + urls: [ + 'test/index.html', + + 'test/amd.html', + 'test/autostart.html', + 'test/events-filters.html', + 'test/events-in-test.html', + 'test/headless.html', + 'test/logs.html', + 'test/module-skip.html', + 'test/module-todo.html', + 'test/only-each.html', + 'test/overload.html', + 'test/performance-mark.html', + 'test/preconfigured.html', + 'test/reorder.html', + 'test/reorderError1.html', + 'test/reorderError2.html', + 'test/reporter-urlparams.html', + 'test/sandboxed-iframe.html', + 'test/seed.html', + 'test/startError.html', + 'test/urlparams-module.html', + 'test/urlparams-moduleId.html', + 'test/urlparams-testId.html', + 'test/webWorker.html', + + 'test/reporter-html/hidepassed.html?hidepassed=true', + 'test/reporter-html/legacy-markup.html', + 'test/reporter-html/no-qunit-element.html', + 'test/reporter-html/config-testId.html', + 'test/reporter-html/urlparams-filter.html', + 'test/reporter-html/window-onerror-preexisting-handler.html', + 'test/reporter-html/window-onerror.html', + 'test/reporter-html/xhtml-escape-details-source.xhtml', + 'test/reporter-html/xhtml-config-testId.xhtml' + + ].map(file => `http://localhost:${connectPort}/${file}`) + } + } + }, + + 'test-on-node': { + files: [ + + // Sync with test/index.html + 'test/main/assert.js', + 'test/main/assert-step.js', + 'test/main/assert-timeout.js', + 'test/main/async.js', + 'test/main/deepEqual.js', + 'test/main/dump.js', + 'test/main/each.js', + 'test/main/modules.js', + 'test/main/onError.js', + 'test/main/onUncaughtException.js', + 'test/main/promise.js', + 'test/main/setTimeout.js', + 'test/main/stack.js', + 'test/main/test.js', + 'test/main/utilities.js', + + // Sync with test/*.html files that also make sense for Node.js + 'test/events-in-test.js', + 'test/logs.js', + 'test/module-skip.js', + 'test/module-todo.js', + + 'test/node/storage-1.js', + 'test/node/storage-2.js', + + 'test/es2018/async-functions.js', + 'test/es2018/rejects.js', + 'test/es2018/throws.js' + ] + } + }); + + grunt.event.on('qunit.coverage', function (file, coverage) { + let testName = file.split('/test/').pop().replace('.html', '').replace(/[/\\]/g, '--'); + let reportPath = path.join('.nyc_output', 'browser--' + testName + '.json'); + + fs.mkdirSync(path.dirname(reportPath), { recursive: true }); + fs.writeFileSync(reportPath, JSON.stringify(coverage) + '\n'); + }); + + grunt.loadTasks('build/tasks'); + grunt.registerTask('test', ['search', 'test-on-node', 'connect:base', 'qunit']); }; diff --git a/bin/qunit.js b/bin/qunit.js index 7303289af..1a8e0b431 100755 --- a/bin/qunit.js +++ b/bin/qunit.js @@ -1,11 +1,11 @@ #!/usr/bin/env node -"use strict"; +'use strict'; -const program = require( "commander" ); -const run = require( "../src/cli/run" ); -const { displayAvailableReporters } = require( "../src/cli/find-reporter" ); -const pkg = require( "../package.json" ); +const program = require('commander'); +const run = require('../src/cli/run'); +const { displayAvailableReporters } = require('../src/cli/find-reporter'); +const pkg = require('../package.json'); const description = `Runs tests using the QUnit framework. @@ -14,43 +14,43 @@ const description = `Runs tests using the QUnit framework. For more info on working with QUnit, check out https://qunitjs.com.`; -function collect( val, collection ) { - collection.push( val ); - return collection; +function collect (val, collection) { + collection.push(val); + return collection; } -program._name = "qunit"; +program._name = 'qunit'; program - .version( pkg.version ) - .usage( "[options] [files]" ) - .description( description ) - .option( "-f, --filter ", "filter which tests run" ) - .option( "-r, --reporter [name]", "specify the reporter to use; " + - "if no match is found or no name is provided, a list of available " + - "reporters will be displayed" ) - .option( "--require ", "specify a module or script to include before running " + - "any tests.", collect, [] ) - .option( "--seed [value]", "specify a seed to re-order your tests; " + - "if specified without a value, a seed will be generated" ) - .option( "-w, --watch", "watch files for changes and re-run the test suite" ) - .parse( process.argv ); + .version(pkg.version) + .usage('[options] [files]') + .description(description) + .option('-f, --filter ', 'filter which tests run') + .option('-r, --reporter [name]', 'specify the reporter to use; ' + + 'if no match is found or no name is provided, a list of available ' + + 'reporters will be displayed') + .option('--require ', 'specify a module or script to include before running ' + + 'any tests.', collect, []) + .option('--seed [value]', 'specify a seed to re-order your tests; ' + + 'if specified without a value, a seed will be generated') + .option('-w, --watch', 'watch files for changes and re-run the test suite') + .parse(process.argv); const opts = program.opts(); -if ( opts.reporter === true ) { - const requireQUnit = require( "../src/cli/require-qunit" ); - displayAvailableReporters( requireQUnit().reporters ); +if (opts.reporter === true) { + const requireQUnit = require('../src/cli/require-qunit'); + displayAvailableReporters(requireQUnit().reporters); } const options = { - filter: opts.filter, - reporter: opts.reporter, - requires: opts.require, - seed: opts.seed + filter: opts.filter, + reporter: opts.reporter, + requires: opts.require, + seed: opts.seed }; -if ( opts.watch ) { - run.watch( program.args, options ); +if (opts.watch) { + run.watch(program.args, options); } else { - run( program.args, options ); + run(program.args, options); } diff --git a/build/auth-cdn-commit.js b/build/auth-cdn-commit.js index faccb0a03..44dc14662 100644 --- a/build/auth-cdn-commit.js +++ b/build/auth-cdn-commit.js @@ -3,83 +3,81 @@ // See also RELEASE.md. // // Inspired by . -/* eslint-env node */ -const fs = require( "fs" ); -const cp = require( "child_process" ); -const path = require( "path" ); +const fs = require('fs'); +const cp = require('child_process'); +const path = require('path'); const remotes = { - real: "git@github.com:jquery/codeorigin.jquery.com.git", - test: "git@github.com:jquery/fake-cdn.git" + real: 'git@github.com:jquery/codeorigin.jquery.com.git', + test: 'git@github.com:jquery/fake-cdn.git' }; const files = { - "qunit/qunit.js": "cdn/qunit/qunit-@VERSION.js", - "qunit/qunit.css": "cdn/qunit/qunit-@VERSION.css" + 'qunit/qunit.js': 'cdn/qunit/qunit-@VERSION.js', + 'qunit/qunit.css': 'cdn/qunit/qunit-@VERSION.css' }; -const commitMessage = "qunit: Added version @VERSION"; +const commitMessage = 'qunit: Added version @VERSION'; const Cdn = { - clone( remoteKey, repoPath ) { - const remote = remotes[ remoteKey ]; - if ( !remote ) { - throw new Error( "Unknown remote " + remoteKey ); - } - console.log( "... cloning " + remote ); - if ( fs.existsSync( repoPath ) ) { - fs.rmdirSync( repoPath, { recursive: true } ); - } - cp.execFileSync( "git", [ - "clone", - "--depth=5", - "-q", - remote, - repoPath - ] ); - }, - commit( remoteKey, version ) { - if ( !remoteKey || !version ) { - throw new Error( - "Parameters are \n\n " + - " One of \"test\" or \"real\".\n " + - " E.g. \"1.2.3\".\n\nExample: node auth-cdn-push.js test 2.4.0\n" - ); - } - const repoPath = path.join( __dirname, "..", "__codeorigin" ); - Cdn.clone( remoteKey, repoPath ); + clone (remoteKey, repoPath) { + const remote = remotes[remoteKey]; + if (!remote) { + throw new Error('Unknown remote ' + remoteKey); + } + console.log('... cloning ' + remote); + if (fs.existsSync(repoPath)) { + fs.rmdirSync(repoPath, { recursive: true }); + } + cp.execFileSync('git', [ + 'clone', + '--depth=5', + '-q', + remote, + repoPath + ]); + }, + commit (remoteKey, version) { + if (!remoteKey || !version) { + throw new Error( + 'Parameters are \n\n ' + + ' One of "test" or "real".\n ' + + ' E.g. "1.2.3".\n\nExample: node auth-cdn-push.js test 2.4.0\n' + ); + } + const repoPath = path.join(__dirname, '..', '__codeorigin'); + Cdn.clone(remoteKey, repoPath); - const toCommit = []; - for ( const src in files ) { - const srcPath = path.join( __dirname, "..", src ); - const dest = files[ src ].replace( "@VERSION", version ); - const destPath = path.join( repoPath, dest ); - console.log( "... copying " + src + " to " + dest ); - if ( remoteKey === "test" ) { - const destParent = path.dirname( destPath ); - fs.mkdirSync( destParent, { recursive: true } ); - } - fs.copyFileSync( srcPath, destPath, fs.constants.COPYFILE_EXCL ); - toCommit.push( dest ); - } + const toCommit = []; + for (const src in files) { + const srcPath = path.join(__dirname, '..', src); + const dest = files[src].replace('@VERSION', version); + const destPath = path.join(repoPath, dest); + console.log('... copying ' + src + ' to ' + dest); + if (remoteKey === 'test') { + const destParent = path.dirname(destPath); + fs.mkdirSync(destParent, { recursive: true }); + } + fs.copyFileSync(srcPath, destPath, fs.constants.COPYFILE_EXCL); + toCommit.push(dest); + } - console.log( "... creating commit" ); - cp.execFileSync( "git", [ - "add", - ...toCommit - ], { cwd: repoPath } ); - cp.execFileSync( "git", [ - "commit", - "-m", - commitMessage.replace( "@VERSION", version ) - ], { cwd: repoPath } ); - - } + console.log('... creating commit'); + cp.execFileSync('git', [ + 'add', + ...toCommit + ], { cwd: repoPath }); + cp.execFileSync('git', [ + 'commit', + '-m', + commitMessage.replace('@VERSION', version) + ], { cwd: repoPath }); + } }; -const [ remote, version ] = process.argv.slice( 2 ); +const [remote, version] = process.argv.slice(2); try { - Cdn.commit( remote, version ); -} catch ( e ) { - console.error( e.toString() ); - process.exit( 1 ); + Cdn.commit(remote, version); +} catch (e) { + console.error(e.toString()); + process.exit(1); } diff --git a/build/coverage-bridge.js b/build/coverage-bridge.js index 26dd42051..1ca8d0fbe 100644 --- a/build/coverage-bridge.js +++ b/build/coverage-bridge.js @@ -1,23 +1,21 @@ /* global QUnit, window, define, require */ -( function( factory ) { +(function (factory) { + // For the amd tests we need to delay setup + if (typeof define === 'function' && define.amd) { + require(['qunit'], factory); + } else { + factory(QUnit); + } +}(function (QUnit) { + function sendMessage () { + window.__grunt_contrib_qunit__.apply(window, arguments); + } - // For the amd tests we need to delay setup - if ( typeof define === "function" && define.amd ) { - require( [ "qunit" ], factory ); - } else { - factory( QUnit ); - } -}( function( QUnit ) { - function sendMessage() { - window.__grunt_contrib_qunit__.apply( window, arguments ); - } - - QUnit.done( function() { - - // send coverage data if available - if ( window.__coverage__ ) { - sendMessage( "qunit.coverage", window.location.pathname, window.__coverage__ ); - } - } ); -} ) ); + QUnit.done(function () { + // send coverage data if available + if (window.__coverage__) { + sendMessage('qunit.coverage', window.location.pathname, window.__coverage__); + } + }); +})); diff --git a/build/dist-replace.js b/build/dist-replace.js index cb43a347b..250b30f4c 100644 --- a/build/dist-replace.js +++ b/build/dist-replace.js @@ -6,36 +6,35 @@ // and update RELEASE.md as needed. // . -let distVersion = require( "../package.json" ).version; +let distVersion = require('../package.json').version; -if ( /pre/.test( distVersion ) ) { - - // During development, include a timestamp. - distVersion += " " + ( new Date() ).toISOString().replace( /:\d+\.\d+Z$/, "Z" ); +if (/pre/.test(distVersion)) { + // During development, include a timestamp. + distVersion += ' ' + (new Date()).toISOString().replace(/:\d+\.\d+Z$/, 'Z'); } const replacements = { - // Normalize CRLF from fuzzysort.js. - // - // The way we upload files to npm, Git, and the jQuery CDN all normalize - // CRLF to LF. Thus, if we don't do this ourselves during the build, then - // reproducible build verification would find that the distribution is - // not identical to the reproduced build artefact. - "\r\n": "\n", + // Normalize CRLF from fuzzysort.js. + // + // The way we upload files to npm, Git, and the jQuery CDN all normalize + // CRLF to LF. Thus, if we don't do this ourselves during the build, then + // reproducible build verification would find that the distribution is + // not identical to the reproduced build artefact. + '\r\n': '\n', - // Embed version - "@VERSION": distVersion + // Embed version + '@VERSION': distVersion }; -function preprocess( code ) { - for ( const [ find, replacement ] of Object.entries( replacements ) ) { - code = code.replace( find, replacement ); - } - return code; +function preprocess (code) { + for (const [find, replacement] of Object.entries(replacements)) { + code = code.replace(find, replacement); + } + return code; } module.exports = { - replacements, - preprocess + replacements, + preprocess }; diff --git a/build/prep-release.js b/build/prep-release.js index 87ee4744c..6bfc9ac39 100644 --- a/build/prep-release.js +++ b/build/prep-release.js @@ -4,128 +4,127 @@ // // Inspired by . -/* eslint-env node */ - -const fs = require( "fs" ); -const path = require( "path" ); -const util = require( "util" ); -const cp = require( "child_process" ); -const gitAuthors = require( "grunt-git-authors" ); - -function parseLineResults( output = "" ) { - output = output.trim(); - return !output ? [] : output.split( "\n" ); +const fs = require('fs'); +const path = require('path'); +const util = require('util'); +const cp = require('child_process'); +const gitAuthors = require('grunt-git-authors'); + +function parseLineResults (output = '') { + output = output.trim(); + return !output ? [] : output.split('\n'); } -function versionAddedString( version ) { - return `version_added: "${version}"`; +function versionAddedString (version) { + return `version_added: "${version}"`; } const Repo = { - async prep( version ) { - if ( typeof version !== "string" || !/^\d+\.\d+\.\d+$/.test( version ) ) { - throw new Error( "Invalid or missing version argument" ); - } - { - const UNRELEASED_VERSION = versionAddedString( "unreleased" ); - - // grep exits non-zero if no results - const results = parseLineResults( cp.execSync( - `grep -l '${UNRELEASED_VERSION}' docs/**/*.md || echo`, - { encoding: "utf8" } - ) ); - results.forEach( filePath => { - const doc = fs.readFileSync( filePath, "utf8" ); - fs.writeFileSync( filePath, - doc.replace( UNRELEASED_VERSION, versionAddedString( version ) ) - ); - } ); - } - { - const file = "History.md"; - console.log( `Updating ${file}...` ); - - const filePath = path.join( __dirname, "..", file ); - const oldContent = fs.readFileSync( filePath, "utf8" ); - const oldVersion = require( "../package.json" ).version.replace( /-.*$/, "" ); - - const changeFilter = [ - /^\* Release \d+\./, - /^\* Build: /, - /^\* Docs: /, - /^\* Tests: / - ]; - let changes = cp.execFileSync( "git", [ - "log", - "--format=* %s. (%aN)", - "--no-merges", - `${oldVersion}...HEAD` - ], { encoding: "utf8" } ); - - changes = parseLineResults( changes ) - .filter( line => !changeFilter.some( filter => filter.test( line ) ) ) - .map( line => { - return { - component: line.replace( /^\* ([^:]+):.*$/, "$1" ), - line - }; - } ) - .sort() - .map( change => change.line ) - .join( "\n" ) + - "\n"; - - const today = new Date().toISOString().slice( 0, 10 ); - const newSection = `\n${version} / ${today} - ================== - - ### Added - - ### Changed - - ### Deprecated - - ### Fixed - - ### Removed - - `.replace( /^[ \t]*/gm, "" ); - - fs.writeFileSync( filePath, newSection + changes + oldContent ); - } - { - const file = "package.json"; - console.log( `Updating ${file}...` ); - const filePath = path.join( __dirname, "..", file ); - const json = fs.readFileSync( filePath, "utf8" ); - const packageIndentation = json.match( /\n([\t\s]+)/ )[ 1 ]; - const data = JSON.parse( json ); - - data.version = `${version}-pre`; - - fs.writeFileSync( - filePath, - JSON.stringify( data, null, packageIndentation ) + "\n" - ); - } - { - const file = "AUTHORS.txt"; - console.log( `Updating ${file}...` ); - const updateAuthors = util.promisify( gitAuthors.updateAuthors ); - await updateAuthors( { - dir: path.dirname( __dirname ), - filename: file, - banner: "Authors ordered by first contribution" - } ); - } - } + async prep (version) { + if (typeof version !== 'string' || !/^\d+\.\d+\.\d+$/.test(version)) { + throw new Error('Invalid or missing version argument'); + } + { + const UNRELEASED_VERSION = versionAddedString('unreleased'); + + // grep exits non-zero if no results + const results = parseLineResults(cp.execSync( + `grep -l '${UNRELEASED_VERSION}' docs/**/*.md || echo`, + { encoding: 'utf8' } + )); + results.forEach(filePath => { + const doc = fs.readFileSync(filePath, 'utf8'); + fs.writeFileSync(filePath, + doc.replace(UNRELEASED_VERSION, versionAddedString(version)) + ); + }); + } + { + const file = 'History.md'; + console.log(`Updating ${file}...`); + + const filePath = path.join(__dirname, '..', file); + const oldContent = fs.readFileSync(filePath, 'utf8'); + const oldVersion = require('../package.json').version.replace(/-.*$/, ''); + + const changeFilter = [ + /^\* Release \d+\./, + /^\* Build: /, + /^\* Docs: /, + /^\* Test: /, + /^\* Tests: / + ]; + let changes = cp.execFileSync('git', [ + 'log', + '--format=* %s. (%aN)', + '--no-merges', + `${oldVersion}...HEAD` + ], { encoding: 'utf8' }); + + changes = parseLineResults(changes) + .filter(line => !changeFilter.some(filter => filter.test(line))) + .map(line => { + return { + component: line.replace(/^\* ([^:]+):.*$/, '$1'), + line + }; + }) + .sort() + .map(change => change.line) + .join('\n') + + '\n'; + + const today = new Date().toISOString().slice(0, 10); + const newSection = `\n${version} / ${today} + ================== + + ### Added + + ### Changed + + ### Deprecated + + ### Fixed + + ### Removed + + `.replace(/^[ \t]*/gm, ''); + + fs.writeFileSync(filePath, newSection + changes + oldContent); + } + { + const file = 'package.json'; + console.log(`Updating ${file}...`); + const filePath = path.join(__dirname, '..', file); + const json = fs.readFileSync(filePath, 'utf8'); + const packageIndentation = json.match(/\n([\t\s]+)/)[1]; + const data = JSON.parse(json); + + data.version = `${version}-pre`; + + fs.writeFileSync( + filePath, + JSON.stringify(data, null, packageIndentation) + '\n' + ); + } + { + const file = 'AUTHORS.txt'; + console.log(`Updating ${file}...`); + const updateAuthors = util.promisify(gitAuthors.updateAuthors); + await updateAuthors({ + dir: path.dirname(__dirname), + filename: file, + banner: 'Authors ordered by first contribution' + }); + } + } }; -const version = process.argv[ 2 ]; +const version = process.argv[2]; -( async function main() { - await Repo.prep( version ); -}() ).catch( e => { - console.error( e.toString() ); - process.exit( 1 ); -} ); +(async function main () { + await Repo.prep(version); +}()).catch(e => { + console.error(e.toString()); + process.exit(1); +}); diff --git a/build/reproducible-builds.js b/build/reproducible-builds.js index 21f550e30..677b84b32 100644 --- a/build/reproducible-builds.js +++ b/build/reproducible-builds.js @@ -1,14 +1,14 @@ // Helper for the "Reproducible builds" job. -const cp = require( "child_process" ); -const fs = require( "fs" ); -const path = require( "path" ); -const util = require( "util" ); +const cp = require('child_process'); +const fs = require('fs'); +const path = require('path'); +const util = require('util'); -const utils = require( "./utils.js" ); -const execFile = util.promisify( cp.execFile ); -const tempDir = path.join( __dirname, "../temp", "reproducible-builds" ); -const SRC_REPO = "https://github.com/qunitjs/qunit.git"; +const utils = require('./utils.js'); +const execFile = util.promisify(cp.execFile); +const tempDir = path.join(__dirname, '../temp', 'reproducible-builds'); +const SRC_REPO = 'https://github.com/qunitjs/qunit.git'; /** * How many past releases to verify. @@ -27,163 +27,166 @@ const SRC_REPO = "https://github.com/qunitjs/qunit.git"; */ const VERIFY_COUNT = 2; -async function buildRelease( version, cacheDir = null ) { - console.log( `... ${version}: checking out the source` ); - - const gitDir = path.join( tempDir, `git-${version}` ); - utils.cleanDir( gitDir ); - - await execFile( "git", [ "clone", "-q", "-b", version, "--depth=5", SRC_REPO, gitDir ] ); - - // Remove any artefacts that were checked into Git - utils.cleanDir( gitDir + "/qunit/" ); - - // Use sync for npm-ci to avoid concurrency bugs with shared cache - console.log( `... ${version}: installing development dependencies from npm` ); - cp.execFileSync( "npm", [ "ci" ], { - env: { - npm_config_cache: cacheDir, - npm_config_update_notifier: "false", - PATH: process.env.PATH, - PUPPETEER_DOWNLOAD_PATH: path.join( cacheDir, "puppeteer_download" ) - }, - cwd: gitDir - } ); - - console.log( `... ${version}: building release` ); - await execFile( "npm", [ "run", "build" ], { - env: { - PATH: process.env.PATH - }, - cwd: gitDir - } ); - - return { - js: fs.readFileSync( gitDir + "/qunit/qunit.js", "utf8" ), - css: fs.readFileSync( gitDir + "/qunit/qunit.css", "utf8" ) - }; +async function buildRelease (version, cacheDir = null) { + console.log(`... ${version}: checking out the source`); + + const gitDir = path.join(tempDir, `git-${version}`); + utils.cleanDir(gitDir); + + await execFile('git', ['clone', '-q', '-b', version, '--depth=5', SRC_REPO, gitDir]); + + // Remove any artefacts that were checked into Git + utils.cleanDir(gitDir + '/qunit/'); + + // Use sync for npm-ci to avoid concurrency bugs with shared cache + console.log(`... ${version}: installing development dependencies from npm`); + cp.execFileSync('npm', ['ci'], { + env: { + npm_config_cache: cacheDir, + npm_config_update_notifier: 'false', + PATH: process.env.PATH, + PUPPETEER_DOWNLOAD_PATH: path.join(cacheDir, 'puppeteer_download') + }, + cwd: gitDir + }); + + console.log(`... ${version}: building release`); + await execFile('npm', ['run', 'build'], { + env: { + PATH: process.env.PATH + }, + cwd: gitDir + }); + + return { + js: fs.readFileSync(gitDir + '/qunit/qunit.js', 'utf8'), + css: fs.readFileSync(gitDir + '/qunit/qunit.css', 'utf8') + }; } const Reproducible = { - async fetch() { - - // Keep the stuff that matters in memory. Below, we will run unaudited npm dev deps - // as part of build commands, which can modify anything on disk. - const releases = {}; - - { - console.log( "Setting up temp directory..." ); - - // This can take a while when running it locally (not CI), - // as it first need to remove any old builds. - utils.cleanDir( tempDir ); - } - { - console.log( "Fetching releases from jQuery CDN..." ); - const cdnIndexUrl = "https://releases.jquery.com/resources/cdn.json"; - const data = JSON.parse( await utils.download( cdnIndexUrl ) ); - - for ( const release of data.qunit.all.slice( 0, VERIFY_COUNT ) ) { - releases[ release.version ] = { - cdn: { - js: await utils.download( `https://code.jquery.com/${release.filename}` ), - css: await utils.download( `https://code.jquery.com/${release.theme}` ) - } - }; - } - } - { - console.log( "Fetching releases from npmjs.org..." ); - const npmIndexUrl = "https://registry.npmjs.org/qunit"; - const data = JSON.parse( await utils.download( npmIndexUrl ) ); - - for ( const version of Object.keys( data.versions ).slice( -VERIFY_COUNT ) ) { - if ( !releases[ version ] ) { - releases[ version ] = {}; - } - - const tarball = data.versions[ version ].dist.tarball; - const tarFile = path.join( tempDir, `npm-${version}${path.extname( tarball )}` ); - await utils.downloadFile( tarball, tarFile ); - - releases[ version ].npm = { - js: cp.execFileSync( - "tar", [ "-xOf", tarFile, "package/qunit/qunit.js" ], - { encoding: "utf8" } - ), - css: cp.execFileSync( - "tar", [ "-xOf", tarFile, "package/qunit/qunit.css" ], - { encoding: "utf8" } - ) - }; - } - } - { - console.log( "Reproducing release builds..." ); - - const cacheDir = path.join( tempDir, "cache" ); - utils.cleanDir( cacheDir ); - - // Start the builds in parallel and await results. - // Let the first error propagate and ignore others (avoids "Unhandled rejection" later). - const buildPromises = []; - for ( const version in releases ) { - releases[ version ].buildPromise = buildRelease( version, cacheDir ); - buildPromises.push( releases[ version ].buildPromise ); - } - await Promise.all( buildPromises ); - - const diffs = []; - for ( const version in releases ) { - const release = releases[ version ]; - const build = await release.buildPromise; - - // For qunit@2.15.0, normalize CRLF to match what Git and npm did during upload. - if ( version === "2.15.0" ) { - build.js = utils.normalizeEOL( build.js ); - } - - let verified = true; - for ( const distro of [ "cdn", "npm" ] ) { - for ( const file of [ "js", "css" ] ) { - if ( release[ distro ][ file ] !== build[ file ] ) { - verified = false; - console.error( - `QUnit ${version} ${file} from ${distro} differs from build` - ); - diffs.push( [ - { name: `qunit-${version}-build.${file}`, - contents: build[ file ] }, - { name: `qunit-${version}-${distro}.${file}`, - contents: release[ distro ][ file ] } - ] ); - } - } - } - if ( verified ) { - console.log( `QUnit ${version} is reproducible and matches distributions!` ); - } - } - - diffs.forEach( diff => { - const fromFile = path.join( tempDir, diff[ 0 ].name ); - const toFile = path.join( tempDir, diff[ 1 ].name ); - fs.writeFileSync( fromFile, utils.verboseNonPrintable( diff[ 0 ].contents ) ); - fs.writeFileSync( toFile, utils.verboseNonPrintable( diff[ 1 ].contents ) ); - process.stdout.write( - utils.getDiff( fromFile, toFile, { ignoreWhitespace: false } ) - ); - } ); - if ( diffs.length ) { - throw new Error( "One or more distributions differ from the reproduced build" ); - } - } - } + async fetch () { + // Keep the stuff that matters in memory. Below, we will run unaudited npm dev deps + // as part of build commands, which can modify anything on disk. + const releases = {}; + + { + console.log('Setting up temp directory...'); + + // This can take a while when running it locally (not CI), + // as it first need to remove any old builds. + utils.cleanDir(tempDir); + } + { + console.log('Fetching releases from jQuery CDN...'); + const cdnIndexUrl = 'https://releases.jquery.com/resources/cdn.json'; + const data = JSON.parse(await utils.download(cdnIndexUrl)); + + for (const release of data.qunit.all.slice(0, VERIFY_COUNT)) { + releases[release.version] = { + cdn: { + js: await utils.download(`https://code.jquery.com/${release.filename}`), + css: await utils.download(`https://code.jquery.com/${release.theme}`) + } + }; + } + } + { + console.log('Fetching releases from npmjs.org...'); + const npmIndexUrl = 'https://registry.npmjs.org/qunit'; + const data = JSON.parse(await utils.download(npmIndexUrl)); + + for (const version of Object.keys(data.versions).slice(-VERIFY_COUNT)) { + if (!releases[version]) { + releases[version] = {}; + } + + const tarball = data.versions[version].dist.tarball; + const tarFile = path.join(tempDir, `npm-${version}${path.extname(tarball)}`); + await utils.downloadFile(tarball, tarFile); + + releases[version].npm = { + js: cp.execFileSync( + 'tar', ['-xOf', tarFile, 'package/qunit/qunit.js'], + { encoding: 'utf8' } + ), + css: cp.execFileSync( + 'tar', ['-xOf', tarFile, 'package/qunit/qunit.css'], + { encoding: 'utf8' } + ) + }; + } + } + { + console.log('Reproducing release builds...'); + + const cacheDir = path.join(tempDir, 'cache'); + utils.cleanDir(cacheDir); + + // Start the builds in parallel and await results. + // Let the first error propagate and ignore others (avoids "Unhandled rejection" later). + const buildPromises = []; + for (const version in releases) { + releases[version].buildPromise = buildRelease(version, cacheDir); + buildPromises.push(releases[version].buildPromise); + } + await Promise.all(buildPromises); + + const diffs = []; + for (const version in releases) { + const release = releases[version]; + const build = await release.buildPromise; + + // For qunit@2.15.0, normalize CRLF to match what Git and npm did during upload. + if (version === '2.15.0') { + build.js = utils.normalizeEOL(build.js); + } + + let verified = true; + for (const distro of ['cdn', 'npm']) { + for (const file of ['js', 'css']) { + if (release[distro][file] !== build[file]) { + verified = false; + console.error( + `QUnit ${version} ${file} from ${distro} differs from build` + ); + diffs.push([ + { + name: `qunit-${version}-build.${file}`, + contents: build[file] + }, + { + name: `qunit-${version}-${distro}.${file}`, + contents: release[distro][file] + } + ]); + } + } + } + if (verified) { + console.log(`QUnit ${version} is reproducible and matches distributions!`); + } + } + + diffs.forEach(diff => { + const fromFile = path.join(tempDir, diff[0].name); + const toFile = path.join(tempDir, diff[1].name); + fs.writeFileSync(fromFile, utils.verboseNonPrintable(diff[0].contents)); + fs.writeFileSync(toFile, utils.verboseNonPrintable(diff[1].contents)); + process.stdout.write( + utils.getDiff(fromFile, toFile, { ignoreWhitespace: false }) + ); + }); + if (diffs.length) { + throw new Error('One or more distributions differ from the reproduced build'); + } + } + } }; -( async function main() { - await Reproducible.fetch(); -}() ).catch( e => { - console.error( e.toString() ); - process.exit( 1 ); -} ); +(async function main () { + await Reproducible.fetch(); +}()).catch(e => { + console.error(e.toString()); + process.exit(1); +}); diff --git a/build/review-package.js b/build/review-package.js index 7bd74830a..991581298 100644 --- a/build/review-package.js +++ b/build/review-package.js @@ -4,78 +4,78 @@ /* eslint-env node */ -const cp = require( "child_process" ); -const fs = require( "fs" ); -const path = require( "path" ); -const readline = require( "readline" ); -const { getDiff, downloadFile } = require( "./utils.js" ); +const cp = require('child_process'); +const fs = require('fs'); +const path = require('path'); +const readline = require('readline'); +const { getDiff, downloadFile } = require('./utils.js'); -async function confirm( text ) { - const rl = readline.createInterface( { input: process.stdin, output: process.stdout } ); - await new Promise( ( resolve, reject ) => { - rl.question( `${text} (y/N)> `, ( answer ) => { - rl.close(); - if ( String( answer ).toLowerCase() === "y" ) { - resolve(); - } else { - reject( new Error( "Audit aborted" ) ); - } - } ); - } ); +async function confirm (text) { + const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); + await new Promise((resolve, reject) => { + rl.question(`${text} (y/N)> `, (answer) => { + rl.close(); + if (String(answer).toLowerCase() === 'y') { + resolve(); + } else { + reject(new Error('Audit aborted')); + } + }); + }); } const ReleaseAssets = { - async audit( prevVersion ) { - if ( typeof prevVersion !== "string" || !/^\d+\.\d+\.\d+$/.test( prevVersion ) ) { - throw new Error( "Invalid or missing version argument" ); - } - { - const file = "package.json"; - console.log( `Auditing ${file}...` ); + async audit (prevVersion) { + if (typeof prevVersion !== 'string' || !/^\d+\.\d+\.\d+$/.test(prevVersion)) { + throw new Error('Invalid or missing version argument'); + } + { + const file = 'package.json'; + console.log(`Auditing ${file}...`); - const prevContent = cp.execFileSync( "git", [ - "show", - `${prevVersion}:package.json` - ], { encoding: "utf8" } ); - const tempPrevPath = path.join( __dirname, "../temp", file ); - fs.writeFileSync( tempPrevPath, prevContent ); + const prevContent = cp.execFileSync('git', [ + 'show', + `${prevVersion}:package.json` + ], { encoding: 'utf8' }); + const tempPrevPath = path.join(__dirname, '../temp', file); + fs.writeFileSync(tempPrevPath, prevContent); - const currentPath = path.join( __dirname, "..", file ); - process.stdout.write( getDiff( tempPrevPath, currentPath ) ); - await confirm( `Accept ${file}?` ); - } - { - const file = "qunit.js"; - console.log( `Auditing ${file}...` ); + const currentPath = path.join(__dirname, '..', file); + process.stdout.write(getDiff(tempPrevPath, currentPath)); + await confirm(`Accept ${file}?`); + } + { + const file = 'qunit.js'; + console.log(`Auditing ${file}...`); - const prevUrl = `https://code.jquery.com/qunit/qunit-${prevVersion}.js`; - const tempPrevPath = path.join( __dirname, "../temp", file ); - await downloadFile( prevUrl, tempPrevPath ); + const prevUrl = `https://code.jquery.com/qunit/qunit-${prevVersion}.js`; + const tempPrevPath = path.join(__dirname, '../temp', file); + await downloadFile(prevUrl, tempPrevPath); - const currentPath = path.join( __dirname, "../qunit", file ); - process.stdout.write( getDiff( tempPrevPath, currentPath ) ); - await confirm( `Accept ${file}?` ); - } - { - const file = "qunit.css"; - console.log( `Auditing ${file}...` ); + const currentPath = path.join(__dirname, '../qunit', file); + process.stdout.write(getDiff(tempPrevPath, currentPath)); + await confirm(`Accept ${file}?`); + } + { + const file = 'qunit.css'; + console.log(`Auditing ${file}...`); - const prevUrl = `https://code.jquery.com/qunit/qunit-${prevVersion}.css`; - const tempPrevPath = path.join( __dirname, "../temp", file ); - await downloadFile( prevUrl, tempPrevPath ); + const prevUrl = `https://code.jquery.com/qunit/qunit-${prevVersion}.css`; + const tempPrevPath = path.join(__dirname, '../temp', file); + await downloadFile(prevUrl, tempPrevPath); - const currentPath = path.join( __dirname, "../qunit", file ); - process.stdout.write( getDiff( tempPrevPath, currentPath ) ); - await confirm( `Accept ${file}?` ); - } - } + const currentPath = path.join(__dirname, '../qunit', file); + process.stdout.write(getDiff(tempPrevPath, currentPath)); + await confirm(`Accept ${file}?`); + } + } }; -const prevVersion = process.argv[ 2 ]; +const prevVersion = process.argv[2]; -( async function main() { - await ReleaseAssets.audit( prevVersion ); -}() ).catch( e => { - console.error( e.toString() ); - process.exit( 1 ); -} ); +(async function main () { + await ReleaseAssets.audit(prevVersion); +}()).catch(e => { + console.error(e.toString()); + process.exit(1); +}); diff --git a/build/set-release.js b/build/set-release.js index 3f73f5ee4..f89df3ad4 100644 --- a/build/set-release.js +++ b/build/set-release.js @@ -4,52 +4,53 @@ // // Inspired by . -/* eslint-env node */ - -const fs = require( "fs" ); +const fs = require('fs'); +const path = require('path'); const Repo = { - setFiles( version ) { - if ( typeof version !== "string" || !/^\d+\.\d+\.\d+$/.test( version ) ) { - throw new Error( "Invalid or missing version argument" ); - } - { - const file = "bower.json"; - console.log( `Updating ${file}...` ); - const json = fs.readFileSync( __dirname + "/../" + file, "utf8" ); - const packageIndentation = json.match( /\n([\t\s]+)/ )[ 1 ]; - const data = JSON.parse( json ); - - data.version = version; - - fs.writeFileSync( - __dirname + "/../" + file, - JSON.stringify( data, null, packageIndentation ) + "\n" - ); - } - { - const file = "package.json"; - console.log( `Updating ${file}...` ); - const json = fs.readFileSync( __dirname + "/../" + file, "utf8" ); - const packageIndentation = json.match( /\n([\t\s]+)/ )[ 1 ]; - const data = JSON.parse( json ); - - data.version = version; - data.author.url = data.author.url.replace( "main", version ); - - fs.writeFileSync( - __dirname + "/../" + file, - JSON.stringify( data, null, packageIndentation ) + "\n" - ); - } - } + setFiles (version) { + if (typeof version !== 'string' || !/^\d+\.\d+\.\d+$/.test(version)) { + throw new Error('Invalid or missing version argument'); + } + { + const file = 'bower.json'; + console.log(`Updating ${file}...`); + const filePath = path.join(__dirname, '..', file); + const json = fs.readFileSync(filePath, 'utf8'); + const packageIndentation = json.match(/\n([\t\s]+)/)[1]; + const data = JSON.parse(json); + + data.version = version; + + fs.writeFileSync( + filePath, + JSON.stringify(data, null, packageIndentation) + '\n' + ); + } + { + const file = 'package.json'; + console.log(`Updating ${file}...`); + const filePath = path.join(__dirname, '..', file); + const json = fs.readFileSync(filePath, 'utf8'); + const packageIndentation = json.match(/\n([\t\s]+)/)[1]; + const data = JSON.parse(json); + + data.version = version; + data.author.url = data.author.url.replace('main', version); + + fs.writeFileSync( + filePath, + JSON.stringify(data, null, packageIndentation) + '\n' + ); + } + } }; -const version = process.argv[ 2 ]; +const version = process.argv[2]; try { - Repo.setFiles( version ); -} catch ( e ) { - console.error( e.toString() ); - process.exit( 1 ); + Repo.setFiles(version); +} catch (e) { + console.error(e.toString()); + process.exit(1); } diff --git a/build/tasks/test-on-node.js b/build/tasks/test-on-node.js index 9f3fe3a9b..5f87be5aa 100644 --- a/build/tasks/test-on-node.js +++ b/build/tasks/test-on-node.js @@ -1,101 +1,98 @@ -/* eslint-env node */ -"use strict"; - -module.exports = function( grunt ) { - grunt.registerMultiTask( "test-on-node", async function() { - var done = this.async(); - - const totals = { files: 0, failed: 0 }; - for ( const file of this.data ) { - totals.failed += await new Promise( resolve => runQUnit( file, resolve ) ); - totals.files++; - } - - if ( totals.failed ) { - grunt.log.error( `Ran ${totals.files} files, ${totals.failed} failed tests` ); - } else { - grunt.log.ok( `Ran ${totals.files} files` ); - } - - // Refresh the QUnit global to be used in other tests - global.QUnit = requireFresh( "../../qunit/qunit.js" ); - - done( !totals.failed ); - } ); - - function requireFresh( path ) { - var resolvedPath = require.resolve( path ); - delete require.cache[ resolvedPath ]; - return require( path ); - } - - function runQUnit( file, runEnd ) { - - // Resolve current QUnit path and remove it from the require cache - // to avoid stacking the QUnit logs. - var QUnit = requireFresh( "../../qunit/qunit.js" ); - - // Expose QUnit to the global scope to be seen on the other tests. - global.QUnit = QUnit; - - QUnit.config.autostart = false; - requireFresh( "../../" + file ); - registerEvents( QUnit, file, runEnd ); - QUnit.start(); - } - - function registerEvents( QUnit, fileName, callback ) { - - // Silence deprecation warnings - const warn = process.stderr.write; - process.stderr.write = function() {}; - function restore() { - process.stderr.write = warn; - } - - // These tests are relatively short. Buffer the output so that we - // only have to restore once, and can craft more condensed output. - let out = ""; - - QUnit.on( "runStart", () => { - out += "Testing " + fileName; - } ); - - QUnit.on( "testEnd", ( testEnd ) => { - if ( testEnd.status === "todo" ) { - return; - } - testEnd.errors.forEach( ( assertion ) => { - out += `\n\ntest: ${testEnd.name}\n` + - `module: ${testEnd.suiteName}\n` + - `message: ${assertion.message}\n${assertion.stack || ""}`; - } ); - } ); - - QUnit.on( "runEnd", ( suiteEnd ) => { - restore(); - const stats = suiteEnd.testCounts; - - if ( suiteEnd.status === "failed" ) { - out += "\n" + `${stats.total} tests in ${suiteEnd.runtime}ms`; - out += `, ${stats.passed} passed`; - if ( stats.failed ) { - out += `, ${stats.failed} failed`; - } - if ( stats.skipped ) { - out += `, ${stats.skipped} skipped`; - } - if ( stats.todo ) { - out += `, ${stats.todo} todo`; - } - out += "."; - grunt.log.error( out ); - } else { - out += ` (${stats.total} tests in ${suiteEnd.runtime}ms)`; - grunt.log.writeln( out ); - } - - callback( stats.failed ); - } ); - } +'use strict'; + +module.exports = function (grunt) { + grunt.registerMultiTask('test-on-node', async function () { + let done = this.async(); + + const totals = { files: 0, failed: 0 }; + for (const file of this.data) { + totals.failed += await new Promise(resolve => runQUnit(file, resolve)); + totals.files++; + } + + if (totals.failed) { + grunt.log.error(`Ran ${totals.files} files, ${totals.failed} failed tests`); + } else { + grunt.log.ok(`Ran ${totals.files} files`); + } + + // Refresh the QUnit global to be used in other tests + global.QUnit = requireFresh('../../qunit/qunit.js'); + + done(!totals.failed); + }); + + function requireFresh (path) { + let resolvedPath = require.resolve(path); + delete require.cache[resolvedPath]; + return require(path); + } + + function runQUnit (file, runEnd) { + // Resolve current QUnit path and remove it from the require cache + // to avoid stacking the QUnit logs. + let QUnit = requireFresh('../../qunit/qunit.js'); + + // Expose QUnit to the global scope to be seen on the other tests. + global.QUnit = QUnit; + + QUnit.config.autostart = false; + requireFresh('../../' + file); + registerEvents(QUnit, file, runEnd); + QUnit.start(); + } + + function registerEvents (QUnit, fileName, callback) { + // Silence deprecation warnings + const warn = process.stderr.write; + process.stderr.write = function () {}; + function restore () { + process.stderr.write = warn; + } + + // These tests are relatively short. Buffer the output so that we + // only have to restore once, and can craft more condensed output. + let out = ''; + + QUnit.on('runStart', () => { + out += 'Testing ' + fileName; + }); + + QUnit.on('testEnd', (testEnd) => { + if (testEnd.status === 'todo') { + return; + } + testEnd.errors.forEach((assertion) => { + out += `\n\ntest: ${testEnd.name}\n` + + `module: ${testEnd.suiteName}\n` + + `message: ${assertion.message}\n${assertion.stack || ''}`; + }); + }); + + QUnit.on('runEnd', (suiteEnd) => { + restore(); + const stats = suiteEnd.testCounts; + + if (suiteEnd.status === 'failed') { + out += '\n' + `${stats.total} tests in ${suiteEnd.runtime}ms`; + out += `, ${stats.passed} passed`; + if (stats.failed) { + out += `, ${stats.failed} failed`; + } + if (stats.skipped) { + out += `, ${stats.skipped} skipped`; + } + if (stats.todo) { + out += `, ${stats.todo} todo`; + } + out += '.'; + grunt.log.error(out); + } else { + out += ` (${stats.total} tests in ${suiteEnd.runtime}ms)`; + grunt.log.writeln(out); + } + + callback(stats.failed); + }); + } }; diff --git a/build/utils.js b/build/utils.js index 2f8776f85..f3e4cd4ef 100644 --- a/build/utils.js +++ b/build/utils.js @@ -1,64 +1,62 @@ -const cp = require( "child_process" ); -const fs = require( "fs" ); -const https = require( "https" ); +const cp = require('child_process'); +const fs = require('fs'); +const https = require('https'); -function getDiff( from, to, options = {} ) { +function getDiff (from, to, options = {}) { + // macOS 10.15+ comes with GNU diff (2.8) + // https://unix.stackexchange.com/a/338960/37512 + // https://stackoverflow.com/a/41770560/319266 + const gnuDiffVersion = cp.execFileSync('diff', ['--version'], { encoding: 'utf8' }); + const versionStr = /diff.* (\d+\.\d+)/.exec(gnuDiffVersion); + const isOld = (versionStr && Number(versionStr[1]) < 3.4); - // macOS 10.15+ comes with GNU diff (2.8) - // https://unix.stackexchange.com/a/338960/37512 - // https://stackoverflow.com/a/41770560/319266 - const gnuDiffVersion = cp.execFileSync( "diff", [ "--version" ], { encoding: "utf8" } ); - const versionStr = /diff.* (\d+\.\d+)/.exec( gnuDiffVersion ); - const isOld = ( versionStr && Number( versionStr[ 1 ] ) < 3.4 ); - - try { - cp.execFileSync( "diff", [ - ...( options.ignoreWhitespace !== false ? [ "-w" ] : [] ), - "--text", - "--unified", - ...( isOld ? [] : [ "--color=always" ] ), - from, - to - ], { encoding: "utf8" } ); - } catch ( e ) { - - // Expected, `diff` command yields non-zero exit status if files differ - return e.stdout; - } - throw new Error( `Unable to diff between ${from} and ${to}` ); + try { + cp.execFileSync('diff', [ + ...(options.ignoreWhitespace !== false ? ['-w'] : []), + '--text', + '--unified', + ...(isOld ? [] : ['--color=always']), + from, + to + ], { encoding: 'utf8' }); + } catch (e) { + // Expected, `diff` command yields non-zero exit status if files differ + return e.stdout; + } + throw new Error(`Unable to diff between ${from} and ${to}`); } -async function download( url ) { - return new Promise( ( resolve, reject ) => { - https.get( url, async resp => { - try { - const chunks = []; - for await ( const chunk of resp ) { - chunks.push( Buffer.from( chunk ) ); - } - resolve( Buffer.concat( chunks ).toString( "utf8" ) ); - } catch ( err ) { - reject( err ); - } - } ); - } ); +async function download (url) { + return new Promise((resolve, reject) => { + https.get(url, async resp => { + try { + const chunks = []; + for await (const chunk of resp) { + chunks.push(Buffer.from(chunk)); + } + resolve(Buffer.concat(chunks).toString('utf8')); + } catch (err) { + reject(err); + } + }); + }); } -async function downloadFile( url, dest ) { - const fileStr = fs.createWriteStream( dest ); - return new Promise( ( resolve, reject ) => { - https.get( url, resp => { - resp.pipe( fileStr ); - fileStr.on( "finish", () => fileStr.close( resolve ) ); - } ).on( "error", err => reject( err ) ); - } ); +async function downloadFile (url, dest) { + const fileStr = fs.createWriteStream(dest); + return new Promise((resolve, reject) => { + https.get(url, resp => { + resp.pipe(fileStr); + fileStr.on('finish', () => fileStr.close(resolve)); + }).on('error', err => reject(err)); + }); } -function cleanDir( dirPath ) { - if ( fs.existsSync( dirPath ) ) { - fs.rmdirSync( dirPath, { recursive: true } ); - } - fs.mkdirSync( dirPath, { recursive: true } ); +function cleanDir (dirPath) { + if (fs.existsSync(dirPath)) { + fs.rmdirSync(dirPath, { recursive: true }); + } + fs.mkdirSync(dirPath, { recursive: true }); } // Turn invisible chars and non-ASCII chars into escape sequences. @@ -66,25 +64,23 @@ function cleanDir( dirPath ) { // This is like `cat --show-nonprinting` and makes diffs easier to understand // when e.g. there is an added/removed line with a Window-style CRLF which // would otherwise look the same in both lines. -function verboseNonPrintable( str ) { - - // Match all chars that are not printable ASCII, - // except \t (U+0009) and \n (U+000A). - return str.replace( /[^\t\n\u0020-\u007F]/g, function( m ) { - return `U+${m.codePointAt( 0 ).toString( 16 ).toUpperCase().padStart( 4, "0" )}`; - } ); +function verboseNonPrintable (str) { + // Match all chars that are not printable ASCII, + // except \t (U+0009) and \n (U+000A). + return str.replace(/[^\t\n\u0020-\u007F]/g, function (m) { + return `U+${m.codePointAt(0).toString(16).toUpperCase().padStart(4, '0')}`; + }); } -function normalizeEOL( str ) { - return str.replace( /\r\n/g, "\n" ); +function normalizeEOL (str) { + return str.replace(/\r\n/g, '\n'); } - module.exports = { - getDiff, - download, - downloadFile, - cleanDir, - verboseNonPrintable, - normalizeEOL + getDiff, + download, + downloadFile, + cleanDir, + verboseNonPrintable, + normalizeEOL }; diff --git a/build/watch.js b/build/watch.js index 0434bf9fc..6be3869c1 100644 --- a/build/watch.js +++ b/build/watch.js @@ -1,118 +1,117 @@ -const fs = require( "fs" ); -const http = require( "http" ); -const path = require( "path" ); - -const rollup = require( "rollup" ); -const kleur = require( "kleur" ); -const watch = require( "node-watch" ); -const loadConfigFile = require( "rollup/dist/loadConfigFile" ); - -const { preprocess } = require( "./dist-replace.js" ); - -const server = http.createServer( ( req, res ) => { - try { - const requestedPath = path.join( - process.cwd(), - - // path.normalize will remove any "..". - // Probably useless as at least Chrome resolves any ".." - // _before_ sending the request. - // Probably still a good idea to avoid accessing anything - // _outside_ of process.cwd() anyways. - path.normalize( req.url ) - ); - if ( fs.existsSync( requestedPath ) ) { - return res.end( fs.readFileSync( requestedPath ) ); - } - res.statusCode = 404; - res.end( `Not found: ${req.url}.` ); - } catch ( e ) { - res.statusCode = 500; - res.end( `Unexpected error: ${e.message}.` ); - } -} ); - -server.listen( 4000 ); - -function relativeOutput( output = [] ) { - return output.map( o => path.relative( process.cwd(), o ) ).join( ", " ); +const fs = require('fs'); +const http = require('http'); +const path = require('path'); + +const rollup = require('rollup'); +const kleur = require('kleur'); +const watch = require('node-watch'); +const loadConfigFile = require('rollup/dist/loadConfigFile'); + +const { preprocess } = require('./dist-replace.js'); + +const server = http.createServer((req, res) => { + try { + const requestedPath = path.join( + process.cwd(), + + // path.normalize will remove any "..". + // Probably useless as at least Chrome resolves any ".." + // _before_ sending the request. + // Probably still a good idea to avoid accessing anything + // _outside_ of process.cwd() anyways. + path.normalize(req.url) + ); + if (fs.existsSync(requestedPath)) { + return res.end(fs.readFileSync(requestedPath)); + } + res.statusCode = 404; + res.end(`Not found: ${req.url}.`); + } catch (e) { + res.statusCode = 500; + res.end(`Unexpected error: ${e.message}.`); + } +}); + +server.listen(4000); + +function relativeOutput (output = []) { + return output.map(o => path.relative(process.cwd(), o)).join(', '); } -function inputToOutput( input, output ) { - return `${input} -> ${relativeOutput( output )}`; +function inputToOutput (input, output) { + return `${input} -> ${relativeOutput(output)}`; } -function errorString( err ) { - let str = kleur.bold( - ( err.plugin ? `(plugin ${err.plugin}) ` : "" ) + err.toString() - ); - str += "\n" + ( err.loc ? `${err.loc.file} ${err.loc.line}:${err.loc.column}` : err.id ); - str += `\n${kleur.grey( err.frame )}`; - str += `\n${kleur.grey( err.stack )}`; - return str; +function errorString (err) { + let str = kleur.bold( + (err.plugin ? `(plugin ${err.plugin}) ` : '') + err.toString() + ); + str += '\n' + (err.loc ? `${err.loc.file} ${err.loc.line}:${err.loc.column}` : err.id); + str += `\n${kleur.grey(err.frame)}`; + str += `\n${kleur.grey(err.stack)}`; + return str; } -async function startRollupWatch() { - const configFile = path.resolve( process.cwd(), "rollup.config.js" ); - const { options, warnings } = await loadConfigFile( configFile ); - - console.log( kleur.grey( `[rollup] We currently have ${warnings.count} warnings` ) ); - warnings.flush(); - - const watcher = rollup.watch( options ); - - watcher.on( "event", event => { - const { code, result } = event; - if ( code === "BUNDLE_START" ) { - const { input, output } = event; - console.log( "[rollup] Start", inputToOutput( input, output ) ); - } else if ( code === "BUNDLE_END" ) { - const { duration, input, output } = event; - console.log( "[rollup] End", inputToOutput( input, output ), `in ${duration}ms` ); - } else if ( code === "ERROR" ) { - console.error( errorString( event.error ) ); - } - if ( result ) { - result.close(); - } - } ); +async function startRollupWatch () { + const configFile = path.resolve(process.cwd(), 'rollup.config.js'); + const { options, warnings } = await loadConfigFile(configFile); + + console.log(kleur.grey(`[rollup] We currently have ${warnings.count} warnings`)); + warnings.flush(); + + const watcher = rollup.watch(options); + + watcher.on('event', event => { + const { code, result } = event; + if (code === 'BUNDLE_START') { + const { input, output } = event; + console.log('[rollup] Start', inputToOutput(input, output)); + } else if (code === 'BUNDLE_END') { + const { duration, input, output } = event; + console.log('[rollup] End', inputToOutput(input, output), `in ${duration}ms`); + } else if (code === 'ERROR') { + console.error(errorString(event.error)); + } + if (result) { + result.close(); + } + }); } -async function startCssWatch() { - const baseDir = process.cwd(); - const cssInputFile = path.resolve( baseDir, "src/qunit.css" ); - const cssOutputFile = path.resolve( baseDir, "qunit/qunit.css" ); - - function copyCss() { - const contents = preprocess( fs.readFileSync( cssInputFile, "utf8" ) ); - fs.writeFileSync( cssOutputFile, contents ); - console.log( "[css] Copied processed stylesheet to qunit/qunit.css" ); - } - - const watcher = watch( cssInputFile, { - persistent: true, - delay: 0 - }, ( event, fullpath ) => { - console.log( `[css] File ${event}: ${path.relative( baseDir, fullpath )}` ); - copyCss(); - } ); - - watcher.on( "ready", () => { - copyCss(); - } ); +async function startCssWatch () { + const baseDir = process.cwd(); + const cssInputFile = path.resolve(baseDir, 'src/qunit.css'); + const cssOutputFile = path.resolve(baseDir, 'qunit/qunit.css'); + + function copyCss () { + const contents = preprocess(fs.readFileSync(cssInputFile, 'utf8')); + fs.writeFileSync(cssOutputFile, contents); + console.log('[css] Copied processed stylesheet to qunit/qunit.css'); + } + + const watcher = watch(cssInputFile, { + persistent: true, + delay: 0 + }, (event, fullpath) => { + console.log(`[css] File ${event}: ${path.relative(baseDir, fullpath)}`); + copyCss(); + }); + + watcher.on('ready', () => { + copyCss(); + }); } -( async function main() { - try { - await startRollupWatch(); - await startCssWatch(); - } catch ( err ) { - - // Handle generic script error from before watcher starts - console.error( err ); - - // Watcher didn't get created so no reason to keep the process. - // It doesn't exit automatically because the http server is alive. - process.exit( 1 ); - } -}() ); +(async function main () { + try { + await startRollupWatch(); + await startCssWatch(); + } catch (err) { + // Handle generic script error from before watcher starts + console.error(err); + + // Watcher didn't get created so no reason to keep the process. + // It doesn't exit automatically because the http server is alive. + process.exit(1); + } +}()); diff --git a/docs/QUnit/hooks.md b/docs/QUnit/hooks.md index 24f9e035c..c1f70582d 100644 --- a/docs/QUnit/hooks.md +++ b/docs/QUnit/hooks.md @@ -25,12 +25,12 @@ For more details about hooks, refer to [QUnit.module § Hooks](./module.md#hooks ## Examples ```js -QUnit.hooks.beforeEach( function() { +QUnit.hooks.beforeEach(function () { this.app = new MyApp(); }); -QUnit.hooks.afterEach( async function( assert ) { - assert.deepEqual( [], await this.app.getErrors(), "MyApp errors" ); +QUnit.hooks.afterEach(async function (assert) { + assert.deepEqual([], await this.app.getErrors(), 'MyApp errors'); MyApp.reset(); }); diff --git a/docs/QUnit/module.md b/docs/QUnit/module.md index 9b0b7c402..8eb85218d 100644 --- a/docs/QUnit/module.md +++ b/docs/QUnit/module.md @@ -100,22 +100,22 @@ Example: [§ Nested module scope](#nested-module-scope). If `QUnit.module` is defined without a `nested` callback argument, all subsequently defined tests will be grouped into the module until another module is defined. ```js -QUnit.module( "Group A" ); +QUnit.module('Group A'); -QUnit.test( "basic test example 1", function( assert ) { - assert.true( true, "this is fine" ); +QUnit.test('basic test example 1', function (assert) { + assert.true(true, 'this is fine'); }); -QUnit.test( "basic test example 2", function( assert ) { - assert.true( true, "this is also fine" ); +QUnit.test('basic test example 2', function (assert) { + assert.true(true, 'this is also fine'); }); -QUnit.module( "Group B" ); +QUnit.module('Group B'); -QUnit.test( "basic test example 3", function( assert ) { - assert.true( true, "this is fine" ); +QUnit.test('basic test example 3', function (assert) { + assert.true(true, 'this is fine'); }); -QUnit.test( "basic test example 4", function( assert ) { - assert.true( true, "this is also fine" ); +QUnit.test('basic test example 4', function (assert) { + assert.true(true, 'this is also fine'); }); ``` @@ -124,39 +124,39 @@ Using modern syntax: ```js const { test } = QUnit; -QUnit.module( "Group A" ); +QUnit.module('Group A'); -test( "basic test example", assert => { - assert.true( true, "this is fine" ); +test('basic test example', assert => { + assert.true(true, 'this is fine'); }); -test( "basic test example 2", assert => { - assert.true( true, "this is also fine" ); +test('basic test example 2', assert => { + assert.true(true, 'this is also fine'); }); -QUnit.module( "Group B" ); +QUnit.module('Group B'); -test( "basic test example 3", assert => { - assert.true( true, "this is fine" ); +test('basic test example 3', assert => { + assert.true(true, 'this is fine'); }); -test( "basic test example 4", assert => { - assert.true( true, "this is also fine" ); +test('basic test example 4', assert => { + assert.true(true, 'this is also fine'); }); ``` ### Declaring module options ```js -QUnit.module( "module A", { - before: function() { +QUnit.module('module A', { + before: function () { // prepare something once for all tests }, - beforeEach: function() { + beforeEach: function () { // prepare something before each test }, - afterEach: function() { + afterEach: function () { // clean up after each test }, - after: function() { + after: function () { // clean up once after all tests are done } }); @@ -167,25 +167,23 @@ QUnit.module( "module A", { ```js const { test } = QUnit; -QUnit.module( "Group A", hooks => { - - test( "basic test example", assert => { - assert.true( true, "this is fine" ); +QUnit.module('Group A', hooks => { + test('basic test example', assert => { + assert.true(true, 'this is fine'); }); - test( "basic test example 2", assert => { - assert.true( true, "this is also fine" ); + test('basic test example 2', assert => { + assert.true(true, 'this is also fine'); }); }); -QUnit.module( "Group B", hooks => { - - test( "basic test example 3", assert => { - assert.true( true, "this is fine" ); +QUnit.module('Group B', hooks => { + test('basic test example 3', assert => { + assert.true(true, 'this is fine'); }); - test( "basic test example 4", assert => { - assert.true( true, "this is also fine" ); + test('basic test example 4', assert => { + assert.true(true, 'this is also fine'); }); }); ``` @@ -197,39 +195,37 @@ Use `before`/`beforeEach` hooks are queued for nested modules. `after`/`afterEac ```js const { test } = QUnit; -QUnit.module( "My Group", hooks => { - +QUnit.module('My Group', hooks => { // It is valid to call the same hook methods more than once. - hooks.beforeEach( assert => { - assert.ok( true, "beforeEach called" ); + hooks.beforeEach(assert => { + assert.ok(true, 'beforeEach called'); }); - hooks.afterEach( assert => { - assert.ok( true, "afterEach called" ); + hooks.afterEach(assert => { + assert.ok(true, 'afterEach called'); }); - test( "with hooks", assert => { + test('with hooks', assert => { // 1 x beforeEach // 1 x afterEach - assert.expect( 2 ); + assert.expect(2); }); - QUnit.module( "Nested Group", hooks => { - + QUnit.module('Nested Group', hooks => { // This will run after the parent module's beforeEach hook - hooks.beforeEach( assert => { - assert.ok( true, "nested beforeEach called" ); + hooks.beforeEach(assert => { + assert.ok(true, 'nested beforeEach called'); }); // This will run before the parent module's afterEach - hooks.afterEach( assert => { - assert.ok( true, "nested afterEach called" ); + hooks.afterEach(assert => { + assert.ok(true, 'nested afterEach called'); }); - test( "with nested hooks", assert => { + test('with nested hooks', assert => { // 2 x beforeEach (parent, current) // 2 x afterEach (current, parent) - assert.expect( 4 ); + assert.expect(4); }); }); }); @@ -240,23 +236,23 @@ QUnit.module( "My Group", hooks => { The test context object is exposed to hook callbacks. ```js -QUnit.module( "Machine Maker", { - beforeEach: function() { +QUnit.module('Machine Maker', { + beforeEach: function () { this.maker = new Maker(); - this.parts = [ "wheels", "motor", "chassis" ]; + this.parts = ['wheels', 'motor', 'chassis']; } }); -QUnit.test( "makes a robot", function( assert ) { - this.parts.push( "arduino" ); - assert.equal( this.maker.build( this.parts ), "robot" ); - assert.deepEqual( this.maker.log, [ "robot" ] ); +QUnit.test('makes a robot', function (assert) { + this.parts.push('arduino'); + assert.equal(this.maker.build(this.parts), 'robot'); + assert.deepEqual(this.maker.log, ['robot']); }); -QUnit.test( "makes a car", function( assert ) { - assert.equal( this.maker.build( this.parts ), "car" ); +QUnit.test('makes a car', function (assert) { + assert.equal(this.maker.build(this.parts), 'car'); this.maker.duplicate(); - assert.deepEqual( this.maker.log, [ "car", "car" ] ); + assert.deepEqual(this.maker.log, ['car', 'car']); }); ``` @@ -266,22 +262,22 @@ in arrow functions. ```js const { test } = QUnit; -QUnit.module( "Machine Maker", hooks => { - hooks.beforeEach( function() { +QUnit.module('Machine Maker', hooks => { + hooks.beforeEach(function () { this.maker = new Maker(); - this.parts = [ "wheels", "motor", "chassis" ]; + this.parts = ['wheels', 'motor', 'chassis']; }); - test( "makes a robot", function( assert ) { - this.parts.push( "arduino" ); - assert.equal( this.maker.build( this.parts ), "robot" ); - assert.deepEqual( this.maker.log, [ "robot" ] ); + test('makes a robot', function (assert) { + this.parts.push('arduino'); + assert.equal(this.maker.build(this.parts), 'robot'); + assert.deepEqual(this.maker.log, ['robot']); }); - test( "makes a car", function( assert ) { - assert.equal( this.maker.build( this.parts ), "car" ); + test('makes a car', function (assert) { + assert.equal(this.maker.build(this.parts), 'car'); this.maker.duplicate(); - assert.deepEqual( this.maker.log, [ "car", "car" ] ); + assert.deepEqual(this.maker.log, ['car', 'car']); }); }); ``` @@ -291,24 +287,24 @@ It might be more convenient to use JavaScript's own lexical scope instead: ```js const { test } = QUnit; -QUnit.module( "Machine Maker", hooks => { +QUnit.module('Machine Maker', hooks => { let maker; let parts; - hooks.beforeEach( () => { + hooks.beforeEach(() => { maker = new Maker(); - parts = [ "wheels", "motor", "chassis" ]; + parts = ['wheels', 'motor', 'chassis']; }); - test( "makes a robot", assert => { - parts.push( "arduino" ); - assert.equal( maker.build( parts ), "robot" ); - assert.deepEqual( maker.log, [ "robot" ] ); + test('makes a robot', assert => { + parts.push('arduino'); + assert.equal(maker.build(parts), 'robot'); + assert.deepEqual(maker.log, ['robot']); }); - test( "makes a car", assert => { - assert.equal( maker.build( parts ), "car" ); + test('makes a car', assert => { + assert.equal(maker.build(parts), 'car'); maker.duplicate(); - assert.deepEqual( maker.log, [ "car", "car" ] ); + assert.deepEqual(maker.log, ['car', 'car']); }); }); ``` @@ -320,23 +316,23 @@ An example of handling an asynchronous `then`able Promise result in hooks. This [ES6 Promise]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise ```js -QUnit.module( "Database connection", { - before: function() { - return new Promise( function( resolve, reject ) { - DB.connect( function( err ) { - if ( err ) { - reject( err ); +QUnit.module('Database connection', { + before: function () { + return new Promise(function (resolve, reject) { + DB.connect(function (err) { + if (err) { + reject(err); } else { resolve(); } }); }); }, - after: function() { - return new Promise( function( resolve, reject ) { - DB.disconnect( function( err ) { - if ( err ) { - reject( err ); + after: function () { + return new Promise(function (resolve, reject) { + DB.disconnect(function (err) { + if (err) { + reject(err); } else { resolve(); } @@ -351,33 +347,33 @@ QUnit.module( "Database connection", { Use `QUnit.module.only()` to treat an entire module's tests as if they used [`QUnit.test.only`](./test.only.md) instead of [`QUnit.test`](./test.md). ```js -QUnit.module( "Robot", hooks => { +QUnit.module('Robot', hooks => { // ... }); // Only execute this module when developing the feature, // skipping tests from other modules. -QUnit.module.only( "Android", hooks => { +QUnit.module.only('Android', hooks => { let android; - hooks.beforeEach( () => { + hooks.beforeEach(() => { android = new Android(); }); - QUnit.test( "Say hello", assert => { - assert.strictEqual( android.hello(), "Hello, my name is AN-2178!" ); + QUnit.test('Say hello', assert => { + assert.strictEqual(android.hello(), 'Hello, my name is AN-2178!'); }); - QUnit.test( "Basic conversation", assert => { - android.loadConversationData( { - "Hi": "Hello", - "What's your name?": "My name is AN-2178.", - "Nice to meet you!": "Nice to meet you too!", - "...": "..." + QUnit.test('Basic conversation', assert => { + android.loadConversationData({ + Hi: 'Hello', + "What's your name?": 'My name is AN-2178.', + 'Nice to meet you!': 'Nice to meet you too!', + '...': '...' }); assert.strictEqual( - android.answer( "What's your name?" ), - "My name is AN-2178." + android.answer("What's your name?"), + 'My name is AN-2178.' ); }); @@ -388,27 +384,27 @@ QUnit.module.only( "Android", hooks => { Use `QUnit.module.skip()` to treat an entire module's tests as if they used [`QUnit.test.skip`](./test.skip.md) instead of [`QUnit.test`](./test.md). ```js -QUnit.module( "Robot", hooks => { +QUnit.module('Robot', hooks => { // ... }); // Skip this module's tests. // For example if the android tests are failing due to unsolved problems. -QUnit.module.skip( "Android", hooks => { +QUnit.module.skip('Android', hooks => { let android; - hooks.beforeEach( () => { + hooks.beforeEach(() => { android = new Android(); }); - QUnit.test( "Say hello", assert => { - assert.strictEqual( android.hello(), "Hello, my name is AN-2178!" ); + QUnit.test('Say hello', assert => { + assert.strictEqual(android.hello(), 'Hello, my name is AN-2178!'); }); - QUnit.test( "Basic conversation", assert => { + QUnit.test('Basic conversation', assert => { // ... assert.strictEqual( - android.answer( "Nice to meet you!" ), - "Nice to meet you too!" + android.answer('Nice to meet you!'), + 'Nice to meet you too!' ); }); @@ -419,21 +415,21 @@ QUnit.module.skip( "Android", hooks => { Use `QUnit.module.todo()` to denote a feature that is still under development, and is known to not yet be passing all its tests. This treats an entire module's tests as if they used [`QUnit.test.todo`](./test.todo.md) instead of [`QUnit.test`](./test.md). ```js -QUnit.module.todo( "Robot", hooks => { +QUnit.module.todo('Robot', hooks => { let robot; - hooks.beforeEach( () => { + hooks.beforeEach(() => { robot = new Robot(); }); - QUnit.test( "Say", assert => { + QUnit.test('Say', assert => { // Currently, it returns undefined - assert.strictEqual( robot.say(), "I'm Robot FN-2187" ); + assert.strictEqual(robot.say(), "I'm Robot FN-2187"); }); - QUnit.test( "Move arm", assert => { + QUnit.test('Move arm', assert => { // Move the arm to point (75, 80). Currently, each throws a NotImplementedError - robot.moveArmTo( 75, 80 ); - assert.deepEqual( robot.getPosition(), { x: 75, y: 80 } ); + robot.moveArmTo(75, 80); + assert.deepEqual(robot.getPosition(), { x: 75, y: 80 }); }); // ... diff --git a/docs/QUnit/start.md b/docs/QUnit/start.md index e8a9965c0..27f2d158d 100644 --- a/docs/QUnit/start.md +++ b/docs/QUnit/start.md @@ -24,7 +24,7 @@ A test run that does not begin when the page is done loading. This example uses QUnit.config.autostart = false; require( - [ "test/tests1.js", "test/tests2.js" ], + ['test/tests1.js', 'test/tests2.js'], QUnit.start ); ``` diff --git a/docs/QUnit/test.each.md b/docs/QUnit/test.each.md index 14279ac1c..6d44fe539 100644 --- a/docs/QUnit/test.each.md +++ b/docs/QUnit/test.each.md @@ -40,12 +40,12 @@ The [`only`](./test.only.md), [`skip`](./test.skip.md), and [`todo`](./test.todo ### Basic data provider ```js -function isEven( x ) { +function isEven (x) { return x % 2 === 0; } -QUnit.test.each( "isEven()", [ 2, 4, 6 ], ( assert, data ) => { - assert.true( isEven( data ), `${data} is even` ); +QUnit.test.each('isEven()', [2, 4, 6], (assert, data) => { + assert.true(isEven(data), `${data} is even`); }); ``` @@ -55,63 +55,63 @@ The original array is passed to your callback. [Array destructuring](https://dev ```js -function square( x ) { +function square (x) { return x * x; } -QUnit.test.each( "square()", [ - [ 2, 4 ], - [ 3, 9 ] -], ( assert, [ value, expected ] ) => { - assert.equal( square( value ), expected, `${value} squared` ); +QUnit.test.each('square()', [ + [2, 4], + [3, 9] +], (assert, [value, expected]) => { + assert.equal(square(value), expected, `${value} squared`); }); ``` ### Object data provider ```js -QUnit.test.each( "isEven()", { - caseEven: [ 2, true ], - caseNotEven: [ 3, false ] -}, ( assert, [ value, expected ] ) => { - assert.strictEqual( isEven( value ), expected ); +QUnit.test.each('isEven()', { + caseEven: [2, true], + caseNotEven: [3, false] +}, (assert, [value, expected]) => { + assert.strictEqual(isEven(value), expected); }); ``` ### Async functions with `each()` ```js -function isEven( x ) { +function isEven (x) { return x % 2 === 0; } -async function isAsyncEven( x ) { - return new Promise( resolve => { - resolve( isEven( x ) ); - } ); +async function isAsyncEven (x) { + return new Promise(resolve => { + resolve(isEven(x)); + }); } -QUnit.test.each( "isAsyncEven()", [ 2, 4 ], async ( assert, data ) => { - assert.true( await isAsyncEven( data ), `${data} is even` ); +QUnit.test.each('isAsyncEven()', [2, 4], async (assert, data) => { + assert.true(await isAsyncEven(data), `${data} is even`); }); ``` Return a Promise from each callback, using classic ES5 syntax: ```js -function isEven( x ) { +function isEven (x) { return x % 2 === 0; } -function isAsyncEven( x ) { - return new Promise( function ( resolve ) { - resolve( isEven( x ) ); - } ); +function isAsyncEven (x) { + return new Promise(function (resolve) { + resolve(isEven(x)); + }); } -QUnit.test.each( "isAsyncEven()", [ 2, 4 ], ( assert, value ) => { - return isAsyncEven( value ).then( ( result ) => { - assert.true( result, `${value} is even` ); - } ); +QUnit.test.each('isAsyncEven()', [2, 4], (assert, value) => { + return isAsyncEven(value).then((result) => { + assert.true(result, `${value} is even`); + }); }); ``` diff --git a/docs/QUnit/test.md b/docs/QUnit/test.md index 32cd0786d..4413a4565 100644 --- a/docs/QUnit/test.md +++ b/docs/QUnit/test.md @@ -48,13 +48,13 @@ See also: A practical example, using the assert argument. ```js -function square( x ) { +function square (x) { return x * x; } -QUnit.test( "square()", assert => { - assert.equal( square( 2 ), 4, "square(2)" ); - assert.equal( square( 3 ), 9, "square(3)" ); +QUnit.test('square()', assert => { + assert.equal(square(2), 4, 'square(2)'); + assert.equal(square(3), 9, 'square(3)'); }); ``` @@ -65,13 +65,13 @@ Following the example above, `QUnit.test` also supports JS [async functions][] s [async functions]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function ```js -QUnit.test( "Test with async-await", async assert => { +QUnit.test('Test with async-await', async assert => { const a = await fetchSquare(2); const b = await fetchSquare(3); - assert.equal( a, 4 ); - assert.equal( b, 9 ); - assert.equal( await fetchSquare(5), 25 ); + assert.equal(a, 4); + assert.equal(b, 9); + assert.equal(await fetchSquare(5), 25); }); ``` @@ -85,17 +85,17 @@ This example returns a Promise that is resolved after waiting for 1 second. [Promise]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise ```js -function fetchSquare( x ) { - return new Promise( function ( resolve ) { - setTimeout( function () { - resolve( x * x ); - }, 1000 ); +function fetchSquare (x) { + return new Promise(function (resolve) { + setTimeout(function () { + resolve(x * x); + }, 1000); }); } -QUnit.test( "Test with Promise", assert => { - return fetchSquare( 3 ).then( result => { - assert.equal( result, 9 ); +QUnit.test('Test with Promise', assert => { + return fetchSquare(3).then(result => { + assert.equal(result, 9); }); }); ``` diff --git a/docs/QUnit/test.only.md b/docs/QUnit/test.only.md index 0184387f9..3b0c8f30b 100644 --- a/docs/QUnit/test.only.md +++ b/docs/QUnit/test.only.md @@ -44,24 +44,24 @@ When debugging a larger area of code, you may want to _only_ run all tests withi How to use `QUnit.test.only` to filter which tests are run. ```js -QUnit.module( "robot", hooks => { +QUnit.module('robot', hooks => { let robot; - hooks.beforeEach( () => { + hooks.beforeEach(() => { robot = new Robot(); }); - QUnit.test( "say()", assert => { - assert.true( robot.say( "Hello" ) ); + QUnit.test('say()', assert => { + assert.true(robot.say('Hello')); }); // Run only this test // For example, you are working on changing this method. - QUnit.test.only( "laser()", assert => { - assert.true( robot.laser() ); + QUnit.test.only('laser()', assert => { + assert.true(robot.laser()); }); - QUnit.test( "take()", assert => { - assert.true( robot.take( 5 ) ); + QUnit.test('take()', assert => { + assert.true(robot.take(5)); }); }); ``` diff --git a/docs/QUnit/test.skip.md b/docs/QUnit/test.skip.md index e70c29850..330b87a77 100644 --- a/docs/QUnit/test.skip.md +++ b/docs/QUnit/test.skip.md @@ -36,19 +36,19 @@ As a codebase becomes bigger, you may sometimes want to temporarily disable an e How to use `skip` as a placeholder for future or temporarily broken tests. ```js -QUnit.module( "robot", hooks => { +QUnit.module('robot', hooks => { let robot; - hooks.beforeEach( () => { + hooks.beforeEach(() => { robot = new Robot(); }); - QUnit.test( "say", assert => { - assert.strictEqual( robot.say(), "Exterminate!" ); + QUnit.test('say', assert => { + assert.strictEqual(robot.say(), 'Exterminate!'); }); // Robot does not yet have a laser() method yet, skip this test for now - QUnit.test.skip( "laser", assert => { - assert.true( robot.laser() ); + QUnit.test.skip('laser', assert => { + assert.true(robot.laser()); }); }); ``` diff --git a/docs/QUnit/test.todo.md b/docs/QUnit/test.todo.md index d74451a1c..f4f55d5de 100644 --- a/docs/QUnit/test.todo.md +++ b/docs/QUnit/test.todo.md @@ -42,16 +42,16 @@ You can also use [`QUnit.module.todo()`](./module.md) to manage the "todo" state How to use `QUnit.test.todo` to denote code that is still under development. ```js -QUnit.module( "Robot", hooks => { +QUnit.module('Robot', hooks => { let robot; - hooks.beforeEach( () => { + hooks.beforeEach(() => { robot = new Robot(); }); // Robot is not yet finished, expect this is a todo test - QUnit.test.todo( "fireLazer", assert => { + QUnit.test.todo('fireLazer', assert => { const result = robot.fireLazer(); - assert.equal( result, "I'm firing my lazer!" ); + assert.equal(result, "I'm firing my lazer!"); }); }); ``` diff --git a/docs/assert/async.md b/docs/assert/async.md index 72aef1c82..f665ff99e 100644 --- a/docs/assert/async.md +++ b/docs/assert/async.md @@ -31,16 +31,16 @@ This replaces functionality previously provided by `QUnit.stop()` and [`QUnit.st Tell QUnit to wait for the `done()` call from a callback. ```js -function fetchDouble( num, callback ) { +function fetchDouble (num, callback) { const double = num * 2; - callback( double ); + callback(double); } -QUnit.test( "async example", assert => { +QUnit.test('async example', assert => { const done = assert.async(); - fetchDouble( 21, res => { - assert.strictEqual( res, 42, "Result" ); + fetchDouble(21, res => { + assert.strictEqual(res, 42, 'Result'); done(); }); }); @@ -50,16 +50,16 @@ QUnit.test( "async example", assert => { Call `assert.async()` multiple times to wait for multiple async operations. Each `done` callback must be called exactly once for the test to pass. ```js -QUnit.test( "two async calls", assert => { +QUnit.test('two async calls', assert => { const done1 = assert.async(); const done2 = assert.async(); - fetchDouble( 3, res => { - assert.strictEqual( res, 6, "double of 3" ); + fetchDouble(3, res => { + assert.strictEqual(res, 6, 'double of 3'); done1(); }); - fetchDouble( 9, res => { - assert.strictEqual( res, 18, "double of 9" ); + fetchDouble(9, res => { + assert.strictEqual(res, 18, 'double of 9'); done2(); }); }); @@ -70,25 +70,25 @@ QUnit.test( "two async calls", assert => { The `count` parameter can be used to require multiple calls to the same callback. In the below example, the test passes after exactly three calls. ```js -function uploadBatch(batch, notify, complete) { - batch.forEach( (item) => { +function uploadBatch (batch, notify, complete) { + batch.forEach((item) => { // Do something with item notify(); }); - complete(null) + complete(null); } -QUnit.test( "multiple calls example", assert => { - assert.timeout( 1000 ); +QUnit.test('multiple calls example', assert => { + assert.timeout(1000); - const notify = assert.async( 3 ); + const notify = assert.async(3); const done = assert.async(); uploadBatch( - [ "a", "b", "c" ], + ['a', 'b', 'c'], notify, (err) => { - assert.strictEqual( err, null, "complete error parameter" ); + assert.strictEqual(err, null, 'complete error parameter'); done(); } diff --git a/docs/assert/deepEqual.md b/docs/assert/deepEqual.md index d0a8cb063..bdd49315d 100644 --- a/docs/assert/deepEqual.md +++ b/docs/assert/deepEqual.md @@ -30,26 +30,26 @@ The `deepEqual` assertion can be used to compare the property values of objects, Validate the properties and values of a given object. ```js -QUnit.test( "good example", assert => { - const result = { foo: "bar" }; +QUnit.test('good example', assert => { + const result = { foo: 'bar' }; - assert.deepEqual( result, { foo: "bar" }, "result object" ); + assert.deepEqual(result, { foo: 'bar' }, 'result object'); }); ``` ```js -QUnit.test( "bad example", assert => { +QUnit.test('bad example', assert => { const result = { - a: "Albert", - b: "Berta", + a: 'Albert', + b: 'Berta', num: 123 }; // fails because the number 123 is not strictly equal to the string "123". - assert.deepEqual( result, { - a: "Albert", - b: "Berta", - num: "123" - } ); -} ); + assert.deepEqual(result, { + a: 'Albert', + b: 'Berta', + num: '123' + }); +}); ``` diff --git a/docs/assert/equal.md b/docs/assert/equal.md index 1c261b7b0..33f3596c3 100644 --- a/docs/assert/equal.md +++ b/docs/assert/equal.md @@ -38,20 +38,20 @@ This method is similar to the `assertEquals()` method found in xUnit-style frame The simplest assertion example: ```js -QUnit.test( "a test", function( assert ) { - assert.equal( 1, "1", "String '1' and number 1 have the same value" ); +QUnit.test('a test', function (assert) { + assert.equal(1, '1', "String '1' and number 1 have the same value"); }); ``` A slightly more thorough set of assertions: ```js -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" ); +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( "three", 3, "Three, 3; equal fails" ); - assert.equal( null, false, "null, false; equal fails" ); + assert.equal('three', 3, 'Three, 3; equal fails'); + assert.equal(null, false, 'null, false; equal fails'); }); ``` diff --git a/docs/assert/expect.md b/docs/assert/expect.md index 56f6a7f17..6819584ed 100644 --- a/docs/assert/expect.md +++ b/docs/assert/expect.md @@ -24,18 +24,18 @@ To ensure that an explicit number of assertions are run within any test, use `as Establish an expected assertion count ```js -QUnit.test( "a test", function( assert ) { - assert.expect( 2 ); +QUnit.test('a test', function (assert) { + assert.expect(2); - function calc( x, operation ) { - return operation( x ); + function calc (x, operation) { + return operation(x); } - var result = calc( 2, function( x ) { - assert.true( true, "calc() calls operation function" ); + let result = calc(2, function (x) { + assert.true(true, 'calc() calls operation function'); return x * x; }); - assert.strictEqual( result, 4, "2 squared equals 4" ); + assert.strictEqual(result, 4, '2 squared equals 4'); }); ``` diff --git a/docs/assert/false.md b/docs/assert/false.md index d092b01b8..c6028ad54 100644 --- a/docs/assert/false.md +++ b/docs/assert/false.md @@ -25,17 +25,17 @@ This method is similar to the `assertFalse()` method found in xUnit-style framew ## Examples ```js -QUnit.test( "example", assert => { +QUnit.test('example', assert => { // success - assert.false( false, "boolean false" ); + assert.false(false, 'boolean false'); // failure - assert.false( "foo", "non-empty string" ); - assert.false( "", "empty string" ); - assert.false( 0, "number zero" ); - assert.false( true, "boolean true" ); - assert.false( NaN, "NaN value" ); - assert.false( null, "null value" ); - assert.false( undefined, "undefined value" ); + assert.false('foo', 'non-empty string'); + assert.false('', 'empty string'); + assert.false(0, 'number zero'); + assert.false(true, 'boolean true'); + assert.false(NaN, 'NaN value'); + assert.false(null, 'null value'); + assert.false(undefined, 'undefined value'); }); ``` diff --git a/docs/assert/notDeepEqual.md b/docs/assert/notDeepEqual.md index 89691864b..bd4fcfd33 100644 --- a/docs/assert/notDeepEqual.md +++ b/docs/assert/notDeepEqual.md @@ -26,10 +26,10 @@ This is the inverse of [`assert.deepEqual()`](./deepEqual.md). Compare the value of two objects. ```js -QUnit.test( "example", assert => { - const result = { foo: "yep" }; +QUnit.test('example', assert => { + const result = { foo: 'yep' }; // succeeds, objects are similar but have a different foo value. - assert.notDeepEqual( result, { foo: "nope" } ); + assert.notDeepEqual(result, { foo: 'nope' }); }); ``` diff --git a/docs/assert/notEqual.md b/docs/assert/notEqual.md index e00e5d8f9..73f8cf121 100644 --- a/docs/assert/notEqual.md +++ b/docs/assert/notEqual.md @@ -30,18 +30,18 @@ The `notEqual` assertion uses the simple inverted comparison operator (`!=`) to The simplest assertion example: ```js -QUnit.test( "good example", assert => { - const result = "2"; +QUnit.test('good example', assert => { + const result = '2'; // succeeds, 1 and 2 are different. - assert.notEqual( result, 1, "string and number" ); + assert.notEqual(result, 1, 'string and number'); }); -QUnit.test( "bad example", assert => { - const result = "2"; +QUnit.test('bad example', assert => { + const result = '2'; // fails, the number 2 and the string "2" are actually considered equal // when loosely compared. Use notStrictEqual instead to consider them different - assert.notEqual( result, 2, "string and number" ); + assert.notEqual(result, 2, 'string and number'); }); ``` diff --git a/docs/assert/notOk.md b/docs/assert/notOk.md index 3f913d479..229f4780d 100644 --- a/docs/assert/notOk.md +++ b/docs/assert/notOk.md @@ -21,18 +21,18 @@ A boolean check, inverse of [`assert.ok()`](./ok.md). Passes if the first argume ## Examples ```js -QUnit.test( "example", assert => { +QUnit.test('example', assert => { // success - assert.notOk( false, "boolean false" ); - assert.notOk( "", "empty string" ); - assert.notOk( 0, "number zero" ); - assert.notOk( NaN, "NaN value" ); - assert.notOk( null, "null value" ); - assert.notOk( undefined, "undefined value" ); + assert.notOk(false, 'boolean false'); + assert.notOk('', 'empty string'); + assert.notOk(0, 'number zero'); + assert.notOk(NaN, 'NaN value'); + assert.notOk(null, 'null value'); + assert.notOk(undefined, 'undefined value'); // failure - assert.notOk( "foo", "non-empty string" ); - assert.notOk( true, "boolean true" ); - assert.notOk( 1, "number one" ); + assert.notOk('foo', 'non-empty string'); + assert.notOk(true, 'boolean true'); + assert.notOk(1, 'number one'); }); ``` diff --git a/docs/assert/notPropContains.md b/docs/assert/notPropContains.md index ad6534324..d9bb8c365 100644 --- a/docs/assert/notPropContains.md +++ b/docs/assert/notPropContains.md @@ -29,51 +29,49 @@ This method is recursive and allows partial comparison of nested objects as well ## Examples ```js -QUnit.test( "example", assert => { - +QUnit.test('example', assert => { const result = { foo: 0, vehicle: { - timeCircuits: "on", - fluxCapacitor: "fluxing", - engine: "running" + timeCircuits: 'on', + fluxCapacitor: 'fluxing', + engine: 'running' }, quux: 1 }; // succeeds, property "timeCircuits" is actually "on" - assert.notPropContains( result, { + assert.notPropContains(result, { vehicle: { - timeCircuits: "off" + timeCircuits: 'off' } - } ); + }); // succeeds, property "wings" is not in the object - assert.notPropContains( result, { + assert.notPropContains(result, { vehicle: { - wings: "flapping" + wings: 'flapping' } - } ); + }); - function Point( x, y ) { + function Point (x, y) { this.x = x; this.y = y; } assert.notPropContains( - new Point( 10, 20 ), + new Point(10, 20), { z: 30 } ); const nested = { north: [ /* ... */ ], - east: new Point( 10, 20 ), + east: new Point(10, 20), south: [ /* ... */ ], west: [ /* ... */ ] }; - assert.notPropContains( nested, { east: new Point( 88, 42 ) } ); - assert.notPropContains( nested, { east: { x: 88 } } ); - + assert.notPropContains(nested, { east: new Point(88, 42) }); + assert.notPropContains(nested, { east: { x: 88 } }); }); ``` diff --git a/docs/assert/notPropEqual.md b/docs/assert/notPropEqual.md index 66f477fb8..72474e34e 100644 --- a/docs/assert/notPropEqual.md +++ b/docs/assert/notPropEqual.md @@ -33,21 +33,22 @@ The test passes if there are properties with different values, or extra properti Compare the values of two objects properties. ```js -QUnit.test( "example", assert => { +QUnit.test('example', assert => { class Foo { - constructor() { - this.x = "1"; + constructor () { + this.x = '1'; this.y = 2; } - walk() {} - run() {} + + walk () {} + run () {} } const foo = new Foo(); // succeeds, only own property values are compared (using strict equality), // and propery "x" is indeed not equal (string instead of number). - assert.notPropEqual( foo, { + assert.notPropEqual(foo, { x: 1, y: 2 }); diff --git a/docs/assert/notStrictEqual.md b/docs/assert/notStrictEqual.md index e31ac59ef..f77ceb96b 100644 --- a/docs/assert/notStrictEqual.md +++ b/docs/assert/notStrictEqual.md @@ -28,10 +28,10 @@ The `notStrictEqual` assertion uses the strict inverted comparison operator (`!= ## Examples ```js -QUnit.test( "example", assert => { - const result = "2"; +QUnit.test('example', assert => { + const result = '2'; // succeeds, while the number 2 and string 2 are similar, they are strictly different. - assert.notStrictEqual( result, 2 ); + assert.notStrictEqual(result, 2); }); ``` diff --git a/docs/assert/ok.md b/docs/assert/ok.md index 4e293026a..69896d5ac 100644 --- a/docs/assert/ok.md +++ b/docs/assert/ok.md @@ -25,18 +25,18 @@ The most basic assertion in QUnit, `ok()` requires just one argument. If the arg ## Examples ```js -QUnit.test( "example", assert => { +QUnit.test('example', assert => { // success - assert.ok( true, "boolean true" ); - assert.ok( "foo", "non-empty string" ); - assert.ok( 1, "number one" ); + assert.ok(true, 'boolean true'); + assert.ok('foo', 'non-empty string'); + assert.ok(1, 'number one'); // failure - assert.ok( false, "boolean false" ); - assert.ok( "", "empty string" ); - assert.ok( 0, "number zero" ); - assert.ok( NaN, "NaN value" ); - assert.ok( null, "null value" ); - assert.ok( undefined, "undefined value" ); + assert.ok(false, 'boolean false'); + assert.ok('', 'empty string'); + assert.ok(0, 'number zero'); + assert.ok(NaN, 'NaN value'); + assert.ok(null, 'null value'); + assert.ok(undefined, 'undefined value'); }); ``` diff --git a/docs/assert/propContains.md b/docs/assert/propContains.md index f7c9dbb81..91e36f5ae 100644 --- a/docs/assert/propContains.md +++ b/docs/assert/propContains.md @@ -31,48 +31,46 @@ This method is recursive and allows partial comparison of nested objects as well ## Examples ```js -QUnit.test( "example", assert => { - +QUnit.test('example', assert => { const result = { foo: 0, vehicle: { - timeCircuits: "on", - fluxCapacitor: "fluxing", - engine: "running" + timeCircuits: 'on', + fluxCapacitor: 'fluxing', + engine: 'running' }, quux: 1 }; - assert.propContains( result, { + assert.propContains(result, { foo: 0, - vehicle: { fluxCapacitor: "fluxing" } - } ); + vehicle: { fluxCapacitor: 'fluxing' } + }); - function Point( x, y ) { + function Point (x, y) { this.x = x; this.y = y; } assert.propContains( - new Point( 10, 20 ), + new Point(10, 20), { y: 20 } ); assert.propContains( - [ "a", "b" ], - { 1: "b" } + [ 'a', 'b' ], + { 1: 'b' } ); const nested = { north: [ /* ... */ ], - east: new Point( 10, 20 ), + east: new Point(10, 20), south: [ /* ... */ ], west: [ /* ... */ ] }; - assert.propContains( nested, { east: new Point( 10, 20 ) } ); - assert.propContains( nested, { east: { x: 10, y: 20 } } ); - assert.propContains( nested, { east: { x: 10 } } ); - + assert.propContains(nested, { east: new Point(10, 20) }); + assert.propContains(nested, { east: { x: 10, y: 20 } }); + assert.propContains(nested, { east: { x: 10 } }); }); ``` diff --git a/docs/assert/propEqual.md b/docs/assert/propEqual.md index 52f78b35d..d03dcc030 100644 --- a/docs/assert/propEqual.md +++ b/docs/assert/propEqual.md @@ -35,21 +35,22 @@ This method is recursive and can compare any nested or complex object via a plai Compare the property values of two objects. ```js -QUnit.test( "example", assert => { +QUnit.test('example', assert => { class Foo { - constructor() { + constructor () { this.x = 1; this.y = 2; } - walk() {} - run() {} + + walk () {} + run () {} } const foo = new Foo(); // succeeds, own properties are strictly equal, // and inherited properties (such as which constructor) are ignored. - assert.propEqual( foo, { + assert.propEqual(foo, { x: 1, y: 2 }); @@ -59,8 +60,8 @@ QUnit.test( "example", assert => { Using classic ES5 syntax: ```js -QUnit.test( "example", function ( assert ) { - function Foo() { +QUnit.test('example', function (assert) { + function Foo () { this.x = 1; this.y = 2; } @@ -74,6 +75,6 @@ QUnit.test( "example", function ( assert ) { x: 1, y: 2 }; - assert.propEqual( foo, expected ); + assert.propEqual(foo, expected); }); ``` diff --git a/docs/assert/pushResult.md b/docs/assert/pushResult.md index 7a20527c4..e9d69e4b2 100644 --- a/docs/assert/pushResult.md +++ b/docs/assert/pushResult.md @@ -23,24 +23,23 @@ Report the result of a custom assertion. If you need to express an expectation that is not abstracted by QUnit's built-in assertions, you can perform your own logic ad-hoc, and then pass two directly comparable values to [`assert.strictEqual()`](./strictEqual.md), or pass your own representative boolean result to [`assert.true()`](./true.md). ```js -QUnit.test( "bad example with remainder", assert => { +QUnit.test('bad example with remainder', assert => { const result = 3; - const actual = ( result % 2 ) === 1; - assert.true( actual, "remainder of mod 2 is 1" ); + const actual = (result % 2) === 1; + assert.true(actual, 'remainder of mod 2 is 1'); // In case of failure, there is no information about the // actually observed remainder or the expected value }); -QUnit.test( "good example with remainder", assert => { +QUnit.test('good example with remainder', assert => { const result = 3; - assert.strictEqual( result % 2, 1, "remainder of mod 2" ); + assert.strictEqual(result % 2, 1, 'remainder of mod 2'); }); - -QUnit.test( "example with value in range", assert => { +QUnit.test('example with value in range', assert => { const actual = 3; - const isBetween = ( actual >= 1 && actual <= 10 ); - assert.true( isBetween, "result between 1 and 10" ); + const isBetween = (actual >= 1 && actual <= 10); + assert.true(isBetween, 'result between 1 and 10'); // No information in case of failure. // Cannot be expressed in a useful way using strictEqual() // @@ -57,20 +56,20 @@ With a custom assertion method, you can control how an assertion should be evalu For example: ```js -QUnit.assert.between = function( actual, from, to, message ) { - const isBetween = ( actual >= from && actual <= to ); +QUnit.assert.between = function (actual, from, to, message) { + const isBetween = (actual >= from && actual <= to); - this.pushResult( { + this.pushResult({ result: isBetween, actual: actual, expected: `between ${from} and ${to} inclusive`, message: message - } ); + }); }; -QUnit.test( "custom assertion example", assert => { +QUnit.test('custom assertion example', assert => { const result = 3; - assert.between( result, 1, 10, "result" ); + assert.between(result, 1, 10, 'result'); // Example of failure if result is out of range // > actual: 42 // > expected: between 1 and 10 diff --git a/docs/assert/rejects.md b/docs/assert/rejects.md index 3573e0020..1caa2d595 100644 --- a/docs/assert/rejects.md +++ b/docs/assert/rejects.md @@ -35,56 +35,55 @@ Note: in order to avoid confusion between the `message` and the `expectedMatcher ## Examples ```js -QUnit.test( "rejects example", assert => { - +QUnit.test('rejects example', assert => { // simple check - assert.rejects( Promise.reject( "some error" ) ); + assert.rejects(Promise.reject('some error')); // simple check assert.rejects( - Promise.reject( "some error" ), - "optional description here" + Promise.reject('some error'), + 'optional description here' ); // match pattern on actual error assert.rejects( - Promise.reject( new Error( "some error" ) ), + Promise.reject(new Error('some error')), /some error/, - "optional description here" + 'optional description here' ); // Using a custom error constructor - function CustomError( message ) { + function CustomError (message) { this.message = message; } - CustomError.prototype.toString = function() { + CustomError.prototype.toString = function () { return this.message; }; // actual error is an instance of the expected constructor assert.rejects( - Promise.reject( new CustomError( "some error" ) ), + Promise.reject(new CustomError('some error')), CustomError ); // actual error has strictly equal `constructor`, `name` and `message` properties // of the expected error object assert.rejects( - Promise.reject( new CustomError( "some error" ) ), - new CustomError( "some error" ) + Promise.reject(new CustomError('some error')), + new CustomError('some error') ); // custom validation arrow function assert.rejects( - Promise.reject( new CustomError( "some error" ) ), - ( err ) => err.toString() === "some error" + Promise.reject(new CustomError('some error')), + (err) => err.toString() === 'some error' ); // custom validation function assert.rejects( - Promise.reject( new CustomError( "some error" ) ), - function( err ) { - return err.toString() === "some error"; + Promise.reject(new CustomError('some error')), + function (err) { + return err.toString() === 'some error'; } ); }); @@ -93,28 +92,28 @@ QUnit.test( "rejects example", assert => { The `assert.rejects()` method returns a `Promise` which handles the (often asynchronous) resolution and rejection logic for test successes and failures. It is not required to `await` the returned value, since QUnit internally handles the async control for you and waits for a settled state. However, if your test code requires a consistent and more isolated state between `rejects` calls, then this should be explicitly awaited to hold back the next statements. ```js -QUnit.test( "stateful rejects example", async assert => { +QUnit.test('stateful rejects example', async assert => { let value; // asynchronously resolve if value < 5, and reject otherwise - function asyncChecker() { + function asyncChecker () { return new Promise((resolve, reject) => { setTimeout(() => { if (value < 5) { resolve(); } else { - reject("bad value: " + value); + reject('bad value: ' + value); } - }, 10) + }, 10); }); } value = 8; - await assert.rejects( asyncChecker(), /bad value: 8/ ); + await assert.rejects(asyncChecker(), /bad value: 8/); // if the above was not awaited, then the next line would change the value // before the previous assertion could occur, and would cause a test failure value = Infinity; - await assert.rejects( asyncChecker(), /bad value: Infinity/ ); + await assert.rejects(asyncChecker(), /bad value: Infinity/); }); ``` diff --git a/docs/assert/step.md b/docs/assert/step.md index cc46c35bf..dbdc6b4ce 100644 --- a/docs/assert/step.md +++ b/docs/assert/step.md @@ -22,14 +22,14 @@ Together with the [`assert.verifySteps`](./verifySteps.md) method, `step()` asse ## Examples ```js -QUnit.test( "step example", assert => { +QUnit.test('step example', assert => { const thing = new MyThing(); - thing.on( "something", () => { - assert.step( "something happened" ); + thing.on('something', () => { + assert.step('something happened'); }); thing.run(); - assert.verifySteps([ "something happened" ]); + assert.verifySteps([ 'something happened' ]); }); ``` diff --git a/docs/assert/strictEqual.md b/docs/assert/strictEqual.md index 6dba2fca0..cf1bd8d96 100644 --- a/docs/assert/strictEqual.md +++ b/docs/assert/strictEqual.md @@ -36,9 +36,9 @@ The `strictEqual()` assertion provides the most rigid comparison of type and val Compare the value of two primitives, having the same value and type. ```js -QUnit.test( "strictEqual example", assert => { +QUnit.test('strictEqual example', assert => { const result = 2; - assert.strictEqual( result, 2 ); + assert.strictEqual(result, 2); }); ``` diff --git a/docs/assert/throws.md b/docs/assert/throws.md index c64a71800..9e547af1a 100644 --- a/docs/assert/throws.md +++ b/docs/assert/throws.md @@ -40,42 +40,41 @@ The `expectedMatcher` argument can be: ## Examples ```js -QUnit.test( "throws example", assert => { - +QUnit.test('throws example', assert => { // simple check - assert.throws( function() { - throw new Error( "boo" ); + assert.throws(function () { + throw new Error('boo'); }); // simple check assert.throws( - function() { - throw new Error( "boo" ); + function () { + throw new Error('boo'); }, - "optional description here" + 'optional description here' ); // match pattern on actual error assert.throws( - function() { - throw new Error( "some error" ); + function () { + throw new Error('some error'); }, /some error/, - "optional description here" + 'optional description here' ); // using a custom error constructor - function CustomError( message ) { + function CustomError (message) { this.message = message; } - CustomError.prototype.toString = function() { + CustomError.prototype.toString = function () { return this.message; }; // actual error is an instance of the expected constructor assert.throws( - function() { - throw new CustomError( "some error" ); + function () { + throw new CustomError('some error'); }, CustomError ); @@ -83,27 +82,27 @@ QUnit.test( "throws example", assert => { // actual error has strictly equal `constructor`, `name` and `message` properties // of the expected error object assert.throws( - function() { - throw new CustomError( "some error" ); + function () { + throw new CustomError('some error'); }, - new CustomError( "some error" ) + new CustomError('some error') ); // custom validation arrow function assert.throws( - function() { - throw new CustomError( "some error" ); + function () { + throw new CustomError('some error'); }, - ( err ) => err.toString() === "some error" + (err) => err.toString() === 'some error' ); // custom validation function assert.throws( - function() { - throw new CustomError( "some error" ); + function () { + throw new CustomError('some error'); }, - function( err ) { - return err.toString() === "some error"; + function (err) { + return err.toString() === 'some error'; } ); }); diff --git a/docs/assert/timeout.md b/docs/assert/timeout.md index 3696eedd7..97d591c1a 100644 --- a/docs/assert/timeout.md +++ b/docs/assert/timeout.md @@ -25,13 +25,13 @@ If `assert.timeout()` is called after a timeout has already been set, the old ti ## Examples ```js -QUnit.test( "wait for an event", assert => { - assert.timeout( 1000 ); // Timeout after 1 second +QUnit.test('wait for an event', assert => { + assert.timeout(1000); // Timeout after 1 second const done = assert.async(); const adder = new NumberAdder(); - adder.on( "ready", res => { - assert.strictEqual( res, 12 ); + adder.on('ready', res => { + assert.strictEqual(res, 12); done(); }); adder.run([ 1, 1, 2, 3, 5 ]); @@ -39,24 +39,24 @@ QUnit.test( "wait for an event", assert => { ``` ```js -QUnit.test( "wait for an async function", async assert => { - assert.timeout( 500 ); // Timeout after 0.5 seconds +QUnit.test('wait for an async function', async assert => { + assert.timeout(500); // Timeout after 0.5 seconds - const result = await asyncAdder( 5, 7 ); - assert.strictEqual( result, 12 ); + const result = await asyncAdder(5, 7); + assert.strictEqual(result, 12); }); ``` Using classic ES5 syntax: ```js -QUnit.test( "wait for a returned promise", function( assert ) { - assert.timeout( 500 ); // Timeout after 0.5 seconds +QUnit.test('wait for a returned promise', function (assert) { + assert.timeout(500); // Timeout after 0.5 seconds - var promise = asyncAdder( 5, 7 ); + var promise = asyncAdder(5, 7); - return promise.then( function( result ) { - assert.strictEqual( result, 12 ); - } ); + return promise.then(function (result) { + assert.strictEqual(result, 12); + }); }); ``` diff --git a/docs/assert/true.md b/docs/assert/true.md index 7a6c16d3c..a0eff6da4 100644 --- a/docs/assert/true.md +++ b/docs/assert/true.md @@ -25,18 +25,18 @@ This method is similar to the `assertTrue()` method found in xUnit-style framewo ## Examples ```js -QUnit.test( "example", assert => { +QUnit.test('example', assert => { // success - assert.true( true, "boolean true" ); + assert.true(true, 'boolean true'); // failure - assert.true( "foo", "non-empty string" ); - assert.true( "", "empty string" ); - assert.true( 0, "number zero" ); - assert.true( false, "boolean false" ); - assert.true( NaN, "NaN value" ); - assert.true( null, "null value" ); - assert.true( undefined, "undefined value" ); + assert.true('foo', 'non-empty string'); + assert.true('', 'empty string'); + assert.true(0, 'number zero'); + assert.true(false, 'boolean false'); + assert.true(NaN, 'NaN value'); + assert.true(null, 'null value'); + assert.true(undefined, 'undefined value'); }); ``` diff --git a/docs/assert/verifySteps.md b/docs/assert/verifySteps.md index e5567377c..965330263 100644 --- a/docs/assert/verifySteps.md +++ b/docs/assert/verifySteps.md @@ -31,24 +31,24 @@ The **Step API** strictly validates the order and frequency of observed values. This example uses a class based on an [`EventEmitter`](https://nodejs.org/api/events.html), such as the one provided by Node.js and other environments: ```js -QUnit.test( "good example", async assert => { +QUnit.test('good example', async assert => { const thing = new MyThing(); - thing.on( "start", () => { - assert.step( "start" ); + thing.on('start', () => { + assert.step('start'); }); - thing.on( "middle", () => { - assert.step( "middle" ); + thing.on('middle', () => { + assert.step('middle'); }); - thing.on( "end", () => { - assert.step( "end" ); + thing.on('end', () => { + assert.step('end'); }); - thing.on( "error", message => { - assert.step( { error: message } ); + thing.on('error', message => { + assert.step({ error: message }); }); await thing.process(); - assert.verifySteps( [ "start", "middle", "end" ] ); + assert.verifySteps([ 'start', 'middle', 'end' ]); }); ``` @@ -56,19 +56,19 @@ When approaching this scenario **without the Step API** one might be tempted to ```js // WARNING: This is a BAD example -QUnit.test( "bad example 1", assert => { +QUnit.test('bad example 1', assert => { const thing = new MyThing(); - thing.on( "start", () => { - assert.true( true, "start" ); + thing.on('start', () => { + assert.true(true, 'start'); }); - thing.on( "middle", () => { - assert.true( true, "middle" ); + thing.on('middle', () => { + assert.true(true, 'middle'); }); - thing.on( "end", () => { - assert.true( true, "end" ); + thing.on('end', () => { + assert.true(true, 'end'); }); - thing.on( "error", () => { - assert.true( false, "error" ); + thing.on('error', () => { + assert.true(false, 'error'); }); return thing.process(); @@ -78,26 +78,25 @@ QUnit.test( "bad example 1", assert => { A less fragile approach could involve a local counter variable with an array that we check with [`deepEqual`](./deepEqual.md). This catches out-of-order issues, unexpected values, duplicate values, and provides detailed debugging information in case of problems. This is basically how the Step API works: ```js -QUnit.test( "manual example without Step API", assert => { +QUnit.test('manual example without Step API', assert => { const values = []; const thing = new MyThing(); - thing.on( "start", () => { - values.push( "start" ); + thing.on('start', () => { + values.push('start'); }); - thing.on( "middle", () => { - values.push( "middle" ); + thing.on('middle', () => { + values.push('middle'); }); - thing.on( "end", () => { - values.push( "end" ); + thing.on('end', () => { + values.push('end'); }); - thing.on( "error", () => { - values.push( "error" ); + thing.on('error', () => { + values.push('error'); }); - return thing.process().then(() => { - assert.deepEqual( values, [ "start", "middle", "end" ] ); + assert.deepEqual(values, [ 'start', 'middle', 'end' ]); }); }); ``` @@ -107,7 +106,7 @@ QUnit.test( "manual example without Step API", assert => { Use the **Step API** to verify messages received in a Pub-Sub channel or topic. ```js -QUnit.test( "good example", assert => { +QUnit.test('good example', assert => { const publisher = new Publisher(); const subscriber1 = (message) => assert.step(`Sub 1: ${message}`); @@ -115,15 +114,15 @@ QUnit.test( "good example", assert => { publisher.subscribe(subscriber1); publisher.subscribe(subscriber2); - publisher.publish( "Hello!" ); + publisher.publish('Hello!'); publisher.unsubscribe(subscriber1); - publisher.publish( "World!" ); + publisher.publish('World!'); assert.verifySteps([ - "Sub 1: Hello!", - "Sub 2: Hello!", - "Sub 2: World!" + 'Sub 1: Hello!', + 'Sub 2: Hello!', + 'Sub 2: World!' ]); }); ``` @@ -133,13 +132,13 @@ QUnit.test( "good example", assert => { The internal buffer of observed steps is automatically reset when calling `verifySteps()`. ```js -QUnit.test( "multiple verifications example", assert => { - assert.step( "one" ); - assert.step( "two" ); - assert.verifySteps([ "one", "two" ]); - - assert.step( "three" ); - assert.step( "four" ); - assert.verifySteps([ "three", "four" ]); +QUnit.test('multiple verifications example', assert => { + assert.step('one'); + assert.step('two'); + assert.verifySteps([ 'one', 'two' ]); + + assert.step('three'); + assert.step('four'); + assert.verifySteps([ 'three', 'four' ]); }); ``` diff --git a/docs/callbacks/QUnit.begin.md b/docs/callbacks/QUnit.begin.md index 29399b714..70a556420 100644 --- a/docs/callbacks/QUnit.begin.md +++ b/docs/callbacks/QUnit.begin.md @@ -32,41 +32,40 @@ Passed to the callback: Get total number of tests known at the start. ```js -QUnit.begin( details => { - console.log( `Test amount: ${details.totalTests}` ); +QUnit.begin(details => { + console.log(`Test amount: ${details.totalTests}`); }); ``` Use async-await to wait for some asynchronous work: ```js -QUnit.begin( async details => { +QUnit.begin(async details => { await someAsyncWork(); - console.log( `Test amount: ${details.totalTests}` ); + console.log(`Test amount: ${details.totalTests}`); }); ``` Using classic ES5 syntax: ```js -QUnit.begin( function( details ) { - console.log( "Test amount:" + details.totalTests ); +QUnit.begin(function (details) { + console.log('Test amount:' + details.totalTests); }); ``` ```js -function someAsyncWork() { - return new Promise( function( resolve, reject ) { +function someAsyncWork () { + return new Promise(function (resolve, reject) { // do some async work resolve(); }); } -QUnit.begin( function( details ) { - return someAsyncWork().then( function () { - - console.log( "Test amount:" + details.totalTests ); +QUnit.begin(function (details) { + return someAsyncWork().then(function () { + console.log('Test amount:' + details.totalTests); }); }); ``` diff --git a/docs/callbacks/QUnit.done.md b/docs/callbacks/QUnit.done.md index 2a4aa9fb3..1281706e2 100644 --- a/docs/callbacks/QUnit.done.md +++ b/docs/callbacks/QUnit.done.md @@ -33,10 +33,10 @@ Passed to the callback: Register a callback that logs test results to the console. ```js -QUnit.done( details => { +QUnit.done(details => { console.log( - `Total: ${details.total} Failed: ${details.failed} ` - + `Passed: ${details.passed} Runtime: ${details.runtime}` + `Total: ${details.total} Failed: ${details.failed} ` + + `Passed: ${details.passed} Runtime: ${details.runtime}` ); }); ``` @@ -44,10 +44,10 @@ QUnit.done( details => { Using classic ES5 syntax: ```js -QUnit.done( function( details ) { +QUnit.done(function (details) { console.log( - "Total: " + details.total + " Failed: " + details.failed - + " Passed: " + details.passed + " Runtime: " + details.runtime + 'Total: ' + details.total + ' Failed: ' + details.failed + + ' Passed: ' + details.passed + ' Runtime: ' + details.runtime ); }); ``` diff --git a/docs/callbacks/QUnit.log.md b/docs/callbacks/QUnit.log.md index 842247a59..fc3fab450 100644 --- a/docs/callbacks/QUnit.log.md +++ b/docs/callbacks/QUnit.log.md @@ -42,8 +42,8 @@ Passed to the callback: Register a callback that logs the assertion result and its message: ```js -QUnit.log( details => { - console.log( `Log: ${details.result}, ${details.message}` ); +QUnit.log(details => { + console.log(`Log: ${details.result}, ${details.message}`); }); ``` @@ -52,23 +52,23 @@ QUnit.log( details => { Log the module name and test result whenever an assertion fails: ```js -QUnit.log( details ) => { - if ( details.result ) { +QUnit.log(details => { + if (details.result) { return; } let output = `[FAILED] ${details.module} > ${details.name}`; - if ( details.message ) { + if (details.message) { output += `: ${details.message}`; } - if ( details.actual ) { + if (details.actual) { output += `\nexpected: ${details.expected}\nactual: ${details.actual}`; } - if ( details.source ) { + if (details.source) { output += `\n${details.source}`; } - console.log( output ); + console.log(output); }); ``` diff --git a/docs/callbacks/QUnit.moduleDone.md b/docs/callbacks/QUnit.moduleDone.md index 8fddd1c31..0f88faf12 100644 --- a/docs/callbacks/QUnit.moduleDone.md +++ b/docs/callbacks/QUnit.moduleDone.md @@ -34,7 +34,7 @@ Passed to the callback: Register a callback that logs the module results ```js -QUnit.moduleDone( details => { - console.log( `Finished running: ${details.name} Failed/total: ${details.failed}/${total}` ); +QUnit.moduleDone(details => { + console.log(`Finished running: ${details.name} Failed/total: ${details.failed}/${total}`); }); ``` diff --git a/docs/callbacks/QUnit.moduleStart.md b/docs/callbacks/QUnit.moduleStart.md index ecf696512..3a55410ca 100644 --- a/docs/callbacks/QUnit.moduleStart.md +++ b/docs/callbacks/QUnit.moduleStart.md @@ -30,7 +30,7 @@ Passed to the callback: Register a callback that logs the module name ```js -QUnit.moduleStart( details => { - console.log( `Now running: ${details.name}` ); +QUnit.moduleStart(details => { + console.log(`Now running: ${details.name}`); }); ``` diff --git a/docs/callbacks/QUnit.on.md b/docs/callbacks/QUnit.on.md index 9301f54fd..2e48424d7 100644 --- a/docs/callbacks/QUnit.on.md +++ b/docs/callbacks/QUnit.on.md @@ -25,11 +25,11 @@ Use this to listen for events related to the test suite's execution. Available e Printing results of a test suite. ```js -QUnit.on( "runEnd", runEnd => { - console.log( `Passed: ${runEnd.passed}` ); - console.log( `Failed: ${runEnd.failed}` ); - console.log( `Skipped: ${runEnd.skipped}` ); - console.log( `Todo: ${runEnd.todo}` ); - console.log( `Total: ${runEnd.total}` ); -} ); +QUnit.on('runEnd', runEnd => { + console.log(`Passed: ${runEnd.passed}`); + console.log(`Failed: ${runEnd.failed}`); + console.log(`Skipped: ${runEnd.skipped}`); + console.log(`Todo: ${runEnd.todo}`); + console.log(`Total: ${runEnd.total}`); +}); ``` diff --git a/docs/callbacks/QUnit.testDone.md b/docs/callbacks/QUnit.testDone.md index 982a28d9b..7a80dd084 100644 --- a/docs/callbacks/QUnit.testDone.md +++ b/docs/callbacks/QUnit.testDone.md @@ -37,20 +37,20 @@ Passed to the callback: Register a callback that logs results of a single test: ```js -QUnit.testDone( details => { +QUnit.testDone(details => { const result = { - "Module name": details.module, - "Test name": details.name, - "Assertions": { - "Total": details.total, - "Passed": details.passed, - "Failed": details.failed + 'Module name': details.module, + 'Test name': details.name, + Assertions: { + Total: details.total, + Passed: details.passed, + Failed: details.failed }, - "Skipped": details.skipped, - "Todo": details.todo, - "Runtime": details.runtime + Skipped: details.skipped, + Todo: details.todo, + Runtime: details.runtime }; - console.log( JSON.stringify( result, null, 2 ) ); -} ); + console.log(JSON.stringify(result, null, 2)); +}); ``` diff --git a/docs/callbacks/QUnit.testStart.md b/docs/callbacks/QUnit.testStart.md index e4040f7ba..0fee169d1 100644 --- a/docs/callbacks/QUnit.testStart.md +++ b/docs/callbacks/QUnit.testStart.md @@ -33,7 +33,7 @@ Passed to the callback: Register a callback that logs the module and test name: ```js -QUnit.testStart( details => { - console.log( `Now running: ${details.module} ${details.name}` ); +QUnit.testStart(details => { + console.log(`Now running: ${details.module} ${details.name}`); }); ``` diff --git a/docs/config/autostart.md b/docs/config/autostart.md index c89af8d1f..7f691395c 100644 --- a/docs/config/autostart.md +++ b/docs/config/autostart.md @@ -33,10 +33,10 @@ QUnit.config.autostart = false; require( [ - "tests/testModule1", - "tests/testModule2" + 'tests/testModule1', + 'tests/testModule2' ], - function() { + function () { QUnit.start(); } ); diff --git a/docs/config/current.md b/docs/config/current.md index 4ed5068ff..fc55fe8e1 100644 --- a/docs/config/current.md +++ b/docs/config/current.md @@ -26,13 +26,13 @@ Internals may change without notice. When possible, use [QUnit.on](../callbacks/ Access `QUnit.config.current.testName` to observe the currently running test's name. ```js -function whatsUp() { - console.log( QUnit.config.current.testName ); +function whatsUp () { + console.log(QUnit.config.current.testName); } -QUnit.test( "example", assert => { +QUnit.test('example', assert => { whatsUp(); - assert.true( true ); + assert.true(true); }); ``` diff --git a/docs/config/index.md b/docs/config/index.md index 199d475c8..bb02c636b 100644 --- a/docs/config/index.md +++ b/docs/config/index.md @@ -18,7 +18,7 @@ If you load QUnit asynchronously or otherwise need to configure QUnit before it window.QUnit = { config: { autostart: false, - noglobals: true, + noglobals: true } }; ``` diff --git a/docs/config/urlConfig.md b/docs/config/urlConfig.md index 1aa573956..3f5ccd809 100644 --- a/docs/config/urlConfig.md +++ b/docs/config/urlConfig.md @@ -30,8 +30,8 @@ Each array item should be an object shaped as follows: id: string, label: string, tooltip: string, // optional - value: string|array|object, // optional -}) + value: string | array | object // optional +}); ``` * The `id` property is used as the key for storing the value under `QUnit.config`, and as URL query parameter. @@ -47,16 +47,16 @@ If `value` is a string, the option will render as a checkbox. The corresponding If `value` is an array, the option will render as a "select one" menu with an empty value as first default option, followed by one option for each item in the array. The corresponding URL parameter will be absent when the empty option is selected, and otherwise will be set to the value of the selected array item. ```js -value: [ "foobar", "baz" ] +value = [ 'foobar', 'baz' ]; ``` If `value` is an object, the option will render as a "select one" menu as for an array. The keys will be used as option values, and the values will be used as option display labels. The corresponding URL parameter will be absent when the empty option is selected, and otherwise will be set to the object key of the selected property. ```js -value: { - "foobar": "Foo with bar" - "baz": "Baz" -} +value = { + foobar: 'Foo with bar', + baz: 'Baz' +}; ``` ## Examples @@ -67,9 +67,9 @@ Add a new checkbox to the toolbar. You can then use the `QUnit.config.min` prope ```js QUnit.config.urlConfig.push({ - id: "min", - label: "Minified source", - tooltip: "Load minified source files instead of the regular unminified ones." + id: 'min', + label: 'Minified source', + tooltip: 'Load minified source files instead of the regular unminified ones.' }); ``` @@ -79,9 +79,9 @@ Add a dropdown to the toolbar. ```js QUnit.config.urlConfig.push({ - id: "jquery", - label: "jQuery version", - value: [ "1.7.2", "1.8.3", "1.9.1" ], - tooltip: "Which jQuery version to test against." + id: 'jquery', + label: 'jQuery version', + value: [ '1.7.2', '1.8.3', '1.9.1' ], + tooltip: 'Which jQuery version to test against.' }); ``` diff --git a/docs/extension/QUnit.dump.parse.md b/docs/extension/QUnit.dump.parse.md index c67911cc4..7666eaf5c 100644 --- a/docs/extension/QUnit.dump.parse.md +++ b/docs/extension/QUnit.dump.parse.md @@ -35,14 +35,14 @@ The following is an example from [grunt-contrib-qunit][], which sends results fr [grunt-contrib-qunit]: https://github.com/gruntjs/grunt-contrib-qunit/blob/188a29af7817e1798fdd95f1ab7d3069231e4859/chrome/bridge.js#L42-L60 ```js -QUnit.log(function( obj ) { +QUnit.log(function (obj) { var actual; var expected; - if ( !obj.result ) { + if (!obj.result) { // Format before sending - actual = QUnit.dump.parse( obj.actual ); - expected = QUnit.dump.parse( obj.expected ); + actual = QUnit.dump.parse(obj.actual); + expected = QUnit.dump.parse(obj.expected); } // ... @@ -54,10 +54,10 @@ QUnit.log(function( obj ) { This example shows the formatted representation of a DOM element. ```js -var qHeader = document.getElementById( "qunit-header" ); -var parsed = QUnit.dump.parse( qHeader ); +var qHeader = document.getElementById('qunit-header'); +var parsed = QUnit.dump.parse(qHeader); -console.log( parsed ); +console.log(parsed); // Logs: '

' ``` @@ -74,10 +74,10 @@ var input = { } }; QUnit.dump.maxDepth = 1; -console.log( QUnit.dump.parse( input ) ); +console.log(QUnit.dump.parse(input)); // Logs: { "parts": [object Object] } QUnit.dump.maxDepth = 2; -console.log( QUnit.dump.parse( input ) ); +console.log(QUnit.dump.parse(input)); // Logs: { "parts": { "back": [object Array], "front": [object Array] } } ``` diff --git a/docs/extension/QUnit.extend.md b/docs/extension/QUnit.extend.md index 23799e32e..8e5a42d9d 100644 --- a/docs/extension/QUnit.extend.md +++ b/docs/extension/QUnit.extend.md @@ -29,21 +29,21 @@ This method will modify the `target` object to contain the "own" properties defi Use `QUnit.extend` to merge two objects. ```js -QUnit.test( "QUnit.extend", assert => { +QUnit.test('QUnit.extend', assert => { const base = { a: 1, b: 2, z: 3 }; - QUnit.extend( base, { + QUnit.extend(base, { b: 2.5, c: 3, z: undefined - } ); + }); - assert.strictEqual( base.a, 1, "Unspecified values are not modified" ); - assert.strictEqual( base.b, 2.5, "Existing values are updated" ); - assert.strictEqual( base.c, 3, "New values are defined" ); - assert.false( "z" in base, "Values specified as `undefined` are removed" ); + assert.strictEqual(base.a, 1, 'Unspecified values are not modified'); + assert.strictEqual(base.b, 2.5, 'Existing values are updated'); + assert.strictEqual(base.c, 3, 'New values are defined'); + assert.false('z' in base, 'Values specified as `undefined` are removed'); }); ``` diff --git a/docs/extension/QUnit.onUncaughtException.md b/docs/extension/QUnit.onUncaughtException.md index 1bc216f22..f4adc512f 100644 --- a/docs/extension/QUnit.onUncaughtException.md +++ b/docs/extension/QUnit.onUncaughtException.md @@ -20,16 +20,16 @@ Handle a global error that should result in a failed test run. ## Examples ```js -const error = new Error( "Failed to reverse the polarity of the neutron flow" ); -QUnit.onUncaughtException( error ); +const error = new Error('Failed to reverse the polarity of the neutron flow'); +QUnit.onUncaughtException(error); ``` ```js -process.on( "uncaughtException", QUnit.onUncaughtException ); +process.on('uncaughtException', QUnit.onUncaughtException); ``` ```js -window.addEventListener( "unhandledrejection", function( event ) { - QUnit.onUncaughtException( event.reason ); -} ); +window.addEventListener('unhandledrejection', function (event) { + QUnit.onUncaughtException(event.reason); +}); ``` diff --git a/docs/extension/QUnit.stack.md b/docs/extension/QUnit.stack.md index fb47a6914..972527003 100644 --- a/docs/extension/QUnit.stack.md +++ b/docs/extension/QUnit.stack.md @@ -30,17 +30,15 @@ Not all [browsers support retrieving stracktraces][browsers]. In those, `QUnit.s The stacktrace line can be used on custom assertions and reporters. The following example [logs](../callbacks/QUnit.log.md) the line of each passing assertion. ```js -QUnit.log( function( details ) { - if ( details.result ) { - +QUnit.log(function (details) { + if (details.result) { // 5 is the line reference for the assertion method, not the following line. - console.log( QUnit.stack( 5 ) ); + console.log(QUnit.stack(5)); } -} ); - -QUnit.test( "foo", assert => { +}); +QUnit.test('foo', assert => { // the log callback will report the position of the following line. - assert.true( true ); -} ); + assert.true(true); +}); ``` diff --git a/package-lock.json b/package-lock.json index 4c7e9e2dc..c83ffcd04 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,8 +1,8596 @@ { "name": "qunit", "version": "2.18.0-pre", - "lockfileVersion": 1, + "lockfileVersion": 2, "requires": true, + "packages": { + "": { + "name": "qunit", + "version": "2.18.0-pre", + "license": "MIT", + "dependencies": { + "commander": "7.2.0", + "node-watch": "0.7.3", + "tiny-glob": "0.2.9" + }, + "bin": { + "qunit": "bin/qunit.js" + }, + "devDependencies": { + "@babel/core": "^7.15.5", + "@babel/plugin-external-helpers": "^7.14.5", + "@babel/preset-env": "^7.15.6", + "@qunitjs/browserstack-runner": "0.9.5-qunitjs.1", + "@rollup/plugin-babel": "^5.3.0", + "@rollup/plugin-commonjs": "^20.0.0", + "@rollup/plugin-node-resolve": "^13.0.4", + "@rollup/plugin-replace": "^3.0.0", + "coveralls": "^3.1.1", + "eslint": "7.32.0", + "eslint-config-semistandard": "^16.0.0", + "eslint-config-standard": "^16.0.3", + "eslint-plugin-compat": "^4.0.2", + "eslint-plugin-html": "^6.1.2", + "eslint-plugin-import": "^2.25.4", + "eslint-plugin-json-es": "^1.5.5", + "eslint-plugin-markdown": "^2.2.1", + "eslint-plugin-node": "^11.1.0", + "eslint-plugin-promise": "^6.0.0", + "eslint-plugin-qunit": "^6.2.0", + "fixturify": "^2.1.1", + "fuzzysort": "1.2.1", + "grunt": "^1.4.1", + "grunt-contrib-connect": "^3.0.0", + "grunt-contrib-copy": "^1.0.0", + "grunt-contrib-qunit": "^5.1.0", + "grunt-git-authors": "^3.2.0", + "grunt-search": "^0.1.8", + "kleur": "4.1.4", + "npm-reporter": "file:./test/cli/fixtures/npm-reporter", + "nyc": "^15.1.0", + "proxyquire": "^1.8.0", + "requirejs": "^2.3.6", + "rimraf": "^3.0.2", + "rollup": "^2.56.3", + "semver": "^7.3.5" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.14.5.tgz", + "integrity": "sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.15.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.15.0.tgz", + "integrity": "sha512-0NqAC1IJE0S0+lL1SWFMxMkz1pKCNCjI4tr2Zx4LJSXxCLAdr6KyArnY+sno5m3yH9g737ygOyPABDsnXkpxiA==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.15.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.15.5.tgz", + "integrity": "sha512-pYgXxiwAgQpgM1bNkZsDEq85f0ggXMA5L7c+o3tskGMh2BunCI9QUwB9Z4jpvXUOuMdyGKiGKQiRe11VS6Jzvg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.14.5", + "@babel/generator": "^7.15.4", + "@babel/helper-compilation-targets": "^7.15.4", + "@babel/helper-module-transforms": "^7.15.4", + "@babel/helpers": "^7.15.4", + "@babel/parser": "^7.15.5", + "@babel/template": "^7.15.4", + "@babel/traverse": "^7.15.4", + "@babel/types": "^7.15.4", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.1.2", + "semver": "^6.3.0", + "source-map": "^0.5.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.15.4.tgz", + "integrity": "sha512-d3itta0tu+UayjEORPNz6e1T3FtvWlP5N4V5M+lhp/CxT4oAA7/NcScnpRyspUMLK6tu9MNHmQHxRykuN2R7hw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.15.4", + "jsesc": "^2.5.1", + "source-map": "^0.5.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.15.4.tgz", + "integrity": "sha512-QwrtdNvUNsPCj2lfNQacsGSQvGX8ee1ttrBrcozUP2Sv/jylewBP/8QFe6ZkBsC8T/GYWonNAWJV4aRR9AL2DA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.15.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.15.4.tgz", + "integrity": "sha512-P8o7JP2Mzi0SdC6eWr1zF+AEYvrsZa7GSY1lTayjF5XJhVH0kjLYUZPvTMflP7tBgZoe9gIhTa60QwFpqh/E0Q==", + "dev": true, + "dependencies": { + "@babel/helper-explode-assignable-expression": "^7.15.4", + "@babel/types": "^7.15.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.15.4.tgz", + "integrity": "sha512-rMWPCirulnPSe4d+gwdWXLfAXTTBj8M3guAf5xFQJ0nvFY7tfNAFnWdqaHegHlgDZOCT4qvhF3BYlSJag8yhqQ==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.15.0", + "@babel/helper-validator-option": "^7.14.5", + "browserslist": "^4.16.6", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.15.4.tgz", + "integrity": "sha512-7ZmzFi+DwJx6A7mHRwbuucEYpyBwmh2Ca0RvI6z2+WLZYCqV0JOaLb+u0zbtmDicebgKBZgqbYfLaKNqSgv5Pw==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.15.4", + "@babel/helper-function-name": "^7.15.4", + "@babel/helper-member-expression-to-functions": "^7.15.4", + "@babel/helper-optimise-call-expression": "^7.15.4", + "@babel/helper-replace-supers": "^7.15.4", + "@babel/helper-split-export-declaration": "^7.15.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.14.5.tgz", + "integrity": "sha512-TLawwqpOErY2HhWbGJ2nZT5wSkR192QpN+nBg1THfBfftrlvOh+WbhrxXCH4q4xJ9Gl16BGPR/48JA+Ryiho/A==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.14.5", + "regexpu-core": "^4.7.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-define-polyfill-provider": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.2.3.tgz", + "integrity": "sha512-RH3QDAfRMzj7+0Nqu5oqgO5q9mFtQEVvCRsi8qCEfzLR9p2BHfn5FzhSB2oj1fF7I2+DcTORkYaQ6aTR9Cofew==", + "dev": true, + "dependencies": { + "@babel/helper-compilation-targets": "^7.13.0", + "@babel/helper-module-imports": "^7.12.13", + "@babel/helper-plugin-utils": "^7.13.0", + "@babel/traverse": "^7.13.0", + "debug": "^4.1.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.14.2", + "semver": "^6.1.2" + }, + "peerDependencies": { + "@babel/core": "^7.4.0-0" + } + }, + "node_modules/@babel/helper-define-polyfill-provider/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-explode-assignable-expression": { + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.15.4.tgz", + "integrity": "sha512-J14f/vq8+hdC2KoWLIQSsGrC9EFBKE4NFts8pfMpymfApds+fPqR30AOUWc4tyr56h9l/GA1Sxv2q3dLZWbQ/g==", + "dev": true, + "dependencies": { + "@babel/types": "^7.15.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-function-name": { + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.15.4.tgz", + "integrity": "sha512-Z91cOMM4DseLIGOnog+Z8OI6YseR9bua+HpvLAQ2XayUGU+neTtX+97caALaLdyu53I/fjhbeCnWnRH1O3jFOw==", + "dev": true, + "dependencies": { + "@babel/helper-get-function-arity": "^7.15.4", + "@babel/template": "^7.15.4", + "@babel/types": "^7.15.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-get-function-arity": { + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.15.4.tgz", + "integrity": "sha512-1/AlxSF92CmGZzHnC515hm4SirTxtpDnLEJ0UyEMgTMZN+6bxXKg04dKhiRx5Enel+SUA1G1t5Ed/yQia0efrA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.15.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-hoist-variables": { + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.15.4.tgz", + "integrity": "sha512-VTy085egb3jUGVK9ycIxQiPbquesq0HUQ+tPO0uv5mPEBZipk+5FkRKiWq5apuyTE9FUrjENB0rCf8y+n+UuhA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.15.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.15.4.tgz", + "integrity": "sha512-cokOMkxC/BTyNP1AlY25HuBWM32iCEsLPI4BHDpJCHHm1FU2E7dKWWIXJgQgSFiu4lp8q3bL1BIKwqkSUviqtA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.15.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.15.4.tgz", + "integrity": "sha512-jeAHZbzUwdW/xHgHQ3QmWR4Jg6j15q4w/gCfwZvtqOxoo5DKtLHk8Bsf4c5RZRC7NmLEs+ohkdq8jFefuvIxAA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.15.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.15.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.15.7.tgz", + "integrity": "sha512-ZNqjjQG/AuFfekFTY+7nY4RgBSklgTu970c7Rj3m/JOhIu5KPBUuTA9AY6zaKcUvk4g6EbDXdBnhi35FAssdSw==", + "dev": true, + "dependencies": { + "@babel/helper-module-imports": "^7.15.4", + "@babel/helper-replace-supers": "^7.15.4", + "@babel/helper-simple-access": "^7.15.4", + "@babel/helper-split-export-declaration": "^7.15.4", + "@babel/helper-validator-identifier": "^7.15.7", + "@babel/template": "^7.15.4", + "@babel/traverse": "^7.15.4", + "@babel/types": "^7.15.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.15.4.tgz", + "integrity": "sha512-E/z9rfbAOt1vDW1DR7k4SzhzotVV5+qMciWV6LaG1g4jeFrkDlJedjtV4h0i4Q/ITnUu+Pk08M7fczsB9GXBDw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.15.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.14.5.tgz", + "integrity": "sha512-/37qQCE3K0vvZKwoK4XU/irIJQdIfCJuhU5eKnNxpFDsOkgFaUAwbv+RYw6eYgsC0E4hS7r5KqGULUogqui0fQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-remap-async-to-generator": { + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.15.4.tgz", + "integrity": "sha512-v53MxgvMK/HCwckJ1bZrq6dNKlmwlyRNYM6ypaRTdXWGOE2c1/SCa6dL/HimhPulGhZKw9W0QhREM583F/t0vQ==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.15.4", + "@babel/helper-wrap-function": "^7.15.4", + "@babel/types": "^7.15.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.15.4.tgz", + "integrity": "sha512-/ztT6khaXF37MS47fufrKvIsiQkx1LBRvSJNzRqmbyeZnTwU9qBxXYLaaT/6KaxfKhjs2Wy8kG8ZdsFUuWBjzw==", + "dev": true, + "dependencies": { + "@babel/helper-member-expression-to-functions": "^7.15.4", + "@babel/helper-optimise-call-expression": "^7.15.4", + "@babel/traverse": "^7.15.4", + "@babel/types": "^7.15.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.15.4.tgz", + "integrity": "sha512-UzazrDoIVOZZcTeHHEPYrr1MvTR/K+wgLg6MY6e1CJyaRhbibftF6fR2KU2sFRtI/nERUZR9fBd6aKgBlIBaPg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.15.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.15.4.tgz", + "integrity": "sha512-BMRLsdh+D1/aap19TycS4eD1qELGrCBJwzaY9IE8LrpJtJb+H7rQkPIdsfgnMtLBA6DJls7X9z93Z4U8h7xw0A==", + "dev": true, + "dependencies": { + "@babel/types": "^7.15.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.15.4.tgz", + "integrity": "sha512-HsFqhLDZ08DxCpBdEVtKmywj6PQbwnF6HHybur0MAnkAKnlS6uHkwnmRIkElB2Owpfb4xL4NwDmDLFubueDXsw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.15.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.15.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz", + "integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.14.5.tgz", + "integrity": "sha512-OX8D5eeX4XwcroVW45NMvoYaIuFI+GQpA2a8Gi+X/U/cDUIRsV37qQfF905F0htTRCREQIB4KqPeaveRJUl3Ow==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-wrap-function": { + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.15.4.tgz", + "integrity": "sha512-Y2o+H/hRV5W8QhIfTpRIBwl57y8PrZt6JM3V8FOo5qarjshHItyH5lXlpMfBfmBefOqSCpKZs/6Dxqp0E/U+uw==", + "dev": true, + "dependencies": { + "@babel/helper-function-name": "^7.15.4", + "@babel/template": "^7.15.4", + "@babel/traverse": "^7.15.4", + "@babel/types": "^7.15.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.15.4.tgz", + "integrity": "sha512-V45u6dqEJ3w2rlryYYXf6i9rQ5YMNu4FLS6ngs8ikblhu2VdR1AqAd6aJjBzmf2Qzh6KOLqKHxEN9+TFbAkAVQ==", + "dev": true, + "dependencies": { + "@babel/template": "^7.15.4", + "@babel/traverse": "^7.15.4", + "@babel/types": "^7.15.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz", + "integrity": "sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.14.5", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.15.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.15.7.tgz", + "integrity": "sha512-rycZXvQ+xS9QyIcJ9HXeDWf1uxqlbVFAUq0Rq0dbc50Zb/+wUe/ehyfzGfm9KZZF0kBejYgxltBXocP+gKdL2g==", + "dev": true, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.15.4.tgz", + "integrity": "sha512-eBnpsl9tlhPhpI10kU06JHnrYXwg3+V6CaP2idsCXNef0aeslpqyITXQ74Vfk5uHgY7IG7XP0yIH8b42KSzHog==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.15.4", + "@babel/plugin-proposal-optional-chaining": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.13.0" + } + }, + "node_modules/@babel/plugin-external-helpers": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-external-helpers/-/plugin-external-helpers-7.14.5.tgz", + "integrity": "sha512-q/B/hLX+nDGk73Xn529d7Ar4ih17J8pNBbsXafq8oXij0XfFEA/bks+u+6q5q04zO5o/qivjzui6BqzPfYShEg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-async-generator-functions": { + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.15.4.tgz", + "integrity": "sha512-2zt2g5vTXpMC3OmK6uyjvdXptbhBXfA77XGrd3gh93zwG8lZYBLOBImiGBEG0RANu3JqKEACCz5CGk73OJROBw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5", + "@babel/helper-remap-async-to-generator": "^7.15.4", + "@babel/plugin-syntax-async-generators": "^7.8.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-class-properties": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.14.5.tgz", + "integrity": "sha512-q/PLpv5Ko4dVc1LYMpCY7RVAAO4uk55qPwrIuJ5QJ8c6cVuAmhu7I/49JOppXL6gXf7ZHzpRVEUZdYoPLM04Gg==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.14.5", + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-class-static-block": { + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.15.4.tgz", + "integrity": "sha512-M682XWrrLNk3chXCjoPUQWOyYsB93B9z3mRyjtqqYJWDf2mfCdIYgDrA11cgNVhAQieaq6F2fn2f3wI0U4aTjA==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.15.4", + "@babel/helper-plugin-utils": "^7.14.5", + "@babel/plugin-syntax-class-static-block": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0" + } + }, + "node_modules/@babel/plugin-proposal-dynamic-import": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.14.5.tgz", + "integrity": "sha512-ExjiNYc3HDN5PXJx+bwC50GIx/KKanX2HiggnIUAYedbARdImiCU4RhhHfdf0Kd7JNXGpsBBBCOm+bBVy3Gb0g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5", + "@babel/plugin-syntax-dynamic-import": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-export-namespace-from": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.14.5.tgz", + "integrity": "sha512-g5POA32bXPMmSBu5Dx/iZGLGnKmKPc5AiY7qfZgurzrCYgIztDlHFbznSNCoQuv57YQLnQfaDi7dxCtLDIdXdA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-json-strings": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.14.5.tgz", + "integrity": "sha512-NSq2fczJYKVRIsUJyNxrVUMhB27zb7N7pOFGQOhBKJrChbGcgEAqyZrmZswkPk18VMurEeJAaICbfm57vUeTbQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5", + "@babel/plugin-syntax-json-strings": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-logical-assignment-operators": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.14.5.tgz", + "integrity": "sha512-YGn2AvZAo9TwyhlLvCCWxD90Xq8xJ4aSgaX3G5D/8DW94L8aaT+dS5cSP+Z06+rCJERGSr9GxMBZ601xoc2taw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-nullish-coalescing-operator": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.14.5.tgz", + "integrity": "sha512-gun/SOnMqjSb98Nkaq2rTKMwervfdAoz6NphdY0vTfuzMfryj+tDGb2n6UkDKwez+Y8PZDhE3D143v6Gepp4Hg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-numeric-separator": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.14.5.tgz", + "integrity": "sha512-yiclALKe0vyZRZE0pS6RXgjUOt87GWv6FYa5zqj15PvhOGFO69R5DusPlgK/1K5dVnCtegTiWu9UaBSrLLJJBg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5", + "@babel/plugin-syntax-numeric-separator": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-object-rest-spread": { + "version": "7.15.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.15.6.tgz", + "integrity": "sha512-qtOHo7A1Vt+O23qEAX+GdBpqaIuD3i9VRrWgCJeq7WO6H2d14EK3q11urj5Te2MAeK97nMiIdRpwd/ST4JFbNg==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.15.0", + "@babel/helper-compilation-targets": "^7.15.4", + "@babel/helper-plugin-utils": "^7.14.5", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-transform-parameters": "^7.15.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-optional-catch-binding": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.14.5.tgz", + "integrity": "sha512-3Oyiixm0ur7bzO5ybNcZFlmVsygSIQgdOa7cTfOYCMY+wEPAYhZAJxi3mixKFCTCKUhQXuCTtQ1MzrpL3WT8ZQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-optional-chaining": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.14.5.tgz", + "integrity": "sha512-ycz+VOzo2UbWNI1rQXxIuMOzrDdHGrI23fRiz/Si2R4kv2XZQ1BK8ccdHwehMKBlcH/joGW/tzrUmo67gbJHlQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.14.5", + "@babel/plugin-syntax-optional-chaining": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-private-methods": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.14.5.tgz", + "integrity": "sha512-838DkdUA1u+QTCplatfq4B7+1lnDa/+QMI89x5WZHBcnNv+47N8QEj2k9I2MUU9xIv8XJ4XvPCviM/Dj7Uwt9g==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.14.5", + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.15.4.tgz", + "integrity": "sha512-X0UTixkLf0PCCffxgu5/1RQyGGbgZuKoI+vXP4iSbJSYwPb7hu06omsFGBvQ9lJEvwgrxHdS8B5nbfcd8GyUNA==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.15.4", + "@babel/helper-create-class-features-plugin": "^7.15.4", + "@babel/helper-plugin-utils": "^7.14.5", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-unicode-property-regex": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.14.5.tgz", + "integrity": "sha512-6axIeOU5LnY471KenAB9vI8I5j7NQ2d652hIYwVyRfgaZT5UpiqFKCuVXCDMSrU+3VFafnu2c5m3lrWIlr6A5Q==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.14.5", + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-dynamic-import": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", + "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-export-namespace-from": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", + "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.3" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-arrow-functions": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.14.5.tgz", + "integrity": "sha512-KOnO0l4+tD5IfOdi4x8C1XmEIRWUjNRV8wc6K2vz/3e8yAOoZZvsRXRRIF/yo/MAOFb4QjtAw9xSxMXbSMRy8A==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-to-generator": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.14.5.tgz", + "integrity": "sha512-szkbzQ0mNk0rpu76fzDdqSyPu0MuvpXgC+6rz5rpMb5OIRxdmHfQxrktL8CYolL2d8luMCZTR0DpIMIdL27IjA==", + "dev": true, + "dependencies": { + "@babel/helper-module-imports": "^7.14.5", + "@babel/helper-plugin-utils": "^7.14.5", + "@babel/helper-remap-async-to-generator": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoped-functions": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.14.5.tgz", + "integrity": "sha512-dtqWqdWZ5NqBX3KzsVCWfQI3A53Ft5pWFCT2eCVUftWZgjc5DpDponbIF1+c+7cSGk2wN0YK7HGL/ezfRbpKBQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoping": { + "version": "7.15.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.15.3.tgz", + "integrity": "sha512-nBAzfZwZb4DkaGtOes1Up1nOAp9TDRRFw4XBzBBSG9QK7KVFmYzgj9o9sbPv7TX5ofL4Auq4wZnxCoPnI/lz2Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-classes": { + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.15.4.tgz", + "integrity": "sha512-Yjvhex8GzBmmPQUvpXRPWQ9WnxXgAFuZSrqOK/eJlOGIXwvv8H3UEdUigl1gb/bnjTrln+e8bkZUYCBt/xYlBg==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.15.4", + "@babel/helper-function-name": "^7.15.4", + "@babel/helper-optimise-call-expression": "^7.15.4", + "@babel/helper-plugin-utils": "^7.14.5", + "@babel/helper-replace-supers": "^7.15.4", + "@babel/helper-split-export-declaration": "^7.15.4", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-computed-properties": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.14.5.tgz", + "integrity": "sha512-pWM+E4283UxaVzLb8UBXv4EIxMovU4zxT1OPnpHJcmnvyY9QbPPTKZfEj31EUvG3/EQRbYAGaYEUZ4yWOBC2xg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-destructuring": { + "version": "7.14.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.14.7.tgz", + "integrity": "sha512-0mDE99nK+kVh3xlc5vKwB6wnP9ecuSj+zQCa/n0voENtP/zymdT4HH6QEb65wjjcbqr1Jb/7z9Qp7TF5FtwYGw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dotall-regex": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.14.5.tgz", + "integrity": "sha512-loGlnBdj02MDsFaHhAIJzh7euK89lBrGIdM9EAtHFo6xKygCUGuuWe07o1oZVk287amtW1n0808sQM99aZt3gw==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.14.5", + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-keys": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.14.5.tgz", + "integrity": "sha512-iJjbI53huKbPDAsJ8EmVmvCKeeq21bAze4fu9GBQtSLqfvzj2oRuHVx4ZkDwEhg1htQ+5OBZh/Ab0XDf5iBZ7A==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-exponentiation-operator": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.14.5.tgz", + "integrity": "sha512-jFazJhMBc9D27o9jDnIE5ZErI0R0m7PbKXVq77FFvqFbzvTMuv8jaAwLZ5PviOLSFttqKIW0/wxNSDbjLk0tYA==", + "dev": true, + "dependencies": { + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.14.5", + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-for-of": { + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.15.4.tgz", + "integrity": "sha512-DRTY9fA751AFBDh2oxydvVm4SYevs5ILTWLs6xKXps4Re/KG5nfUkr+TdHCrRWB8C69TlzVgA9b3RmGWmgN9LA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-function-name": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.14.5.tgz", + "integrity": "sha512-vbO6kv0fIzZ1GpmGQuvbwwm+O4Cbm2NrPzwlup9+/3fdkuzo1YqOZcXw26+YUJB84Ja7j9yURWposEHLYwxUfQ==", + "dev": true, + "dependencies": { + "@babel/helper-function-name": "^7.14.5", + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-literals": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.14.5.tgz", + "integrity": "sha512-ql33+epql2F49bi8aHXxvLURHkxJbSmMKl9J5yHqg4PLtdE6Uc48CH1GS6TQvZ86eoB/ApZXwm7jlA+B3kra7A==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-member-expression-literals": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.14.5.tgz", + "integrity": "sha512-WkNXxH1VXVTKarWFqmso83xl+2V3Eo28YY5utIkbsmXoItO8Q3aZxN4BTS2k0hz9dGUloHK26mJMyQEYfkn/+Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-amd": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.14.5.tgz", + "integrity": "sha512-3lpOU8Vxmp3roC4vzFpSdEpGUWSMsHFreTWOMMLzel2gNGfHE5UWIh/LN6ghHs2xurUp4jRFYMUIZhuFbody1g==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.14.5", + "@babel/helper-plugin-utils": "^7.14.5", + "babel-plugin-dynamic-import-node": "^2.3.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.15.4.tgz", + "integrity": "sha512-qg4DPhwG8hKp4BbVDvX1s8cohM8a6Bvptu4l6Iingq5rW+yRUAhe/YRup/YcW2zCOlrysEWVhftIcKzrEZv3sA==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.15.4", + "@babel/helper-plugin-utils": "^7.14.5", + "@babel/helper-simple-access": "^7.15.4", + "babel-plugin-dynamic-import-node": "^2.3.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-systemjs": { + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.15.4.tgz", + "integrity": "sha512-fJUnlQrl/mezMneR72CKCgtOoahqGJNVKpompKwzv3BrEXdlPspTcyxrZ1XmDTIr9PpULrgEQo3qNKp6dW7ssw==", + "dev": true, + "dependencies": { + "@babel/helper-hoist-variables": "^7.15.4", + "@babel/helper-module-transforms": "^7.15.4", + "@babel/helper-plugin-utils": "^7.14.5", + "@babel/helper-validator-identifier": "^7.14.9", + "babel-plugin-dynamic-import-node": "^2.3.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-umd": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.14.5.tgz", + "integrity": "sha512-RfPGoagSngC06LsGUYyM9QWSXZ8MysEjDJTAea1lqRjNECE3y0qIJF/qbvJxc4oA4s99HumIMdXOrd+TdKaAAA==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.14.5", + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.14.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.14.9.tgz", + "integrity": "sha512-l666wCVYO75mlAtGFfyFwnWmIXQm3kSH0C3IRnJqWcZbWkoihyAdDhFm2ZWaxWTqvBvhVFfJjMRQ0ez4oN1yYA==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-new-target": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.14.5.tgz", + "integrity": "sha512-Nx054zovz6IIRWEB49RDRuXGI4Gy0GMgqG0cII9L3MxqgXz/+rgII+RU58qpo4g7tNEx1jG7rRVH4ihZoP4esQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-super": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.14.5.tgz", + "integrity": "sha512-MKfOBWzK0pZIrav9z/hkRqIk/2bTv9qvxHzPQc12RcVkMOzpIKnFCNYJip00ssKWYkd8Sf5g0Wr7pqJ+cmtuFg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5", + "@babel/helper-replace-supers": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-parameters": { + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.15.4.tgz", + "integrity": "sha512-9WB/GUTO6lvJU3XQsSr6J/WKvBC2hcs4Pew8YxZagi6GkTdniyqp8On5kqdK8MN0LMeu0mGbhPN+O049NV/9FQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-property-literals": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.14.5.tgz", + "integrity": "sha512-r1uilDthkgXW8Z1vJz2dKYLV1tuw2xsbrp3MrZmD99Wh9vsfKoob+JTgri5VUb/JqyKRXotlOtwgu4stIYCmnw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regenerator": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.14.5.tgz", + "integrity": "sha512-NVIY1W3ITDP5xQl50NgTKlZ0GrotKtLna08/uGY6ErQt6VEQZXla86x/CTddm5gZdcr+5GSsvMeTmWA5Ii6pkg==", + "dev": true, + "dependencies": { + "regenerator-transform": "^0.14.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-reserved-words": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.14.5.tgz", + "integrity": "sha512-cv4F2rv1nD4qdexOGsRQXJrOcyb5CrgjUH9PKrrtyhSDBNWGxd0UIitjyJiWagS+EbUGjG++22mGH1Pub8D6Vg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-shorthand-properties": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.14.5.tgz", + "integrity": "sha512-xLucks6T1VmGsTB+GWK5Pl9Jl5+nRXD1uoFdA5TSO6xtiNjtXTjKkmPdFXVLGlK5A2/or/wQMKfmQ2Y0XJfn5g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-spread": { + "version": "7.14.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.14.6.tgz", + "integrity": "sha512-Zr0x0YroFJku7n7+/HH3A2eIrGMjbmAIbJSVv0IZ+t3U2WUQUA64S/oeied2e+MaGSjmt4alzBCsK9E8gh+fag==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-sticky-regex": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.14.5.tgz", + "integrity": "sha512-Z7F7GyvEMzIIbwnziAZmnSNpdijdr4dWt+FJNBnBLz5mwDFkqIXU9wmBcWWad3QeJF5hMTkRe4dAq2sUZiG+8A==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-template-literals": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.14.5.tgz", + "integrity": "sha512-22btZeURqiepOfuy/VkFr+zStqlujWaarpMErvay7goJS6BWwdd6BY9zQyDLDa4x2S3VugxFb162IZ4m/S/+Gg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typeof-symbol": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.14.5.tgz", + "integrity": "sha512-lXzLD30ffCWseTbMQzrvDWqljvZlHkXU+CnseMhkMNqU1sASnCsz3tSzAaH3vCUXb9PHeUb90ZT1BdFTm1xxJw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-escapes": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.14.5.tgz", + "integrity": "sha512-crTo4jATEOjxj7bt9lbYXcBAM3LZaUrbP2uUdxb6WIorLmjNKSpHfIybgY4B8SRpbf8tEVIWH3Vtm7ayCrKocA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-regex": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.14.5.tgz", + "integrity": "sha512-UygduJpC5kHeCiRw/xDVzC+wj8VaYSoKl5JNVmbP7MadpNinAm3SvZCxZ42H37KZBKztz46YC73i9yV34d0Tzw==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.14.5", + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-env": { + "version": "7.15.6", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.15.6.tgz", + "integrity": "sha512-L+6jcGn7EWu7zqaO2uoTDjjMBW+88FXzV8KvrBl2z6MtRNxlsmUNRlZPaNNPUTgqhyC5DHNFk/2Jmra+ublZWw==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.15.0", + "@babel/helper-compilation-targets": "^7.15.4", + "@babel/helper-plugin-utils": "^7.14.5", + "@babel/helper-validator-option": "^7.14.5", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.15.4", + "@babel/plugin-proposal-async-generator-functions": "^7.15.4", + "@babel/plugin-proposal-class-properties": "^7.14.5", + "@babel/plugin-proposal-class-static-block": "^7.15.4", + "@babel/plugin-proposal-dynamic-import": "^7.14.5", + "@babel/plugin-proposal-export-namespace-from": "^7.14.5", + "@babel/plugin-proposal-json-strings": "^7.14.5", + "@babel/plugin-proposal-logical-assignment-operators": "^7.14.5", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.14.5", + "@babel/plugin-proposal-numeric-separator": "^7.14.5", + "@babel/plugin-proposal-object-rest-spread": "^7.15.6", + "@babel/plugin-proposal-optional-catch-binding": "^7.14.5", + "@babel/plugin-proposal-optional-chaining": "^7.14.5", + "@babel/plugin-proposal-private-methods": "^7.14.5", + "@babel/plugin-proposal-private-property-in-object": "^7.15.4", + "@babel/plugin-proposal-unicode-property-regex": "^7.14.5", + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5", + "@babel/plugin-transform-arrow-functions": "^7.14.5", + "@babel/plugin-transform-async-to-generator": "^7.14.5", + "@babel/plugin-transform-block-scoped-functions": "^7.14.5", + "@babel/plugin-transform-block-scoping": "^7.15.3", + "@babel/plugin-transform-classes": "^7.15.4", + "@babel/plugin-transform-computed-properties": "^7.14.5", + "@babel/plugin-transform-destructuring": "^7.14.7", + "@babel/plugin-transform-dotall-regex": "^7.14.5", + "@babel/plugin-transform-duplicate-keys": "^7.14.5", + "@babel/plugin-transform-exponentiation-operator": "^7.14.5", + "@babel/plugin-transform-for-of": "^7.15.4", + "@babel/plugin-transform-function-name": "^7.14.5", + "@babel/plugin-transform-literals": "^7.14.5", + "@babel/plugin-transform-member-expression-literals": "^7.14.5", + "@babel/plugin-transform-modules-amd": "^7.14.5", + "@babel/plugin-transform-modules-commonjs": "^7.15.4", + "@babel/plugin-transform-modules-systemjs": "^7.15.4", + "@babel/plugin-transform-modules-umd": "^7.14.5", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.14.9", + "@babel/plugin-transform-new-target": "^7.14.5", + "@babel/plugin-transform-object-super": "^7.14.5", + "@babel/plugin-transform-parameters": "^7.15.4", + "@babel/plugin-transform-property-literals": "^7.14.5", + "@babel/plugin-transform-regenerator": "^7.14.5", + "@babel/plugin-transform-reserved-words": "^7.14.5", + "@babel/plugin-transform-shorthand-properties": "^7.14.5", + "@babel/plugin-transform-spread": "^7.14.6", + "@babel/plugin-transform-sticky-regex": "^7.14.5", + "@babel/plugin-transform-template-literals": "^7.14.5", + "@babel/plugin-transform-typeof-symbol": "^7.14.5", + "@babel/plugin-transform-unicode-escapes": "^7.14.5", + "@babel/plugin-transform-unicode-regex": "^7.14.5", + "@babel/preset-modules": "^0.1.4", + "@babel/types": "^7.15.6", + "babel-plugin-polyfill-corejs2": "^0.2.2", + "babel-plugin-polyfill-corejs3": "^0.2.2", + "babel-plugin-polyfill-regenerator": "^0.2.2", + "core-js-compat": "^3.16.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-env/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/preset-modules": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.4.tgz", + "integrity": "sha512-J36NhwnfdzpmH41M1DrnkkgAqhZaqr/NBdPfQ677mLzlaXo+oDiv1deyCDtgAhz8p328otdob0Du7+xgHGZbKg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", + "@babel/plugin-transform-dotall-regex": "^7.4.4", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.15.4.tgz", + "integrity": "sha512-99catp6bHCaxr4sJ/DbTGgHS4+Rs2RVd2g7iOap6SLGPDknRK9ztKNsE/Fg6QhSeh1FGE5f6gHGQmvvn3I3xhw==", + "dev": true, + "dependencies": { + "regenerator-runtime": "^0.13.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.15.4.tgz", + "integrity": "sha512-UgBAfEa1oGuYgDIPM2G+aHa4Nlo9Lh6mGD2bDBGMTbYnc38vulXPuC1MGjYILIEmlwl6Rd+BPR9ee3gm20CBtg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.14.5", + "@babel/parser": "^7.15.4", + "@babel/types": "^7.15.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.15.4.tgz", + "integrity": "sha512-W6lQD8l4rUbQR/vYgSuCAE75ADyyQvOpFVsvPPdkhf6lATXAsQIG9YdtOcu8BB1dZ0LKu+Zo3c1wEcbKeuhdlA==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.14.5", + "@babel/generator": "^7.15.4", + "@babel/helper-function-name": "^7.15.4", + "@babel/helper-hoist-variables": "^7.15.4", + "@babel/helper-split-export-declaration": "^7.15.4", + "@babel/parser": "^7.15.4", + "@babel/types": "^7.15.4", + "debug": "^4.1.0", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.15.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.15.6.tgz", + "integrity": "sha512-BPU+7QhqNjmWyDO0/vitH/CuhpV8ZmK1wpKva8nuyNF5MJfuRNWMc+hc14+u9xT93kvykMdncrJT19h74uB1Ig==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.14.9", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz", + "integrity": "sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.1.1", + "espree": "^7.3.0", + "globals": "^13.9.0", + "ignore": "^4.0.6", + "import-fresh": "^3.2.1", + "js-yaml": "^3.13.1", + "minimatch": "^3.0.4", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "13.11.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.11.0.tgz", + "integrity": "sha512-08/xrJ7wQjK9kkkRoI3OFUBbLx4f+6x3SGwcPvQ0QH6goFDrOU2oyAWrmh3dJezu65buo+HBMzAMQy6rovVC3g==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz", + "integrity": "sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^1.2.0", + "debug": "^4.1.1", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.0.tgz", + "integrity": "sha512-wdppn25U8z/2yiaT6YGquE6X8sSv7hNMWSXYSSU1jGv/yd6XqjXgTDJ8KP4NgjTXfJ3GbRjeeb8RTV7a/VpM+w==", + "dev": true + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@mdn/browser-compat-data": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@mdn/browser-compat-data/-/browser-compat-data-4.1.13.tgz", + "integrity": "sha512-OFTriJmUcXnA1RjfKDJOfEmhHO44OV7Gxpqnh7eOM+3qep64Yt+uNF4jEeIFHnwTFH5tMcnBQxEI91dXDGiGYQ==", + "dev": true + }, + "node_modules/@qunitjs/browserstack-runner": { + "version": "0.9.5-qunitjs.1", + "resolved": "https://registry.npmjs.org/@qunitjs/browserstack-runner/-/browserstack-runner-0.9.5-qunitjs.1.tgz", + "integrity": "sha512-FAF+DWr6naJI0PrnummKqfQaCOKqZn9Enao7obr9i4hEoAO3xD/osXiNZU8JNmuH1+/q6YQ6uIXwIk3A2s+ddQ==", + "dev": true, + "dependencies": { + "browserstack": "1.3.0", + "chalk": "0.4.0", + "circular-json": "0.3.1", + "js-reporters": "2.1.0", + "mime": "1.6.0", + "resolve": "1.1.7", + "send": "0.16.2", + "tunnel": "0.0.3", + "yargs": "15.3.1" + }, + "bin": { + "browserstack-runner": "bin/runner.js" + } + }, + "node_modules/@qunitjs/browserstack-runner/node_modules/ansi-styles": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-1.0.0.tgz", + "integrity": "sha1-yxAt8cVvUSPquLZ817mAJ6AnkXg=", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@qunitjs/browserstack-runner/node_modules/chalk": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-0.4.0.tgz", + "integrity": "sha1-UZmj3c0MHv4jvAjBsCewYXbgxk8=", + "dev": true, + "dependencies": { + "ansi-styles": "~1.0.0", + "has-color": "~0.1.0", + "strip-ansi": "~0.1.0" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@qunitjs/browserstack-runner/node_modules/resolve": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", + "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=", + "dev": true + }, + "node_modules/@rollup/plugin-babel": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.0.tgz", + "integrity": "sha512-9uIC8HZOnVLrLHxayq/PTzw+uS25E14KPUBh5ktF+18Mjo5yK0ToMMx6epY0uEgkjwJw0aBW4x2horYXh8juWw==", + "dev": true, + "dependencies": { + "@babel/helper-module-imports": "^7.10.4", + "@rollup/pluginutils": "^3.1.0" + }, + "engines": { + "node": ">= 10.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0", + "@types/babel__core": "^7.1.9", + "rollup": "^1.20.0||^2.0.0" + }, + "peerDependenciesMeta": { + "@types/babel__core": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-commonjs": { + "version": "20.0.0", + "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-20.0.0.tgz", + "integrity": "sha512-5K0g5W2Ol8hAcTHqcTBHiA7M58tfmYi1o9KxeJuuRNpGaTa5iLjcyemBitCBcKXaHamOBBEH2dGom6v6Unmqjg==", + "dev": true, + "dependencies": { + "@rollup/pluginutils": "^3.1.0", + "commondir": "^1.0.1", + "estree-walker": "^2.0.1", + "glob": "^7.1.6", + "is-reference": "^1.2.1", + "magic-string": "^0.25.7", + "resolve": "^1.17.0" + }, + "engines": { + "node": ">= 8.0.0" + }, + "peerDependencies": { + "rollup": "^2.38.3" + } + }, + "node_modules/@rollup/plugin-commonjs/node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true + }, + "node_modules/@rollup/plugin-node-resolve": { + "version": "13.0.4", + "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-13.0.4.tgz", + "integrity": "sha512-eYq4TFy40O8hjeDs+sIxEH/jc9lyuI2k9DM557WN6rO5OpnC2qXMBNj4IKH1oHrnAazL49C5p0tgP0/VpqJ+/w==", + "dev": true, + "dependencies": { + "@rollup/pluginutils": "^3.1.0", + "@types/resolve": "1.17.1", + "builtin-modules": "^3.1.0", + "deepmerge": "^4.2.2", + "is-module": "^1.0.0", + "resolve": "^1.19.0" + }, + "engines": { + "node": ">= 10.0.0" + }, + "peerDependencies": { + "rollup": "^2.42.0" + } + }, + "node_modules/@rollup/plugin-replace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-3.0.0.tgz", + "integrity": "sha512-3c7JCbMuYXM4PbPWT4+m/4Y6U60SgsnDT/cCyAyUKwFHg7pTSfsSQzIpETha3a3ig6OdOKzZz87D9ZXIK3qsDg==", + "dev": true, + "dependencies": { + "@rollup/pluginutils": "^3.1.0", + "magic-string": "^0.25.7" + }, + "peerDependencies": { + "rollup": "^1.20.0 || ^2.0.0" + } + }, + "node_modules/@rollup/pluginutils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz", + "integrity": "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==", + "dev": true, + "dependencies": { + "@types/estree": "0.0.39", + "estree-walker": "^1.0.1", + "picomatch": "^2.2.2" + }, + "engines": { + "node": ">= 8.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0" + } + }, + "node_modules/@types/estree": { + "version": "0.0.39", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", + "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==", + "dev": true + }, + "node_modules/@types/fs-extra": { + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-8.1.2.tgz", + "integrity": "sha512-SvSrYXfWSc7R4eqnOzbQF4TZmfpNSM9FrSWLU3EUnWBuyZqNBOrv1B1JA3byUDPUl9z4Ab3jeZG2eDdySlgNMg==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/glob": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.4.tgz", + "integrity": "sha512-w+LsMxKyYQm347Otw+IfBXOv9UWVjpHpCDdbBMt8Kz/xbvCYNjP+0qPh91Km3iKfSRLBB0P7fAMf0KHrPu+MyA==", + "dev": true, + "dependencies": { + "@types/minimatch": "*", + "@types/node": "*" + } + }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", + "dev": true + }, + "node_modules/@types/mdast": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.10.tgz", + "integrity": "sha512-W864tg/Osz1+9f4lrGTZpCSO5/z4608eUp19tbozkq2HJK6i3z1kT0H9tlADXuYIb1YYOBByU4Jsqkk75q48qA==", + "dev": true, + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/minimatch": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.5.tgz", + "integrity": "sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==", + "dev": true + }, + "node_modules/@types/node": { + "version": "16.9.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.9.4.tgz", + "integrity": "sha512-KDazLNYAGIuJugdbULwFZULF9qQ13yNWEBFnfVpqlpgAAo6H/qnM9RjBgh0A0kmHf3XxAKLdN5mTIng9iUvVLA==", + "dev": true + }, + "node_modules/@types/resolve": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz", + "integrity": "sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/rimraf": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/rimraf/-/rimraf-2.0.5.tgz", + "integrity": "sha512-YyP+VfeaqAyFmXoTh3HChxOQMyjByRMsHU7kc5KOJkSlXudhMhQIALbYV7rHh/l8d2lX3VUQzprrcAgWdRuU8g==", + "dev": true, + "dependencies": { + "@types/glob": "*", + "@types/node": "*" + } + }, + "node_modules/@types/unist": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.6.tgz", + "integrity": "sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==", + "dev": true + }, + "node_modules/@types/yauzl": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.9.2.tgz", + "integrity": "sha512-8uALY5LTvSuHgloDVUvWP3pIauILm+8/0pDMokuDYIoNsOkSwd5AiHBTSEJjKTDcZr5z8UpgOWZkxBF4iJftoA==", + "dev": true, + "optional": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "dev": true + }, + "node_modules/accepts": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", + "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", + "dev": true, + "dependencies": { + "mime-types": "~2.1.24", + "negotiator": "0.6.2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/agent-base": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-5.1.1.tgz", + "integrity": "sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g==", + "dev": true, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dev": true, + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/append-transform": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-2.0.0.tgz", + "integrity": "sha512-7yeyCEurROLQJFv5Xj4lEGTy0borxepjFv1g22oAdqFu//SrAlDl1O1Nxx15SH1RoliUml6p8dwJW9jvZughhg==", + "dev": true, + "dependencies": { + "default-require-extensions": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/archy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", + "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=", + "dev": true + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/array-each": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/array-each/-/array-each-1.0.1.tgz", + "integrity": "sha1-p5SvDAWrF1KEbudTofIRoFugxE8=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-includes": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.4.tgz", + "integrity": "sha512-ZTNSQkmWumEbiHO2GF4GmWxYVTiQyJy2XOTa15sdQSrvKn7l+180egQMqlrMOUMCyLMD7pmyQe4mMDUT6Behrw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.1", + "get-intrinsic": "^1.1.1", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-slice": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-1.1.0.tgz", + "integrity": "sha512-B1qMD3RBP7O8o0H2KbrXDyB0IccejMF15+87Lvlor12ONPRHP6gTjXMNkt/d3ZuOGbAe66hFmaCfECI24Ufp6w==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.2.5.tgz", + "integrity": "sha512-KaYU+S+ndVqyUnignHftkwc58o3uVU1jzczILJ1tN2YaIZpFIKBiP/x/j97E5MVPsaCloPbqWLB/8qCTVvT2qg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/asn1": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "dev": true, + "dependencies": { + "safer-buffer": "~2.1.0" + } + }, + "node_modules/assert": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/assert/-/assert-1.4.1.tgz", + "integrity": "sha1-mZEtWRg2tab1s0XA8H7vwI/GXZE=", + "dev": true, + "dependencies": { + "util": "0.10.3" + } + }, + "node_modules/assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/ast-metadata-inferer": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/ast-metadata-inferer/-/ast-metadata-inferer-0.7.0.tgz", + "integrity": "sha512-OkMLzd8xelb3gmnp6ToFvvsHLtS6CbagTkFQvQ+ZYFe3/AIl9iKikNR9G7pY3GfOR/2Xc222hwBjzI7HLkE76Q==", + "dev": true, + "dependencies": { + "@mdn/browser-compat-data": "^3.3.14" + } + }, + "node_modules/ast-metadata-inferer/node_modules/@mdn/browser-compat-data": { + "version": "3.3.14", + "resolved": "https://registry.npmjs.org/@mdn/browser-compat-data/-/browser-compat-data-3.3.14.tgz", + "integrity": "sha512-n2RC9d6XatVbWFdHLimzzUJxJ1KY8LdjqrW6YvGPiRmsHkhOUx74/Ct10x5Yo7bC/Jvqx7cDEW8IMPv/+vwEzA==", + "dev": true + }, + "node_modules/astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/async": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.1.tgz", + "integrity": "sha512-XdD5lRO/87udXCMC9meWdYiR+Nq6ZjUfXidViUZGu2F1MO4T3XwZ1et0hb2++BgLfhyJwy44BGB/yx80ABx8hg==", + "dev": true + }, + "node_modules/async-limiter": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", + "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==", + "dev": true + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "dev": true + }, + "node_modules/aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/aws4": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", + "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==", + "dev": true + }, + "node_modules/babel-plugin-dynamic-import-node": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz", + "integrity": "sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==", + "dev": true, + "dependencies": { + "object.assign": "^4.1.0" + } + }, + "node_modules/babel-plugin-polyfill-corejs2": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.2.2.tgz", + "integrity": "sha512-kISrENsJ0z5dNPq5eRvcctITNHYXWOA4DUZRFYCz3jYCcvTb/A546LIddmoGNMVYg2U38OyFeNosQwI9ENTqIQ==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.13.11", + "@babel/helper-define-polyfill-provider": "^0.2.2", + "semver": "^6.1.1" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/babel-plugin-polyfill-corejs2/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.2.4.tgz", + "integrity": "sha512-z3HnJE5TY/j4EFEa/qpQMSbcUJZ5JQi+3UFjXzn6pQCmIKc5Ug5j98SuYyH+m4xQnvKlMDIW4plLfgyVnd0IcQ==", + "dev": true, + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.2.2", + "core-js-compat": "^3.14.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/babel-plugin-polyfill-regenerator": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.2.2.tgz", + "integrity": "sha512-Goy5ghsc21HgPDFtzRkSirpZVW35meGoTmTOb2bxqdl60ghub4xOidgNTHaZfQ2FaxQsKmwvXtOAkcIS4SMBWg==", + "dev": true, + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.2.2" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/basic-auth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "dev": true, + "dependencies": { + "safe-buffer": "5.1.2" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/batch": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", + "integrity": "sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY=", + "dev": true + }, + "node_modules/bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "dev": true, + "dependencies": { + "tweetnacl": "^0.14.3" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/bl/node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/bl/node_modules/readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.17.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.17.0.tgz", + "integrity": "sha512-g2BJ2a0nEYvEFQC208q8mVAhfNwpZ5Mu8BwgtCdZKO3qx98HChmeg448fPdUzld8aFmfLgVh7yymqV+q1lJZ5g==", + "dev": true, + "dependencies": { + "caniuse-lite": "^1.0.30001254", + "colorette": "^1.3.0", + "electron-to-chromium": "^1.3.830", + "escalade": "^3.1.1", + "node-releases": "^1.1.75" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + } + }, + "node_modules/browserstack": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/browserstack/-/browserstack-1.3.0.tgz", + "integrity": "sha1-hDgFPvasu4RNxrKRUQwZQznrUN8=", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/builtin-modules": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.2.0.tgz", + "integrity": "sha512-lGzLKcioL90C7wMczpkY0n/oART3MbBa8R9OFGE1rJxoVI86u4WAGfEk8Wjv10eKSyTHVGkSo3bvBylCEtk7LA==", + "dev": true, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/caching-transform": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-4.0.0.tgz", + "integrity": "sha512-kpqOvwXnjjN44D89K5ccQC+RUrsy7jB/XLlRrx0D7/2HNcTPqzsb6XgYoErwko6QsV184CA2YgS1fxDiiDZMWA==", + "dev": true, + "dependencies": { + "hasha": "^5.0.0", + "make-dir": "^3.0.0", + "package-hash": "^4.0.0", + "write-file-atomic": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001320", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001320.tgz", + "integrity": "sha512-MWPzG54AGdo3nWx7zHZTefseM5Y1ccM7hlQKHRqJkPozUaw3hNbBTMmLn16GG2FUzjR13Cr3NPfhIieX5PzXDA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + } + ] + }, + "node_modules/caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", + "dev": true + }, + "node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/character-entities": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-1.2.4.tgz", + "integrity": "sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz", + "integrity": "sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-reference-invalid": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz", + "integrity": "sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "dev": true + }, + "node_modules/circular-json": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.1.tgz", + "integrity": "sha1-vos2rvzN6LPKeqLWr8B6NyQsDS0=", + "deprecated": "CircularJSON is in maintenance only, flatted is its successor.", + "dev": true + }, + "node_modules/clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "node_modules/colorette": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz", + "integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==", + "dev": true + }, + "node_modules/colors": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz", + "integrity": "sha1-FopHAXVran9RoSzgyXv6KMCE7WM=", + "dev": true, + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "engines": { + "node": ">= 10" + } + }, + "node_modules/commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", + "dev": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "node_modules/connect": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", + "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", + "dev": true, + "dependencies": { + "debug": "2.6.9", + "finalhandler": "1.1.2", + "parseurl": "~1.3.3", + "utils-merge": "1.0.1" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/connect-livereload": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/connect-livereload/-/connect-livereload-0.6.1.tgz", + "integrity": "sha512-3R0kMOdL7CjJpU66fzAkCe6HNtd3AavCS4m+uW4KtJjrdGPT0SQEZieAYd+cm+lJoBznNQ4lqipYWkhBMgk00g==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/connect/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/connect/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "node_modules/convert-source-map": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", + "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.1" + } + }, + "node_modules/core-js": { + "version": "3.21.1", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.21.1.tgz", + "integrity": "sha512-FRq5b/VMrWlrmCzwRrpDYNxyHP9BcAZC+xHJaqTgIE5091ZV1NTmyh0sGOg5XqpnHvR0svdy0sv1gWA1zmhxig==", + "dev": true, + "hasInstallScript": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-js-compat": { + "version": "3.17.3", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.17.3.tgz", + "integrity": "sha512-+in61CKYs4hQERiADCJsdgewpdl/X0GhEX77pjKgbeibXviIt2oxEjTc8O2fqHX8mDdBrDvX8MYD/RYsBv4OiA==", + "dev": true, + "dependencies": { + "browserslist": "^4.17.0", + "semver": "7.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-js-compat/node_modules/semver": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", + "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true + }, + "node_modules/coveralls": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/coveralls/-/coveralls-3.1.1.tgz", + "integrity": "sha512-+dxnG2NHncSD1NrqbSM3dn/lE57O6Qf/koe9+I7c+wzkqRmEvcp0kgJdxKInzYzkICKkFMZsX3Vct3++tsF9ww==", + "dev": true, + "dependencies": { + "js-yaml": "^3.13.1", + "lcov-parse": "^1.0.0", + "log-driver": "^1.2.7", + "minimist": "^1.2.5", + "request": "^2.88.2" + }, + "bin": { + "coveralls": "bin/coveralls.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "dev": true, + "dependencies": { + "assert-plus": "^1.0.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/dateformat": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz", + "integrity": "sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/debug": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/deepmerge": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", + "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/default-require-extensions": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-3.0.0.tgz", + "integrity": "sha512-ek6DpXq/SCpvjhpFsLFRVtIxJCRw6fUR42lYMVZuUMK7n8eMz4Uh5clckdBjEpLhn/gEBZo7hDJnJcwdKLKQjg==", + "dev": true, + "dependencies": { + "strip-bom": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "dev": true, + "dependencies": { + "object-keys": "^1.0.12" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=", + "dev": true + }, + "node_modules/detect-file": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz", + "integrity": "sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/devtools-protocol": { + "version": "0.0.818844", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.818844.tgz", + "integrity": "sha512-AD1hi7iVJ8OD0aMLQU5VK0XH9LDlA1+BcPIgrAxPfaibx2DbWucuyOhc4oyQCbnvDDO68nN6/LcKfqTP343Jjg==", + "dev": true + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/dom-serializer": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.3.2.tgz", + "integrity": "sha512-5c54Bk5Dw4qAxNOI1pFEizPSjVsx5+bpJKmL2kPn8JhBUq2q09tTCa3mjijun2NfK78NMouDYNMBkOrPZiS+ig==", + "dev": true, + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.2.0", + "entities": "^2.0.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.2.0.tgz", + "integrity": "sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ] + }, + "node_modules/domhandler": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.2.2.tgz", + "integrity": "sha512-PzE9aBMsdZO8TK4BnuJwH0QT41wgMbRzuZrHUcpYncEjmQazq8QEaBWgLG7ZyC/DAZKEgglpIA6j4Qn/HmxS3w==", + "dev": true, + "dependencies": { + "domelementtype": "^2.2.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", + "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", + "dev": true, + "dependencies": { + "dom-serializer": "^1.0.1", + "domelementtype": "^2.2.0", + "domhandler": "^4.2.0" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/duplexify": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", + "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", + "dev": true, + "dependencies": { + "end-of-stream": "^1.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.0.0", + "stream-shift": "^1.0.0" + } + }, + "node_modules/ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "dev": true, + "dependencies": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", + "dev": true + }, + "node_modules/electron-to-chromium": { + "version": "1.3.843", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.843.tgz", + "integrity": "sha512-OWEwAbzaVd1Lk9MohVw8LxMXFlnYd9oYTYxfX8KS++kLLjDfbovLOcEEXwRhG612dqGQ6+44SZvim0GXuBRiKg==", + "dev": true + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/enquirer": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", + "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", + "dev": true, + "dependencies": { + "ansi-colors": "^4.1.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/ensure-posix-path": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ensure-posix-path/-/ensure-posix-path-1.1.1.tgz", + "integrity": "sha512-VWU0/zXzVbeJNXvME/5EmLuEj2TauvoaTz6aFYK1Z92JCBlDlZ3Gu0tuGR42kpW1754ywTs+QB0g5TP0oj9Zaw==", + "dev": true + }, + "node_modules/entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "dev": true, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/es-abstract": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.19.1.tgz", + "integrity": "sha512-2vJ6tjA/UfqLm2MPs7jxVybLoB8i1t1Jd9R3kISld20sIxPcTbLuggQOUxeWeAvIUkduv/CfMjuh4WmiXr2v9w==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "get-intrinsic": "^1.1.1", + "get-symbol-description": "^1.0.0", + "has": "^1.0.3", + "has-symbols": "^1.0.2", + "internal-slot": "^1.0.3", + "is-callable": "^1.2.4", + "is-negative-zero": "^2.0.1", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.1", + "is-string": "^1.0.7", + "is-weakref": "^1.0.1", + "object-inspect": "^1.11.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.2", + "string.prototype.trimend": "^1.0.4", + "string.prototype.trimstart": "^1.0.4", + "unbox-primitive": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es6-error": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", + "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", + "dev": true + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=", + "dev": true + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/eslint": { + "version": "7.32.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.32.0.tgz", + "integrity": "sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==", + "dev": true, + "dependencies": { + "@babel/code-frame": "7.12.11", + "@eslint/eslintrc": "^0.4.3", + "@humanwhocodes/config-array": "^0.5.0", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.0.1", + "doctrine": "^3.0.0", + "enquirer": "^2.3.5", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^5.1.1", + "eslint-utils": "^2.1.0", + "eslint-visitor-keys": "^2.0.0", + "espree": "^7.3.1", + "esquery": "^1.4.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^5.1.2", + "globals": "^13.6.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "js-yaml": "^3.13.1", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.0.4", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "progress": "^2.0.0", + "regexpp": "^3.1.0", + "semver": "^7.2.1", + "strip-ansi": "^6.0.0", + "strip-json-comments": "^3.1.0", + "table": "^6.0.9", + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-config-semistandard": { + "version": "16.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-semistandard/-/eslint-config-semistandard-16.0.0.tgz", + "integrity": "sha512-oD8QOo4mSInRJhQb3Zi6L8HebwZaB6SI3A+NNrPdVN0nN1K45L5pXK3joY+ksWDlT3ew/M+fJk2tuMCjIpjRzQ==", + "dev": true, + "peerDependencies": { + "eslint": ">=7.12.1", + "eslint-config-standard": ">=16.0.3", + "eslint-plugin-import": ">=2.22.1", + "eslint-plugin-node": ">=11.1.0", + "eslint-plugin-promise": ">=4.2.1" + } + }, + "node_modules/eslint-config-standard": { + "version": "16.0.3", + "resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-16.0.3.tgz", + "integrity": "sha512-x4fmJL5hGqNJKGHSjnLdgA6U6h1YW/G2dW9fA+cyVur4SK6lyue8+UgNKWlZtUDTXvgKDD/Oa3GQjmB5kjtVvg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "peerDependencies": { + "eslint": "^7.12.1", + "eslint-plugin-import": "^2.22.1", + "eslint-plugin-node": "^11.1.0", + "eslint-plugin-promise": "^4.2.1 || ^5.0.0" + } + }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.6.tgz", + "integrity": "sha512-0En0w03NRVMn9Uiyn8YRPDKvWjxCWkslUEhGNTdGx15RvPJYQ+lbOlqrlNI2vEAs4pDYK4f/HN2TbDmk5TP0iw==", + "dev": true, + "dependencies": { + "debug": "^3.2.7", + "resolve": "^1.20.0" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-module-utils": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.7.3.tgz", + "integrity": "sha512-088JEC7O3lDZM9xGe0RerkOMd0EjFl+Yvd1jPWIkMT5u3H9+HC34mWWPnqPrN13gieT9pBOO+Qt07Nb/6TresQ==", + "dev": true, + "dependencies": { + "debug": "^3.2.7", + "find-up": "^2.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-module-utils/node_modules/find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "dev": true, + "dependencies": { + "locate-path": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint-module-utils/node_modules/locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "dev": true, + "dependencies": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint-module-utils/node_modules/p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "dependencies": { + "p-try": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint-module-utils/node_modules/p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "dev": true, + "dependencies": { + "p-limit": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint-module-utils/node_modules/p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint-module-utils/node_modules/path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint-plugin-compat": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-compat/-/eslint-plugin-compat-4.0.2.tgz", + "integrity": "sha512-xqvoO54CLTVaEYGMzhu35Wzwk/As7rCvz/2dqwnFiWi0OJccEtGIn+5qq3zqIu9nboXlpdBN579fZcItC73Ycg==", + "dev": true, + "dependencies": { + "@mdn/browser-compat-data": "^4.1.5", + "ast-metadata-inferer": "^0.7.0", + "browserslist": "^4.16.8", + "caniuse-lite": "^1.0.30001304", + "core-js": "^3.16.2", + "find-up": "^5.0.0", + "lodash.memoize": "4.1.2", + "semver": "7.3.5" + }, + "engines": { + "node": ">=9.x" + }, + "peerDependencies": { + "eslint": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/eslint-plugin-compat/node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint-plugin-compat/node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint-plugin-compat/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint-plugin-compat/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint-plugin-es": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-3.0.1.tgz", + "integrity": "sha512-GUmAsJaN4Fc7Gbtl8uOBlayo2DqhwWvEzykMHSCZHU3XdJ+NSzzZcVhXh3VxX5icqQ+oQdIEawXX8xkR3mIFmQ==", + "dev": true, + "dependencies": { + "eslint-utils": "^2.0.0", + "regexpp": "^3.0.0" + }, + "engines": { + "node": ">=8.10.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=4.19.1" + } + }, + "node_modules/eslint-plugin-html": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-html/-/eslint-plugin-html-6.1.2.tgz", + "integrity": "sha512-bhBIRyZFqI4EoF12lGDHAmgfff8eLXx6R52/K3ESQhsxzCzIE6hdebS7Py651f7U3RBotqroUnC3L29bR7qJWQ==", + "dev": true, + "dependencies": { + "htmlparser2": "^6.0.1" + } + }, + "node_modules/eslint-plugin-import": { + "version": "2.25.4", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.25.4.tgz", + "integrity": "sha512-/KJBASVFxpu0xg1kIBn9AUa8hQVnszpwgE7Ld0lKAlx7Ie87yzEzCgSkekt+le/YVhiaosO4Y14GDAOc41nfxA==", + "dev": true, + "dependencies": { + "array-includes": "^3.1.4", + "array.prototype.flat": "^1.2.5", + "debug": "^2.6.9", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.6", + "eslint-module-utils": "^2.7.2", + "has": "^1.0.3", + "is-core-module": "^2.8.0", + "is-glob": "^4.0.3", + "minimatch": "^3.0.4", + "object.values": "^1.1.5", + "resolve": "^1.20.0", + "tsconfig-paths": "^3.12.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" + } + }, + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/eslint-plugin-import/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-import/node_modules/is-core-module": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.1.tgz", + "integrity": "sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/eslint-plugin-import/node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-import/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "node_modules/eslint-plugin-json-es": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-json-es/-/eslint-plugin-json-es-1.5.5.tgz", + "integrity": "sha512-J5wsCFuV8u+1nJzLtJo3SInE624sAr7yfKoktk7t7Px/gC/DLPYtXkwFu/QBaoPo4yvQ2wG532Jv9OpY1h212w==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.3.0", + "espree": "^9.3.1" + }, + "peerDependencies": { + "eslint": ">= 7" + } + }, + "node_modules/eslint-plugin-json-es/node_modules/acorn": { + "version": "8.7.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", + "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/eslint-plugin-json-es/node_modules/eslint-visitor-keys": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", + "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/eslint-plugin-json-es/node_modules/espree": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.3.1.tgz", + "integrity": "sha512-bvdyLmJMfwkV3NCRl5ZhJf22zBFo1y8bYh3VYb+bfzqNB4Je68P2sSuXyuFquzWLebHpNd2/d5uv7yoP9ISnGQ==", + "dev": true, + "dependencies": { + "acorn": "^8.7.0", + "acorn-jsx": "^5.3.1", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/eslint-plugin-markdown": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-markdown/-/eslint-plugin-markdown-2.2.1.tgz", + "integrity": "sha512-FgWp4iyYvTFxPwfbxofTvXxgzPsDuSKHQy2S+a8Ve6savbujey+lgrFFbXQA0HPygISpRYWYBjooPzhYSF81iA==", + "dev": true, + "dependencies": { + "mdast-util-from-markdown": "^0.8.5" + }, + "engines": { + "node": "^8.10.0 || ^10.12.0 || >= 12.0.0" + }, + "peerDependencies": { + "eslint": ">=6.0.0" + } + }, + "node_modules/eslint-plugin-node": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-11.1.0.tgz", + "integrity": "sha512-oUwtPJ1W0SKD0Tr+wqu92c5xuCeQqB3hSCHasn/ZgjFdA9iDGNkNf2Zi9ztY7X+hNuMib23LNGRm6+uN+KLE3g==", + "dev": true, + "dependencies": { + "eslint-plugin-es": "^3.0.0", + "eslint-utils": "^2.0.0", + "ignore": "^5.1.1", + "minimatch": "^3.0.4", + "resolve": "^1.10.1", + "semver": "^6.1.0" + }, + "engines": { + "node": ">=8.10.0" + }, + "peerDependencies": { + "eslint": ">=5.16.0" + } + }, + "node_modules/eslint-plugin-node/node_modules/ignore": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", + "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/eslint-plugin-node/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-plugin-promise": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-6.0.0.tgz", + "integrity": "sha512-7GPezalm5Bfi/E22PnQxDWH2iW9GTvAlUNTztemeHb6c1BniSyoeTrM87JkC0wYdi6aQrZX9p2qEiAno8aTcbw==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + } + }, + "node_modules/eslint-plugin-qunit": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-qunit/-/eslint-plugin-qunit-6.2.0.tgz", + "integrity": "sha512-KvPmkIC2MHpfRxs/r8WUeeGkG6y+3qwSi2AZIBtjcM/YG6Z3k0GxW5Hbu3l7X0TDhljVCeBb9Q5puUkHzl83Mw==", + "dev": true, + "dependencies": { + "eslint-utils": "^3.0.0", + "requireindex": "^1.2.0" + }, + "engines": { + "node": "10.x || 12.x || >=14.0.0" + } + }, + "node_modules/eslint-plugin-qunit/node_modules/eslint-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^2.0.0" + }, + "engines": { + "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=5" + } + }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/eslint-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^1.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, + "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint/node_modules/@babel/code-frame": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", + "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.10.4" + } + }, + "node_modules/eslint/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/eslint/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/eslint/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/eslint/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/eslint/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/globals": { + "version": "13.11.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.11.0.tgz", + "integrity": "sha512-08/xrJ7wQjK9kkkRoI3OFUBbLx4f+6x3SGwcPvQ0QH6goFDrOU2oyAWrmh3dJezu65buo+HBMzAMQy6rovVC3g==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/espree": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", + "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", + "dev": true, + "dependencies": { + "acorn": "^7.4.0", + "acorn-jsx": "^5.3.1", + "eslint-visitor-keys": "^1.3.0" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", + "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esquery/node_modules/estraverse": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", + "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse/node_modules/estraverse": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", + "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-walker": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz", + "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==", + "dev": true + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/eventemitter2": { + "version": "0.4.14", + "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-0.4.14.tgz", + "integrity": "sha1-j2G3XN4BKy6esoTUVFWDtWQ7Yas=", + "dev": true + }, + "node_modules/events": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", + "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=", + "dev": true, + "engines": { + "node": ">=0.4.x" + } + }, + "node_modules/exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/expand-tilde": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", + "integrity": "sha1-l+gBqgUt8CRU3kawK/YhZCzchQI=", + "dev": true, + "dependencies": { + "homedir-polyfill": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true + }, + "node_modules/extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, + "bin": { + "extract-zip": "cli.js" + }, + "engines": { + "node": ">= 10.17.0" + }, + "optionalDependencies": { + "@types/yauzl": "^2.9.1" + } + }, + "node_modules/extract-zip/node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", + "dev": true, + "engines": [ + "node >=0.6.0" + ] + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true + }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=", + "dev": true, + "dependencies": { + "pend": "~1.2.0" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/file-sync-cmp": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/file-sync-cmp/-/file-sync-cmp-0.1.1.tgz", + "integrity": "sha1-peeo/7+kk7Q7kju9TKiaU7Y7YSs=", + "dev": true + }, + "node_modules/fill-keys": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/fill-keys/-/fill-keys-1.0.2.tgz", + "integrity": "sha1-mo+jb06K1jTjv2tPPIiCVRRS6yA=", + "dev": true, + "dependencies": { + "is-object": "~1.0.1", + "merge-descriptors": "~1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "dev": true, + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "node_modules/finalhandler/node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/find-cache-dir": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", + "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", + "dev": true, + "dependencies": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/avajs/find-cache-dir?sponsor=1" + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/findup-sync": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-0.3.0.tgz", + "integrity": "sha1-N5MKpdgWt3fANEXhlmzGeQpMCxY=", + "dev": true, + "dependencies": { + "glob": "~5.0.0" + }, + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/findup-sync/node_modules/glob": { + "version": "5.0.15", + "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", + "integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=", + "dev": true, + "dependencies": { + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "2 || 3", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + } + }, + "node_modules/fined": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fined/-/fined-1.2.0.tgz", + "integrity": "sha512-ZYDqPLGxDkDhDZBjZBb+oD1+j0rA4E0pXY50eplAAOPg2N/gUBSSk5IM1/QhPfyVo19lJ+CvXpqfvk+b2p/8Ng==", + "dev": true, + "dependencies": { + "expand-tilde": "^2.0.2", + "is-plain-object": "^2.0.3", + "object.defaults": "^1.1.0", + "object.pick": "^1.2.0", + "parse-filepath": "^1.0.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/fixturify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/fixturify/-/fixturify-2.1.1.tgz", + "integrity": "sha512-SRgwIMXlxkb6AUgaVjIX+jCEqdhyXu9hah7mcK+lWynjKtX73Ux1TDv71B7XyaQ+LJxkYRHl5yCL8IycAvQRUw==", + "dev": true, + "dependencies": { + "@types/fs-extra": "^8.1.0", + "@types/minimatch": "^3.0.3", + "@types/rimraf": "^2.0.3", + "fs-extra": "^8.1.0", + "matcher-collection": "^2.0.1", + "walk-sync": "^2.0.2" + }, + "engines": { + "node": "10.* || >= 12.*" + } + }, + "node_modules/flagged-respawn": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/flagged-respawn/-/flagged-respawn-1.0.1.tgz", + "integrity": "sha512-lNaHNVymajmk0OJMBn8fVUAU1BtDeKIqKoVhk4xAALB57aALg6b4W0MfJ/cUE0g9YBXy5XhSlPIpYIJ7HaY/3Q==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "dependencies": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.2.tgz", + "integrity": "sha512-JaTY/wtrcSyvXJl4IMFHPKyFur1sE9AUqc0QnhOaJ0CxHtAoIV8pYDzeEfAaNEtGkOfq4gr3LBFmdXW5mOQFnA==", + "dev": true + }, + "node_modules/for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/for-own": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz", + "integrity": "sha1-xjMy9BXO3EsE2/5wz4NklMU8tEs=", + "dev": true, + "dependencies": { + "for-in": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/foreground-child": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz", + "integrity": "sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "dev": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 0.12" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fromentries": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fromentries/-/fromentries-1.3.2.tgz", + "integrity": "sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "dev": true + }, + "node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "node_modules/functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", + "dev": true + }, + "node_modules/fuzzysort": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/fuzzysort/-/fuzzysort-1.2.1.tgz", + "integrity": "sha512-egTSF3U6H6T9tXtAhEm5P5guSSDjd96/NUWrbmoGlIu3ATMdXra13gwQdEFRY6ehsFe8xec7UnQz+k34CGWCIg==", + "dev": true + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", + "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-symbol-description": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", + "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/getobject": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/getobject/-/getobject-1.0.2.tgz", + "integrity": "sha512-2zblDBaFcb3rB4rF77XVnuINOE2h2k/OnqXAiy0IrTxUfV1iFp3la33oAQVY9pCpWU268WFYVt2t71hlMuLsOg==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "dev": true, + "dependencies": { + "assert-plus": "^1.0.0" + } + }, + "node_modules/glob": { + "version": "7.1.7", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", + "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/global-modules": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", + "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==", + "dev": true, + "dependencies": { + "global-prefix": "^1.0.1", + "is-windows": "^1.0.1", + "resolve-dir": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/global-prefix": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-1.0.2.tgz", + "integrity": "sha1-2/dDxsFJklk8ZVVoy2btMsASLr4=", + "dev": true, + "dependencies": { + "expand-tilde": "^2.0.2", + "homedir-polyfill": "^1.0.1", + "ini": "^1.3.4", + "is-windows": "^1.0.1", + "which": "^1.2.14" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/global-prefix/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/globalyzer": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/globalyzer/-/globalyzer-0.1.0.tgz", + "integrity": "sha512-40oNTM9UfG6aBmuKxk/giHn5nQ8RVz/SS4Ir6zgzOv9/qC3kKZ9v4etGTcJbEl/NyVQH7FGU7d+X1egr57Md2Q==" + }, + "node_modules/globrex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz", + "integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==" + }, + "node_modules/graceful-fs": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz", + "integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==", + "dev": true + }, + "node_modules/grunt": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/grunt/-/grunt-1.4.1.tgz", + "integrity": "sha512-ZXIYXTsAVrA7sM+jZxjQdrBOAg7DyMUplOMhTaspMRExei+fD0BTwdWXnn0W5SXqhb/Q/nlkzXclSi3IH55PIA==", + "dev": true, + "dependencies": { + "dateformat": "~3.0.3", + "eventemitter2": "~0.4.13", + "exit": "~0.1.2", + "findup-sync": "~0.3.0", + "glob": "~7.1.6", + "grunt-cli": "~1.4.2", + "grunt-known-options": "~2.0.0", + "grunt-legacy-log": "~3.0.0", + "grunt-legacy-util": "~2.0.1", + "iconv-lite": "~0.4.13", + "js-yaml": "~3.14.0", + "minimatch": "~3.0.4", + "mkdirp": "~1.0.4", + "nopt": "~3.0.6", + "rimraf": "~3.0.2" + }, + "bin": { + "grunt": "bin/grunt" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/grunt-contrib-connect": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/grunt-contrib-connect/-/grunt-contrib-connect-3.0.0.tgz", + "integrity": "sha512-L1GXk6PqDP/meX0IOX1MByBvOph6h8Pvx4/iBIYD7dpokVCAAQPR/IIV1jkTONEM09xig/Y8/y3R9Fqc8U3HSA==", + "dev": true, + "dependencies": { + "async": "^3.2.0", + "connect": "^3.7.0", + "connect-livereload": "^0.6.1", + "morgan": "^1.10.0", + "node-http2": "^4.0.1", + "opn": "^6.0.0", + "portscanner": "^2.2.0", + "serve-index": "^1.9.1", + "serve-static": "^1.14.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/grunt-contrib-copy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/grunt-contrib-copy/-/grunt-contrib-copy-1.0.0.tgz", + "integrity": "sha1-cGDGWB6QS4qw0A8HbgqPbj58NXM=", + "dev": true, + "dependencies": { + "chalk": "^1.1.1", + "file-sync-cmp": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/grunt-contrib-copy/node_modules/ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/grunt-contrib-copy/node_modules/ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/grunt-contrib-copy/node_modules/chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "dependencies": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/grunt-contrib-copy/node_modules/strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/grunt-contrib-copy/node_modules/supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/grunt-contrib-qunit": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/grunt-contrib-qunit/-/grunt-contrib-qunit-5.1.0.tgz", + "integrity": "sha512-c7UOwkAHnGcnjtwR49++/eyfqNyWd9ymVQIXFXgkEvKDVzIQRNbMmjkA/j7DSeVyRzDDD4DHulYe2MPYgxx5/A==", + "dev": true, + "dependencies": { + "eventemitter2": "^6.4.2", + "p-each-series": "^2.1.0", + "puppeteer": "^5.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/grunt-contrib-qunit/node_modules/eventemitter2": { + "version": "6.4.4", + "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.4.tgz", + "integrity": "sha512-HLU3NDY6wARrLCEwyGKRBvuWYyvW6mHYv72SJJAH3iJN3a6eVUvkjFkcxah1bcTgGVBBrFdIopBJPhCQFMLyXw==", + "dev": true + }, + "node_modules/grunt-git-authors": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/grunt-git-authors/-/grunt-git-authors-3.2.0.tgz", + "integrity": "sha1-D/WrbTxu/+CrIV1jNDRcD2v+FnI=", + "dev": true, + "dependencies": { + "spawnback": "~1.0.0" + } + }, + "node_modules/grunt-known-options": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/grunt-known-options/-/grunt-known-options-2.0.0.tgz", + "integrity": "sha512-GD7cTz0I4SAede1/+pAbmJRG44zFLPipVtdL9o3vqx9IEyb7b4/Y3s7r6ofI3CchR5GvYJ+8buCSioDv5dQLiA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/grunt-legacy-log": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/grunt-legacy-log/-/grunt-legacy-log-3.0.0.tgz", + "integrity": "sha512-GHZQzZmhyq0u3hr7aHW4qUH0xDzwp2YXldLPZTCjlOeGscAOWWPftZG3XioW8MasGp+OBRIu39LFx14SLjXRcA==", + "dev": true, + "dependencies": { + "colors": "~1.1.2", + "grunt-legacy-log-utils": "~2.1.0", + "hooker": "~0.2.3", + "lodash": "~4.17.19" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/grunt-legacy-log-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/grunt-legacy-log-utils/-/grunt-legacy-log-utils-2.1.0.tgz", + "integrity": "sha512-lwquaPXJtKQk0rUM1IQAop5noEpwFqOXasVoedLeNzaibf/OPWjKYvvdqnEHNmU+0T0CaReAXIbGo747ZD+Aaw==", + "dev": true, + "dependencies": { + "chalk": "~4.1.0", + "lodash": "~4.17.19" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/grunt-legacy-log-utils/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/grunt-legacy-log-utils/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/grunt-legacy-log-utils/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/grunt-legacy-log-utils/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/grunt-legacy-log-utils/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/grunt-legacy-log-utils/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/grunt-legacy-util": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/grunt-legacy-util/-/grunt-legacy-util-2.0.1.tgz", + "integrity": "sha512-2bQiD4fzXqX8rhNdXkAywCadeqiPiay0oQny77wA2F3WF4grPJXCvAcyoWUJV+po/b15glGkxuSiQCK299UC2w==", + "dev": true, + "dependencies": { + "async": "~3.2.0", + "exit": "~0.1.2", + "getobject": "~1.0.0", + "hooker": "~0.2.3", + "lodash": "~4.17.21", + "underscore.string": "~3.3.5", + "which": "~2.0.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/grunt-search": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/grunt-search/-/grunt-search-0.1.8.tgz", + "integrity": "sha1-9K1kQLr49d+122p/KSeUDNJ9Vec=", + "dev": true, + "engines": { + "node": ">= 0.8.0" + }, + "peerDependencies": { + "grunt": ">=0.4.1" + } + }, + "node_modules/grunt/node_modules/grunt-cli": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/grunt-cli/-/grunt-cli-1.4.3.tgz", + "integrity": "sha512-9Dtx/AhVeB4LYzsViCjUQkd0Kw0McN2gYpdmGYKtE2a5Yt7v1Q+HYZVWhqXc/kGnxlMtqKDxSwotiGeFmkrCoQ==", + "dev": true, + "dependencies": { + "grunt-known-options": "~2.0.0", + "interpret": "~1.1.0", + "liftup": "~3.0.1", + "nopt": "~4.0.1", + "v8flags": "~3.2.0" + }, + "bin": { + "grunt": "bin/grunt" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/grunt/node_modules/grunt-cli/node_modules/nopt": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.3.tgz", + "integrity": "sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg==", + "dev": true, + "dependencies": { + "abbrev": "1", + "osenv": "^0.1.4" + }, + "bin": { + "nopt": "bin/nopt.js" + } + }, + "node_modules/har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/har-validator": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", + "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", + "deprecated": "this library is no longer supported", + "dev": true, + "dependencies": { + "ajv": "^6.12.3", + "har-schema": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "dev": true, + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-ansi/node_modules/ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-bigints": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz", + "integrity": "sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-color": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/has-color/-/has-color-0.1.7.tgz", + "integrity": "sha1-ZxRKUmDDT8PMpnfQQdr1L+e3iy8=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/has-symbols": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", + "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasha": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.2.tgz", + "integrity": "sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ==", + "dev": true, + "dependencies": { + "is-stream": "^2.0.0", + "type-fest": "^0.8.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/hasha/node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/hasha/node_modules/type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/homedir-polyfill": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", + "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==", + "dev": true, + "dependencies": { + "parse-passwd": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/hooker": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/hooker/-/hooker-0.2.3.tgz", + "integrity": "sha1-uDT3I8xKJCqmWWNFnfbZhMXT2Vk=", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, + "node_modules/htmlparser2": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz", + "integrity": "sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==", + "dev": true, + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.0.0", + "domutils": "^2.5.2", + "entities": "^2.0.0" + } + }, + "node_modules/http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", + "dev": true, + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "dev": true, + "dependencies": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + }, + "engines": { + "node": ">=0.8", + "npm": ">=1.3.7" + } + }, + "node_modules/https-browserify": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-0.0.1.tgz", + "integrity": "sha1-P5E2XKvmC3ftDruiS0VOPgnZWoI=", + "dev": true + }, + "node_modules/https-proxy-agent": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-4.0.0.tgz", + "integrity": "sha512-zoDhWrkR3of1l9QAL8/scJZyLu8j/gBkcwcaQOZh7Gyh/+uJQzGVETdgT30akuwkpL8HTRfssqI3BZuV18teDg==", + "dev": true, + "dependencies": { + "agent-base": "5", + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true + }, + "node_modules/internal-slot": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz", + "integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.0", + "has": "^1.0.3", + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/interpret": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.1.0.tgz", + "integrity": "sha1-ftGxQQxqDg94z5XTuEQMY/eLhhQ=", + "dev": true + }, + "node_modules/is-absolute": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz", + "integrity": "sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==", + "dev": true, + "dependencies": { + "is-relative": "^1.0.0", + "is-windows": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-alphabetical": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.4.tgz", + "integrity": "sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumerical": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz", + "integrity": "sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==", + "dev": true, + "dependencies": { + "is-alphabetical": "^1.0.0", + "is-decimal": "^1.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, + "dependencies": { + "has-bigints": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.4.tgz", + "integrity": "sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.6.0.tgz", + "integrity": "sha512-wShG8vs60jKfPWpF2KZRaAtvt3a20OAn7+IJ6hLPECpSABLcKtFKTTI4ZtH5QcBruBHlq+WsdHWyz0BCZW7svQ==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-decimal": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.4.tgz", + "integrity": "sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-hexadecimal": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz", + "integrity": "sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", + "integrity": "sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE=", + "dev": true + }, + "node_modules/is-negative-zero": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", + "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-like": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/is-number-like/-/is-number-like-1.0.8.tgz", + "integrity": "sha512-6rZi3ezCyFcn5L71ywzz2bS5b2Igl1En3eTlZlvKjpz1n3IZLAYMbKYAIQgFmEu0GENg92ziU/faEOA/aixjbA==", + "dev": true, + "dependencies": { + "lodash.isfinite": "^3.3.2" + } + }, + "node_modules/is-number-object": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.6.tgz", + "integrity": "sha512-bEVOqiRcvo3zO1+G2lVMy+gkkEm9Yh7cDMRusKKu5ZJKPUYSJwICTKZrNKHA2EbSP0Tu0+6B/emsYNHZyn6K8g==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-object": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-object/-/is-object-1.0.2.tgz", + "integrity": "sha512-2rRIahhZr2UWb45fIOuvZGpFtz0TyOZLf32KxBbSoUCeZR495zCKlWUKKUByk3geS2eAs7ZAABt0Y/Rx0GiQGA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-reference": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz", + "integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==", + "dev": true, + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-relative": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz", + "integrity": "sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==", + "dev": true, + "dependencies": { + "is-unc-path": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.1.tgz", + "integrity": "sha512-IU0NmyknYZN0rChcKhRO1X8LYz5Isj/Fsqh8NJOSf+N/hCOTwy29F32Ik7a+QszE63IdvmwdTPDd6cZ5pg4cwA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "dev": true + }, + "node_modules/is-unc-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz", + "integrity": "sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==", + "dev": true, + "dependencies": { + "unc-path-regex": "^0.1.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-wsl": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", + "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", + "dev": true + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.0.tgz", + "integrity": "sha512-UiUIqxMgRDET6eR+o5HbfRYP1l0hqkWOs7vNxC/mggutCMUIhWMm8gAHb8tHlyfD3/l6rlgNA5cKdDzEAf6hEg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-hook": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-3.0.0.tgz", + "integrity": "sha512-Pt/uge1Q9s+5VAZ+pCo16TYMWPBIl+oaNIjgLQxcX0itS6ueeaA+pEfThZpH8WxhFgCiEb8sAJY6MdUKgiIWaQ==", + "dev": true, + "dependencies": { + "append-transform": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz", + "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==", + "dev": true, + "dependencies": { + "@babel/core": "^7.7.5", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.0.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/istanbul-lib-processinfo": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-processinfo/-/istanbul-lib-processinfo-2.0.2.tgz", + "integrity": "sha512-kOwpa7z9hme+IBPZMzQ5vdQj8srYgAtaRqeI48NGmAQ+/5yKiHLV0QbYqQpxsdEF0+w14SoB8YbnHKcXE2KnYw==", + "dev": true, + "dependencies": { + "archy": "^1.0.0", + "cross-spawn": "^7.0.0", + "istanbul-lib-coverage": "^3.0.0-alpha.1", + "make-dir": "^3.0.0", + "p-map": "^3.0.0", + "rimraf": "^3.0.0", + "uuid": "^3.3.3" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", + "dev": true, + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^3.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.0.tgz", + "integrity": "sha512-c16LpFRkR8vQXyHZ5nLpY35JZtzj1PQY1iZmesUbf1FZHbIupcWfjgOXBY9YHkLEQ6puz1u4Dgj6qmU/DisrZg==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-source-maps/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/istanbul-reports": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.0.2.tgz", + "integrity": "sha512-9tZvz7AiR3PEDNGiV9vIouQ/EAcqMXFmkcA1CDFTwOB98OZVDL0PH9glHotf5Ugp6GCOTypfzGWI/OqjWNCRUw==", + "dev": true, + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/js-reporters": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/js-reporters/-/js-reporters-2.1.0.tgz", + "integrity": "sha512-Q4GcEcPSb6ovhqp91claM3WPbSntQxbIn+3JiJgEXturys2ttWgs31VC60Yja+2unpNOH2A2qyjWFU2thCQ8sg==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "dev": true + }, + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", + "dev": true + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "dev": true + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", + "dev": true + }, + "node_modules/json5": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", + "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==", + "dev": true, + "dependencies": { + "minimist": "^1.2.5" + }, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "dev": true, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "dev": true, + "engines": [ + "node >=0.6.0" + ], + "dependencies": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/kleur": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.4.tgz", + "integrity": "sha512-8QADVssbrFjivHWQU7KkMgptGTl6WAcSdlbBPY4uNF+mWr6DGcKrvY2w4FQJoXch7+fKMjj0dRrL75vk3k23OA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/lcov-parse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lcov-parse/-/lcov-parse-1.0.0.tgz", + "integrity": "sha1-6w1GtUER68VhrLTECO+TY73I9+A=", + "dev": true, + "bin": { + "lcov-parse": "bin/cli.js" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/liftup": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/liftup/-/liftup-3.0.1.tgz", + "integrity": "sha512-yRHaiQDizWSzoXk3APcA71eOI/UuhEkNN9DiW2Tt44mhYzX4joFoCZlxsSOF7RyeLlfqzFLQI1ngFq3ggMPhOw==", + "dev": true, + "dependencies": { + "extend": "^3.0.2", + "findup-sync": "^4.0.0", + "fined": "^1.2.0", + "flagged-respawn": "^1.0.1", + "is-plain-object": "^2.0.4", + "object.map": "^1.0.1", + "rechoir": "^0.7.0", + "resolve": "^1.19.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/liftup/node_modules/findup-sync": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-4.0.0.tgz", + "integrity": "sha512-6jvvn/12IC4quLBL1KNokxC7wWTvYncaVUYSoxWw7YykPLuRrnv4qdHcSOywOI5RpkOVGeQRtWM8/q+G6W6qfQ==", + "dev": true, + "dependencies": { + "detect-file": "^1.0.0", + "is-glob": "^4.0.0", + "micromatch": "^4.0.2", + "resolve-dir": "^1.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "node_modules/lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=", + "dev": true + }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=", + "dev": true + }, + "node_modules/lodash.flattendeep": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", + "integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=", + "dev": true + }, + "node_modules/lodash.isfinite": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/lodash.isfinite/-/lodash.isfinite-3.3.2.tgz", + "integrity": "sha1-+4m2WpqAKBgz8LdHizpRBPiY67M=", + "dev": true + }, + "node_modules/lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=", + "dev": true + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/lodash.truncate": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", + "integrity": "sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=", + "dev": true + }, + "node_modules/log-driver": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/log-driver/-/log-driver-1.2.7.tgz", + "integrity": "sha512-U7KCmLdqsGHBLeWqYlFA0V0Sl6P08EE1ZrmA9cxjUE0WVqT9qnyVDPz1kzpFEP0jdJuFnasWIfSd7fsaNXkpbg==", + "dev": true, + "engines": { + "node": ">=0.8.6" + } + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/magic-string": { + "version": "0.25.7", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz", + "integrity": "sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==", + "dev": true, + "dependencies": { + "sourcemap-codec": "^1.4.4" + } + }, + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/make-iterator": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/make-iterator/-/make-iterator-1.0.1.tgz", + "integrity": "sha512-pxiuXh0iVEq7VM7KMIhs5gxsfxCux2URptUQaXo4iZZJxBAzTPOLE2BumO5dbfVYq/hBJFBR/a1mFDmOx5AGmw==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/map-cache": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", + "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/matcher-collection": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/matcher-collection/-/matcher-collection-2.0.1.tgz", + "integrity": "sha512-daE62nS2ZQsDg9raM0IlZzLmI2u+7ZapXBwdoeBUKAYERPDDIc0qNqA8E0Rp2D+gspKR7BgIFP52GeujaGXWeQ==", + "dev": true, + "dependencies": { + "@types/minimatch": "^3.0.3", + "minimatch": "^3.0.2" + }, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/mdast-util-from-markdown": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-0.8.5.tgz", + "integrity": "sha512-2hkTXtYYnr+NubD/g6KGBS/0mFmBcifAsI0yIWRiRo0PjVs6SSOSOdtzbp6kSGnShDN6G5aWZpKQ2lWRy27mWQ==", + "dev": true, + "dependencies": { + "@types/mdast": "^3.0.0", + "mdast-util-to-string": "^2.0.0", + "micromark": "~2.11.0", + "parse-entities": "^2.0.0", + "unist-util-stringify-position": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-2.0.0.tgz", + "integrity": "sha512-AW4DRS3QbBayY/jJmD8437V1Gombjf8RSOUCMFBuo5iHi58AGEgVCKQ+ezHkZZDpAQS75hcBMpLqjpJTjtUL7w==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=", + "dev": true + }, + "node_modules/micromark": { + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-2.11.4.tgz", + "integrity": "sha512-+WoovN/ppKolQOFIAajxi7Lu9kInbPxFuTBVEavFcL8eAfVstoc5MocPmqBeAdBOJV00uaVjegzH4+MA0DN/uA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "debug": "^4.0.0", + "parse-entities": "^2.0.0" + } + }, + "node_modules/micromatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", + "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", + "dev": true, + "dependencies": { + "braces": "^3.0.1", + "picomatch": "^2.2.3" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.49.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.49.0.tgz", + "integrity": "sha512-CIc8j9URtOVApSFCQIF+VBkX1RwXp/oMMOrqdyXSBXq5RWNEsRfyj1kiRnQgmNXmHxPoFIxOroKA3zcU9P+nAA==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.32", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.32.tgz", + "integrity": "sha512-hJGaVS4G4c9TSMYh2n6SQAGrC4RnfU+daP8G7cSCmaqNjiOoUY0VHCMS42pxnQmVF1GWwFhbHWn3RIxCqTmZ9A==", + "dev": true, + "dependencies": { + "mime-db": "1.49.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "dev": true + }, + "node_modules/module-not-found-error": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/module-not-found-error/-/module-not-found-error-1.0.1.tgz", + "integrity": "sha1-z4tP9PKWQGdNbN0CsOO8UjwrvcA=", + "dev": true + }, + "node_modules/morgan": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz", + "integrity": "sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==", + "dev": true, + "dependencies": { + "basic-auth": "~2.0.1", + "debug": "2.6.9", + "depd": "~2.0.0", + "on-finished": "~2.3.0", + "on-headers": "~1.0.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/morgan/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/morgan/node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/morgan/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true + }, + "node_modules/negotiator": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", + "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-fetch": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.2.tgz", + "integrity": "sha512-aLoxToI6RfZ+0NOjmWAgn9+LEd30YCkJKFSyWacNZdEKTit/ZMcKjGkTRo8uWEsnIb/hfKecNPEbln02PdWbcA==", + "dev": true, + "engines": { + "node": "4.x || >=6.0.0" + } + }, + "node_modules/node-http2": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/node-http2/-/node-http2-4.0.1.tgz", + "integrity": "sha1-Fk/1O13SLITwrxQrh3xerraAmVk=", + "dev": true, + "dependencies": { + "assert": "1.4.1", + "events": "1.1.1", + "https-browserify": "0.0.1", + "setimmediate": "^1.0.5", + "stream-browserify": "2.0.1", + "timers-browserify": "2.0.2", + "url": "^0.11.0", + "websocket-stream": "^5.0.1" + }, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/node-preload": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/node-preload/-/node-preload-0.2.1.tgz", + "integrity": "sha512-RM5oyBy45cLEoHqCeh+MNuFAxO0vTFBLskvQbOKnEE7YTTSN4tbN8QWDIPQ6L+WvKsB/qLEGpYe2ZZ9d4W9OIQ==", + "dev": true, + "dependencies": { + "process-on-spawn": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/node-releases": { + "version": "1.1.75", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.75.tgz", + "integrity": "sha512-Qe5OUajvqrqDSy6wrWFmMwfJ0jVgwiw4T3KqmbTcZ62qW0gQkheXYhcFM1+lOVcGUoRxcEcfyvFMAnDgaF1VWw==", + "dev": true + }, + "node_modules/node-watch": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/node-watch/-/node-watch-0.7.3.tgz", + "integrity": "sha512-3l4E8uMPY1HdMMryPRUAl+oIHtXtyiTlIiESNSVSNxcPfzAFzeTbXFQkZfAwBbo0B1qMSG8nUABx+Gd+YrbKrQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/nopt": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", + "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=", + "dev": true, + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + } + }, + "node_modules/npm-reporter": { + "resolved": "test/cli/fixtures/npm-reporter", + "link": true + }, + "node_modules/nyc": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/nyc/-/nyc-15.1.0.tgz", + "integrity": "sha512-jMW04n9SxKdKi1ZMGhvUTHBN0EICCRkHemEoE5jm6mTYcqcdas0ATzgUgejlQUHMvpnOZqGB5Xxsv9KxJW1j8A==", + "dev": true, + "dependencies": { + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "caching-transform": "^4.0.0", + "convert-source-map": "^1.7.0", + "decamelize": "^1.2.0", + "find-cache-dir": "^3.2.0", + "find-up": "^4.1.0", + "foreground-child": "^2.0.0", + "get-package-type": "^0.1.0", + "glob": "^7.1.6", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-hook": "^3.0.0", + "istanbul-lib-instrument": "^4.0.0", + "istanbul-lib-processinfo": "^2.0.2", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.0.2", + "make-dir": "^3.0.0", + "node-preload": "^0.2.1", + "p-map": "^3.0.0", + "process-on-spawn": "^1.0.0", + "resolve-from": "^5.0.0", + "rimraf": "^3.0.0", + "signal-exit": "^3.0.2", + "spawn-wrap": "^2.0.0", + "test-exclude": "^6.0.0", + "yargs": "^15.0.2" + }, + "bin": { + "nyc": "bin/nyc.js" + }, + "engines": { + "node": ">=8.9" + } + }, + "node_modules/nyc/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/object-inspect": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.0.tgz", + "integrity": "sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", + "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "has-symbols": "^1.0.1", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.defaults": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/object.defaults/-/object.defaults-1.1.0.tgz", + "integrity": "sha1-On+GgzS0B96gbaFtiNXNKeQ1/s8=", + "dev": true, + "dependencies": { + "array-each": "^1.0.1", + "array-slice": "^1.0.0", + "for-own": "^1.0.0", + "isobject": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object.map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object.map/-/object.map-1.0.1.tgz", + "integrity": "sha1-z4Plncj8wK1fQlDh94s7gb2AHTc=", + "dev": true, + "dependencies": { + "for-own": "^1.0.0", + "make-iterator": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object.pick": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", + "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", + "dev": true, + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object.values": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.5.tgz", + "integrity": "sha512-QUZRW0ilQ3PnPpbNtgdNV1PDbEqLIiSFB3l+EnGtBQ/8SUTLj1PZwtQHABZtLgwpJZTSZhuGLOGk57Drx2IvYg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "dev": true, + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/opn": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/opn/-/opn-6.0.0.tgz", + "integrity": "sha512-I9PKfIZC+e4RXZ/qr1RhgyCnGgYX0UEIlXgWnCOVACIvFgaC9rz6Won7xbdhoHrd8IIhV7YEpHjreNUNkqCGkQ==", + "deprecated": "The package has been renamed to `open`", + "dev": true, + "dependencies": { + "is-wsl": "^1.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dev": true, + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/osenv": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", + "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", + "dev": true, + "dependencies": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, + "node_modules/p-each-series": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-2.2.0.tgz", + "integrity": "sha512-ycIL2+1V32th+8scbpTvyHNaHe02z0sjgh91XXjAk+ZeXoPN4Z46DVUnzdso0aX4KckKw0FNNFHdjZ2UsZvxiA==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-map": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", + "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", + "dev": true, + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/package-hash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/package-hash/-/package-hash-4.0.0.tgz", + "integrity": "sha512-whdkPIooSu/bASggZ96BWVvZTRMOFxnyUG5PnTSGKoJE2gd5mbVNmR2Nj20QFzxYYgAXpoqC+AiXzl+UMRh7zQ==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.15", + "hasha": "^5.0.0", + "lodash.flattendeep": "^4.4.0", + "release-zalgo": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-entities": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-2.0.0.tgz", + "integrity": "sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==", + "dev": true, + "dependencies": { + "character-entities": "^1.0.0", + "character-entities-legacy": "^1.0.0", + "character-reference-invalid": "^1.0.0", + "is-alphanumerical": "^1.0.0", + "is-decimal": "^1.0.0", + "is-hexadecimal": "^1.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/parse-filepath": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/parse-filepath/-/parse-filepath-1.0.2.tgz", + "integrity": "sha1-pjISf1Oq89FYdvWHLz/6x2PWyJE=", + "dev": true, + "dependencies": { + "is-absolute": "^1.0.0", + "map-cache": "^0.2.0", + "path-root": "^0.1.1" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/parse-passwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", + "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/path-root": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/path-root/-/path-root-0.1.1.tgz", + "integrity": "sha1-mkpoFMrBwM1zNgqV8yCDyOpHRbc=", + "dev": true, + "dependencies": { + "path-root-regex": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-root-regex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/path-root-regex/-/path-root-regex-0.1.2.tgz", + "integrity": "sha1-v8zcjfWxLcUsi0PsONGNcsBLqW0=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=", + "dev": true + }, + "node_modules/performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", + "dev": true + }, + "node_modules/picomatch": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", + "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/portscanner": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/portscanner/-/portscanner-2.2.0.tgz", + "integrity": "sha512-IFroCz/59Lqa2uBvzK3bKDbDDIEaAY8XJ1jFxcLWTqosrsc32//P4VuSB2vZXoHiHqOmx8B5L5hnKOxL/7FlPw==", + "dev": true, + "dependencies": { + "async": "^2.6.0", + "is-number-like": "^1.0.3" + }, + "engines": { + "node": ">=0.4", + "npm": ">=1.0.0" + } + }, + "node_modules/portscanner/node_modules/async": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", + "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", + "dev": true, + "dependencies": { + "lodash": "^4.17.14" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true + }, + "node_modules/process-on-spawn": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/process-on-spawn/-/process-on-spawn-1.0.0.tgz", + "integrity": "sha512-1WsPDsUSMmZH5LeMLegqkPDrsGgsWwk1Exipy2hvB0o/F0ASzbpIctSCcZIK1ykJvtTJULEH+20WOFjMvGnCTg==", + "dev": true, + "dependencies": { + "fromentries": "^1.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "dev": true + }, + "node_modules/proxyquire": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/proxyquire/-/proxyquire-1.8.0.tgz", + "integrity": "sha1-AtUUpb7ZhvBMuyCTrxZ0FTX3ntw=", + "dev": true, + "dependencies": { + "fill-keys": "^1.0.2", + "module-not-found-error": "^1.0.0", + "resolve": "~1.1.7" + } + }, + "node_modules/proxyquire/node_modules/resolve": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", + "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=", + "dev": true + }, + "node_modules/psl": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", + "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==", + "dev": true + }, + "node_modules/pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/puppeteer": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-5.5.0.tgz", + "integrity": "sha512-OM8ZvTXAhfgFA7wBIIGlPQzvyEETzDjeRa4mZRCRHxYL+GNH5WAuYUQdja3rpWZvkX/JKqmuVgbsxDNsDFjMEg==", + "deprecated": "Version no longer supported. Upgrade to @latest", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "debug": "^4.1.0", + "devtools-protocol": "0.0.818844", + "extract-zip": "^2.0.0", + "https-proxy-agent": "^4.0.0", + "node-fetch": "^2.6.1", + "pkg-dir": "^4.2.0", + "progress": "^2.0.1", + "proxy-from-env": "^1.0.0", + "rimraf": "^3.0.2", + "tar-fs": "^2.0.0", + "unbzip2-stream": "^1.3.3", + "ws": "^7.2.3" + }, + "engines": { + "node": ">=10.18.1" + } + }, + "node_modules/puppeteer/node_modules/ws": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.5.tgz", + "integrity": "sha512-BAkMFcAzl8as1G/hArkxOxq3G7pjUqQ3gzYbLL0/5zNkph70e+lCoxBGnm6AW1+/aiNeV4fnKqZ8m4GZewmH2w==", + "dev": true, + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", + "dev": true, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/querystring": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", + "deprecated": "The querystring API is considered Legacy. new code should use the URLSearchParams API instead.", + "dev": true, + "engines": { + "node": ">=0.4.x" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/rechoir": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.7.1.tgz", + "integrity": "sha512-/njmZ8s1wVeR6pjTZ+0nCnv8SpZNRMT2D1RLOJQESlYFDBvwpTA4KWJpZ+sBJ4+vhjILRcK7JIFdGCdxEAAitg==", + "dev": true, + "dependencies": { + "resolve": "^1.9.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/regenerate": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", + "dev": true + }, + "node_modules/regenerate-unicode-properties": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-9.0.0.tgz", + "integrity": "sha512-3E12UeNSPfjrgwjkR81m5J7Aw/T55Tu7nUyZVQYCKEOs+2dkxEY+DpPtZzO4YruuiPb7NkYLVcyJC4+zCbk5pA==", + "dev": true, + "dependencies": { + "regenerate": "^1.4.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.13.9", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", + "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==", + "dev": true + }, + "node_modules/regenerator-transform": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.14.5.tgz", + "integrity": "sha512-eOf6vka5IO151Jfsw2NO9WpGX58W6wWmefK3I1zEGr0lOD0u8rwPaNqQL1aRxUaxLeKO3ArNh3VYg1KbaD+FFw==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.8.4" + } + }, + "node_modules/regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, + "node_modules/regexpu-core": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.8.0.tgz", + "integrity": "sha512-1F6bYsoYiz6is+oz70NWur2Vlh9KWtswuRuzJOfeYUrfPX2o8n74AnUVaOGDbUqVGO9fNHu48/pjJO4sNVwsOg==", + "dev": true, + "dependencies": { + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^9.0.0", + "regjsgen": "^0.5.2", + "regjsparser": "^0.7.0", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regjsgen": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.2.tgz", + "integrity": "sha512-OFFT3MfrH90xIW8OOSyUrk6QHD5E9JOTeGodiJeBS3J6IwlgzJMNE/1bZklWz5oTg+9dCMyEetclvCVXOPoN3A==", + "dev": true + }, + "node_modules/regjsparser": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.7.0.tgz", + "integrity": "sha512-A4pcaORqmNMDVwUjWoTzuhwMGpP+NykpfqAsEgI1FSH/EzC7lrN5TMd+kN8YCovX+jMpu8eaqXgXPCa0g8FQNQ==", + "dev": true, + "dependencies": { + "jsesc": "~0.5.0" + }, + "bin": { + "regjsparser": "bin/parser" + } + }, + "node_modules/regjsparser/node_modules/jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + } + }, + "node_modules/release-zalgo": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz", + "integrity": "sha1-CXALflB0Mpc5Mw5TXFqQ+2eFFzA=", + "dev": true, + "dependencies": { + "es6-error": "^4.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/request": { + "version": "2.88.2", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", + "deprecated": "request has been deprecated, see https://github.com/request/request/issues/3142", + "dev": true, + "dependencies": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.5.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true + }, + "node_modules/requireindex": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/requireindex/-/requireindex-1.2.0.tgz", + "integrity": "sha512-L9jEkOi3ASd9PYit2cwRfyppc9NoABujTP8/5gFcbERmo5jUoAKovIC3fsF17pkTnGsrByysqX+Kxd2OTNI1ww==", + "dev": true, + "engines": { + "node": ">=0.10.5" + } + }, + "node_modules/requirejs": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/requirejs/-/requirejs-2.3.6.tgz", + "integrity": "sha512-ipEzlWQe6RK3jkzikgCupiTbTvm4S0/CAU5GlgptkN5SO6F3u0UD0K18wy6ErDqiCyP4J4YYe1HuAShvsxePLg==", + "dev": true, + "bin": { + "r_js": "bin/r.js", + "r.js": "bin/r.js" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/resolve": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", + "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", + "dev": true, + "dependencies": { + "is-core-module": "^2.2.0", + "path-parse": "^1.0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-dir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz", + "integrity": "sha1-eaQGRMNivoLybv/nOcm7U4IEb0M=", + "dev": true, + "dependencies": { + "expand-tilde": "^2.0.0", + "global-modules": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rollup": { + "version": "2.56.3", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.56.3.tgz", + "integrity": "sha512-Au92NuznFklgQCUcV96iXlxUbHuB1vQMaH76DHl5M11TotjOHwqk9CwcrT78+Tnv4FN9uTBxq6p4EJoYkpyekg==", + "dev": true, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=10.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "node_modules/semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz", + "integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==", + "dev": true, + "dependencies": { + "debug": "2.6.9", + "depd": "~1.1.2", + "destroy": "~1.0.4", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "~1.6.2", + "mime": "1.4.1", + "ms": "2.0.0", + "on-finished": "~2.3.0", + "range-parser": "~1.2.0", + "statuses": "~1.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/mime": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", + "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==", + "dev": true, + "bin": { + "mime": "cli.js" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "node_modules/serve-index": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", + "integrity": "sha1-03aNabHn2C5c4FD/9bRTvqEqkjk=", + "dev": true, + "dependencies": { + "accepts": "~1.3.4", + "batch": "0.6.1", + "debug": "2.6.9", + "escape-html": "~1.0.3", + "http-errors": "~1.6.2", + "mime-types": "~2.1.17", + "parseurl": "~1.3.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/serve-index/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/serve-index/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "node_modules/serve-static": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", + "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", + "dev": true, + "dependencies": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.17.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/serve-static/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/serve-static/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "node_modules/serve-static/node_modules/http-errors": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.3.tgz", + "integrity": "sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==", + "dev": true, + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.1.1", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-static/node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/serve-static/node_modules/ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + }, + "node_modules/serve-static/node_modules/send": { + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", + "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", + "dev": true, + "dependencies": { + "debug": "2.6.9", + "depd": "~1.1.2", + "destroy": "~1.0.4", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "~1.7.2", + "mime": "1.6.0", + "ms": "2.1.1", + "on-finished": "~2.3.0", + "range-parser": "~1.2.1", + "statuses": "~1.5.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/serve-static/node_modules/setprototypeof": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", + "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==", + "dev": true + }, + "node_modules/serve-static/node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true + }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=", + "dev": true + }, + "node_modules/setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", + "dev": true + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.4.tgz", + "integrity": "sha512-rqYhcAnZ6d/vTPGghdrw7iumdcbXpsk1b8IG/rz+VWV51DM0p7XCtMoJ3qhPLIbp3tvyt3pKRbaaEMZYpHto8Q==", + "dev": true + }, + "node_modules/slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/slice-ansi/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sourcemap-codec": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", + "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", + "dev": true + }, + "node_modules/spawn-wrap": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-2.0.0.tgz", + "integrity": "sha512-EeajNjfN9zMnULLwhZZQU3GWBoFNkbngTUPfaawT4RkMiviTxcX0qfhVbGey39mfctfDHkWtuecgQ8NJcyQWHg==", + "dev": true, + "dependencies": { + "foreground-child": "^2.0.0", + "is-windows": "^1.0.2", + "make-dir": "^3.0.0", + "rimraf": "^3.0.0", + "signal-exit": "^3.0.2", + "which": "^2.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/spawnback": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/spawnback/-/spawnback-1.0.1.tgz", + "integrity": "sha512-340ZqtqJzWAZtHwaCC2gx4mdQOnkUWAWNDp7y0bCEatdjmgQ4j7b0qQ7qO5WIJWx/luNrKcrYzpKbH3NTR030A==", + "dev": true + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "node_modules/sshpk": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", + "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", + "dev": true, + "dependencies": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + }, + "bin": { + "sshpk-conv": "bin/sshpk-conv", + "sshpk-sign": "bin/sshpk-sign", + "sshpk-verify": "bin/sshpk-verify" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/statuses": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", + "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/stream-browserify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.1.tgz", + "integrity": "sha1-ZiZu5fm9uZQKTkUUyvtDu3Hlyds=", + "dev": true, + "dependencies": { + "inherits": "~2.0.1", + "readable-stream": "^2.0.2" + } + }, + "node_modules/stream-shift": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", + "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==", + "dev": true + }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/string-width": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", + "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/node_modules/strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz", + "integrity": "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz", + "integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-ansi": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-0.1.1.tgz", + "integrity": "sha1-OeipjQRNFQZgq+SmgIrPcLt7yZE=", + "dev": true, + "bin": { + "strip-ansi": "cli.js" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/table": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/table/-/table-6.7.1.tgz", + "integrity": "sha512-ZGum47Yi6KOOFDE8m223td53ath2enHcYLgOCjGr5ngu8bdIARQk6mN/wRMv4yMRcHnCSnHbCEha4sobQx5yWg==", + "dev": true, + "dependencies": { + "ajv": "^8.0.1", + "lodash.clonedeep": "^4.5.0", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/table/node_modules/ajv": { + "version": "8.6.3", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.6.3.tgz", + "integrity": "sha512-SMJOdDP6LqTkD0Uq8qLi+gMwSt0imXLSV080qFVwJCpH9U6Mb+SUGHAXM0KNbcBPguytWyvFxcHgMLe2D2XSpw==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/table/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "node_modules/table/node_modules/strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tar-fs": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", + "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "dev": true, + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dev": true, + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tar-stream/node_modules/readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true + }, + "node_modules/timers-browserify": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.2.tgz", + "integrity": "sha1-q0iDz1l9zVCvIRNJoA+8pWrIa4Y=", + "dev": true, + "dependencies": { + "setimmediate": "^1.0.4" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/tiny-glob": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/tiny-glob/-/tiny-glob-0.2.9.tgz", + "integrity": "sha512-g/55ssRPUjShh+xkfx9UPDXqhckHEsHr4Vd9zX55oSdGZc/MD0m3sferOkwWtp98bv+kcVfEHtRJgBVJzelrzg==", + "dependencies": { + "globalyzer": "0.1.0", + "globrex": "^0.1.2" + } + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", + "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", + "dev": true, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "dev": true, + "dependencies": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/tsconfig-paths": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz", + "integrity": "sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==", + "dev": true, + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.1", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "node_modules/tsconfig-paths/node_modules/json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/tsconfig-paths/node_modules/minimist": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", + "dev": true + }, + "node_modules/tsconfig-paths/node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/tunnel": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.3.tgz", + "integrity": "sha1-6PmIEVynvp0HbHofrkeIvnCPDPE=", + "dev": true, + "engines": { + "node": ">=0.6.11 <=0.7.0 || >=0.7.3" + } + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "dev": true, + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "dev": true + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "dev": true, + "dependencies": { + "is-typedarray": "^1.0.0" + } + }, + "node_modules/ultron": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz", + "integrity": "sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og==", + "dev": true + }, + "node_modules/unbox-primitive": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz", + "integrity": "sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1", + "has-bigints": "^1.0.1", + "has-symbols": "^1.0.2", + "which-boxed-primitive": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/unbzip2-stream": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", + "integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==", + "dev": true, + "dependencies": { + "buffer": "^5.2.1", + "through": "^2.3.8" + } + }, + "node_modules/unc-path-regex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz", + "integrity": "sha1-5z3T17DXxe2G+6xrCufYxqadUPo=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/underscore.string": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-3.3.5.tgz", + "integrity": "sha512-g+dpmgn+XBneLmXXo+sGlW5xQEt4ErkS3mgeN2GFbremYeMBSJKr9Wf2KJplQVaiPY/f7FN6atosWYNm9ovrYg==", + "dev": true, + "dependencies": { + "sprintf-js": "^1.0.3", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": "*" + } + }, + "node_modules/unicode-canonical-property-names-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", + "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", + "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "dev": true, + "dependencies": { + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-value-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.0.0.tgz", + "integrity": "sha512-7Yhkc0Ye+t4PNYzOGKedDhXbYIBe1XEQYQxOPyhcXNMJ0WCABqqj6ckydd6pWRZTHV4GuCPKdBAUiMc60tsKVw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-property-aliases-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.0.0.tgz", + "integrity": "sha512-5Zfuy9q/DFr4tfO7ZPeVXb1aPoeQSdeFMLpYuFebehDAhbuevLs5yxSZmIFN1tP5F9Wl4IpJrYojg85/zgyZHQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-2.0.3.tgz", + "integrity": "sha512-3faScn5I+hy9VleOq/qNbAd6pAx7iH5jYBMS9I1HgQVijz/4mv5Bvw5iw1sC/90CODiKo81G/ps8AJrISn687g==", + "dev": true, + "dependencies": { + "@types/unist": "^2.0.2" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/url": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", + "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=", + "dev": true, + "dependencies": { + "punycode": "1.3.2", + "querystring": "0.2.0" + } + }, + "node_modules/url/node_modules/punycode": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=", + "dev": true + }, + "node_modules/util": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", + "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", + "dev": true, + "dependencies": { + "inherits": "2.0.1" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, + "node_modules/util/node_modules/inherits": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", + "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=", + "dev": true + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", + "dev": true, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", + "dev": true, + "bin": { + "uuid": "bin/uuid" + } + }, + "node_modules/v8-compile-cache": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", + "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", + "dev": true + }, + "node_modules/v8flags": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-3.2.0.tgz", + "integrity": "sha512-mH8etigqMfiGWdeXpaaqGfs6BndypxusHHcv2qSHyZkGEznCd/qAXCWWRzeowtL54147cktFOC4P5y+kl8d8Jg==", + "dev": true, + "dependencies": { + "homedir-polyfill": "^1.0.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "dev": true, + "engines": [ + "node >=0.6.0" + ], + "dependencies": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "node_modules/walk-sync": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/walk-sync/-/walk-sync-2.2.0.tgz", + "integrity": "sha512-IC8sL7aB4/ZgFcGI2T1LczZeFWZ06b3zoHH7jBPyHxOtIIz1jppWHjjEXkOFvFojBVAK9pV7g47xOZ4LW3QLfg==", + "dev": true, + "dependencies": { + "@types/minimatch": "^3.0.3", + "ensure-posix-path": "^1.1.0", + "matcher-collection": "^2.0.0", + "minimatch": "^3.0.4" + }, + "engines": { + "node": "8.* || >= 10.*" + } + }, + "node_modules/websocket-stream": { + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/websocket-stream/-/websocket-stream-5.5.2.tgz", + "integrity": "sha512-8z49MKIHbGk3C4HtuHWDtYX8mYej1wWabjthC/RupM9ngeukU4IWoM46dgth1UOS/T4/IqgEdCDJuMe2039OQQ==", + "dev": true, + "dependencies": { + "duplexify": "^3.5.1", + "inherits": "^2.0.1", + "readable-stream": "^2.3.3", + "safe-buffer": "^5.1.2", + "ws": "^3.2.0", + "xtend": "^4.0.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, + "dependencies": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": true + }, + "node_modules/word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/wrap-ansi/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "node_modules/write-file-atomic": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", + "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", + "dev": true, + "dependencies": { + "imurmurhash": "^0.1.4", + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" + } + }, + "node_modules/ws": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz", + "integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==", + "dev": true, + "dependencies": { + "async-limiter": "~1.0.0", + "safe-buffer": "~5.1.0", + "ultron": "~1.1.0" + } + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true, + "engines": { + "node": ">=0.4" + } + }, + "node_modules/y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "dev": true + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/yargs": { + "version": "15.3.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.3.1.tgz", + "integrity": "sha512-92O1HWEjw27sBfgmXiixJWT5hRBp2eobqXicLtPBIDBhYB+1HpwZlXmbW2luivBJHBzki+7VyCLRtAkScbTBQA==", + "dev": true, + "dependencies": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "dev": true, + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=", + "dev": true, + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "test/cli/fixtures/npm-reporter": { + "version": "1.0.0", + "dev": true + } + }, "dependencies": { "@babel/code-frame": { "version": "7.14.5", @@ -1174,6 +9762,12 @@ "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", "dev": true }, + "@mdn/browser-compat-data": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@mdn/browser-compat-data/-/browser-compat-data-4.1.13.tgz", + "integrity": "sha512-OFTriJmUcXnA1RjfKDJOfEmhHO44OV7Gxpqnh7eOM+3qep64Yt+uNF4jEeIFHnwTFH5tMcnBQxEI91dXDGiGYQ==", + "dev": true + }, "@qunitjs/browserstack-runner": { "version": "0.9.5-qunitjs.1", "resolved": "https://registry.npmjs.org/@qunitjs/browserstack-runner/-/browserstack-runner-0.9.5-qunitjs.1.tgz", @@ -1309,6 +9903,21 @@ "@types/node": "*" } }, + "@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", + "dev": true + }, + "@types/mdast": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.10.tgz", + "integrity": "sha512-W864tg/Osz1+9f4lrGTZpCSO5/z4608eUp19tbozkq2HJK6i3z1kT0H9tlADXuYIb1YYOBByU4Jsqkk75q48qA==", + "dev": true, + "requires": { + "@types/unist": "*" + } + }, "@types/minimatch": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.5.tgz", @@ -1340,6 +9949,12 @@ "@types/node": "*" } }, + "@types/unist": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.6.tgz", + "integrity": "sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==", + "dev": true + }, "@types/yauzl": { "version": "2.9.2", "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.9.2.tgz", @@ -1376,7 +9991,8 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true + "dev": true, + "requires": {} }, "agent-base": { "version": "5.1.1", @@ -1457,12 +10073,36 @@ "integrity": "sha1-p5SvDAWrF1KEbudTofIRoFugxE8=", "dev": true }, + "array-includes": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.4.tgz", + "integrity": "sha512-ZTNSQkmWumEbiHO2GF4GmWxYVTiQyJy2XOTa15sdQSrvKn7l+180egQMqlrMOUMCyLMD7pmyQe4mMDUT6Behrw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.1", + "get-intrinsic": "^1.1.1", + "is-string": "^1.0.7" + } + }, "array-slice": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-1.1.0.tgz", "integrity": "sha512-B1qMD3RBP7O8o0H2KbrXDyB0IccejMF15+87Lvlor12ONPRHP6gTjXMNkt/d3ZuOGbAe66hFmaCfECI24Ufp6w==", "dev": true }, + "array.prototype.flat": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.2.5.tgz", + "integrity": "sha512-KaYU+S+ndVqyUnignHftkwc58o3uVU1jzczILJ1tN2YaIZpFIKBiP/x/j97E5MVPsaCloPbqWLB/8qCTVvT2qg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.0" + } + }, "asn1": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", @@ -1487,6 +10127,23 @@ "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", "dev": true }, + "ast-metadata-inferer": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/ast-metadata-inferer/-/ast-metadata-inferer-0.7.0.tgz", + "integrity": "sha512-OkMLzd8xelb3gmnp6ToFvvsHLtS6CbagTkFQvQ+ZYFe3/AIl9iKikNR9G7pY3GfOR/2Xc222hwBjzI7HLkE76Q==", + "dev": true, + "requires": { + "@mdn/browser-compat-data": "^3.3.14" + }, + "dependencies": { + "@mdn/browser-compat-data": { + "version": "3.3.14", + "resolved": "https://registry.npmjs.org/@mdn/browser-compat-data/-/browser-compat-data-3.3.14.tgz", + "integrity": "sha512-n2RC9d6XatVbWFdHLimzzUJxJ1KY8LdjqrW6YvGPiRmsHkhOUx74/Ct10x5Yo7bC/Jvqx7cDEW8IMPv/+vwEzA==", + "dev": true + } + } + }, "astral-regex": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", @@ -1731,9 +10388,9 @@ "dev": true }, "caniuse-lite": { - "version": "1.0.30001258", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001258.tgz", - "integrity": "sha512-RBByOG6xWXUp0CR2/WU2amXz3stjKpSl5J1xU49F1n2OxD//uBZO4wCKUiG+QMGf7CHGfDDcqoKriomoGVxTeA==", + "version": "1.0.30001320", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001320.tgz", + "integrity": "sha512-MWPzG54AGdo3nWx7zHZTefseM5Y1ccM7hlQKHRqJkPozUaw3hNbBTMmLn16GG2FUzjR13Cr3NPfhIieX5PzXDA==", "dev": true }, "caseless": { @@ -1753,6 +10410,24 @@ "supports-color": "^5.3.0" } }, + "character-entities": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-1.2.4.tgz", + "integrity": "sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==", + "dev": true + }, + "character-entities-legacy": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz", + "integrity": "sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==", + "dev": true + }, + "character-reference-invalid": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz", + "integrity": "sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==", + "dev": true + }, "chownr": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", @@ -1890,6 +10565,12 @@ "safe-buffer": "~5.1.1" } }, + "core-js": { + "version": "3.21.1", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.21.1.tgz", + "integrity": "sha512-FRq5b/VMrWlrmCzwRrpDYNxyHP9BcAZC+xHJaqTgIE5091ZV1NTmyh0sGOg5XqpnHvR0svdy0sv1gWA1zmhxig==", + "dev": true + }, "core-js-compat": { "version": "3.17.3", "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.17.3.tgz", @@ -2150,6 +10831,45 @@ "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", "dev": true }, + "es-abstract": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.19.1.tgz", + "integrity": "sha512-2vJ6tjA/UfqLm2MPs7jxVybLoB8i1t1Jd9R3kISld20sIxPcTbLuggQOUxeWeAvIUkduv/CfMjuh4WmiXr2v9w==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "get-intrinsic": "^1.1.1", + "get-symbol-description": "^1.0.0", + "has": "^1.0.3", + "has-symbols": "^1.0.2", + "internal-slot": "^1.0.3", + "is-callable": "^1.2.4", + "is-negative-zero": "^2.0.1", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.1", + "is-string": "^1.0.7", + "is-weakref": "^1.0.1", + "object-inspect": "^1.11.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.2", + "string.prototype.trimend": "^1.0.4", + "string.prototype.trimstart": "^1.0.4", + "unbox-primitive": "^1.0.1" + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, "es6-error": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", @@ -2277,41 +10997,195 @@ "integrity": "sha512-08/xrJ7wQjK9kkkRoI3OFUBbLx4f+6x3SGwcPvQ0QH6goFDrOU2oyAWrmh3dJezu65buo+HBMzAMQy6rovVC3g==", "dev": true, "requires": { - "type-fest": "^0.20.2" + "type-fest": "^0.20.2" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "eslint-config-semistandard": { + "version": "16.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-semistandard/-/eslint-config-semistandard-16.0.0.tgz", + "integrity": "sha512-oD8QOo4mSInRJhQb3Zi6L8HebwZaB6SI3A+NNrPdVN0nN1K45L5pXK3joY+ksWDlT3ew/M+fJk2tuMCjIpjRzQ==", + "dev": true, + "requires": {} + }, + "eslint-config-standard": { + "version": "16.0.3", + "resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-16.0.3.tgz", + "integrity": "sha512-x4fmJL5hGqNJKGHSjnLdgA6U6h1YW/G2dW9fA+cyVur4SK6lyue8+UgNKWlZtUDTXvgKDD/Oa3GQjmB5kjtVvg==", + "dev": true, + "requires": {} + }, + "eslint-import-resolver-node": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.6.tgz", + "integrity": "sha512-0En0w03NRVMn9Uiyn8YRPDKvWjxCWkslUEhGNTdGx15RvPJYQ+lbOlqrlNI2vEAs4pDYK4f/HN2TbDmk5TP0iw==", + "dev": true, + "requires": { + "debug": "^3.2.7", + "resolve": "^1.20.0" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + } + } + }, + "eslint-module-utils": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.7.3.tgz", + "integrity": "sha512-088JEC7O3lDZM9xGe0RerkOMd0EjFl+Yvd1jPWIkMT5u3H9+HC34mWWPnqPrN13gieT9pBOO+Qt07Nb/6TresQ==", + "dev": true, + "requires": { + "debug": "^3.2.7", + "find-up": "^2.1.0" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "dev": true, + "requires": { + "locate-path": "^2.0.0" + } + }, + "locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "dev": true, + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "requires": { + "p-try": "^1.0.0" + } + }, + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "dev": true, + "requires": { + "p-limit": "^1.1.0" + } + }, + "p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "dev": true + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + } + } + }, + "eslint-plugin-compat": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-compat/-/eslint-plugin-compat-4.0.2.tgz", + "integrity": "sha512-xqvoO54CLTVaEYGMzhu35Wzwk/As7rCvz/2dqwnFiWi0OJccEtGIn+5qq3zqIu9nboXlpdBN579fZcItC73Ycg==", + "dev": true, + "requires": { + "@mdn/browser-compat-data": "^4.1.5", + "ast-metadata-inferer": "^0.7.0", + "browserslist": "^4.16.8", + "caniuse-lite": "^1.0.30001304", + "core-js": "^3.16.2", + "find-up": "^5.0.0", + "lodash.memoize": "4.1.2", + "semver": "7.3.5" + }, + "dependencies": { + "find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" } }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "strip-ansi": { + "locate-path": { "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, "requires": { - "ansi-regex": "^5.0.0" + "p-locate": "^5.0.0" } }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dev": true, "requires": { - "has-flag": "^4.0.0" + "yocto-queue": "^0.1.0" + } + }, + "p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "requires": { + "p-limit": "^3.0.2" } } } }, - "eslint-config-jquery": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/eslint-config-jquery/-/eslint-config-jquery-3.0.0.tgz", - "integrity": "sha512-VDdRAIlNq1EM5P7J4JGQSCnZEIvIlNGGTUTCPT2wQNZ2GT69rsAwSIqZVcoiyZbwY7TaaMwLOxwSjqm+DEUjbA==", - "dev": true - }, "eslint-plugin-es": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-3.0.1.tgz", @@ -2331,6 +11205,115 @@ "htmlparser2": "^6.0.1" } }, + "eslint-plugin-import": { + "version": "2.25.4", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.25.4.tgz", + "integrity": "sha512-/KJBASVFxpu0xg1kIBn9AUa8hQVnszpwgE7Ld0lKAlx7Ie87yzEzCgSkekt+le/YVhiaosO4Y14GDAOc41nfxA==", + "dev": true, + "requires": { + "array-includes": "^3.1.4", + "array.prototype.flat": "^1.2.5", + "debug": "^2.6.9", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.6", + "eslint-module-utils": "^2.7.2", + "has": "^1.0.3", + "is-core-module": "^2.8.0", + "is-glob": "^4.0.3", + "minimatch": "^3.0.4", + "object.values": "^1.1.5", + "resolve": "^1.20.0", + "tsconfig-paths": "^3.12.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "is-core-module": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.1.tgz", + "integrity": "sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, + "eslint-plugin-json-es": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-json-es/-/eslint-plugin-json-es-1.5.5.tgz", + "integrity": "sha512-J5wsCFuV8u+1nJzLtJo3SInE624sAr7yfKoktk7t7Px/gC/DLPYtXkwFu/QBaoPo4yvQ2wG532Jv9OpY1h212w==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^3.3.0", + "espree": "^9.3.1" + }, + "dependencies": { + "acorn": { + "version": "8.7.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", + "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==", + "dev": true + }, + "eslint-visitor-keys": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", + "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", + "dev": true + }, + "espree": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.3.1.tgz", + "integrity": "sha512-bvdyLmJMfwkV3NCRl5ZhJf22zBFo1y8bYh3VYb+bfzqNB4Je68P2sSuXyuFquzWLebHpNd2/d5uv7yoP9ISnGQ==", + "dev": true, + "requires": { + "acorn": "^8.7.0", + "acorn-jsx": "^5.3.1", + "eslint-visitor-keys": "^3.3.0" + } + } + } + }, + "eslint-plugin-markdown": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-markdown/-/eslint-plugin-markdown-2.2.1.tgz", + "integrity": "sha512-FgWp4iyYvTFxPwfbxofTvXxgzPsDuSKHQy2S+a8Ve6savbujey+lgrFFbXQA0HPygISpRYWYBjooPzhYSF81iA==", + "dev": true, + "requires": { + "mdast-util-from-markdown": "^0.8.5" + } + }, "eslint-plugin-node": { "version": "11.1.0", "resolved": "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-11.1.0.tgz", @@ -2346,9 +11329,9 @@ }, "dependencies": { "ignore": { - "version": "5.1.8", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", - "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", + "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", "dev": true }, "semver": { @@ -2359,6 +11342,13 @@ } } }, + "eslint-plugin-promise": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-6.0.0.tgz", + "integrity": "sha512-7GPezalm5Bfi/E22PnQxDWH2iW9GTvAlUNTztemeHb6c1BniSyoeTrM87JkC0wYdi6aQrZX9p2qEiAno8aTcbw==", + "dev": true, + "requires": {} + }, "eslint-plugin-qunit": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/eslint-plugin-qunit/-/eslint-plugin-qunit-6.2.0.tgz", @@ -2882,6 +11872,16 @@ "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", "dev": true }, + "get-symbol-description": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", + "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + } + }, "getobject": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/getobject/-/getobject-1.0.2.tgz", @@ -3222,7 +12222,8 @@ "version": "0.1.8", "resolved": "https://registry.npmjs.org/grunt-search/-/grunt-search-0.1.8.tgz", "integrity": "sha1-9K1kQLr49d+122p/KSeUDNJ9Vec=", - "dev": true + "dev": true, + "requires": {} }, "har-schema": { "version": "2.0.0", @@ -3266,6 +12267,12 @@ } } }, + "has-bigints": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz", + "integrity": "sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==", + "dev": true + }, "has-color": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/has-color/-/has-color-0.1.7.tgz", @@ -3284,6 +12291,15 @@ "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==", "dev": true }, + "has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "dev": true, + "requires": { + "has-symbols": "^1.0.2" + } + }, "hasha": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.2.tgz", @@ -3445,6 +12461,17 @@ "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", "dev": true }, + "internal-slot": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz", + "integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==", + "dev": true, + "requires": { + "get-intrinsic": "^1.1.0", + "has": "^1.0.3", + "side-channel": "^1.0.4" + } + }, "interpret": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.1.0.tgz", @@ -3461,6 +12488,47 @@ "is-windows": "^1.0.1" } }, + "is-alphabetical": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.4.tgz", + "integrity": "sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==", + "dev": true + }, + "is-alphanumerical": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz", + "integrity": "sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==", + "dev": true, + "requires": { + "is-alphabetical": "^1.0.0", + "is-decimal": "^1.0.0" + } + }, + "is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, + "requires": { + "has-bigints": "^1.0.1" + } + }, + "is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + } + }, + "is-callable": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.4.tgz", + "integrity": "sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w==", + "dev": true + }, "is-core-module": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.6.0.tgz", @@ -3470,6 +12538,21 @@ "has": "^1.0.3" } }, + "is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dev": true, + "requires": { + "has-tostringtag": "^1.0.0" + } + }, + "is-decimal": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.4.tgz", + "integrity": "sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==", + "dev": true + }, "is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -3491,12 +12574,24 @@ "is-extglob": "^2.1.1" } }, + "is-hexadecimal": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz", + "integrity": "sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==", + "dev": true + }, "is-module": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", "integrity": "sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE=", "dev": true }, + "is-negative-zero": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", + "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", + "dev": true + }, "is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -3512,6 +12607,15 @@ "lodash.isfinite": "^3.3.2" } }, + "is-number-object": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.6.tgz", + "integrity": "sha512-bEVOqiRcvo3zO1+G2lVMy+gkkEm9Yh7cDMRusKKu5ZJKPUYSJwICTKZrNKHA2EbSP0Tu0+6B/emsYNHZyn6K8g==", + "dev": true, + "requires": { + "has-tostringtag": "^1.0.0" + } + }, "is-object": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-object/-/is-object-1.0.2.tgz", @@ -3536,6 +12640,16 @@ "@types/estree": "*" } }, + "is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + } + }, "is-relative": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz", @@ -3545,6 +12659,30 @@ "is-unc-path": "^1.0.0" } }, + "is-shared-array-buffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.1.tgz", + "integrity": "sha512-IU0NmyknYZN0rChcKhRO1X8LYz5Isj/Fsqh8NJOSf+N/hCOTwy29F32Ik7a+QszE63IdvmwdTPDd6cZ5pg4cwA==", + "dev": true + }, + "is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, + "requires": { + "has-tostringtag": "^1.0.0" + } + }, + "is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, + "requires": { + "has-symbols": "^1.0.2" + } + }, "is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", @@ -3560,6 +12698,15 @@ "unc-path-regex": "^0.1.2" } }, + "is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2" + } + }, "is-windows": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", @@ -3888,6 +13035,12 @@ "integrity": "sha1-+4m2WpqAKBgz8LdHizpRBPiY67M=", "dev": true }, + "lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=", + "dev": true + }, "lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -3966,12 +13119,41 @@ "minimatch": "^3.0.2" } }, + "mdast-util-from-markdown": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-0.8.5.tgz", + "integrity": "sha512-2hkTXtYYnr+NubD/g6KGBS/0mFmBcifAsI0yIWRiRo0PjVs6SSOSOdtzbp6kSGnShDN6G5aWZpKQ2lWRy27mWQ==", + "dev": true, + "requires": { + "@types/mdast": "^3.0.0", + "mdast-util-to-string": "^2.0.0", + "micromark": "~2.11.0", + "parse-entities": "^2.0.0", + "unist-util-stringify-position": "^2.0.0" + } + }, + "mdast-util-to-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-2.0.0.tgz", + "integrity": "sha512-AW4DRS3QbBayY/jJmD8437V1Gombjf8RSOUCMFBuo5iHi58AGEgVCKQ+ezHkZZDpAQS75hcBMpLqjpJTjtUL7w==", + "dev": true + }, "merge-descriptors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=", "dev": true }, + "micromark": { + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-2.11.4.tgz", + "integrity": "sha512-+WoovN/ppKolQOFIAajxi7Lu9kInbPxFuTBVEavFcL8eAfVstoc5MocPmqBeAdBOJV00uaVjegzH4+MA0DN/uA==", + "dev": true, + "requires": { + "debug": "^4.0.0", + "parse-entities": "^2.0.0" + } + }, "micromatch": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", @@ -4142,8 +13324,7 @@ } }, "npm-reporter": { - "version": "file:test/cli/fixtures/npm-reporter", - "dev": true + "version": "file:test/cli/fixtures/npm-reporter" }, "nyc": { "version": "15.1.0", @@ -4194,6 +13375,12 @@ "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", "dev": true }, + "object-inspect": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.0.tgz", + "integrity": "sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g==", + "dev": true + }, "object-keys": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", @@ -4243,6 +13430,17 @@ "isobject": "^3.0.1" } }, + "object.values": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.5.tgz", + "integrity": "sha512-QUZRW0ilQ3PnPpbNtgdNV1PDbEqLIiSFB3l+EnGtBQ/8SUTLj1PZwtQHABZtLgwpJZTSZhuGLOGk57Drx2IvYg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.1" + } + }, "on-finished": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", @@ -4372,6 +13570,20 @@ "callsites": "^3.0.0" } }, + "parse-entities": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-2.0.0.tgz", + "integrity": "sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==", + "dev": true, + "requires": { + "character-entities": "^1.0.0", + "character-entities-legacy": "^1.0.0", + "character-reference-invalid": "^1.0.0", + "is-alphanumerical": "^1.0.0", + "is-decimal": "^1.0.0", + "is-hexadecimal": "^1.0.0" + } + }, "parse-filepath": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/parse-filepath/-/parse-filepath-1.0.2.tgz", @@ -4580,7 +13792,8 @@ "version": "7.5.5", "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.5.tgz", "integrity": "sha512-BAkMFcAzl8as1G/hArkxOxq3G7pjUqQ3gzYbLL0/5zNkph70e+lCoxBGnm6AW1+/aiNeV4fnKqZ8m4GZewmH2w==", - "dev": true + "dev": true, + "requires": {} } } }, @@ -5029,6 +14242,17 @@ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true }, + "side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + } + }, "signal-exit": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.4.tgz", @@ -5149,6 +14373,15 @@ "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==", "dev": true }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, "string-width": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", @@ -5171,13 +14404,24 @@ } } }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "string.prototype.trimend": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz", + "integrity": "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==", "dev": true, "requires": { - "safe-buffer": "~5.1.0" + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + } + }, + "string.prototype.trimstart": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz", + "integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" } }, "strip-ansi": { @@ -5360,6 +14604,41 @@ "punycode": "^2.1.1" } }, + "tsconfig-paths": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz", + "integrity": "sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==", + "dev": true, + "requires": { + "@types/json5": "^0.0.29", + "json5": "^1.0.1", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + }, + "dependencies": { + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + }, + "minimist": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", + "dev": true + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true + } + } + }, "tunnel": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.3.tgz", @@ -5411,6 +14690,18 @@ "integrity": "sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og==", "dev": true }, + "unbox-primitive": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz", + "integrity": "sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "has-bigints": "^1.0.1", + "has-symbols": "^1.0.2", + "which-boxed-primitive": "^1.0.2" + } + }, "unbzip2-stream": { "version": "1.4.3", "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", @@ -5465,6 +14756,15 @@ "integrity": "sha512-5Zfuy9q/DFr4tfO7ZPeVXb1aPoeQSdeFMLpYuFebehDAhbuevLs5yxSZmIFN1tP5F9Wl4IpJrYojg85/zgyZHQ==", "dev": true }, + "unist-util-stringify-position": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-2.0.3.tgz", + "integrity": "sha512-3faScn5I+hy9VleOq/qNbAd6pAx7iH5jYBMS9I1HgQVijz/4mv5Bvw5iw1sC/90CODiKo81G/ps8AJrISn687g==", + "dev": true, + "requires": { + "@types/unist": "^2.0.2" + } + }, "universalify": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", @@ -5600,6 +14900,19 @@ "isexe": "^2.0.0" } }, + "which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, + "requires": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + } + }, "which-module": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", @@ -5743,6 +15056,12 @@ "buffer-crc32": "~0.2.3", "fd-slicer": "~1.1.0" } + }, + "yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true } } } diff --git a/package.json b/package.json index 4da561530..7d49731a9 100644 --- a/package.json +++ b/package.json @@ -49,16 +49,22 @@ "@babel/core": "^7.15.5", "@babel/plugin-external-helpers": "^7.14.5", "@babel/preset-env": "^7.15.6", + "@qunitjs/browserstack-runner": "0.9.5-qunitjs.1", "@rollup/plugin-babel": "^5.3.0", "@rollup/plugin-commonjs": "^20.0.0", "@rollup/plugin-node-resolve": "^13.0.4", "@rollup/plugin-replace": "^3.0.0", - "@qunitjs/browserstack-runner": "0.9.5-qunitjs.1", "coveralls": "^3.1.1", "eslint": "7.32.0", - "eslint-config-jquery": "^3.0.0", + "eslint-config-semistandard": "^16.0.0", + "eslint-config-standard": "^16.0.3", + "eslint-plugin-compat": "^4.0.2", "eslint-plugin-html": "^6.1.2", + "eslint-plugin-import": "^2.25.4", + "eslint-plugin-json-es": "^1.5.5", + "eslint-plugin-markdown": "^2.2.1", "eslint-plugin-node": "^11.1.0", + "eslint-plugin-promise": "^6.0.0", "eslint-plugin-qunit": "^6.2.0", "fixturify": "^2.1.1", "fuzzysort": "1.2.1", diff --git a/rollup.config.js b/rollup.config.js index 3968887ab..11e638e57 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -1,49 +1,49 @@ /* eslint-env node */ -const { babel } = require( "@rollup/plugin-babel" ); -const { nodeResolve } = require( "@rollup/plugin-node-resolve" ); -const commonjs = require( "@rollup/plugin-commonjs" ); -const replace = require( "@rollup/plugin-replace" ); +const { babel } = require('@rollup/plugin-babel'); +const { nodeResolve } = require('@rollup/plugin-node-resolve'); +const commonjs = require('@rollup/plugin-commonjs'); +const replace = require('@rollup/plugin-replace'); -const { replacements } = require( "./build/dist-replace.js" ); -const isCoverage = process.env.BUILD_TARGET === "coverage"; +const { replacements } = require('./build/dist-replace.js'); +const isCoverage = process.env.BUILD_TARGET === 'coverage'; module.exports = { - input: "src/qunit.js", - output: { - file: "qunit/qunit.js", - sourcemap: isCoverage, - format: "iife", - exports: "none", + input: 'src/qunit.js', + output: { + file: 'qunit/qunit.js', + sourcemap: isCoverage, + format: 'iife', + exports: 'none', - // eslint-disable-next-line no-multi-str - banner: "/*!\n\ + // eslint-disable-next-line no-multi-str + banner: '/*!\n\ * QUnit @VERSION\n\ * https://qunitjs.com/\n\ *\n\ * Copyright OpenJS Foundation and other contributors\n\ * Released under the MIT license\n\ * https://jquery.org/license\n\ - */" - }, - plugins: [ - replace( { - preventAssignment: true, - delimiters: [ "", "" ], - ...replacements - } ), - nodeResolve(), - commonjs(), - babel( { - babelHelpers: "bundled", - babelrc: false, - presets: [ - [ "@babel/preset-env", { - targets: { - ie: 9 - } - } ] - ] - } ) - ] + */' + }, + plugins: [ + replace({ + preventAssignment: true, + delimiters: ['', ''], + ...replacements + }), + nodeResolve(), + commonjs(), + babel({ + babelHelpers: 'bundled', + babelrc: false, + presets: [ + ['@babel/preset-env', { + targets: { + ie: 9 + } + }] + ] + }) + ] }; diff --git a/src/assert.js b/src/assert.js index 1e6465f48..2bc73c06e 100644 --- a/src/assert.js +++ b/src/assert.js @@ -1,475 +1,465 @@ -import dump from "./dump"; -import equiv from "./equiv"; -import Logger from "./logger"; +import dump from './dump'; +import equiv from './equiv'; +import Logger from './logger'; -import config from "./core/config"; -import { objectType, objectValues, objectValuesSubset, errorString } from "./core/utilities"; -import { sourceFromStacktrace } from "./core/stacktrace"; -import { clearTimeout } from "./globals"; +import config from './core/config'; +import { objectType, objectValues, objectValuesSubset, errorString } from './core/utilities'; +import { sourceFromStacktrace } from './core/stacktrace'; +import { clearTimeout } from './globals'; class Assert { - constructor( testContext ) { - this.test = testContext; - } - - // Assert helpers - - timeout( duration ) { - if ( typeof duration !== "number" ) { - throw new Error( "You must pass a number as the duration to assert.timeout" ); - } - - this.test.timeout = duration; - - // If a timeout has been set, clear it and reset with the new duration - if ( config.timeout ) { - clearTimeout( config.timeout ); - config.timeout = null; - - if ( config.timeoutHandler && this.test.timeout > 0 ) { - this.test.internalResetTimeout( this.test.timeout ); - } - } - } - - // Documents a "step", which is a string value, in a test as a passing assertion - step( message ) { - let assertionMessage = message; - let result = !!message; - - this.test.steps.push( message ); - - if ( objectType( message ) === "undefined" || message === "" ) { - assertionMessage = "You must provide a message to assert.step"; - } else if ( objectType( message ) !== "string" ) { - assertionMessage = "You must provide a string value to assert.step"; - result = false; - } - - this.pushResult( { - result, - message: assertionMessage - } ); - } - - // Verifies the steps in a test match a given array of string values - verifySteps( steps, message ) { - - // Since the steps array is just string values, we can clone with slice - const actualStepsClone = this.test.steps.slice(); - this.deepEqual( actualStepsClone, steps, message ); - this.test.steps.length = 0; - } - - // Specify the number of expected assertions to guarantee that failed test - // (no assertions are run at all) don't slip through. - expect( asserts ) { - if ( arguments.length === 1 ) { - this.test.expected = asserts; - } else { - return this.test.expected; - } - } - - // Create a new async pause and return a new function that can release the pause. - async( count ) { - const requiredCalls = count === undefined ? 1 : count; - return this.test.internalStop( requiredCalls ); - } - - // Exports test.push() to the user API - // Alias of pushResult. - push( result, actual, expected, message, negative ) { - Logger.warn( "assert.push is deprecated and will be removed in QUnit 3.0." + - " Please use assert.pushResult instead (https://api.qunitjs.com/assert/pushResult)." ); - - const currentAssert = this instanceof Assert ? this : config.current.assert; - return currentAssert.pushResult( { - result, - actual, - expected, - message, - negative - } ); - } - - pushResult( resultInfo ) { - - // Destructure of resultInfo = { result, actual, expected, message, negative } - let assert = this; - const currentTest = ( assert instanceof Assert && assert.test ) || config.current; - - // Backwards compatibility fix. - // Allows the direct use of global exported assertions and QUnit.assert.* - // Although, it's use is not recommended as it can leak assertions - // to other tests from async tests, because we only get a reference to the current test, - // not exactly the test where assertion were intended to be called. - if ( !currentTest ) { - throw new Error( "assertion outside test context, in " + sourceFromStacktrace( 2 ) ); - } - - if ( !( assert instanceof Assert ) ) { - assert = currentTest.assert; - } - - return assert.test.pushResult( resultInfo ); - } - - ok( result, message ) { - if ( !message ) { - message = result ? - "okay" : - `failed, expected argument to be truthy, was: ${dump.parse( result )}`; - } - - this.pushResult( { - result: !!result, - actual: result, - expected: true, - message - } ); - } - - notOk( result, message ) { - if ( !message ) { - message = !result ? - "okay" : - `failed, expected argument to be falsy, was: ${dump.parse( result )}`; - } - - this.pushResult( { - result: !result, - actual: result, - expected: false, - message - } ); - } - - true( result, message ) { - this.pushResult( { - result: result === true, - actual: result, - expected: true, - message - } ); - } - - false( result, message ) { - this.pushResult( { - result: result === false, - actual: result, - expected: false, - message - } ); - } - - equal( actual, expected, message ) { - - // eslint-disable-next-line eqeqeq - const result = expected == actual; - - this.pushResult( { - result, - actual, - expected, - message - } ); - } - - notEqual( actual, expected, message ) { - - // eslint-disable-next-line eqeqeq - const result = expected != actual; - - this.pushResult( { - result, - actual, - expected, - message, - negative: true - } ); - } - - propEqual( actual, expected, message ) { - actual = objectValues( actual ); - expected = objectValues( expected ); - - this.pushResult( { - result: equiv( actual, expected ), - actual, - expected, - message - } ); - } - - notPropEqual( actual, expected, message ) { - actual = objectValues( actual ); - expected = objectValues( expected ); - - this.pushResult( { - result: !equiv( actual, expected ), - actual, - expected, - message, - negative: true - } ); - } - - propContains( actual, expected, message ) { - actual = objectValuesSubset( actual, expected ); - - // The expected parameter is usually a plain object, but clone it for - // consistency with propEqual(), and to make it easy to explain that - // inheritence is not considered (on either side), and to support - // recursively checking subsets of nested objects. - expected = objectValues( expected, false ); - - this.pushResult( { - result: equiv( actual, expected ), - actual, - expected, - message - } ); - } - - notPropContains( actual, expected, message ) { - actual = objectValuesSubset( actual, expected ); - expected = objectValues( expected ); - - this.pushResult( { - result: !equiv( actual, expected ), - actual, - expected, - message, - negative: true - } ); - } - - deepEqual( actual, expected, message ) { - this.pushResult( { - result: equiv( actual, expected ), - actual, - expected, - message - } ); - } - - notDeepEqual( actual, expected, message ) { - this.pushResult( { - result: !equiv( actual, expected ), - actual, - expected, - message, - negative: true - } ); - } - - strictEqual( actual, expected, message ) { - this.pushResult( { - result: expected === actual, - actual, - expected, - message - } ); - } - - notStrictEqual( actual, expected, message ) { - this.pushResult( { - result: expected !== actual, - actual, - expected, - message, - negative: true - } ); - } - - [ "throws" ]( block, expected, message ) { - - [ expected, message ] = validateExpectedExceptionArgs( expected, message, "throws" ); - - const currentTest = ( this instanceof Assert && this.test ) || config.current; - - if ( objectType( block ) !== "function" ) { - const message = "The value provided to `assert.throws` in " + - "\"" + currentTest.testName + "\" was not a function."; - - currentTest.assert.pushResult( { - result: false, - actual: block, - message - } ); - - return; - } - - let actual; - let result = false; - - currentTest.ignoreGlobalErrors = true; - try { - block.call( currentTest.testEnvironment ); - } catch ( e ) { - actual = e; - } - currentTest.ignoreGlobalErrors = false; - - if ( actual ) { - [ result, expected, message ] = validateException( actual, expected, message ); - } - - currentTest.assert.pushResult( { - result, - - // undefined if it didn't throw - actual: actual && errorString( actual ), - expected, - message - } ); - } - - rejects( promise, expected, message ) { - - [ expected, message ] = validateExpectedExceptionArgs( expected, message, "rejects" ); - - const currentTest = ( this instanceof Assert && this.test ) || config.current; - - const then = promise && promise.then; - if ( objectType( then ) !== "function" ) { - const message = "The value provided to `assert.rejects` in " + - "\"" + currentTest.testName + "\" was not a promise."; - - currentTest.assert.pushResult( { - result: false, - message: message, - actual: promise - } ); - - return; - } - - const done = this.async(); - - return then.call( - promise, - function handleFulfillment() { - const message = "The promise returned by the `assert.rejects` callback in " + - "\"" + currentTest.testName + "\" did not reject."; - - currentTest.assert.pushResult( { - result: false, - message: message, - actual: promise - } ); - - done(); - }, - - function handleRejection( actual ) { - let result; - [ result, expected, message ] = validateException( actual, expected, message ); - - currentTest.assert.pushResult( { - result, - - // leave rejection value of undefined as-is - actual: actual && errorString( actual ), - expected, - message - } ); - done(); - } - ); - } + constructor (testContext) { + this.test = testContext; + } + + timeout (duration) { + if (typeof duration !== 'number') { + throw new Error('You must pass a number as the duration to assert.timeout'); + } + + this.test.timeout = duration; + + // If a timeout has been set, clear it and reset with the new duration + if (config.timeout) { + clearTimeout(config.timeout); + config.timeout = null; + + if (config.timeoutHandler && this.test.timeout > 0) { + this.test.internalResetTimeout(this.test.timeout); + } + } + } + + // Documents a "step", which is a string value, in a test as a passing assertion + step (message) { + let assertionMessage = message; + let result = !!message; + + this.test.steps.push(message); + + if (objectType(message) === 'undefined' || message === '') { + assertionMessage = 'You must provide a message to assert.step'; + } else if (objectType(message) !== 'string') { + assertionMessage = 'You must provide a string value to assert.step'; + result = false; + } + + this.pushResult({ + result, + message: assertionMessage + }); + } + + // Verifies the steps in a test match a given array of string values + verifySteps (steps, message) { + // Since the steps array is just string values, we can clone with slice + const actualStepsClone = this.test.steps.slice(); + this.deepEqual(actualStepsClone, steps, message); + this.test.steps.length = 0; + } + + // Specify the number of expected assertions to guarantee that failed test + // (no assertions are run at all) don't slip through. + expect (asserts) { + if (arguments.length === 1) { + this.test.expected = asserts; + } else { + return this.test.expected; + } + } + + // Create a new async pause and return a new function that can release the pause. + async (count) { + const requiredCalls = count === undefined ? 1 : count; + return this.test.internalStop(requiredCalls); + } + + // Exports test.push() to the user API + // Alias of pushResult. + push (result, actual, expected, message, negative) { + Logger.warn('assert.push is deprecated and will be removed in QUnit 3.0.' + + ' Please use assert.pushResult instead (https://api.qunitjs.com/assert/pushResult).'); + + const currentAssert = this instanceof Assert ? this : config.current.assert; + return currentAssert.pushResult({ + result, + actual, + expected, + message, + negative + }); + } + + pushResult (resultInfo) { + // Destructure of resultInfo = { result, actual, expected, message, negative } + let assert = this; + const currentTest = (assert instanceof Assert && assert.test) || config.current; + + // Backwards compatibility fix. + // Allows the direct use of global exported assertions and QUnit.assert.* + // Although, it's use is not recommended as it can leak assertions + // to other tests from async tests, because we only get a reference to the current test, + // not exactly the test where assertion were intended to be called. + if (!currentTest) { + throw new Error('assertion outside test context, in ' + sourceFromStacktrace(2)); + } + + if (!(assert instanceof Assert)) { + assert = currentTest.assert; + } + + return assert.test.pushResult(resultInfo); + } + + ok (result, message) { + if (!message) { + message = result + ? 'okay' + : `failed, expected argument to be truthy, was: ${dump.parse(result)}`; + } + + this.pushResult({ + result: !!result, + actual: result, + expected: true, + message + }); + } + + notOk (result, message) { + if (!message) { + message = !result + ? 'okay' + : `failed, expected argument to be falsy, was: ${dump.parse(result)}`; + } + + this.pushResult({ + result: !result, + actual: result, + expected: false, + message + }); + } + + true (result, message) { + this.pushResult({ + result: result === true, + actual: result, + expected: true, + message + }); + } + + false (result, message) { + this.pushResult({ + result: result === false, + actual: result, + expected: false, + message + }); + } + + equal (actual, expected, message) { + // eslint-disable-next-line eqeqeq + const result = expected == actual; + + this.pushResult({ + result, + actual, + expected, + message + }); + } + + notEqual (actual, expected, message) { + // eslint-disable-next-line eqeqeq + const result = expected != actual; + + this.pushResult({ + result, + actual, + expected, + message, + negative: true + }); + } + + propEqual (actual, expected, message) { + actual = objectValues(actual); + expected = objectValues(expected); + + this.pushResult({ + result: equiv(actual, expected), + actual, + expected, + message + }); + } + + notPropEqual (actual, expected, message) { + actual = objectValues(actual); + expected = objectValues(expected); + + this.pushResult({ + result: !equiv(actual, expected), + actual, + expected, + message, + negative: true + }); + } + + propContains (actual, expected, message) { + actual = objectValuesSubset(actual, expected); + + // The expected parameter is usually a plain object, but clone it for + // consistency with propEqual(), and to make it easy to explain that + // inheritence is not considered (on either side), and to support + // recursively checking subsets of nested objects. + expected = objectValues(expected, false); + + this.pushResult({ + result: equiv(actual, expected), + actual, + expected, + message + }); + } + + notPropContains (actual, expected, message) { + actual = objectValuesSubset(actual, expected); + expected = objectValues(expected); + + this.pushResult({ + result: !equiv(actual, expected), + actual, + expected, + message, + negative: true + }); + } + + deepEqual (actual, expected, message) { + this.pushResult({ + result: equiv(actual, expected), + actual, + expected, + message + }); + } + + notDeepEqual (actual, expected, message) { + this.pushResult({ + result: !equiv(actual, expected), + actual, + expected, + message, + negative: true + }); + } + + strictEqual (actual, expected, message) { + this.pushResult({ + result: expected === actual, + actual, + expected, + message + }); + } + + notStrictEqual (actual, expected, message) { + this.pushResult({ + result: expected !== actual, + actual, + expected, + message, + negative: true + }); + } + + ['throws'] (block, expected, message) { + [expected, message] = validateExpectedExceptionArgs(expected, message, 'throws'); + + const currentTest = (this instanceof Assert && this.test) || config.current; + + if (objectType(block) !== 'function') { + const message = 'The value provided to `assert.throws` in ' + + '"' + currentTest.testName + '" was not a function.'; + + currentTest.assert.pushResult({ + result: false, + actual: block, + message + }); + + return; + } + + let actual; + let result = false; + + currentTest.ignoreGlobalErrors = true; + try { + block.call(currentTest.testEnvironment); + } catch (e) { + actual = e; + } + currentTest.ignoreGlobalErrors = false; + + if (actual) { + [result, expected, message] = validateException(actual, expected, message); + } + + currentTest.assert.pushResult({ + result, + + // undefined if it didn't throw + actual: actual && errorString(actual), + expected, + message + }); + } + + rejects (promise, expected, message) { + [expected, message] = validateExpectedExceptionArgs(expected, message, 'rejects'); + + const currentTest = (this instanceof Assert && this.test) || config.current; + + const then = promise && promise.then; + if (objectType(then) !== 'function') { + const message = 'The value provided to `assert.rejects` in ' + + '"' + currentTest.testName + '" was not a promise.'; + + currentTest.assert.pushResult({ + result: false, + message: message, + actual: promise + }); + + return; + } + + const done = this.async(); + + return then.call( + promise, + function handleFulfillment () { + const message = 'The promise returned by the `assert.rejects` callback in ' + + '"' + currentTest.testName + '" did not reject.'; + + currentTest.assert.pushResult({ + result: false, + message: message, + actual: promise + }); + + done(); + }, + + function handleRejection (actual) { + let result; + [result, expected, message] = validateException(actual, expected, message); + + currentTest.assert.pushResult({ + result, + + // leave rejection value of undefined as-is + actual: actual && errorString(actual), + expected, + message + }); + done(); + } + ); + } } -function validateExpectedExceptionArgs( expected, message, assertionMethod ) { - const expectedType = objectType( expected ); - - // 'expected' is optional unless doing string comparison - if ( expectedType === "string" ) { - if ( message === undefined ) { - message = expected; - expected = undefined; - return [ expected, message ]; - } else { - throw new Error( - "assert." + assertionMethod + - " does not accept a string value for the expected argument.\n" + - "Use a non-string object value (e.g. RegExp or validator function) " + - "instead if necessary." - ); - } - } - - const valid = - !expected || // TODO: be more explicit here - expectedType === "regexp" || - expectedType === "function" || - expectedType === "object"; - - if ( !valid ) { - const message = - "Invalid expected value type (" + expectedType + ") " + - "provided to assert." + assertionMethod + "."; - throw new Error( message ); - } - - return [ expected, message ]; +function validateExpectedExceptionArgs (expected, message, assertionMethod) { + const expectedType = objectType(expected); + + // 'expected' is optional unless doing string comparison + if (expectedType === 'string') { + if (message === undefined) { + message = expected; + expected = undefined; + return [expected, message]; + } else { + throw new Error( + 'assert.' + assertionMethod + + ' does not accept a string value for the expected argument.\n' + + 'Use a non-string object value (e.g. RegExp or validator function) ' + + 'instead if necessary.' + ); + } + } + + const valid = + !expected || // TODO: be more explicit here + expectedType === 'regexp' || + expectedType === 'function' || + expectedType === 'object'; + + if (!valid) { + const message = + 'Invalid expected value type (' + expectedType + ') ' + + 'provided to assert.' + assertionMethod + '.'; + throw new Error(message); + } + + return [expected, message]; } -function validateException( actual, expected, message ) { - let result = false; - const expectedType = objectType( expected ); - - // These branches should be exhaustive, based on validation done in validateExpectedException - - // We don't want to validate - if ( !expected ) { - result = true; - - // Expected is a regexp - } else if ( expectedType === "regexp" ) { - result = expected.test( errorString( actual ) ); - - // Log the string form of the regexp - expected = String( expected ); - - // Expected is a constructor, maybe an Error constructor. - // Note the extra check on its prototype - this is an implicit - // requirement of "instanceof", else it will throw a TypeError. - } else if ( expectedType === "function" && - expected.prototype !== undefined && actual instanceof expected ) { - result = true; - - // Expected is an Error object - } else if ( expectedType === "object" ) { - result = actual instanceof expected.constructor && - actual.name === expected.name && - actual.message === expected.message; - - // Log the string form of the Error object - expected = errorString( expected ); - - // Expected is a validation function which returns true if validation passed - } else if ( expectedType === "function" ) { - - // protect against accidental semantics which could hard error in the test - try { - result = expected.call( {}, actual ) === true; - expected = null; - } catch ( e ) { - - // assign the "expected" to a nice error string to communicate the local failure to the user - expected = errorString( e ); - } - } - - return [ result, expected, message ]; +function validateException (actual, expected, message) { + let result = false; + const expectedType = objectType(expected); + + // These branches should be exhaustive, based on validation done in validateExpectedException + + // We don't want to validate + if (!expected) { + result = true; + + // Expected is a regexp + } else if (expectedType === 'regexp') { + result = expected.test(errorString(actual)); + + // Log the string form of the regexp + expected = String(expected); + + // Expected is a constructor, maybe an Error constructor. + // Note the extra check on its prototype - this is an implicit + // requirement of "instanceof", else it will throw a TypeError. + } else if (expectedType === 'function' && + expected.prototype !== undefined && actual instanceof expected) { + result = true; + + // Expected is an Error object + } else if (expectedType === 'object') { + result = actual instanceof expected.constructor && + actual.name === expected.name && + actual.message === expected.message; + + // Log the string form of the Error object + expected = errorString(expected); + + // Expected is a validation function which returns true if validation passed + } else if (expectedType === 'function') { + // protect against accidental semantics which could hard error in the test + try { + result = expected.call({}, actual) === true; + expected = null; + } catch (e) { + // assign the "expected" to a nice error string to communicate the local failure to the user + expected = errorString(e); + } + } + + return [result, expected, message]; } // Provide an alternative to assert.throws(), for environments that consider throws a reserved word // Known to us are: Closure Compiler, Narwhal // eslint-disable-next-line dot-notation -Assert.prototype.raises = Assert.prototype[ "throws" ]; +Assert.prototype.raises = Assert.prototype['throws']; export default Assert; diff --git a/src/cli/find-reporter.js b/src/cli/find-reporter.js index 21459cfbf..a76c9f2bf 100644 --- a/src/cli/find-reporter.js +++ b/src/cli/find-reporter.js @@ -1,74 +1,74 @@ -"use strict"; +'use strict'; -const utils = require( "./utils" ); -const pkg = require( "../../package.json" ); +const utils = require('./utils'); +const pkg = require('../../package.json'); const hasOwn = Object.prototype.hasOwnProperty; -function findReporter( reporterName, builtin ) { - if ( !reporterName ) { - return builtin.tap; - } +function findReporter (reporterName, builtin) { + if (!reporterName) { + return builtin.tap; + } - // First, check if the reporter is one of the built-in ones - if ( hasOwn.call( builtin, reporterName ) ) { - return builtin[ reporterName ]; - } + // First, check if the reporter is one of the built-in ones + if (hasOwn.call(builtin, reporterName)) { + return builtin[reporterName]; + } - // Second, check if the reporter is an npm package - try { - return require( reporterName ); - } catch ( e ) { - if ( e.code !== "MODULE_NOT_FOUND" ) { - throw e; - } - } + // Second, check if the reporter is an npm package + try { + return require(reporterName); + } catch (e) { + if (e.code !== 'MODULE_NOT_FOUND') { + throw e; + } + } - // If we didn't find a reporter, display the available reporters and exit - displayAvailableReporters( builtin, reporterName ); + // If we didn't find a reporter, display the available reporters and exit + displayAvailableReporters(builtin, reporterName); } -function displayAvailableReporters( builtin, inputReporterName ) { - const message = []; +function displayAvailableReporters (builtin, inputReporterName) { + const message = []; - if ( inputReporterName ) { - message.push( `No reporter found matching "${inputReporterName}".` ); - } + if (inputReporterName) { + message.push(`No reporter found matching "${inputReporterName}".`); + } - const jsReporters = Object.keys( builtin ).sort(); - message.push( `Built-in reporters: ${jsReporters.join( ", " )}` ); + const jsReporters = Object.keys(builtin).sort(); + message.push(`Built-in reporters: ${jsReporters.join(', ')}`); - const npmReporters = getReportersFromDependencies(); - if ( npmReporters.length ) { - message.push( - `Extra reporters found among package dependencies: ${npmReporters.join( ", " )}` - ); - } + const npmReporters = getReportersFromDependencies(); + if (npmReporters.length) { + message.push( + `Extra reporters found among package dependencies: ${npmReporters.join(', ')}` + ); + } - utils.error( message.join( "\n" ) ); + utils.error(message.join('\n')); } -function getReportersFromDependencies() { - const dependencies = [].concat( - Object.keys( pkg.dependencies ), - Object.keys( pkg.devDependencies ) - ); - return dependencies.filter( dep => { - try { - const pkg = require( dep + "/package.json" ); +function getReportersFromDependencies () { + const dependencies = [].concat( + Object.keys(pkg.dependencies), + Object.keys(pkg.devDependencies) + ); + return dependencies.filter(dep => { + try { + const pkg = require(dep + '/package.json'); - return !!pkg.keywords && pkg.keywords.indexOf( "js-reporter" ) !== -1; - } catch ( e ) { - if ( e.code !== "MODULE_NOT_FOUND" && e.code !== "ERR_PACKAGE_PATH_NOT_EXPORTED" ) { - throw e; - } - } + return !!pkg.keywords && pkg.keywords.indexOf('js-reporter') !== -1; + } catch (e) { + if (e.code !== 'MODULE_NOT_FOUND' && e.code !== 'ERR_PACKAGE_PATH_NOT_EXPORTED') { + throw e; + } + } - return false; - } ); + return false; + }); } module.exports = { - findReporter, - displayAvailableReporters + findReporter, + displayAvailableReporters }; diff --git a/src/cli/require-from-cwd.js b/src/cli/require-from-cwd.js index d23562d0e..335bb96c8 100644 --- a/src/cli/require-from-cwd.js +++ b/src/cli/require-from-cwd.js @@ -1,4 +1,4 @@ -module.exports = function requireFromCWD( mod ) { - const resolvedPath = require.resolve( mod, { paths: [ process.cwd() ] } ); - return require( resolvedPath ); +module.exports = function requireFromCWD (mod) { + const resolvedPath = require.resolve(mod, { paths: [process.cwd()] }); + return require(resolvedPath); }; diff --git a/src/cli/require-qunit.js b/src/cli/require-qunit.js index 81ea84182..8b823c16b 100644 --- a/src/cli/require-qunit.js +++ b/src/cli/require-qunit.js @@ -1,52 +1,47 @@ // Depending on the exact usage, QUnit could be in one of several places, this // function handles finding it. -module.exports = function requireQUnit( resolve = require.resolve ) { - try { +module.exports = function requireQUnit (resolve = require.resolve) { + try { + // For: + // + // - QUnit is installed as local dependency and invoked normally + // within the current project, e.g. via `npm test`, `npm run …`, + // or `node_modules/.bin/qunit`. + // The below will lead to the same package directory that + // the CLI command belonged to. + // + // - QUnit is installed both as local dependency in the current project, + // and also globally installed via npm by the end-user. + // If the user (accidentally) ran the CLI command from their global + // install, then we prefer to stil use the qunit library file from the + // current project's dependency. + const localQUnitPath = resolve('qunit', { - // For: - // - // - QUnit is installed as local dependency and invoked normally - // within the current project, e.g. via `npm test`, `npm run …`, - // or `node_modules/.bin/qunit`. - // The below will lead to the same package directory that - // the CLI command belonged to. - // - // - QUnit is installed both as local dependency in the current project, - // and also globally installed via npm by the end-user. - // If the user (accidentally) ran the CLI command from their global - // install, then we prefer to stil use the qunit library file from the - // current project's dependency. - // eslint-disable-next-line node/no-missing-require - const localQUnitPath = resolve( "qunit", { + // Support: Node 10. Explicitly check "node_modules" to avoid a bug. + // Fixed in Node 12+. See https://github.com/nodejs/node/issues/35367. + paths: [process.cwd() + '/node_modules', process.cwd()] + }); + delete require.cache[localQUnitPath]; + return require(localQUnitPath); + } catch (e) { + if (e.code === 'MODULE_NOT_FOUND') { + // For: + // + // - QUnit is installed globally via npm by the end-user, and the + // the user ran this global CLI command in a project directory that + // does not have a qunit dependency installed. + // Use the library file relative to the global CLI command in that case. + // + // - We are running a local command from within the source directory + // of the QUnit project itself (e.g. qunit Git repository). + // Use the library file relative to this command, within the source directory. + // If you get "Error: Cannot find module ../../qunit/qunit", it probably + // means you haven't yet run "npm run build". + // + delete require.cache[resolve('../../qunit/qunit')]; + return require('../../qunit/qunit'); + } - // Support: Node 10. Explicitly check "node_modules" to avoid a bug. - // Fixed in Node 12+. See https://github.com/nodejs/node/issues/35367. - paths: [ process.cwd() + "/node_modules", process.cwd() ] - } ); - delete require.cache[ localQUnitPath ]; - return require( localQUnitPath ); - } catch ( e ) { - if ( e.code === "MODULE_NOT_FOUND" ) { - - // For: - // - // - QUnit is installed globally via npm by the end-user, and the - // the user ran this global CLI command in a project directory that - // does not have a qunit dependency installed. - // Use the library file relative to the global CLI command in that case. - // - // - We are running a local command from within the source directory - // of the QUnit project itself (e.g. qunit Git repository). - // Use the library file relative to this command, within the source directory. - // If you get "Error: Cannot find module ../../qunit/qunit", it probably - // means you haven't yet run "npm run build". - // - // eslint-disable-next-line node/no-missing-require, node/no-unpublished-require - delete require.cache[ resolve( "../../qunit/qunit" ) ]; - // eslint-disable-next-line node/no-missing-require, node/no-unpublished-require - return require( "../../qunit/qunit" ); - } - - throw e; - } + throw e; + } }; diff --git a/src/cli/run.js b/src/cli/run.js index 4e5fdb7f8..e202a5e33 100644 --- a/src/cli/run.js +++ b/src/cli/run.js @@ -1,12 +1,12 @@ -"use strict"; +'use strict'; -const path = require( "path" ); -const url = require( "url" ); +const path = require('path'); +const url = require('url'); -const requireFromCWD = require( "./require-from-cwd" ); -const requireQUnit = require( "./require-qunit" ); -const utils = require( "./utils" ); -const { findReporter } = require( "./find-reporter" ); +const requireFromCWD = require('./require-from-cwd'); +const requireQUnit = require('./require-qunit'); +const utils = require('./utils'); +const { findReporter } = require('./find-reporter'); const DEBOUNCE_WATCH_LENGTH = 60; const DEBOUNCE_RESTART_LENGTH = 200 - DEBOUNCE_WATCH_LENGTH; @@ -15,227 +15,224 @@ const changedPendingPurge = []; let QUnit; -async function run( args, options ) { - - // Default to non-zero exit code to avoid false positives - process.exitCode = 1; - - const files = utils.getFilesFromArgs( args ); - - QUnit = requireQUnit(); - - if ( options.filter ) { - QUnit.config.filter = options.filter; - } - - const seed = options.seed; - if ( seed ) { - if ( seed === true ) { - QUnit.config.seed = Math.random().toString( 36 ).slice( 2 ); - } else { - QUnit.config.seed = seed; - } - - console.log( `Running tests with seed: ${QUnit.config.seed}` ); - } - - // TODO: Enable mode where QUnit is not auto-injected, but other setup is - // still done automatically. - global.QUnit = QUnit; - - options.requires.forEach( requireFromCWD ); - - findReporter( options.reporter, QUnit.reporters ).init( QUnit ); - - for ( let i = 0; i < files.length; i++ ) { - const filePath = path.resolve( process.cwd(), files[ i ] ); - delete require.cache[ filePath ]; - - // Node.js 12.0.0 has node_module_version=72 - // https://nodejs.org/en/download/releases/ - const nodeVint = process.config.variables.node_module_version; - - try { - - // QUnit supports passing ESM files to the 'qunit' command when used on - // Node.js 12 or later. The dynamic import() keyword supports both CommonJS files - // (.js, .cjs) and ESM files (.mjs), so we could simply use that unconditionally on - // newer Node versions, regardless of the given file path. - // - // But: - // - Node.js 12 emits a confusing "ExperimentalWarning" when using import(), - // even if just to load a non-ESM file. So we should try to avoid it on non-ESM. - // - This Node.js feature is still considered experimental so to avoid unexpected - // breakage we should continue using require(). Consider flipping once stable and/or - // as part of QUnit 3.0. - // - Plugins and CLI bootstrap scripts may be hooking into require.extensions to modify - // or transform code as it gets loaded. For compatibility with that, we should - // support that until at least QUnit 3.0. - // - File extensions are not sufficient to differentiate between CJS and ESM. - // Use of ".mjs" is optional, as a package may configure Node to default to ESM - // and optionally use ".cjs" for CJS files. - // - // https://nodejs.org/docs/v12.7.0/api/modules.html#modules_addenda_the_mjs_extension - // https://nodejs.org/docs/v12.7.0/api/esm.html#esm_code_import_code_expressions - // https://github.com/qunitjs/qunit/issues/1465 - try { - require( filePath ); - } catch ( e ) { - if ( ( e.code === "ERR_REQUIRE_ESM" || - ( e instanceof SyntaxError && - e.message === "Cannot use import statement outside a module" ) ) && - ( !nodeVint || nodeVint >= 72 ) ) { - - // filePath is an absolute file path here (per path.resolve above). - // On Windows, Node.js enforces that absolute paths via ESM use valid URLs, - // e.g. file-protocol) https://github.com/qunitjs/qunit/issues/1667 - await import( url.pathToFileURL( filePath ) ); // eslint-disable-line node/no-unsupported-features/es-syntax - } else { - throw e; - } - } - } catch ( e ) { - const error = new Error( `Failed to load file ${files[ i ]}\n${e.name}: ${e.message}` ); - error.stack = e.stack; - QUnit.onUncaughtException( error ); - } - } - - // Handle the unhandled - process.on( "unhandledRejection", ( reason, _promise ) => { - QUnit.onUncaughtException( reason ); - } ); - process.on( "uncaughtException", ( error, _origin ) => { - QUnit.onUncaughtException( error ); - } ); - - let running = true; - process.on( "exit", function() { - if ( running ) { - console.error( "Error: Process exited before tests finished running" ); - - const currentTest = QUnit.config.current; - if ( currentTest && currentTest.pauses.size > 0 ) { - const name = currentTest.testName; - console.error( "Last test to run (" + name + ") has an async hold. " + - "Ensure all assert.async() callbacks are invoked and Promises resolve. " + - "You should also set a standard timeout via QUnit.config.testTimeout." ); - } - } - } ); - - QUnit.on( "error", function( _error ) { - - // Set exitCode directly, to make sure it is set to fail even if "runEnd" will never be - // reached, or if "runEnd" was already fired in the past and the process crashed later. - process.exitCode = 1; - } ); - - QUnit.on( "runEnd", function setExitCode( data ) { - running = false; - - if ( data.testCounts.failed ) { - process.exitCode = 1; - } else { - process.exitCode = 0; - } - } ); - - QUnit.start(); +async function run (args, options) { + // Default to non-zero exit code to avoid false positives + process.exitCode = 1; + + const files = utils.getFilesFromArgs(args); + + QUnit = requireQUnit(); + + if (options.filter) { + QUnit.config.filter = options.filter; + } + + const seed = options.seed; + if (seed) { + if (seed === true) { + QUnit.config.seed = Math.random().toString(36).slice(2); + } else { + QUnit.config.seed = seed; + } + + console.log(`Running tests with seed: ${QUnit.config.seed}`); + } + + // TODO: Enable mode where QUnit is not auto-injected, but other setup is + // still done automatically. + global.QUnit = QUnit; + + options.requires.forEach(requireFromCWD); + + findReporter(options.reporter, QUnit.reporters).init(QUnit); + + for (let i = 0; i < files.length; i++) { + const filePath = path.resolve(process.cwd(), files[i]); + delete require.cache[filePath]; + + // Node.js 12.0.0 has node_module_version=72 + // https://nodejs.org/en/download/releases/ + const nodeVint = process.config.variables.node_module_version; + + try { + // QUnit supports passing ESM files to the 'qunit' command when used on + // Node.js 12 or later. The dynamic import() keyword supports both CommonJS files + // (.js, .cjs) and ESM files (.mjs), so we could simply use that unconditionally on + // newer Node versions, regardless of the given file path. + // + // But: + // - Node.js 12 emits a confusing "ExperimentalWarning" when using import(), + // even if just to load a non-ESM file. So we should try to avoid it on non-ESM. + // - This Node.js feature is still considered experimental so to avoid unexpected + // breakage we should continue using require(). Consider flipping once stable and/or + // as part of QUnit 3.0. + // - Plugins and CLI bootstrap scripts may be hooking into require.extensions to modify + // or transform code as it gets loaded. For compatibility with that, we should + // support that until at least QUnit 3.0. + // - File extensions are not sufficient to differentiate between CJS and ESM. + // Use of ".mjs" is optional, as a package may configure Node to default to ESM + // and optionally use ".cjs" for CJS files. + // + // https://nodejs.org/docs/v12.7.0/api/modules.html#modules_addenda_the_mjs_extension + // https://nodejs.org/docs/v12.7.0/api/esm.html#esm_code_import_code_expressions + // https://github.com/qunitjs/qunit/issues/1465 + try { + require(filePath); + } catch (e) { + if ( + (e.code === 'ERR_REQUIRE_ESM' || + (e instanceof SyntaxError && + e.message === 'Cannot use import statement outside a module')) && + (!nodeVint || nodeVint >= 72) + ) { + // filePath is an absolute file path here (per path.resolve above). + // On Windows, Node.js enforces that absolute paths via ESM use valid URLs, + // e.g. file-protocol) https://github.com/qunitjs/qunit/issues/1667 + await import(url.pathToFileURL(filePath)); // eslint-disable-line node/no-unsupported-features/es-syntax + } else { + throw e; + } + } + } catch (e) { + const error = new Error(`Failed to load file ${files[i]}\n${e.name}: ${e.message}`); + error.stack = e.stack; + QUnit.onUncaughtException(error); + } + } + + // Handle the unhandled + process.on('unhandledRejection', (reason, _promise) => { + QUnit.onUncaughtException(reason); + }); + process.on('uncaughtException', (error, _origin) => { + QUnit.onUncaughtException(error); + }); + + let running = true; + process.on('exit', function () { + if (running) { + console.error('Error: Process exited before tests finished running'); + + const currentTest = QUnit.config.current; + if (currentTest && currentTest.pauses.size > 0) { + const name = currentTest.testName; + console.error('Last test to run (' + name + ') has an async hold. ' + + 'Ensure all assert.async() callbacks are invoked and Promises resolve. ' + + 'You should also set a standard timeout via QUnit.config.testTimeout.'); + } + } + }); + + QUnit.on('error', function (_error) { + // Set exitCode directly, to make sure it is set to fail even if "runEnd" will never be + // reached, or if "runEnd" was already fired in the past and the process crashed later. + process.exitCode = 1; + }); + + QUnit.on('runEnd', function setExitCode (data) { + running = false; + + if (data.testCounts.failed) { + process.exitCode = 1; + } else { + process.exitCode = 0; + } + }); + + QUnit.start(); } -run.restart = function( args ) { - clearTimeout( this._restartDebounceTimer ); +run.restart = function (args) { + clearTimeout(this._restartDebounceTimer); - this._restartDebounceTimer = setTimeout( () => { + this._restartDebounceTimer = setTimeout(() => { + changedPendingPurge.forEach(file => delete require.cache[path.resolve(file)]); + changedPendingPurge.length = 0; - changedPendingPurge.forEach( file => delete require.cache[ path.resolve( file ) ] ); - changedPendingPurge.length = 0; + if (QUnit.config.queue.length) { + console.log('Finishing current test and restarting...'); + } else { + console.log('Restarting...'); + } - if ( QUnit.config.queue.length ) { - console.log( "Finishing current test and restarting..." ); - } else { - console.log( "Restarting..." ); - } - - run.abort( () => run.apply( null, args ) ); - }, DEBOUNCE_RESTART_LENGTH ); + run.abort(() => run.apply(null, args)); + }, DEBOUNCE_RESTART_LENGTH); }; -run.abort = function( callback ) { - function clearQUnit() { - delete global.QUnit; - QUnit = null; - if ( callback ) { - callback(); - } - } - - if ( QUnit.config.queue.length ) { - const nextTestIndex = QUnit.config.queue.findIndex( fn => fn.name === "runTest" ); - QUnit.config.queue.splice( nextTestIndex ); - QUnit.on( "runEnd", clearQUnit ); - } else { - clearQUnit(); - } +run.abort = function (callback) { + function clearQUnit () { + delete global.QUnit; + QUnit = null; + if (callback) { + callback(); + } + } + + if (QUnit.config.queue.length) { + const nextTestIndex = QUnit.config.queue.findIndex(fn => fn.name === 'runTest'); + QUnit.config.queue.splice(nextTestIndex); + QUnit.on('runEnd', clearQUnit); + } else { + clearQUnit(); + } }; -run.watch = function watch( _, options ) { - const watch = require( "node-watch" ); - const args = Array.prototype.slice.call( arguments ); - const baseDir = process.cwd(); - - QUnit = requireQUnit(); - global.QUnit = QUnit; - options.requires.forEach( requireFromCWD ); - - // Include TypeScript when in use (automatically via require.extensions), - // https://github.com/qunitjs/qunit/issues/1669. - // - // Include ".json" (part of require.extensions) for test suites that use a data files, - // and for changes to package.json that may affect how a file is parsed (e.g. type=module). - // - // Include ".cjs" and ".mjs", which Node.js doesn't expose via require.extensions by default. - // - // eslint-disable-next-line node/no-deprecated-api - const includeExts = Object.keys( require.extensions ).concat( [ ".cjs", ".mjs" ] ); - const ignoreDirs = [ ".git", "node_modules" ]; - - const watcher = watch( baseDir, { - persistent: true, - recursive: true, - - // Bare minimum delay, we have another debounce in run.restart(). - delay: DEBOUNCE_WATCH_LENGTH, - filter: ( fullpath, skip ) => { - if ( /\/node_modules\//.test( fullpath ) || - ignoreDirs.includes( path.basename( fullpath ) ) - ) { - return skip; - } - return includeExts.includes( path.extname( fullpath ) ); - } - }, ( event, fullpath ) => { - console.log( `File ${event}: ${path.relative( baseDir, fullpath )}` ); - changedPendingPurge.push( fullpath ); - run.restart( args ); - } ); - - watcher.on( "ready", () => { - run.apply( null, args ); - } ); - - function stop() { - console.log( "Stopping QUnit..." ); - - watcher.close(); - run.abort( () => { - process.exit(); - } ); - } - - process.on( "SIGTERM", stop ); - process.on( "SIGINT", stop ); +run.watch = function watch (_, options) { + const watch = require('node-watch'); + const args = Array.prototype.slice.call(arguments); + const baseDir = process.cwd(); + + QUnit = requireQUnit(); + global.QUnit = QUnit; + options.requires.forEach(requireFromCWD); + + // Include TypeScript when in use (automatically via require.extensions), + // https://github.com/qunitjs/qunit/issues/1669. + // + // Include ".json" (part of require.extensions) for test suites that use a data files, + // and for changes to package.json that may affect how a file is parsed (e.g. type=module). + // + // Include ".cjs" and ".mjs", which Node.js doesn't expose via require.extensions by default. + // + // eslint-disable-next-line node/no-deprecated-api + const includeExts = Object.keys(require.extensions).concat(['.cjs', '.mjs']); + const ignoreDirs = ['.git', 'node_modules']; + + const watcher = watch(baseDir, { + persistent: true, + recursive: true, + + // Bare minimum delay, we have another debounce in run.restart(). + delay: DEBOUNCE_WATCH_LENGTH, + filter: (fullpath, skip) => { + if (/\/node_modules\//.test(fullpath) || + ignoreDirs.includes(path.basename(fullpath)) + ) { + return skip; + } + return includeExts.includes(path.extname(fullpath)); + } + }, (event, fullpath) => { + console.log(`File ${event}: ${path.relative(baseDir, fullpath)}`); + changedPendingPurge.push(fullpath); + run.restart(args); + }); + + watcher.on('ready', () => { + run.apply(null, args); + }); + + function stop () { + console.log('Stopping QUnit...'); + + watcher.close(); + run.abort(() => { + process.exit(); + }); + } + + process.on('SIGTERM', stop); + process.on('SIGINT', stop); }; module.exports = run; diff --git a/src/cli/utils.js b/src/cli/utils.js index a50efc3e5..4f985cacc 100644 --- a/src/cli/utils.js +++ b/src/cli/utils.js @@ -1,88 +1,85 @@ -"use strict"; +'use strict'; -const fs = require( "fs" ); -const path = require( "path" ); -const glob = require( "tiny-glob/sync" ); +const fs = require('fs'); +const path = require('path'); -function existsStat() { - try { - return fs.statSync.apply( fs, arguments ); - } catch ( e ) { - return null; - } -} - - -function getIgnoreList( baseDir ) { - const gitFilePath = path.join( baseDir, ".gitignore" ); - if ( fs.existsSync( gitFilePath ) ) { - const gitIgnore = fs.readFileSync( gitFilePath, "utf8" ); +const glob = require('tiny-glob/sync'); - // Account for Windows-style line endings - return gitIgnore.trim().split( /\r?\n/ ); - } - return []; +function existsStat () { + try { + return fs.statSync.apply(fs, arguments); + } catch (e) { + return null; + } } -function getFilesFromArgs( args ) { - const patterns = args.slice(); - - // Default to files in the test directory - if ( !patterns.length ) { - - // TODO: In QUnit 3.0, change the default to {js,mjs} - patterns.push( "test/**/*.js" ); - } - - const files = new Set(); +function getIgnoreList (baseDir) { + const gitFilePath = path.join(baseDir, '.gitignore'); + if (fs.existsSync(gitFilePath)) { + const gitIgnore = fs.readFileSync(gitFilePath, 'utf8'); - // For each of the potential globs, we check if it is a directory path and - // update it so that it matches the JS files in that directory. - patterns.forEach( pattern => { - const stat = existsStat( pattern ); - - if ( stat && stat.isFile() ) { - - // Optimisation: - // For non-glob simple files, skip (slow) directory-wide scanning. - // https://github.com/qunitjs/qunit/pull/1385 - files.add( pattern ); - } else { - if ( stat && stat.isDirectory() ) { - - // TODO: In QUnit 3.0, change the default to {js,mjs} - pattern = `${pattern}/**/*.js`; - } - const results = glob( pattern, { - cwd: process.cwd(), - filesOnly: true, - flush: true - } ); - for ( const result of results ) { - files.add( result ); - } - } - } ); - - if ( !files.size ) { - error( "No files were found matching: " + args.join( ", " ) ); - } + // Account for Windows-style line endings + return gitIgnore.trim().split(/\r?\n/); + } + return []; +} - return Array.from( files ).sort(); +function getFilesFromArgs (args) { + const patterns = args.slice(); + + // Default to files in the test directory + if (!patterns.length) { + // TODO: In QUnit 3.0, change the default to {js,mjs} + patterns.push('test/**/*.js'); + } + + const files = new Set(); + + // For each of the potential globs, we check if it is a directory path and + // update it so that it matches the JS files in that directory. + patterns.forEach(pattern => { + const stat = existsStat(pattern); + + if (stat && stat.isFile()) { + // Optimisation: + // For non-glob simple files, skip (slow) directory-wide scanning. + // https://github.com/qunitjs/qunit/pull/1385 + files.add(pattern); + } else { + if (stat && stat.isDirectory()) { + // TODO: In QUnit 3.0, change the default to {js,mjs} + pattern = `${pattern}/**/*.js`; + } + const results = glob(pattern, { + cwd: process.cwd(), + filesOnly: true, + flush: true + }); + for (const result of results) { + files.add(result); + } + } + }); + + if (!files.size) { + error('No files were found matching: ' + args.join(', ')); + } + + return Array.from(files).sort(); } -function error( message ) { - console.error( message ); - process.exit( 1 ); +function error (message) { + console.error(message); + process.exit(1); } -function capitalize( string ) { - return string.charAt( 0 ).toUpperCase() + string.slice( 1 ); +function capitalize (string) { + return string.charAt(0).toUpperCase() + string.slice(1); } module.exports = { - capitalize, - error, - getFilesFromArgs, - getIgnoreList + capitalize, + error, + getFilesFromArgs, + getIgnoreList }; diff --git a/src/core.js b/src/core.js index d95a361bd..9af63837a 100644 --- a/src/core.js +++ b/src/core.js @@ -1,24 +1,24 @@ -import { window, document, setTimeout } from "./globals"; - -import equiv from "./equiv"; -import dump from "./dump"; -import { runSuite, module } from "./module"; -import Assert from "./assert"; -import Logger from "./logger"; -import Test, { test, pushFailure } from "./test"; -import exportQUnit from "./export"; -import reporters from "./reporters"; - -import config from "./core/config"; -import { hooks } from "./core/hooks"; -import { extend, objectType, is, now } from "./core/utilities"; -import { registerLoggingCallbacks, runLoggingCallbacks } from "./core/logging"; -import { sourceFromStacktrace } from "./core/stacktrace"; -import ProcessingQueue from "./core/processing-queue"; - -import { on, emit } from "./events"; -import onWindowError from "./core/onerror"; -import onUncaughtException from "./core/on-uncaught-exception"; +import { window, document, setTimeout } from './globals'; + +import equiv from './equiv'; +import dump from './dump'; +import { runSuite, module } from './module'; +import Assert from './assert'; +import Logger from './logger'; +import Test, { test, pushFailure } from './test'; +import exportQUnit from './export'; +import reporters from './reporters'; + +import config from './core/config'; +import { hooks } from './core/hooks'; +import { extend, objectType, is, now } from './core/utilities'; +import { registerLoggingCallbacks, runLoggingCallbacks } from './core/logging'; +import { sourceFromStacktrace } from './core/stacktrace'; +import ProcessingQueue from './core/processing-queue'; + +import { on, emit } from './events'; +import onWindowError from './core/onerror'; +import onUncaughtException from './core/on-uncaught-exception'; const QUnit = {}; @@ -34,168 +34,163 @@ let globalStartCalled = false; let runStarted = false; // Figure out if we're running the tests from a server or not -QUnit.isLocal = ( window && window.location && window.location.protocol === "file:" ); +QUnit.isLocal = (window && window.location && window.location.protocol === 'file:'); // Expose the current QUnit version -QUnit.version = "@VERSION"; - -extend( QUnit, { - config, - - dump, - equiv, - reporters, - hooks, - is, - objectType, - on, - onError: onWindowError, - onUncaughtException, - pushFailure, - - assert: Assert.prototype, - module, - test, - - // alias other test flavors for easy access - todo: test.todo, - skip: test.skip, - only: test.only, - - start: function( count ) { - - if ( config.current ) { - throw new Error( "QUnit.start cannot be called inside a test context." ); - } - - const globalStartAlreadyCalled = globalStartCalled; - globalStartCalled = true; - - if ( runStarted ) { - throw new Error( "Called start() while test already started running" ); - } - if ( globalStartAlreadyCalled || count > 1 ) { - throw new Error( "Called start() outside of a test context too many times" ); - } - if ( config.autostart ) { - throw new Error( "Called start() outside of a test context when " + - "QUnit.config.autostart was true" ); - } - - if ( !config.pageLoaded ) { - - // The page isn't completely loaded yet, so we set autostart and then - // load if we're in Node or wait for the browser's load event. - config.autostart = true; - - // Starts from Node even if .load was not previously called. We still return - // early otherwise we'll wind up "beginning" twice. - if ( !document ) { - QUnit.load(); - } - - return; - } - - scheduleBegin(); - - }, - - onUnhandledRejection: function( reason ) { - Logger.warn( "QUnit.onUnhandledRejection is deprecated and will be removed in QUnit 3.0." + - " Please use QUnit.onUncaughtException instead." ); - onUncaughtException( reason ); - }, - - extend: function( ...args ) { - Logger.warn( "QUnit.extend is deprecated and will be removed in QUnit 3.0." + - " Please use Object.assign instead." ); - - // delegate to utility implementation, which does not warn and can be used elsewhere internally - return extend.apply( this, args ); - }, - - load: function() { - config.pageLoaded = true; - - // Initialize the configuration options - extend( config, { - started: 0, - updateRate: 1000, - autostart: true, - filter: "" - }, true ); - - if ( !runStarted ) { - config.blocking = false; - - if ( config.autostart ) { - scheduleBegin(); - } - } - }, - - stack: function( offset ) { - offset = ( offset || 0 ) + 2; - return sourceFromStacktrace( offset ); - } -} ); - -registerLoggingCallbacks( QUnit ); - -function scheduleBegin() { - - runStarted = true; - - // Add a slight delay to allow definition of more modules and tests. - if ( setTimeout ) { - setTimeout( function() { - begin(); - } ); - } else { - begin(); - } +QUnit.version = '@VERSION'; + +extend(QUnit, { + config, + + dump, + equiv, + reporters, + hooks, + is, + objectType, + on, + onError: onWindowError, + onUncaughtException, + pushFailure, + + assert: Assert.prototype, + module, + test, + + // alias other test flavors for easy access + todo: test.todo, + skip: test.skip, + only: test.only, + + start: function (count) { + if (config.current) { + throw new Error('QUnit.start cannot be called inside a test context.'); + } + + const globalStartAlreadyCalled = globalStartCalled; + globalStartCalled = true; + + if (runStarted) { + throw new Error('Called start() while test already started running'); + } + if (globalStartAlreadyCalled || count > 1) { + throw new Error('Called start() outside of a test context too many times'); + } + if (config.autostart) { + throw new Error('Called start() outside of a test context when ' + + 'QUnit.config.autostart was true'); + } + + if (!config.pageLoaded) { + // The page isn't completely loaded yet, so we set autostart and then + // load if we're in Node or wait for the browser's load event. + config.autostart = true; + + // Starts from Node even if .load was not previously called. We still return + // early otherwise we'll wind up "beginning" twice. + if (!document) { + QUnit.load(); + } + + return; + } + + scheduleBegin(); + }, + + onUnhandledRejection: function (reason) { + Logger.warn('QUnit.onUnhandledRejection is deprecated and will be removed in QUnit 3.0.' + + ' Please use QUnit.onUncaughtException instead.'); + onUncaughtException(reason); + }, + + extend: function (...args) { + Logger.warn('QUnit.extend is deprecated and will be removed in QUnit 3.0.' + + ' Please use Object.assign instead.'); + + // delegate to utility implementation, which does not warn and can be used elsewhere internally + return extend.apply(this, args); + }, + + load: function () { + config.pageLoaded = true; + + // Initialize the configuration options + extend(config, { + started: 0, + updateRate: 1000, + autostart: true, + filter: '' + }, true); + + if (!runStarted) { + config.blocking = false; + + if (config.autostart) { + scheduleBegin(); + } + } + }, + + stack: function (offset) { + offset = (offset || 0) + 2; + return sourceFromStacktrace(offset); + } +}); + +registerLoggingCallbacks(QUnit); + +function scheduleBegin () { + runStarted = true; + + // Add a slight delay to allow definition of more modules and tests. + if (setTimeout) { + setTimeout(function () { + begin(); + }); + } else { + begin(); + } } -function unblockAndAdvanceQueue() { - config.blocking = false; - ProcessingQueue.advance(); +function unblockAndAdvanceQueue () { + config.blocking = false; + ProcessingQueue.advance(); } -export function begin() { - - if ( config.started ) { - unblockAndAdvanceQueue(); - return; - } - - // The test run hasn't officially begun yet - // Record the time of the test run's beginning - config.started = now(); - - // Delete the loose unnamed module if unused. - if ( config.modules[ 0 ].name === "" && config.modules[ 0 ].tests.length === 0 ) { - config.modules.shift(); - } - - // Avoid unnecessary information by not logging modules' test environments - const l = config.modules.length; - const modulesLog = []; - for ( let i = 0; i < l; i++ ) { - modulesLog.push( { - name: config.modules[ i ].name, - tests: config.modules[ i ].tests - } ); - } - - // The test run is officially beginning now - emit( "runStart", runSuite.start( true ) ); - runLoggingCallbacks( "begin", { - totalTests: Test.count, - modules: modulesLog - } ).then( unblockAndAdvanceQueue ); +export function begin () { + if (config.started) { + unblockAndAdvanceQueue(); + return; + } + + // The test run hasn't officially begun yet + // Record the time of the test run's beginning + config.started = now(); + + // Delete the loose unnamed module if unused. + if (config.modules[0].name === '' && config.modules[0].tests.length === 0) { + config.modules.shift(); + } + + // Avoid unnecessary information by not logging modules' test environments + const l = config.modules.length; + const modulesLog = []; + for (let i = 0; i < l; i++) { + modulesLog.push({ + name: config.modules[i].name, + tests: config.modules[i].tests + }); + } + + // The test run is officially beginning now + emit('runStart', runSuite.start(true)); + runLoggingCallbacks('begin', { + totalTests: Test.count, + modules: modulesLog + }).then(unblockAndAdvanceQueue); } -exportQUnit( QUnit ); +exportQUnit(QUnit); export default QUnit; diff --git a/src/core/config.js b/src/core/config.js index 92394a874..0570177cc 100644 --- a/src/core/config.js +++ b/src/core/config.js @@ -1,5 +1,5 @@ -import { window, localSessionStorage } from "../globals"; -import { extend } from "./utilities"; +import { window, localSessionStorage } from '../globals'; +import { extend } from './utilities'; /** * Config object: Maintain internal state @@ -8,110 +8,110 @@ import { extend } from "./utilities"; */ const config = { - // The queue of tests to run - queue: [], - - stats: { all: 0, bad: 0, testCount: 0 }, - - // Block until document ready - blocking: true, - - // whether or not to fail when there are zero tests - // defaults to `true` - failOnZeroTests: true, - - // By default, run previously failed tests first - // very useful in combination with "Hide passed tests" checked - reorder: true, - - // By default, modify document.title when suite is done - altertitle: true, - - // HTML Reporter: collapse every test except the first failing test - // If false, all failing tests will be expanded - collapse: true, - - // By default, scroll to top of the page when suite is done - scrolltop: true, - - // Depth up-to which object will be dumped - maxDepth: 5, - - // When enabled, all tests must call expect() - requireExpects: false, - - // Placeholder for user-configurable form-exposed URL parameters - urlConfig: [], - - // Set of all modules. - modules: [], - - // The first unnamed module - // - // By being defined as the intial value for currentModule, it is the - // receptacle and implied parent for any global tests. It is as if we - // called `QUnit.module( "" );` before any other tests were defined. - // - // If we reach begin() and no tests were put in it, we dequeue it as if it - // never existed, and in that case never expose it to the events and - // callbacks API. - // - // When global tests are defined, then this unnamed module will execute - // as any other module, including moduleStart/moduleDone events etc. - // - // Since this module isn't explicitly created by the user, they have no - // access to add hooks for it. The hooks object is defined to comply - // with internal expectations of test.js, but they will be empty. - // To apply hooks, place tests explicitly in a QUnit.module(), and use - // its hooks accordingly. - // - // For global hooks that apply to all tests and all modules, use QUnit.hooks. - // - // NOTE: This is *not* a "global module". It is not an ancestor of all modules - // and tests. It is merely the parent for any tests defined globally, - // before the first QUnit.module(). As such, the events for this unnamed - // module will fire as normal, right after its last test, and *not* at - // the end of the test run. - // - // NOTE: This also should probably also not become a global module, unless - // we keep it out of the public API. For example, it would likely not - // improve the user interface and plugin behaviour if all modules became - // wrapped between the start and end events of this module, and thus - // needlessly add indentation, indirection, or other visible noise. - // Unit tests for the callbacks API would detect that as a regression. - currentModule: { - name: "", - tests: [], - childModules: [], - testsRun: 0, - testsIgnored: 0, - hooks: { - before: [], - beforeEach: [], - afterEach: [], - after: [] - } - }, - - // Exposed to make resets easier - // Ref https://github.com/qunitjs/qunit/pull/1598 - globalHooks: {}, - - callbacks: {}, - - // The storage module to use for reordering tests - storage: localSessionStorage + // The queue of tests to run + queue: [], + + stats: { all: 0, bad: 0, testCount: 0 }, + + // Block until document ready + blocking: true, + + // whether or not to fail when there are zero tests + // defaults to `true` + failOnZeroTests: true, + + // By default, run previously failed tests first + // very useful in combination with "Hide passed tests" checked + reorder: true, + + // By default, modify document.title when suite is done + altertitle: true, + + // HTML Reporter: collapse every test except the first failing test + // If false, all failing tests will be expanded + collapse: true, + + // By default, scroll to top of the page when suite is done + scrolltop: true, + + // Depth up-to which object will be dumped + maxDepth: 5, + + // When enabled, all tests must call expect() + requireExpects: false, + + // Placeholder for user-configurable form-exposed URL parameters + urlConfig: [], + + // Set of all modules. + modules: [], + + // The first unnamed module + // + // By being defined as the intial value for currentModule, it is the + // receptacle and implied parent for any global tests. It is as if we + // called `QUnit.module( "" );` before any other tests were defined. + // + // If we reach begin() and no tests were put in it, we dequeue it as if it + // never existed, and in that case never expose it to the events and + // callbacks API. + // + // When global tests are defined, then this unnamed module will execute + // as any other module, including moduleStart/moduleDone events etc. + // + // Since this module isn't explicitly created by the user, they have no + // access to add hooks for it. The hooks object is defined to comply + // with internal expectations of test.js, but they will be empty. + // To apply hooks, place tests explicitly in a QUnit.module(), and use + // its hooks accordingly. + // + // For global hooks that apply to all tests and all modules, use QUnit.hooks. + // + // NOTE: This is *not* a "global module". It is not an ancestor of all modules + // and tests. It is merely the parent for any tests defined globally, + // before the first QUnit.module(). As such, the events for this unnamed + // module will fire as normal, right after its last test, and *not* at + // the end of the test run. + // + // NOTE: This also should probably also not become a global module, unless + // we keep it out of the public API. For example, it would likely not + // improve the user interface and plugin behaviour if all modules became + // wrapped between the start and end events of this module, and thus + // needlessly add indentation, indirection, or other visible noise. + // Unit tests for the callbacks API would detect that as a regression. + currentModule: { + name: '', + tests: [], + childModules: [], + testsRun: 0, + testsIgnored: 0, + hooks: { + before: [], + beforeEach: [], + afterEach: [], + after: [] + } + }, + + // Exposed to make resets easier + // Ref https://github.com/qunitjs/qunit/pull/1598 + globalHooks: {}, + + callbacks: {}, + + // The storage module to use for reordering tests + storage: localSessionStorage }; // take a predefined QUnit.config and extend the defaults const globalConfig = window && window.QUnit && window.QUnit.config; // only extend the global config if there is no QUnit overload -if ( window && window.QUnit && !window.QUnit.version ) { - extend( config, globalConfig ); +if (window && window.QUnit && !window.QUnit.version) { + extend(config, globalConfig); } // Push a loose unnamed module to the modules collection -config.modules.push( config.currentModule ); +config.modules.push(config.currentModule); export default config; diff --git a/src/core/hooks.js b/src/core/hooks.js index 204b8aa0d..3cfb77729 100644 --- a/src/core/hooks.js +++ b/src/core/hooks.js @@ -1,15 +1,15 @@ -import config from "./config"; +import config from './config'; -function makeAddGlobalHook( hookName ) { - return function addGlobalHook( callback ) { - if ( !config.globalHooks[ hookName ] ) { - config.globalHooks[ hookName ] = []; - } - config.globalHooks[ hookName ].push( callback ); - }; +function makeAddGlobalHook (hookName) { + return function addGlobalHook (callback) { + if (!config.globalHooks[hookName]) { + config.globalHooks[hookName] = []; + } + config.globalHooks[hookName].push(callback); + }; } export const hooks = { - beforeEach: makeAddGlobalHook( "beforeEach" ), - afterEach: makeAddGlobalHook( "afterEach" ) + beforeEach: makeAddGlobalHook('beforeEach'), + afterEach: makeAddGlobalHook('afterEach') }; diff --git a/src/core/logging.js b/src/core/logging.js index f9524c44f..abf53f00b 100644 --- a/src/core/logging.js +++ b/src/core/logging.js @@ -1,54 +1,54 @@ -import config from "./config"; -import { objectType } from "./utilities"; -import Promise from "../promise"; +import config from './config'; +import { objectType } from './utilities'; +import Promise from '../promise'; // Register logging callbacks -export function registerLoggingCallbacks( obj ) { - const callbackNames = [ "begin", "done", "log", "testStart", "testDone", - "moduleStart", "moduleDone" ]; - - function registerLoggingCallback( key ) { - const loggingCallback = function( callback ) { - if ( objectType( callback ) !== "function" ) { - throw new Error( - "QUnit logging methods require a callback function as their first parameters." - ); - } - - config.callbacks[ key ].push( callback ); - }; - - return loggingCallback; - } - - for ( let i = 0, l = callbackNames.length; i < l; i++ ) { - const key = callbackNames[ i ]; - - // Initialize key collection of logging callback - if ( objectType( config.callbacks[ key ] ) === "undefined" ) { - config.callbacks[ key ] = []; - } - - obj[ key ] = registerLoggingCallback( key ); - } +export function registerLoggingCallbacks (obj) { + const callbackNames = ['begin', 'done', 'log', 'testStart', 'testDone', + 'moduleStart', 'moduleDone']; + + function registerLoggingCallback (key) { + const loggingCallback = function (callback) { + if (objectType(callback) !== 'function') { + throw new Error( + 'QUnit logging methods require a callback function as their first parameters.' + ); + } + + config.callbacks[key].push(callback); + }; + + return loggingCallback; + } + + for (let i = 0, l = callbackNames.length; i < l; i++) { + const key = callbackNames[i]; + + // Initialize key collection of logging callback + if (objectType(config.callbacks[key]) === 'undefined') { + config.callbacks[key] = []; + } + + obj[key] = registerLoggingCallback(key); + } } -export function runLoggingCallbacks( key, args ) { - const callbacks = config.callbacks[ key ]; - - // Handling 'log' callbacks separately. Unlike the other callbacks, - // the log callback is not controlled by the processing queue, - // but rather used by asserts. Hence to promisfy the 'log' callback - // would mean promisfying each step of a test - if ( key === "log" ) { - callbacks.map( callback => callback( args ) ); - return; - } - - // ensure that each callback is executed serially - return callbacks.reduce( ( promiseChain, callback ) => { - return promiseChain.then( () => { - return Promise.resolve( callback( args ) ); - } ); - }, Promise.resolve( [] ) ); +export function runLoggingCallbacks (key, args) { + const callbacks = config.callbacks[key]; + + // Handling 'log' callbacks separately. Unlike the other callbacks, + // the log callback is not controlled by the processing queue, + // but rather used by asserts. Hence to promisfy the 'log' callback + // would mean promisfying each step of a test + if (key === 'log') { + callbacks.map(callback => callback(args)); + return; + } + + // ensure that each callback is executed serially + return callbacks.reduce((promiseChain, callback) => { + return promiseChain.then(() => { + return Promise.resolve(callback(args)); + }); + }, Promise.resolve([])); } diff --git a/src/core/on-uncaught-exception.js b/src/core/on-uncaught-exception.js index 9b177aa48..889fa2c1b 100644 --- a/src/core/on-uncaught-exception.js +++ b/src/core/on-uncaught-exception.js @@ -1,8 +1,8 @@ -import config from "./config"; -import { runSuite } from "../module"; -import { sourceFromStacktrace } from "./stacktrace"; -import { errorString } from "./utilities"; -import { emit } from "../events"; +import config from './config'; +import { runSuite } from '../module'; +import { sourceFromStacktrace } from './stacktrace'; +import { errorString } from './utilities'; +import { emit } from '../events'; /** * Handle a global error that should result in a failed test run. @@ -23,29 +23,28 @@ import { emit } from "../events"; * @since 2.17.0 * @param {Error|any} error */ -export default function onUncaughtException( error ) { - if ( config.current ) { - config.current.assert.pushResult( { - result: false, - message: `global failure: ${errorString( error )}`, +export default function onUncaughtException (error) { + if (config.current) { + config.current.assert.pushResult({ + result: false, + message: `global failure: ${errorString(error)}`, - // We could let callers specify an offset to subtract a number of frames via - // sourceFromStacktrace, in case they are a wrapper further away from the error - // handler, and thus reduce some noise in the stack trace. However, we're not - // doing this right now because it would almost never be used in practice given - // the vast majority of error values will be Error objects, and thus have their - // own stack trace already. - source: ( error && error.stack ) || sourceFromStacktrace( 2 ) - } ); - } else { - - // The "error" event was added in QUnit 2.17. - // Increase "bad assertion" stats despite no longer pushing an assertion in this case. - // This ensures "runEnd" and "QUnit.done()" handlers behave as expected, since the "bad" - // count is typically how reporters decide on the boolean outcome of the test run. - runSuite.globalFailureCount++; - config.stats.bad++; - config.stats.all++; - emit( "error", error ); - } + // We could let callers specify an offset to subtract a number of frames via + // sourceFromStacktrace, in case they are a wrapper further away from the error + // handler, and thus reduce some noise in the stack trace. However, we're not + // doing this right now because it would almost never be used in practice given + // the vast majority of error values will be Error objects, and thus have their + // own stack trace already. + source: (error && error.stack) || sourceFromStacktrace(2) + }); + } else { + // The "error" event was added in QUnit 2.17. + // Increase "bad assertion" stats despite no longer pushing an assertion in this case. + // This ensures "runEnd" and "QUnit.done()" handlers behave as expected, since the "bad" + // count is typically how reporters decide on the boolean outcome of the test run. + runSuite.globalFailureCount++; + config.stats.bad++; + config.stats.all++; + emit('error', error); + } } diff --git a/src/core/onerror.js b/src/core/onerror.js index e71569567..131ff7b4e 100644 --- a/src/core/onerror.js +++ b/src/core/onerror.js @@ -1,6 +1,6 @@ -import Logger from "../logger"; -import config from "./config"; -import onUncaughtException from "./on-uncaught-exception"; +import Logger from '../logger'; +import config from './config'; +import onUncaughtException from './on-uncaught-exception'; /** * Handle a window.onerror error. @@ -22,17 +22,17 @@ import onUncaughtException from "./on-uncaught-exception"; * @param {string|undefined} [details.stacktrace] * @return {bool} True if native error reporting should be suppressed. */ -export default function onWindowError( details ) { - Logger.warn( "QUnit.onError is deprecated and will be removed in QUnit 3.0." + - " Please use QUnit.onUncaughtException instead." ); +export default function onWindowError (details) { + Logger.warn('QUnit.onError is deprecated and will be removed in QUnit 3.0.' + + ' Please use QUnit.onUncaughtException instead.'); - if ( config.current && config.current.ignoreGlobalErrors ) { - return true; - } + if (config.current && config.current.ignoreGlobalErrors) { + return true; + } - const err = new Error( details.message ); - err.stack = details.stacktrace || details.fileName + ":" + details.lineNumber; - onUncaughtException( err ); + const err = new Error(details.message); + err.stack = details.stacktrace || details.fileName + ':' + details.lineNumber; + onUncaughtException(err); - return false; + return false; } diff --git a/src/core/processing-queue.js b/src/core/processing-queue.js index 2b49b06d2..0c133335c 100644 --- a/src/core/processing-queue.js +++ b/src/core/processing-queue.js @@ -1,12 +1,12 @@ -import config from "./config"; -import { extend, generateHash, now } from "./utilities"; -import { runLoggingCallbacks } from "./logging"; +import config from './config'; +import { extend, generateHash, now } from './utilities'; +import { runLoggingCallbacks } from './logging'; -import Promise from "../promise"; -import { test } from "../test"; -import { runSuite } from "../module"; -import { emit } from "../events"; -import { setTimeout } from "../globals"; +import Promise from '../promise'; +import { test } from '../test'; +import { runSuite } from '../module'; +import { emit } from '../events'; +import { setTimeout } from '../globals'; let priorityCount = 0; let unitSampler; @@ -20,82 +20,82 @@ const taskQueue = []; * Advances the taskQueue to the next task. If the taskQueue is empty, * process the testQueue */ -function advance() { - advanceTaskQueue(); +function advance () { + advanceTaskQueue(); - if ( !taskQueue.length && !config.blocking && !config.current ) { - advanceTestQueue(); - } + if (!taskQueue.length && !config.blocking && !config.current) { + advanceTestQueue(); + } } /** * Advances the taskQueue with an increased depth */ -function advanceTaskQueue() { - const start = now(); - config.depth = ( config.depth || 0 ) + 1; +function advanceTaskQueue () { + const start = now(); + config.depth = (config.depth || 0) + 1; - processTaskQueue( start ); + processTaskQueue(start); - config.depth--; + config.depth--; } /** * Process the first task on the taskQueue as a promise. * Each task is a function added by Test#queue() in /src/test.js */ -function processTaskQueue( start ) { - if ( taskQueue.length && !config.blocking ) { - const elapsedTime = now() - start; - - if ( !setTimeout || config.updateRate <= 0 || elapsedTime < config.updateRate ) { - const task = taskQueue.shift(); - Promise.resolve( task() ).then( function() { - if ( !taskQueue.length ) { - advance(); - } else { - processTaskQueue( start ); - } - } ); - } else { - setTimeout( advance ); - } - } +function processTaskQueue (start) { + if (taskQueue.length && !config.blocking) { + const elapsedTime = now() - start; + + if (!setTimeout || config.updateRate <= 0 || elapsedTime < config.updateRate) { + const task = taskQueue.shift(); + Promise.resolve(task()).then(function () { + if (!taskQueue.length) { + advance(); + } else { + processTaskQueue(start); + } + }); + } else { + setTimeout(advance); + } + } } /** * Advance the testQueue to the next test to process. Call done() if testQueue completes. */ -function advanceTestQueue() { - if ( !config.blocking && !config.queue.length && config.depth === 0 ) { - done(); - return; - } +function advanceTestQueue () { + if (!config.blocking && !config.queue.length && config.depth === 0) { + done(); + return; + } - const testTasks = config.queue.shift(); - addToTaskQueue( testTasks() ); + const testTasks = config.queue.shift(); + addToTaskQueue(testTasks()); - if ( priorityCount > 0 ) { - priorityCount--; - } + if (priorityCount > 0) { + priorityCount--; + } - advance(); + advance(); } /** * Enqueue the tasks for a test into the task queue. * @param {Array} tasksArray */ -function addToTaskQueue( tasksArray ) { - taskQueue.push( ...tasksArray ); +function addToTaskQueue (tasksArray) { + taskQueue.push(...tasksArray); } /** * Return the number of tasks remaining in the task queue to be processed. * @return {number} */ -function taskQueueLength() { - return taskQueue.length; +function taskQueueLength () { + return taskQueue.length; } /** @@ -104,115 +104,112 @@ function taskQueueLength() { * @param {boolean} prioritize * @param {string} seed */ -function addToTestQueue( testTasksFunc, prioritize, seed ) { - if ( prioritize ) { - config.queue.splice( priorityCount++, 0, testTasksFunc ); - } else if ( seed ) { - if ( !unitSampler ) { - unitSampler = unitSamplerGenerator( seed ); - } - - // Insert into a random position after all prioritized items - const index = Math.floor( unitSampler() * ( config.queue.length - priorityCount + 1 ) ); - config.queue.splice( priorityCount + index, 0, testTasksFunc ); - } else { - config.queue.push( testTasksFunc ); - } +function addToTestQueue (testTasksFunc, prioritize, seed) { + if (prioritize) { + config.queue.splice(priorityCount++, 0, testTasksFunc); + } else if (seed) { + if (!unitSampler) { + unitSampler = unitSamplerGenerator(seed); + } + + // Insert into a random position after all prioritized items + const index = Math.floor(unitSampler() * (config.queue.length - priorityCount + 1)); + config.queue.splice(priorityCount + index, 0, testTasksFunc); + } else { + config.queue.push(testTasksFunc); + } } /** * Creates a seeded "sample" generator which is used for randomizing tests. */ -function unitSamplerGenerator( seed ) { - - // 32-bit xorshift, requires only a nonzero seed - // https://excamera.com/sphinx/article-xorshift.html - let sample = parseInt( generateHash( seed ), 16 ) || -1; - return function() { - sample ^= sample << 13; - sample ^= sample >>> 17; - sample ^= sample << 5; - - // ECMAScript has no unsigned number type - if ( sample < 0 ) { - sample += 0x100000000; - } - - return sample / 0x100000000; - }; +function unitSamplerGenerator (seed) { + // 32-bit xorshift, requires only a nonzero seed + // https://excamera.com/sphinx/article-xorshift.html + let sample = parseInt(generateHash(seed), 16) || -1; + return function () { + sample ^= sample << 13; + sample ^= sample >>> 17; + sample ^= sample << 5; + + // ECMAScript has no unsigned number type + if (sample < 0) { + sample += 0x100000000; + } + + return sample / 0x100000000; + }; } /** * This function is called when the ProcessingQueue is done processing all * items. It handles emitting the final run events. */ -function done() { - - // We have reached the end of the processing queue and are about to emit the - // "runEnd" event after which reporters typically stop listening and exit - // the process. First, check if we need to emit one final test. - if ( config.stats.testCount === 0 && config.failOnZeroTests === true ) { - let error; - if ( config.filter && config.filter.length ) { - error = new Error( `No tests matched the filter "${config.filter}".` ); - } else if ( config.module && config.module.length ) { - error = new Error( `No tests matched the module "${config.module}".` ); - } else if ( config.moduleId && config.moduleId.length ) { - error = new Error( `No tests matched the moduleId "${config.moduleId}".` ); - } else if ( config.testId && config.testId.length ) { - error = new Error( `No tests matched the testId "${config.testId}".` ); - } else { - error = new Error( "No tests were run." ); - } - - test( "global failure", extend( function( assert ) { - assert.pushResult( { - result: false, - message: error.message, - source: error.stack - } ); - }, { validTest: true } ) ); - - // We do need to call `advance()` in order to resume the processing queue. - // Once this new test is finished processing, we'll reach `done` again, and - // that time the above condition will evaluate to false. - advance(); - return; - } - - const storage = config.storage; - - const runtime = now() - config.started; - const passed = config.stats.all - config.stats.bad; - - ProcessingQueue.finished = true; - - emit( "runEnd", runSuite.end( true ) ); - runLoggingCallbacks( "done", { - passed, - failed: config.stats.bad, - total: config.stats.all, - runtime - } ).then( () => { - - // Clear own storage items if all tests passed - if ( storage && config.stats.bad === 0 ) { - for ( let i = storage.length - 1; i >= 0; i-- ) { - const key = storage.key( i ); - - if ( key.indexOf( "qunit-test-" ) === 0 ) { - storage.removeItem( key ); - } - } - } - } ); +function done () { + // We have reached the end of the processing queue and are about to emit the + // "runEnd" event after which reporters typically stop listening and exit + // the process. First, check if we need to emit one final test. + if (config.stats.testCount === 0 && config.failOnZeroTests === true) { + let error; + if (config.filter && config.filter.length) { + error = new Error(`No tests matched the filter "${config.filter}".`); + } else if (config.module && config.module.length) { + error = new Error(`No tests matched the module "${config.module}".`); + } else if (config.moduleId && config.moduleId.length) { + error = new Error(`No tests matched the moduleId "${config.moduleId}".`); + } else if (config.testId && config.testId.length) { + error = new Error(`No tests matched the testId "${config.testId}".`); + } else { + error = new Error('No tests were run.'); + } + + test('global failure', extend(function (assert) { + assert.pushResult({ + result: false, + message: error.message, + source: error.stack + }); + }, { validTest: true })); + + // We do need to call `advance()` in order to resume the processing queue. + // Once this new test is finished processing, we'll reach `done` again, and + // that time the above condition will evaluate to false. + advance(); + return; + } + + const storage = config.storage; + + const runtime = now() - config.started; + const passed = config.stats.all - config.stats.bad; + + ProcessingQueue.finished = true; + + emit('runEnd', runSuite.end(true)); + runLoggingCallbacks('done', { + passed, + failed: config.stats.bad, + total: config.stats.all, + runtime + }).then(() => { + // Clear own storage items if all tests passed + if (storage && config.stats.bad === 0) { + for (let i = storage.length - 1; i >= 0; i--) { + const key = storage.key(i); + + if (key.indexOf('qunit-test-') === 0) { + storage.removeItem(key); + } + } + } + }); } const ProcessingQueue = { - finished: false, - add: addToTestQueue, - advance, - taskCount: taskQueueLength + finished: false, + add: addToTestQueue, + advance, + taskCount: taskQueueLength }; export default ProcessingQueue; diff --git a/src/core/stacktrace.js b/src/core/stacktrace.js index c8e42eab7..8bef672b3 100644 --- a/src/core/stacktrace.js +++ b/src/core/stacktrace.js @@ -1,45 +1,45 @@ // Doesn't support IE9, it will return undefined on these browsers // See also https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error/Stack -const fileName = ( sourceFromStacktrace( 0 ) || "" ) - .replace( /(:\d+)+\)?/, "" ) - .replace( /.+\//, "" ); +const fileName = (sourceFromStacktrace(0) || '') + .replace(/(:\d+)+\)?/, '') + .replace(/.+\//, ''); -export function extractStacktrace( e, offset ) { - offset = offset === undefined ? 4 : offset; +export function extractStacktrace (e, offset) { + offset = offset === undefined ? 4 : offset; - if ( e && e.stack ) { - const stack = e.stack.split( "\n" ); - if ( /^error$/i.test( stack[ 0 ] ) ) { - stack.shift(); - } - if ( fileName ) { - const include = []; - for ( let i = offset; i < stack.length; i++ ) { - if ( stack[ i ].indexOf( fileName ) !== -1 ) { - break; - } - include.push( stack[ i ] ); - } - if ( include.length ) { - return include.join( "\n" ); - } - } - return stack[ offset ]; - } + if (e && e.stack) { + const stack = e.stack.split('\n'); + if (/^error$/i.test(stack[0])) { + stack.shift(); + } + if (fileName) { + const include = []; + for (let i = offset; i < stack.length; i++) { + if (stack[i].indexOf(fileName) !== -1) { + break; + } + include.push(stack[i]); + } + if (include.length) { + return include.join('\n'); + } + } + return stack[offset]; + } } -export function sourceFromStacktrace( offset ) { - let error = new Error(); +export function sourceFromStacktrace (offset) { + let error = new Error(); - // Support: Safari <=7 only, IE <=10 - 11 only - // Not all browsers generate the `stack` property for `new Error()`, see also #636 - if ( !error.stack ) { - try { - throw error; - } catch ( err ) { - error = err; - } - } + // Support: Safari <=7 only, IE <=10 - 11 only + // Not all browsers generate the `stack` property for `new Error()`, see also #636 + if (!error.stack) { + try { + throw error; + } catch (err) { + error = err; + } + } - return extractStacktrace( error, offset ); + return extractStacktrace(error, offset); } diff --git a/src/core/utilities.js b/src/core/utilities.js index b10399d45..3c8743776 100644 --- a/src/core/utilities.js +++ b/src/core/utilities.js @@ -1,57 +1,58 @@ -import { window } from "../globals"; -import Logger from "../logger"; +import { window } from '../globals'; +import Logger from '../logger'; export const toString = Object.prototype.toString; export const hasOwn = Object.prototype.hasOwnProperty; -export const now = Date.now || function() { - return new Date().getTime(); +export const now = Date.now || function () { + return new Date().getTime(); }; const nativePerf = getNativePerf(); -function getNativePerf() { - if ( window && - typeof window.performance !== "undefined" && - typeof window.performance.mark === "function" && - typeof window.performance.measure === "function" - ) { - return window.performance; - } else { - return undefined; - } +function getNativePerf () { + if (window && + typeof window.performance !== 'undefined' && + typeof window.performance.mark === 'function' && + typeof window.performance.measure === 'function' + ) { + return window.performance; + } else { + return undefined; + } } export const performance = { - now: nativePerf ? - nativePerf.now.bind( nativePerf ) : - now, - measure: nativePerf ? function( comment, startMark, endMark ) { - - // `performance.measure` may fail if the mark could not be found. - // reasons a specific mark could not be found include: outside code invoking `performance.clearMarks()` - try { - nativePerf.measure( comment, startMark, endMark ); - } catch ( ex ) { - Logger.warn( "performance.measure could not be executed because of ", ex.message ); - } - } : function() {}, - mark: nativePerf ? nativePerf.mark.bind( nativePerf ) : function() {} + now: nativePerf + ? nativePerf.now.bind(nativePerf) + : now, + measure: nativePerf + ? function (comment, startMark, endMark) { + // `performance.measure` may fail if the mark could not be found. + // reasons a specific mark could not be found include: outside code invoking `performance.clearMarks()` + try { + nativePerf.measure(comment, startMark, endMark); + } catch (ex) { + Logger.warn('performance.measure could not be executed because of ', ex.message); + } + } + : function () {}, + mark: nativePerf ? nativePerf.mark.bind(nativePerf) : function () {} }; // Returns a new Array with the elements that are in a but not in b -export function diff( a, b ) { - const result = a.slice(); - - for ( let i = 0; i < result.length; i++ ) { - for ( let j = 0; j < b.length; j++ ) { - if ( result[ i ] === b[ j ] ) { - result.splice( i, 1 ); - i--; - break; - } - } - } - return result; +export function diff (a, b) { + const result = a.slice(); + + for (let i = 0; i < result.length; i++) { + for (let j = 0; j < b.length; j++) { + if (result[i] === b[j]) { + result.splice(i, 1); + i--; + break; + } + } + } + return result; } /** @@ -62,8 +63,8 @@ export function diff( a, b ) { * @param {Array} array * @return {boolean} */ -export function inArray( elem, array ) { - return array.indexOf( elem ) !== -1; +export function inArray (elem, array) { + return array.indexOf(elem) !== -1; } /** @@ -74,15 +75,15 @@ export function inArray( elem, array ) { * @param {bool} [allowArray=true] * @return {Object|Array} */ -export function objectValues( obj, allowArray = true ) { - const vals = ( allowArray && is( "array", obj ) ) ? [] : {}; - for ( const key in obj ) { - if ( hasOwn.call( obj, key ) ) { - const val = obj[ key ]; - vals[ key ] = val === Object( val ) ? objectValues( val, allowArray ) : val; - } - } - return vals; +export function objectValues (obj, allowArray = true) { + const vals = (allowArray && is('array', obj)) ? [] : {}; + for (const key in obj) { + if (hasOwn.call(obj, key)) { + const val = obj[key]; + vals[key] = val === Object(val) ? objectValues(val, allowArray) : val; + } + } + return vals; } /** @@ -93,100 +94,99 @@ export function objectValues( obj, allowArray = true ) { * @param {any} model * @return {Object} */ -export function objectValuesSubset( obj, model ) { - - // Return primitive values unchanged to avoid false positives or confusing - // results from assert.propContains(). - // E.g. an actual null or false wrongly equaling an empty object, - // or an actual string being reported as object not matching a partial object. - if ( obj !== Object( obj ) ) { - return obj; - } - - // Unlike objectValues(), subset arrays to a plain objects as well. - // This enables subsetting [20, 30] with {1: 30}. - const subset = {}; - - for ( const key in model ) { - if ( hasOwn.call( model, key ) && hasOwn.call( obj, key ) ) { - subset[ key ] = objectValuesSubset( obj[ key ], model[ key ] ); - } - } - return subset; +export function objectValuesSubset (obj, model) { + // Return primitive values unchanged to avoid false positives or confusing + // results from assert.propContains(). + // E.g. an actual null or false wrongly equaling an empty object, + // or an actual string being reported as object not matching a partial object. + if (obj !== Object(obj)) { + return obj; + } + + // Unlike objectValues(), subset arrays to a plain objects as well. + // This enables subsetting [20, 30] with {1: 30}. + const subset = {}; + + for (const key in model) { + if (hasOwn.call(model, key) && hasOwn.call(obj, key)) { + subset[key] = objectValuesSubset(obj[key], model[key]); + } + } + return subset; } -export function extend( a, b, undefOnly ) { - for ( const prop in b ) { - if ( hasOwn.call( b, prop ) ) { - if ( b[ prop ] === undefined ) { - delete a[ prop ]; - } else if ( !( undefOnly && typeof a[ prop ] !== "undefined" ) ) { - a[ prop ] = b[ prop ]; - } - } - } - - return a; +export function extend (a, b, undefOnly) { + for (const prop in b) { + if (hasOwn.call(b, prop)) { + if (b[prop] === undefined) { + delete a[prop]; + } else if (!(undefOnly && typeof a[prop] !== 'undefined')) { + a[prop] = b[prop]; + } + } + } + + return a; } -export function objectType( obj ) { - if ( typeof obj === "undefined" ) { - return "undefined"; - } - - // Consider: typeof null === object - if ( obj === null ) { - return "null"; - } - - const match = toString.call( obj ).match( /^\[object\s(.*)\]$/ ); - const type = match && match[ 1 ]; - - switch ( type ) { - case "Number": - if ( isNaN( obj ) ) { - return "nan"; - } - return "number"; - case "String": - case "Boolean": - case "Array": - case "Set": - case "Map": - case "Date": - case "RegExp": - case "Function": - case "Symbol": - return type.toLowerCase(); - default: - return typeof obj; - } +export function objectType (obj) { + if (typeof obj === 'undefined') { + return 'undefined'; + } + + // Consider: typeof null === object + if (obj === null) { + return 'null'; + } + + const match = toString.call(obj).match(/^\[object\s(.*)\]$/); + const type = match && match[1]; + + switch (type) { + case 'Number': + if (isNaN(obj)) { + return 'nan'; + } + return 'number'; + case 'String': + case 'Boolean': + case 'Array': + case 'Set': + case 'Map': + case 'Date': + case 'RegExp': + case 'Function': + case 'Symbol': + return type.toLowerCase(); + default: + return typeof obj; + } } // Safe object type checking -export function is( type, obj ) { - return objectType( obj ) === type; +export function is (type, obj) { + return objectType(obj) === type; } // Based on Java's String.hashCode, a simple but not // rigorously collision resistant hashing function -export function generateHash( module, testName ) { - const str = module + "\x1C" + testName; - let hash = 0; - - for ( let i = 0; i < str.length; i++ ) { - hash = ( ( hash << 5 ) - hash ) + str.charCodeAt( i ); - hash |= 0; - } - - // Convert the possibly negative integer hash code into an 8 character hex string, which isn't - // strictly necessary but increases user understanding that the id is a SHA-like hash - let hex = ( 0x100000000 + hash ).toString( 16 ); - if ( hex.length < 8 ) { - hex = "0000000" + hex; - } - - return hex.slice( -8 ); +export function generateHash (module, testName) { + const str = module + '\x1C' + testName; + let hash = 0; + + for (let i = 0; i < str.length; i++) { + hash = ((hash << 5) - hash) + str.charCodeAt(i); + hash |= 0; + } + + // Convert the possibly negative integer hash code into an 8 character hex string, which isn't + // strictly necessary but increases user understanding that the id is a SHA-like hash + let hex = (0x100000000 + hash).toString(16); + if (hex.length < 8) { + hex = '0000000' + hex; + } + + return hex.slice(-8); } /** @@ -195,18 +195,16 @@ export function generateHash( module, testName ) { * @param {Error|any} error * @return {string} */ -export function errorString( error ) { - - // Use String() instead of toString() to handle non-object values like undefined or null. - const resultErrorString = String( error ); - - // If the error wasn't a subclass of Error but something like - // an object literal with name and message properties... - if ( resultErrorString.slice( 0, 7 ) === "[object" ) { - - // Based on https://es5.github.io/#x15.11.4.4 - return ( error.name || "Error" ) + ( error.message ? `: ${error.message}` : "" ); - } else { - return resultErrorString; - } +export function errorString (error) { + // Use String() instead of toString() to handle non-object values like undefined or null. + const resultErrorString = String(error); + + // If the error wasn't a subclass of Error but something like + // an object literal with name and message properties... + if (resultErrorString.slice(0, 7) === '[object') { + // Based on https://es5.github.io/#x15.11.4.4 + return (error.name || 'Error') + (error.message ? `: ${error.message}` : ''); + } else { + return resultErrorString; + } } diff --git a/src/dump.js b/src/dump.js index 7192bee51..894ce45a8 100644 --- a/src/dump.js +++ b/src/dump.js @@ -28,289 +28,287 @@ // POSSIBILITY OF SUCH DAMAGE. // ------- -import config from "./core/config"; -import { inArray, toString, is } from "./core/utilities"; - -export default ( function() { - function quote( str ) { - return "\"" + str.toString().replace( /\\/g, "\\\\" ).replace( /"/g, "\\\"" ) + "\""; - } - function literal( o ) { - return o + ""; - } - function join( pre, arr, post ) { - const s = dump.separator(); - const inner = dump.indent( 1 ); - if ( arr.join ) { - arr = arr.join( "," + s + inner ); - } - if ( !arr ) { - return pre + post; - } - const base = dump.indent(); - return [ pre, inner + arr, base + post ].join( s ); - } - function array( arr, stack ) { - - if ( dump.maxDepth && dump.depth > dump.maxDepth ) { - return "[object Array]"; - } - - this.up(); - - let i = arr.length; - const ret = new Array( i ); - while ( i-- ) { - ret[ i ] = this.parse( arr[ i ], undefined, stack ); - } - this.down(); - return join( "[", ret, "]" ); - } - - function isArray( obj ) { - return ( - - //Native Arrays - toString.call( obj ) === "[object Array]" || - - // NodeList objects - ( typeof obj.length === "number" && obj.item !== undefined ) && - ( obj.length ? - obj.item( 0 ) === obj[ 0 ] : - ( obj.item( 0 ) === null && obj[ 0 ] === undefined ) - ) - ); - } - - const reName = /^function (\w+)/; - const dump = { - - // The objType is used mostly internally, you can fix a (custom) type in advance - parse: function( obj, objType, stack ) { - stack = stack || []; - const objIndex = stack.indexOf( obj ); - - if ( objIndex !== -1 ) { - return `recursion(${objIndex - stack.length})`; - } - - objType = objType || this.typeOf( obj ); - const parser = this.parsers[ objType ]; - const parserType = typeof parser; - - if ( parserType === "function" ) { - stack.push( obj ); - const res = parser.call( this, obj, stack ); - stack.pop(); - return res; - } - if ( parserType === "string" ) { - return parser; - } - return "[ERROR: Missing QUnit.dump formatter for type " + objType + "]"; - }, - typeOf: function( obj ) { - let type; - - if ( obj === null ) { - type = "null"; - } else if ( typeof obj === "undefined" ) { - type = "undefined"; - } else if ( is( "regexp", obj ) ) { - type = "regexp"; - } else if ( is( "date", obj ) ) { - type = "date"; - } else if ( is( "function", obj ) ) { - type = "function"; - } else if ( obj.setInterval !== undefined && - obj.document !== undefined && - obj.nodeType === undefined ) { - type = "window"; - } else if ( obj.nodeType === 9 ) { - type = "document"; - } else if ( obj.nodeType ) { - type = "node"; - } else if ( isArray( obj ) ) { - type = "array"; - } else if ( obj.constructor === Error.prototype.constructor ) { - type = "error"; - } else { - type = typeof obj; - } - return type; - }, - - separator: function() { - if ( this.multiline ) { - return this.HTML ? "
" : "\n"; - } else { - return this.HTML ? " " : " "; - } - }, - - // Extra can be a number, shortcut for increasing-calling-decreasing - indent: function( extra ) { - if ( !this.multiline ) { - return ""; - } - let chr = this.indentChar; - if ( this.HTML ) { - chr = chr.replace( /\t/g, " " ).replace( / /g, " " ); - } - return new Array( this.depth + ( extra || 0 ) ).join( chr ); - }, - up: function( a ) { - this.depth += a || 1; - }, - down: function( a ) { - this.depth -= a || 1; - }, - setParser: function( name, parser ) { - this.parsers[ name ] = parser; - }, - - // The next 3 are exposed so you can use them - quote: quote, - literal: literal, - join: join, - depth: 1, - maxDepth: config.maxDepth, - - // This is the list of parsers, to modify them, use dump.setParser - parsers: { - window: "[Window]", - document: "[Document]", - error: function( error ) { - return "Error(\"" + error.message + "\")"; - }, - - // This has been unused since QUnit 1.0.0. - // @todo Deprecate and remove. - unknown: "[Unknown]", - "null": "null", - "undefined": "undefined", - "function": function( fn ) { - let ret = "function"; - - // Functions never have name in IE - const name = "name" in fn ? fn.name : ( reName.exec( fn ) || [] )[ 1 ]; - - if ( name ) { - ret += " " + name; - } - ret += "("; - - ret = [ ret, dump.parse( fn, "functionArgs" ), "){" ].join( "" ); - return join( ret, dump.parse( fn, "functionCode" ), "}" ); - }, - array: array, - nodelist: array, - "arguments": array, - object: function( map, stack ) { - const ret = []; - - if ( dump.maxDepth && dump.depth > dump.maxDepth ) { - return "[object Object]"; - } - - dump.up(); - const keys = []; - for ( const key in map ) { - keys.push( key ); - } - - // Some properties are not always enumerable on Error objects. - const nonEnumerableProperties = [ "message", "name" ]; - for ( const i in nonEnumerableProperties ) { - const key = nonEnumerableProperties[ i ]; - if ( key in map && !inArray( key, keys ) ) { - keys.push( key ); - } - } - keys.sort(); - for ( let i = 0; i < keys.length; i++ ) { - const key = keys[ i ]; - const val = map[ key ]; - ret.push( dump.parse( key, "key" ) + ": " + - dump.parse( val, undefined, stack ) ); - } - dump.down(); - return join( "{", ret, "}" ); - }, - node: function( node ) { - const open = dump.HTML ? "<" : "<"; - const close = dump.HTML ? ">" : ">"; - const tag = node.nodeName.toLowerCase(); - let ret = open + tag; - const attrs = node.attributes; - - if ( attrs ) { - for ( let i = 0, len = attrs.length; i < len; i++ ) { - const val = attrs[ i ].nodeValue; - - // IE6 includes all attributes in .attributes, even ones not explicitly - // set. Those have values like undefined, null, 0, false, "" or - // "inherit". - if ( val && val !== "inherit" ) { - ret += " " + attrs[ i ].nodeName + "=" + - dump.parse( val, "attribute" ); - } - } - } - ret += close; - - // Show content of TextNode or CDATASection - if ( node.nodeType === 3 || node.nodeType === 4 ) { - ret += node.nodeValue; - } - - return ret + open + "/" + tag + close; - }, - - // Function calls it internally, it's the arguments part of the function - functionArgs: function( fn ) { - let l = fn.length; - - if ( !l ) { - return ""; - } - - const args = new Array( l ); - while ( l-- ) { - - // 97 is 'a' - args[ l ] = String.fromCharCode( 97 + l ); - } - return " " + args.join( ", " ) + " "; - }, - - // Object calls it internally, the key part of an item in a map - key: quote, - - // Function calls it internally, it's the content of the function - functionCode: "[code]", - - // Node calls it internally, it's a html attribute value - attribute: quote, - string: quote, - date: quote, - regexp: literal, - number: literal, - "boolean": literal, - symbol: function( sym ) { - return sym.toString(); - } - }, - - // If true, entities are escaped ( <, >, \t, space and \n ) - HTML: false, - - // Indentation unit - indentChar: " ", - - // If true, items in a collection, are separated by a \n, else just a space. - multiline: true - }; - - return dump; -} )(); +import config from './core/config'; +import { inArray, toString, is } from './core/utilities'; + +export default (function () { + function quote (str) { + return '"' + str.toString().replace(/\\/g, '\\\\').replace(/"/g, '\\"') + '"'; + } + function literal (o) { + return o + ''; + } + function join (pre, arr, post) { + const s = dump.separator(); + const inner = dump.indent(1); + if (arr.join) { + arr = arr.join(',' + s + inner); + } + if (!arr) { + return pre + post; + } + const base = dump.indent(); + return [pre, inner + arr, base + post].join(s); + } + function array (arr, stack) { + if (dump.maxDepth && dump.depth > dump.maxDepth) { + return '[object Array]'; + } + + this.up(); + + let i = arr.length; + const ret = new Array(i); + while (i--) { + ret[i] = this.parse(arr[i], undefined, stack); + } + this.down(); + return join('[', ret, ']'); + } + + function isArray (obj) { + return ( + // Native Arrays + toString.call(obj) === '[object Array]' || + + // NodeList objects + (typeof obj.length === 'number' && obj.item !== undefined && + (obj.length + ? obj.item(0) === obj[0] + : (obj.item(0) === null && obj[0] === undefined) + ) + ) + ); + } + + const reName = /^function (\w+)/; + const dump = { + + // The objType is used mostly internally, you can fix a (custom) type in advance + parse: function (obj, objType, stack) { + stack = stack || []; + const objIndex = stack.indexOf(obj); + + if (objIndex !== -1) { + return `recursion(${objIndex - stack.length})`; + } + + objType = objType || this.typeOf(obj); + const parser = this.parsers[objType]; + const parserType = typeof parser; + + if (parserType === 'function') { + stack.push(obj); + const res = parser.call(this, obj, stack); + stack.pop(); + return res; + } + if (parserType === 'string') { + return parser; + } + return '[ERROR: Missing QUnit.dump formatter for type ' + objType + ']'; + }, + typeOf: function (obj) { + let type; + + if (obj === null) { + type = 'null'; + } else if (typeof obj === 'undefined') { + type = 'undefined'; + } else if (is('regexp', obj)) { + type = 'regexp'; + } else if (is('date', obj)) { + type = 'date'; + } else if (is('function', obj)) { + type = 'function'; + } else if (obj.setInterval !== undefined && + obj.document !== undefined && + obj.nodeType === undefined) { + type = 'window'; + } else if (obj.nodeType === 9) { + type = 'document'; + } else if (obj.nodeType) { + type = 'node'; + } else if (isArray(obj)) { + type = 'array'; + } else if (obj.constructor === Error.prototype.constructor) { + type = 'error'; + } else { + type = typeof obj; + } + return type; + }, + + separator: function () { + if (this.multiline) { + return this.HTML ? '
' : '\n'; + } else { + return this.HTML ? ' ' : ' '; + } + }, + + // Extra can be a number, shortcut for increasing-calling-decreasing + indent: function (extra) { + if (!this.multiline) { + return ''; + } + let chr = this.indentChar; + if (this.HTML) { + chr = chr.replace(/\t/g, ' ').replace(/ /g, ' '); + } + return new Array(this.depth + (extra || 0)).join(chr); + }, + up: function (a) { + this.depth += a || 1; + }, + down: function (a) { + this.depth -= a || 1; + }, + setParser: function (name, parser) { + this.parsers[name] = parser; + }, + + // The next 3 are exposed so you can use them + quote: quote, + literal: literal, + join: join, + depth: 1, + maxDepth: config.maxDepth, + + // This is the list of parsers, to modify them, use dump.setParser + parsers: { + window: '[Window]', + document: '[Document]', + error: function (error) { + return 'Error("' + error.message + '")'; + }, + + // This has been unused since QUnit 1.0.0. + // @todo Deprecate and remove. + unknown: '[Unknown]', + null: 'null', + undefined: 'undefined', + function: function (fn) { + let ret = 'function'; + + // Functions never have name in IE + const name = 'name' in fn ? fn.name : (reName.exec(fn) || [])[1]; + + if (name) { + ret += ' ' + name; + } + ret += '('; + + ret = [ret, dump.parse(fn, 'functionArgs'), '){'].join(''); + return join(ret, dump.parse(fn, 'functionCode'), '}'); + }, + array: array, + nodelist: array, + arguments: array, + object: function (map, stack) { + const ret = []; + + if (dump.maxDepth && dump.depth > dump.maxDepth) { + return '[object Object]'; + } + + dump.up(); + const keys = []; + for (const key in map) { + keys.push(key); + } + + // Some properties are not always enumerable on Error objects. + const nonEnumerableProperties = ['message', 'name']; + for (const i in nonEnumerableProperties) { + const key = nonEnumerableProperties[i]; + if (key in map && !inArray(key, keys)) { + keys.push(key); + } + } + keys.sort(); + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; + const val = map[key]; + ret.push(dump.parse(key, 'key') + ': ' + + dump.parse(val, undefined, stack)); + } + dump.down(); + return join('{', ret, '}'); + }, + node: function (node) { + const open = dump.HTML ? '<' : '<'; + const close = dump.HTML ? '>' : '>'; + const tag = node.nodeName.toLowerCase(); + let ret = open + tag; + const attrs = node.attributes; + + if (attrs) { + for (let i = 0, len = attrs.length; i < len; i++) { + const val = attrs[i].nodeValue; + + // IE6 includes all attributes in .attributes, even ones not explicitly + // set. Those have values like undefined, null, 0, false, "" or + // "inherit". + if (val && val !== 'inherit') { + ret += ' ' + attrs[i].nodeName + '=' + + dump.parse(val, 'attribute'); + } + } + } + ret += close; + + // Show content of TextNode or CDATASection + if (node.nodeType === 3 || node.nodeType === 4) { + ret += node.nodeValue; + } + + return ret + open + '/' + tag + close; + }, + + // Function calls it internally, it's the arguments part of the function + functionArgs: function (fn) { + let l = fn.length; + + if (!l) { + return ''; + } + + const args = new Array(l); + while (l--) { + // 97 is 'a' + args[l] = String.fromCharCode(97 + l); + } + return ' ' + args.join(', ') + ' '; + }, + + // Object calls it internally, the key part of an item in a map + key: quote, + + // Function calls it internally, it's the content of the function + functionCode: '[code]', + + // Node calls it internally, it's a html attribute value + attribute: quote, + string: quote, + date: quote, + regexp: literal, + number: literal, + boolean: literal, + symbol: function (sym) { + return sym.toString(); + } + }, + + // If true, entities are escaped ( <, >, \t, space and \n ) + HTML: false, + + // Indentation unit + indentChar: ' ', + + // If true, items in a collection, are separated by a \n, else just a space. + multiline: true + }; + + return dump; +})(); diff --git a/src/equiv.js b/src/equiv.js index 61caf56aa..ccfd99d0f 100644 --- a/src/equiv.js +++ b/src/equiv.js @@ -1,342 +1,325 @@ -import { objectType } from "./core/utilities"; +import { objectType } from './core/utilities'; // Test for equality any JavaScript type. // Authors: Philippe Rathé , David Chan -export default ( function() { - - // Value pairs queued for comparison. Used for breadth-first processing order, recursion - // detection and avoiding repeated comparison (see below for details). - // Elements are { a: val, b: val }. - let pairs = []; - - const getProto = Object.getPrototypeOf || function( obj ) { - return obj.__proto__; - }; - - function useStrictEquality( a, b ) { - - // This only gets called if a and b are not strict equal, and is used to compare on - // the primitive values inside object wrappers. For example: - // `var i = 1;` - // `var j = new Number(1);` - // Neither a nor b can be null, as a !== b and they have the same type. - if ( typeof a === "object" ) { - a = a.valueOf(); - } - if ( typeof b === "object" ) { - b = b.valueOf(); - } - - return a === b; - } - - function compareConstructors( a, b ) { - let protoA = getProto( a ); - let protoB = getProto( b ); - - // Comparing constructors is more strict than using `instanceof` - if ( a.constructor === b.constructor ) { - return true; - } - - // Ref #851 - // If the obj prototype descends from a null constructor, treat it - // as a null prototype. - if ( protoA && protoA.constructor === null ) { - protoA = null; - } - if ( protoB && protoB.constructor === null ) { - protoB = null; - } - - // Allow objects with no prototype to be equivalent to - // objects with Object as their constructor. - if ( ( protoA === null && protoB === Object.prototype ) || - ( protoB === null && protoA === Object.prototype ) ) { - return true; - } - - return false; - } - - function getRegExpFlags( regexp ) { - return "flags" in regexp ? regexp.flags : regexp.toString().match( /[gimuy]*$/ )[ 0 ]; - } - - function isContainer( val ) { - return [ "object", "array", "map", "set" ].indexOf( objectType( val ) ) !== -1; - } - - function breadthFirstCompareChild( a, b ) { - - // If a is a container not reference-equal to b, postpone the comparison to the - // end of the pairs queue -- unless (a, b) has been seen before, in which case skip - // over the pair. - if ( a === b ) { - return true; - } - if ( !isContainer( a ) ) { - return typeEquiv( a, b ); - } - if ( pairs.every( function( pair ) { - return pair.a !== a || pair.b !== b; - } ) ) { - - // Not yet started comparing this pair - pairs.push( { a: a, b: b } ); - } - return true; - } - - const callbacks = { - "string": useStrictEquality, - "boolean": useStrictEquality, - "number": useStrictEquality, - "null": useStrictEquality, - "undefined": useStrictEquality, - "symbol": useStrictEquality, - "date": useStrictEquality, - - "nan": function() { - return true; - }, - - "regexp": function( a, b ) { - return a.source === b.source && - - // Include flags in the comparison - getRegExpFlags( a ) === getRegExpFlags( b ); - }, - - // abort (identical references / instance methods were skipped earlier) - "function": function() { - return false; - }, - - "array": function( a, b ) { - - const len = a.length; - if ( len !== b.length ) { - - // Safe and faster - return false; - } - - - for ( let i = 0; i < len; i++ ) { - - // Compare non-containers; queue non-reference-equal containers - if ( !breadthFirstCompareChild( a[ i ], b[ i ] ) ) { - return false; - } - } - return true; - }, - - // Define sets a and b to be equivalent if for each element aVal in a, there - // is some element bVal in b such that aVal and bVal are equivalent. Element - // repetitions are not counted, so these are equivalent: - // a = new Set( [ {}, [], [] ] ); - // b = new Set( [ {}, {}, [] ] ); - "set": function( a, b ) { - - if ( a.size !== b.size ) { - - // This optimization has certain quirks because of the lack of - // repetition counting. For instance, adding the same - // (reference-identical) element to two equivalent sets can - // make them non-equivalent. - return false; - } - - let outerEq = true; - - a.forEach( function( aVal ) { - - // Short-circuit if the result is already known. (Using for...of - // with a break clause would be cleaner here, but it would cause - // a syntax error on older JavaScript implementations even if - // Set is unused) - if ( !outerEq ) { - return; - } - - let innerEq = false; - - b.forEach( function( bVal ) { - - // Likewise, short-circuit if the result is already known - if ( innerEq ) { - return; - } - - // Swap out the global pairs list, as the nested call to - // innerEquiv will clobber its contents - const parentPairs = pairs; - if ( innerEquiv( bVal, aVal ) ) { - innerEq = true; - } - - // Replace the global pairs list - pairs = parentPairs; - } ); - - if ( !innerEq ) { - outerEq = false; - } - } ); - - return outerEq; - }, - - // Define maps a and b to be equivalent if for each key-value pair (aKey, aVal) - // in a, there is some key-value pair (bKey, bVal) in b such that - // [ aKey, aVal ] and [ bKey, bVal ] are equivalent. Key repetitions are not - // counted, so these are equivalent: - // a = new Map( [ [ {}, 1 ], [ {}, 1 ], [ [], 1 ] ] ); - // b = new Map( [ [ {}, 1 ], [ [], 1 ], [ [], 1 ] ] ); - "map": function( a, b ) { - - if ( a.size !== b.size ) { - - // This optimization has certain quirks because of the lack of - // repetition counting. For instance, adding the same - // (reference-identical) key-value pair to two equivalent maps - // can make them non-equivalent. - return false; - } - - let outerEq = true; - - a.forEach( function( aVal, aKey ) { - - // Short-circuit if the result is already known. (Using for...of - // with a break clause would be cleaner here, but it would cause - // a syntax error on older JavaScript implementations even if - // Map is unused) - if ( !outerEq ) { - return; - } - - let innerEq = false; - - b.forEach( function( bVal, bKey ) { - - // Likewise, short-circuit if the result is already known - if ( innerEq ) { - return; - } - - // Swap out the global pairs list, as the nested call to - // innerEquiv will clobber its contents - const parentPairs = pairs; - if ( innerEquiv( [ bVal, bKey ], [ aVal, aKey ] ) ) { - innerEq = true; - } - - // Replace the global pairs list - pairs = parentPairs; - } ); - - if ( !innerEq ) { - outerEq = false; - } - } ); - - return outerEq; - }, - - "object": function( a, b ) { - - if ( compareConstructors( a, b ) === false ) { - return false; - } - - const aProperties = []; - const bProperties = []; - - // Be strict: don't ensure hasOwnProperty and go deep - for ( const i in a ) { - - // Collect a's properties - aProperties.push( i ); - - // Skip OOP methods that look the same - if ( - a.constructor !== Object && - typeof a.constructor !== "undefined" && - typeof a[ i ] === "function" && - typeof b[ i ] === "function" && - a[ i ].toString() === b[ i ].toString() - ) { - continue; - } - - // Compare non-containers; queue non-reference-equal containers - if ( !breadthFirstCompareChild( a[ i ], b[ i ] ) ) { - return false; - } - } - - for ( const i in b ) { - - // Collect b's properties - bProperties.push( i ); - } - - // Ensures identical properties name - return typeEquiv( aProperties.sort(), bProperties.sort() ); - } - }; - - function typeEquiv( a, b ) { - const type = objectType( a ); - - // Callbacks for containers will append to the pairs queue to achieve breadth-first - // search order. The pairs queue is also used to avoid reprocessing any pair of - // containers that are reference-equal to a previously visited pair (a special case - // this being recursion detection). - // - // Because of this approach, once typeEquiv returns a false value, it should not be - // called again without clearing the pair queue else it may wrongly report a visited - // pair as being equivalent. - return objectType( b ) === type && callbacks[ type ]( a, b ); - } - - function innerEquiv( a, b ) { - - // We're done when there's nothing more to compare - if ( arguments.length < 2 ) { - return true; - } - - // Clear the global pair queue and add the top-level values being compared - pairs = [ { a: a, b: b } ]; - - for ( let i = 0; i < pairs.length; i++ ) { - const pair = pairs[ i ]; - - // Perform type-specific comparison on any pairs that are not strictly - // equal. For container types, that comparison will postpone comparison - // of any sub-container pair to the end of the pair queue. This gives - // breadth-first search order. It also avoids the reprocessing of - // reference-equal siblings, cousins etc, which can have a significant speed - // impact when comparing a container of small objects each of which has a - // reference to the same (singleton) large object. - if ( pair.a !== pair.b && !typeEquiv( pair.a, pair.b ) ) { - return false; - } - } - - // ...across all consecutive argument pairs - return arguments.length === 2 || innerEquiv.apply( this, [].slice.call( arguments, 1 ) ); - } - - return ( ...args ) => { - const result = innerEquiv( ...args ); - - // Release any retained objects - pairs.length = 0; - return result; - }; -}() ); +export default (function () { + // Value pairs queued for comparison. Used for breadth-first processing order, recursion + // detection and avoiding repeated comparison (see below for details). + // Elements are { a: val, b: val }. + let pairs = []; + + const getProto = Object.getPrototypeOf || function (obj) { + // eslint-disable-next-line no-proto + return obj.__proto__; + }; + + function useStrictEquality (a, b) { + // This only gets called if a and b are not strict equal, and is used to compare on + // the primitive values inside object wrappers. For example: + // `var i = 1;` + // `var j = new Number(1);` + // Neither a nor b can be null, as a !== b and they have the same type. + if (typeof a === 'object') { + a = a.valueOf(); + } + if (typeof b === 'object') { + b = b.valueOf(); + } + + return a === b; + } + + function compareConstructors (a, b) { + let protoA = getProto(a); + let protoB = getProto(b); + + // Comparing constructors is more strict than using `instanceof` + if (a.constructor === b.constructor) { + return true; + } + + // Ref #851 + // If the obj prototype descends from a null constructor, treat it + // as a null prototype. + if (protoA && protoA.constructor === null) { + protoA = null; + } + if (protoB && protoB.constructor === null) { + protoB = null; + } + + // Allow objects with no prototype to be equivalent to + // objects with Object as their constructor. + if ( + (protoA === null && protoB === Object.prototype) || + (protoB === null && protoA === Object.prototype) + ) { + return true; + } + + return false; + } + + function getRegExpFlags (regexp) { + return 'flags' in regexp ? regexp.flags : regexp.toString().match(/[gimuy]*$/)[0]; + } + + function isContainer (val) { + return ['object', 'array', 'map', 'set'].indexOf(objectType(val)) !== -1; + } + + function breadthFirstCompareChild (a, b) { + // If a is a container not reference-equal to b, postpone the comparison to the + // end of the pairs queue -- unless (a, b) has been seen before, in which case skip + // over the pair. + if (a === b) { + return true; + } + if (!isContainer(a)) { + return typeEquiv(a, b); + } + if (pairs.every(function (pair) { + return pair.a !== a || pair.b !== b; + })) { + // Not yet started comparing this pair + pairs.push({ a: a, b: b }); + } + return true; + } + + const callbacks = { + string: useStrictEquality, + boolean: useStrictEquality, + number: useStrictEquality, + null: useStrictEquality, + undefined: useStrictEquality, + symbol: useStrictEquality, + date: useStrictEquality, + + nan: function () { + return true; + }, + + regexp: function (a, b) { + return a.source === b.source && + + // Include flags in the comparison + getRegExpFlags(a) === getRegExpFlags(b); + }, + + // abort (identical references / instance methods were skipped earlier) + function: function () { + return false; + }, + + array: function (a, b) { + const len = a.length; + if (len !== b.length) { + // Safe and faster + return false; + } + + for (let i = 0; i < len; i++) { + // Compare non-containers; queue non-reference-equal containers + if (!breadthFirstCompareChild(a[i], b[i])) { + return false; + } + } + return true; + }, + + // Define sets a and b to be equivalent if for each element aVal in a, there + // is some element bVal in b such that aVal and bVal are equivalent. Element + // repetitions are not counted, so these are equivalent: + // a = new Set( [ {}, [], [] ] ); + // b = new Set( [ {}, {}, [] ] ); + set: function (a, b) { + if (a.size !== b.size) { + // This optimization has certain quirks because of the lack of + // repetition counting. For instance, adding the same + // (reference-identical) element to two equivalent sets can + // make them non-equivalent. + return false; + } + + let outerEq = true; + + a.forEach(function (aVal) { + // Short-circuit if the result is already known. (Using for...of + // with a break clause would be cleaner here, but it would cause + // a syntax error on older JavaScript implementations even if + // Set is unused) + if (!outerEq) { + return; + } + + let innerEq = false; + + b.forEach(function (bVal) { + // Likewise, short-circuit if the result is already known + if (innerEq) { + return; + } + + // Swap out the global pairs list, as the nested call to + // innerEquiv will clobber its contents + const parentPairs = pairs; + if (innerEquiv(bVal, aVal)) { + innerEq = true; + } + + // Replace the global pairs list + pairs = parentPairs; + }); + + if (!innerEq) { + outerEq = false; + } + }); + + return outerEq; + }, + + // Define maps a and b to be equivalent if for each key-value pair (aKey, aVal) + // in a, there is some key-value pair (bKey, bVal) in b such that + // [ aKey, aVal ] and [ bKey, bVal ] are equivalent. Key repetitions are not + // counted, so these are equivalent: + // a = new Map( [ [ {}, 1 ], [ {}, 1 ], [ [], 1 ] ] ); + // b = new Map( [ [ {}, 1 ], [ [], 1 ], [ [], 1 ] ] ); + map: function (a, b) { + if (a.size !== b.size) { + // This optimization has certain quirks because of the lack of + // repetition counting. For instance, adding the same + // (reference-identical) key-value pair to two equivalent maps + // can make them non-equivalent. + return false; + } + + let outerEq = true; + + a.forEach(function (aVal, aKey) { + // Short-circuit if the result is already known. (Using for...of + // with a break clause would be cleaner here, but it would cause + // a syntax error on older JavaScript implementations even if + // Map is unused) + if (!outerEq) { + return; + } + + let innerEq = false; + + b.forEach(function (bVal, bKey) { + // Likewise, short-circuit if the result is already known + if (innerEq) { + return; + } + + // Swap out the global pairs list, as the nested call to + // innerEquiv will clobber its contents + const parentPairs = pairs; + if (innerEquiv([bVal, bKey], [aVal, aKey])) { + innerEq = true; + } + + // Replace the global pairs list + pairs = parentPairs; + }); + + if (!innerEq) { + outerEq = false; + } + }); + + return outerEq; + }, + + object: function (a, b) { + if (compareConstructors(a, b) === false) { + return false; + } + + const aProperties = []; + const bProperties = []; + + // Be strict: don't ensure hasOwnProperty and go deep + for (const i in a) { + // Collect a's properties + aProperties.push(i); + + // Skip OOP methods that look the same + if ( + a.constructor !== Object && + typeof a.constructor !== 'undefined' && + typeof a[i] === 'function' && + typeof b[i] === 'function' && + a[i].toString() === b[i].toString() + ) { + continue; + } + + // Compare non-containers; queue non-reference-equal containers + if (!breadthFirstCompareChild(a[i], b[i])) { + return false; + } + } + + for (const i in b) { + // Collect b's properties + bProperties.push(i); + } + + // Ensures identical properties name + return typeEquiv(aProperties.sort(), bProperties.sort()); + } + }; + + function typeEquiv (a, b) { + const type = objectType(a); + + // Callbacks for containers will append to the pairs queue to achieve breadth-first + // search order. The pairs queue is also used to avoid reprocessing any pair of + // containers that are reference-equal to a previously visited pair (a special case + // this being recursion detection). + // + // Because of this approach, once typeEquiv returns a false value, it should not be + // called again without clearing the pair queue else it may wrongly report a visited + // pair as being equivalent. + return objectType(b) === type && callbacks[type](a, b); + } + + function innerEquiv (a, b) { + // We're done when there's nothing more to compare + if (arguments.length < 2) { + return true; + } + + // Clear the global pair queue and add the top-level values being compared + pairs = [{ a: a, b: b }]; + + for (let i = 0; i < pairs.length; i++) { + const pair = pairs[i]; + + // Perform type-specific comparison on any pairs that are not strictly + // equal. For container types, that comparison will postpone comparison + // of any sub-container pair to the end of the pair queue. This gives + // breadth-first search order. It also avoids the reprocessing of + // reference-equal siblings, cousins etc, which can have a significant speed + // impact when comparing a container of small objects each of which has a + // reference to the same (singleton) large object. + if (pair.a !== pair.b && !typeEquiv(pair.a, pair.b)) { + return false; + } + } + + // ...across all consecutive argument pairs + return arguments.length === 2 || innerEquiv.apply(this, [].slice.call(arguments, 1)); + } + + return (...args) => { + const result = innerEquiv(...args); + + // Release any retained objects + pairs.length = 0; + return result; + }; +}()); diff --git a/src/events.js b/src/events.js index db039620c..5e8439223 100644 --- a/src/events.js +++ b/src/events.js @@ -1,15 +1,15 @@ -import { objectType, inArray } from "./core/utilities"; +import { objectType, inArray } from './core/utilities'; -const LISTENERS = Object.create( null ); +const LISTENERS = Object.create(null); const SUPPORTED_EVENTS = [ - "error", - "runStart", - "suiteStart", - "testStart", - "assertion", - "testEnd", - "suiteEnd", - "runEnd" + 'error', + 'runStart', + 'suiteStart', + 'testStart', + 'assertion', + 'testEnd', + 'suiteEnd', + 'runEnd' ]; /** @@ -24,18 +24,18 @@ const SUPPORTED_EVENTS = [ * @param {Object} data * @return {void} */ -export function emit( eventName, data ) { - if ( objectType( eventName ) !== "string" ) { - throw new TypeError( "eventName must be a string when emitting an event" ); - } +export function emit (eventName, data) { + if (objectType(eventName) !== 'string') { + throw new TypeError('eventName must be a string when emitting an event'); + } - // Clone the callbacks in case one of them registers a new callback - const originalCallbacks = LISTENERS[ eventName ]; - const callbacks = originalCallbacks ? [ ...originalCallbacks ] : []; + // Clone the callbacks in case one of them registers a new callback + const originalCallbacks = LISTENERS[eventName]; + const callbacks = originalCallbacks ? [...originalCallbacks] : []; - for ( let i = 0; i < callbacks.length; i++ ) { - callbacks[ i ]( data ); - } + for (let i = 0; i < callbacks.length; i++) { + callbacks[i](data); + } } /** @@ -47,22 +47,22 @@ export function emit( eventName, data ) { * @param {Function} callback * @return {void} */ -export function on( eventName, callback ) { - if ( objectType( eventName ) !== "string" ) { - throw new TypeError( "eventName must be a string when registering a listener" ); - } else if ( !inArray( eventName, SUPPORTED_EVENTS ) ) { - const events = SUPPORTED_EVENTS.join( ", " ); - throw new Error( `"${eventName}" is not a valid event; must be one of: ${events}.` ); - } else if ( objectType( callback ) !== "function" ) { - throw new TypeError( "callback must be a function when registering a listener" ); - } +export function on (eventName, callback) { + if (objectType(eventName) !== 'string') { + throw new TypeError('eventName must be a string when registering a listener'); + } else if (!inArray(eventName, SUPPORTED_EVENTS)) { + const events = SUPPORTED_EVENTS.join(', '); + throw new Error(`"${eventName}" is not a valid event; must be one of: ${events}.`); + } else if (objectType(callback) !== 'function') { + throw new TypeError('callback must be a function when registering a listener'); + } - if ( !LISTENERS[ eventName ] ) { - LISTENERS[ eventName ] = []; - } + if (!LISTENERS[eventName]) { + LISTENERS[eventName] = []; + } - // Don't register the same callback more than once - if ( !inArray( callback, LISTENERS[ eventName ] ) ) { - LISTENERS[ eventName ].push( callback ); - } + // Don't register the same callback more than once + if (!inArray(callback, LISTENERS[eventName])) { + LISTENERS[eventName].push(callback); + } } diff --git a/src/export.js b/src/export.js index 65f11fe4b..e0bd6da3b 100644 --- a/src/export.js +++ b/src/export.js @@ -1,51 +1,50 @@ /* global module, exports, define */ -import { window, document, globalThis } from "./globals"; +import { window, document, globalThis } from './globals'; -export default function exportQUnit( QUnit ) { - let exportedModule = false; +export default function exportQUnit (QUnit) { + let exportedModule = false; - if ( window && document ) { + if (window && document) { + // QUnit may be defined when it is preconfigured but then only QUnit and QUnit.config may be defined. + if (window.QUnit && window.QUnit.version) { + throw new Error('QUnit has already been defined.'); + } - // QUnit may be defined when it is preconfigured but then only QUnit and QUnit.config may be defined. - if ( window.QUnit && window.QUnit.version ) { - throw new Error( "QUnit has already been defined." ); - } + window.QUnit = QUnit; - window.QUnit = QUnit; + exportedModule = true; + } - exportedModule = true; - } + // For Node.js + if (typeof module !== 'undefined' && module && module.exports) { + module.exports = QUnit; - // For Node.js - if ( typeof module !== "undefined" && module && module.exports ) { - module.exports = QUnit; + // For consistency with CommonJS environments' exports + module.exports.QUnit = QUnit; - // For consistency with CommonJS environments' exports - module.exports.QUnit = QUnit; + exportedModule = true; + } - exportedModule = true; - } + // For CommonJS with exports, but without module.exports, like Rhino + if (typeof exports !== 'undefined' && exports) { + exports.QUnit = QUnit; - // For CommonJS with exports, but without module.exports, like Rhino - if ( typeof exports !== "undefined" && exports ) { - exports.QUnit = QUnit; + exportedModule = true; + } - exportedModule = true; - } + // For AMD + if (typeof define === 'function' && define.amd) { + define(function () { + return QUnit; + }); + QUnit.config.autostart = false; - // For AMD - if ( typeof define === "function" && define.amd ) { - define( function() { - return QUnit; - } ); - QUnit.config.autostart = false; + exportedModule = true; + } - exportedModule = true; - } - - // For other environments, including Web Workers (globalThis === self), - // SpiderMonkey (mozjs), and other embedded JavaScript engines - if ( !exportedModule ) { - globalThis.QUnit = QUnit; - } + // For other environments, including Web Workers (globalThis === self), + // SpiderMonkey (mozjs), and other embedded JavaScript engines + if (!exportedModule) { + globalThis.QUnit = QUnit; + } } diff --git a/src/globals.js b/src/globals.js index ee3be7c3f..254a0f021 100644 --- a/src/globals.js +++ b/src/globals.js @@ -13,34 +13,29 @@ // // Instead, simply check the four options that already exist // in all supported environments. -function getGlobalThis() { - if ( typeof globalThis !== "undefined" ) { - - // For SpiderMonkey, modern browsers, and recent Node.js - // eslint-disable-next-line no-undef - return globalThis; - } - if ( typeof self !== "undefined" ) { - - // For web workers - // eslint-disable-next-line no-undef - return self; - } - if ( typeof window !== "undefined" ) { - - // For document context in browsers - return window; - } - if ( typeof global !== "undefined" ) { - - // For Node.js - // eslint-disable-next-line no-undef - return global; - } - throw new Error( "Unable to locate global object" ); +function getGlobalThis () { + if (typeof globalThis !== 'undefined') { + // For SpiderMonkey, modern browsers, and recent Node.js + // eslint-disable-next-line no-undef + return globalThis; + } + if (typeof self !== 'undefined') { + // For web workers + // eslint-disable-next-line no-undef + return self; + } + if (typeof window !== 'undefined') { + // For document context in browsers + return window; + } + if (typeof global !== 'undefined') { + // For Node.js + // eslint-disable-next-line no-undef + return global; + } + throw new Error('Unable to locate global object'); } - // This avoids a simple `export const` assignment as that would lead Rollup // to change getGlobalThis and use the same (generated) variable name there. const g = getGlobalThis(); @@ -53,46 +48,48 @@ export const clearTimeout = g.clearTimeout; export const document = window && window.document; export const navigator = window && window.navigator; -export const localSessionStorage = ( function() { - const x = "qunit-test-string"; - try { - g.sessionStorage.setItem( x, x ); - g.sessionStorage.removeItem( x ); - return g.sessionStorage; - } catch ( e ) { - return undefined; - } -}() ); +export const localSessionStorage = (function () { + const x = 'qunit-test-string'; + try { + g.sessionStorage.setItem(x, x); + g.sessionStorage.removeItem(x); + return g.sessionStorage; + } catch (e) { + return undefined; + } +}()); // Basic fallback for ES6 Map // Support: IE 9-10, Safari 7, PhantomJS -export const StringMap = typeof g.Map === "function" ? g.Map : function StringMap() { - var store = Object.create( null ); - var hasOwn = Object.prototype.hasOwnProperty; - this.get = function( strKey ) { - return store[ strKey ]; - }; - this.set = function( strKey, val ) { - if ( !hasOwn.call( store, strKey ) ) { - this.size++; - } - store[ strKey ] = val; - return this; - }; - this.delete = function( strKey ) { - if ( hasOwn.call( store, strKey ) ) { - delete store[ strKey ]; - this.size--; - } - }; - this.forEach = function( callback ) { - for ( var strKey in store ) { - callback( store[ strKey ], strKey ); - } - }; - this.clear = function() { - store = Object.create( null ); - this.size = 0; - }; - this.size = 0; -}; +export const StringMap = typeof g.Map === 'function' + ? g.Map + : function StringMap () { + let store = Object.create(null); + let hasOwn = Object.prototype.hasOwnProperty; + this.get = function (strKey) { + return store[strKey]; + }; + this.set = function (strKey, val) { + if (!hasOwn.call(store, strKey)) { + this.size++; + } + store[strKey] = val; + return this; + }; + this.delete = function (strKey) { + if (hasOwn.call(store, strKey)) { + delete store[strKey]; + this.size--; + } + }; + this.forEach = function (callback) { + for (let strKey in store) { + callback(store[strKey], strKey); + } + }; + this.clear = function () { + store = Object.create(null); + this.size = 0; + }; + this.size = 0; + }; diff --git a/src/html-reporter/diff.js b/src/html-reporter/diff.js index 52101da07..c6cb2c78b 100644 --- a/src/html-reporter/diff.js +++ b/src/html-reporter/diff.js @@ -1,5 +1,5 @@ -import QUnit from "../core"; -import { escapeText } from "./html"; +import QUnit from '../core'; +import { escapeText } from './html'; /* * This file is a modified version of google-diff-match-patch's JavaScript implementation @@ -29,1140 +29,1109 @@ import { escapeText } from "./html"; * Usage: QUnit.diff(expected, actual) * */ -QUnit.diff = ( function() { - function DiffMatchPatch() { - } - - // DIFF FUNCTIONS - - /** - * The data structure representing a diff is an array of tuples: - * [[DIFF_DELETE, 'Hello'], [DIFF_INSERT, 'Goodbye'], [DIFF_EQUAL, ' world.']] - * which means: delete 'Hello', add 'Goodbye' and keep ' world.' - */ - var DIFF_DELETE = -1, - DIFF_INSERT = 1, - DIFF_EQUAL = 0, - hasOwn = Object.prototype.hasOwnProperty; - - /** - * Find the differences between two texts. Simplifies the problem by stripping - * any common prefix or suffix off the texts before diffing. - * @param {string} text1 Old string to be diffed. - * @param {string} text2 New string to be diffed. - * @param {boolean=} optChecklines Optional speedup flag. If present and false, - * then don't run a line-level diff first to identify the changed areas. - * Defaults to true, which does a faster, slightly less optimal diff. - * @return {!Array.} Array of diff tuples. - */ - DiffMatchPatch.prototype.DiffMain = function( text1, text2, optChecklines ) { - var deadline, checklines, commonlength, - commonprefix, commonsuffix, diffs; - - // The diff must be complete in up to 1 second. - deadline = ( new Date() ).getTime() + 1000; - - // Check for null inputs. - if ( text1 === null || text2 === null ) { - throw new Error( "Null input. (DiffMain)" ); - } - - // Check for equality (speedup). - if ( text1 === text2 ) { - if ( text1 ) { - return [ - [ DIFF_EQUAL, text1 ] - ]; - } - return []; - } - - if ( typeof optChecklines === "undefined" ) { - optChecklines = true; - } - - checklines = optChecklines; - - // Trim off common prefix (speedup). - commonlength = this.diffCommonPrefix( text1, text2 ); - commonprefix = text1.substring( 0, commonlength ); - text1 = text1.substring( commonlength ); - text2 = text2.substring( commonlength ); - - // Trim off common suffix (speedup). - commonlength = this.diffCommonSuffix( text1, text2 ); - commonsuffix = text1.substring( text1.length - commonlength ); - text1 = text1.substring( 0, text1.length - commonlength ); - text2 = text2.substring( 0, text2.length - commonlength ); - - // Compute the diff on the middle block. - diffs = this.diffCompute( text1, text2, checklines, deadline ); - - // Restore the prefix and suffix. - if ( commonprefix ) { - diffs.unshift( [ DIFF_EQUAL, commonprefix ] ); - } - if ( commonsuffix ) { - diffs.push( [ DIFF_EQUAL, commonsuffix ] ); - } - this.diffCleanupMerge( diffs ); - return diffs; - }; - - /** - * Reduce the number of edits by eliminating operationally trivial equalities. - * @param {!Array.} diffs Array of diff tuples. - */ - DiffMatchPatch.prototype.diffCleanupEfficiency = function( diffs ) { - var changes, equalities, equalitiesLength, lastequality, - pointer, preIns, preDel, postIns, postDel; - changes = false; - equalities = []; // Stack of indices where equalities are found. - equalitiesLength = 0; // Keeping our own length var is faster in JS. - /** @type {?string} */ - lastequality = null; - - // Always equal to diffs[equalities[equalitiesLength - 1]][1] - pointer = 0; // Index of current position. - - // Is there an insertion operation before the last equality. - preIns = false; - - // Is there a deletion operation before the last equality. - preDel = false; - - // Is there an insertion operation after the last equality. - postIns = false; - - // Is there a deletion operation after the last equality. - postDel = false; - while ( pointer < diffs.length ) { - - // Equality found. - if ( diffs[ pointer ][ 0 ] === DIFF_EQUAL ) { - if ( diffs[ pointer ][ 1 ].length < 4 && ( postIns || postDel ) ) { - - // Candidate found. - equalities[ equalitiesLength++ ] = pointer; - preIns = postIns; - preDel = postDel; - lastequality = diffs[ pointer ][ 1 ]; - } else { - - // Not a candidate, and can never become one. - equalitiesLength = 0; - lastequality = null; - } - postIns = postDel = false; - - // An insertion or deletion. - } else { - - if ( diffs[ pointer ][ 0 ] === DIFF_DELETE ) { - postDel = true; - } else { - postIns = true; - } - - /* - * Five types to be split: - * ABXYCD - * AXCD - * ABXC - * AXCD - * ABXC - */ - if ( lastequality && ( ( preIns && preDel && postIns && postDel ) || - ( ( lastequality.length < 2 ) && - ( preIns + preDel + postIns + postDel ) === 3 ) ) ) { - - // Duplicate record. - diffs.splice( - equalities[ equalitiesLength - 1 ], - 0, - [ DIFF_DELETE, lastequality ] - ); - - // Change second copy to insert. - diffs[ equalities[ equalitiesLength - 1 ] + 1 ][ 0 ] = DIFF_INSERT; - equalitiesLength--; // Throw away the equality we just deleted; - lastequality = null; - if ( preIns && preDel ) { - - // No changes made which could affect previous entry, keep going. - postIns = postDel = true; - equalitiesLength = 0; - } else { - equalitiesLength--; // Throw away the previous equality. - pointer = equalitiesLength > 0 ? equalities[ equalitiesLength - 1 ] : -1; - postIns = postDel = false; - } - changes = true; - } - } - pointer++; - } - - if ( changes ) { - this.diffCleanupMerge( diffs ); - } - }; - - /** - * Convert a diff array into a pretty HTML report. - * @param {!Array.} diffs Array of diff tuples. - * @param {integer} string to be beautified. - * @return {string} HTML representation. - */ - DiffMatchPatch.prototype.diffPrettyHtml = function( diffs ) { - var op, data, x, - html = []; - for ( x = 0; x < diffs.length; x++ ) { - op = diffs[ x ][ 0 ]; // Operation (insert, delete, equal) - data = diffs[ x ][ 1 ]; // Text of change. - switch ( op ) { - case DIFF_INSERT: - html[ x ] = "" + escapeText( data ) + ""; - break; - case DIFF_DELETE: - html[ x ] = "" + escapeText( data ) + ""; - break; - case DIFF_EQUAL: - html[ x ] = "" + escapeText( data ) + ""; - break; - } - } - return html.join( "" ); - }; - - /** - * Determine the common prefix of two strings. - * @param {string} text1 First string. - * @param {string} text2 Second string. - * @return {number} The number of characters common to the start of each - * string. - */ - DiffMatchPatch.prototype.diffCommonPrefix = function( text1, text2 ) { - var pointermid, pointermax, pointermin, pointerstart; - - // Quick check for common null cases. - if ( !text1 || !text2 || text1.charAt( 0 ) !== text2.charAt( 0 ) ) { - return 0; - } - - // Binary search. - // Performance analysis: https://neil.fraser.name/news/2007/10/09/ - pointermin = 0; - pointermax = Math.min( text1.length, text2.length ); - pointermid = pointermax; - pointerstart = 0; - while ( pointermin < pointermid ) { - if ( text1.substring( pointerstart, pointermid ) === - text2.substring( pointerstart, pointermid ) ) { - pointermin = pointermid; - pointerstart = pointermin; - } else { - pointermax = pointermid; - } - pointermid = Math.floor( ( pointermax - pointermin ) / 2 + pointermin ); - } - return pointermid; - }; - - /** - * Determine the common suffix of two strings. - * @param {string} text1 First string. - * @param {string} text2 Second string. - * @return {number} The number of characters common to the end of each string. - */ - DiffMatchPatch.prototype.diffCommonSuffix = function( text1, text2 ) { - var pointermid, pointermax, pointermin, pointerend; - - // Quick check for common null cases. - if ( !text1 || - !text2 || - text1.charAt( text1.length - 1 ) !== text2.charAt( text2.length - 1 ) ) { - return 0; - } - - // Binary search. - // Performance analysis: https://neil.fraser.name/news/2007/10/09/ - pointermin = 0; - pointermax = Math.min( text1.length, text2.length ); - pointermid = pointermax; - pointerend = 0; - while ( pointermin < pointermid ) { - if ( text1.substring( text1.length - pointermid, text1.length - pointerend ) === - text2.substring( text2.length - pointermid, text2.length - pointerend ) ) { - pointermin = pointermid; - pointerend = pointermin; - } else { - pointermax = pointermid; - } - pointermid = Math.floor( ( pointermax - pointermin ) / 2 + pointermin ); - } - return pointermid; - }; - - /** - * Find the differences between two texts. Assumes that the texts do not - * have any common prefix or suffix. - * @param {string} text1 Old string to be diffed. - * @param {string} text2 New string to be diffed. - * @param {boolean} checklines Speedup flag. If false, then don't run a - * line-level diff first to identify the changed areas. - * If true, then run a faster, slightly less optimal diff. - * @param {number} deadline Time when the diff should be complete by. - * @return {!Array.} Array of diff tuples. - * @private - */ - DiffMatchPatch.prototype.diffCompute = function( text1, text2, checklines, deadline ) { - var diffs, longtext, shorttext, i, hm, - text1A, text2A, text1B, text2B, - midCommon, diffsA, diffsB; - - if ( !text1 ) { - - // Just add some text (speedup). - return [ - [ DIFF_INSERT, text2 ] - ]; - } - - if ( !text2 ) { - - // Just delete some text (speedup). - return [ - [ DIFF_DELETE, text1 ] - ]; - } - - longtext = text1.length > text2.length ? text1 : text2; - shorttext = text1.length > text2.length ? text2 : text1; - i = longtext.indexOf( shorttext ); - if ( i !== -1 ) { - - // Shorter text is inside the longer text (speedup). - diffs = [ - [ DIFF_INSERT, longtext.substring( 0, i ) ], - [ DIFF_EQUAL, shorttext ], - [ DIFF_INSERT, longtext.substring( i + shorttext.length ) ] - ]; - - // Swap insertions for deletions if diff is reversed. - if ( text1.length > text2.length ) { - diffs[ 0 ][ 0 ] = diffs[ 2 ][ 0 ] = DIFF_DELETE; - } - return diffs; - } - - if ( shorttext.length === 1 ) { - - // Single character string. - // After the previous speedup, the character can't be an equality. - return [ - [ DIFF_DELETE, text1 ], - [ DIFF_INSERT, text2 ] - ]; - } - - // Check to see if the problem can be split in two. - hm = this.diffHalfMatch( text1, text2 ); - if ( hm ) { - - // A half-match was found, sort out the return data. - text1A = hm[ 0 ]; - text1B = hm[ 1 ]; - text2A = hm[ 2 ]; - text2B = hm[ 3 ]; - midCommon = hm[ 4 ]; - - // Send both pairs off for separate processing. - diffsA = this.DiffMain( text1A, text2A, checklines, deadline ); - diffsB = this.DiffMain( text1B, text2B, checklines, deadline ); - - // Merge the results. - return diffsA.concat( [ - [ DIFF_EQUAL, midCommon ] - ], diffsB ); - } - - if ( checklines && text1.length > 100 && text2.length > 100 ) { - return this.diffLineMode( text1, text2, deadline ); - } - - return this.diffBisect( text1, text2, deadline ); - }; - - /** - * Do the two texts share a substring which is at least half the length of the - * longer text? - * This speedup can produce non-minimal diffs. - * @param {string} text1 First string. - * @param {string} text2 Second string. - * @return {Array.} Five element Array, containing the prefix of - * text1, the suffix of text1, the prefix of text2, the suffix of - * text2 and the common middle. Or null if there was no match. - * @private - */ - DiffMatchPatch.prototype.diffHalfMatch = function( text1, text2 ) { - var longtext, shorttext, dmp, - text1A, text2B, text2A, text1B, midCommon, - hm1, hm2, hm; - - longtext = text1.length > text2.length ? text1 : text2; - shorttext = text1.length > text2.length ? text2 : text1; - if ( longtext.length < 4 || shorttext.length * 2 < longtext.length ) { - return null; // Pointless. - } - dmp = this; // 'this' becomes 'window' in a closure. - - /** - * Does a substring of shorttext exist within longtext such that the substring - * is at least half the length of longtext? - * Closure, but does not reference any external variables. - * @param {string} longtext Longer string. - * @param {string} shorttext Shorter string. - * @param {number} i Start index of quarter length substring within longtext. - * @return {Array.} Five element Array, containing the prefix of - * longtext, the suffix of longtext, the prefix of shorttext, the suffix - * of shorttext and the common middle. Or null if there was no match. - * @private - */ - function diffHalfMatchI( longtext, shorttext, i ) { - var seed, j, bestCommon, prefixLength, suffixLength, - bestLongtextA, bestLongtextB, bestShorttextA, bestShorttextB; - - // Start with a 1/4 length substring at position i as a seed. - seed = longtext.substring( i, i + Math.floor( longtext.length / 4 ) ); - j = -1; - bestCommon = ""; - while ( ( j = shorttext.indexOf( seed, j + 1 ) ) !== -1 ) { - prefixLength = dmp.diffCommonPrefix( longtext.substring( i ), - shorttext.substring( j ) ); - suffixLength = dmp.diffCommonSuffix( longtext.substring( 0, i ), - shorttext.substring( 0, j ) ); - if ( bestCommon.length < suffixLength + prefixLength ) { - bestCommon = shorttext.substring( j - suffixLength, j ) + - shorttext.substring( j, j + prefixLength ); - bestLongtextA = longtext.substring( 0, i - suffixLength ); - bestLongtextB = longtext.substring( i + prefixLength ); - bestShorttextA = shorttext.substring( 0, j - suffixLength ); - bestShorttextB = shorttext.substring( j + prefixLength ); - } - } - if ( bestCommon.length * 2 >= longtext.length ) { - return [ bestLongtextA, bestLongtextB, - bestShorttextA, bestShorttextB, bestCommon - ]; - } else { - return null; - } - } - - // First check if the second quarter is the seed for a half-match. - hm1 = diffHalfMatchI( longtext, shorttext, - Math.ceil( longtext.length / 4 ) ); - - // Check again based on the third quarter. - hm2 = diffHalfMatchI( longtext, shorttext, - Math.ceil( longtext.length / 2 ) ); - if ( !hm1 && !hm2 ) { - return null; - } else if ( !hm2 ) { - hm = hm1; - } else if ( !hm1 ) { - hm = hm2; - } else { - - // Both matched. Select the longest. - hm = hm1[ 4 ].length > hm2[ 4 ].length ? hm1 : hm2; - } - - // A half-match was found, sort out the return data. - if ( text1.length > text2.length ) { - text1A = hm[ 0 ]; - text1B = hm[ 1 ]; - text2A = hm[ 2 ]; - text2B = hm[ 3 ]; - } else { - text2A = hm[ 0 ]; - text2B = hm[ 1 ]; - text1A = hm[ 2 ]; - text1B = hm[ 3 ]; - } - midCommon = hm[ 4 ]; - return [ text1A, text1B, text2A, text2B, midCommon ]; - }; - - /** - * Do a quick line-level diff on both strings, then rediff the parts for - * greater accuracy. - * This speedup can produce non-minimal diffs. - * @param {string} text1 Old string to be diffed. - * @param {string} text2 New string to be diffed. - * @param {number} deadline Time when the diff should be complete by. - * @return {!Array.} Array of diff tuples. - * @private - */ - DiffMatchPatch.prototype.diffLineMode = function( text1, text2, deadline ) { - var a, diffs, linearray, pointer, countInsert, - countDelete, textInsert, textDelete, j; - - // Scan the text on a line-by-line basis first. - a = this.diffLinesToChars( text1, text2 ); - text1 = a.chars1; - text2 = a.chars2; - linearray = a.lineArray; - - diffs = this.DiffMain( text1, text2, false, deadline ); - - // Convert the diff back to original text. - this.diffCharsToLines( diffs, linearray ); - - // Eliminate freak matches (e.g. blank lines) - this.diffCleanupSemantic( diffs ); - - // Rediff any replacement blocks, this time character-by-character. - // Add a dummy entry at the end. - diffs.push( [ DIFF_EQUAL, "" ] ); - pointer = 0; - countDelete = 0; - countInsert = 0; - textDelete = ""; - textInsert = ""; - while ( pointer < diffs.length ) { - switch ( diffs[ pointer ][ 0 ] ) { - case DIFF_INSERT: - countInsert++; - textInsert += diffs[ pointer ][ 1 ]; - break; - case DIFF_DELETE: - countDelete++; - textDelete += diffs[ pointer ][ 1 ]; - break; - case DIFF_EQUAL: - - // Upon reaching an equality, check for prior redundancies. - if ( countDelete >= 1 && countInsert >= 1 ) { - - // Delete the offending records and add the merged ones. - diffs.splice( pointer - countDelete - countInsert, - countDelete + countInsert ); - pointer = pointer - countDelete - countInsert; - a = this.DiffMain( textDelete, textInsert, false, deadline ); - for ( j = a.length - 1; j >= 0; j-- ) { - diffs.splice( pointer, 0, a[ j ] ); - } - pointer = pointer + a.length; - } - countInsert = 0; - countDelete = 0; - textDelete = ""; - textInsert = ""; - break; - } - pointer++; - } - diffs.pop(); // Remove the dummy entry at the end. - - return diffs; - }; - - /** - * Find the 'middle snake' of a diff, split the problem in two - * and return the recursively constructed diff. - * See Myers 1986 paper: An O(ND) Difference Algorithm and Its Variations. - * @param {string} text1 Old string to be diffed. - * @param {string} text2 New string to be diffed. - * @param {number} deadline Time at which to bail if not yet complete. - * @return {!Array.} Array of diff tuples. - * @private - */ - DiffMatchPatch.prototype.diffBisect = function( text1, text2, deadline ) { - var text1Length, text2Length, maxD, vOffset, vLength, - v1, v2, x, delta, front, k1start, k1end, k2start, - k2end, k2Offset, k1Offset, x1, x2, y1, y2, d, k1, k2; - - // Cache the text lengths to prevent multiple calls. - text1Length = text1.length; - text2Length = text2.length; - maxD = Math.ceil( ( text1Length + text2Length ) / 2 ); - vOffset = maxD; - vLength = 2 * maxD; - v1 = new Array( vLength ); - v2 = new Array( vLength ); - - // Setting all elements to -1 is faster in Chrome & Firefox than mixing - // integers and undefined. - for ( x = 0; x < vLength; x++ ) { - v1[ x ] = -1; - v2[ x ] = -1; - } - v1[ vOffset + 1 ] = 0; - v2[ vOffset + 1 ] = 0; - delta = text1Length - text2Length; - - // If the total number of characters is odd, then the front path will collide - // with the reverse path. - front = ( delta % 2 !== 0 ); - - // Offsets for start and end of k loop. - // Prevents mapping of space beyond the grid. - k1start = 0; - k1end = 0; - k2start = 0; - k2end = 0; - for ( d = 0; d < maxD; d++ ) { - - // Bail out if deadline is reached. - if ( ( new Date() ).getTime() > deadline ) { - break; - } - - // Walk the front path one step. - for ( k1 = -d + k1start; k1 <= d - k1end; k1 += 2 ) { - k1Offset = vOffset + k1; - if ( k1 === -d || ( k1 !== d && v1[ k1Offset - 1 ] < v1[ k1Offset + 1 ] ) ) { - x1 = v1[ k1Offset + 1 ]; - } else { - x1 = v1[ k1Offset - 1 ] + 1; - } - y1 = x1 - k1; - while ( x1 < text1Length && y1 < text2Length && - text1.charAt( x1 ) === text2.charAt( y1 ) ) { - x1++; - y1++; - } - v1[ k1Offset ] = x1; - if ( x1 > text1Length ) { - - // Ran off the right of the graph. - k1end += 2; - } else if ( y1 > text2Length ) { - - // Ran off the bottom of the graph. - k1start += 2; - } else if ( front ) { - k2Offset = vOffset + delta - k1; - if ( k2Offset >= 0 && k2Offset < vLength && v2[ k2Offset ] !== -1 ) { - - // Mirror x2 onto top-left coordinate system. - x2 = text1Length - v2[ k2Offset ]; - if ( x1 >= x2 ) { - - // Overlap detected. - return this.diffBisectSplit( text1, text2, x1, y1, deadline ); - } - } - } - } - - // Walk the reverse path one step. - for ( k2 = -d + k2start; k2 <= d - k2end; k2 += 2 ) { - k2Offset = vOffset + k2; - if ( k2 === -d || ( k2 !== d && v2[ k2Offset - 1 ] < v2[ k2Offset + 1 ] ) ) { - x2 = v2[ k2Offset + 1 ]; - } else { - x2 = v2[ k2Offset - 1 ] + 1; - } - y2 = x2 - k2; - while ( x2 < text1Length && y2 < text2Length && - text1.charAt( text1Length - x2 - 1 ) === - text2.charAt( text2Length - y2 - 1 ) ) { - x2++; - y2++; - } - v2[ k2Offset ] = x2; - if ( x2 > text1Length ) { - - // Ran off the left of the graph. - k2end += 2; - } else if ( y2 > text2Length ) { - - // Ran off the top of the graph. - k2start += 2; - } else if ( !front ) { - k1Offset = vOffset + delta - k2; - if ( k1Offset >= 0 && k1Offset < vLength && v1[ k1Offset ] !== -1 ) { - x1 = v1[ k1Offset ]; - y1 = vOffset + x1 - k1Offset; - - // Mirror x2 onto top-left coordinate system. - x2 = text1Length - x2; - if ( x1 >= x2 ) { - - // Overlap detected. - return this.diffBisectSplit( text1, text2, x1, y1, deadline ); - } - } - } - } - } - - // Diff took too long and hit the deadline or - // number of diffs equals number of characters, no commonality at all. - return [ - [ DIFF_DELETE, text1 ], - [ DIFF_INSERT, text2 ] - ]; - }; - - /** - * Given the location of the 'middle snake', split the diff in two parts - * and recurse. - * @param {string} text1 Old string to be diffed. - * @param {string} text2 New string to be diffed. - * @param {number} x Index of split point in text1. - * @param {number} y Index of split point in text2. - * @param {number} deadline Time at which to bail if not yet complete. - * @return {!Array.} Array of diff tuples. - * @private - */ - DiffMatchPatch.prototype.diffBisectSplit = function( text1, text2, x, y, deadline ) { - var text1a, text1b, text2a, text2b, diffs, diffsb; - text1a = text1.substring( 0, x ); - text2a = text2.substring( 0, y ); - text1b = text1.substring( x ); - text2b = text2.substring( y ); - - // Compute both diffs serially. - diffs = this.DiffMain( text1a, text2a, false, deadline ); - diffsb = this.DiffMain( text1b, text2b, false, deadline ); - - return diffs.concat( diffsb ); - }; - - /** - * Reduce the number of edits by eliminating semantically trivial equalities. - * @param {!Array.} diffs Array of diff tuples. - */ - DiffMatchPatch.prototype.diffCleanupSemantic = function( diffs ) { - var changes, equalities, equalitiesLength, lastequality, - pointer, lengthInsertions2, lengthDeletions2, lengthInsertions1, - lengthDeletions1, deletion, insertion, overlapLength1, overlapLength2; - changes = false; - equalities = []; // Stack of indices where equalities are found. - equalitiesLength = 0; // Keeping our own length var is faster in JS. - /** @type {?string} */ - lastequality = null; - - // Always equal to diffs[equalities[equalitiesLength - 1]][1] - pointer = 0; // Index of current position. - - // Number of characters that changed prior to the equality. - lengthInsertions1 = 0; - lengthDeletions1 = 0; - - // Number of characters that changed after the equality. - lengthInsertions2 = 0; - lengthDeletions2 = 0; - while ( pointer < diffs.length ) { - if ( diffs[ pointer ][ 0 ] === DIFF_EQUAL ) { // Equality found. - equalities[ equalitiesLength++ ] = pointer; - lengthInsertions1 = lengthInsertions2; - lengthDeletions1 = lengthDeletions2; - lengthInsertions2 = 0; - lengthDeletions2 = 0; - lastequality = diffs[ pointer ][ 1 ]; - } else { // An insertion or deletion. - if ( diffs[ pointer ][ 0 ] === DIFF_INSERT ) { - lengthInsertions2 += diffs[ pointer ][ 1 ].length; - } else { - lengthDeletions2 += diffs[ pointer ][ 1 ].length; - } - - // Eliminate an equality that is smaller or equal to the edits on both - // sides of it. - if ( lastequality && ( lastequality.length <= - Math.max( lengthInsertions1, lengthDeletions1 ) ) && - ( lastequality.length <= Math.max( lengthInsertions2, - lengthDeletions2 ) ) ) { - - // Duplicate record. - diffs.splice( - equalities[ equalitiesLength - 1 ], - 0, - [ DIFF_DELETE, lastequality ] - ); - - // Change second copy to insert. - diffs[ equalities[ equalitiesLength - 1 ] + 1 ][ 0 ] = DIFF_INSERT; - - // Throw away the equality we just deleted. - equalitiesLength--; - - // Throw away the previous equality (it needs to be reevaluated). - equalitiesLength--; - pointer = equalitiesLength > 0 ? equalities[ equalitiesLength - 1 ] : -1; - - // Reset the counters. - lengthInsertions1 = 0; - lengthDeletions1 = 0; - lengthInsertions2 = 0; - lengthDeletions2 = 0; - lastequality = null; - changes = true; - } - } - pointer++; - } - - // Normalize the diff. - if ( changes ) { - this.diffCleanupMerge( diffs ); - } - - // Find any overlaps between deletions and insertions. - // e.g: abcxxxxxxdef - // -> abcxxxdef - // e.g: xxxabcdefxxx - // -> defxxxabc - // Only extract an overlap if it is as big as the edit ahead or behind it. - pointer = 1; - while ( pointer < diffs.length ) { - if ( diffs[ pointer - 1 ][ 0 ] === DIFF_DELETE && - diffs[ pointer ][ 0 ] === DIFF_INSERT ) { - deletion = diffs[ pointer - 1 ][ 1 ]; - insertion = diffs[ pointer ][ 1 ]; - overlapLength1 = this.diffCommonOverlap( deletion, insertion ); - overlapLength2 = this.diffCommonOverlap( insertion, deletion ); - if ( overlapLength1 >= overlapLength2 ) { - if ( overlapLength1 >= deletion.length / 2 || - overlapLength1 >= insertion.length / 2 ) { - - // Overlap found. Insert an equality and trim the surrounding edits. - diffs.splice( - pointer, - 0, - [ DIFF_EQUAL, insertion.substring( 0, overlapLength1 ) ] - ); - diffs[ pointer - 1 ][ 1 ] = - deletion.substring( 0, deletion.length - overlapLength1 ); - diffs[ pointer + 1 ][ 1 ] = insertion.substring( overlapLength1 ); - pointer++; - } - } else { - if ( overlapLength2 >= deletion.length / 2 || - overlapLength2 >= insertion.length / 2 ) { - - // Reverse overlap found. - // Insert an equality and swap and trim the surrounding edits. - diffs.splice( - pointer, - 0, - [ DIFF_EQUAL, deletion.substring( 0, overlapLength2 ) ] - ); - - diffs[ pointer - 1 ][ 0 ] = DIFF_INSERT; - diffs[ pointer - 1 ][ 1 ] = - insertion.substring( 0, insertion.length - overlapLength2 ); - diffs[ pointer + 1 ][ 0 ] = DIFF_DELETE; - diffs[ pointer + 1 ][ 1 ] = - deletion.substring( overlapLength2 ); - pointer++; - } - } - pointer++; - } - pointer++; - } - }; - - /** - * Determine if the suffix of one string is the prefix of another. - * @param {string} text1 First string. - * @param {string} text2 Second string. - * @return {number} The number of characters common to the end of the first - * string and the start of the second string. - * @private - */ - DiffMatchPatch.prototype.diffCommonOverlap = function( text1, text2 ) { - var text1Length, text2Length, textLength, - best, length, pattern, found; - - // Cache the text lengths to prevent multiple calls. - text1Length = text1.length; - text2Length = text2.length; - - // Eliminate the null case. - if ( text1Length === 0 || text2Length === 0 ) { - return 0; - } - - // Truncate the longer string. - if ( text1Length > text2Length ) { - text1 = text1.substring( text1Length - text2Length ); - } else if ( text1Length < text2Length ) { - text2 = text2.substring( 0, text1Length ); - } - textLength = Math.min( text1Length, text2Length ); - - // Quick check for the worst case. - if ( text1 === text2 ) { - return textLength; - } - - // Start by looking for a single character match - // and increase length until no match is found. - // Performance analysis: https://neil.fraser.name/news/2010/11/04/ - best = 0; - length = 1; - while ( true ) { - pattern = text1.substring( textLength - length ); - found = text2.indexOf( pattern ); - if ( found === -1 ) { - return best; - } - length += found; - if ( found === 0 || text1.substring( textLength - length ) === - text2.substring( 0, length ) ) { - best = length; - length++; - } - } - }; - - /** - * Split two texts into an array of strings. Reduce the texts to a string of - * hashes where each Unicode character represents one line. - * @param {string} text1 First string. - * @param {string} text2 Second string. - * @return {{chars1: string, chars2: string, lineArray: !Array.}} - * An object containing the encoded text1, the encoded text2 and - * the array of unique strings. - * The zeroth element of the array of unique strings is intentionally blank. - * @private - */ - DiffMatchPatch.prototype.diffLinesToChars = function( text1, text2 ) { - var lineArray, lineHash, chars1, chars2; - lineArray = []; // E.g. lineArray[4] === 'Hello\n' - lineHash = {}; // E.g. lineHash['Hello\n'] === 4 - - // '\x00' is a valid character, but various debuggers don't like it. - // So we'll insert a junk entry to avoid generating a null character. - lineArray[ 0 ] = ""; - - /** - * Split a text into an array of strings. Reduce the texts to a string of - * hashes where each Unicode character represents one line. - * Modifies linearray and linehash through being a closure. - * @param {string} text String to encode. - * @return {string} Encoded string. - * @private - */ - function diffLinesToCharsMunge( text ) { - var chars, lineStart, lineEnd, lineArrayLength, line; - chars = ""; - - // Walk the text, pulling out a substring for each line. - // text.split('\n') would would temporarily double our memory footprint. - // Modifying text would create many large strings to garbage collect. - lineStart = 0; - lineEnd = -1; - - // Keeping our own length variable is faster than looking it up. - lineArrayLength = lineArray.length; - while ( lineEnd < text.length - 1 ) { - lineEnd = text.indexOf( "\n", lineStart ); - if ( lineEnd === -1 ) { - lineEnd = text.length - 1; - } - line = text.substring( lineStart, lineEnd + 1 ); - lineStart = lineEnd + 1; - - if ( hasOwn.call( lineHash, line ) ) { - chars += String.fromCharCode( lineHash[ line ] ); - } else { - chars += String.fromCharCode( lineArrayLength ); - lineHash[ line ] = lineArrayLength; - lineArray[ lineArrayLength++ ] = line; - } - } - return chars; - } - - chars1 = diffLinesToCharsMunge( text1 ); - chars2 = diffLinesToCharsMunge( text2 ); - return { - chars1: chars1, - chars2: chars2, - lineArray: lineArray - }; - }; - - /** - * Rehydrate the text in a diff from a string of line hashes to real lines of - * text. - * @param {!Array.} diffs Array of diff tuples. - * @param {!Array.} lineArray Array of unique strings. - * @private - */ - DiffMatchPatch.prototype.diffCharsToLines = function( diffs, lineArray ) { - var x, chars, text, y; - for ( x = 0; x < diffs.length; x++ ) { - chars = diffs[ x ][ 1 ]; - text = []; - for ( y = 0; y < chars.length; y++ ) { - text[ y ] = lineArray[ chars.charCodeAt( y ) ]; - } - diffs[ x ][ 1 ] = text.join( "" ); - } - }; - - /** - * Reorder and merge like edit sections. Merge equalities. - * Any edit section can move as long as it doesn't cross an equality. - * @param {!Array.} diffs Array of diff tuples. - */ - DiffMatchPatch.prototype.diffCleanupMerge = function( diffs ) { - var pointer, countDelete, countInsert, textInsert, textDelete, - commonlength, changes, diffPointer, position; - diffs.push( [ DIFF_EQUAL, "" ] ); // Add a dummy entry at the end. - pointer = 0; - countDelete = 0; - countInsert = 0; - textDelete = ""; - textInsert = ""; - - while ( pointer < diffs.length ) { - switch ( diffs[ pointer ][ 0 ] ) { - case DIFF_INSERT: - countInsert++; - textInsert += diffs[ pointer ][ 1 ]; - pointer++; - break; - case DIFF_DELETE: - countDelete++; - textDelete += diffs[ pointer ][ 1 ]; - pointer++; - break; - case DIFF_EQUAL: - - // Upon reaching an equality, check for prior redundancies. - if ( countDelete + countInsert > 1 ) { - if ( countDelete !== 0 && countInsert !== 0 ) { - - // Factor out any common prefixes. - commonlength = this.diffCommonPrefix( textInsert, textDelete ); - if ( commonlength !== 0 ) { - if ( ( pointer - countDelete - countInsert ) > 0 && - diffs[ pointer - countDelete - countInsert - 1 ][ 0 ] === - DIFF_EQUAL ) { - diffs[ pointer - countDelete - countInsert - 1 ][ 1 ] += - textInsert.substring( 0, commonlength ); - } else { - diffs.splice( 0, 0, [ DIFF_EQUAL, - textInsert.substring( 0, commonlength ) - ] ); - pointer++; - } - textInsert = textInsert.substring( commonlength ); - textDelete = textDelete.substring( commonlength ); - } - - // Factor out any common suffixies. - commonlength = this.diffCommonSuffix( textInsert, textDelete ); - if ( commonlength !== 0 ) { - diffs[ pointer ][ 1 ] = textInsert.substring( textInsert.length - - commonlength ) + diffs[ pointer ][ 1 ]; - textInsert = textInsert.substring( 0, textInsert.length - - commonlength ); - textDelete = textDelete.substring( 0, textDelete.length - - commonlength ); - } - } - - // Delete the offending records and add the merged ones. - if ( countDelete === 0 ) { - diffs.splice( pointer - countInsert, - countDelete + countInsert, [ DIFF_INSERT, textInsert ] ); - } else if ( countInsert === 0 ) { - diffs.splice( pointer - countDelete, - countDelete + countInsert, [ DIFF_DELETE, textDelete ] ); - } else { - diffs.splice( - pointer - countDelete - countInsert, - countDelete + countInsert, - [ DIFF_DELETE, textDelete ], [ DIFF_INSERT, textInsert ] - ); - } - pointer = pointer - countDelete - countInsert + - ( countDelete ? 1 : 0 ) + ( countInsert ? 1 : 0 ) + 1; - } else if ( pointer !== 0 && diffs[ pointer - 1 ][ 0 ] === DIFF_EQUAL ) { - - // Merge this equality with the previous one. - diffs[ pointer - 1 ][ 1 ] += diffs[ pointer ][ 1 ]; - diffs.splice( pointer, 1 ); - } else { - pointer++; - } - countInsert = 0; - countDelete = 0; - textDelete = ""; - textInsert = ""; - break; - } - } - if ( diffs[ diffs.length - 1 ][ 1 ] === "" ) { - diffs.pop(); // Remove the dummy entry at the end. - } - - // Second pass: look for single edits surrounded on both sides by equalities - // which can be shifted sideways to eliminate an equality. - // e.g: ABAC -> ABAC - changes = false; - pointer = 1; - - // Intentionally ignore the first and last element (don't need checking). - while ( pointer < diffs.length - 1 ) { - if ( diffs[ pointer - 1 ][ 0 ] === DIFF_EQUAL && - diffs[ pointer + 1 ][ 0 ] === DIFF_EQUAL ) { - - diffPointer = diffs[ pointer ][ 1 ]; - position = diffPointer.substring( - diffPointer.length - diffs[ pointer - 1 ][ 1 ].length - ); - - // This is a single edit surrounded by equalities. - if ( position === diffs[ pointer - 1 ][ 1 ] ) { - - // Shift the edit over the previous equality. - diffs[ pointer ][ 1 ] = diffs[ pointer - 1 ][ 1 ] + - diffs[ pointer ][ 1 ].substring( 0, diffs[ pointer ][ 1 ].length - - diffs[ pointer - 1 ][ 1 ].length ); - diffs[ pointer + 1 ][ 1 ] = - diffs[ pointer - 1 ][ 1 ] + diffs[ pointer + 1 ][ 1 ]; - diffs.splice( pointer - 1, 1 ); - changes = true; - } else if ( diffPointer.substring( 0, diffs[ pointer + 1 ][ 1 ].length ) === - diffs[ pointer + 1 ][ 1 ] ) { - - // Shift the edit over the next equality. - diffs[ pointer - 1 ][ 1 ] += diffs[ pointer + 1 ][ 1 ]; - diffs[ pointer ][ 1 ] = - diffs[ pointer ][ 1 ].substring( diffs[ pointer + 1 ][ 1 ].length ) + - diffs[ pointer + 1 ][ 1 ]; - diffs.splice( pointer + 1, 1 ); - changes = true; - } - } - pointer++; - } - - // If shifts were made, the diff needs reordering and another shift sweep. - if ( changes ) { - this.diffCleanupMerge( diffs ); - } - }; - - return function( o, n ) { - var diff, output, text; - diff = new DiffMatchPatch(); - output = diff.DiffMain( o, n ); - diff.diffCleanupEfficiency( output ); - text = diff.diffPrettyHtml( output ); - - return text; - }; -}() ); +QUnit.diff = (function () { + function DiffMatchPatch () { + } + + // DIFF FUNCTIONS + + /** + * The data structure representing a diff is an array of tuples: + * [[DIFF_DELETE, 'Hello'], [DIFF_INSERT, 'Goodbye'], [DIFF_EQUAL, ' world.']] + * which means: delete 'Hello', add 'Goodbye' and keep ' world.' + */ + const DIFF_DELETE = -1; + const DIFF_INSERT = 1; + const DIFF_EQUAL = 0; + const hasOwn = Object.prototype.hasOwnProperty; + + /** + * Find the differences between two texts. Simplifies the problem by stripping + * any common prefix or suffix off the texts before diffing. + * @param {string} text1 Old string to be diffed. + * @param {string} text2 New string to be diffed. + * @param {boolean=} optChecklines Optional speedup flag. If present and false, + * then don't run a line-level diff first to identify the changed areas. + * Defaults to true, which does a faster, slightly less optimal diff. + * @return {!Array.} Array of diff tuples. + */ + DiffMatchPatch.prototype.DiffMain = function (text1, text2, optChecklines) { + let deadline, checklines, commonlength, + commonprefix, commonsuffix, diffs; + + // The diff must be complete in up to 1 second. + deadline = (new Date()).getTime() + 1000; + + // Check for null inputs. + if (text1 === null || text2 === null) { + throw new Error('Null input. (DiffMain)'); + } + + // Check for equality (speedup). + if (text1 === text2) { + if (text1) { + return [ + [DIFF_EQUAL, text1] + ]; + } + return []; + } + + if (typeof optChecklines === 'undefined') { + optChecklines = true; + } + + checklines = optChecklines; + + // Trim off common prefix (speedup). + commonlength = this.diffCommonPrefix(text1, text2); + commonprefix = text1.substring(0, commonlength); + text1 = text1.substring(commonlength); + text2 = text2.substring(commonlength); + + // Trim off common suffix (speedup). + commonlength = this.diffCommonSuffix(text1, text2); + commonsuffix = text1.substring(text1.length - commonlength); + text1 = text1.substring(0, text1.length - commonlength); + text2 = text2.substring(0, text2.length - commonlength); + + // Compute the diff on the middle block. + diffs = this.diffCompute(text1, text2, checklines, deadline); + + // Restore the prefix and suffix. + if (commonprefix) { + diffs.unshift([DIFF_EQUAL, commonprefix]); + } + if (commonsuffix) { + diffs.push([DIFF_EQUAL, commonsuffix]); + } + this.diffCleanupMerge(diffs); + return diffs; + }; + + /** + * Reduce the number of edits by eliminating operationally trivial equalities. + * @param {!Array.} diffs Array of diff tuples. + */ + DiffMatchPatch.prototype.diffCleanupEfficiency = function (diffs) { + let changes, equalities, equalitiesLength, lastequality, + pointer, preIns, preDel, postIns, postDel; + changes = false; + equalities = []; // Stack of indices where equalities are found. + equalitiesLength = 0; // Keeping our own length var is faster in JS. + /** @type {?string} */ + lastequality = null; + + // Always equal to diffs[equalities[equalitiesLength - 1]][1] + pointer = 0; // Index of current position. + + // Is there an insertion operation before the last equality. + preIns = false; + + // Is there a deletion operation before the last equality. + preDel = false; + + // Is there an insertion operation after the last equality. + postIns = false; + + // Is there a deletion operation after the last equality. + postDel = false; + while (pointer < diffs.length) { + // Equality found. + if (diffs[pointer][0] === DIFF_EQUAL) { + if (diffs[pointer][1].length < 4 && (postIns || postDel)) { + // Candidate found. + equalities[equalitiesLength++] = pointer; + preIns = postIns; + preDel = postDel; + lastequality = diffs[pointer][1]; + } else { + // Not a candidate, and can never become one. + equalitiesLength = 0; + lastequality = null; + } + postIns = postDel = false; + + // An insertion or deletion. + } else { + if (diffs[pointer][0] === DIFF_DELETE) { + postDel = true; + } else { + postIns = true; + } + + /* + * Five types to be split: + * ABXYCD + * AXCD + * ABXC + * AXCD + * ABXC + */ + if (lastequality && ((preIns && preDel && postIns && postDel) || + ((lastequality.length < 2) && + (preIns + preDel + postIns + postDel) === 3))) { + // Duplicate record. + diffs.splice( + equalities[equalitiesLength - 1], + 0, + [DIFF_DELETE, lastequality] + ); + + // Change second copy to insert. + diffs[equalities[equalitiesLength - 1] + 1][0] = DIFF_INSERT; + equalitiesLength--; // Throw away the equality we just deleted; + lastequality = null; + if (preIns && preDel) { + // No changes made which could affect previous entry, keep going. + postIns = postDel = true; + equalitiesLength = 0; + } else { + equalitiesLength--; // Throw away the previous equality. + pointer = equalitiesLength > 0 ? equalities[equalitiesLength - 1] : -1; + postIns = postDel = false; + } + changes = true; + } + } + pointer++; + } + + if (changes) { + this.diffCleanupMerge(diffs); + } + }; + + /** + * Convert a diff array into a pretty HTML report. + * @param {!Array.} diffs Array of diff tuples. + * @param {integer} string to be beautified. + * @return {string} HTML representation. + */ + DiffMatchPatch.prototype.diffPrettyHtml = function (diffs) { + const html = []; + for (let x = 0; x < diffs.length; x++) { + const op = diffs[x][0]; // Operation (insert, delete, equal) + const data = diffs[x][1]; // Text of change. + switch (op) { + case DIFF_INSERT: + html[x] = '' + escapeText(data) + ''; + break; + case DIFF_DELETE: + html[x] = '' + escapeText(data) + ''; + break; + case DIFF_EQUAL: + html[x] = '' + escapeText(data) + ''; + break; + } + } + return html.join(''); + }; + + /** + * Determine the common prefix of two strings. + * @param {string} text1 First string. + * @param {string} text2 Second string. + * @return {number} The number of characters common to the start of each + * string. + */ + DiffMatchPatch.prototype.diffCommonPrefix = function (text1, text2) { + let pointermid, pointermax, pointermin, pointerstart; + + // Quick check for common null cases. + if (!text1 || !text2 || text1.charAt(0) !== text2.charAt(0)) { + return 0; + } + + // Binary search. + // Performance analysis: https://neil.fraser.name/news/2007/10/09/ + pointermin = 0; + pointermax = Math.min(text1.length, text2.length); + pointermid = pointermax; + pointerstart = 0; + while (pointermin < pointermid) { + if (text1.substring(pointerstart, pointermid) === + text2.substring(pointerstart, pointermid)) { + pointermin = pointermid; + pointerstart = pointermin; + } else { + pointermax = pointermid; + } + pointermid = Math.floor((pointermax - pointermin) / 2 + pointermin); + } + return pointermid; + }; + + /** + * Determine the common suffix of two strings. + * @param {string} text1 First string. + * @param {string} text2 Second string. + * @return {number} The number of characters common to the end of each string. + */ + DiffMatchPatch.prototype.diffCommonSuffix = function (text1, text2) { + let pointermid, pointermax, pointermin, pointerend; + + // Quick check for common null cases. + if (!text1 || + !text2 || + text1.charAt(text1.length - 1) !== text2.charAt(text2.length - 1)) { + return 0; + } + + // Binary search. + // Performance analysis: https://neil.fraser.name/news/2007/10/09/ + pointermin = 0; + pointermax = Math.min(text1.length, text2.length); + pointermid = pointermax; + pointerend = 0; + while (pointermin < pointermid) { + if (text1.substring(text1.length - pointermid, text1.length - pointerend) === + text2.substring(text2.length - pointermid, text2.length - pointerend)) { + pointermin = pointermid; + pointerend = pointermin; + } else { + pointermax = pointermid; + } + pointermid = Math.floor((pointermax - pointermin) / 2 + pointermin); + } + return pointermid; + }; + + /** + * Find the differences between two texts. Assumes that the texts do not + * have any common prefix or suffix. + * @param {string} text1 Old string to be diffed. + * @param {string} text2 New string to be diffed. + * @param {boolean} checklines Speedup flag. If false, then don't run a + * line-level diff first to identify the changed areas. + * If true, then run a faster, slightly less optimal diff. + * @param {number} deadline Time when the diff should be complete by. + * @return {!Array.} Array of diff tuples. + * @private + */ + DiffMatchPatch.prototype.diffCompute = function (text1, text2, checklines, deadline) { + let diffs, longtext, shorttext, i, hm, + text1A, text2A, text1B, text2B, + midCommon, diffsA, diffsB; + + if (!text1) { + // Just add some text (speedup). + return [ + [DIFF_INSERT, text2] + ]; + } + + if (!text2) { + // Just delete some text (speedup). + return [ + [DIFF_DELETE, text1] + ]; + } + + longtext = text1.length > text2.length ? text1 : text2; + shorttext = text1.length > text2.length ? text2 : text1; + i = longtext.indexOf(shorttext); + if (i !== -1) { + // Shorter text is inside the longer text (speedup). + diffs = [ + [DIFF_INSERT, longtext.substring(0, i)], + [DIFF_EQUAL, shorttext], + [DIFF_INSERT, longtext.substring(i + shorttext.length)] + ]; + + // Swap insertions for deletions if diff is reversed. + if (text1.length > text2.length) { + diffs[0][0] = diffs[2][0] = DIFF_DELETE; + } + return diffs; + } + + if (shorttext.length === 1) { + // Single character string. + // After the previous speedup, the character can't be an equality. + return [ + [DIFF_DELETE, text1], + [DIFF_INSERT, text2] + ]; + } + + // Check to see if the problem can be split in two. + hm = this.diffHalfMatch(text1, text2); + if (hm) { + // A half-match was found, sort out the return data. + text1A = hm[0]; + text1B = hm[1]; + text2A = hm[2]; + text2B = hm[3]; + midCommon = hm[4]; + + // Send both pairs off for separate processing. + diffsA = this.DiffMain(text1A, text2A, checklines, deadline); + diffsB = this.DiffMain(text1B, text2B, checklines, deadline); + + // Merge the results. + return diffsA.concat([ + [DIFF_EQUAL, midCommon] + ], diffsB); + } + + if (checklines && text1.length > 100 && text2.length > 100) { + return this.diffLineMode(text1, text2, deadline); + } + + return this.diffBisect(text1, text2, deadline); + }; + + /** + * Do the two texts share a substring which is at least half the length of the + * longer text? + * This speedup can produce non-minimal diffs. + * @param {string} text1 First string. + * @param {string} text2 Second string. + * @return {Array.} Five element Array, containing the prefix of + * text1, the suffix of text1, the prefix of text2, the suffix of + * text2 and the common middle. Or null if there was no match. + * @private + */ + DiffMatchPatch.prototype.diffHalfMatch = function (text1, text2) { + let longtext, shorttext, dmp, + text1A, text2B, text2A, text1B, midCommon, + hm1, hm2, hm; + + longtext = text1.length > text2.length ? text1 : text2; + shorttext = text1.length > text2.length ? text2 : text1; + if (longtext.length < 4 || shorttext.length * 2 < longtext.length) { + return null; // Pointless. + } + dmp = this; // 'this' becomes 'window' in a closure. + + /** + * Does a substring of shorttext exist within longtext such that the substring + * is at least half the length of longtext? + * Closure, but does not reference any external variables. + * @param {string} longtext Longer string. + * @param {string} shorttext Shorter string. + * @param {number} i Start index of quarter length substring within longtext. + * @return {Array.} Five element Array, containing the prefix of + * longtext, the suffix of longtext, the prefix of shorttext, the suffix + * of shorttext and the common middle. Or null if there was no match. + * @private + */ + function diffHalfMatchI (longtext, shorttext, i) { + let seed, j, bestCommon, prefixLength, suffixLength, + bestLongtextA, bestLongtextB, bestShorttextA, bestShorttextB; + + // Start with a 1/4 length substring at position i as a seed. + seed = longtext.substring(i, i + Math.floor(longtext.length / 4)); + j = -1; + bestCommon = ''; + while ((j = shorttext.indexOf(seed, j + 1)) !== -1) { + prefixLength = dmp.diffCommonPrefix(longtext.substring(i), + shorttext.substring(j)); + suffixLength = dmp.diffCommonSuffix(longtext.substring(0, i), + shorttext.substring(0, j)); + if (bestCommon.length < suffixLength + prefixLength) { + bestCommon = shorttext.substring(j - suffixLength, j) + + shorttext.substring(j, j + prefixLength); + bestLongtextA = longtext.substring(0, i - suffixLength); + bestLongtextB = longtext.substring(i + prefixLength); + bestShorttextA = shorttext.substring(0, j - suffixLength); + bestShorttextB = shorttext.substring(j + prefixLength); + } + } + if (bestCommon.length * 2 >= longtext.length) { + return [bestLongtextA, bestLongtextB, + bestShorttextA, bestShorttextB, bestCommon + ]; + } else { + return null; + } + } + + // First check if the second quarter is the seed for a half-match. + hm1 = diffHalfMatchI(longtext, shorttext, + Math.ceil(longtext.length / 4)); + + // Check again based on the third quarter. + hm2 = diffHalfMatchI(longtext, shorttext, + Math.ceil(longtext.length / 2)); + if (!hm1 && !hm2) { + return null; + } else if (!hm2) { + hm = hm1; + } else if (!hm1) { + hm = hm2; + } else { + // Both matched. Select the longest. + hm = hm1[4].length > hm2[4].length ? hm1 : hm2; + } + + // A half-match was found, sort out the return data. + if (text1.length > text2.length) { + text1A = hm[0]; + text1B = hm[1]; + text2A = hm[2]; + text2B = hm[3]; + } else { + text2A = hm[0]; + text2B = hm[1]; + text1A = hm[2]; + text1B = hm[3]; + } + midCommon = hm[4]; + return [text1A, text1B, text2A, text2B, midCommon]; + }; + + /** + * Do a quick line-level diff on both strings, then rediff the parts for + * greater accuracy. + * This speedup can produce non-minimal diffs. + * @param {string} text1 Old string to be diffed. + * @param {string} text2 New string to be diffed. + * @param {number} deadline Time when the diff should be complete by. + * @return {!Array.} Array of diff tuples. + * @private + */ + DiffMatchPatch.prototype.diffLineMode = function (text1, text2, deadline) { + let a, diffs, linearray, pointer, countInsert, + countDelete, textInsert, textDelete, j; + + // Scan the text on a line-by-line basis first. + a = this.diffLinesToChars(text1, text2); + text1 = a.chars1; + text2 = a.chars2; + linearray = a.lineArray; + + diffs = this.DiffMain(text1, text2, false, deadline); + + // Convert the diff back to original text. + this.diffCharsToLines(diffs, linearray); + + // Eliminate freak matches (e.g. blank lines) + this.diffCleanupSemantic(diffs); + + // Rediff any replacement blocks, this time character-by-character. + // Add a dummy entry at the end. + diffs.push([DIFF_EQUAL, '']); + pointer = 0; + countDelete = 0; + countInsert = 0; + textDelete = ''; + textInsert = ''; + while (pointer < diffs.length) { + switch (diffs[pointer][0]) { + case DIFF_INSERT: + countInsert++; + textInsert += diffs[pointer][1]; + break; + case DIFF_DELETE: + countDelete++; + textDelete += diffs[pointer][1]; + break; + case DIFF_EQUAL: + + // Upon reaching an equality, check for prior redundancies. + if (countDelete >= 1 && countInsert >= 1) { + // Delete the offending records and add the merged ones. + diffs.splice(pointer - countDelete - countInsert, + countDelete + countInsert); + pointer = pointer - countDelete - countInsert; + a = this.DiffMain(textDelete, textInsert, false, deadline); + for (j = a.length - 1; j >= 0; j--) { + diffs.splice(pointer, 0, a[j]); + } + pointer = pointer + a.length; + } + countInsert = 0; + countDelete = 0; + textDelete = ''; + textInsert = ''; + break; + } + pointer++; + } + diffs.pop(); // Remove the dummy entry at the end. + + return diffs; + }; + + /** + * Find the 'middle snake' of a diff, split the problem in two + * and return the recursively constructed diff. + * See Myers 1986 paper: An O(ND) Difference Algorithm and Its Variations. + * @param {string} text1 Old string to be diffed. + * @param {string} text2 New string to be diffed. + * @param {number} deadline Time at which to bail if not yet complete. + * @return {!Array.} Array of diff tuples. + * @private + */ + DiffMatchPatch.prototype.diffBisect = function (text1, text2, deadline) { + let text1Length, text2Length, maxD, vOffset, vLength, + v1, v2, x, delta, front, k1start, k1end, k2start, + k2end, k2Offset, k1Offset, x1, x2, y1, y2, d, k1, k2; + + // Cache the text lengths to prevent multiple calls. + text1Length = text1.length; + text2Length = text2.length; + maxD = Math.ceil((text1Length + text2Length) / 2); + vOffset = maxD; + vLength = 2 * maxD; + v1 = new Array(vLength); + v2 = new Array(vLength); + + // Setting all elements to -1 is faster in Chrome & Firefox than mixing + // integers and undefined. + for (x = 0; x < vLength; x++) { + v1[x] = -1; + v2[x] = -1; + } + v1[vOffset + 1] = 0; + v2[vOffset + 1] = 0; + delta = text1Length - text2Length; + + // If the total number of characters is odd, then the front path will collide + // with the reverse path. + front = (delta % 2 !== 0); + + // Offsets for start and end of k loop. + // Prevents mapping of space beyond the grid. + k1start = 0; + k1end = 0; + k2start = 0; + k2end = 0; + for (d = 0; d < maxD; d++) { + // Bail out if deadline is reached. + if ((new Date()).getTime() > deadline) { + break; + } + + // Walk the front path one step. + for (k1 = -d + k1start; k1 <= d - k1end; k1 += 2) { + k1Offset = vOffset + k1; + if (k1 === -d || (k1 !== d && v1[k1Offset - 1] < v1[k1Offset + 1])) { + x1 = v1[k1Offset + 1]; + } else { + x1 = v1[k1Offset - 1] + 1; + } + y1 = x1 - k1; + while (x1 < text1Length && y1 < text2Length && + text1.charAt(x1) === text2.charAt(y1)) { + x1++; + y1++; + } + v1[k1Offset] = x1; + if (x1 > text1Length) { + // Ran off the right of the graph. + k1end += 2; + } else if (y1 > text2Length) { + // Ran off the bottom of the graph. + k1start += 2; + } else if (front) { + k2Offset = vOffset + delta - k1; + if (k2Offset >= 0 && k2Offset < vLength && v2[k2Offset] !== -1) { + // Mirror x2 onto top-left coordinate system. + x2 = text1Length - v2[k2Offset]; + if (x1 >= x2) { + // Overlap detected. + return this.diffBisectSplit(text1, text2, x1, y1, deadline); + } + } + } + } + + // Walk the reverse path one step. + for (k2 = -d + k2start; k2 <= d - k2end; k2 += 2) { + k2Offset = vOffset + k2; + if (k2 === -d || (k2 !== d && v2[k2Offset - 1] < v2[k2Offset + 1])) { + x2 = v2[k2Offset + 1]; + } else { + x2 = v2[k2Offset - 1] + 1; + } + y2 = x2 - k2; + while (x2 < text1Length && y2 < text2Length && + text1.charAt(text1Length - x2 - 1) === + text2.charAt(text2Length - y2 - 1)) { + x2++; + y2++; + } + v2[k2Offset] = x2; + if (x2 > text1Length) { + // Ran off the left of the graph. + k2end += 2; + } else if (y2 > text2Length) { + // Ran off the top of the graph. + k2start += 2; + } else if (!front) { + k1Offset = vOffset + delta - k2; + if (k1Offset >= 0 && k1Offset < vLength && v1[k1Offset] !== -1) { + x1 = v1[k1Offset]; + y1 = vOffset + x1 - k1Offset; + + // Mirror x2 onto top-left coordinate system. + x2 = text1Length - x2; + if (x1 >= x2) { + // Overlap detected. + return this.diffBisectSplit(text1, text2, x1, y1, deadline); + } + } + } + } + } + + // Diff took too long and hit the deadline or + // number of diffs equals number of characters, no commonality at all. + return [ + [DIFF_DELETE, text1], + [DIFF_INSERT, text2] + ]; + }; + + /** + * Given the location of the 'middle snake', split the diff in two parts + * and recurse. + * @param {string} text1 Old string to be diffed. + * @param {string} text2 New string to be diffed. + * @param {number} x Index of split point in text1. + * @param {number} y Index of split point in text2. + * @param {number} deadline Time at which to bail if not yet complete. + * @return {!Array.} Array of diff tuples. + * @private + */ + DiffMatchPatch.prototype.diffBisectSplit = function (text1, text2, x, y, deadline) { + let text1a, text1b, text2a, text2b, diffs, diffsb; + text1a = text1.substring(0, x); + text2a = text2.substring(0, y); + text1b = text1.substring(x); + text2b = text2.substring(y); + + // Compute both diffs serially. + diffs = this.DiffMain(text1a, text2a, false, deadline); + diffsb = this.DiffMain(text1b, text2b, false, deadline); + + return diffs.concat(diffsb); + }; + + /** + * Reduce the number of edits by eliminating semantically trivial equalities. + * @param {!Array.} diffs Array of diff tuples. + */ + DiffMatchPatch.prototype.diffCleanupSemantic = function (diffs) { + let changes, equalities, equalitiesLength, lastequality, + pointer, lengthInsertions2, lengthDeletions2, lengthInsertions1, + lengthDeletions1, deletion, insertion, overlapLength1, overlapLength2; + changes = false; + equalities = []; // Stack of indices where equalities are found. + equalitiesLength = 0; // Keeping our own length var is faster in JS. + /** @type {?string} */ + lastequality = null; + + // Always equal to diffs[equalities[equalitiesLength - 1]][1] + pointer = 0; // Index of current position. + + // Number of characters that changed prior to the equality. + lengthInsertions1 = 0; + lengthDeletions1 = 0; + + // Number of characters that changed after the equality. + lengthInsertions2 = 0; + lengthDeletions2 = 0; + while (pointer < diffs.length) { + if (diffs[pointer][0] === DIFF_EQUAL) { // Equality found. + equalities[equalitiesLength++] = pointer; + lengthInsertions1 = lengthInsertions2; + lengthDeletions1 = lengthDeletions2; + lengthInsertions2 = 0; + lengthDeletions2 = 0; + lastequality = diffs[pointer][1]; + } else { // An insertion or deletion. + if (diffs[pointer][0] === DIFF_INSERT) { + lengthInsertions2 += diffs[pointer][1].length; + } else { + lengthDeletions2 += diffs[pointer][1].length; + } + + // Eliminate an equality that is smaller or equal to the edits on both + // sides of it. + if (lastequality && (lastequality.length <= + Math.max(lengthInsertions1, lengthDeletions1)) && + (lastequality.length <= Math.max(lengthInsertions2, + lengthDeletions2))) { + // Duplicate record. + diffs.splice( + equalities[equalitiesLength - 1], + 0, + [DIFF_DELETE, lastequality] + ); + + // Change second copy to insert. + diffs[equalities[equalitiesLength - 1] + 1][0] = DIFF_INSERT; + + // Throw away the equality we just deleted. + equalitiesLength--; + + // Throw away the previous equality (it needs to be reevaluated). + equalitiesLength--; + pointer = equalitiesLength > 0 ? equalities[equalitiesLength - 1] : -1; + + // Reset the counters. + lengthInsertions1 = 0; + lengthDeletions1 = 0; + lengthInsertions2 = 0; + lengthDeletions2 = 0; + lastequality = null; + changes = true; + } + } + pointer++; + } + + // Normalize the diff. + if (changes) { + this.diffCleanupMerge(diffs); + } + + // Find any overlaps between deletions and insertions. + // e.g: abcxxxxxxdef + // -> abcxxxdef + // e.g: xxxabcdefxxx + // -> defxxxabc + // Only extract an overlap if it is as big as the edit ahead or behind it. + pointer = 1; + while (pointer < diffs.length) { + if (diffs[pointer - 1][0] === DIFF_DELETE && + diffs[pointer][0] === DIFF_INSERT) { + deletion = diffs[pointer - 1][1]; + insertion = diffs[pointer][1]; + overlapLength1 = this.diffCommonOverlap(deletion, insertion); + overlapLength2 = this.diffCommonOverlap(insertion, deletion); + if (overlapLength1 >= overlapLength2) { + if (overlapLength1 >= deletion.length / 2 || + overlapLength1 >= insertion.length / 2) { + // Overlap found. Insert an equality and trim the surrounding edits. + diffs.splice( + pointer, + 0, + [DIFF_EQUAL, insertion.substring(0, overlapLength1)] + ); + diffs[pointer - 1][1] = + deletion.substring(0, deletion.length - overlapLength1); + diffs[pointer + 1][1] = insertion.substring(overlapLength1); + pointer++; + } + } else { + if (overlapLength2 >= deletion.length / 2 || + overlapLength2 >= insertion.length / 2) { + // Reverse overlap found. + // Insert an equality and swap and trim the surrounding edits. + diffs.splice( + pointer, + 0, + [DIFF_EQUAL, deletion.substring(0, overlapLength2)] + ); + + diffs[pointer - 1][0] = DIFF_INSERT; + diffs[pointer - 1][1] = + insertion.substring(0, insertion.length - overlapLength2); + diffs[pointer + 1][0] = DIFF_DELETE; + diffs[pointer + 1][1] = + deletion.substring(overlapLength2); + pointer++; + } + } + pointer++; + } + pointer++; + } + }; + + /** + * Determine if the suffix of one string is the prefix of another. + * @param {string} text1 First string. + * @param {string} text2 Second string. + * @return {number} The number of characters common to the end of the first + * string and the start of the second string. + * @private + */ + DiffMatchPatch.prototype.diffCommonOverlap = function (text1, text2) { + let text1Length, text2Length, textLength, + best, length, pattern, found; + + // Cache the text lengths to prevent multiple calls. + text1Length = text1.length; + text2Length = text2.length; + + // Eliminate the null case. + if (text1Length === 0 || text2Length === 0) { + return 0; + } + + // Truncate the longer string. + if (text1Length > text2Length) { + text1 = text1.substring(text1Length - text2Length); + } else if (text1Length < text2Length) { + text2 = text2.substring(0, text1Length); + } + textLength = Math.min(text1Length, text2Length); + + // Quick check for the worst case. + if (text1 === text2) { + return textLength; + } + + // Start by looking for a single character match + // and increase length until no match is found. + // Performance analysis: https://neil.fraser.name/news/2010/11/04/ + best = 0; + length = 1; + while (true) { + pattern = text1.substring(textLength - length); + found = text2.indexOf(pattern); + if (found === -1) { + return best; + } + length += found; + if (found === 0 || text1.substring(textLength - length) === + text2.substring(0, length)) { + best = length; + length++; + } + } + }; + + /** + * Split two texts into an array of strings. Reduce the texts to a string of + * hashes where each Unicode character represents one line. + * @param {string} text1 First string. + * @param {string} text2 Second string. + * @return {{chars1: string, chars2: string, lineArray: !Array.}} + * An object containing the encoded text1, the encoded text2 and + * the array of unique strings. + * The zeroth element of the array of unique strings is intentionally blank. + * @private + */ + DiffMatchPatch.prototype.diffLinesToChars = function (text1, text2) { + let lineArray, lineHash, chars1, chars2; + lineArray = []; // E.g. lineArray[4] === 'Hello\n' + lineHash = {}; // E.g. lineHash['Hello\n'] === 4 + + // '\x00' is a valid character, but various debuggers don't like it. + // So we'll insert a junk entry to avoid generating a null character. + lineArray[0] = ''; + + /** + * Split a text into an array of strings. Reduce the texts to a string of + * hashes where each Unicode character represents one line. + * Modifies linearray and linehash through being a closure. + * @param {string} text String to encode. + * @return {string} Encoded string. + * @private + */ + function diffLinesToCharsMunge (text) { + let chars = ''; + + // Walk the text, pulling out a substring for each line. + // text.split('\n') would would temporarily double our memory footprint. + // Modifying text would create many large strings to garbage collect. + let lineStart = 0; + let lineEnd = -1; + + // Keeping our own length variable is faster than looking it up. + let lineArrayLength = lineArray.length; + while (lineEnd < text.length - 1) { + lineEnd = text.indexOf('\n', lineStart); + if (lineEnd === -1) { + lineEnd = text.length - 1; + } + let line = text.substring(lineStart, lineEnd + 1); + lineStart = lineEnd + 1; + + if (hasOwn.call(lineHash, line)) { + chars += String.fromCharCode(lineHash[line]); + } else { + chars += String.fromCharCode(lineArrayLength); + lineHash[line] = lineArrayLength; + lineArray[lineArrayLength++] = line; + } + } + return chars; + } + + chars1 = diffLinesToCharsMunge(text1); + chars2 = diffLinesToCharsMunge(text2); + return { + chars1: chars1, + chars2: chars2, + lineArray: lineArray + }; + }; + + /** + * Rehydrate the text in a diff from a string of line hashes to real lines of + * text. + * @param {!Array.} diffs Array of diff tuples. + * @param {!Array.} lineArray Array of unique strings. + * @private + */ + DiffMatchPatch.prototype.diffCharsToLines = function (diffs, lineArray) { + let x, chars, text, y; + for (x = 0; x < diffs.length; x++) { + chars = diffs[x][1]; + text = []; + for (y = 0; y < chars.length; y++) { + text[y] = lineArray[chars.charCodeAt(y)]; + } + diffs[x][1] = text.join(''); + } + }; + + /** + * Reorder and merge like edit sections. Merge equalities. + * Any edit section can move as long as it doesn't cross an equality. + * @param {!Array.} diffs Array of diff tuples. + */ + DiffMatchPatch.prototype.diffCleanupMerge = function (diffs) { + let pointer, countDelete, countInsert, textInsert, textDelete, + commonlength, changes, diffPointer, position; + diffs.push([DIFF_EQUAL, '']); // Add a dummy entry at the end. + pointer = 0; + countDelete = 0; + countInsert = 0; + textDelete = ''; + textInsert = ''; + + while (pointer < diffs.length) { + switch (diffs[pointer][0]) { + case DIFF_INSERT: + countInsert++; + textInsert += diffs[pointer][1]; + pointer++; + break; + case DIFF_DELETE: + countDelete++; + textDelete += diffs[pointer][1]; + pointer++; + break; + case DIFF_EQUAL: + + // Upon reaching an equality, check for prior redundancies. + if (countDelete + countInsert > 1) { + if (countDelete !== 0 && countInsert !== 0) { + // Factor out any common prefixes. + commonlength = this.diffCommonPrefix(textInsert, textDelete); + if (commonlength !== 0) { + if ((pointer - countDelete - countInsert) > 0 && + diffs[pointer - countDelete - countInsert - 1][0] === + DIFF_EQUAL) { + diffs[pointer - countDelete - countInsert - 1][1] += + textInsert.substring(0, commonlength); + } else { + diffs.splice(0, 0, [DIFF_EQUAL, + textInsert.substring(0, commonlength) + ]); + pointer++; + } + textInsert = textInsert.substring(commonlength); + textDelete = textDelete.substring(commonlength); + } + + // Factor out any common suffixies. + commonlength = this.diffCommonSuffix(textInsert, textDelete); + if (commonlength !== 0) { + diffs[pointer][1] = textInsert.substring(textInsert.length - + commonlength) + diffs[pointer][1]; + textInsert = textInsert.substring(0, textInsert.length - + commonlength); + textDelete = textDelete.substring(0, textDelete.length - + commonlength); + } + } + + // Delete the offending records and add the merged ones. + if (countDelete === 0) { + diffs.splice(pointer - countInsert, + countDelete + countInsert, [DIFF_INSERT, textInsert]); + } else if (countInsert === 0) { + diffs.splice(pointer - countDelete, + countDelete + countInsert, [DIFF_DELETE, textDelete]); + } else { + diffs.splice( + pointer - countDelete - countInsert, + countDelete + countInsert, + [DIFF_DELETE, textDelete], [DIFF_INSERT, textInsert] + ); + } + pointer = pointer - countDelete - countInsert + + (countDelete ? 1 : 0) + (countInsert ? 1 : 0) + 1; + } else if (pointer !== 0 && diffs[pointer - 1][0] === DIFF_EQUAL) { + // Merge this equality with the previous one. + diffs[pointer - 1][1] += diffs[pointer][1]; + diffs.splice(pointer, 1); + } else { + pointer++; + } + countInsert = 0; + countDelete = 0; + textDelete = ''; + textInsert = ''; + break; + } + } + if (diffs[diffs.length - 1][1] === '') { + diffs.pop(); // Remove the dummy entry at the end. + } + + // Second pass: look for single edits surrounded on both sides by equalities + // which can be shifted sideways to eliminate an equality. + // e.g: ABAC -> ABAC + changes = false; + pointer = 1; + + // Intentionally ignore the first and last element (don't need checking). + while (pointer < diffs.length - 1) { + if (diffs[pointer - 1][0] === DIFF_EQUAL && + diffs[pointer + 1][0] === DIFF_EQUAL) { + diffPointer = diffs[pointer][1]; + position = diffPointer.substring( + diffPointer.length - diffs[pointer - 1][1].length + ); + + // This is a single edit surrounded by equalities. + if (position === diffs[pointer - 1][1]) { + // Shift the edit over the previous equality. + diffs[pointer][1] = diffs[pointer - 1][1] + + diffs[pointer][1].substring(0, diffs[pointer][1].length - + diffs[pointer - 1][1].length); + diffs[pointer + 1][1] = + diffs[pointer - 1][1] + diffs[pointer + 1][1]; + diffs.splice(pointer - 1, 1); + changes = true; + } else if (diffPointer.substring(0, diffs[pointer + 1][1].length) === + diffs[pointer + 1][1]) { + // Shift the edit over the next equality. + diffs[pointer - 1][1] += diffs[pointer + 1][1]; + diffs[pointer][1] = + diffs[pointer][1].substring(diffs[pointer + 1][1].length) + + diffs[pointer + 1][1]; + diffs.splice(pointer + 1, 1); + changes = true; + } + } + pointer++; + } + + // If shifts were made, the diff needs reordering and another shift sweep. + if (changes) { + this.diffCleanupMerge(diffs); + } + }; + + return function (o, n) { + let diff, output, text; + diff = new DiffMatchPatch(); + output = diff.DiffMain(o, n); + diff.diffCleanupEfficiency(output); + text = diff.diffPrettyHtml(output); + + return text; + }; +}()); diff --git a/src/html-reporter/fixture.js b/src/html-reporter/fixture.js index 2ec2e036b..e4c5807de 100644 --- a/src/html-reporter/fixture.js +++ b/src/html-reporter/fixture.js @@ -1,52 +1,48 @@ -import QUnit from "../core"; -import { window, document } from "../globals"; - -( function() { - - if ( !window || !document ) { - return; - } - - var config = QUnit.config, - hasOwn = Object.prototype.hasOwnProperty; - - // Stores fixture HTML for resetting later - function storeFixture() { - - // Avoid overwriting user-defined values - if ( hasOwn.call( config, "fixture" ) ) { - return; - } - - var fixture = document.getElementById( "qunit-fixture" ); - if ( fixture ) { - config.fixture = fixture.cloneNode( true ); - } - } - - QUnit.begin( storeFixture ); - - // Resets the fixture DOM element if available. - function resetFixture() { - if ( config.fixture == null ) { - return; - } - - var fixture = document.getElementById( "qunit-fixture" ); - var resetFixtureType = typeof config.fixture; - if ( resetFixtureType === "string" ) { - - // support user defined values for `config.fixture` - var newFixture = document.createElement( "div" ); - newFixture.setAttribute( "id", "qunit-fixture" ); - newFixture.innerHTML = config.fixture; - fixture.parentNode.replaceChild( newFixture, fixture ); - } else { - const clonedFixture = config.fixture.cloneNode( true ); - fixture.parentNode.replaceChild( clonedFixture, fixture ); - } - } - - QUnit.testStart( resetFixture ); - -} )(); +import QUnit from '../core'; +import { window, document } from '../globals'; + +(function () { + if (!window || !document) { + return; + } + + const config = QUnit.config; + const hasOwn = Object.prototype.hasOwnProperty; + + // Stores fixture HTML for resetting later + function storeFixture () { + // Avoid overwriting user-defined values + if (hasOwn.call(config, 'fixture')) { + return; + } + + const fixture = document.getElementById('qunit-fixture'); + if (fixture) { + config.fixture = fixture.cloneNode(true); + } + } + + QUnit.begin(storeFixture); + + // Resets the fixture DOM element if available. + function resetFixture () { + if (config.fixture == null) { + return; + } + + const fixture = document.getElementById('qunit-fixture'); + const resetFixtureType = typeof config.fixture; + if (resetFixtureType === 'string') { + // support user defined values for `config.fixture` + const newFixture = document.createElement('div'); + newFixture.setAttribute('id', 'qunit-fixture'); + newFixture.innerHTML = config.fixture; + fixture.parentNode.replaceChild(newFixture, fixture); + } else { + const clonedFixture = config.fixture.cloneNode(true); + fixture.parentNode.replaceChild(clonedFixture, fixture); + } + } + + QUnit.testStart(resetFixture); +})(); diff --git a/src/html-reporter/html.js b/src/html-reporter/html.js index 1f57f49e4..f0da64837 100644 --- a/src/html-reporter/html.js +++ b/src/html-reporter/html.js @@ -1,1138 +1,1117 @@ -import QUnit from "../core"; -import { extend, errorString } from "../core/utilities"; -import { window, document, navigator, console } from "../globals"; -import "./urlparams"; -import fuzzysort from "fuzzysort"; +import QUnit from '../core'; +import { extend, errorString } from '../core/utilities'; +import { window, document, navigator, console } from '../globals'; +import './urlparams'; +import fuzzysort from 'fuzzysort'; const stats = { - failedTests: [], - defined: 0, - completed: 0 + failedTests: [], + defined: 0, + completed: 0 }; // Escape text for attribute or text content. -export function escapeText( s ) { - if ( !s ) { - return ""; - } - s = s + ""; - - // Both single quotes and double quotes (for attributes) - return s.replace( /['"<>&]/g, function( s ) { - switch ( s ) { - case "'": - return "'"; - case "\"": - return """; - case "<": - return "<"; - case ">": - return ">"; - case "&": - return "&"; - } - } ); +export function escapeText (s) { + if (!s) { + return ''; + } + s = s + ''; + + // Both single quotes and double quotes (for attributes) + return s.replace(/['"<>&]/g, function (s) { + switch (s) { + case "'": + return '''; + case '"': + return '"'; + case '<': + return '<'; + case '>': + return '>'; + case '&': + return '&'; + } + }); } -( function() { - - // Don't load the HTML Reporter on non-browser environments - if ( !window || !document ) { - return; - } - - var config = QUnit.config, - hiddenTests = [], - collapseNext = false, - hasOwn = Object.prototype.hasOwnProperty, - unfilteredUrl = setUrl( { filter: undefined, module: undefined, - moduleId: undefined, testId: undefined } ); - - - function trim( string ) { - if ( typeof string.trim === "function" ) { - return string.trim(); - } else { - return string.replace( /^\s+|\s+$/g, "" ); - } - } - - function addEvent( elem, type, fn ) { - elem.addEventListener( type, fn, false ); - } - - function removeEvent( elem, type, fn ) { - elem.removeEventListener( type, fn, false ); - } - - function addEvents( elems, type, fn ) { - var i = elems.length; - while ( i-- ) { - addEvent( elems[ i ], type, fn ); - } - } - - function hasClass( elem, name ) { - return ( " " + elem.className + " " ).indexOf( " " + name + " " ) >= 0; - } - - function addClass( elem, name ) { - if ( !hasClass( elem, name ) ) { - elem.className += ( elem.className ? " " : "" ) + name; - } - } - - function toggleClass( elem, name, force ) { - if ( force || typeof force === "undefined" && !hasClass( elem, name ) ) { - addClass( elem, name ); - } else { - removeClass( elem, name ); - } - } - - function removeClass( elem, name ) { - var set = " " + elem.className + " "; - - // Class name may appear multiple times - while ( set.indexOf( " " + name + " " ) >= 0 ) { - set = set.replace( " " + name + " ", " " ); - } - - // Trim for prettiness - elem.className = trim( set ); - } - - function id( name ) { - return document.getElementById && document.getElementById( name ); - } - - function abortTests() { - var abortButton = id( "qunit-abort-tests-button" ); - if ( abortButton ) { - abortButton.disabled = true; - abortButton.innerHTML = "Aborting..."; - } - QUnit.config.queue.length = 0; - return false; - } - - function interceptNavigation( ev ) { - - // Trim potential accidental whitespace so that QUnit doesn't throw an error about no tests matching the filter. - var filterInputElem = id( "qunit-filter-input" ); - filterInputElem.value = trim( filterInputElem.value ); - - applyUrlParams(); - - if ( ev && ev.preventDefault ) { - ev.preventDefault(); - } - - return false; - } - - function getUrlConfigHtml() { - var i, j, val, - escaped, escapedTooltip, - selection = false, - urlConfig = config.urlConfig, - urlConfigHtml = ""; - - for ( i = 0; i < urlConfig.length; i++ ) { - - // Options can be either strings or objects with nonempty "id" properties - val = config.urlConfig[ i ]; - if ( typeof val === "string" ) { - val = { - id: val, - label: val - }; - } - - escaped = escapeText( val.id ); - escapedTooltip = escapeText( val.tooltip ); - - if ( !val.value || typeof val.value === "string" ) { - urlConfigHtml += ""; - } else { - urlConfigHtml += ""; - } - } - - return urlConfigHtml; - } - - // Handle "click" events on toolbar checkboxes and "change" for select menus. - // Updates the URL with the new state of `config.urlConfig` values. - function toolbarChanged() { - var updatedUrl, value, tests, - field = this, - params = {}; - - // Detect if field is a select menu or a checkbox - if ( "selectedIndex" in field ) { - value = field.options[ field.selectedIndex ].value || undefined; - } else { - value = field.checked ? ( field.defaultValue || true ) : undefined; - } - - params[ field.name ] = value; - updatedUrl = setUrl( params ); - - // Check if we can apply the change without a page refresh - if ( "hidepassed" === field.name && "replaceState" in window.history ) { - QUnit.urlParams[ field.name ] = value; - config[ field.name ] = value || false; - tests = id( "qunit-tests" ); - if ( tests ) { - var length = tests.children.length; - var children = tests.children; - - if ( field.checked ) { - for ( var i = 0; i < length; i++ ) { - var test = children[ i ]; - var className = test ? test.className : ""; - var classNameHasPass = className.indexOf( "pass" ) > -1; - var classNameHasSkipped = className.indexOf( "skipped" ) > -1; - - if ( classNameHasPass || classNameHasSkipped ) { - hiddenTests.push( test ); - } - } - - for ( const hiddenTest of hiddenTests ) { - tests.removeChild( hiddenTest ); - } - } else { - while ( ( test = hiddenTests.pop() ) != null ) { - tests.appendChild( test ); - } - } - } - window.history.replaceState( null, "", updatedUrl ); - } else { - window.location = updatedUrl; - } - } - - function setUrl( params ) { - var key, arrValue, i, - querystring = "?", - location = window.location; - - params = extend( extend( {}, QUnit.urlParams ), params ); - - for ( key in params ) { - - // Skip inherited or undefined properties - if ( hasOwn.call( params, key ) && params[ key ] !== undefined ) { - - // Output a parameter for each value of this key - // (but usually just one) - arrValue = [].concat( params[ key ] ); - for ( i = 0; i < arrValue.length; i++ ) { - querystring += encodeURIComponent( key ); - if ( arrValue[ i ] !== true ) { - querystring += "=" + encodeURIComponent( arrValue[ i ] ); - } - querystring += "&"; - } - } - } - return location.protocol + "//" + location.host + - location.pathname + querystring.slice( 0, -1 ); - } - - function applyUrlParams() { - var i, - selectedModules = [], - modulesList = id( "qunit-modulefilter-dropdown-list" ).getElementsByTagName( "input" ), - filter = id( "qunit-filter-input" ).value; - - for ( i = 0; i < modulesList.length; i++ ) { - if ( modulesList[ i ].checked ) { - selectedModules.push( modulesList[ i ].value ); - } - } - - window.location = setUrl( { - filter: ( filter === "" ) ? undefined : filter, - moduleId: ( selectedModules.length === 0 ) ? undefined : selectedModules, - - // Remove module and testId filter - module: undefined, - testId: undefined - } ); - } - - function toolbarUrlConfigContainer() { - var urlConfigContainer = document.createElement( "span" ); - - urlConfigContainer.innerHTML = getUrlConfigHtml(); - addClass( urlConfigContainer, "qunit-url-config" ); - - addEvents( urlConfigContainer.getElementsByTagName( "input" ), "change", toolbarChanged ); - addEvents( urlConfigContainer.getElementsByTagName( "select" ), "change", toolbarChanged ); - - return urlConfigContainer; - } - - function abortTestsButton() { - var button = document.createElement( "button" ); - button.id = "qunit-abort-tests-button"; - button.innerHTML = "Abort"; - addEvent( button, "click", abortTests ); - return button; - } - - function toolbarLooseFilter() { - var filter = document.createElement( "form" ), - label = document.createElement( "label" ), - input = document.createElement( "input" ), - button = document.createElement( "button" ); - - addClass( filter, "qunit-filter" ); - - label.innerHTML = "Filter: "; - - input.type = "text"; - input.value = config.filter || ""; - input.name = "filter"; - input.id = "qunit-filter-input"; - - button.innerHTML = "Go"; - - label.appendChild( input ); - - filter.appendChild( label ); - filter.appendChild( document.createTextNode( " " ) ); - filter.appendChild( button ); - addEvent( filter, "submit", interceptNavigation ); - - return filter; - } - - function moduleListHtml( modules ) { - var i, checked, - html = ""; - - for ( i = 0; i < modules.length; i++ ) { - if ( modules[ i ].name !== "" ) { - checked = config.moduleId.indexOf( modules[ i ].moduleId ) > -1; - html += "
  • "; - } - } - - return html; - } - - function toolbarModuleFilter() { - var commit, reset, - moduleFilter = document.createElement( "form" ), - label = document.createElement( "label" ), - moduleSearch = document.createElement( "input" ), - dropDown = document.createElement( "div" ), - actions = document.createElement( "span" ), - applyButton = document.createElement( "button" ), - resetButton = document.createElement( "button" ), - allModulesLabel = document.createElement( "label" ), - allCheckbox = document.createElement( "input" ), - dropDownList = document.createElement( "ul" ), - dirty = false; - - moduleSearch.id = "qunit-modulefilter-search"; - moduleSearch.autocomplete = "off"; - addEvent( moduleSearch, "input", searchInput ); - addEvent( moduleSearch, "input", searchFocus ); - addEvent( moduleSearch, "focus", searchFocus ); - addEvent( moduleSearch, "click", searchFocus ); - - config.modules.forEach( module => module.namePrepared = fuzzysort.prepare( module.name ) ); - - label.id = "qunit-modulefilter-search-container"; - label.innerHTML = "Module: "; - label.appendChild( moduleSearch ); - - applyButton.textContent = "Apply"; - applyButton.style.display = "none"; - - resetButton.textContent = "Reset"; - resetButton.type = "reset"; - resetButton.style.display = "none"; - - allCheckbox.type = "checkbox"; - allCheckbox.checked = config.moduleId.length === 0; - - allModulesLabel.className = "clickable"; - if ( config.moduleId.length ) { - allModulesLabel.className = "checked"; - } - allModulesLabel.appendChild( allCheckbox ); - allModulesLabel.appendChild( document.createTextNode( "All modules" ) ); - - actions.id = "qunit-modulefilter-actions"; - actions.appendChild( applyButton ); - actions.appendChild( resetButton ); - actions.appendChild( allModulesLabel ); - commit = actions.firstChild; - reset = commit.nextSibling; - addEvent( commit, "click", applyUrlParams ); - - dropDownList.id = "qunit-modulefilter-dropdown-list"; - dropDownList.innerHTML = moduleListHtml( config.modules ); - - dropDown.id = "qunit-modulefilter-dropdown"; - dropDown.style.display = "none"; - dropDown.appendChild( actions ); - dropDown.appendChild( dropDownList ); - addEvent( dropDown, "change", selectionChange ); - selectionChange(); - - moduleFilter.id = "qunit-modulefilter"; - moduleFilter.appendChild( label ); - moduleFilter.appendChild( dropDown ); - addEvent( moduleFilter, "submit", interceptNavigation ); - addEvent( moduleFilter, "reset", function() { - - // Let the reset happen, then update styles - window.setTimeout( selectionChange ); - } ); - - // Enables show/hide for the dropdown - function searchFocus() { - if ( dropDown.style.display !== "none" ) { - return; - } - - dropDown.style.display = "block"; - addEvent( document, "click", hideHandler ); - addEvent( document, "keydown", hideHandler ); - - // Hide on Escape keydown or outside-container click - function hideHandler( e ) { - var inContainer = moduleFilter.contains( e.target ); - - if ( e.keyCode === 27 || !inContainer ) { - if ( e.keyCode === 27 && inContainer ) { - moduleSearch.focus(); - } - dropDown.style.display = "none"; - removeEvent( document, "click", hideHandler ); - removeEvent( document, "keydown", hideHandler ); - moduleSearch.value = ""; - searchInput(); - } - } - } - - function filterModules( searchText ) { - if ( searchText === "" ) { - return config.modules; - } - return fuzzysort - .go( searchText, config.modules, { key: "namePrepared", threshold: -10000 } ) - .map( module => module.obj ); - } - - // Processes module search box input - var searchInputTimeout; - function searchInput() { - window.clearTimeout( searchInputTimeout ); - searchInputTimeout = window.setTimeout( () => { - var searchText = moduleSearch.value.toLowerCase(), - filteredModules = filterModules( searchText ); - dropDownList.innerHTML = moduleListHtml( filteredModules ); - }, 200 ); - } - - // Processes selection changes - function selectionChange( evt ) { - var i, item, - checkbox = evt && evt.target || allCheckbox, - modulesList = dropDownList.getElementsByTagName( "input" ), - selectedNames = []; - - toggleClass( checkbox.parentNode, "checked", checkbox.checked ); - - dirty = false; - if ( checkbox.checked && checkbox !== allCheckbox ) { - allCheckbox.checked = false; - removeClass( allCheckbox.parentNode, "checked" ); - } - for ( i = 0; i < modulesList.length; i++ ) { - item = modulesList[ i ]; - if ( !evt ) { - toggleClass( item.parentNode, "checked", item.checked ); - } else if ( checkbox === allCheckbox && checkbox.checked ) { - item.checked = false; - removeClass( item.parentNode, "checked" ); - } - dirty = dirty || ( item.checked !== item.defaultChecked ); - if ( item.checked ) { - selectedNames.push( item.parentNode.textContent ); - } - } - - commit.style.display = reset.style.display = dirty ? "" : "none"; - moduleSearch.placeholder = selectedNames.join( ", " ) || - allCheckbox.parentNode.textContent; - moduleSearch.title = "Type to filter list. Current selection:\n" + - ( selectedNames.join( "\n" ) || allCheckbox.parentNode.textContent ); - } - - return moduleFilter; - } - - function toolbarFilters() { - var toolbarFilters = document.createElement( "span" ); - - toolbarFilters.id = "qunit-toolbar-filters"; - toolbarFilters.appendChild( toolbarLooseFilter() ); - toolbarFilters.appendChild( toolbarModuleFilter() ); - - return toolbarFilters; - } - - function appendToolbar() { - var toolbar = id( "qunit-testrunner-toolbar" ); - - if ( toolbar ) { - toolbar.appendChild( toolbarUrlConfigContainer() ); - toolbar.appendChild( toolbarFilters() ); - toolbar.appendChild( document.createElement( "div" ) ).className = "clearfix"; - } - } - - function appendHeader() { - var header = id( "qunit-header" ); - - if ( header ) { - header.innerHTML = "" + header.innerHTML + - " "; - } - } - - function appendBanner() { - var banner = id( "qunit-banner" ); - - if ( banner ) { - banner.className = ""; - } - } - - function appendTestResults() { - var tests = id( "qunit-tests" ), - result = id( "qunit-testresult" ), - controls; - - if ( result ) { - result.parentNode.removeChild( result ); - } - - if ( tests ) { - tests.innerHTML = ""; - result = document.createElement( "p" ); - result.id = "qunit-testresult"; - result.className = "result"; - tests.parentNode.insertBefore( result, tests ); - result.innerHTML = "
    Running...
     
    " + - "
    " + - "
    "; - controls = id( "qunit-testresult-controls" ); - } - - if ( controls ) { - controls.appendChild( abortTestsButton() ); - } - } - - function appendFilteredTest() { - var testId = QUnit.config.testId; - if ( !testId || testId.length <= 0 ) { - return ""; - } - return "
    Rerunning selected tests: " + - escapeText( testId.join( ", " ) ) + - " Run all tests
    "; - } - - function appendUserAgent() { - var userAgent = id( "qunit-userAgent" ); - - if ( userAgent ) { - userAgent.innerHTML = ""; - userAgent.appendChild( - document.createTextNode( - "QUnit " + QUnit.version + "; " + navigator.userAgent - ) - ); - } - } - - function appendInterface() { - var qunit = id( "qunit" ); - - // For compat with QUnit 1.2, and to support fully custom theme HTML, - // we will use any existing elements if no id="qunit" element exists. - // - // Note that we don't fail or fallback to creating it ourselves, - // because not having id="qunit" (and not having the below elements) - // simply means QUnit acts headless, allowing users to use their own - // reporters, or for a test runner to listen for events directly without - // having the HTML reporter actively render anything. - if ( qunit ) { - - qunit.setAttribute( "role", "main" ); - - // Since QUnit 1.3, these are created automatically if the page - // contains id="qunit". - qunit.innerHTML = - "

    " + escapeText( document.title ) + "

    " + - "

    " + - "" + - appendFilteredTest() + - "

    " + - "
      "; - } - - appendHeader(); - appendBanner(); - appendTestResults(); - appendUserAgent(); - appendToolbar(); - } - - function appendTest( name, testId, moduleName ) { - var title, rerunTrigger, testBlock, assertList, - tests = id( "qunit-tests" ); - - if ( !tests ) { - return; - } - - title = document.createElement( "strong" ); - title.innerHTML = getNameHtml( name, moduleName ); - - testBlock = document.createElement( "li" ); - testBlock.appendChild( title ); - - // No ID or rerun link for "global failure" blocks - if ( testId !== undefined ) { - rerunTrigger = document.createElement( "a" ); - rerunTrigger.innerHTML = "Rerun"; - rerunTrigger.href = setUrl( { testId: testId } ); - - testBlock.id = "qunit-test-output-" + testId; - testBlock.appendChild( rerunTrigger ); - } - - assertList = document.createElement( "ol" ); - assertList.className = "qunit-assert-list"; - - testBlock.appendChild( assertList ); - - tests.appendChild( testBlock ); - - return testBlock; - } - - // HTML Reporter initialization and load - QUnit.on( "runStart", function( runStart ) { - - stats.defined = runStart.testCounts.total; - } ); - - QUnit.begin( function() { - - // Initialize QUnit elements - // This is done from begin() instead of runStart, because - // urlparams.js uses begin(), which we need to wait for. - // urlparams.js in turn uses begin() to allow plugins to - // add entries to QUnit.config.urlConfig, which may be done - // asynchronously. - // - appendInterface(); - } ); - - function getRerunFailedHtml( failedTests ) { - if ( failedTests.length === 0 ) { - return ""; - } - - var href = setUrl( { testId: failedTests } ); - return [ - "
      ", - failedTests.length === 1 ? - "Rerun 1 failed test" : - "Rerun " + failedTests.length + " failed tests", - "" - ].join( "" ); - } - - QUnit.on( "runEnd", function( runEnd ) { - var banner = id( "qunit-banner" ), - tests = id( "qunit-tests" ), - abortButton = id( "qunit-abort-tests-button" ), - assertPassed = config.stats.all - config.stats.bad, - html = [ - runEnd.testCounts.total, - " tests completed in ", - runEnd.runtime, - " milliseconds, with ", - runEnd.testCounts.failed, - " failed, ", - runEnd.testCounts.skipped, - " skipped, and ", - runEnd.testCounts.todo, - " todo.
      ", - "", - assertPassed, - " assertions of ", - config.stats.all, - " passed, ", - config.stats.bad, - " failed.", - getRerunFailedHtml( stats.failedTests ) - ].join( "" ), - test, - assertLi, - assertList; - - // Update remaining tests to aborted - if ( abortButton && abortButton.disabled ) { - html = "Tests aborted after " + runEnd.runtime + " milliseconds."; - - for ( var i = 0; i < tests.children.length; i++ ) { - test = tests.children[ i ]; - if ( test.className === "" || test.className === "running" ) { - test.className = "aborted"; - assertList = test.getElementsByTagName( "ol" )[ 0 ]; - assertLi = document.createElement( "li" ); - assertLi.className = "fail"; - assertLi.innerHTML = "Test aborted."; - assertList.appendChild( assertLi ); - } - } - } - - if ( banner && ( !abortButton || abortButton.disabled === false ) ) { - banner.className = runEnd.status === "failed" ? "qunit-fail" : "qunit-pass"; - } - - if ( abortButton ) { - abortButton.parentNode.removeChild( abortButton ); - } - - if ( tests ) { - id( "qunit-testresult-display" ).innerHTML = html; - } - - if ( config.altertitle && document.title ) { - - // Show ✖ for good, ✔ for bad suite result in title - // use escape sequences in case file gets loaded with non-utf-8 - // charset - document.title = [ - ( runEnd.status === "failed" ? "\u2716" : "\u2714" ), - document.title.replace( /^[\u2714\u2716] /i, "" ) - ].join( " " ); - } - - // Scroll back to top to show results - if ( config.scrolltop && window.scrollTo ) { - window.scrollTo( 0, 0 ); - } - } ); - - function getNameHtml( name, module ) { - var nameHtml = ""; - - if ( module ) { - nameHtml = "" + escapeText( module ) + ": "; - } - - nameHtml += "" + escapeText( name ) + ""; - - return nameHtml; - } - - function getProgressHtml( stats ) { - return [ - stats.completed, - " / ", - stats.defined, - " tests completed.
      " - ].join( "" ); - } - - QUnit.testStart( function( details ) { - var running, bad; - - appendTest( details.name, details.testId, details.module ); - - running = id( "qunit-testresult-display" ); - - if ( running ) { - addClass( running, "running" ); - - bad = QUnit.config.reorder && details.previousFailure; - - running.innerHTML = [ - getProgressHtml( stats ), - bad ? - "Rerunning previously failed test:
      " : - "Running: ", - getNameHtml( details.name, details.module ), - getRerunFailedHtml( stats.failedTests ) - ].join( "" ); - } - - } ); - - function stripHtml( string ) { - - // Strip tags, html entity and whitespaces - return string - .replace( /<\/?[^>]+(>|$)/g, "" ) - .replace( /"/g, "" ) - .replace( /\s+/g, "" ); - } - - QUnit.log( function( details ) { - var assertList, assertLi, - message, expected, actual, diff, - showDiff = false, - testItem = id( "qunit-test-output-" + details.testId ); - - if ( !testItem ) { - return; - } - - message = escapeText( details.message ) || ( details.result ? "okay" : "failed" ); - message = "" + message + ""; - message += "@ " + details.runtime + " ms"; - - // The pushFailure doesn't provide details.expected - // when it calls, it's implicit to also not show expected and diff stuff - // Also, we need to check details.expected existence, as it can exist and be undefined - if ( !details.result && hasOwn.call( details, "expected" ) ) { - if ( details.negative ) { - expected = "NOT " + QUnit.dump.parse( details.expected ); - } else { - expected = QUnit.dump.parse( details.expected ); - } - - actual = QUnit.dump.parse( details.actual ); - message += ""; - - if ( actual !== expected ) { - - message += ""; - - if ( typeof details.actual === "number" && typeof details.expected === "number" ) { - if ( !isNaN( details.actual ) && !isNaN( details.expected ) ) { - showDiff = true; - diff = ( details.actual - details.expected ); - diff = ( diff > 0 ? "+" : "" ) + diff; - } - } else if ( typeof details.actual !== "boolean" && - typeof details.expected !== "boolean" ) { - diff = QUnit.diff( expected, actual ); - - // don't show diff if there is zero overlap - showDiff = stripHtml( diff ).length !== - stripHtml( expected ).length + - stripHtml( actual ).length; - } - - if ( showDiff ) { - message += ""; - } - } else if ( expected.indexOf( "[object Array]" ) !== -1 || - expected.indexOf( "[object Object]" ) !== -1 ) { - message += ""; - } else { - message += ""; - } - - if ( details.source ) { - message += ""; - } - - message += "
      Expected:
      " +
      -			escapeText( expected ) +
      -			"
      Result:
      " +
      -					escapeText( actual ) + "
      Diff:
      " +
      -					diff + "
      Message: " + - "Diff suppressed as the depth of object is more than current max depth (" + - QUnit.config.maxDepth + ").

      Hint: Use QUnit.dump.maxDepth to " + - " run with a higher max depth or " + - "Rerun without max depth.

      Message: " + - "Diff suppressed as the expected and actual results have an equivalent" + - " serialization
      Source:
      " +
      -				escapeText( details.source ) + "
      "; - - // This occurs when pushFailure is set and we have an extracted stack trace - } else if ( !details.result && details.source ) { - message += "" + - "" + - "
      Source:
      " +
      -			escapeText( details.source ) + "
      "; - } - - assertList = testItem.getElementsByTagName( "ol" )[ 0 ]; - - assertLi = document.createElement( "li" ); - assertLi.className = details.result ? "pass" : "fail"; - assertLi.innerHTML = message; - assertList.appendChild( assertLi ); - } ); - - QUnit.testDone( function( details ) { - var testTitle, time, assertList, status, - good, bad, testCounts, skipped, sourceName, - tests = id( "qunit-tests" ), - testItem = id( "qunit-test-output-" + details.testId ); - - if ( !tests || !testItem ) { - return; - } - - removeClass( testItem, "running" ); - - if ( details.failed > 0 ) { - status = "failed"; - } else if ( details.todo ) { - status = "todo"; - } else { - status = details.skipped ? "skipped" : "passed"; - } - - assertList = testItem.getElementsByTagName( "ol" )[ 0 ]; - - good = details.passed; - bad = details.failed; - - // This test passed if it has no unexpected failed assertions - const testPassed = details.failed > 0 ? details.todo : !details.todo; - - if ( testPassed ) { - - // Collapse the passing tests - addClass( assertList, "qunit-collapsed" ); - } else { - - stats.failedTests.push( details.testId ); - - if ( config.collapse ) { - if ( !collapseNext ) { - - // Skip collapsing the first failing test - collapseNext = true; - } else { - - // Collapse remaining tests - addClass( assertList, "qunit-collapsed" ); - } - } - } - - // The testItem.firstChild is the test name - testTitle = testItem.firstChild; - - testCounts = bad ? - "" + bad + ", " + "" + good + ", " : - ""; - - testTitle.innerHTML += " (" + testCounts + - details.assertions.length + ")"; - - stats.completed++; - - if ( details.skipped ) { - testItem.className = "skipped"; - skipped = document.createElement( "em" ); - skipped.className = "qunit-skipped-label"; - skipped.innerHTML = "skipped"; - testItem.insertBefore( skipped, testTitle ); - } else { - addEvent( testTitle, "click", function() { - toggleClass( assertList, "qunit-collapsed" ); - } ); - - testItem.className = testPassed ? "pass" : "fail"; - - if ( details.todo ) { - const todoLabel = document.createElement( "em" ); - todoLabel.className = "qunit-todo-label"; - todoLabel.innerHTML = "todo"; - testItem.className += " todo"; - testItem.insertBefore( todoLabel, testTitle ); - } - - time = document.createElement( "span" ); - time.className = "runtime"; - time.innerHTML = details.runtime + " ms"; - testItem.insertBefore( time, assertList ); - } - - // Show the source of the test when showing assertions - if ( details.source ) { - sourceName = document.createElement( "p" ); - sourceName.innerHTML = "Source: " + escapeText( details.source ); - addClass( sourceName, "qunit-source" ); - if ( testPassed ) { - addClass( sourceName, "qunit-collapsed" ); - } - addEvent( testTitle, "click", function() { - toggleClass( sourceName, "qunit-collapsed" ); - } ); - testItem.appendChild( sourceName ); - } - - if ( config.hidepassed && ( status === "passed" || details.skipped ) ) { - - // use removeChild instead of remove because of support - hiddenTests.push( testItem ); - - tests.removeChild( testItem ); - } - } ); - - QUnit.on( "error", ( error ) => { - const testItem = appendTest( "global failure" ); - if ( !testItem ) { - - // HTML Reporter is probably disabled or not yet initialized. - return; - } - - // Render similar to a failed assertion (see above QUnit.log callback) - let message = escapeText( errorString( error ) ); - message = "" + message + ""; - if ( error && error.stack ) { - message += "" + - "" + - "
      Source:
      " +
      -				escapeText( error.stack ) + "
      "; - } - const assertList = testItem.getElementsByTagName( "ol" )[ 0 ]; - const assertLi = document.createElement( "li" ); - assertLi.className = "fail"; - assertLi.innerHTML = message; - assertList.appendChild( assertLi ); - - // Make it visible - testItem.className = "fail"; - } ); - - // Avoid readyState issue with phantomjs - // Ref: #818 - var usingPhantom = ( function( p ) { - return ( p && p.version && p.version.major > 0 ); - } )( window.phantom ); - - if ( usingPhantom ) { - console.warn( "Support for PhantomJS is deprecated and will be removed in QUnit 3.0." ); - } - - if ( !usingPhantom && document.readyState === "complete" ) { - QUnit.load(); - } else { - addEvent( window, "load", QUnit.load ); - } - - // Wrap window.onerror. We will call the original window.onerror to see if - // the existing handler fully handles the error; if not, we will call the - // QUnit.onError function. - var originalWindowOnError = window.onerror; - - // Cover uncaught exceptions - // Returning true will suppress the default browser handler, - // returning false will let it run. - window.onerror = function( message, fileName, lineNumber, columnNumber, errorObj, ...args ) { - var ret = false; - if ( originalWindowOnError ) { - ret = originalWindowOnError.call( - this, - message, - fileName, - lineNumber, - columnNumber, - errorObj, - ...args - ); - } - - // Treat return value as window.onerror itself does, - // Only do our handling if not suppressed. - if ( ret !== true ) { - - // If there is a current test that sets the internal `ignoreGlobalErrors` field - // (such as during `assert.throws()`), then the error is ignored and native - // error reporting is suppressed as well. This is because in browsers, an error - // can sometimes end up in `window.onerror` instead of in the local try/catch. - // This ignoring of errors does not apply to our general onUncaughtException - // method, nor to our `unhandledRejection` handlers, as those are not meant - // to receive an "expected" error during `assert.throws()`. - if ( config.current && config.current.ignoreGlobalErrors ) { - return true; - } - - // According to - // https://blog.sentry.io/2016/01/04/client-javascript-reporting-window-onerror, - // most modern browsers support an errorObj argument; use that to - // get a full stack trace if it's available. - const error = errorObj || new Error( message ); - if ( !error.stack && fileName && lineNumber ) { - error.stack = `${fileName}:${lineNumber}`; - } - QUnit.onUncaughtException( error ); - } - - return ret; - }; - - window.addEventListener( "unhandledrejection", function( event ) { - QUnit.onUncaughtException( event.reason ); - } ); -}() ); +(function () { + // Don't load the HTML Reporter on non-browser environments + if (!window || !document) { + return; + } + + const config = QUnit.config; + const hiddenTests = []; + let collapseNext = false; + const hasOwn = Object.prototype.hasOwnProperty; + const unfilteredUrl = setUrl({ + filter: undefined, + module: undefined, + moduleId: undefined, + testId: undefined + }); + + function trim (string) { + if (typeof string.trim === 'function') { + return string.trim(); + } else { + return string.replace(/^\s+|\s+$/g, ''); + } + } + + function addEvent (elem, type, fn) { + elem.addEventListener(type, fn, false); + } + + function removeEvent (elem, type, fn) { + elem.removeEventListener(type, fn, false); + } + + function addEvents (elems, type, fn) { + let i = elems.length; + while (i--) { + addEvent(elems[i], type, fn); + } + } + + function hasClass (elem, name) { + return (' ' + elem.className + ' ').indexOf(' ' + name + ' ') >= 0; + } + + function addClass (elem, name) { + if (!hasClass(elem, name)) { + elem.className += (elem.className ? ' ' : '') + name; + } + } + + function toggleClass (elem, name, force) { + if (force || (typeof force === 'undefined' && !hasClass(elem, name))) { + addClass(elem, name); + } else { + removeClass(elem, name); + } + } + + function removeClass (elem, name) { + let set = ' ' + elem.className + ' '; + + // Class name may appear multiple times + while (set.indexOf(' ' + name + ' ') >= 0) { + set = set.replace(' ' + name + ' ', ' '); + } + + // Trim for prettiness + elem.className = trim(set); + } + + function id (name) { + return document.getElementById && document.getElementById(name); + } + + function abortTests () { + const abortButton = id('qunit-abort-tests-button'); + if (abortButton) { + abortButton.disabled = true; + abortButton.innerHTML = 'Aborting...'; + } + QUnit.config.queue.length = 0; + return false; + } + + function interceptNavigation (ev) { + // Trim potential accidental whitespace so that QUnit doesn't throw an error about no tests matching the filter. + const filterInputElem = id('qunit-filter-input'); + filterInputElem.value = trim(filterInputElem.value); + + applyUrlParams(); + + if (ev && ev.preventDefault) { + ev.preventDefault(); + } + + return false; + } + + function getUrlConfigHtml () { + let selection = false; + const urlConfig = config.urlConfig; + let urlConfigHtml = ''; + + for (let i = 0; i < urlConfig.length; i++) { + // Options can be either strings or objects with nonempty "id" properties + let val = config.urlConfig[i]; + if (typeof val === 'string') { + val = { + id: val, + label: val + }; + } + + let escaped = escapeText(val.id); + let escapedTooltip = escapeText(val.tooltip); + + if (!val.value || typeof val.value === 'string') { + urlConfigHtml += "'; + } else { + urlConfigHtml += "'; + } + } + + return urlConfigHtml; + } + + // Handle "click" events on toolbar checkboxes and "change" for select menus. + // Updates the URL with the new state of `config.urlConfig` values. + function toolbarChanged () { + const field = this; + const params = {}; + + // Detect if field is a select menu or a checkbox + let value; + if ('selectedIndex' in field) { + value = field.options[field.selectedIndex].value || undefined; + } else { + value = field.checked ? (field.defaultValue || true) : undefined; + } + + params[field.name] = value; + let updatedUrl = setUrl(params); + + // Check if we can apply the change without a page refresh + if (field.name === 'hidepassed' && 'replaceState' in window.history) { + QUnit.urlParams[field.name] = value; + config[field.name] = value || false; + let tests = id('qunit-tests'); + if (tests) { + const length = tests.children.length; + const children = tests.children; + + if (field.checked) { + for (let i = 0; i < length; i++) { + const test = children[i]; + const className = test ? test.className : ''; + const classNameHasPass = className.indexOf('pass') > -1; + const classNameHasSkipped = className.indexOf('skipped') > -1; + + if (classNameHasPass || classNameHasSkipped) { + hiddenTests.push(test); + } + } + + for (const hiddenTest of hiddenTests) { + tests.removeChild(hiddenTest); + } + } else { + let test; + while ((test = hiddenTests.pop()) != null) { + tests.appendChild(test); + } + } + } + window.history.replaceState(null, '', updatedUrl); + } else { + window.location = updatedUrl; + } + } + + function setUrl (params) { + let querystring = '?'; + const location = window.location; + + params = extend(extend({}, QUnit.urlParams), params); + + for (let key in params) { + // Skip inherited or undefined properties + if (hasOwn.call(params, key) && params[key] !== undefined) { + // Output a parameter for each value of this key + // (but usually just one) + let arrValue = [].concat(params[key]); + for (let i = 0; i < arrValue.length; i++) { + querystring += encodeURIComponent(key); + if (arrValue[i] !== true) { + querystring += '=' + encodeURIComponent(arrValue[i]); + } + querystring += '&'; + } + } + } + return location.protocol + '//' + location.host + + location.pathname + querystring.slice(0, -1); + } + + function applyUrlParams () { + let i; + const selectedModules = []; + const modulesList = id('qunit-modulefilter-dropdown-list').getElementsByTagName('input'); + const filter = id('qunit-filter-input').value; + + for (i = 0; i < modulesList.length; i++) { + if (modulesList[i].checked) { + selectedModules.push(modulesList[i].value); + } + } + + window.location = setUrl({ + filter: (filter === '') ? undefined : filter, + moduleId: (selectedModules.length === 0) ? undefined : selectedModules, + + // Remove module and testId filter + module: undefined, + testId: undefined + }); + } + + function toolbarUrlConfigContainer () { + const urlConfigContainer = document.createElement('span'); + + urlConfigContainer.innerHTML = getUrlConfigHtml(); + addClass(urlConfigContainer, 'qunit-url-config'); + + addEvents(urlConfigContainer.getElementsByTagName('input'), 'change', toolbarChanged); + addEvents(urlConfigContainer.getElementsByTagName('select'), 'change', toolbarChanged); + + return urlConfigContainer; + } + + function abortTestsButton () { + const button = document.createElement('button'); + button.id = 'qunit-abort-tests-button'; + button.innerHTML = 'Abort'; + addEvent(button, 'click', abortTests); + return button; + } + + function toolbarLooseFilter () { + const filter = document.createElement('form'); + const label = document.createElement('label'); + const input = document.createElement('input'); + const button = document.createElement('button'); + + addClass(filter, 'qunit-filter'); + + label.innerHTML = 'Filter: '; + + input.type = 'text'; + input.value = config.filter || ''; + input.name = 'filter'; + input.id = 'qunit-filter-input'; + + button.innerHTML = 'Go'; + + label.appendChild(input); + + filter.appendChild(label); + filter.appendChild(document.createTextNode(' ')); + filter.appendChild(button); + addEvent(filter, 'submit', interceptNavigation); + + return filter; + } + + function moduleListHtml (modules) { + let html = ''; + + for (let i = 0; i < modules.length; i++) { + if (modules[i].name !== '') { + let checked = config.moduleId.indexOf(modules[i].moduleId) > -1; + html += "
    1. '; + } + } + + return html; + } + + function toolbarModuleFilter () { + const moduleFilter = document.createElement('form'); + const label = document.createElement('label'); + const moduleSearch = document.createElement('input'); + const dropDown = document.createElement('div'); + const actions = document.createElement('span'); + const applyButton = document.createElement('button'); + const resetButton = document.createElement('button'); + const allModulesLabel = document.createElement('label'); + const allCheckbox = document.createElement('input'); + const dropDownList = document.createElement('ul'); + let dirty = false; + + moduleSearch.id = 'qunit-modulefilter-search'; + moduleSearch.autocomplete = 'off'; + addEvent(moduleSearch, 'input', searchInput); + addEvent(moduleSearch, 'input', searchFocus); + addEvent(moduleSearch, 'focus', searchFocus); + addEvent(moduleSearch, 'click', searchFocus); + + config.modules.forEach(module => { + module.namePrepared = fuzzysort.prepare(module.name); + }); + + label.id = 'qunit-modulefilter-search-container'; + label.innerHTML = 'Module: '; + label.appendChild(moduleSearch); + + applyButton.textContent = 'Apply'; + applyButton.style.display = 'none'; + + resetButton.textContent = 'Reset'; + resetButton.type = 'reset'; + resetButton.style.display = 'none'; + + allCheckbox.type = 'checkbox'; + allCheckbox.checked = config.moduleId.length === 0; + + allModulesLabel.className = 'clickable'; + if (config.moduleId.length) { + allModulesLabel.className = 'checked'; + } + allModulesLabel.appendChild(allCheckbox); + allModulesLabel.appendChild(document.createTextNode('All modules')); + + actions.id = 'qunit-modulefilter-actions'; + actions.appendChild(applyButton); + actions.appendChild(resetButton); + actions.appendChild(allModulesLabel); + let commit = actions.firstChild; + let reset = commit.nextSibling; + addEvent(commit, 'click', applyUrlParams); + + dropDownList.id = 'qunit-modulefilter-dropdown-list'; + dropDownList.innerHTML = moduleListHtml(config.modules); + + dropDown.id = 'qunit-modulefilter-dropdown'; + dropDown.style.display = 'none'; + dropDown.appendChild(actions); + dropDown.appendChild(dropDownList); + addEvent(dropDown, 'change', selectionChange); + selectionChange(); + + moduleFilter.id = 'qunit-modulefilter'; + moduleFilter.appendChild(label); + moduleFilter.appendChild(dropDown); + addEvent(moduleFilter, 'submit', interceptNavigation); + addEvent(moduleFilter, 'reset', function () { + // Let the reset happen, then update styles + window.setTimeout(selectionChange); + }); + + // Enables show/hide for the dropdown + function searchFocus () { + if (dropDown.style.display !== 'none') { + return; + } + + dropDown.style.display = 'block'; + addEvent(document, 'click', hideHandler); + addEvent(document, 'keydown', hideHandler); + + // Hide on Escape keydown or outside-container click + function hideHandler (e) { + const inContainer = moduleFilter.contains(e.target); + + if (e.keyCode === 27 || !inContainer) { + if (e.keyCode === 27 && inContainer) { + moduleSearch.focus(); + } + dropDown.style.display = 'none'; + removeEvent(document, 'click', hideHandler); + removeEvent(document, 'keydown', hideHandler); + moduleSearch.value = ''; + searchInput(); + } + } + } + + function filterModules (searchText) { + if (searchText === '') { + return config.modules; + } + return fuzzysort + .go(searchText, config.modules, { key: 'namePrepared', threshold: -10000 }) + .map(module => module.obj); + } + + // Processes module search box input + let searchInputTimeout; + function searchInput () { + window.clearTimeout(searchInputTimeout); + searchInputTimeout = window.setTimeout(() => { + const searchText = moduleSearch.value.toLowerCase(); + const filteredModules = filterModules(searchText); + dropDownList.innerHTML = moduleListHtml(filteredModules); + }, 200); + } + + // Processes selection changes + function selectionChange (evt) { + const checkbox = (evt && evt.target) || allCheckbox; + const modulesList = dropDownList.getElementsByTagName('input'); + const selectedNames = []; + + toggleClass(checkbox.parentNode, 'checked', checkbox.checked); + + dirty = false; + if (checkbox.checked && checkbox !== allCheckbox) { + allCheckbox.checked = false; + removeClass(allCheckbox.parentNode, 'checked'); + } + for (let i = 0; i < modulesList.length; i++) { + let item = modulesList[i]; + if (!evt) { + toggleClass(item.parentNode, 'checked', item.checked); + } else if (checkbox === allCheckbox && checkbox.checked) { + item.checked = false; + removeClass(item.parentNode, 'checked'); + } + dirty = dirty || (item.checked !== item.defaultChecked); + if (item.checked) { + selectedNames.push(item.parentNode.textContent); + } + } + + commit.style.display = reset.style.display = dirty ? '' : 'none'; + moduleSearch.placeholder = selectedNames.join(', ') || + allCheckbox.parentNode.textContent; + moduleSearch.title = 'Type to filter list. Current selection:\n' + + (selectedNames.join('\n') || allCheckbox.parentNode.textContent); + } + + return moduleFilter; + } + + function toolbarFilters () { + const toolbarFilters = document.createElement('span'); + + toolbarFilters.id = 'qunit-toolbar-filters'; + toolbarFilters.appendChild(toolbarLooseFilter()); + toolbarFilters.appendChild(toolbarModuleFilter()); + + return toolbarFilters; + } + + function appendToolbar () { + const toolbar = id('qunit-testrunner-toolbar'); + + if (toolbar) { + toolbar.appendChild(toolbarUrlConfigContainer()); + toolbar.appendChild(toolbarFilters()); + toolbar.appendChild(document.createElement('div')).className = 'clearfix'; + } + } + + function appendHeader () { + const header = id('qunit-header'); + + if (header) { + header.innerHTML = "" + header.innerHTML + + ' '; + } + } + + function appendBanner () { + const banner = id('qunit-banner'); + + if (banner) { + banner.className = ''; + } + } + + function appendTestResults () { + const tests = id('qunit-tests'); + let result = id('qunit-testresult'); + let controls; + + if (result) { + result.parentNode.removeChild(result); + } + + if (tests) { + tests.innerHTML = ''; + result = document.createElement('p'); + result.id = 'qunit-testresult'; + result.className = 'result'; + tests.parentNode.insertBefore(result, tests); + result.innerHTML = '
      Running...
       
      ' + + '
      ' + + '
      '; + controls = id('qunit-testresult-controls'); + } + + if (controls) { + controls.appendChild(abortTestsButton()); + } + } + + function appendFilteredTest () { + const testId = QUnit.config.testId; + if (!testId || testId.length <= 0) { + return ''; + } + return "
      Rerunning selected tests: " + + escapeText(testId.join(', ')) + + " Run all tests
      "; + } + + function appendUserAgent () { + const userAgent = id('qunit-userAgent'); + + if (userAgent) { + userAgent.innerHTML = ''; + userAgent.appendChild( + document.createTextNode( + 'QUnit ' + QUnit.version + '; ' + navigator.userAgent + ) + ); + } + } + + function appendInterface () { + const qunit = id('qunit'); + + // For compat with QUnit 1.2, and to support fully custom theme HTML, + // we will use any existing elements if no id="qunit" element exists. + // + // Note that we don't fail or fallback to creating it ourselves, + // because not having id="qunit" (and not having the below elements) + // simply means QUnit acts headless, allowing users to use their own + // reporters, or for a test runner to listen for events directly without + // having the HTML reporter actively render anything. + if (qunit) { + qunit.setAttribute('role', 'main'); + + // Since QUnit 1.3, these are created automatically if the page + // contains id="qunit". + qunit.innerHTML = + "

      " + escapeText(document.title) + '

      ' + + "

      " + + "" + + appendFilteredTest() + + "

      " + + "
        "; + } + + appendHeader(); + appendBanner(); + appendTestResults(); + appendUserAgent(); + appendToolbar(); + } + + function appendTest (name, testId, moduleName) { + const tests = id('qunit-tests'); + if (!tests) { + return; + } + + let title = document.createElement('strong'); + title.innerHTML = getNameHtml(name, moduleName); + + let testBlock = document.createElement('li'); + testBlock.appendChild(title); + + // No ID or rerun link for "global failure" blocks + if (testId !== undefined) { + let rerunTrigger = document.createElement('a'); + rerunTrigger.innerHTML = 'Rerun'; + rerunTrigger.href = setUrl({ testId: testId }); + + testBlock.id = 'qunit-test-output-' + testId; + testBlock.appendChild(rerunTrigger); + } + + let assertList = document.createElement('ol'); + assertList.className = 'qunit-assert-list'; + + testBlock.appendChild(assertList); + + tests.appendChild(testBlock); + + return testBlock; + } + + // HTML Reporter initialization and load + QUnit.on('runStart', function (runStart) { + stats.defined = runStart.testCounts.total; + }); + + QUnit.begin(function () { + // Initialize QUnit elements + // This is done from begin() instead of runStart, because + // urlparams.js uses begin(), which we need to wait for. + // urlparams.js in turn uses begin() to allow plugins to + // add entries to QUnit.config.urlConfig, which may be done + // asynchronously. + // + appendInterface(); + }); + + function getRerunFailedHtml (failedTests) { + if (failedTests.length === 0) { + return ''; + } + + const href = setUrl({ testId: failedTests }); + return [ + "
        ", + failedTests.length === 1 + ? 'Rerun 1 failed test' + : 'Rerun ' + failedTests.length + ' failed tests', + '' + ].join(''); + } + + QUnit.on('runEnd', function (runEnd) { + const banner = id('qunit-banner'); + const tests = id('qunit-tests'); + const abortButton = id('qunit-abort-tests-button'); + const assertPassed = config.stats.all - config.stats.bad; + let html = [ + runEnd.testCounts.total, + ' tests completed in ', + runEnd.runtime, + ' milliseconds, with ', + runEnd.testCounts.failed, + ' failed, ', + runEnd.testCounts.skipped, + ' skipped, and ', + runEnd.testCounts.todo, + ' todo.
        ', + "", + assertPassed, + " assertions of ", + config.stats.all, + " passed, ", + config.stats.bad, + ' failed.', + getRerunFailedHtml(stats.failedTests) + ].join(''); + let test; + let assertLi; + let assertList; + + // Update remaining tests to aborted + if (abortButton && abortButton.disabled) { + html = 'Tests aborted after ' + runEnd.runtime + ' milliseconds.'; + + for (let i = 0; i < tests.children.length; i++) { + test = tests.children[i]; + if (test.className === '' || test.className === 'running') { + test.className = 'aborted'; + assertList = test.getElementsByTagName('ol')[0]; + assertLi = document.createElement('li'); + assertLi.className = 'fail'; + assertLi.innerHTML = 'Test aborted.'; + assertList.appendChild(assertLi); + } + } + } + + if (banner && (!abortButton || abortButton.disabled === false)) { + banner.className = runEnd.status === 'failed' ? 'qunit-fail' : 'qunit-pass'; + } + + if (abortButton) { + abortButton.parentNode.removeChild(abortButton); + } + + if (tests) { + id('qunit-testresult-display').innerHTML = html; + } + + if (config.altertitle && document.title) { + // Show ✖ for good, ✔ for bad suite result in title + // use escape sequences in case file gets loaded with non-utf-8 + // charset + document.title = [ + (runEnd.status === 'failed' ? '\u2716' : '\u2714'), + document.title.replace(/^[\u2714\u2716] /i, '') + ].join(' '); + } + + // Scroll back to top to show results + if (config.scrolltop && window.scrollTo) { + window.scrollTo(0, 0); + } + }); + + function getNameHtml (name, module) { + let nameHtml = ''; + + if (module) { + nameHtml = "" + escapeText(module) + ': '; + } + + nameHtml += "" + escapeText(name) + ''; + + return nameHtml; + } + + function getProgressHtml (stats) { + return [ + stats.completed, + ' / ', + stats.defined, + ' tests completed.
        ' + ].join(''); + } + + QUnit.testStart(function (details) { + let running, bad; + + appendTest(details.name, details.testId, details.module); + + running = id('qunit-testresult-display'); + + if (running) { + addClass(running, 'running'); + + bad = QUnit.config.reorder && details.previousFailure; + + running.innerHTML = [ + getProgressHtml(stats), + bad + ? 'Rerunning previously failed test:
        ' + : 'Running: ', + getNameHtml(details.name, details.module), + getRerunFailedHtml(stats.failedTests) + ].join(''); + } + }); + + function stripHtml (string) { + // Strip tags, html entity and whitespaces + return string + .replace(/<\/?[^>]+(>|$)/g, '') + .replace(/"/g, '') + .replace(/\s+/g, ''); + } + + QUnit.log(function (details) { + const testItem = id('qunit-test-output-' + details.testId); + if (!testItem) { + return; + } + + let message = escapeText(details.message) || (details.result ? 'okay' : 'failed'); + message = "" + message + ''; + message += "@ " + details.runtime + ' ms'; + + let expected; + let actual; + let diff; + let showDiff = false; + + // The pushFailure doesn't provide details.expected + // when it calls, it's implicit to also not show expected and diff stuff + // Also, we need to check details.expected existence, as it can exist and be undefined + if (!details.result && hasOwn.call(details, 'expected')) { + if (details.negative) { + expected = 'NOT ' + QUnit.dump.parse(details.expected); + } else { + expected = QUnit.dump.parse(details.expected); + } + + actual = QUnit.dump.parse(details.actual); + message += "'; + + if (actual !== expected) { + message += "'; + + if (typeof details.actual === 'number' && typeof details.expected === 'number') { + if (!isNaN(details.actual) && !isNaN(details.expected)) { + showDiff = true; + diff = (details.actual - details.expected); + diff = (diff > 0 ? '+' : '') + diff; + } + } else if (typeof details.actual !== 'boolean' && + typeof details.expected !== 'boolean') { + diff = QUnit.diff(expected, actual); + + // don't show diff if there is zero overlap + showDiff = stripHtml(diff).length !== + stripHtml(expected).length + + stripHtml(actual).length; + } + + if (showDiff) { + message += "'; + } + } else if (expected.indexOf('[object Array]') !== -1 || + expected.indexOf('[object Object]') !== -1) { + message += "'; + } else { + message += "'; + } + + if (details.source) { + message += "'; + } + + message += '
        Expected:
        " +
        +      escapeText(expected) +
        +      '
        Result:
        " +
        +          escapeText(actual) + '
        Diff:
        " +
        +          diff + '
        Message: " + + 'Diff suppressed as the depth of object is more than current max depth (' + + QUnit.config.maxDepth + ').

        Hint: Use QUnit.dump.maxDepth to ' + + " run with a higher max depth or " + + 'Rerun without max depth.

        Message: " + + 'Diff suppressed as the expected and actual results have an equivalent' + + ' serialization
        Source:
        " +
        +        escapeText(details.source) + '
        '; + + // This occurs when pushFailure is set and we have an extracted stack trace + } else if (!details.result && details.source) { + message += '' + + "' + + '
        Source:
        " +
        +      escapeText(details.source) + '
        '; + } + + let assertList = testItem.getElementsByTagName('ol')[0]; + + let assertLi = document.createElement('li'); + assertLi.className = details.result ? 'pass' : 'fail'; + assertLi.innerHTML = message; + assertList.appendChild(assertLi); + }); + + QUnit.testDone(function (details) { + const tests = id('qunit-tests'); + const testItem = id('qunit-test-output-' + details.testId); + if (!tests || !testItem) { + return; + } + + removeClass(testItem, 'running'); + + let status; + if (details.failed > 0) { + status = 'failed'; + } else if (details.todo) { + status = 'todo'; + } else { + status = details.skipped ? 'skipped' : 'passed'; + } + + let assertList = testItem.getElementsByTagName('ol')[0]; + + let good = details.passed; + let bad = details.failed; + + // This test passed if it has no unexpected failed assertions + const testPassed = details.failed > 0 ? details.todo : !details.todo; + + if (testPassed) { + // Collapse the passing tests + addClass(assertList, 'qunit-collapsed'); + } else { + stats.failedTests.push(details.testId); + + if (config.collapse) { + if (!collapseNext) { + // Skip collapsing the first failing test + collapseNext = true; + } else { + // Collapse remaining tests + addClass(assertList, 'qunit-collapsed'); + } + } + } + + // The testItem.firstChild is the test name + let testTitle = testItem.firstChild; + + let testCounts = bad + ? "" + bad + ', ' + "" + good + ', ' + : ''; + + testTitle.innerHTML += " (" + testCounts + + details.assertions.length + ')'; + + stats.completed++; + + if (details.skipped) { + testItem.className = 'skipped'; + let skipped = document.createElement('em'); + skipped.className = 'qunit-skipped-label'; + skipped.innerHTML = 'skipped'; + testItem.insertBefore(skipped, testTitle); + } else { + addEvent(testTitle, 'click', function () { + toggleClass(assertList, 'qunit-collapsed'); + }); + + testItem.className = testPassed ? 'pass' : 'fail'; + + if (details.todo) { + const todoLabel = document.createElement('em'); + todoLabel.className = 'qunit-todo-label'; + todoLabel.innerHTML = 'todo'; + testItem.className += ' todo'; + testItem.insertBefore(todoLabel, testTitle); + } + + let time = document.createElement('span'); + time.className = 'runtime'; + time.innerHTML = details.runtime + ' ms'; + testItem.insertBefore(time, assertList); + } + + // Show the source of the test when showing assertions + if (details.source) { + let sourceName = document.createElement('p'); + sourceName.innerHTML = 'Source: ' + escapeText(details.source); + addClass(sourceName, 'qunit-source'); + if (testPassed) { + addClass(sourceName, 'qunit-collapsed'); + } + addEvent(testTitle, 'click', function () { + toggleClass(sourceName, 'qunit-collapsed'); + }); + testItem.appendChild(sourceName); + } + + if (config.hidepassed && (status === 'passed' || details.skipped)) { + // use removeChild instead of remove because of support + hiddenTests.push(testItem); + + tests.removeChild(testItem); + } + }); + + QUnit.on('error', (error) => { + const testItem = appendTest('global failure'); + if (!testItem) { + // HTML Reporter is probably disabled or not yet initialized. + return; + } + + // Render similar to a failed assertion (see above QUnit.log callback) + let message = escapeText(errorString(error)); + message = "" + message + ''; + if (error && error.stack) { + message += '' + + "' + + '
        Source:
        " +
        +        escapeText(error.stack) + '
        '; + } + const assertList = testItem.getElementsByTagName('ol')[0]; + const assertLi = document.createElement('li'); + assertLi.className = 'fail'; + assertLi.innerHTML = message; + assertList.appendChild(assertLi); + + // Make it visible + testItem.className = 'fail'; + }); + + // Avoid readyState issue with phantomjs + // Ref: #818 + const usingPhantom = (function (p) { + return (p && p.version && p.version.major > 0); + })(window.phantom); + + if (usingPhantom) { + console.warn('Support for PhantomJS is deprecated and will be removed in QUnit 3.0.'); + } + + if (!usingPhantom && document.readyState === 'complete') { + QUnit.load(); + } else { + addEvent(window, 'load', QUnit.load); + } + + // Wrap window.onerror. We will call the original window.onerror to see if + // the existing handler fully handles the error; if not, we will call the + // QUnit.onError function. + const originalWindowOnError = window.onerror; + + // Cover uncaught exceptions + // Returning true will suppress the default browser handler, + // returning false will let it run. + window.onerror = function (message, fileName, lineNumber, columnNumber, errorObj, ...args) { + let ret = false; + if (originalWindowOnError) { + ret = originalWindowOnError.call( + this, + message, + fileName, + lineNumber, + columnNumber, + errorObj, + ...args + ); + } + + // Treat return value as window.onerror itself does, + // Only do our handling if not suppressed. + if (ret !== true) { + // If there is a current test that sets the internal `ignoreGlobalErrors` field + // (such as during `assert.throws()`), then the error is ignored and native + // error reporting is suppressed as well. This is because in browsers, an error + // can sometimes end up in `window.onerror` instead of in the local try/catch. + // This ignoring of errors does not apply to our general onUncaughtException + // method, nor to our `unhandledRejection` handlers, as those are not meant + // to receive an "expected" error during `assert.throws()`. + if (config.current && config.current.ignoreGlobalErrors) { + return true; + } + + // According to + // https://blog.sentry.io/2016/01/04/client-javascript-reporting-window-onerror, + // most modern browsers support an errorObj argument; use that to + // get a full stack trace if it's available. + const error = errorObj || new Error(message); + if (!error.stack && fileName && lineNumber) { + error.stack = `${fileName}:${lineNumber}`; + } + QUnit.onUncaughtException(error); + } + + return ret; + }; + + window.addEventListener('unhandledrejection', function (event) { + QUnit.onUncaughtException(event.reason); + }); +}()); diff --git a/src/html-reporter/reporter.js b/src/html-reporter/reporter.js index d28704305..ebdd17b57 100644 --- a/src/html-reporter/reporter.js +++ b/src/html-reporter/reporter.js @@ -1,3 +1,3 @@ -import "./fixture"; -import "./diff"; -import "./html"; +import './fixture'; +import './diff'; +import './html'; diff --git a/src/html-reporter/urlparams.js b/src/html-reporter/urlparams.js index 15e890a9d..38193537f 100644 --- a/src/html-reporter/urlparams.js +++ b/src/html-reporter/urlparams.js @@ -1,103 +1,97 @@ -import QUnit from "../core"; -import { window } from "../globals"; - -( function() { - - // Only interact with URLs via window.location - var location = typeof window !== "undefined" && window.location; - if ( !location ) { - return; - } - - var urlParams = getUrlParams(); - - QUnit.urlParams = urlParams; - - // Match module/test by inclusion in an array - QUnit.config.moduleId = [].concat( urlParams.moduleId || [] ); - QUnit.config.testId = [].concat( urlParams.testId || [] ); - - // Exact case-insensitive match of the module name - QUnit.config.module = urlParams.module; - - // Regular expression or case-insenstive substring match against "moduleName: testName" - QUnit.config.filter = urlParams.filter; - - // Test order randomization - if ( urlParams.seed === true ) { - - // Generate a random seed if the option is specified without a value - QUnit.config.seed = Math.random().toString( 36 ).slice( 2 ); - } else if ( urlParams.seed ) { - QUnit.config.seed = urlParams.seed; - } - - // Add URL-parameter-mapped config values with UI form rendering data - QUnit.config.urlConfig.push( - { - id: "hidepassed", - label: "Hide passed tests", - tooltip: "Only show tests and assertions that fail. Stored as query-strings." - }, - { - id: "noglobals", - label: "Check for Globals", - tooltip: "Enabling this will test if any test introduces new properties on the " + - "global object (`window` in Browsers). Stored as query-strings." - }, - { - id: "notrycatch", - label: "No try-catch", - tooltip: "Enabling this will run tests outside of a try-catch block. Makes debugging " + - "exceptions in IE reasonable. Stored as query-strings." - } - ); - - QUnit.begin( function() { - var i, option, - urlConfig = QUnit.config.urlConfig; - - for ( i = 0; i < urlConfig.length; i++ ) { - - // Options can be either strings or objects with nonempty "id" properties - option = QUnit.config.urlConfig[ i ]; - if ( typeof option !== "string" ) { - option = option.id; - } - - if ( QUnit.config[ option ] === undefined ) { - QUnit.config[ option ] = urlParams[ option ]; - } - } - } ); - - function getUrlParams() { - var i, param, name, value; - var urlParams = Object.create( null ); - var params = location.search.slice( 1 ).split( "&" ); - var length = params.length; - - for ( i = 0; i < length; i++ ) { - if ( params[ i ] ) { - param = params[ i ].split( "=" ); - name = decodeQueryParam( param[ 0 ] ); - - // Allow just a key to turn on a flag, e.g., test.html?noglobals - value = param.length === 1 || - decodeQueryParam( param.slice( 1 ).join( "=" ) ); - if ( name in urlParams ) { - urlParams[ name ] = [].concat( urlParams[ name ], value ); - } else { - urlParams[ name ] = value; - } - } - } - - return urlParams; - } - - function decodeQueryParam( param ) { - return decodeURIComponent( param.replace( /\+/g, "%20" ) ); - } - -}() ); +import QUnit from '../core'; +import { window } from '../globals'; + +(function () { + // Only interact with URLs via window.location + const location = typeof window !== 'undefined' && window.location; + if (!location) { + return; + } + + const urlParams = getUrlParams(); + + QUnit.urlParams = urlParams; + + // Match module/test by inclusion in an array + QUnit.config.moduleId = [].concat(urlParams.moduleId || []); + QUnit.config.testId = [].concat(urlParams.testId || []); + + // Exact case-insensitive match of the module name + QUnit.config.module = urlParams.module; + + // Regular expression or case-insenstive substring match against "moduleName: testName" + QUnit.config.filter = urlParams.filter; + + // Test order randomization + if (urlParams.seed === true) { + // Generate a random seed if the option is specified without a value + QUnit.config.seed = Math.random().toString(36).slice(2); + } else if (urlParams.seed) { + QUnit.config.seed = urlParams.seed; + } + + // Add URL-parameter-mapped config values with UI form rendering data + QUnit.config.urlConfig.push( + { + id: 'hidepassed', + label: 'Hide passed tests', + tooltip: 'Only show tests and assertions that fail. Stored as query-strings.' + }, + { + id: 'noglobals', + label: 'Check for Globals', + tooltip: 'Enabling this will test if any test introduces new properties on the ' + + 'global object (`window` in Browsers). Stored as query-strings.' + }, + { + id: 'notrycatch', + label: 'No try-catch', + tooltip: 'Enabling this will run tests outside of a try-catch block. Makes debugging ' + + 'exceptions in IE reasonable. Stored as query-strings.' + } + ); + + QUnit.begin(function () { + const urlConfig = QUnit.config.urlConfig; + + for (let i = 0; i < urlConfig.length; i++) { + // Options can be either strings or objects with nonempty "id" properties + let option = QUnit.config.urlConfig[i]; + if (typeof option !== 'string') { + option = option.id; + } + + if (QUnit.config[option] === undefined) { + QUnit.config[option] = urlParams[option]; + } + } + }); + + function getUrlParams () { + const urlParams = Object.create(null); + const params = location.search.slice(1).split('&'); + const length = params.length; + + for (let i = 0; i < length; i++) { + if (params[i]) { + const param = params[i].split('='); + const name = decodeQueryParam(param[0]); + + // Allow just a key to turn on a flag, e.g., test.html?noglobals + const value = param.length === 1 || + decodeQueryParam(param.slice(1).join('=')); + if (name in urlParams) { + urlParams[name] = [].concat(urlParams[name], value); + } else { + urlParams[name] = value; + } + } + } + + return urlParams; + } + + function decodeQueryParam (param) { + return decodeURIComponent(param.replace(/\+/g, '%20')); + } +}()); diff --git a/src/logger.js b/src/logger.js index af93f5ca4..024fc032d 100644 --- a/src/logger.js +++ b/src/logger.js @@ -1,4 +1,4 @@ -import { console } from "./globals"; +import { console } from './globals'; // Support: IE 9 // Detect if the console object exists and no-op otherwise. @@ -12,7 +12,7 @@ import { console } from "./globals"; // The console object has a log method, but no warn method. export default { - warn: console ? - Function.prototype.bind.call( console.warn || console.log, console ) : - function() {} + warn: console + ? Function.prototype.bind.call(console.warn || console.log, console) + : function () {} }; diff --git a/src/module.js b/src/module.js index d5b4ee416..621713dc5 100644 --- a/src/module.js +++ b/src/module.js @@ -1,182 +1,176 @@ -import Logger from "./logger"; - -import config from "./core/config"; - -import SuiteReport from "./reports/suite"; - -import { extend, objectType, generateHash } from "./core/utilities"; +import Logger from './logger'; +import config from './core/config'; +import SuiteReport from './reports/suite'; +import { extend, objectType, generateHash } from './core/utilities'; const moduleStack = []; export const runSuite = new SuiteReport(); -function isParentModuleInQueue() { - const modulesInQueue = config.modules - .filter( module => !module.ignored ) - .map( module => module.moduleId ); - return moduleStack.some( module => modulesInQueue.includes( module.moduleId ) ); +function isParentModuleInQueue () { + const modulesInQueue = config.modules + .filter(module => !module.ignored) + .map(module => module.moduleId); + return moduleStack.some(module => modulesInQueue.includes(module.moduleId)); } -function createModule( name, testEnvironment, modifiers ) { - const parentModule = moduleStack.length ? moduleStack.slice( -1 )[ 0 ] : null; - const moduleName = parentModule !== null ? [ parentModule.name, name ].join( " > " ) : name; - const parentSuite = parentModule ? parentModule.suiteReport : runSuite; - - const skip = parentModule !== null && parentModule.skip || modifiers.skip; - const todo = parentModule !== null && parentModule.todo || modifiers.todo; - - const env = {}; - if ( parentModule ) { - extend( env, parentModule.testEnvironment ); - } - extend( env, testEnvironment ); - - const module = { - name: moduleName, - parentModule: parentModule, - hooks: { - before: [], - beforeEach: [], - afterEach: [], - after: [] - }, - testEnvironment: env, - tests: [], - moduleId: generateHash( moduleName ), - testsRun: 0, - testsIgnored: 0, - childModules: [], - suiteReport: new SuiteReport( name, parentSuite ), - - // Initialised by test.js when the module start executing, - // i.e. before the first test in this module (or a child). - stats: null, - - // Pass along `skip` and `todo` properties from parent module, in case - // there is one, to childs. And use own otherwise. - // This property will be used to mark own tests and tests of child suites - // as either `skipped` or `todo`. - skip: skip, - todo: skip ? false : todo, - ignored: modifiers.ignored || false - }; - - if ( parentModule ) { - parentModule.childModules.push( module ); - } - - config.modules.push( module ); - return module; +function createModule (name, testEnvironment, modifiers) { + const parentModule = moduleStack.length ? moduleStack.slice(-1)[0] : null; + const moduleName = parentModule !== null ? [parentModule.name, name].join(' > ') : name; + const parentSuite = parentModule ? parentModule.suiteReport : runSuite; + + const skip = (parentModule !== null && parentModule.skip) || modifiers.skip; + const todo = (parentModule !== null && parentModule.todo) || modifiers.todo; + + const env = {}; + if (parentModule) { + extend(env, parentModule.testEnvironment); + } + extend(env, testEnvironment); + + const module = { + name: moduleName, + parentModule: parentModule, + hooks: { + before: [], + beforeEach: [], + afterEach: [], + after: [] + }, + testEnvironment: env, + tests: [], + moduleId: generateHash(moduleName), + testsRun: 0, + testsIgnored: 0, + childModules: [], + suiteReport: new SuiteReport(name, parentSuite), + + // Initialised by test.js when the module start executing, + // i.e. before the first test in this module (or a child). + stats: null, + + // Pass along `skip` and `todo` properties from parent module, in case + // there is one, to childs. And use own otherwise. + // This property will be used to mark own tests and tests of child suites + // as either `skipped` or `todo`. + skip: skip, + todo: skip ? false : todo, + ignored: modifiers.ignored || false + }; + + if (parentModule) { + parentModule.childModules.push(module); + } + + config.modules.push(module); + return module; } -function setHookFromEnvironment( hooks, environment, name ) { - const potentialHook = environment[ name ]; - if ( typeof potentialHook === "function" ) { - hooks[ name ].push( potentialHook ); - } - delete environment[ name ]; +function setHookFromEnvironment (hooks, environment, name) { + const potentialHook = environment[name]; + if (typeof potentialHook === 'function') { + hooks[name].push(potentialHook); + } + delete environment[name]; } -function makeSetHook( module, hookName ) { - return function setHook( callback ) { - if ( config.currentModule !== module ) { - Logger.warn( "The `" + hookName + "` hook was called inside the wrong module (`" + - config.currentModule.name + "`). " + - "Instead, use hooks provided by the callback to the containing module (`" + - module.name + "`). " + - "This will become an error in QUnit 3.0." ); - } - module.hooks[ hookName ].push( callback ); - }; +function makeSetHook (module, hookName) { + return function setHook (callback) { + if (config.currentModule !== module) { + Logger.warn('The `' + hookName + '` hook was called inside the wrong module (`' + + config.currentModule.name + '`). ' + + 'Instead, use hooks provided by the callback to the containing module (`' + + module.name + '`). ' + + 'This will become an error in QUnit 3.0.'); + } + module.hooks[hookName].push(callback); + }; } -function processModule( name, options, executeNow, modifiers = {} ) { - if ( objectType( options ) === "function" ) { - executeNow = options; - options = undefined; - } - - const module = createModule( name, options, modifiers ); - - // Transfer any initial hooks from the options object to the 'hooks' object - const testEnvironment = module.testEnvironment; - const hooks = module.hooks; - - setHookFromEnvironment( hooks, testEnvironment, "before" ); - setHookFromEnvironment( hooks, testEnvironment, "beforeEach" ); - setHookFromEnvironment( hooks, testEnvironment, "afterEach" ); - setHookFromEnvironment( hooks, testEnvironment, "after" ); - - const moduleFns = { - before: makeSetHook( module, "before" ), - beforeEach: makeSetHook( module, "beforeEach" ), - afterEach: makeSetHook( module, "afterEach" ), - after: makeSetHook( module, "after" ) - }; - - const prevModule = config.currentModule; - config.currentModule = module; - - if ( objectType( executeNow ) === "function" ) { - moduleStack.push( module ); - - try { - const cbReturnValue = executeNow.call( module.testEnvironment, moduleFns ); - if ( cbReturnValue != null && objectType( cbReturnValue.then ) === "function" ) { - Logger.warn( "Returning a promise from a module callback is not supported. " + - "Instead, use hooks for async behavior. " + - "This will become an error in QUnit 3.0." ); - } - } finally { - - // If the module closure threw an uncaught error during the load phase, - // we let this bubble up to global error handlers. But, not until after - // we teardown internal state to ensure correct module nesting. - // Ref https://github.com/qunitjs/qunit/issues/1478. - moduleStack.pop(); - config.currentModule = module.parentModule || prevModule; - } - } +function processModule (name, options, executeNow, modifiers = {}) { + if (objectType(options) === 'function') { + executeNow = options; + options = undefined; + } + + const module = createModule(name, options, modifiers); + + // Transfer any initial hooks from the options object to the 'hooks' object + const testEnvironment = module.testEnvironment; + const hooks = module.hooks; + + setHookFromEnvironment(hooks, testEnvironment, 'before'); + setHookFromEnvironment(hooks, testEnvironment, 'beforeEach'); + setHookFromEnvironment(hooks, testEnvironment, 'afterEach'); + setHookFromEnvironment(hooks, testEnvironment, 'after'); + + const moduleFns = { + before: makeSetHook(module, 'before'), + beforeEach: makeSetHook(module, 'beforeEach'), + afterEach: makeSetHook(module, 'afterEach'), + after: makeSetHook(module, 'after') + }; + + const prevModule = config.currentModule; + config.currentModule = module; + + if (objectType(executeNow) === 'function') { + moduleStack.push(module); + + try { + const cbReturnValue = executeNow.call(module.testEnvironment, moduleFns); + if (cbReturnValue != null && objectType(cbReturnValue.then) === 'function') { + Logger.warn('Returning a promise from a module callback is not supported. ' + + 'Instead, use hooks for async behavior. ' + + 'This will become an error in QUnit 3.0.'); + } + } finally { + // If the module closure threw an uncaught error during the load phase, + // we let this bubble up to global error handlers. But, not until after + // we teardown internal state to ensure correct module nesting. + // Ref https://github.com/qunitjs/qunit/issues/1478. + moduleStack.pop(); + config.currentModule = module.parentModule || prevModule; + } + } } let focused = false; // indicates that the "only" filter was used -export function module( name, options, executeNow ) { +export function module (name, options, executeNow) { + const ignored = focused && !isParentModuleInQueue(); - const ignored = focused && !isParentModuleInQueue(); - - processModule( name, options, executeNow, { ignored } ); + processModule(name, options, executeNow, { ignored }); } -module.only = function( ...args ) { - if ( !focused ) { - - // Upon the first module.only() call, - // delete any and all previously registered modules and tests. - config.modules.length = 0; - config.queue.length = 0; +module.only = function (...args) { + if (!focused) { + // Upon the first module.only() call, + // delete any and all previously registered modules and tests. + config.modules.length = 0; + config.queue.length = 0; - // Ignore any tests declared after this block within the same - // module parent. https://github.com/qunitjs/qunit/issues/1645 - config.currentModule.ignored = true; - } + // Ignore any tests declared after this block within the same + // module parent. https://github.com/qunitjs/qunit/issues/1645 + config.currentModule.ignored = true; + } - focused = true; - processModule( ...args ); + focused = true; + processModule(...args); }; -module.skip = function( name, options, executeNow ) { - if ( focused ) { - return; - } +module.skip = function (name, options, executeNow) { + if (focused) { + return; + } - processModule( name, options, executeNow, { skip: true } ); + processModule(name, options, executeNow, { skip: true }); }; -module.todo = function( name, options, executeNow ) { - if ( focused ) { - return; - } +module.todo = function (name, options, executeNow) { + if (focused) { + return; + } - processModule( name, options, executeNow, { todo: true } ); + processModule(name, options, executeNow, { todo: true }); }; diff --git a/src/promise.js b/src/promise.js index 889ce42e2..e5871b295 100644 --- a/src/promise.js +++ b/src/promise.js @@ -1,3 +1,3 @@ -import _Promise from "../lib/promise-polyfill.js"; +import _Promise from '../lib/promise-polyfill.js'; export default _Promise; diff --git a/src/qunit.js b/src/qunit.js index f3c960a54..8aec540fa 100644 --- a/src/qunit.js +++ b/src/qunit.js @@ -1,2 +1,2 @@ -import "./core"; -import "./html-reporter/reporter"; +import './core'; +import './html-reporter/reporter'; diff --git a/src/reporters.js b/src/reporters.js index 4e52753c8..207c43473 100644 --- a/src/reporters.js +++ b/src/reporters.js @@ -1,7 +1,7 @@ -import ConsoleReporter from "./reporters/ConsoleReporter.js"; -import TapReporter from "./reporters/TapReporter.js"; +import ConsoleReporter from './reporters/ConsoleReporter.js'; +import TapReporter from './reporters/TapReporter.js'; export default { - console: ConsoleReporter, - tap: TapReporter + console: ConsoleReporter, + tap: TapReporter }; diff --git a/src/reporters/ConsoleReporter.js b/src/reporters/ConsoleReporter.js index 88512d9f6..0a4143fca 100644 --- a/src/reporters/ConsoleReporter.js +++ b/src/reporters/ConsoleReporter.js @@ -1,42 +1,41 @@ -import { console } from "../globals"; +import { console } from '../globals'; export default class ConsoleReporter { - constructor( runner, options = {} ) { - - // Cache references to console methods to ensure we can report failures - // from tests tests that mock the console object itself. - // https://github.com/qunitjs/qunit/issues/1340 - // Support IE 9: Function#bind is supported, but no console.log.bind(). - this.log = options.log || Function.prototype.bind.call( console.log, console ); - - runner.on( "error", this.onError.bind( this ) ); - runner.on( "runStart", this.onRunStart.bind( this ) ); - runner.on( "testStart", this.onTestStart.bind( this ) ); - runner.on( "testEnd", this.onTestEnd.bind( this ) ); - runner.on( "runEnd", this.onRunEnd.bind( this ) ); - } - - static init( runner, options ) { - return new ConsoleReporter( runner, options ); - } - - onError( error ) { - this.log( "error", error ); - } - - onRunStart( runStart ) { - this.log( "runStart", runStart ); - } - - onTestStart( test ) { - this.log( "testStart", test ); - } - - onTestEnd( test ) { - this.log( "testEnd", test ); - } - - onRunEnd( runEnd ) { - this.log( "runEnd", runEnd ); - } + constructor (runner, options = {}) { + // Cache references to console methods to ensure we can report failures + // from tests tests that mock the console object itself. + // https://github.com/qunitjs/qunit/issues/1340 + // Support IE 9: Function#bind is supported, but no console.log.bind(). + this.log = options.log || Function.prototype.bind.call(console.log, console); + + runner.on('error', this.onError.bind(this)); + runner.on('runStart', this.onRunStart.bind(this)); + runner.on('testStart', this.onTestStart.bind(this)); + runner.on('testEnd', this.onTestEnd.bind(this)); + runner.on('runEnd', this.onRunEnd.bind(this)); + } + + static init (runner, options) { + return new ConsoleReporter(runner, options); + } + + onError (error) { + this.log('error', error); + } + + onRunStart (runStart) { + this.log('runStart', runStart); + } + + onTestStart (test) { + this.log('testStart', test); + } + + onTestEnd (test) { + this.log('testEnd', test); + } + + onRunEnd (runEnd) { + this.log('runEnd', runEnd); + } } diff --git a/src/reporters/TapReporter.js b/src/reporters/TapReporter.js index 1ac4ca4b4..b947995f8 100644 --- a/src/reporters/TapReporter.js +++ b/src/reporters/TapReporter.js @@ -1,6 +1,6 @@ -import kleur from "kleur"; -import { errorString } from "../core/utilities"; -import { console } from "../globals"; +import kleur from 'kleur'; +import { errorString } from '../core/utilities'; +import { console } from '../globals'; const hasOwn = Object.prototype.hasOwnProperty; /** @@ -35,259 +35,250 @@ const hasOwn = Object.prototype.hasOwnProperty; * Objects with cyclical references will be stringifed as * "[Circular]" as they cannot otherwise be represented. */ -function prettyYamlValue( value, indent = 4 ) { - if ( value === undefined ) { - - // Not supported in JSON/YAML, turn into string - // and let the below output it as bare string. - value = String( value ); - } - - // Support IE 9-11: Use isFinite instead of ES6 Number.isFinite - if ( typeof value === "number" && !isFinite( value ) ) { - - // Turn NaN and Infinity into simple strings. - // Paranoia: Don't return directly just in case there's - // a way to add special characters here. - value = String( value ); - } - - if ( typeof value === "number" ) { - - // Simple numbers - return JSON.stringify( value ); - } - - if ( typeof value === "string" ) { - - // If any of these match, then we can't output it - // as bare unquoted text, because that would either - // cause data loss or invalid YAML syntax. - // - // - Quotes, escapes, line breaks, or JSON-like stuff. - const rSpecialJson = /['"\\/[{}\]\r\n]/; - - // - Characters that are special at the start of a YAML value - const rSpecialYaml = /[-?:,[\]{}#&*!|=>'"%@`]/; - - // - Leading or trailing whitespace. - const rUntrimmed = /(^\s|\s$)/; - - // - Ambiguous as YAML number, e.g. '2', '-1.2', '.2', or '2_000' - const rNumerical = /^[\d._-]+$/; - - // - Ambiguous as YAML bool. - // Use case-insensitive match, although technically only - // fully-lower, fully-upper, or uppercase-first would be ambiguous. - // e.g. true/True/TRUE, but not tRUe. - const rBool = /^(true|false|y|n|yes|no|on|off)$/i; - - // Is this a complex string? - if ( - value === "" || - rSpecialJson.test( value ) || - rSpecialYaml.test( value[ 0 ] ) || - rUntrimmed.test( value ) || - rNumerical.test( value ) || - rBool.test( value ) - ) { - if ( !/\n/.test( value ) ) { - - // Complex one-line string, use JSON (quoted string) - return JSON.stringify( value ); - } - - // See also - // Support IE 9-11: Avoid ES6 String#repeat - const prefix = ( new Array( indent + 1 ) ).join( " " ); - - const trailingLinebreakMatch = value.match( /\n+$/ ); - const trailingLinebreaks = trailingLinebreakMatch ? - trailingLinebreakMatch[ 0 ].length : 0; - - if ( trailingLinebreaks === 1 ) { - - // Use the most straight-forward "Block" string in YAML - // without any "Chomping" indicators. - const lines = value - - // Ignore the last new line, since we'll get that one for free - // with the straight-forward Block syntax. - .replace( /\n$/, "" ) - .split( "\n" ) - .map( line => prefix + line ); - return "|\n" + lines.join( "\n" ); - } else { - - // This has either no trailing new lines, or more than 1. - // Use |+ so that YAML parsers will preserve it exactly. - const lines = value - .split( "\n" ) - .map( line => prefix + line ); - return "|+\n" + lines.join( "\n" ); - } - } else { - - // Simple string, use bare unquoted text - return value; - } - } - - // Handle null, boolean, array, and object - return JSON.stringify( decycledShallowClone( value ), null, 2 ); +function prettyYamlValue (value, indent = 4) { + if (value === undefined) { + // Not supported in JSON/YAML, turn into string + // and let the below output it as bare string. + value = String(value); + } + + // Support IE 9-11: Use isFinite instead of ES6 Number.isFinite + if (typeof value === 'number' && !isFinite(value)) { + // Turn NaN and Infinity into simple strings. + // Paranoia: Don't return directly just in case there's + // a way to add special characters here. + value = String(value); + } + + if (typeof value === 'number') { + // Simple numbers + return JSON.stringify(value); + } + + if (typeof value === 'string') { + // If any of these match, then we can't output it + // as bare unquoted text, because that would either + // cause data loss or invalid YAML syntax. + // + // - Quotes, escapes, line breaks, or JSON-like stuff. + const rSpecialJson = /['"\\/[{}\]\r\n]/; + + // - Characters that are special at the start of a YAML value + const rSpecialYaml = /[-?:,[\]{}#&*!|=>'"%@`]/; + + // - Leading or trailing whitespace. + const rUntrimmed = /(^\s|\s$)/; + + // - Ambiguous as YAML number, e.g. '2', '-1.2', '.2', or '2_000' + const rNumerical = /^[\d._-]+$/; + + // - Ambiguous as YAML bool. + // Use case-insensitive match, although technically only + // fully-lower, fully-upper, or uppercase-first would be ambiguous. + // e.g. true/True/TRUE, but not tRUe. + const rBool = /^(true|false|y|n|yes|no|on|off)$/i; + + // Is this a complex string? + if ( + value === '' || + rSpecialJson.test(value) || + rSpecialYaml.test(value[0]) || + rUntrimmed.test(value) || + rNumerical.test(value) || + rBool.test(value) + ) { + if (!/\n/.test(value)) { + // Complex one-line string, use JSON (quoted string) + return JSON.stringify(value); + } + + // See also + // Support IE 9-11: Avoid ES6 String#repeat + const prefix = (new Array(indent + 1)).join(' '); + + const trailingLinebreakMatch = value.match(/\n+$/); + const trailingLinebreaks = trailingLinebreakMatch + ? trailingLinebreakMatch[0].length + : 0; + + if (trailingLinebreaks === 1) { + // Use the most straight-forward "Block" string in YAML + // without any "Chomping" indicators. + const lines = value + + // Ignore the last new line, since we'll get that one for free + // with the straight-forward Block syntax. + .replace(/\n$/, '') + .split('\n') + .map(line => prefix + line); + return '|\n' + lines.join('\n'); + } else { + // This has either no trailing new lines, or more than 1. + // Use |+ so that YAML parsers will preserve it exactly. + const lines = value + .split('\n') + .map(line => prefix + line); + return '|+\n' + lines.join('\n'); + } + } else { + // Simple string, use bare unquoted text + return value; + } + } + + // Handle null, boolean, array, and object + return JSON.stringify(decycledShallowClone(value), null, 2); } /** * Creates a shallow clone of an object where cycles have * been replaced with "[Circular]". */ -function decycledShallowClone( object, ancestors = [] ) { - if ( ancestors.indexOf( object ) !== -1 ) { - return "[Circular]"; - } - - let clone; - - const type = Object.prototype.toString - .call( object ) - .replace( /^\[.+\s(.+?)]$/, "$1" ) - .toLowerCase(); - - switch ( type ) { - case "array": - ancestors.push( object ); - clone = object.map( function( element ) { - return decycledShallowClone( element, ancestors ); - } ); - ancestors.pop(); - break; - case "object": - ancestors.push( object ); - clone = {}; - Object.keys( object ).forEach( function( key ) { - clone[ key ] = decycledShallowClone( object[ key ], ancestors ); - } ); - ancestors.pop(); - break; - default: - clone = object; - } - - return clone; +function decycledShallowClone (object, ancestors = []) { + if (ancestors.indexOf(object) !== -1) { + return '[Circular]'; + } + + const type = Object.prototype.toString + .call(object) + .replace(/^\[.+\s(.+?)]$/, '$1') + .toLowerCase(); + + let clone; + + switch (type) { + case 'array': + ancestors.push(object); + clone = object.map(function (element) { + return decycledShallowClone(element, ancestors); + }); + ancestors.pop(); + break; + case 'object': + ancestors.push(object); + clone = {}; + Object.keys(object).forEach(function (key) { + clone[key] = decycledShallowClone(object[key], ancestors); + }); + ancestors.pop(); + break; + default: + clone = object; + } + + return clone; } export default class TapReporter { - constructor( runner, options = {} ) { - - // Cache references to console methods to ensure we can report failures - // from tests tests that mock the console object itself. - // https://github.com/qunitjs/qunit/issues/1340 - // Support IE 9: Function#bind is supported, but no console.log.bind(). - this.log = options.log || Function.prototype.bind.call( console.log, console ); - - this.testCount = 0; - this.ended = false; - this.bailed = false; - - runner.on( "error", this.onError.bind( this ) ); - runner.on( "runStart", this.onRunStart.bind( this ) ); - runner.on( "testEnd", this.onTestEnd.bind( this ) ); - runner.on( "runEnd", this.onRunEnd.bind( this ) ); - } - - static init( runner, options ) { - return new TapReporter( runner, options ); - } - - onRunStart( _runSuite ) { - this.log( "TAP version 13" ); - } - - onError( error ) { - if ( this.bailed ) { - return; - } - - this.bailed = true; - - // Imitate onTestEnd - // Skip this if we're past "runEnd" as it would look odd - if ( !this.ended ) { - this.testCount = this.testCount + 1; - this.log( kleur.red( `not ok ${this.testCount} global failure` ) ); - this.logError( error ); - } - - this.log( "Bail out! " + errorString( error ).split( "\n" )[ 0 ] ); - if ( this.ended ) { - this.logError( error ); - } - } - - onTestEnd( test ) { - this.testCount = this.testCount + 1; - - if ( test.status === "passed" ) { - this.log( `ok ${this.testCount} ${test.fullName.join( " > " )}` ); - } else if ( test.status === "skipped" ) { - this.log( - kleur.yellow( `ok ${this.testCount} # SKIP ${test.fullName.join( " > " )}` ) - ); - } else if ( test.status === "todo" ) { - this.log( - kleur.cyan( `not ok ${this.testCount} # TODO ${test.fullName.join( " > " )}` ) - ); - test.errors.forEach( ( error ) => this.logAssertion( error, "todo" ) ); - } else { - this.log( - kleur.red( `not ok ${this.testCount} ${test.fullName.join( " > " )}` ) - ); - test.errors.forEach( ( error ) => this.logAssertion( error ) ); - } - } - - onRunEnd( runSuite ) { - this.ended = true; - - this.log( `1..${runSuite.testCounts.total}` ); - this.log( `# pass ${runSuite.testCounts.passed}` ); - this.log( kleur.yellow( `# skip ${runSuite.testCounts.skipped}` ) ); - this.log( kleur.cyan( `# todo ${runSuite.testCounts.todo}` ) ); - this.log( kleur.red( `# fail ${runSuite.testCounts.failed}` ) ); - } - - logAssertion( error, severity ) { - let out = " ---"; - out += `\n message: ${prettyYamlValue( error.message || "failed" )}`; - out += `\n severity: ${prettyYamlValue( severity || "failed" )}`; - - if ( hasOwn.call( error, "actual" ) ) { - out += `\n actual : ${prettyYamlValue( error.actual )}`; - } - - if ( hasOwn.call( error, "expected" ) ) { - out += `\n expected: ${prettyYamlValue( error.expected )}`; - } - - if ( error.stack ) { - - // Since stacks aren't user generated, take a bit of liberty by - // adding a trailing new line to allow a straight-forward YAML Blocks. - out += `\n stack: ${prettyYamlValue( error.stack + "\n" )}`; - } - - out += "\n ..."; - this.log( out ); - } - - logError( error ) { - let out = " ---"; - out += `\n message: ${prettyYamlValue( errorString( error ) )}`; - out += `\n severity: ${prettyYamlValue( "failed" )}`; - if ( error && error.stack ) { - out += `\n stack: ${prettyYamlValue( error.stack + "\n" )}`; - } - out += "\n ..."; - this.log( out ); - } + constructor (runner, options = {}) { + // Cache references to console methods to ensure we can report failures + // from tests tests that mock the console object itself. + // https://github.com/qunitjs/qunit/issues/1340 + // Support IE 9: Function#bind is supported, but no console.log.bind(). + this.log = options.log || Function.prototype.bind.call(console.log, console); + + this.testCount = 0; + this.ended = false; + this.bailed = false; + + runner.on('error', this.onError.bind(this)); + runner.on('runStart', this.onRunStart.bind(this)); + runner.on('testEnd', this.onTestEnd.bind(this)); + runner.on('runEnd', this.onRunEnd.bind(this)); + } + + static init (runner, options) { + return new TapReporter(runner, options); + } + + onRunStart (_runSuite) { + this.log('TAP version 13'); + } + + onError (error) { + if (this.bailed) { + return; + } + + this.bailed = true; + + // Imitate onTestEnd + // Skip this if we're past "runEnd" as it would look odd + if (!this.ended) { + this.testCount = this.testCount + 1; + this.log(kleur.red(`not ok ${this.testCount} global failure`)); + this.logError(error); + } + + this.log('Bail out! ' + errorString(error).split('\n')[0]); + if (this.ended) { + this.logError(error); + } + } + + onTestEnd (test) { + this.testCount = this.testCount + 1; + + if (test.status === 'passed') { + this.log(`ok ${this.testCount} ${test.fullName.join(' > ')}`); + } else if (test.status === 'skipped') { + this.log( + kleur.yellow(`ok ${this.testCount} # SKIP ${test.fullName.join(' > ')}`) + ); + } else if (test.status === 'todo') { + this.log( + kleur.cyan(`not ok ${this.testCount} # TODO ${test.fullName.join(' > ')}`) + ); + test.errors.forEach((error) => this.logAssertion(error, 'todo')); + } else { + this.log( + kleur.red(`not ok ${this.testCount} ${test.fullName.join(' > ')}`) + ); + test.errors.forEach((error) => this.logAssertion(error)); + } + } + + onRunEnd (runSuite) { + this.ended = true; + + this.log(`1..${runSuite.testCounts.total}`); + this.log(`# pass ${runSuite.testCounts.passed}`); + this.log(kleur.yellow(`# skip ${runSuite.testCounts.skipped}`)); + this.log(kleur.cyan(`# todo ${runSuite.testCounts.todo}`)); + this.log(kleur.red(`# fail ${runSuite.testCounts.failed}`)); + } + + logAssertion (error, severity) { + let out = ' ---'; + out += `\n message: ${prettyYamlValue(error.message || 'failed')}`; + out += `\n severity: ${prettyYamlValue(severity || 'failed')}`; + + if (hasOwn.call(error, 'actual')) { + out += `\n actual : ${prettyYamlValue(error.actual)}`; + } + + if (hasOwn.call(error, 'expected')) { + out += `\n expected: ${prettyYamlValue(error.expected)}`; + } + + if (error.stack) { + // Since stacks aren't user generated, take a bit of liberty by + // adding a trailing new line to allow a straight-forward YAML Blocks. + out += `\n stack: ${prettyYamlValue(error.stack + '\n')}`; + } + + out += '\n ...'; + this.log(out); + } + + logError (error) { + let out = ' ---'; + out += `\n message: ${prettyYamlValue(errorString(error))}`; + out += `\n severity: ${prettyYamlValue('failed')}`; + if (error && error.stack) { + out += `\n stack: ${prettyYamlValue(error.stack + '\n')}`; + } + out += '\n ...'; + this.log(out); + } } diff --git a/src/reports/suite.js b/src/reports/suite.js index 973dbf9b0..ebb2ff26a 100644 --- a/src/reports/suite.js +++ b/src/reports/suite.js @@ -1,116 +1,116 @@ -import { performance } from "../core/utilities"; +import { performance } from '../core/utilities'; export default class SuiteReport { - constructor( name, parentSuite ) { - this.name = name; - this.fullName = parentSuite ? parentSuite.fullName.concat( name ) : []; - - // When an "error" event is emitted from onUncaughtException(), the - // "runEnd" event should report the status as failed. The "runEnd" event data - // is tracked through this property (via the "runSuite" instance). - this.globalFailureCount = 0; - - this.tests = []; - this.childSuites = []; - - if ( parentSuite ) { - parentSuite.pushChildSuite( this ); - } - } - - start( recordTime ) { - if ( recordTime ) { - this._startTime = performance.now(); - - const suiteLevel = this.fullName.length; - performance.mark( `qunit_suite_${suiteLevel}_start` ); - } - - return { - name: this.name, - fullName: this.fullName.slice(), - tests: this.tests.map( test => test.start() ), - childSuites: this.childSuites.map( suite => suite.start() ), - testCounts: { - total: this.getTestCounts().total - } - }; - } - - end( recordTime ) { - if ( recordTime ) { - this._endTime = performance.now(); - - const suiteLevel = this.fullName.length; - const suiteName = this.fullName.join( " – " ); - - performance.mark( `qunit_suite_${suiteLevel}_end` ); - performance.measure( - suiteLevel === 0 ? "QUnit Test Run" : `QUnit Test Suite: ${suiteName}`, - `qunit_suite_${suiteLevel}_start`, - `qunit_suite_${suiteLevel}_end` - ); - } - - return { - name: this.name, - fullName: this.fullName.slice(), - tests: this.tests.map( test => test.end() ), - childSuites: this.childSuites.map( suite => suite.end() ), - testCounts: this.getTestCounts(), - runtime: this.getRuntime(), - status: this.getStatus() - }; - } - - pushChildSuite( suite ) { - this.childSuites.push( suite ); - } - - pushTest( test ) { - this.tests.push( test ); - } - - getRuntime() { - return this._endTime - this._startTime; - } - - getTestCounts( counts = { passed: 0, failed: 0, skipped: 0, todo: 0, total: 0 } ) { - counts.failed += this.globalFailureCount; - counts.total += this.globalFailureCount; - - counts = this.tests.reduce( ( counts, test ) => { - if ( test.valid ) { - counts[ test.getStatus() ]++; - counts.total++; - } - - return counts; - }, counts ); - - return this.childSuites.reduce( ( counts, suite ) => { - return suite.getTestCounts( counts ); - }, counts ); - } - - getStatus() { - const { - total, - failed, - skipped, - todo - } = this.getTestCounts(); - - if ( failed ) { - return "failed"; - } else { - if ( skipped === total ) { - return "skipped"; - } else if ( todo === total ) { - return "todo"; - } else { - return "passed"; - } - } - } + constructor (name, parentSuite) { + this.name = name; + this.fullName = parentSuite ? parentSuite.fullName.concat(name) : []; + + // When an "error" event is emitted from onUncaughtException(), the + // "runEnd" event should report the status as failed. The "runEnd" event data + // is tracked through this property (via the "runSuite" instance). + this.globalFailureCount = 0; + + this.tests = []; + this.childSuites = []; + + if (parentSuite) { + parentSuite.pushChildSuite(this); + } + } + + start (recordTime) { + if (recordTime) { + this._startTime = performance.now(); + + const suiteLevel = this.fullName.length; + performance.mark(`qunit_suite_${suiteLevel}_start`); + } + + return { + name: this.name, + fullName: this.fullName.slice(), + tests: this.tests.map(test => test.start()), + childSuites: this.childSuites.map(suite => suite.start()), + testCounts: { + total: this.getTestCounts().total + } + }; + } + + end (recordTime) { + if (recordTime) { + this._endTime = performance.now(); + + const suiteLevel = this.fullName.length; + const suiteName = this.fullName.join(' – '); + + performance.mark(`qunit_suite_${suiteLevel}_end`); + performance.measure( + suiteLevel === 0 ? 'QUnit Test Run' : `QUnit Test Suite: ${suiteName}`, + `qunit_suite_${suiteLevel}_start`, + `qunit_suite_${suiteLevel}_end` + ); + } + + return { + name: this.name, + fullName: this.fullName.slice(), + tests: this.tests.map(test => test.end()), + childSuites: this.childSuites.map(suite => suite.end()), + testCounts: this.getTestCounts(), + runtime: this.getRuntime(), + status: this.getStatus() + }; + } + + pushChildSuite (suite) { + this.childSuites.push(suite); + } + + pushTest (test) { + this.tests.push(test); + } + + getRuntime () { + return this._endTime - this._startTime; + } + + getTestCounts (counts = { passed: 0, failed: 0, skipped: 0, todo: 0, total: 0 }) { + counts.failed += this.globalFailureCount; + counts.total += this.globalFailureCount; + + counts = this.tests.reduce((counts, test) => { + if (test.valid) { + counts[test.getStatus()]++; + counts.total++; + } + + return counts; + }, counts); + + return this.childSuites.reduce((counts, suite) => { + return suite.getTestCounts(counts); + }, counts); + } + + getStatus () { + const { + total, + failed, + skipped, + todo + } = this.getTestCounts(); + + if (failed) { + return 'failed'; + } else { + if (skipped === total) { + return 'skipped'; + } else if (todo === total) { + return 'todo'; + } else { + return 'passed'; + } + } + } } diff --git a/src/reports/test.js b/src/reports/test.js index 2321bdcac..aab7f28e3 100644 --- a/src/reports/test.js +++ b/src/reports/test.js @@ -1,100 +1,100 @@ -import { extend, performance } from "../core/utilities"; +import { extend, performance } from '../core/utilities'; export default class TestReport { - constructor( name, suite, options ) { - this.name = name; - this.suiteName = suite.name; - this.fullName = suite.fullName.concat( name ); - this.runtime = 0; - this.assertions = []; - - this.skipped = !!options.skip; - this.todo = !!options.todo; - - this.valid = options.valid; - - this._startTime = 0; - this._endTime = 0; - - suite.pushTest( this ); - } - - start( recordTime ) { - if ( recordTime ) { - this._startTime = performance.now(); - performance.mark( "qunit_test_start" ); - } - - return { - name: this.name, - suiteName: this.suiteName, - fullName: this.fullName.slice() - }; - } - - end( recordTime ) { - if ( recordTime ) { - this._endTime = performance.now(); - if ( performance ) { - performance.mark( "qunit_test_end" ); - - const testName = this.fullName.join( " – " ); - - performance.measure( - `QUnit Test: ${testName}`, - "qunit_test_start", - "qunit_test_end" - ); - } - } - - return extend( this.start(), { - runtime: this.getRuntime(), - status: this.getStatus(), - errors: this.getFailedAssertions(), - assertions: this.getAssertions() - } ); - } - - pushAssertion( assertion ) { - this.assertions.push( assertion ); - } - - getRuntime() { - return this._endTime - this._startTime; - } - - getStatus() { - if ( this.skipped ) { - return "skipped"; - } - - const testPassed = this.getFailedAssertions().length > 0 ? this.todo : !this.todo; - - if ( !testPassed ) { - return "failed"; - } else if ( this.todo ) { - return "todo"; - } else { - return "passed"; - } - } - - getFailedAssertions() { - return this.assertions.filter( assertion => !assertion.passed ); - } - - getAssertions() { - return this.assertions.slice(); - } - - // Remove actual and expected values from assertions. This is to prevent - // leaking memory throughout a test suite. - slimAssertions() { - this.assertions = this.assertions.map( assertion => { - delete assertion.actual; - delete assertion.expected; - return assertion; - } ); - } + constructor (name, suite, options) { + this.name = name; + this.suiteName = suite.name; + this.fullName = suite.fullName.concat(name); + this.runtime = 0; + this.assertions = []; + + this.skipped = !!options.skip; + this.todo = !!options.todo; + + this.valid = options.valid; + + this._startTime = 0; + this._endTime = 0; + + suite.pushTest(this); + } + + start (recordTime) { + if (recordTime) { + this._startTime = performance.now(); + performance.mark('qunit_test_start'); + } + + return { + name: this.name, + suiteName: this.suiteName, + fullName: this.fullName.slice() + }; + } + + end (recordTime) { + if (recordTime) { + this._endTime = performance.now(); + if (performance) { + performance.mark('qunit_test_end'); + + const testName = this.fullName.join(' – '); + + performance.measure( + `QUnit Test: ${testName}`, + 'qunit_test_start', + 'qunit_test_end' + ); + } + } + + return extend(this.start(), { + runtime: this.getRuntime(), + status: this.getStatus(), + errors: this.getFailedAssertions(), + assertions: this.getAssertions() + }); + } + + pushAssertion (assertion) { + this.assertions.push(assertion); + } + + getRuntime () { + return this._endTime - this._startTime; + } + + getStatus () { + if (this.skipped) { + return 'skipped'; + } + + const testPassed = this.getFailedAssertions().length > 0 ? this.todo : !this.todo; + + if (!testPassed) { + return 'failed'; + } else if (this.todo) { + return 'todo'; + } else { + return 'passed'; + } + } + + getFailedAssertions () { + return this.assertions.filter(assertion => !assertion.passed); + } + + getAssertions () { + return this.assertions.slice(); + } + + // Remove actual and expected values from assertions. This is to prevent + // leaking memory throughout a test suite. + slimAssertions () { + this.assertions = this.assertions.map(assertion => { + delete assertion.actual; + delete assertion.expected; + return assertion; + }); + } } diff --git a/src/test.js b/src/test.js index 34757ec11..8ce06b01f 100644 --- a/src/test.js +++ b/src/test.js @@ -1,1074 +1,1062 @@ -import { globalThis, setTimeout, clearTimeout, StringMap } from "./globals"; -import { emit } from "./events"; -import Assert from "./assert"; -import Logger from "./logger"; -import Promise from "./promise"; +import { globalThis, setTimeout, clearTimeout, StringMap } from './globals'; +import { emit } from './events'; +import Assert from './assert'; +import Logger from './logger'; +import Promise from './promise'; -import config from "./core/config"; +import config from './core/config'; import { - diff, - errorString, - extend, - generateHash, - hasOwn, - inArray, - now, - objectType -} from "./core/utilities"; -import { runLoggingCallbacks } from "./core/logging"; -import { extractStacktrace, sourceFromStacktrace } from "./core/stacktrace"; -import ProcessingQueue from "./core/processing-queue"; - -import TestReport from "./reports/test"; - -export default function Test( settings ) { - this.expected = null; - this.assertions = []; - this.module = config.currentModule; - this.steps = []; - this.timeout = undefined; - this.data = undefined; - this.withData = false; - this.pauses = new StringMap(); - this.nextPauseId = 1; - extend( this, settings ); - - // If a module is skipped, all its tests and the tests of the child suites - // should be treated as skipped even if they are defined as `only` or `todo`. - // As for `todo` module, all its tests will be treated as `todo` except for - // tests defined as `skip` which will be left intact. - // - // So, if a test is defined as `todo` and is inside a skipped module, we should - // then treat that test as if was defined as `skip`. - if ( this.module.skip ) { - this.skip = true; - this.todo = false; - - // Skipped tests should be left intact - } else if ( this.module.todo && !this.skip ) { - this.todo = true; - } - - // Queuing a late test after the run has ended is not allowed. - // This was once supported for internal use by QUnit.onError(). - // Ref https://github.com/qunitjs/qunit/issues/1377 - if ( ProcessingQueue.finished ) { - - // Using this for anything other than onError(), such as testing in QUnit.done(), - // is unstable and will likely result in the added tests being ignored by CI. - // (Meaning the CI passes irregardless of the added tests). - // - // TODO: Make this an error in QUnit 3.0 - // throw new Error( "Unexpected new test after the run already ended" ); - Logger.warn( "Unexpected test after runEnd. This is unstable and will fail in QUnit 3.0." ); - return; - } - if ( !this.skip && typeof this.callback !== "function" ) { - const method = this.todo ? "QUnit.todo" : "QUnit.test"; - throw new TypeError( `You must provide a callback to ${method}("${this.testName}")` ); - } - - // No validation after this. Beyond this point, failures must be recorded as - // a completed test with errors, instead of early bail out. - // Otherwise, internals may be left in an inconsistent state. - // Ref https://github.com/qunitjs/qunit/issues/1514 - - ++Test.count; - this.errorForStack = new Error(); - if ( this.callback && this.callback.validTest ) { - - // Omit the test-level trace for the internal "No tests" test failure, - // There is already an assertion-level trace, and that's noisy enough - // as it is. - this.errorForStack.stack = undefined; - } - this.testReport = new TestReport( this.testName, this.module.suiteReport, { - todo: this.todo, - skip: this.skip, - valid: this.valid() - } ); - - // Register unique strings - for ( let i = 0, l = this.module.tests; i < l.length; i++ ) { - if ( this.module.tests[ i ].name === this.testName ) { - this.testName += " "; - } - } - - this.testId = generateHash( this.module.name, this.testName ); - - this.module.tests.push( { - name: this.testName, - testId: this.testId, - skip: !!this.skip - } ); - - if ( this.skip ) { - - // Skipped tests will fully ignore any sent callback - this.callback = function() {}; - this.async = false; - this.expected = 0; - } else { - this.assert = new Assert( this ); - } + diff, + errorString, + extend, + generateHash, + hasOwn, + inArray, + now, + objectType +} from './core/utilities'; +import { runLoggingCallbacks } from './core/logging'; +import { extractStacktrace, sourceFromStacktrace } from './core/stacktrace'; +import ProcessingQueue from './core/processing-queue'; + +import TestReport from './reports/test'; + +export default function Test (settings) { + this.expected = null; + this.assertions = []; + this.module = config.currentModule; + this.steps = []; + this.timeout = undefined; + this.data = undefined; + this.withData = false; + this.pauses = new StringMap(); + this.nextPauseId = 1; + extend(this, settings); + + // If a module is skipped, all its tests and the tests of the child suites + // should be treated as skipped even if they are defined as `only` or `todo`. + // As for `todo` module, all its tests will be treated as `todo` except for + // tests defined as `skip` which will be left intact. + // + // So, if a test is defined as `todo` and is inside a skipped module, we should + // then treat that test as if was defined as `skip`. + if (this.module.skip) { + this.skip = true; + this.todo = false; + + // Skipped tests should be left intact + } else if (this.module.todo && !this.skip) { + this.todo = true; + } + + // Queuing a late test after the run has ended is not allowed. + // This was once supported for internal use by QUnit.onError(). + // Ref https://github.com/qunitjs/qunit/issues/1377 + if (ProcessingQueue.finished) { + // Using this for anything other than onError(), such as testing in QUnit.done(), + // is unstable and will likely result in the added tests being ignored by CI. + // (Meaning the CI passes irregardless of the added tests). + // + // TODO: Make this an error in QUnit 3.0 + // throw new Error( "Unexpected new test after the run already ended" ); + Logger.warn('Unexpected test after runEnd. This is unstable and will fail in QUnit 3.0.'); + return; + } + if (!this.skip && typeof this.callback !== 'function') { + const method = this.todo ? 'QUnit.todo' : 'QUnit.test'; + throw new TypeError(`You must provide a callback to ${method}("${this.testName}")`); + } + + // No validation after this. Beyond this point, failures must be recorded as + // a completed test with errors, instead of early bail out. + // Otherwise, internals may be left in an inconsistent state. + // Ref https://github.com/qunitjs/qunit/issues/1514 + + ++Test.count; + this.errorForStack = new Error(); + if (this.callback && this.callback.validTest) { + // Omit the test-level trace for the internal "No tests" test failure, + // There is already an assertion-level trace, and that's noisy enough + // as it is. + this.errorForStack.stack = undefined; + } + this.testReport = new TestReport(this.testName, this.module.suiteReport, { + todo: this.todo, + skip: this.skip, + valid: this.valid() + }); + + // Register unique strings + for (let i = 0, l = this.module.tests; i < l.length; i++) { + if (this.module.tests[i].name === this.testName) { + this.testName += ' '; + } + } + + this.testId = generateHash(this.module.name, this.testName); + + this.module.tests.push({ + name: this.testName, + testId: this.testId, + skip: !!this.skip + }); + + if (this.skip) { + // Skipped tests will fully ignore any sent callback + this.callback = function () {}; + this.async = false; + this.expected = 0; + } else { + this.assert = new Assert(this); + } } Test.count = 0; -function getNotStartedModules( startModule ) { - let module = startModule; - const modules = []; +function getNotStartedModules (startModule) { + let module = startModule; + const modules = []; - while ( module && module.testsRun === 0 ) { - modules.push( module ); - module = module.parentModule; - } + while (module && module.testsRun === 0) { + modules.push(module); + module = module.parentModule; + } - // The above push modules from the child to the parent - // return a reversed order with the top being the top most parent module - return modules.reverse(); + // The above push modules from the child to the parent + // return a reversed order with the top being the top most parent module + return modules.reverse(); } Test.prototype = { - // generating a stack trace can be expensive, so using a getter defers this until we need it - get stack() { - return extractStacktrace( this.errorForStack, 2 ); - }, - - before: function() { - const module = this.module; - const notStartedModules = getNotStartedModules( module ); - - // ensure the callbacks are executed serially for each module - const callbackPromises = notStartedModules.reduce( ( promiseChain, startModule ) => { - return promiseChain.then( () => { - startModule.stats = { all: 0, bad: 0, started: now() }; - emit( "suiteStart", startModule.suiteReport.start( true ) ); - return runLoggingCallbacks( "moduleStart", { - name: startModule.name, - tests: startModule.tests - } ); - } ); - }, Promise.resolve( [] ) ); - - return callbackPromises.then( () => { - config.current = this; - - this.testEnvironment = extend( {}, module.testEnvironment ); - - this.started = now(); - emit( "testStart", this.testReport.start( true ) ); - return runLoggingCallbacks( "testStart", { - name: this.testName, - module: module.name, - testId: this.testId, - previousFailure: this.previousFailure - } ).then( () => { - if ( !config.pollution ) { - saveGlobal(); - } - } ); - } ); - }, - - run: function() { - - config.current = this; - - this.callbackStarted = now(); - - if ( config.notrycatch ) { - runTest( this ); - return; - } - - try { - runTest( this ); - } catch ( e ) { - this.pushFailure( "Died on test #" + ( this.assertions.length + 1 ) + " " + - this.stack + ": " + ( e.message || e ), extractStacktrace( e, 0 ) ); - - // Else next test will carry the responsibility - saveGlobal(); - - // Restart the tests if they're blocking - if ( config.blocking ) { - internalRecover( this ); - } - } - - function runTest( test ) { - let promise; - if ( test.withData ) { - promise = test.callback.call( test.testEnvironment, test.assert, test.data ); - } else { - promise = test.callback.call( test.testEnvironment, test.assert ); - } - test.resolvePromise( promise ); - - // If the test has an async "pause" on it, but the timeout is 0, then we push a - // failure as the test should be synchronous. - if ( test.timeout === 0 && test.pauses.size > 0 ) { - pushFailure( - "Test did not finish synchronously even though assert.timeout( 0 ) was used.", - sourceFromStacktrace( 2 ) - ); - } - } - }, - - after: function() { - checkPollution(); - }, - - queueGlobalHook( hook, hookName ) { - const runHook = () => { - config.current = this; - - let promise; - - if ( config.notrycatch ) { - promise = hook.call( this.testEnvironment, this.assert ); - - } else { - try { - promise = hook.call( this.testEnvironment, this.assert ); - } catch ( error ) { - this.pushFailure( - "Global " + hookName + " failed on " + this.testName + - ": " + errorString( error ), - extractStacktrace( error, 0 ) - ); - return; - } - } - - this.resolvePromise( promise, hookName ); - }; - - return runHook; - }, - - queueHook( hook, hookName, hookOwner ) { - const callHook = () => { - const promise = hook.call( this.testEnvironment, this.assert ); - this.resolvePromise( promise, hookName ); - }; - - const runHook = () => { - if ( hookName === "before" ) { - if ( hookOwner.testsRun !== 0 ) { - return; - } - - this.preserveEnvironment = true; - } - - // The 'after' hook should only execute when there are not tests left and - // when the 'after' and 'finish' tasks are the only tasks left to process - if ( hookName === "after" && - !lastTestWithinModuleExecuted( hookOwner ) && - ( config.queue.length > 0 || ProcessingQueue.taskCount() > 2 ) ) { - return; - } - - config.current = this; - if ( config.notrycatch ) { - callHook(); - return; - } - try { - - // This try-block includes the indirect call to resolvePromise, which shouldn't - // have to be inside try-catch. But, since we support any user-provided thenable - // object, the thenable might throw in some unexpected way. - // This subtle behaviour is undocumented. To avoid new failures in minor releases - // we will not change this until QUnit 3. - // TODO: In QUnit 3, reduce this try-block to just hook.call(), matching - // the simplicity of queueGlobalHook. - callHook(); - } catch ( error ) { - this.pushFailure( hookName + " failed on " + this.testName + ": " + - ( error.message || error ), extractStacktrace( error, 0 ) ); - } - }; - - return runHook; - }, - - // Currently only used for module level hooks, can be used to add global level ones - hooks( handler ) { - const hooks = []; - - function processGlobalhooks( test ) { - if ( - ( handler === "beforeEach" || handler === "afterEach" ) && - config.globalHooks[ handler ] - ) { - for ( let i = 0; i < config.globalHooks[ handler ].length; i++ ) { - hooks.push( - test.queueGlobalHook( config.globalHooks[ handler ][ i ], handler ) - ); - } - } - } - - function processHooks( test, module ) { - if ( module.parentModule ) { - processHooks( test, module.parentModule ); - } - - if ( module.hooks[ handler ].length ) { - for ( let i = 0; i < module.hooks[ handler ].length; i++ ) { - hooks.push( test.queueHook( module.hooks[ handler ][ i ], handler, module ) ); - } - } - } - - // Hooks are ignored on skipped tests - if ( !this.skip ) { - processGlobalhooks( this ); - processHooks( this, this.module ); - } - - return hooks; - }, - - finish: function() { - config.current = this; - - // Release the test callback to ensure that anything referenced has been - // released to be garbage collected. - this.callback = undefined; - - if ( this.steps.length ) { - const stepsList = this.steps.join( ", " ); - this.pushFailure( "Expected assert.verifySteps() to be called before end of test " + - `after using assert.step(). Unverified steps: ${stepsList}`, this.stack ); - } - - if ( config.requireExpects && this.expected === null ) { - this.pushFailure( "Expected number of assertions to be defined, but expect() was " + - "not called.", this.stack ); - } else if ( this.expected !== null && this.expected !== this.assertions.length ) { - this.pushFailure( "Expected " + this.expected + " assertions, but " + - this.assertions.length + " were run", this.stack ); - } else if ( this.expected === null && !this.assertions.length ) { - this.pushFailure( "Expected at least one assertion, but none were run - call " + - "expect(0) to accept zero assertions.", this.stack ); - } - - const module = this.module; - const moduleName = module.name; - const testName = this.testName; - const skipped = !!this.skip; - const todo = !!this.todo; - let bad = 0; - const storage = config.storage; - - this.runtime = now() - this.started; - - config.stats.all += this.assertions.length; - config.stats.testCount += 1; - module.stats.all += this.assertions.length; - - for ( let i = 0; i < this.assertions.length; i++ ) { - - // A failing assertion will counts toward the HTML Reporter's - // "X assertions, Y failed" line even if it was inside a todo. - // Inverting this would be similarly confusing since all but the last - // passing assertion inside a todo test should be considered as good. - // These stats don't decide the outcome of anything, so counting them - // as failing seems the most intuitive. - if ( !this.assertions[ i ].result ) { - bad++; - config.stats.bad++; - module.stats.bad++; - } - } - - if ( skipped ) { - incrementTestsIgnored( module ); - } else { - incrementTestsRun( module ); - } - - // Store result when possible. - // Note that this also marks todo tests as bad, thus they get hoisted, - // and always run first on refresh. - if ( storage ) { - if ( bad ) { - storage.setItem( "qunit-test-" + moduleName + "-" + testName, bad ); - } else { - storage.removeItem( "qunit-test-" + moduleName + "-" + testName ); - } - } - - // After emitting the js-reporters event we cleanup the assertion data to - // avoid leaking it. It is not used by the legacy testDone callbacks. - emit( "testEnd", this.testReport.end( true ) ); - this.testReport.slimAssertions(); - const test = this; - - return runLoggingCallbacks( "testDone", { - name: testName, - module: moduleName, - skipped: skipped, - todo: todo, - failed: bad, - passed: this.assertions.length - bad, - total: this.assertions.length, - runtime: skipped ? 0 : this.runtime, - - // HTML Reporter use - assertions: this.assertions, - testId: this.testId, - - // Source of Test - // generating stack trace is expensive, so using a getter will help defer this until we need it - get source() { return test.stack; } - } ).then( function() { - if ( allTestsExecuted( module ) ) { - const completedModules = [ module ]; - - // Check if the parent modules, iteratively, are done. If that the case, - // we emit the `suiteEnd` event and trigger `moduleDone` callback. - let parent = module.parentModule; - while ( parent && allTestsExecuted( parent ) ) { - completedModules.push( parent ); - parent = parent.parentModule; - } - - return completedModules.reduce( ( promiseChain, completedModule ) => { - return promiseChain.then( () => { - return logSuiteEnd( completedModule ); - } ); - }, Promise.resolve( [] ) ); - } - } ).then( function() { - config.current = undefined; - } ); - - function logSuiteEnd( module ) { - - // Reset `module.hooks` to ensure that anything referenced in these hooks - // has been released to be garbage collected. Descendant modules that were - // entirely skipped, e.g. due to filtering, will never have this method - // called for them, but might have hooks with references pinning data in - // memory (even if the hooks weren't actually executed), so we reset the - // hooks on all descendant modules here as well. This is safe because we - // will never call this as long as any descendant modules still have tests - // to run. This also means that in multi-tiered nesting scenarios we might - // reset the hooks multiple times on some modules, but that's harmless. - const modules = [ module ]; - while ( modules.length ) { - const nextModule = modules.shift(); - nextModule.hooks = {}; - modules.push( ...nextModule.childModules ); - } - - emit( "suiteEnd", module.suiteReport.end( true ) ); - return runLoggingCallbacks( "moduleDone", { - name: module.name, - tests: module.tests, - failed: module.stats.bad, - passed: module.stats.all - module.stats.bad, - total: module.stats.all, - runtime: now() - module.stats.started - } ); - } - }, - - preserveTestEnvironment: function() { - if ( this.preserveEnvironment ) { - this.module.testEnvironment = this.testEnvironment; - this.testEnvironment = extend( {}, this.module.testEnvironment ); - } - }, - - queue() { - const test = this; - - if ( !this.valid() ) { - incrementTestsIgnored( this.module ); - return; - } - - function runTest() { - return [ - function() { - return test.before(); - }, - - ...test.hooks( "before" ), - - function() { - test.preserveTestEnvironment(); - }, - - ...test.hooks( "beforeEach" ), - - function() { - test.run(); - }, - - ...test.hooks( "afterEach" ).reverse(), - ...test.hooks( "after" ).reverse(), - - function() { - test.after(); - }, - - function() { - return test.finish(); - } - ]; - } - - const previousFailCount = config.storage && - +config.storage.getItem( "qunit-test-" + this.module.name + "-" + this.testName ); - - // Prioritize previously failed tests, detected from storage - const prioritize = config.reorder && !!previousFailCount; - - this.previousFailure = !!previousFailCount; - - ProcessingQueue.add( runTest, prioritize, config.seed ); - }, - - pushResult: function( resultInfo ) { - if ( this !== config.current ) { - const message = resultInfo && resultInfo.message || ""; - const testName = this && this.testName || ""; - const error = "Assertion occurred after test finished.\n" + - "> Test: " + testName + "\n" + - "> Message: " + message + "\n"; - - throw new Error( error ); - } - - // Destructure of resultInfo = { result, actual, expected, message, negative } - const details = { - module: this.module.name, - name: this.testName, - result: resultInfo.result, - message: resultInfo.message, - actual: resultInfo.actual, - testId: this.testId, - negative: resultInfo.negative || false, - runtime: now() - this.started, - todo: !!this.todo - }; - - if ( hasOwn.call( resultInfo, "expected" ) ) { - details.expected = resultInfo.expected; - } - - if ( !resultInfo.result ) { - const source = resultInfo.source || sourceFromStacktrace(); - - if ( source ) { - details.source = source; - } - } - - this.logAssertion( details ); - - this.assertions.push( { - result: !!resultInfo.result, - message: resultInfo.message - } ); - }, - - pushFailure: function( message, source, actual ) { - if ( !( this instanceof Test ) ) { - throw new Error( "pushFailure() assertion outside test context, was " + - sourceFromStacktrace( 2 ) ); - } - - this.pushResult( { - result: false, - message: message || "error", - actual: actual || null, - source - } ); - }, - - /** - * Log assertion details using both the old QUnit.log interface and - * QUnit.on( "assertion" ) interface. - * - * @private - */ - logAssertion( details ) { - runLoggingCallbacks( "log", details ); - - const assertion = { - passed: details.result, - actual: details.actual, - expected: details.expected, - message: details.message, - stack: details.source, - todo: details.todo - }; - this.testReport.pushAssertion( assertion ); - emit( "assertion", assertion ); - }, - - /** - * Reset config.timeout with a new timeout duration. - * - * @param {number} timeoutDuration - */ - internalResetTimeout( timeoutDuration ) { - clearTimeout( config.timeout ); - config.timeout = setTimeout( config.timeoutHandler( timeoutDuration ), timeoutDuration ); - }, - - /** - * Create a new async pause and return a new function that can release the pause. - * - * This mechanism is internally used by: - * - * - explicit async pauses, created by calling `assert.async()`, - * - implicit async pauses, created when `QUnit.test()` or module hook callbacks - * use async-await or otherwise return a Promise. - * - * Happy scenario: - * - * - Pause is created by calling internalStop(). - * - * Pause is released normally by invoking release() during the same test. - * - * The release() callback lets internal processing resume. - * - * Failure scenarios: - * - * - The test fails due to an uncaught exception. - * - * In this case, Test.run() will call internalRecover() which empties the clears all - * async pauses and sets the cancelled flag, which means we silently ignore any - * late calls to the resume() callback, as we will have moved on to a different - * test by then, and we don't want to cause an extra "release during a different test" - * errors that the developer isn't really responsible for. This can happen when a test - * correctly schedules a call to release(), but also causes an uncaught error. The - * uncaught error means we will no longer wait for the release (as it might not arrive). - * - * - Pause is never released, or called an insufficient number of times. - * - * Our timeout handler will kill the pause and resume test processing, basically - * like internalRecover(), but for one pause instead of any/all. - * - * Here, too, any late calls to resume() will be silently ignored to avoid - * extra errors. We tolerate this since the original test will have already been - * marked as failure. - * - * TODO: QUnit 3 will enable timeouts by default , - * but right now a test will hang indefinitely if async pauses are not released, - * unless QUnit.config.testTimeout or assert.timeout() is used. - * - * - Pause is spontaneously released during a different test, - * or when no test is currently running. - * - * This is close to impossible because this error only happens if the original test - * succesfully finished first (since other failure scenarios kill pauses and ignore - * late calls). It can happen if a test ended exactly as expected, but has some - * external or shared state continuing to hold a reference to the release callback, - * and either the same test scheduled another call to it in the future, or a later test - * causes it to be called through some shared state. - * - * - Pause release() is called too often, during the same test. - * - * This simply throws an error, after which uncaught error handling picks it up - * and processing resumes. - * - * @param {number} [requiredCalls=1] - */ - internalStop( requiredCalls = 1 ) { - config.blocking = true; - - const test = this; - const pauseId = this.nextPauseId++; - const pause = { - cancelled: false, - remaining: requiredCalls - }; - test.pauses.set( pauseId, pause ); - - function release() { - if ( pause.cancelled ) { - return; - } - if ( config.current === undefined ) { - throw new Error( "Unexpected release of async pause after tests finished.\n" + - `> Test: ${test.testName} [async #${pauseId}]` ); - } - if ( config.current !== test ) { - throw new Error( "Unexpected release of async pause during a different test.\n" + - `> Test: ${test.testName} [async #${pauseId}]` ); - } - if ( pause.remaining <= 0 ) { - throw new Error( "Tried to release async pause that was already released.\n" + - `> Test: ${test.testName} [async #${pauseId}]` ); - } - - // The `requiredCalls` parameter exists to support `assert.async(count)` - pause.remaining--; - if ( pause.remaining === 0 ) { - test.pauses.delete( pauseId ); - } - - internalStart( test ); - } - - // Set a recovery timeout, if so configured. - if ( setTimeout ) { - let timeoutDuration; - if ( typeof test.timeout === "number" ) { - timeoutDuration = test.timeout; - } else if ( typeof config.testTimeout === "number" ) { - timeoutDuration = config.testTimeout; - } - - if ( typeof timeoutDuration === "number" && timeoutDuration > 0 ) { - config.timeoutHandler = function( timeout ) { - return function() { - config.timeout = null; - pause.cancelled = true; - test.pauses.delete( pauseId ); - - test.pushFailure( - `Test took longer than ${timeout}ms; test timed out.`, - sourceFromStacktrace( 2 ) - ); - internalStart( test ); - }; - }; - clearTimeout( config.timeout ); - config.timeout = setTimeout( - config.timeoutHandler( timeoutDuration ), - timeoutDuration - ); - } - } - - return release; - }, - - resolvePromise: function( promise, phase ) { - if ( promise != null ) { - const test = this; - const then = promise.then; - if ( objectType( then ) === "function" ) { - const resume = test.internalStop(); - const resolve = function() { resume(); }; - if ( config.notrycatch ) { - then.call( promise, resolve ); - } else { - const reject = function( error ) { - const message = "Promise rejected " + - ( !phase ? "during" : phase.replace( /Each$/, "" ) ) + - " \"" + test.testName + "\": " + - ( ( error && error.message ) || error ); - test.pushFailure( message, extractStacktrace( error, 0 ) ); - - // Else next test will carry the responsibility - saveGlobal(); - - // Unblock - internalRecover( test ); - }; - then.call( promise, resolve, reject ); - } - } - } - }, - - valid: function() { - const filter = config.filter; - const regexFilter = /^(!?)\/([\w\W]*)\/(i?$)/.exec( filter ); - const module = config.module && config.module.toLowerCase(); - const fullName = ( this.module.name + ": " + this.testName ); - - function moduleChainNameMatch( testModule ) { - const testModuleName = testModule.name ? testModule.name.toLowerCase() : null; - if ( testModuleName === module ) { - return true; - } else if ( testModule.parentModule ) { - return moduleChainNameMatch( testModule.parentModule ); - } else { - return false; - } - } - - function moduleChainIdMatch( testModule ) { - return inArray( testModule.moduleId, config.moduleId ) || - testModule.parentModule && moduleChainIdMatch( testModule.parentModule ); - } - - // Internally-generated tests are always valid - if ( this.callback && this.callback.validTest ) { - return true; - } - - if ( config.moduleId && config.moduleId.length > 0 && - !moduleChainIdMatch( this.module ) ) { - - return false; - } - - if ( config.testId && config.testId.length > 0 && - !inArray( this.testId, config.testId ) ) { - - return false; - } - - if ( module && !moduleChainNameMatch( this.module ) ) { - return false; - } - - if ( !filter ) { - return true; - } - - return regexFilter ? - this.regexFilter( !!regexFilter[ 1 ], regexFilter[ 2 ], regexFilter[ 3 ], fullName ) : - this.stringFilter( filter, fullName ); - }, - - regexFilter: function( exclude, pattern, flags, fullName ) { - const regex = new RegExp( pattern, flags ); - const match = regex.test( fullName ); - - return match !== exclude; - }, - - stringFilter: function( filter, fullName ) { - filter = filter.toLowerCase(); - fullName = fullName.toLowerCase(); - - const include = filter.charAt( 0 ) !== "!"; - if ( !include ) { - filter = filter.slice( 1 ); - } - - // If the filter matches, we need to honour include - if ( fullName.indexOf( filter ) !== -1 ) { - return include; - } - - // Otherwise, do the opposite - return !include; - } + // generating a stack trace can be expensive, so using a getter defers this until we need it + get stack () { + return extractStacktrace(this.errorForStack, 2); + }, + + before: function () { + const module = this.module; + const notStartedModules = getNotStartedModules(module); + + // ensure the callbacks are executed serially for each module + const callbackPromises = notStartedModules.reduce((promiseChain, startModule) => { + return promiseChain.then(() => { + startModule.stats = { all: 0, bad: 0, started: now() }; + emit('suiteStart', startModule.suiteReport.start(true)); + return runLoggingCallbacks('moduleStart', { + name: startModule.name, + tests: startModule.tests + }); + }); + }, Promise.resolve([])); + + return callbackPromises.then(() => { + config.current = this; + + this.testEnvironment = extend({}, module.testEnvironment); + + this.started = now(); + emit('testStart', this.testReport.start(true)); + return runLoggingCallbacks('testStart', { + name: this.testName, + module: module.name, + testId: this.testId, + previousFailure: this.previousFailure + }).then(() => { + if (!config.pollution) { + saveGlobal(); + } + }); + }); + }, + + run: function () { + config.current = this; + + this.callbackStarted = now(); + + if (config.notrycatch) { + runTest(this); + return; + } + + try { + runTest(this); + } catch (e) { + this.pushFailure('Died on test #' + (this.assertions.length + 1) + ' ' + + this.stack + ': ' + (e.message || e), extractStacktrace(e, 0)); + + // Else next test will carry the responsibility + saveGlobal(); + + // Restart the tests if they're blocking + if (config.blocking) { + internalRecover(this); + } + } + + function runTest (test) { + let promise; + if (test.withData) { + promise = test.callback.call(test.testEnvironment, test.assert, test.data); + } else { + promise = test.callback.call(test.testEnvironment, test.assert); + } + test.resolvePromise(promise); + + // If the test has an async "pause" on it, but the timeout is 0, then we push a + // failure as the test should be synchronous. + if (test.timeout === 0 && test.pauses.size > 0) { + pushFailure( + 'Test did not finish synchronously even though assert.timeout( 0 ) was used.', + sourceFromStacktrace(2) + ); + } + } + }, + + after: function () { + checkPollution(); + }, + + queueGlobalHook (hook, hookName) { + const runHook = () => { + config.current = this; + + let promise; + + if (config.notrycatch) { + promise = hook.call(this.testEnvironment, this.assert); + } else { + try { + promise = hook.call(this.testEnvironment, this.assert); + } catch (error) { + this.pushFailure( + 'Global ' + hookName + ' failed on ' + this.testName + + ': ' + errorString(error), + extractStacktrace(error, 0) + ); + return; + } + } + + this.resolvePromise(promise, hookName); + }; + + return runHook; + }, + + queueHook (hook, hookName, hookOwner) { + const callHook = () => { + const promise = hook.call(this.testEnvironment, this.assert); + this.resolvePromise(promise, hookName); + }; + + const runHook = () => { + if (hookName === 'before') { + if (hookOwner.testsRun !== 0) { + return; + } + + this.preserveEnvironment = true; + } + + // The 'after' hook should only execute when there are not tests left and + // when the 'after' and 'finish' tasks are the only tasks left to process + if (hookName === 'after' && + !lastTestWithinModuleExecuted(hookOwner) && + (config.queue.length > 0 || ProcessingQueue.taskCount() > 2)) { + return; + } + + config.current = this; + if (config.notrycatch) { + callHook(); + return; + } + try { + // This try-block includes the indirect call to resolvePromise, which shouldn't + // have to be inside try-catch. But, since we support any user-provided thenable + // object, the thenable might throw in some unexpected way. + // This subtle behaviour is undocumented. To avoid new failures in minor releases + // we will not change this until QUnit 3. + // TODO: In QUnit 3, reduce this try-block to just hook.call(), matching + // the simplicity of queueGlobalHook. + callHook(); + } catch (error) { + this.pushFailure(hookName + ' failed on ' + this.testName + ': ' + + (error.message || error), extractStacktrace(error, 0)); + } + }; + + return runHook; + }, + + // Currently only used for module level hooks, can be used to add global level ones + hooks (handler) { + const hooks = []; + + function processGlobalhooks (test) { + if ( + (handler === 'beforeEach' || handler === 'afterEach') && + config.globalHooks[handler] + ) { + for (let i = 0; i < config.globalHooks[handler].length; i++) { + hooks.push( + test.queueGlobalHook(config.globalHooks[handler][i], handler) + ); + } + } + } + + function processHooks (test, module) { + if (module.parentModule) { + processHooks(test, module.parentModule); + } + + if (module.hooks[handler].length) { + for (let i = 0; i < module.hooks[handler].length; i++) { + hooks.push(test.queueHook(module.hooks[handler][i], handler, module)); + } + } + } + + // Hooks are ignored on skipped tests + if (!this.skip) { + processGlobalhooks(this); + processHooks(this, this.module); + } + + return hooks; + }, + + finish: function () { + config.current = this; + + // Release the test callback to ensure that anything referenced has been + // released to be garbage collected. + this.callback = undefined; + + if (this.steps.length) { + const stepsList = this.steps.join(', '); + this.pushFailure('Expected assert.verifySteps() to be called before end of test ' + + `after using assert.step(). Unverified steps: ${stepsList}`, this.stack); + } + + if (config.requireExpects && this.expected === null) { + this.pushFailure('Expected number of assertions to be defined, but expect() was ' + + 'not called.', this.stack); + } else if (this.expected !== null && this.expected !== this.assertions.length) { + this.pushFailure('Expected ' + this.expected + ' assertions, but ' + + this.assertions.length + ' were run', this.stack); + } else if (this.expected === null && !this.assertions.length) { + this.pushFailure('Expected at least one assertion, but none were run - call ' + + 'expect(0) to accept zero assertions.', this.stack); + } + + const module = this.module; + const moduleName = module.name; + const testName = this.testName; + const skipped = !!this.skip; + const todo = !!this.todo; + let bad = 0; + const storage = config.storage; + + this.runtime = now() - this.started; + + config.stats.all += this.assertions.length; + config.stats.testCount += 1; + module.stats.all += this.assertions.length; + + for (let i = 0; i < this.assertions.length; i++) { + // A failing assertion will counts toward the HTML Reporter's + // "X assertions, Y failed" line even if it was inside a todo. + // Inverting this would be similarly confusing since all but the last + // passing assertion inside a todo test should be considered as good. + // These stats don't decide the outcome of anything, so counting them + // as failing seems the most intuitive. + if (!this.assertions[i].result) { + bad++; + config.stats.bad++; + module.stats.bad++; + } + } + + if (skipped) { + incrementTestsIgnored(module); + } else { + incrementTestsRun(module); + } + + // Store result when possible. + // Note that this also marks todo tests as bad, thus they get hoisted, + // and always run first on refresh. + if (storage) { + if (bad) { + storage.setItem('qunit-test-' + moduleName + '-' + testName, bad); + } else { + storage.removeItem('qunit-test-' + moduleName + '-' + testName); + } + } + + // After emitting the js-reporters event we cleanup the assertion data to + // avoid leaking it. It is not used by the legacy testDone callbacks. + emit('testEnd', this.testReport.end(true)); + this.testReport.slimAssertions(); + const test = this; + + return runLoggingCallbacks('testDone', { + name: testName, + module: moduleName, + skipped: skipped, + todo: todo, + failed: bad, + passed: this.assertions.length - bad, + total: this.assertions.length, + runtime: skipped ? 0 : this.runtime, + + // HTML Reporter use + assertions: this.assertions, + testId: this.testId, + + // Source of Test + // generating stack trace is expensive, so using a getter will help defer this until we need it + get source () { return test.stack; } + }).then(function () { + if (allTestsExecuted(module)) { + const completedModules = [module]; + + // Check if the parent modules, iteratively, are done. If that the case, + // we emit the `suiteEnd` event and trigger `moduleDone` callback. + let parent = module.parentModule; + while (parent && allTestsExecuted(parent)) { + completedModules.push(parent); + parent = parent.parentModule; + } + + return completedModules.reduce((promiseChain, completedModule) => { + return promiseChain.then(() => { + return logSuiteEnd(completedModule); + }); + }, Promise.resolve([])); + } + }).then(function () { + config.current = undefined; + }); + + function logSuiteEnd (module) { + // Reset `module.hooks` to ensure that anything referenced in these hooks + // has been released to be garbage collected. Descendant modules that were + // entirely skipped, e.g. due to filtering, will never have this method + // called for them, but might have hooks with references pinning data in + // memory (even if the hooks weren't actually executed), so we reset the + // hooks on all descendant modules here as well. This is safe because we + // will never call this as long as any descendant modules still have tests + // to run. This also means that in multi-tiered nesting scenarios we might + // reset the hooks multiple times on some modules, but that's harmless. + const modules = [module]; + while (modules.length) { + const nextModule = modules.shift(); + nextModule.hooks = {}; + modules.push(...nextModule.childModules); + } + + emit('suiteEnd', module.suiteReport.end(true)); + return runLoggingCallbacks('moduleDone', { + name: module.name, + tests: module.tests, + failed: module.stats.bad, + passed: module.stats.all - module.stats.bad, + total: module.stats.all, + runtime: now() - module.stats.started + }); + } + }, + + preserveTestEnvironment: function () { + if (this.preserveEnvironment) { + this.module.testEnvironment = this.testEnvironment; + this.testEnvironment = extend({}, this.module.testEnvironment); + } + }, + + queue () { + const test = this; + + if (!this.valid()) { + incrementTestsIgnored(this.module); + return; + } + + function runTest () { + return [ + function () { + return test.before(); + }, + + ...test.hooks('before'), + + function () { + test.preserveTestEnvironment(); + }, + + ...test.hooks('beforeEach'), + + function () { + test.run(); + }, + + ...test.hooks('afterEach').reverse(), + ...test.hooks('after').reverse(), + + function () { + test.after(); + }, + + function () { + return test.finish(); + } + ]; + } + + const previousFailCount = config.storage && + +config.storage.getItem('qunit-test-' + this.module.name + '-' + this.testName); + + // Prioritize previously failed tests, detected from storage + const prioritize = config.reorder && !!previousFailCount; + + this.previousFailure = !!previousFailCount; + + ProcessingQueue.add(runTest, prioritize, config.seed); + }, + + pushResult: function (resultInfo) { + if (this !== config.current) { + const message = (resultInfo && resultInfo.message) || ''; + const testName = (this && this.testName) || ''; + const error = 'Assertion occurred after test finished.\n' + + '> Test: ' + testName + '\n' + + '> Message: ' + message + '\n'; + + throw new Error(error); + } + + // Destructure of resultInfo = { result, actual, expected, message, negative } + const details = { + module: this.module.name, + name: this.testName, + result: resultInfo.result, + message: resultInfo.message, + actual: resultInfo.actual, + testId: this.testId, + negative: resultInfo.negative || false, + runtime: now() - this.started, + todo: !!this.todo + }; + + if (hasOwn.call(resultInfo, 'expected')) { + details.expected = resultInfo.expected; + } + + if (!resultInfo.result) { + const source = resultInfo.source || sourceFromStacktrace(); + + if (source) { + details.source = source; + } + } + + this.logAssertion(details); + + this.assertions.push({ + result: !!resultInfo.result, + message: resultInfo.message + }); + }, + + pushFailure: function (message, source, actual) { + if (!(this instanceof Test)) { + throw new Error('pushFailure() assertion outside test context, was ' + + sourceFromStacktrace(2)); + } + + this.pushResult({ + result: false, + message: message || 'error', + actual: actual || null, + source + }); + }, + + /** + * Log assertion details using both the old QUnit.log interface and + * QUnit.on( "assertion" ) interface. + * + * @private + */ + logAssertion (details) { + runLoggingCallbacks('log', details); + + const assertion = { + passed: details.result, + actual: details.actual, + expected: details.expected, + message: details.message, + stack: details.source, + todo: details.todo + }; + this.testReport.pushAssertion(assertion); + emit('assertion', assertion); + }, + + /** + * Reset config.timeout with a new timeout duration. + * + * @param {number} timeoutDuration + */ + internalResetTimeout (timeoutDuration) { + clearTimeout(config.timeout); + config.timeout = setTimeout(config.timeoutHandler(timeoutDuration), timeoutDuration); + }, + + /** + * Create a new async pause and return a new function that can release the pause. + * + * This mechanism is internally used by: + * + * - explicit async pauses, created by calling `assert.async()`, + * - implicit async pauses, created when `QUnit.test()` or module hook callbacks + * use async-await or otherwise return a Promise. + * + * Happy scenario: + * + * - Pause is created by calling internalStop(). + * + * Pause is released normally by invoking release() during the same test. + * + * The release() callback lets internal processing resume. + * + * Failure scenarios: + * + * - The test fails due to an uncaught exception. + * + * In this case, Test.run() will call internalRecover() which empties the clears all + * async pauses and sets the cancelled flag, which means we silently ignore any + * late calls to the resume() callback, as we will have moved on to a different + * test by then, and we don't want to cause an extra "release during a different test" + * errors that the developer isn't really responsible for. This can happen when a test + * correctly schedules a call to release(), but also causes an uncaught error. The + * uncaught error means we will no longer wait for the release (as it might not arrive). + * + * - Pause is never released, or called an insufficient number of times. + * + * Our timeout handler will kill the pause and resume test processing, basically + * like internalRecover(), but for one pause instead of any/all. + * + * Here, too, any late calls to resume() will be silently ignored to avoid + * extra errors. We tolerate this since the original test will have already been + * marked as failure. + * + * TODO: QUnit 3 will enable timeouts by default , + * but right now a test will hang indefinitely if async pauses are not released, + * unless QUnit.config.testTimeout or assert.timeout() is used. + * + * - Pause is spontaneously released during a different test, + * or when no test is currently running. + * + * This is close to impossible because this error only happens if the original test + * succesfully finished first (since other failure scenarios kill pauses and ignore + * late calls). It can happen if a test ended exactly as expected, but has some + * external or shared state continuing to hold a reference to the release callback, + * and either the same test scheduled another call to it in the future, or a later test + * causes it to be called through some shared state. + * + * - Pause release() is called too often, during the same test. + * + * This simply throws an error, after which uncaught error handling picks it up + * and processing resumes. + * + * @param {number} [requiredCalls=1] + */ + internalStop (requiredCalls = 1) { + config.blocking = true; + + const test = this; + const pauseId = this.nextPauseId++; + const pause = { + cancelled: false, + remaining: requiredCalls + }; + test.pauses.set(pauseId, pause); + + function release () { + if (pause.cancelled) { + return; + } + if (config.current === undefined) { + throw new Error('Unexpected release of async pause after tests finished.\n' + + `> Test: ${test.testName} [async #${pauseId}]`); + } + if (config.current !== test) { + throw new Error('Unexpected release of async pause during a different test.\n' + + `> Test: ${test.testName} [async #${pauseId}]`); + } + if (pause.remaining <= 0) { + throw new Error('Tried to release async pause that was already released.\n' + + `> Test: ${test.testName} [async #${pauseId}]`); + } + + // The `requiredCalls` parameter exists to support `assert.async(count)` + pause.remaining--; + if (pause.remaining === 0) { + test.pauses.delete(pauseId); + } + + internalStart(test); + } + + // Set a recovery timeout, if so configured. + if (setTimeout) { + let timeoutDuration; + if (typeof test.timeout === 'number') { + timeoutDuration = test.timeout; + } else if (typeof config.testTimeout === 'number') { + timeoutDuration = config.testTimeout; + } + + if (typeof timeoutDuration === 'number' && timeoutDuration > 0) { + config.timeoutHandler = function (timeout) { + return function () { + config.timeout = null; + pause.cancelled = true; + test.pauses.delete(pauseId); + + test.pushFailure( + `Test took longer than ${timeout}ms; test timed out.`, + sourceFromStacktrace(2) + ); + internalStart(test); + }; + }; + clearTimeout(config.timeout); + config.timeout = setTimeout( + config.timeoutHandler(timeoutDuration), + timeoutDuration + ); + } + } + + return release; + }, + + resolvePromise: function (promise, phase) { + if (promise != null) { + const test = this; + const then = promise.then; + if (objectType(then) === 'function') { + const resume = test.internalStop(); + const resolve = function () { resume(); }; + if (config.notrycatch) { + then.call(promise, resolve); + } else { + const reject = function (error) { + const message = 'Promise rejected ' + + (!phase ? 'during' : phase.replace(/Each$/, '')) + + ' "' + test.testName + '": ' + + ((error && error.message) || error); + test.pushFailure(message, extractStacktrace(error, 0)); + + // Else next test will carry the responsibility + saveGlobal(); + + // Unblock + internalRecover(test); + }; + then.call(promise, resolve, reject); + } + } + } + }, + + valid: function () { + const filter = config.filter; + const regexFilter = /^(!?)\/([\w\W]*)\/(i?$)/.exec(filter); + const module = config.module && config.module.toLowerCase(); + const fullName = (this.module.name + ': ' + this.testName); + + function moduleChainNameMatch (testModule) { + const testModuleName = testModule.name ? testModule.name.toLowerCase() : null; + if (testModuleName === module) { + return true; + } else if (testModule.parentModule) { + return moduleChainNameMatch(testModule.parentModule); + } else { + return false; + } + } + + function moduleChainIdMatch (testModule) { + return inArray(testModule.moduleId, config.moduleId) || + (testModule.parentModule && moduleChainIdMatch(testModule.parentModule)); + } + + // Internally-generated tests are always valid + if (this.callback && this.callback.validTest) { + return true; + } + + if (config.moduleId && config.moduleId.length > 0 && + !moduleChainIdMatch(this.module)) { + return false; + } + + if (config.testId && config.testId.length > 0 && + !inArray(this.testId, config.testId)) { + return false; + } + + if (module && !moduleChainNameMatch(this.module)) { + return false; + } + + if (!filter) { + return true; + } + + return regexFilter + ? this.regexFilter(!!regexFilter[1], regexFilter[2], regexFilter[3], fullName) + : this.stringFilter(filter, fullName); + }, + + regexFilter: function (exclude, pattern, flags, fullName) { + const regex = new RegExp(pattern, flags); + const match = regex.test(fullName); + + return match !== exclude; + }, + + stringFilter: function (filter, fullName) { + filter = filter.toLowerCase(); + fullName = fullName.toLowerCase(); + + const include = filter.charAt(0) !== '!'; + if (!include) { + filter = filter.slice(1); + } + + // If the filter matches, we need to honour include + if (fullName.indexOf(filter) !== -1) { + return include; + } + + // Otherwise, do the opposite + return !include; + } }; -export function pushFailure() { - if ( !config.current ) { - throw new Error( "pushFailure() assertion outside test context, in " + - sourceFromStacktrace( 2 ) ); - } +export function pushFailure () { + if (!config.current) { + throw new Error('pushFailure() assertion outside test context, in ' + + sourceFromStacktrace(2)); + } - // Gets current test obj - const currentTest = config.current; + // Gets current test obj + const currentTest = config.current; - return currentTest.pushFailure.apply( currentTest, arguments ); + return currentTest.pushFailure.apply(currentTest, arguments); } -function saveGlobal() { - config.pollution = []; - - if ( config.noglobals ) { - for ( const key in globalThis ) { - if ( hasOwn.call( globalThis, key ) ) { - - // In Opera sometimes DOM element ids show up here, ignore them - if ( /^qunit-test-output/.test( key ) ) { - continue; - } - config.pollution.push( key ); - } - } - } +function saveGlobal () { + config.pollution = []; + + if (config.noglobals) { + for (const key in globalThis) { + if (hasOwn.call(globalThis, key)) { + // In Opera sometimes DOM element ids show up here, ignore them + if (/^qunit-test-output/.test(key)) { + continue; + } + config.pollution.push(key); + } + } + } } -function checkPollution() { - const old = config.pollution; +function checkPollution () { + const old = config.pollution; - saveGlobal(); + saveGlobal(); - const newGlobals = diff( config.pollution, old ); - if ( newGlobals.length > 0 ) { - pushFailure( "Introduced global variable(s): " + newGlobals.join( ", " ) ); - } + const newGlobals = diff(config.pollution, old); + if (newGlobals.length > 0) { + pushFailure('Introduced global variable(s): ' + newGlobals.join(', ')); + } - const deletedGlobals = diff( old, config.pollution ); - if ( deletedGlobals.length > 0 ) { - pushFailure( "Deleted global variable(s): " + deletedGlobals.join( ", " ) ); - } + const deletedGlobals = diff(old, config.pollution); + if (deletedGlobals.length > 0) { + pushFailure('Deleted global variable(s): ' + deletedGlobals.join(', ')); + } } let focused = false; // indicates that the "only" filter was used -function addTest( settings ) { - if ( focused || config.currentModule.ignored ) { - return; - } +function addTest (settings) { + if (focused || config.currentModule.ignored) { + return; + } - const newTest = new Test( settings ); + const newTest = new Test(settings); - newTest.queue(); + newTest.queue(); } -function addOnlyTest( settings ) { - if ( config.currentModule.ignored ) { - return; - } - if ( !focused ) { - config.queue.length = 0; - focused = true; - } +function addOnlyTest (settings) { + if (config.currentModule.ignored) { + return; + } + if (!focused) { + config.queue.length = 0; + focused = true; + } - const newTest = new Test( settings ); + const newTest = new Test(settings); - newTest.queue(); + newTest.queue(); } // Will be exposed as QUnit.test -export function test( testName, callback ) { - addTest( { testName, callback } ); +export function test (testName, callback) { + addTest({ testName, callback }); } -function makeEachTestName( testName, argument ) { - return `${testName} [${argument}]`; +function makeEachTestName (testName, argument) { + return `${testName} [${argument}]`; } -function runEach( data, eachFn ) { - if ( Array.isArray( data ) ) { - data.forEach( eachFn ); - } else if ( typeof data === "object" && data !== null ) { - const keys = Object.keys( data ); - keys.forEach( ( key ) => { - eachFn( data[ key ], key ); - } ); - } else { - throw new Error( - `test.each() expects an array or object as input, but +function runEach (data, eachFn) { + if (Array.isArray(data)) { + data.forEach(eachFn); + } else if (typeof data === 'object' && data !== null) { + const keys = Object.keys(data); + keys.forEach((key) => { + eachFn(data[key], key); + }); + } else { + throw new Error( + `test.each() expects an array or object as input, but found ${typeof data} instead.` - ); - } + ); + } } -extend( test, { - todo: function( testName, callback ) { - addTest( { testName, callback, todo: true } ); - }, - skip: function( testName ) { - addTest( { testName, skip: true } ); - }, - only: function( testName, callback ) { - addOnlyTest( { testName, callback } ); - }, - each: function( testName, dataset, callback ) { - runEach( dataset, ( data, testKey ) => { - addTest( { - testName: makeEachTestName( testName, testKey ), - callback, - withData: true, - data - } ); - } ); - } -} ); - -test.todo.each = function( testName, dataset, callback ) { - runEach( dataset, ( data, testKey ) => { - addTest( { - testName: makeEachTestName( testName, testKey ), - callback, - todo: true, - withData: true, - data - } ); - } ); +extend(test, { + todo: function (testName, callback) { + addTest({ testName, callback, todo: true }); + }, + skip: function (testName) { + addTest({ testName, skip: true }); + }, + only: function (testName, callback) { + addOnlyTest({ testName, callback }); + }, + each: function (testName, dataset, callback) { + runEach(dataset, (data, testKey) => { + addTest({ + testName: makeEachTestName(testName, testKey), + callback, + withData: true, + data + }); + }); + } +}); + +test.todo.each = function (testName, dataset, callback) { + runEach(dataset, (data, testKey) => { + addTest({ + testName: makeEachTestName(testName, testKey), + callback, + todo: true, + withData: true, + data + }); + }); }; -test.skip.each = function( testName, dataset ) { - runEach( dataset, ( _, testKey ) => { - addTest( { - testName: makeEachTestName( testName, testKey ), - skip: true - } ); - } ); +test.skip.each = function (testName, dataset) { + runEach(dataset, (_, testKey) => { + addTest({ + testName: makeEachTestName(testName, testKey), + skip: true + }); + }); }; -test.only.each = function( testName, dataset, callback ) { - runEach( dataset, ( data, testKey ) => { - addOnlyTest( { - testName: makeEachTestName( testName, testKey ), - callback, - withData: true, - data - } ); - } ); +test.only.each = function (testName, dataset, callback) { + runEach(dataset, (data, testKey) => { + addOnlyTest({ + testName: makeEachTestName(testName, testKey), + callback, + withData: true, + data + }); + }); }; // Forcefully release all processing holds. -function internalRecover( test ) { - test.pauses.forEach( pause => { - pause.cancelled = true; - } ); - test.pauses.clear(); - internalStart( test ); +function internalRecover (test) { + test.pauses.forEach(pause => { + pause.cancelled = true; + }); + test.pauses.clear(); + internalStart(test); } // Release a processing hold, scheduling a resumption attempt if no holds remain. -function internalStart( test ) { - - // Ignore if other async pauses still exist. - if ( test.pauses.size > 0 ) { - return; - } - - // Add a slight delay to allow more assertions etc. - if ( setTimeout ) { - clearTimeout( config.timeout ); - config.timeout = setTimeout( function() { - if ( test.pauses.size > 0 ) { - return; - } - - clearTimeout( config.timeout ); - config.timeout = null; - - config.blocking = false; - ProcessingQueue.advance(); - } ); - } else { - config.blocking = false; - ProcessingQueue.advance(); - } +function internalStart (test) { + // Ignore if other async pauses still exist. + if (test.pauses.size > 0) { + return; + } + + // Add a slight delay to allow more assertions etc. + if (setTimeout) { + clearTimeout(config.timeout); + config.timeout = setTimeout(function () { + if (test.pauses.size > 0) { + return; + } + + clearTimeout(config.timeout); + config.timeout = null; + + config.blocking = false; + ProcessingQueue.advance(); + }); + } else { + config.blocking = false; + ProcessingQueue.advance(); + } } -function collectTests( module ) { - const tests = [].concat( module.tests ); - const modules = [ ...module.childModules ]; +function collectTests (module) { + const tests = [].concat(module.tests); + const modules = [...module.childModules]; - // Do a breadth-first traversal of the child modules - while ( modules.length ) { - const nextModule = modules.shift(); - tests.push.apply( tests, nextModule.tests ); - modules.push( ...nextModule.childModules ); - } + // Do a breadth-first traversal of the child modules + while (modules.length) { + const nextModule = modules.shift(); + tests.push.apply(tests, nextModule.tests); + modules.push(...nextModule.childModules); + } - return tests; + return tests; } // This returns true after all executable and skippable tests // in a module have been proccessed, and informs 'suiteEnd' // and moduleDone(). -function allTestsExecuted( module ) { - return module.testsRun + module.testsIgnored === collectTests( module ).length; +function allTestsExecuted (module) { + return module.testsRun + module.testsIgnored === collectTests(module).length; } // This returns true during the last executable non-skipped test @@ -1076,20 +1064,20 @@ function allTestsExecuted( module ) { // for a given module. This runs only once for a given module, // but must run during the last non-skipped test. When it runs, // there may be non-zero skipped tests left. -function lastTestWithinModuleExecuted( module ) { - return module.testsRun === collectTests( module ).filter( test => !test.skip ).length - 1; +function lastTestWithinModuleExecuted (module) { + return module.testsRun === collectTests(module).filter(test => !test.skip).length - 1; } -function incrementTestsRun( module ) { - module.testsRun++; - while ( ( module = module.parentModule ) ) { - module.testsRun++; - } +function incrementTestsRun (module) { + module.testsRun++; + while ((module = module.parentModule)) { + module.testsRun++; + } } -function incrementTestsIgnored( module ) { - module.testsIgnored++; - while ( ( module = module.parentModule ) ) { - module.testsIgnored++; - } +function incrementTestsIgnored (module) { + module.testsIgnored++; + while ((module = module.parentModule)) { + module.testsIgnored++; + } } diff --git a/test/.eslintrc.json b/test/.eslintrc.json deleted file mode 100644 index ce90ffaa4..000000000 --- a/test/.eslintrc.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "extends": [ "plugin:qunit/recommended" ], - "env": { - "browser": true, - "es6": false - }, - "plugins": [ - "html", - "qunit" - ], - "parserOptions": { - "sourceType": "script" - }, - "globals": { - "QUnit": false, - "Promise": false, - "console": false - }, - "rules": { - "qunit/literal-compare-order": "off", - "qunit/no-arrow-tests": "off", - "qunit/no-assert-equal-boolean": "off", - "qunit/no-assert-logical-expression": "off", - "qunit/no-only": "off", - "qunit/require-expect": "off", - "qunit/resolve-async": "off", - "max-len": "off" - }, - "ignorePatterns": [ - "cli/fixtures/sourcemap/*.min.js" - ] -} diff --git a/test/amd.html b/test/amd.html index 7154fff15..3cd3dce8a 100644 --- a/test/amd.html +++ b/test/amd.html @@ -1,29 +1,29 @@ - - QUnit AMD Test Suite - - + + QUnit AMD Test Suite + + -
        - + require(['qunit', './amd'], function (QUnit, tests) { + QUnit.begin(function (data) { + window.beginData = data; + }); + tests(); + QUnit.start(); + }); + diff --git a/test/amd.js b/test/amd.js index 9bf9bdcb1..06e3c4267 100644 --- a/test/amd.js +++ b/test/amd.js @@ -1,23 +1,21 @@ -/* eslint-env amd */ -define( [ "qunit" ], function( QUnit ) { +/* eslint-env browser, amd */ +define(['qunit'], function (QUnit) { + return function () { + QUnit.module('AMD autostart', { + after: function (assert) { + assert.true(true, 'after hook ran'); + } + }); - return function() { - QUnit.module( "AMD autostart", { - after: function( assert ) { - assert.true( true, "after hook ran" ); - } - } ); + QUnit.test('Prove the test run started as expected', function (assert) { + assert.expect(2); + assert.strictEqual(window.beginData.totalTests, 1, 'Should have expected 1 test'); + }); - QUnit.test( "Prove the test run started as expected", function( assert ) { - assert.expect( 2 ); - assert.strictEqual( window.beginData.totalTests, 1, "Should have expected 1 test" ); - } ); - - setTimeout( function() { - QUnit.test( "Async-loaded tests should not run after hook again", function( assert ) { - assert.expect( 0 ); - } ); - }, 5000 ); - }; - -} ); + setTimeout(function () { + QUnit.test('Async-loaded tests should not run after hook again', function (assert) { + assert.expect(0); + }); + }, 5000); + }; +}); diff --git a/test/autostart.html b/test/autostart.html index ff19ddfb9..4581a20c1 100644 --- a/test/autostart.html +++ b/test/autostart.html @@ -1,40 +1,40 @@ - - QUnit Autostart Test Suite - + + autostart + -
        - - + + QUnit.begin(function (data) { + window.beginData = data; + window.times.runStarted = now(); + }); + }()); + diff --git a/test/autostart.js b/test/autostart.js index f8db0c49d..b63eb620d 100644 --- a/test/autostart.js +++ b/test/autostart.js @@ -1,8 +1,14 @@ +/* eslint-env browser */ QUnit.start(); -QUnit.module( "autostart" ); +QUnit.module('autostart'); -QUnit.test( "Prove the test run started as expected", function( assert ) { - assert.true( window.times.autostartOff <= window.times.runStarted ); - assert.strictEqual( window.beginData.totalTests, 1, "Should have expected 1 test" ); -} ); +QUnit.test('Prove the test run started as expected', function (assert) { + var delay = window.times.runStarted - window.times.autostartOff; + assert.pushResult({ + result: delay >= 1000, + actual: delay, + message: 'delay' + }); + assert.strictEqual(window.beginData.totalTests, 1, 'Should have expected 1 test'); +}); diff --git a/test/cli/.eslintrc.json b/test/cli/.eslintrc.json deleted file mode 100644 index da21cfbd2..000000000 --- a/test/cli/.eslintrc.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "parserOptions": { - "ecmaVersion": 2018 - }, - "env": { - "node": true, - "es6": true - } -} diff --git a/test/cli/ConsoleReporter.js b/test/cli/ConsoleReporter.js index a7df3b143..1f5ff97e9 100644 --- a/test/cli/ConsoleReporter.js +++ b/test/cli/ConsoleReporter.js @@ -1,43 +1,42 @@ -const { EventEmitter } = require( "events" ); - -QUnit.module( "ConsoleReporter", hooks => { - let emitter; - let callCount; - - hooks.beforeEach( function() { - emitter = new EventEmitter(); - callCount = 0; - const con = { - log: () => { - callCount++; - } - }; - QUnit.reporters.console.init( emitter, con ); - } ); - - QUnit.test( "Event \"runStart\"", assert => { - emitter.emit( "runStart", {} ); - assert.equal( callCount, 1 ); - } ); - - QUnit.test( "Event \"runEnd\"", assert => { - emitter.emit( "runEnd", {} ); - assert.equal( callCount, 1 ); - } ); - - QUnit.test( "Event \"testStart\"", assert => { - emitter.emit( "testStart", {} ); - assert.equal( callCount, 1 ); - } ); - - QUnit.test( "Event \"testEnd\"", assert => { - emitter.emit( "testEnd", {} ); - assert.equal( callCount, 1 ); - } ); - - QUnit.test( "Event \"error\"", assert => { - emitter.emit( "error", {} ); - assert.equal( callCount, 1 ); - } ); - -} ); +const { EventEmitter } = require('events'); + +QUnit.module('ConsoleReporter', hooks => { + let emitter; + let callCount; + + hooks.beforeEach(function () { + emitter = new EventEmitter(); + callCount = 0; + const con = { + log: () => { + callCount++; + } + }; + QUnit.reporters.console.init(emitter, con); + }); + + QUnit.test('Event "runStart"', assert => { + emitter.emit('runStart', {}); + assert.equal(callCount, 1); + }); + + QUnit.test('Event "runEnd"', assert => { + emitter.emit('runEnd', {}); + assert.equal(callCount, 1); + }); + + QUnit.test('Event "testStart"', assert => { + emitter.emit('testStart', {}); + assert.equal(callCount, 1); + }); + + QUnit.test('Event "testEnd"', assert => { + emitter.emit('testEnd', {}); + assert.equal(callCount, 1); + }); + + QUnit.test('Event "error"', assert => { + emitter.emit('error', {}); + assert.equal(callCount, 1); + }); +}); diff --git a/test/cli/TapReporter.js b/test/cli/TapReporter.js index 037269926..2190f679e 100644 --- a/test/cli/TapReporter.js +++ b/test/cli/TapReporter.js @@ -1,130 +1,130 @@ -const kleur = require( "kleur" ); -const { EventEmitter } = require( "events" ); +const kleur = require('kleur'); +const { EventEmitter } = require('events'); -function mockStack( error ) { - error.stack = ` at Object. (/dev/null/test/unit/data.js:6:5) +function mockStack (error) { + error.stack = ` at Object. (/dev/null/test/unit/data.js:6:5) at require (internal/helpers.js:22:18) at /dev/null/src/example/foo.js:220:27`; - return error; + return error; } -function makeFailingTestEnd( actualValue ) { - return { - name: "Failing", - suiteName: null, - fullName: [ "Failing" ], - status: "failed", - runtime: 0, - errors: [ { - passed: false, - actual: actualValue, - expected: "expected" - } ], - assertions: null - }; +function makeFailingTestEnd (actualValue) { + return { + name: 'Failing', + suiteName: null, + fullName: ['Failing'], + status: 'failed', + runtime: 0, + errors: [{ + passed: false, + actual: actualValue, + expected: 'expected' + }], + assertions: null + }; } -QUnit.module( "TapReporter", hooks => { - let emitter; - let last; - let buffer; - - function log( str ) { - buffer += str + "\n"; - last = str; - } - - hooks.beforeEach( function() { - emitter = new EventEmitter(); - last = undefined; - buffer = ""; - QUnit.reporters.tap.init( emitter, { - log: log - } ); - } ); - - QUnit.test( "output the TAP header", assert => { - emitter.emit( "runStart", {} ); - - assert.strictEqual( last, "TAP version 13" ); - } ); - - QUnit.test( "output ok for a passing test", assert => { - const expected = "ok 1 name"; - - emitter.emit( "testEnd", { - name: "name", - suiteName: null, - fullName: [ "name" ], - status: "passed", - runtime: 0, - errors: [], - assertions: [] - } ); - - assert.strictEqual( last, expected ); - } ); - - QUnit.test( "output ok for a skipped test", assert => { - const expected = kleur.yellow( "ok 1 # SKIP name" ); - - emitter.emit( "testEnd", { - name: "name", - suiteName: null, - fullName: [ "name" ], - status: "skipped", - runtime: 0, - errors: [], - assertions: [] - } ); - assert.strictEqual( last, expected ); - } ); - - QUnit.test( "output not ok for a todo test", assert => { - const expected = kleur.cyan( "not ok 1 # TODO name" ); - - emitter.emit( "testEnd", { - name: "name", - suiteName: null, - fullName: [ "name" ], - status: "todo", - runtime: 0, - errors: [], - assertions: [] - } ); - assert.strictEqual( last, expected ); - } ); - - QUnit.test( "output not ok for a failing test", assert => { - const expected = kleur.red( "not ok 1 name" ); - - emitter.emit( "testEnd", { - name: "name", - suiteName: null, - fullName: [ "name" ], - status: "failed", - runtime: 0, - errors: [], - assertions: [] - } ); - assert.strictEqual( last, expected ); - } ); - - QUnit.test( "output all errors for a failing test", assert => { - emitter.emit( "testEnd", { - name: "name", - suiteName: null, - fullName: [ "name" ], - status: "failed", - runtime: 0, - errors: [ - mockStack( new Error( "first error" ) ), - mockStack( new Error( "second error" ) ) - ], - assertions: [] - } ); - - assert.strictEqual( buffer, `${kleur.red( "not ok 1 name" )} +QUnit.module('TapReporter', hooks => { + let emitter; + let last; + let buffer; + + function log (str) { + buffer += str + '\n'; + last = str; + } + + hooks.beforeEach(function () { + emitter = new EventEmitter(); + last = undefined; + buffer = ''; + QUnit.reporters.tap.init(emitter, { + log: log + }); + }); + + QUnit.test('output the TAP header', assert => { + emitter.emit('runStart', {}); + + assert.strictEqual(last, 'TAP version 13'); + }); + + QUnit.test('output ok for a passing test', assert => { + const expected = 'ok 1 name'; + + emitter.emit('testEnd', { + name: 'name', + suiteName: null, + fullName: ['name'], + status: 'passed', + runtime: 0, + errors: [], + assertions: [] + }); + + assert.strictEqual(last, expected); + }); + + QUnit.test('output ok for a skipped test', assert => { + const expected = kleur.yellow('ok 1 # SKIP name'); + + emitter.emit('testEnd', { + name: 'name', + suiteName: null, + fullName: ['name'], + status: 'skipped', + runtime: 0, + errors: [], + assertions: [] + }); + assert.strictEqual(last, expected); + }); + + QUnit.test('output not ok for a todo test', assert => { + const expected = kleur.cyan('not ok 1 # TODO name'); + + emitter.emit('testEnd', { + name: 'name', + suiteName: null, + fullName: ['name'], + status: 'todo', + runtime: 0, + errors: [], + assertions: [] + }); + assert.strictEqual(last, expected); + }); + + QUnit.test('output not ok for a failing test', assert => { + const expected = kleur.red('not ok 1 name'); + + emitter.emit('testEnd', { + name: 'name', + suiteName: null, + fullName: ['name'], + status: 'failed', + runtime: 0, + errors: [], + assertions: [] + }); + assert.strictEqual(last, expected); + }); + + QUnit.test('output all errors for a failing test', assert => { + emitter.emit('testEnd', { + name: 'name', + suiteName: null, + fullName: ['name'], + status: 'failed', + runtime: 0, + errors: [ + mockStack(new Error('first error')), + mockStack(new Error('second error')) + ], + assertions: [] + }); + + assert.strictEqual(buffer, `${kleur.red('not ok 1 name')} --- message: first error severity: failed @@ -142,28 +142,28 @@ QUnit.module( "TapReporter", hooks => { at /dev/null/src/example/foo.js:220:27 ... ` - ); - } ); + ); + }); - QUnit.test( "output global failure (string)", assert => { - emitter.emit( "error", "Boo" ); + QUnit.test('output global failure (string)', assert => { + emitter.emit('error', 'Boo'); - assert.strictEqual( buffer, `${kleur.red( "not ok 1 global failure" )} + assert.strictEqual(buffer, `${kleur.red('not ok 1 global failure')} --- message: Boo severity: failed ... Bail out! Boo ` - ); - } ); + ); + }); - QUnit.test( "output global failure (Error)", assert => { - const err = new ReferenceError( "Boo is not defined" ); - mockStack( err ); - emitter.emit( "error", err ); + QUnit.test('output global failure (Error)', assert => { + const err = new ReferenceError('Boo is not defined'); + mockStack(err); + emitter.emit('error', err); - assert.strictEqual( buffer, `${kleur.red( "not ok 1 global failure" )} + assert.strictEqual(buffer, `${kleur.red('not ok 1 global failure')} --- message: ReferenceError: Boo is not defined severity: failed @@ -174,82 +174,82 @@ Bail out! Boo ... Bail out! ReferenceError: Boo is not defined ` - ); - } ); - - QUnit.test( "output expected value of Infinity", assert => { - emitter.emit( "testEnd", { - name: "Failing", - suiteName: null, - fullName: [ "Failing" ], - status: "failed", - runtime: 0, - errors: [ { - passed: false, - actual: "actual", - expected: Infinity - } ], - assertions: null - } ); - assert.strictEqual( last, ` --- + ); + }); + + QUnit.test('output expected value of Infinity', assert => { + emitter.emit('testEnd', { + name: 'Failing', + suiteName: null, + fullName: ['Failing'], + status: 'failed', + runtime: 0, + errors: [{ + passed: false, + actual: 'actual', + expected: Infinity + }], + assertions: null + }); + assert.strictEqual(last, ` --- message: failed severity: failed actual : actual expected: Infinity ...` - ); - } ); + ); + }); - QUnit.test( "output actual value of undefined", assert => { - emitter.emit( "testEnd", makeFailingTestEnd( undefined ) ); - assert.strictEqual( last, ` --- + QUnit.test('output actual value of undefined', assert => { + emitter.emit('testEnd', makeFailingTestEnd(undefined)); + assert.strictEqual(last, ` --- message: failed severity: failed actual : undefined expected: expected ...` - ); - } ); + ); + }); - QUnit.test( "output actual value of Infinity", assert => { - emitter.emit( "testEnd", makeFailingTestEnd( Infinity ) ); - assert.strictEqual( last, ` --- + QUnit.test('output actual value of Infinity', assert => { + emitter.emit('testEnd', makeFailingTestEnd(Infinity)); + assert.strictEqual(last, ` --- message: failed severity: failed actual : Infinity expected: expected ...` - ); - } ); + ); + }); - QUnit.test( "output actual value of a string", assert => { - emitter.emit( "testEnd", makeFailingTestEnd( "abc" ) ); + QUnit.test('output actual value of a string', assert => { + emitter.emit('testEnd', makeFailingTestEnd('abc')); - // No redundant quotes - assert.strictEqual( last, ` --- + // No redundant quotes + assert.strictEqual(last, ` --- message: failed severity: failed actual : abc expected: expected ...` - ); - } ); + ); + }); - QUnit.test( "output actual value with one trailing line break", assert => { - emitter.emit( "testEnd", makeFailingTestEnd( "abc\n" ) ); - assert.strictEqual( last, ` --- + QUnit.test('output actual value with one trailing line break', assert => { + emitter.emit('testEnd', makeFailingTestEnd('abc\n')); + assert.strictEqual(last, ` --- message: failed severity: failed actual : | abc expected: expected ...` - ); - } ); + ); + }); - QUnit.test( "output actual value with two trailing line breaks", assert => { - emitter.emit( "testEnd", makeFailingTestEnd( "abc\n\n" ) ); - assert.strictEqual( last, ` --- + QUnit.test('output actual value with two trailing line breaks', assert => { + emitter.emit('testEnd', makeFailingTestEnd('abc\n\n')); + assert.strictEqual(last, ` --- message: failed severity: failed actual : |+ @@ -258,67 +258,66 @@ Bail out! ReferenceError: Boo is not defined expected: expected ...` - ); - } ); + ); + }); - QUnit.test( "output actual value of a number string", assert => { - emitter.emit( "testEnd", makeFailingTestEnd( "2" ) ); + QUnit.test('output actual value of a number string', assert => { + emitter.emit('testEnd', makeFailingTestEnd('2')); - // Quotes required to disambiguate YAML value - assert.strictEqual( last, ` --- + // Quotes required to disambiguate YAML value + assert.strictEqual(last, ` --- message: failed severity: failed actual : "2" expected: expected ...` - ); - } ); + ); + }); - QUnit.test( "output actual value of boolean string", assert => { - emitter.emit( "testEnd", makeFailingTestEnd( "true" ) ); + QUnit.test('output actual value of boolean string', assert => { + emitter.emit('testEnd', makeFailingTestEnd('true')); - // Quotes required to disambiguate YAML value - assert.strictEqual( last, ` --- + // Quotes required to disambiguate YAML value + assert.strictEqual(last, ` --- message: failed severity: failed actual : "true" expected: expected ...` - ); - } ); + ); + }); - QUnit.test( "output actual value of 0", assert => { - emitter.emit( "testEnd", makeFailingTestEnd( 0 ) ); - assert.strictEqual( last, ` --- + QUnit.test('output actual value of 0', assert => { + emitter.emit('testEnd', makeFailingTestEnd(0)); + assert.strictEqual(last, ` --- message: failed severity: failed actual : 0 expected: expected ...` - ); - } ); + ); + }); - QUnit.test( "output actual assertion value of empty array", assert => { - emitter.emit( "testEnd", makeFailingTestEnd( [] ) ); - assert.strictEqual( last, ` --- + QUnit.test('output actual assertion value of empty array', assert => { + emitter.emit('testEnd', makeFailingTestEnd([])); + assert.strictEqual(last, ` --- message: failed severity: failed actual : [] expected: expected ...` - ); - } ); - - QUnit.test( "output actual value with a cyclical structure", assert => { - - /// Creates an object that has a cyclical reference. - function createCyclical() { - const cyclical = { a: "example" }; - cyclical.cycle = cyclical; - return cyclical; - } - emitter.emit( "testEnd", makeFailingTestEnd( createCyclical() ) ); - assert.strictEqual( last, ` --- + ); + }); + + QUnit.test('output actual value with a cyclical structure', assert => { + /// Creates an object that has a cyclical reference. + function createCyclical () { + const cyclical = { a: 'example' }; + cyclical.cycle = cyclical; + return cyclical; + } + emitter.emit('testEnd', makeFailingTestEnd(createCyclical())); + assert.strictEqual(last, ` --- message: failed severity: failed actual : { @@ -327,19 +326,18 @@ Bail out! ReferenceError: Boo is not defined } expected: expected ...` - ); - } ); - - QUnit.test( "output actual value with a subobject cyclical structure", assert => { - - // Creates an object that has a cyclical reference in a subobject. - function createSubobjectCyclical() { - const cyclical = { a: "example", sub: {} }; - cyclical.sub.cycle = cyclical; - return cyclical; - } - emitter.emit( "testEnd", makeFailingTestEnd( createSubobjectCyclical() ) ); - assert.strictEqual( last, ` --- + ); + }); + + QUnit.test('output actual value with a subobject cyclical structure', assert => { + // Creates an object that has a cyclical reference in a subobject. + function createSubobjectCyclical () { + const cyclical = { a: 'example', sub: {} }; + cyclical.sub.cycle = cyclical; + return cyclical; + } + emitter.emit('testEnd', makeFailingTestEnd(createSubobjectCyclical())); + assert.strictEqual(last, ` --- message: failed severity: failed actual : { @@ -350,25 +348,24 @@ Bail out! ReferenceError: Boo is not defined } expected: expected ...` - ); - } ); - - QUnit.test( "output actual assertion value of an acyclical structure", assert => { - - // Creates an object that references another object more - // than once in an acyclical way. - function createDuplicateAcyclical() { - const duplicate = { - example: "value" - }; - return { - a: duplicate, - b: duplicate, - c: "unique" - }; - } - emitter.emit( "testEnd", makeFailingTestEnd( createDuplicateAcyclical() ) ); - assert.strictEqual( last, ` --- + ); + }); + + QUnit.test('output actual assertion value of an acyclical structure', assert => { + // Creates an object that references another object more + // than once in an acyclical way. + function createDuplicateAcyclical () { + const duplicate = { + example: 'value' + }; + return { + a: duplicate, + b: duplicate, + c: 'unique' + }; + } + emitter.emit('testEnd', makeFailingTestEnd(createDuplicateAcyclical())); + assert.strictEqual(last, ` --- message: failed severity: failed actual : { @@ -382,26 +379,26 @@ Bail out! ReferenceError: Boo is not defined } expected: expected ...` - ); - } ); - - QUnit.test( "output the total number of tests", assert => { - emitter.emit( "runEnd", { - testCounts: { - total: 6, - passed: 3, - failed: 2, - skipped: 1, - todo: 0 - } - } ); - - assert.strictEqual( buffer, `1..6 + ); + }); + + QUnit.test('output the total number of tests', assert => { + emitter.emit('runEnd', { + testCounts: { + total: 6, + passed: 3, + failed: 2, + skipped: 1, + todo: 0 + } + }); + + assert.strictEqual(buffer, `1..6 # pass 3 -${kleur.yellow( "# skip 1" )} -${kleur.cyan( "# todo 0" )} -${kleur.red( "# fail 2" )} +${kleur.yellow('# skip 1')} +${kleur.cyan('# todo 0')} +${kleur.red('# fail 2')} ` - ); - } ); -} ); + ); + }); +}); diff --git a/test/cli/cli-main.js b/test/cli/cli-main.js index 12ac23bf7..e9c4190a3 100644 --- a/test/cli/cli-main.js +++ b/test/cli/cli-main.js @@ -1,260 +1,260 @@ -"use strict"; +'use strict'; -const expectedOutput = require( "./fixtures/expected/tap-outputs" ); -const { execute, prettyPrintCommand } = require( "./helpers/execute" ); -const semver = require( "semver" ); +const expectedOutput = require('./fixtures/expected/tap-outputs'); +const { execute, prettyPrintCommand } = require('./helpers/execute'); +const semver = require('semver'); -const skipOnWinTest = ( process.platform === "win32" ? "skip" : "test" ); +const skipOnWinTest = (process.platform === 'win32' ? 'skip' : 'test'); -function getExpected( command ) { - return expectedOutput[ prettyPrintCommand( command ) ]; +function getExpected (command) { + return expectedOutput[prettyPrintCommand(command)]; } -QUnit.module( "CLI Main", () => { - QUnit.test( "defaults to running tests in 'test' directory", async assert => { - const command = [ "qunit" ]; - const execution = await execute( command ); - - assert.equal( execution.code, 0 ); - assert.equal( execution.stderr, "" ); - assert.equal( execution.stdout, getExpected( command ) ); - } ); - - QUnit.test( "errors if no test files are found to run", async assert => { - try { - await execute( [ "qunit", "does-not-exist.js" ] ); - } catch ( e ) { - assert.true( e.stderr.includes( "No files were found matching" ) ); - } - } ); - - QUnit.test( "accepts globs for test files to run", async assert => { - const command = [ "qunit", "glob/**/*-test.js" ]; - const execution = await execute( command ); - - assert.equal( execution.code, 0 ); - assert.equal( execution.stderr, "" ); - assert.equal( execution.stdout, getExpected( command ) ); - } ); - - QUnit.test( "runs a single JS file", async assert => { - const command = [ "qunit", "single.js" ]; - const execution = await execute( command ); - - assert.equal( execution.code, 0 ); - assert.equal( execution.stderr, "" ); - assert.equal( execution.stdout, getExpected( command ) ); - } ); - - QUnit.test( "runs multiple JS files", async assert => { - const command = [ "qunit", "single.js", "double.js" ]; - const execution = await execute( command ); - - assert.equal( execution.code, 0 ); - assert.equal( execution.stderr, "" ); - assert.equal( execution.stdout, getExpected( command ) ); - } ); - - QUnit.test( "runs all JS files in a directory matching an arg", async assert => { - const command = [ "qunit", "test" ]; - const execution = await execute( command ); - - assert.equal( execution.code, 0 ); - assert.equal( execution.stderr, "" ); - assert.equal( execution.stdout, getExpected( command ) ); - } ); - - QUnit.test( "runs multiple types of file paths", async assert => { - const command = [ "qunit", "test", "single.js", "glob/**/*-test.js" ]; - const execution = await execute( command ); - - assert.equal( execution.code, 0 ); - assert.equal( execution.stderr, "" ); - assert.equal( execution.stdout, getExpected( command ) ); - } ); - - QUnit.test( "logs test files that fail to load properly", async assert => { - try { - await execute( [ "qunit", "syntax-error/test.js" ] ); - } catch ( e ) { - assert.true( e.stdout.includes( "not ok 1 global failure" ) ); - assert.true( e.stdout.includes( "Failed to load file syntax-error/test.js" ) ); - assert.true( e.stdout.includes( "ReferenceError: varIsNotDefined is not defined" ) ); - assert.equal( e.code, 1 ); - } - } ); - - // TODO: Figure out why trace isn't trimmed on Windows. https://github.com/qunitjs/qunit/issues/1359 - QUnit[ skipOnWinTest ]( "report assert.throws() failures properly", async assert => { - const command = [ "qunit", "fail/throws-match.js" ]; - try { - await execute( command ); - } catch ( e ) { - assert.equal( e.code, 1 ); - assert.equal( e.stderr, "" ); - assert.equal( e.stdout, getExpected( command ) ); - } - } ); - - QUnit.test( "exit code is 1 when failing tests are present", async assert => { - try { - await execute( [ "qunit", "fail/failure.js" ] ); - } catch ( e ) { - assert.equal( e.code, 1 ); - } - } ); - - QUnit.test( "exit code is 1 when no tests are run", async assert => { - const command = [ "qunit", "no-tests" ]; - try { - await execute( command ); - } catch ( e ) { - assert.equal( e.code, 1 ); - assert.equal( e.stderr, "" ); - assert.equal( e.stdout, getExpected( command ) ); - } - } ); - - QUnit.test( "exit code is 0 when no tests are run and failOnZeroTests is `false`", async assert => { - const command = [ "qunit", "assert-expect/no-tests.js" ]; - const execution = await execute( command ); - - assert.equal( execution.code, 0 ); - assert.equal( execution.stderr, "" ); - assert.equal( execution.stdout, getExpected( command ) ); - } ); - - QUnit.test( "exit code is 1 when no tests exit before done", async assert => { - const command = [ "qunit", "hanging-test" ]; - try { - await execute( command ); - } catch ( e ) { - assert.equal( e.code, 1 ); - assert.equal( e.stderr, getExpected( command ) ); - } - } ); - - QUnit.test( "unhandled rejections fail tests", async assert => { - const command = [ "qunit", "unhandled-rejection.js" ]; - - try { - const result = await execute( command ); - assert.pushResult( { - result: false, - actual: result.stdout - } ); - } catch ( e ) { - assert.equal( e.code, 1 ); - assert.equal( e.stderr, "" ); - assert.equal( e.stdout, getExpected( command ) ); - } - } ); - - QUnit.test( "hard errors in test using `assert.async` are caught and reported", async assert => { - const command = [ "qunit", "hard-error-in-test-with-no-async-handler.js" ]; - - try { - const result = await execute( command ); - assert.pushResult( { - result: false, - actual: result.stdout - } ); - } catch ( e ) { - assert.equal( e.code, 1 ); - assert.equal( e.stderr, "" ); - assert.true( e.stdout.includes( "Died on test #2 at " ) ); - assert.true( e.stdout.includes( "Error: expected error thrown in test" ) ); - } - } ); - - QUnit.test( "hard errors in hook are caught and reported", async assert => { - const command = [ "qunit", "hard-error-in-hook.js" ]; - - try { - const result = await execute( command ); - assert.pushResult( { - result: false, - actual: result.stdout - } ); - } catch ( e ) { - assert.equal( e.code, 1 ); - assert.equal( e.stderr, "" ); - assert.true( e.stdout.includes( "message: before failed on contains a hard error: expected error thrown in hook" ) ); - assert.true( e.stdout.includes( "Error: expected error thrown in hook" ) ); - } - } ); - - // Regression test against "details of begin error swallowed" - // https://github.com/qunitjs/qunit/issues/1446 - QUnit.test( "report failure in begin callback", async assert => { - const command = [ "qunit", "bad-callbacks/begin-throw.js" ]; - - try { - const result = await execute( command ); - assert.pushResult( { - result: false, - actual: result.stdout - } ); - } catch ( e ) { - assert.equal( e.code, 1 ); - assert.equal( e.stdout, getExpected( command ) ); - } - } ); - - QUnit.test( "report failure in done callback", async assert => { - const command = [ "qunit", "bad-callbacks/done-throw.js" ]; - - try { - const result = await execute( command ); - assert.pushResult( { - result: false, - actual: result.stdout - } ); - } catch ( e ) { - assert.equal( e.code, 1 ); - assert.equal( e.stdout, getExpected( command ) ); - } - } ); - - QUnit.test( "report failure in moduleDone callback", async assert => { - const command = [ "qunit", "bad-callbacks/moduleDone-throw.js" ]; - - try { - const result = await execute( command ); - assert.pushResult( { - result: false, - actual: result.stdout - } ); - } catch ( e ) { - assert.equal( e.code, 1 ); - - // FIXME: The details of this error are swallowed - assert.equal( e.stdout, `TAP version 13 -ok 1 module1 > test1` ); - assert.equal( e.stderr, "Error: Process exited before tests finished running" ); - } - } ); - - QUnit.test( "report failure in testStart callback", async assert => { - const command = [ "qunit", "bad-callbacks/testStart-throw.js" ]; - - try { - const result = await execute( command ); - assert.pushResult( { - result: false, - actual: result.stdout - } ); - } catch ( e ) { - assert.equal( e.code, 1 ); - - // FIXME: The details of this error are swallowed - assert.equal( e.stdout, "TAP version 13" ); - assert.equal( e.stderr, "Error: Process exited before tests finished running" ); - } - } ); - - QUnit.test( "callbacks", async assert => { - const expected = `CALLBACK: begin1 +QUnit.module('CLI Main', () => { + QUnit.test("defaults to running tests in 'test' directory", async assert => { + const command = ['qunit']; + const execution = await execute(command); + + assert.equal(execution.code, 0); + assert.equal(execution.stderr, ''); + assert.equal(execution.stdout, getExpected(command)); + }); + + QUnit.test('errors if no test files are found to run', async assert => { + try { + await execute(['qunit', 'does-not-exist.js']); + } catch (e) { + assert.true(e.stderr.includes('No files were found matching')); + } + }); + + QUnit.test('accepts globs for test files to run', async assert => { + const command = ['qunit', 'glob/**/*-test.js']; + const execution = await execute(command); + + assert.equal(execution.code, 0); + assert.equal(execution.stderr, ''); + assert.equal(execution.stdout, getExpected(command)); + }); + + QUnit.test('runs a single JS file', async assert => { + const command = ['qunit', 'single.js']; + const execution = await execute(command); + + assert.equal(execution.code, 0); + assert.equal(execution.stderr, ''); + assert.equal(execution.stdout, getExpected(command)); + }); + + QUnit.test('runs multiple JS files', async assert => { + const command = ['qunit', 'single.js', 'double.js']; + const execution = await execute(command); + + assert.equal(execution.code, 0); + assert.equal(execution.stderr, ''); + assert.equal(execution.stdout, getExpected(command)); + }); + + QUnit.test('runs all JS files in a directory matching an arg', async assert => { + const command = ['qunit', 'test']; + const execution = await execute(command); + + assert.equal(execution.code, 0); + assert.equal(execution.stderr, ''); + assert.equal(execution.stdout, getExpected(command)); + }); + + QUnit.test('runs multiple types of file paths', async assert => { + const command = ['qunit', 'test', 'single.js', 'glob/**/*-test.js']; + const execution = await execute(command); + + assert.equal(execution.code, 0); + assert.equal(execution.stderr, ''); + assert.equal(execution.stdout, getExpected(command)); + }); + + QUnit.test('logs test files that fail to load properly', async assert => { + try { + await execute(['qunit', 'syntax-error/test.js']); + } catch (e) { + assert.true(e.stdout.includes('not ok 1 global failure')); + assert.true(e.stdout.includes('Failed to load file syntax-error/test.js')); + assert.true(e.stdout.includes('ReferenceError: varIsNotDefined is not defined')); + assert.equal(e.code, 1); + } + }); + + // TODO: Figure out why trace isn't trimmed on Windows. https://github.com/qunitjs/qunit/issues/1359 + QUnit[skipOnWinTest]('report assert.throws() failures properly', async assert => { + const command = ['qunit', 'fail/throws-match.js']; + try { + await execute(command); + } catch (e) { + assert.equal(e.code, 1); + assert.equal(e.stderr, ''); + assert.equal(e.stdout, getExpected(command)); + } + }); + + QUnit.test('exit code is 1 when failing tests are present', async assert => { + try { + await execute(['qunit', 'fail/failure.js']); + } catch (e) { + assert.equal(e.code, 1); + } + }); + + QUnit.test('exit code is 1 when no tests are run', async assert => { + const command = ['qunit', 'no-tests']; + try { + await execute(command); + } catch (e) { + assert.equal(e.code, 1); + assert.equal(e.stderr, ''); + assert.equal(e.stdout, getExpected(command)); + } + }); + + QUnit.test('exit code is 0 when no tests are run and failOnZeroTests is `false`', async assert => { + const command = ['qunit', 'assert-expect/no-tests.js']; + const execution = await execute(command); + + assert.equal(execution.code, 0); + assert.equal(execution.stderr, ''); + assert.equal(execution.stdout, getExpected(command)); + }); + + QUnit.test('exit code is 1 when no tests exit before done', async assert => { + const command = ['qunit', 'hanging-test']; + try { + await execute(command); + } catch (e) { + assert.equal(e.code, 1); + assert.equal(e.stderr, getExpected(command)); + } + }); + + QUnit.test('unhandled rejections fail tests', async assert => { + const command = ['qunit', 'unhandled-rejection.js']; + + try { + const result = await execute(command); + assert.pushResult({ + result: false, + actual: result.stdout + }); + } catch (e) { + assert.equal(e.code, 1); + assert.equal(e.stderr, ''); + assert.equal(e.stdout, getExpected(command)); + } + }); + + QUnit.test('hard errors in test using `assert.async` are caught and reported', async assert => { + const command = ['qunit', 'hard-error-in-test-with-no-async-handler.js']; + + try { + const result = await execute(command); + assert.pushResult({ + result: false, + actual: result.stdout + }); + } catch (e) { + assert.equal(e.code, 1); + assert.equal(e.stderr, ''); + assert.true(e.stdout.includes('Died on test #2 at ')); + assert.true(e.stdout.includes('Error: expected error thrown in test')); + } + }); + + QUnit.test('hard errors in hook are caught and reported', async assert => { + const command = ['qunit', 'hard-error-in-hook.js']; + + try { + const result = await execute(command); + assert.pushResult({ + result: false, + actual: result.stdout + }); + } catch (e) { + assert.equal(e.code, 1); + assert.equal(e.stderr, ''); + assert.true(e.stdout.includes('message: before failed on contains a hard error: expected error thrown in hook')); + assert.true(e.stdout.includes('Error: expected error thrown in hook')); + } + }); + + // Regression test against "details of begin error swallowed" + // https://github.com/qunitjs/qunit/issues/1446 + QUnit.test('report failure in begin callback', async assert => { + const command = ['qunit', 'bad-callbacks/begin-throw.js']; + + try { + const result = await execute(command); + assert.pushResult({ + result: false, + actual: result.stdout + }); + } catch (e) { + assert.equal(e.code, 1); + assert.equal(e.stdout, getExpected(command)); + } + }); + + QUnit.test('report failure in done callback', async assert => { + const command = ['qunit', 'bad-callbacks/done-throw.js']; + + try { + const result = await execute(command); + assert.pushResult({ + result: false, + actual: result.stdout + }); + } catch (e) { + assert.equal(e.code, 1); + assert.equal(e.stdout, getExpected(command)); + } + }); + + QUnit.test('report failure in moduleDone callback', async assert => { + const command = ['qunit', 'bad-callbacks/moduleDone-throw.js']; + + try { + const result = await execute(command); + assert.pushResult({ + result: false, + actual: result.stdout + }); + } catch (e) { + assert.equal(e.code, 1); + + // FIXME: The details of this error are swallowed + assert.equal(e.stdout, `TAP version 13 +ok 1 module1 > test1`); + assert.equal(e.stderr, 'Error: Process exited before tests finished running'); + } + }); + + QUnit.test('report failure in testStart callback', async assert => { + const command = ['qunit', 'bad-callbacks/testStart-throw.js']; + + try { + const result = await execute(command); + assert.pushResult({ + result: false, + actual: result.stdout + }); + } catch (e) { + assert.equal(e.code, 1); + + // FIXME: The details of this error are swallowed + assert.equal(e.stdout, 'TAP version 13'); + assert.equal(e.stderr, 'Error: Process exited before tests finished running'); + } + }); + + QUnit.test('callbacks', async assert => { + const expected = `CALLBACK: begin1 CALLBACK: begin2 CALLBACK: moduleStart1 CALLBACK: moduleStart2 @@ -334,15 +334,15 @@ CALLBACK: moduleDone2 CALLBACK: done1 CALLBACK: done2`; - const command = [ "qunit", "callbacks.js" ]; - const execution = await execute( command ); + const command = ['qunit', 'callbacks.js']; + const execution = await execute(command); - assert.equal( execution.stderr, expected ); - assert.equal( execution.code, 0 ); - } ); + assert.equal(execution.stderr, expected); + assert.equal(execution.code, 0); + }); - QUnit.test( "callbacks with promises", async assert => { - const expected = `CALLBACK: begin + QUnit.test('callbacks with promises', async assert => { + const expected = `CALLBACK: begin CALLBACK: begin2 CALLBACK: moduleStart CALLBACK: moduleStart @@ -358,15 +358,15 @@ CALLBACK: moduleDone - module1 > nestedModule2 CALLBACK: moduleDone - module1 CALLBACK: done`; - const command = [ "qunit", "callbacks-promises.js" ]; - const execution = await execute( command ); + const command = ['qunit', 'callbacks-promises.js']; + const execution = await execute(command); - assert.equal( execution.stderr, expected ); - assert.equal( execution.code, 0 ); - } ); + assert.equal(execution.stderr, expected); + assert.equal(execution.code, 0); + }); - QUnit.test( "global hooks order", async assert => { - const expected = ` + QUnit.test('global hooks order', async assert => { + const expected = ` HOOK: A1 @ global beforeEach-1 HOOK: A1 @ global beforeEach-2 HOOK: A1 @ global afterEach-2 @@ -416,488 +416,480 @@ HOOK: BCD1 @ BCD after HOOK: BCD1 @ BC after HOOK: BCD1 @ B after`; - const command = [ "qunit", "hooks-global-order.js" ]; - const execution = await execute( command ); - - assert.equal( execution.stderr, expected.trim() ); - assert.equal( execution.code, 0 ); - } ); - - QUnit.test( "global hooks context", async assert => { - const command = [ "qunit", "hooks-global-context.js" ]; - const execution = await execute( command ); - - assert.equal( execution.code, 0 ); - assert.equal( execution.stderr, "" ); - assert.equal( execution.stdout, getExpected( command ) ); - } ); - - if ( semver.gte( process.versions.node, "12.0.0" ) ) { - QUnit.test( "run ESM test suite with import statement", async assert => { - const command = [ "qunit", "../../es2018/esm.mjs" ]; - const execution = await execute( command ); - - assert.equal( execution.code, 0 ); - - // Node 12 enabled ESM by default, without experimental flag, - // but left the warning in stderr. The warning was removed in Node 14. - // Don't bother checking stderr - const stderr = semver.gte( process.versions.node, "14.0.0" ) ? execution.stderr : ""; - assert.equal( stderr, "" ); - - assert.equal( execution.stdout, getExpected( command ) ); - } ); - } - - // https://nodejs.org/dist/v12.12.0/docs/api/cli.html#cli_enable_source_maps - if ( semver.gte( process.versions.node, "14.0.0" ) ) { - - // TODO: Figure out why trace isn't trimmed on Windows. https://github.com/qunitjs/qunit/issues/1359 - QUnit[ skipOnWinTest ]( "normal trace with native source map", async assert => { - const command = [ "qunit", "sourcemap/source.js" ]; - try { - await execute( command ); - } catch ( e ) { - assert.equal( e.code, 1 ); - assert.equal( e.stderr, "" ); - assert.equal( e.stdout, getExpected( command ) ); - } - } ); - - // skip if running in code coverage mode, - // as that leads to conflicting maps-on-maps that invalidate this test - // - // TODO: Figure out why trace isn't trimmed on Windows. https://github.com/qunitjs/qunit/issues/1359 - QUnit[ process.env.NYC_PROCESS_ID ? "skip" : skipOnWinTest ]( - "mapped trace with native source map", async function( assert ) { - const command = [ "qunit", "sourcemap/source.min.js" ]; - try { - await execute( command, { - env: { NODE_OPTIONS: "--enable-source-maps" } - } ); - } catch ( e ) { - assert.equal( e.code, 1 ); - assert.equal( e.stderr, "" ); - assert.equal( e.stdout, getExpected( command ) ); - } - } ); - } - - QUnit.test( "timeouts correctly recover", async assert => { - const command = [ "qunit", "timeout" ]; - try { - await execute( command ); - } catch ( e ) { - assert.equal( e.code, 1 ); - assert.equal( e.stderr, "" ); - assert.equal( e.stdout, getExpected( command ) ); - } - } ); - - QUnit.test( "allows running zero-assertion tests", async assert => { - const command = [ "qunit", "zero-assertions.js" ]; - const execution = await execute( command ); - - assert.equal( execution.code, 0 ); - assert.equal( execution.stderr, "" ); - assert.equal( execution.stdout, getExpected( command ) ); - } ); - - // https://nodejs.org/docs/v14.0.0/api/v8.html#v8_v8_getheapsnapshot - // Created in Node 11.x, but starts working the way we need from Node 14. - if ( semver.gte( process.versions.node, "14.0.0" ) ) { - QUnit.test( "callbacks and hooks from modules are properly released for garbage collection", async assert => { - const command = [ "node", "--expose-gc", "../../../bin/qunit.js", "memory-leak/*.js" ]; - const execution = await execute( command ); - - assert.equal( execution.code, 0 ); - assert.equal( execution.stderr, "" ); - assert.equal( execution.stdout, getExpected( command ) ); - } ); - - QUnit.test( "callbacks and hooks from filtered-out modules are properly released for garbage collection", async assert => { - const command = [ "node", "--expose-gc", "../../../bin/qunit.js", "--filter", "!child", "memory-leak/*.js" ]; - const execution = await execute( command ); - - assert.equal( execution.code, 0 ); - assert.equal( execution.stderr, "" ); - assert.equal( execution.stdout, getExpected( command ) ); - } ); - } - - QUnit.module( "filter", () => { - QUnit.test( "can properly filter tests", async assert => { - const command = [ "qunit", "--filter", "single", "test", "single.js", "glob/**/*-test.js" ]; - const equivalentCommand = [ "qunit", "single.js" ]; - const execution = await execute( command ); - - assert.equal( execution.code, 0 ); - assert.equal( execution.stderr, "" ); - assert.equal( execution.stdout, getExpected( equivalentCommand ) ); - } ); - - // TODO: Workaround fact that child_process.spawn() args array is a lie on Windows. - // https://github.com/nodejs/node/issues/29532 - // Can't trivially quote since that breaks Linux which would interpret quotes - // as literals. - QUnit[ skipOnWinTest ]( "exit code is 1 when no tests match filter", async assert => { - - const command = [ "qunit", "--filter", "no matches", "test" ]; - try { - await execute( command ); - } catch ( e ) { - assert.equal( e.code, 1 ); - assert.equal( e.stderr, "" ); - assert.equal( e.stdout, getExpected( command ) ); - } - } ); - } ); - - QUnit.module( "require", () => { - QUnit.test( "can properly require dependencies and modules", async assert => { - const command = [ "qunit", "single.js", - "--require", "require-dep", - "--require", "./node_modules/require-dep/module.js" - ]; - const execution = await execute( command ); - - assert.equal( execution.code, 0 ); - assert.equal( execution.stderr, "" ); - assert.equal( execution.stdout, getExpected( command ) ); - } ); - - QUnit.test( "displays helpful error when failing to require a file", async assert => { - const command = [ "qunit", "single.js", "--require", "does-not-exist-at-all" ]; - try { - await execute( command ); - } catch ( e ) { - assert.equal( e.code, 1 ); - assert.true( e.stderr.includes( "Error: Cannot find module 'does-not-exist-at-all'" ) ); - assert.equal( e.stdout, "" ); - } - } ); - } ); - - QUnit.module( "seed", () => { - QUnit.test( "can properly seed tests", async assert => { - const command = [ "qunit", "--seed", "s33d", "test", "single.js", "glob/**/*-test.js" ]; - const execution = await execute( command ); - - assert.equal( execution.code, 0 ); - assert.equal( execution.stderr, "" ); - assert.equal( execution.stdout, getExpected( command ) ); - } ); - } ); - - QUnit.module( "notrycatch", () => { - QUnit.test( "errors if notrycatch is used and a rejection occurs", async assert => { - try { - await execute( [ "qunit", "notrycatch/returns-rejection.js" ] ); - } catch ( e ) { - assert.pushResult( { - - // only in stdout due to using `console.log` in manual `unhandledRejection` handler - result: e.stdout.includes( "Unhandled Rejection: bad things happen sometimes" ), - actual: e.stdout + "\n" + e.stderr - } ); - } - } ); - - QUnit.test( "errors if notrycatch is used and a rejection occurs in a hook", async assert => { - try { - await execute( [ "qunit", "notrycatch/returns-rejection-in-hook.js" ] ); - } catch ( e ) { - assert.pushResult( { - - // only in stdout due to using `console.log` in manual `unhandledRejection` handler - result: e.stdout.includes( "Unhandled Rejection: bad things happen sometimes" ), - actual: e.stdout + "\n" + e.stderr - } ); - } - } ); - } ); - - QUnit.test( "config.filter (string)", async assert => { - const command = [ "qunit", "config-filter-string.js" ]; - const execution = await execute( command ); - - assert.equal( execution.code, 0 ); - assert.equal( execution.stderr, "" ); - assert.equal( execution.stdout, getExpected( command ) ); - } ); - - QUnit.test( "config.filter (regex)", async assert => { - const command = [ "qunit", "config-filter-regex.js" ]; - const execution = await execute( command ); - - assert.equal( execution.code, 0 ); - assert.equal( execution.stderr, "" ); - assert.equal( execution.stdout, getExpected( command ) ); - } ); - - QUnit.test( "config.filter (regex exclude)", async assert => { - const command = [ "qunit", "config-filter-regex-exclude.js" ]; - const execution = await execute( command ); - - assert.equal( execution.code, 0 ); - assert.equal( execution.stderr, "" ); - assert.equal( execution.stdout, getExpected( command ) ); - } ); - - QUnit.test( "config.module", async assert => { - const command = [ "qunit", "config-module.js" ]; - const execution = await execute( command ); - - assert.equal( execution.code, 0 ); - assert.equal( execution.stderr, "" ); - assert.equal( execution.stdout, getExpected( command ) ); - } ); - - QUnit.test( "config.moduleId", async assert => { - const command = [ "qunit", "config-moduleId.js" ]; - const execution = await execute( command ); - - assert.equal( execution.code, 0 ); - assert.equal( execution.stderr, "" ); - assert.equal( execution.stdout, getExpected( command ) ); - } ); - - QUnit.test( "config.testId", async assert => { - const command = [ "qunit", "config-testId.js" ]; - const execution = await execute( command ); - - assert.equal( execution.code, 0 ); - assert.equal( execution.stderr, "" ); - assert.equal( execution.stdout, getExpected( command ) ); - } ); - - QUnit.test( "config.testTimeout", async assert => { - const command = [ "qunit", "config-testTimeout.js" ]; - - try { - await execute( command ); - } catch ( e ) { - assert.equal( e.code, 1 ); - assert.equal( e.stderr, "" ); - assert.equal( e.stdout, getExpected( command ) ); - } - } ); - - QUnit.module( "noglobals", () => { - QUnit.test( "add global variable", async assert => { - try { - await execute( [ "qunit", "noglobals/add-global.js" ] ); - } catch ( e ) { - assert.pushResult( { - result: e.stdout.includes( "message: Introduced global variable(s): dummyGlobal" ), - actual: e.stdout + "\n" + e.stderr - } ); - } - } ); - - QUnit.test( "remove global variable", async assert => { - try { - await execute( [ "qunit", "noglobals/remove-global.js" ] ); - } catch ( e ) { - assert.pushResult( { - result: e.stdout.includes( "message: Deleted global variable(s): dummyGlobal" ), - actual: e.stdout + "\n" + e.stderr - } ); - } - } ); - - QUnit.test( "forgive qunit DOM global variables", async assert => { - const execution = await execute( [ "qunit", "noglobals/ignored.js" ] ); - assert.equal( execution.code, 0 ); - assert.equal( execution.stderr, "" ); - } ); - } ); - - QUnit.module( "assert.async", () => { - - QUnit.test( "call after tests timeout", async assert => { - const command = [ "qunit", "done-after-timeout.js" ]; - try { - await execute( command ); - } catch ( e ) { - assert.equal( e.code, 1 ); - assert.equal( e.stdout, getExpected( command ) ); - } - } ); - - // TODO: Figure out why trace isn't trimmed on Windows. https://github.com/qunitjs/qunit/issues/1359 - QUnit[ skipOnWinTest ]( "drooling call to callback across tests", async assert => { - const command = [ "qunit", "drooling-done.js" ]; - try { - await execute( command ); - } catch ( e ) { - assert.equal( e.code, 1 ); - assert.equal( e.stdout, getExpected( command ) ); - } - } ); - - // TODO: Figure out why trace isn't trimmed on Windows. https://github.com/qunitjs/qunit/issues/1359 - QUnit[ skipOnWinTest ]( "extra call to callback across tests", async assert => { - const command = [ "qunit", "drooling-extra-done.js" ]; - try { - await execute( command ); - } catch ( e ) { - assert.equal( e.code, 1 ); - assert.equal( e.stdout, getExpected( command ) ); - } - } ); - - QUnit.test( "extra call to callback outside tests", async assert => { - const command = [ "qunit", "drooling-extra-done-outside.js" ]; - try { - await execute( command ); - } catch ( e ) { - assert.equal( e.code, 1 ); - assert.equal( e.stdout, getExpected( command ) ); - } - } ); - - // TODO: Figure out why trace isn't trimmed on Windows. https://github.com/qunitjs/qunit/issues/1359 - QUnit[ skipOnWinTest ]( "too many calls to callback", async assert => { - const command = [ "qunit", "too-many-done-calls.js" ]; - try { - await execute( command ); - } catch ( e ) { - assert.equal( e.code, 1 ); - assert.equal( e.stdout, getExpected( command ) ); - } - } ); - - } ); - - QUnit.module( "only", () => { - QUnit.test( "test", async assert => { - - const command = [ "qunit", "only/test.js" ]; - const execution = await execute( command ); - - assert.equal( execution.code, 0 ); - assert.equal( execution.stderr, "" ); - assert.equal( execution.stdout, getExpected( command ) ); - } ); - - // TODO: Figure out why trace isn't trimmed on Windows. https://github.com/qunitjs/qunit/issues/1359 - QUnit[ skipOnWinTest ]( "nested modules", async assert => { - - const command = [ "qunit", "only/module.js" ]; - const execution = await execute( command ); - - assert.equal( execution.code, 0 ); - assert.equal( execution.stderr, "" ); - assert.equal( execution.stdout, getExpected( command ) ); - } ); - - QUnit.test( "module followed by test", async assert => { - - const command = [ "qunit", "only/module-then-test.js" ]; - const execution = await execute( command ); - - assert.equal( execution.code, 0 ); - assert.equal( execution.stderr, "" ); - assert.equal( execution.stdout, getExpected( command ) ); - } ); - - // TODO: Figure out why trace isn't trimmed on Windows. https://github.com/qunitjs/qunit/issues/1359 - QUnit[ skipOnWinTest ]( "flat modules", async assert => { - - const command = [ "qunit", "only/module-flat.js" ]; - const execution = await execute( command ); - - assert.equal( execution.code, 0 ); - assert.equal( execution.stderr, "" ); - assert.equal( execution.stdout, getExpected( command ) ); - } ); - } ); - - // Regression test for https://github.com/qunitjs/qunit/issues/1478 - QUnit.test( "nested module scopes", async assert => { - const command = [ "qunit", "module-nested.js" ]; - const execution = await execute( command ); - - assert.equal( execution.code, 0 ); - assert.equal( execution.stdout, getExpected( command ) ); - } ); - - QUnit.test( "warns about incorrect hook usage", async assert => { - const command = [ "qunit", "incorrect-hooks-warning/test.js" ]; - const execution = await execute( command ); - - assert.equal( execution.code, 0 ); - assert.equal( execution.stderr, "The `beforeEach` hook was called inside the wrong module (`module providing hooks > module not providing hooks`). Instead, use hooks provided by the callback to the containing module (`module providing hooks`). This will become an error in QUnit 3.0.", "The warning shows" ); - assert.equal( execution.stdout, getExpected( command ) ); - } ); - - QUnit.test( "warns about unsupported async module callback", async assert => { - const command = [ "qunit", "async-module-warning/test.js" ]; - const execution = await execute( command ); - - assert.equal( execution.code, 0 ); - assert.equal( execution.stderr, "Returning a promise from a module callback is not supported. Instead, use hooks for async behavior. This will become an error in QUnit 3.0.", "The warning shows" ); - assert.equal( execution.stdout, getExpected( command ) ); - } ); - - QUnit.test( "warns about unsupported promise return value from module", async assert => { - const command = [ "qunit", "async-module-warning/promise-test.js" ]; - const execution = await execute( command ); - - assert.equal( execution.code, 0 ); - assert.equal( execution.stderr, "Returning a promise from a module callback is not supported. Instead, use hooks for async behavior. This will become an error in QUnit 3.0.", "The warning shows" ); - assert.equal( execution.stdout, getExpected( command ) ); - } ); - - QUnit.module( "assert.expect failing conditions", () => { - QUnit.test( "mismatched expected assertions", async assert => { - const command = [ "qunit", "assert-expect/failing-expect.js" ]; - try { - const result = await execute( command ); - assert.pushResult( { - result: false, - actual: result.stdout - } ); - } catch ( e ) { - assert.equal( e.code, 1 ); - assert.equal( e.stderr, "" ); - - // can't match exactly due to stack frames including internal line numbers - assert.true( e.stdout.includes( "message: Expected 2 assertions, but 1 were run" ), e.stdout ); - } - } ); - - QUnit.test( "no assertions run - use expect(0)", async assert => { - const command = [ "qunit", "assert-expect/no-assertions.js" ]; - try { - const result = await execute( command ); - assert.pushResult( { - result: true, - actual: result.stdout - } ); - } catch ( e ) { - assert.equal( e.code, 1 ); - assert.equal( e.stderr, "" ); - - // can't match exactly due to stack frames including internal line numbers - assert.true( e.stdout.includes( "Expected at least one assertion, but none were run - call expect(0) to accept zero assertions." ), e.stdout ); - } - } ); - - QUnit.test( "requireExpects", async assert => { - const command = [ "qunit", "assert-expect/require-expects.js" ]; - try { - const result = await execute( command ); - assert.pushResult( { - result: false, - actual: result.stdout - } ); - } catch ( e ) { - assert.equal( e.code, 1 ); - assert.equal( e.stderr, "" ); - assert.true( e.stdout.includes( "message: Expected number of assertions to be defined, but expect() was not called." ), e.stdout ); - } - } ); - } ); -} ); + const command = ['qunit', 'hooks-global-order.js']; + const execution = await execute(command); + + assert.equal(execution.stderr, expected.trim()); + assert.equal(execution.code, 0); + }); + + QUnit.test('global hooks context', async assert => { + const command = ['qunit', 'hooks-global-context.js']; + const execution = await execute(command); + + assert.equal(execution.code, 0); + assert.equal(execution.stderr, ''); + assert.equal(execution.stdout, getExpected(command)); + }); + + if (semver.gte(process.versions.node, '12.0.0')) { + QUnit.test('run ESM test suite with import statement', async assert => { + const command = ['qunit', '../../es2018/esm.mjs']; + const execution = await execute(command); + + assert.equal(execution.code, 0); + + // Node 12 enabled ESM by default, without experimental flag, + // but left the warning in stderr. The warning was removed in Node 14. + // Don't bother checking stderr + const stderr = semver.gte(process.versions.node, '14.0.0') ? execution.stderr : ''; + assert.equal(stderr, ''); + + assert.equal(execution.stdout, getExpected(command)); + }); + } + + // https://nodejs.org/dist/v12.12.0/docs/api/cli.html#cli_enable_source_maps + if (semver.gte(process.versions.node, '14.0.0')) { + // TODO: Figure out why trace isn't trimmed on Windows. https://github.com/qunitjs/qunit/issues/1359 + QUnit[skipOnWinTest]('normal trace with native source map', async assert => { + const command = ['qunit', 'sourcemap/source.js']; + try { + await execute(command); + } catch (e) { + assert.equal(e.code, 1); + assert.equal(e.stderr, ''); + assert.equal(e.stdout, getExpected(command)); + } + }); + + // skip if running in code coverage mode, + // as that leads to conflicting maps-on-maps that invalidate this test + // + // TODO: Figure out why trace isn't trimmed on Windows. https://github.com/qunitjs/qunit/issues/1359 + QUnit[process.env.NYC_PROCESS_ID ? 'skip' : skipOnWinTest]( + 'mapped trace with native source map', async function (assert) { + const command = ['qunit', 'sourcemap/source.min.js']; + try { + await execute(command, { + env: { NODE_OPTIONS: '--enable-source-maps' } + }); + } catch (e) { + assert.equal(e.code, 1); + assert.equal(e.stderr, ''); + assert.equal(e.stdout, getExpected(command)); + } + }); + } + + QUnit.test('timeouts correctly recover', async assert => { + const command = ['qunit', 'timeout']; + try { + await execute(command); + } catch (e) { + assert.equal(e.code, 1); + assert.equal(e.stderr, ''); + assert.equal(e.stdout, getExpected(command)); + } + }); + + QUnit.test('allows running zero-assertion tests', async assert => { + const command = ['qunit', 'zero-assertions.js']; + const execution = await execute(command); + + assert.equal(execution.code, 0); + assert.equal(execution.stderr, ''); + assert.equal(execution.stdout, getExpected(command)); + }); + + // https://nodejs.org/docs/v14.0.0/api/v8.html#v8_v8_getheapsnapshot + // Created in Node 11.x, but starts working the way we need from Node 14. + if (semver.gte(process.versions.node, '14.0.0')) { + QUnit.test('callbacks and hooks from modules are properly released for garbage collection', async assert => { + const command = ['node', '--expose-gc', '../../../bin/qunit.js', 'memory-leak/*.js']; + const execution = await execute(command); + + assert.equal(execution.code, 0); + assert.equal(execution.stderr, ''); + assert.equal(execution.stdout, getExpected(command)); + }); + + QUnit.test('callbacks and hooks from filtered-out modules are properly released for garbage collection', async assert => { + const command = ['node', '--expose-gc', '../../../bin/qunit.js', '--filter', '!child', 'memory-leak/*.js']; + const execution = await execute(command); + + assert.equal(execution.code, 0); + assert.equal(execution.stderr, ''); + assert.equal(execution.stdout, getExpected(command)); + }); + } + + QUnit.module('filter', () => { + QUnit.test('can properly filter tests', async assert => { + const command = ['qunit', '--filter', 'single', 'test', 'single.js', 'glob/**/*-test.js']; + const equivalentCommand = ['qunit', 'single.js']; + const execution = await execute(command); + + assert.equal(execution.code, 0); + assert.equal(execution.stderr, ''); + assert.equal(execution.stdout, getExpected(equivalentCommand)); + }); + + // TODO: Workaround fact that child_process.spawn() args array is a lie on Windows. + // https://github.com/nodejs/node/issues/29532 + // Can't trivially quote since that breaks Linux which would interpret quotes + // as literals. + QUnit[skipOnWinTest]('exit code is 1 when no tests match filter', async assert => { + const command = ['qunit', '--filter', 'no matches', 'test']; + try { + await execute(command); + } catch (e) { + assert.equal(e.code, 1); + assert.equal(e.stderr, ''); + assert.equal(e.stdout, getExpected(command)); + } + }); + }); + + QUnit.module('require', () => { + QUnit.test('can properly require dependencies and modules', async assert => { + const command = ['qunit', 'single.js', + '--require', 'require-dep', + '--require', './node_modules/require-dep/module.js' + ]; + const execution = await execute(command); + + assert.equal(execution.code, 0); + assert.equal(execution.stderr, ''); + assert.equal(execution.stdout, getExpected(command)); + }); + + QUnit.test('displays helpful error when failing to require a file', async assert => { + const command = ['qunit', 'single.js', '--require', 'does-not-exist-at-all']; + try { + await execute(command); + } catch (e) { + assert.equal(e.code, 1); + assert.true(e.stderr.includes("Error: Cannot find module 'does-not-exist-at-all'")); + assert.equal(e.stdout, ''); + } + }); + }); + + QUnit.module('seed', () => { + QUnit.test('can properly seed tests', async assert => { + const command = ['qunit', '--seed', 's33d', 'test', 'single.js', 'glob/**/*-test.js']; + const execution = await execute(command); + + assert.equal(execution.code, 0); + assert.equal(execution.stderr, ''); + assert.equal(execution.stdout, getExpected(command)); + }); + }); + + QUnit.module('notrycatch', () => { + QUnit.test('errors if notrycatch is used and a rejection occurs', async assert => { + try { + await execute(['qunit', 'notrycatch/returns-rejection.js']); + } catch (e) { + assert.pushResult({ + + // only in stdout due to using `console.log` in manual `unhandledRejection` handler + result: e.stdout.includes('Unhandled Rejection: bad things happen sometimes'), + actual: e.stdout + '\n' + e.stderr + }); + } + }); + + QUnit.test('errors if notrycatch is used and a rejection occurs in a hook', async assert => { + try { + await execute(['qunit', 'notrycatch/returns-rejection-in-hook.js']); + } catch (e) { + assert.pushResult({ + + // only in stdout due to using `console.log` in manual `unhandledRejection` handler + result: e.stdout.includes('Unhandled Rejection: bad things happen sometimes'), + actual: e.stdout + '\n' + e.stderr + }); + } + }); + }); + + QUnit.test('config.filter (string)', async assert => { + const command = ['qunit', 'config-filter-string.js']; + const execution = await execute(command); + + assert.equal(execution.code, 0); + assert.equal(execution.stderr, ''); + assert.equal(execution.stdout, getExpected(command)); + }); + + QUnit.test('config.filter (regex)', async assert => { + const command = ['qunit', 'config-filter-regex.js']; + const execution = await execute(command); + + assert.equal(execution.code, 0); + assert.equal(execution.stderr, ''); + assert.equal(execution.stdout, getExpected(command)); + }); + + QUnit.test('config.filter (regex exclude)', async assert => { + const command = ['qunit', 'config-filter-regex-exclude.js']; + const execution = await execute(command); + + assert.equal(execution.code, 0); + assert.equal(execution.stderr, ''); + assert.equal(execution.stdout, getExpected(command)); + }); + + QUnit.test('config.module', async assert => { + const command = ['qunit', 'config-module.js']; + const execution = await execute(command); + + assert.equal(execution.code, 0); + assert.equal(execution.stderr, ''); + assert.equal(execution.stdout, getExpected(command)); + }); + + QUnit.test('config.moduleId', async assert => { + const command = ['qunit', 'config-moduleId.js']; + const execution = await execute(command); + + assert.equal(execution.code, 0); + assert.equal(execution.stderr, ''); + assert.equal(execution.stdout, getExpected(command)); + }); + + QUnit.test('config.testId', async assert => { + const command = ['qunit', 'config-testId.js']; + const execution = await execute(command); + + assert.equal(execution.code, 0); + assert.equal(execution.stderr, ''); + assert.equal(execution.stdout, getExpected(command)); + }); + + QUnit.test('config.testTimeout', async assert => { + const command = ['qunit', 'config-testTimeout.js']; + + try { + await execute(command); + } catch (e) { + assert.equal(e.code, 1); + assert.equal(e.stderr, ''); + assert.equal(e.stdout, getExpected(command)); + } + }); + + QUnit.module('noglobals', () => { + QUnit.test('add global variable', async assert => { + try { + await execute(['qunit', 'noglobals/add-global.js']); + } catch (e) { + assert.pushResult({ + result: e.stdout.includes('message: Introduced global variable(s): dummyGlobal'), + actual: e.stdout + '\n' + e.stderr + }); + } + }); + + QUnit.test('remove global variable', async assert => { + try { + await execute(['qunit', 'noglobals/remove-global.js']); + } catch (e) { + assert.pushResult({ + result: e.stdout.includes('message: Deleted global variable(s): dummyGlobal'), + actual: e.stdout + '\n' + e.stderr + }); + } + }); + + QUnit.test('forgive qunit DOM global variables', async assert => { + const execution = await execute(['qunit', 'noglobals/ignored.js']); + assert.equal(execution.code, 0); + assert.equal(execution.stderr, ''); + }); + }); + + QUnit.module('assert.async', () => { + QUnit.test('call after tests timeout', async assert => { + const command = ['qunit', 'done-after-timeout.js']; + try { + await execute(command); + } catch (e) { + assert.equal(e.code, 1); + assert.equal(e.stdout, getExpected(command)); + } + }); + + // TODO: Figure out why trace isn't trimmed on Windows. https://github.com/qunitjs/qunit/issues/1359 + QUnit[skipOnWinTest]('drooling call to callback across tests', async assert => { + const command = ['qunit', 'drooling-done.js']; + try { + await execute(command); + } catch (e) { + assert.equal(e.code, 1); + assert.equal(e.stdout, getExpected(command)); + } + }); + + // TODO: Figure out why trace isn't trimmed on Windows. https://github.com/qunitjs/qunit/issues/1359 + QUnit[skipOnWinTest]('extra call to callback across tests', async assert => { + const command = ['qunit', 'drooling-extra-done.js']; + try { + await execute(command); + } catch (e) { + assert.equal(e.code, 1); + assert.equal(e.stdout, getExpected(command)); + } + }); + + QUnit.test('extra call to callback outside tests', async assert => { + const command = ['qunit', 'drooling-extra-done-outside.js']; + try { + await execute(command); + } catch (e) { + assert.equal(e.code, 1); + assert.equal(e.stdout, getExpected(command)); + } + }); + + // TODO: Figure out why trace isn't trimmed on Windows. https://github.com/qunitjs/qunit/issues/1359 + QUnit[skipOnWinTest]('too many calls to callback', async assert => { + const command = ['qunit', 'too-many-done-calls.js']; + try { + await execute(command); + } catch (e) { + assert.equal(e.code, 1); + assert.equal(e.stdout, getExpected(command)); + } + }); + }); + + QUnit.module('only', () => { + QUnit.test('test', async assert => { + const command = ['qunit', 'only/test.js']; + const execution = await execute(command); + + assert.equal(execution.code, 0); + assert.equal(execution.stderr, ''); + assert.equal(execution.stdout, getExpected(command)); + }); + + // TODO: Figure out why trace isn't trimmed on Windows. https://github.com/qunitjs/qunit/issues/1359 + QUnit[skipOnWinTest]('nested modules', async assert => { + const command = ['qunit', 'only/module.js']; + const execution = await execute(command); + + assert.equal(execution.code, 0); + assert.equal(execution.stderr, ''); + assert.equal(execution.stdout, getExpected(command)); + }); + + QUnit.test('module followed by test', async assert => { + const command = ['qunit', 'only/module-then-test.js']; + const execution = await execute(command); + + assert.equal(execution.code, 0); + assert.equal(execution.stderr, ''); + assert.equal(execution.stdout, getExpected(command)); + }); + + // TODO: Figure out why trace isn't trimmed on Windows. https://github.com/qunitjs/qunit/issues/1359 + QUnit[skipOnWinTest]('flat modules', async assert => { + const command = ['qunit', 'only/module-flat.js']; + const execution = await execute(command); + + assert.equal(execution.code, 0); + assert.equal(execution.stderr, ''); + assert.equal(execution.stdout, getExpected(command)); + }); + }); + + // Regression test for https://github.com/qunitjs/qunit/issues/1478 + QUnit.test('nested module scopes', async assert => { + const command = ['qunit', 'module-nested.js']; + const execution = await execute(command); + + assert.equal(execution.code, 0); + assert.equal(execution.stdout, getExpected(command)); + }); + + QUnit.test('warns about incorrect hook usage', async assert => { + const command = ['qunit', 'incorrect-hooks-warning/test.js']; + const execution = await execute(command); + + assert.equal(execution.code, 0); + assert.equal(execution.stderr, 'The `beforeEach` hook was called inside the wrong module (`module providing hooks > module not providing hooks`). Instead, use hooks provided by the callback to the containing module (`module providing hooks`). This will become an error in QUnit 3.0.', 'The warning shows'); + assert.equal(execution.stdout, getExpected(command)); + }); + + QUnit.test('warns about unsupported async module callback', async assert => { + const command = ['qunit', 'async-module-warning/test.js']; + const execution = await execute(command); + + assert.equal(execution.code, 0); + assert.equal(execution.stderr, 'Returning a promise from a module callback is not supported. Instead, use hooks for async behavior. This will become an error in QUnit 3.0.', 'The warning shows'); + assert.equal(execution.stdout, getExpected(command)); + }); + + QUnit.test('warns about unsupported promise return value from module', async assert => { + const command = ['qunit', 'async-module-warning/promise-test.js']; + const execution = await execute(command); + + assert.equal(execution.code, 0); + assert.equal(execution.stderr, 'Returning a promise from a module callback is not supported. Instead, use hooks for async behavior. This will become an error in QUnit 3.0.', 'The warning shows'); + assert.equal(execution.stdout, getExpected(command)); + }); + + QUnit.module('assert.expect failing conditions', () => { + QUnit.test('mismatched expected assertions', async assert => { + const command = ['qunit', 'assert-expect/failing-expect.js']; + try { + const result = await execute(command); + assert.pushResult({ + result: false, + actual: result.stdout + }); + } catch (e) { + assert.equal(e.code, 1); + assert.equal(e.stderr, ''); + + // can't match exactly due to stack frames including internal line numbers + assert.true(e.stdout.includes('message: Expected 2 assertions, but 1 were run'), e.stdout); + } + }); + + QUnit.test('no assertions run - use expect(0)', async assert => { + const command = ['qunit', 'assert-expect/no-assertions.js']; + try { + const result = await execute(command); + assert.pushResult({ + result: true, + actual: result.stdout + }); + } catch (e) { + assert.equal(e.code, 1); + assert.equal(e.stderr, ''); + + // can't match exactly due to stack frames including internal line numbers + assert.true(e.stdout.includes('Expected at least one assertion, but none were run - call expect(0) to accept zero assertions.'), e.stdout); + } + }); + + QUnit.test('requireExpects', async assert => { + const command = ['qunit', 'assert-expect/require-expects.js']; + try { + const result = await execute(command); + assert.pushResult({ + result: false, + actual: result.stdout + }); + } catch (e) { + assert.equal(e.code, 1); + assert.equal(e.stderr, ''); + assert.true(e.stdout.includes('message: Expected number of assertions to be defined, but expect() was not called.'), e.stdout); + } + }); + }); +}); diff --git a/test/cli/cli-watch.js b/test/cli/cli-watch.js index 2f2fbeba1..2c71a286b 100644 --- a/test/cli/cli-watch.js +++ b/test/cli/cli-watch.js @@ -1,408 +1,405 @@ -"use strict"; +'use strict'; -const fs = require( "fs" ); -const path = require( "path" ); -const fixturify = require( "fixturify" ); -const rimraf = require( "rimraf" ); +const fs = require('fs'); +const path = require('path'); +const fixturify = require('fixturify'); +const rimraf = require('rimraf'); -const expectedWatchOutput = require( "./fixtures/expected/watch-tap-outputs" ); -const { execute } = require( "./helpers/execute" ); +const expectedWatchOutput = require('./fixtures/expected/watch-tap-outputs'); +const { execute } = require('./helpers/execute'); // Executes the provided command from within the fixtures directory -function executeIpc( command, hook ) { - return execute( command, { stdio: [ null, null, null, "ipc" ] }, hook ); +function executeIpc (command, hook) { + return execute(command, { stdio: [null, null, null, 'ipc'] }, hook); } -const fixturePath = path.join( __dirname, "fixtures", "watching" ); +const fixturePath = path.join(__dirname, 'fixtures', 'watching'); // Kills the provided executing process, handling differences in platforms -function kill( execution, signal ) { - const sig = signal || "SIGINT"; +function kill (execution, signal) { + const sig = signal || 'SIGINT'; - process.kill( execution.pid, sig ); + process.kill(execution.pid, sig); } // TODO: Make watch tests work on Windows. https://github.com/qunitjs/qunit/issues/1359 -if ( process.platform === "win32" ) { - return; +if (process.platform === 'win32') { + return; } -QUnit.module( "CLI Watch", function( hooks ) { - hooks.before( function() { - rimraf.sync( fixturePath ); - } ); - - hooks.beforeEach( function() { - fs.mkdirSync( path.dirname( fixturePath ), { recursive: true } ); - fixturify.writeSync( fixturePath, { - "setup.js": "QUnit.on('runEnd', function() { process.send('runEnd'); });" - } ); - } ); - - hooks.afterEach( function() { - rimraf.sync( fixturePath ); - } ); - - QUnit.test( "runs tests and waits until SIGTERM", async assert => { - fixturify.writeSync( fixturePath, { - "foo.js": "QUnit.test('foo', function(assert) { assert.true(true); });" - } ); - - const command = [ "qunit", "--watch", "watching" ]; - const result = await executeIpc( - command, - execution => { - execution.on( "message", data => { - assert.step( data ); - kill( execution, "SIGTERM" ); - } ); - } - ); - - assert.verifySteps( [ "runEnd" ] ); - assert.equal( result.code, 0 ); - assert.equal( result.stderr, "" ); - assert.equal( result.stdout, expectedWatchOutput[ "no-change" ] ); - } ); - - QUnit.test( "runs tests and waits until SIGINT", async assert => { - fixturify.writeSync( fixturePath, { - "foo.js": "QUnit.test('foo', function(assert) { assert.true(true); });" - } ); - - const command = [ "qunit", "--watch", "watching" ]; - const result = await executeIpc( - command, - execution => { - execution.on( "message", data => { - assert.step( data ); - kill( execution ); - } ); - } - ); - - assert.verifySteps( [ "runEnd" ] ); - assert.equal( result.code, 0 ); - assert.equal( result.stderr, "" ); - assert.equal( result.stdout, expectedWatchOutput[ "no-change" ] ); - } ); - - QUnit.test( "re-runs tests on file changed", async assert => { - fixturify.writeSync( fixturePath, { - "foo.js": "QUnit.test('foo', function(assert) { assert.true(true); });" - } ); - - const command = [ "qunit", "--watch", "watching" ]; - const result = await executeIpc( - command, - execution => { - execution.once( "message", data => { - assert.step( data ); - fixturify.writeSync( fixturePath, { - "foo.js": "QUnit.test('bar', function(assert) { assert.true(true); });" - } ); - - execution.once( "message", data => { - assert.step( data ); - kill( execution ); - } ); - } ); - } - ); - - assert.verifySteps( [ "runEnd", "runEnd" ] ); - assert.equal( result.code, 0 ); - assert.equal( result.stderr, "" ); - assert.equal( result.stdout, expectedWatchOutput[ "change-file" ] ); - } ); - - QUnit.test( "re-runs tests on file added", async assert => { - fixturify.writeSync( fixturePath, { - "foo.js": "QUnit.test('foo', function(assert) { assert.true(true); });" - } ); - - const command = [ "qunit", "--watch", "watching" ]; - const result = await executeIpc( - command, - execution => { - execution.once( "message", data => { - assert.step( data ); - fixturify.writeSync( fixturePath, { - "bar.js": "QUnit.test('bar', function(assert) { assert.true(true); });" - } ); - - execution.once( "message", data => { - assert.step( data ); - kill( execution ); - } ); - } ); - } - ); - - assert.verifySteps( [ "runEnd", "runEnd" ] ); - assert.equal( result.code, 0 ); - assert.equal( result.stderr, "" ); - assert.equal( result.stdout, expectedWatchOutput[ "add-file" ] ); - } ); - - QUnit.test( "re-runs tests on file removed", async assert => { - fixturify.writeSync( fixturePath, { - "foo.js": "QUnit.test('foo', function(assert) { assert.true(true); });", - "bar.js": "QUnit.test('bar', function(assert) { assert.true(true); });" - } ); - - const command = [ "qunit", "--watch", "watching" ]; - const result = await executeIpc( - command, - execution => { - execution.once( "message", data => { - assert.step( data ); - fixturify.writeSync( fixturePath, { - "bar.js": null - } ); - - execution.once( "message", data => { - assert.step( data ); - kill( execution ); - } ); - } ); - } - ); - - assert.verifySteps( [ "runEnd", "runEnd" ] ); - assert.equal( result.code, 0 ); - assert.equal( result.stderr, "" ); - assert.equal( result.stdout, expectedWatchOutput[ "remove-file" ] ); - } ); - - // Skip in coverage mode since NYC adds non-default extensions - QUnit[ process.env.NYC_PROCESS_ID ? "skip" : "test" ]( "default file extensions", async assert => { - fixturify.writeSync( fixturePath, { - "tests": { - "setup.js": "QUnit.on('runEnd', function() { process.send('runEnd'); });", - "foo.js": "QUnit.test('foo', function(assert) { assert.true(true); });" - } - } ); - - const command = [ "qunit", "--watch", "watching/tests" ]; - const result = await executeIpc( - command, - execution => { - execution.once( "message", () => { - fixturify.writeSync( fixturePath, { - "x.cjs": "-", - "x.js": "-", - "x.json": "-", - "x.mjs": "-", - "x.ts": "-", - "x.txt": "-", - - "node_modules": { - "x": { - "y.js": "-" - } - }, - - "tests": { - "foo.js": "QUnit.test('foo2', function(assert) { assert.true(true); });", - "setup.js": "QUnit.on('runEnd', function() { process.send('runEnd2'); });" - } - } ); - - execution.once( "message", data => { - - // Ignore other re-runs - if ( data === "runEnd2" ) { - kill( execution ); - } - } ); - } ); - } - ); - - assert.equal( result.code, 0 ); - assert.equal( result.stderr, "" ); - assert.equal( result.stdout, expectedWatchOutput[ "file-extensions" ] ); - } ); - - // Skip in coverage mode since NYC adds non-default extensions - QUnit[ process.env.NYC_PROCESS_ID ? "skip" : "test" ]( "TypeScript file extension", async assert => { - fixturify.writeSync( fixturePath, { - - // Simulate what ts-node/register does - "register.js": "require.extensions['.ts'] = function() {};", - "tests": { - "setup.js": "QUnit.on('runEnd', function() { process.send('runEnd'); });", - "foo.js": "QUnit.test('foo', function(assert) { assert.true(true); });" - } - } ); - - const command = [ "qunit", "--watch", "--require", "./watching/register", "watching/tests" ]; - const result = await executeIpc( - command, - execution => { - execution.once( "message", () => { - fixturify.writeSync( fixturePath, { - "x.js": "-", - "x.ts": "-", - "tests": { - "foo.js": "QUnit.test('foo2', function(assert) { assert.true(true); });", - "setup.js": "QUnit.on('runEnd', function() { process.send('runEnd2'); });" - } - } ); - - execution.once( "message", () => { - kill( execution ); - } ); - } ); - } - ); - - assert.equal( result.code, 0 ); - assert.equal( result.stderr, "" ); - assert.equal( result.stdout, expectedWatchOutput[ "file-extension-ts" ] ); - } ); - - QUnit.test( "aborts and restarts when in middle of run", async assert => { - - // A proper abort finishes the currently running test and runs any remaining - // afterEach/after hooks to ensure cleanup happens. - - fixturify.writeSync( fixturePath, { - "tests": { - "setup.js": "QUnit.on('runEnd', function() { process.send('runEnd'); });", - "foo.js": ` - process.send(require('../bar')); - QUnit.module('Foo', { - before() { process.send('before'); }, - beforeEach() { process.send('beforeEach'); }, - afterEach() { process.send('afterEach'); }, - after() { process.send('after'); } - }); - QUnit.test('one', function(assert) { - process.send('testRunning'); - var done = assert.async(); - setTimeout(function() { - assert.true(true); - done(); - }, 500); - }); - QUnit.test('two', function(assert) { assert.true(true); });` - }, - "bar.js": "module.exports = 'bar export first';" - } ); - - const command = [ "qunit", "--watch", "watching/tests" ]; - const result = await executeIpc( - command, - execution => { - execution.on( "message", function handle( data ) { - if ( data === "testRunning" ) { - fixturify.writeSync( fixturePath, { - "bar.js": "module.exports = 'bar export second';" - } ); - } - - assert.step( data ); - - if ( data === "runEnd" ) { - execution.off( "message", handle ); - execution.on( "message", data => { - assert.step( data ); - - if ( data === "runEnd" ) { - kill( execution ); - } - } ); - } - } ); - } - ); - - assert.verifySteps( [ - "bar export first", - "before", - "beforeEach", - "testRunning", - "afterEach", - "after", - "runEnd", - "bar export second", - "before", - "beforeEach", - "testRunning", - "afterEach", - "beforeEach", - "afterEach", - "after", - "runEnd" - ] ); - assert.equal( result.code, 0 ); - assert.equal( result.stderr, "" ); - assert.equal( result.stdout, expectedWatchOutput[ "change-file-mid-run" ] ); - } ); - - QUnit.test( "properly watches files after initial run", async assert => { - - fixturify.writeSync( fixturePath, { - "tests": { - "setup.js": "QUnit.on('runEnd', function() { process.send('runEnd'); });", - "foo.js": ` - QUnit.module('Module'); - QUnit.test('Test', function(assert) { - assert.true(true); - });` - } - } ); - - let count = 0; - const command = [ "qunit", "--watch", "watching/tests" ]; - const result = await executeIpc( - command, - execution => { - execution.on( "message", data => { - assert.step( data ); - - if ( data === "runEnd" ) { - count++; - - if ( count === 1 ) { - fixturify.writeSync( fixturePath, { - "tests": { - "foo.js": ` - process.send(require('../bar.js')); - QUnit.module('Module'); - QUnit.test('Test', function(assert) { - assert.true(true); - });` - }, - "bar.js": "module.exports = 'bar export first';" - } ); - } - - if ( count === 2 ) { - fixturify.writeSync( fixturePath, { - "bar.js": "module.exports = 'bar export second';" - } ); - } - - if ( count === 3 ) { - kill( execution ); - } - } - } ); - } - ); - - assert.verifySteps( [ - "runEnd", - "bar export first", - "runEnd", - "bar export second", - "runEnd" - ] ); - assert.equal( result.code, 0 ); - assert.equal( result.stderr, "" ); - assert.equal( result.stdout, expectedWatchOutput[ "add-file-after-run" ] ); - } ); -} ); +QUnit.module('CLI Watch', function (hooks) { + hooks.before(function () { + rimraf.sync(fixturePath); + }); + + hooks.beforeEach(function () { + fs.mkdirSync(path.dirname(fixturePath), { recursive: true }); + fixturify.writeSync(fixturePath, { + 'setup.js': "QUnit.on('runEnd', function() { process.send('runEnd'); });" + }); + }); + + hooks.afterEach(function () { + rimraf.sync(fixturePath); + }); + + QUnit.test('runs tests and waits until SIGTERM', async assert => { + fixturify.writeSync(fixturePath, { + 'foo.js': "QUnit.test('foo', function(assert) { assert.true(true); });" + }); + + const command = ['qunit', '--watch', 'watching']; + const result = await executeIpc( + command, + execution => { + execution.on('message', data => { + assert.step(data); + kill(execution, 'SIGTERM'); + }); + } + ); + + assert.verifySteps(['runEnd']); + assert.equal(result.code, 0); + assert.equal(result.stderr, ''); + assert.equal(result.stdout, expectedWatchOutput['no-change']); + }); + + QUnit.test('runs tests and waits until SIGINT', async assert => { + fixturify.writeSync(fixturePath, { + 'foo.js': "QUnit.test('foo', function(assert) { assert.true(true); });" + }); + + const command = ['qunit', '--watch', 'watching']; + const result = await executeIpc( + command, + execution => { + execution.on('message', data => { + assert.step(data); + kill(execution); + }); + } + ); + + assert.verifySteps(['runEnd']); + assert.equal(result.code, 0); + assert.equal(result.stderr, ''); + assert.equal(result.stdout, expectedWatchOutput['no-change']); + }); + + QUnit.test('re-runs tests on file changed', async assert => { + fixturify.writeSync(fixturePath, { + 'foo.js': "QUnit.test('foo', function(assert) { assert.true(true); });" + }); + + const command = ['qunit', '--watch', 'watching']; + const result = await executeIpc( + command, + execution => { + execution.once('message', data => { + assert.step(data); + fixturify.writeSync(fixturePath, { + 'foo.js': "QUnit.test('bar', function(assert) { assert.true(true); });" + }); + + execution.once('message', data => { + assert.step(data); + kill(execution); + }); + }); + } + ); + + assert.verifySteps(['runEnd', 'runEnd']); + assert.equal(result.code, 0); + assert.equal(result.stderr, ''); + assert.equal(result.stdout, expectedWatchOutput['change-file']); + }); + + QUnit.test('re-runs tests on file added', async assert => { + fixturify.writeSync(fixturePath, { + 'foo.js': "QUnit.test('foo', function(assert) { assert.true(true); });" + }); + + const command = ['qunit', '--watch', 'watching']; + const result = await executeIpc( + command, + execution => { + execution.once('message', data => { + assert.step(data); + fixturify.writeSync(fixturePath, { + 'bar.js': "QUnit.test('bar', function(assert) { assert.true(true); });" + }); + + execution.once('message', data => { + assert.step(data); + kill(execution); + }); + }); + } + ); + + assert.verifySteps(['runEnd', 'runEnd']); + assert.equal(result.code, 0); + assert.equal(result.stderr, ''); + assert.equal(result.stdout, expectedWatchOutput['add-file']); + }); + + QUnit.test('re-runs tests on file removed', async assert => { + fixturify.writeSync(fixturePath, { + 'foo.js': "QUnit.test('foo', function(assert) { assert.true(true); });", + 'bar.js': "QUnit.test('bar', function(assert) { assert.true(true); });" + }); + + const command = ['qunit', '--watch', 'watching']; + const result = await executeIpc( + command, + execution => { + execution.once('message', data => { + assert.step(data); + fixturify.writeSync(fixturePath, { + 'bar.js': null + }); + + execution.once('message', data => { + assert.step(data); + kill(execution); + }); + }); + } + ); + + assert.verifySteps(['runEnd', 'runEnd']); + assert.equal(result.code, 0); + assert.equal(result.stderr, ''); + assert.equal(result.stdout, expectedWatchOutput['remove-file']); + }); + + // Skip in coverage mode since NYC adds non-default extensions + QUnit[process.env.NYC_PROCESS_ID ? 'skip' : 'test']('default file extensions', async assert => { + fixturify.writeSync(fixturePath, { + tests: { + 'setup.js': "QUnit.on('runEnd', function() { process.send('runEnd'); });", + 'foo.js': "QUnit.test('foo', function(assert) { assert.true(true); });" + } + }); + + const command = ['qunit', '--watch', 'watching/tests']; + const result = await executeIpc( + command, + execution => { + execution.once('message', () => { + fixturify.writeSync(fixturePath, { + 'x.cjs': '-', + 'x.js': '-', + 'x.json': '-', + 'x.mjs': '-', + 'x.ts': '-', + 'x.txt': '-', + + node_modules: { + x: { + 'y.js': '-' + } + }, + + tests: { + 'foo.js': "QUnit.test('foo2', function(assert) { assert.true(true); });", + 'setup.js': "QUnit.on('runEnd', function() { process.send('runEnd2'); });" + } + }); + + execution.once('message', data => { + // Ignore other re-runs + if (data === 'runEnd2') { + kill(execution); + } + }); + }); + } + ); + + assert.equal(result.code, 0); + assert.equal(result.stderr, ''); + assert.equal(result.stdout, expectedWatchOutput['file-extensions']); + }); + + // Skip in coverage mode since NYC adds non-default extensions + QUnit[process.env.NYC_PROCESS_ID ? 'skip' : 'test']('TypeScript file extension', async assert => { + fixturify.writeSync(fixturePath, { + + // Simulate what ts-node/register does + 'register.js': "require.extensions['.ts'] = function() {};", + tests: { + 'setup.js': "QUnit.on('runEnd', function() { process.send('runEnd'); });", + 'foo.js': "QUnit.test('foo', function(assert) { assert.true(true); });" + } + }); + + const command = ['qunit', '--watch', '--require', './watching/register', 'watching/tests']; + const result = await executeIpc( + command, + execution => { + execution.once('message', () => { + fixturify.writeSync(fixturePath, { + 'x.js': '-', + 'x.ts': '-', + tests: { + 'foo.js': "QUnit.test('foo2', function(assert) { assert.true(true); });", + 'setup.js': "QUnit.on('runEnd', function() { process.send('runEnd2'); });" + } + }); + + execution.once('message', () => { + kill(execution); + }); + }); + } + ); + + assert.equal(result.code, 0); + assert.equal(result.stderr, ''); + assert.equal(result.stdout, expectedWatchOutput['file-extension-ts']); + }); + + QUnit.test('aborts and restarts when in middle of run', async assert => { + // A proper abort finishes the currently running test and runs any remaining + // afterEach/after hooks to ensure cleanup happens. + + fixturify.writeSync(fixturePath, { + tests: { + 'setup.js': "QUnit.on('runEnd', function() { process.send('runEnd'); });", + 'foo.js': ` + process.send(require('../bar')); + QUnit.module('Foo', { + before() { process.send('before'); }, + beforeEach() { process.send('beforeEach'); }, + afterEach() { process.send('afterEach'); }, + after() { process.send('after'); } + }); + QUnit.test('one', function(assert) { + process.send('testRunning'); + var done = assert.async(); + setTimeout(function() { + assert.true(true); + done(); + }, 500); + }); + QUnit.test('two', function(assert) { assert.true(true); });` + }, + 'bar.js': "module.exports = 'bar export first';" + }); + + const command = ['qunit', '--watch', 'watching/tests']; + const result = await executeIpc( + command, + execution => { + execution.on('message', function handle (data) { + if (data === 'testRunning') { + fixturify.writeSync(fixturePath, { + 'bar.js': "module.exports = 'bar export second';" + }); + } + + assert.step(data); + + if (data === 'runEnd') { + execution.off('message', handle); + execution.on('message', data => { + assert.step(data); + + if (data === 'runEnd') { + kill(execution); + } + }); + } + }); + } + ); + + assert.verifySteps([ + 'bar export first', + 'before', + 'beforeEach', + 'testRunning', + 'afterEach', + 'after', + 'runEnd', + 'bar export second', + 'before', + 'beforeEach', + 'testRunning', + 'afterEach', + 'beforeEach', + 'afterEach', + 'after', + 'runEnd' + ]); + assert.equal(result.code, 0); + assert.equal(result.stderr, ''); + assert.equal(result.stdout, expectedWatchOutput['change-file-mid-run']); + }); + + QUnit.test('properly watches files after initial run', async assert => { + fixturify.writeSync(fixturePath, { + tests: { + 'setup.js': "QUnit.on('runEnd', function() { process.send('runEnd'); });", + 'foo.js': ` + QUnit.module('Module'); + QUnit.test('Test', function(assert) { + assert.true(true); + });` + } + }); + + let count = 0; + const command = ['qunit', '--watch', 'watching/tests']; + const result = await executeIpc( + command, + execution => { + execution.on('message', data => { + assert.step(data); + + if (data === 'runEnd') { + count++; + + if (count === 1) { + fixturify.writeSync(fixturePath, { + tests: { + 'foo.js': ` + process.send(require('../bar.js')); + QUnit.module('Module'); + QUnit.test('Test', function(assert) { + assert.true(true); + });` + }, + 'bar.js': "module.exports = 'bar export first';" + }); + } + + if (count === 2) { + fixturify.writeSync(fixturePath, { + 'bar.js': "module.exports = 'bar export second';" + }); + } + + if (count === 3) { + kill(execution); + } + } + }); + } + ); + + assert.verifySteps([ + 'runEnd', + 'bar export first', + 'runEnd', + 'bar export second', + 'runEnd' + ]); + assert.equal(result.code, 0); + assert.equal(result.stderr, ''); + assert.equal(result.stdout, expectedWatchOutput['add-file-after-run']); + }); +}); diff --git a/test/cli/custom-reporter.js b/test/cli/custom-reporter.js index b6421b638..316bc53f3 100644 --- a/test/cli/custom-reporter.js +++ b/test/cli/custom-reporter.js @@ -1,71 +1,71 @@ -const NPMReporter = require( "npm-reporter" ); +const NPMReporter = require('npm-reporter'); -const findReporter = require( "../../src/cli/find-reporter" ).findReporter; +const findReporter = require('../../src/cli/find-reporter').findReporter; -const expectedOutput = require( "./fixtures/expected/tap-outputs" ); -const { execute, prettyPrintCommand } = require( "./helpers/execute" ); +const expectedOutput = require('./fixtures/expected/tap-outputs'); +const { execute, prettyPrintCommand } = require('./helpers/execute'); -function getExpected( command ) { - return expectedOutput[ prettyPrintCommand( command ) ]; +function getExpected (command) { + return expectedOutput[prettyPrintCommand(command)]; } -QUnit.module( "find-reporter", function() { - QUnit.test( "tap reporter is bundled", function( assert ) { - assert.strictEqual( typeof QUnit.reporters.tap, "function" ); - } ); +QUnit.module('find-reporter', function () { + QUnit.test('tap reporter is bundled', function (assert) { + assert.strictEqual(typeof QUnit.reporters.tap, 'function'); + }); - QUnit.test( "find console reporter", function( assert ) { - const reporter = findReporter( "console", QUnit.reporters ); - assert.strictEqual( reporter, QUnit.reporters.console ); - } ); + QUnit.test('find console reporter', function (assert) { + const reporter = findReporter('console', QUnit.reporters); + assert.strictEqual(reporter, QUnit.reporters.console); + }); - QUnit.test( "find tap reporter", function( assert ) { - const reporter = findReporter( "tap", QUnit.reporters ); - assert.strictEqual( reporter, QUnit.reporters.tap ); - } ); + QUnit.test('find tap reporter', function (assert) { + const reporter = findReporter('tap', QUnit.reporters); + assert.strictEqual(reporter, QUnit.reporters.tap); + }); - QUnit.test( "default to tap reporter", function( assert ) { - const reporter = findReporter( undefined, QUnit.reporters ); - assert.strictEqual( reporter, QUnit.reporters.tap ); - } ); + QUnit.test('default to tap reporter', function (assert) { + const reporter = findReporter(undefined, QUnit.reporters); + assert.strictEqual(reporter, QUnit.reporters.tap); + }); - QUnit.test( "find extra reporter package", function( assert ) { - const reporter = findReporter( "npm-reporter", QUnit.reporters ); - assert.strictEqual( reporter, NPMReporter ); - } ); -} ); + QUnit.test('find extra reporter package', function (assert) { + const reporter = findReporter('npm-reporter', QUnit.reporters); + assert.strictEqual(reporter, NPMReporter); + }); +}); -QUnit.module( "CLI Reporter", function() { - QUnit.test( "runs tests using the specified reporter", async function( assert ) { - const command = [ "qunit", "--reporter", "npm-reporter" ]; - const execution = await execute( command ); +QUnit.module('CLI Reporter', function () { + QUnit.test('runs tests using the specified reporter', async function (assert) { + const command = ['qunit', '--reporter', 'npm-reporter']; + const execution = await execute(command); - assert.equal( execution.code, 0 ); - assert.equal( execution.stderr, "" ); - assert.equal( execution.stdout, getExpected( command ) ); - } ); + assert.equal(execution.code, 0); + assert.equal(execution.stderr, ''); + assert.equal(execution.stdout, getExpected(command)); + }); - QUnit.test( "exits early and lists available reporters if reporter is not found", async function( assert ) { - const command = [ "qunit", "--reporter", "does-not-exist" ]; + QUnit.test('exits early and lists available reporters if reporter is not found', async function (assert) { + const command = ['qunit', '--reporter', 'does-not-exist']; - try { - await execute( command ); - } catch ( e ) { - assert.equal( e.code, 1 ); - assert.equal( e.stderr, getExpected( command ) ); - assert.equal( e.stdout, "" ); - } - } ); + try { + await execute(command); + } catch (e) { + assert.equal(e.code, 1); + assert.equal(e.stderr, getExpected(command)); + assert.equal(e.stdout, ''); + } + }); - QUnit.test( "exits early and lists available reporters if reporter option is used with no value", async function( assert ) { - const command = [ "qunit", "--reporter" ]; + QUnit.test('exits early and lists available reporters if reporter option is used with no value', async function (assert) { + const command = ['qunit', '--reporter']; - try { - await execute( command ); - } catch ( e ) { - assert.equal( e.code, 1 ); - assert.equal( e.stderr, getExpected( command ) ); - assert.equal( e.stdout, "" ); - } - } ); -} ); + try { + await execute(command); + } catch (e) { + assert.equal(e.code, 1); + assert.equal(e.stderr, getExpected(command)); + assert.equal(e.stdout, ''); + } + }); +}); diff --git a/test/cli/events.js b/test/cli/events.js index 1957e82a2..74b283d1f 100644 --- a/test/cli/events.js +++ b/test/cli/events.js @@ -5,283 +5,281 @@ */ const invokedHooks = []; const invokedHookDetails = {}; -const requireQUnit = require( "../../src/cli/require-qunit" ); +const requireQUnit = require('../../src/cli/require-qunit'); const myQUnit = requireQUnit(); -function callback( name ) { - invokedHookDetails[ name ] = []; +function callback (name) { + invokedHookDetails[name] = []; - return function( details ) { - invokedHooks.push( name ); - invokedHookDetails[ name ].push( details ); - }; + return function (details) { + invokedHooks.push(name); + invokedHookDetails[name].push(details); + }; } -myQUnit.on( "runStart", callback( "runStart" ) ); -myQUnit.on( "suiteStart", callback( "suiteStart" ) ); -myQUnit.on( "testStart", callback( "testStart" ) ); -myQUnit.on( "assertion", callback( "assertion1" ) ); -myQUnit.on( "assertion", callback( "assertion2" ) ); -myQUnit.on( "testEnd", callback( "testEnd" ) ); -myQUnit.on( "suiteEnd", callback( "suiteEnd" ) ); -myQUnit.on( "runEnd", callback( "runEnd" ) ); - -myQUnit.module( "Events", function() { - myQUnit.module( "Nested", function() { - myQUnit.todo( "test1", function( assert ) { - assert.true( false, "failing assertion" ); - } ); - } ); - - myQUnit.test( "test2", function( assert ) { - assert.true( true, "passing assertion" ); - } ); - - myQUnit.skip( "test3" ); -} ); - -const myQunitRun = new Promise( resolve => { - myQUnit.on( "runEnd", resolve ); -} ); +myQUnit.on('runStart', callback('runStart')); +myQUnit.on('suiteStart', callback('suiteStart')); +myQUnit.on('testStart', callback('testStart')); +myQUnit.on('assertion', callback('assertion1')); +myQUnit.on('assertion', callback('assertion2')); +myQUnit.on('testEnd', callback('testEnd')); +myQUnit.on('suiteEnd', callback('suiteEnd')); +myQUnit.on('runEnd', callback('runEnd')); + +myQUnit.module('Events', function () { + myQUnit.module('Nested', function () { + myQUnit.todo('test1', function (assert) { + assert.true(false, 'failing assertion'); + }); + }); + + myQUnit.test('test2', function (assert) { + assert.true(true, 'passing assertion'); + }); + + myQUnit.skip('test3'); +}); + +const myQunitRun = new Promise(resolve => { + myQUnit.on('runEnd', resolve); +}); myQUnit.start(); var test1Start = { - name: "test1", - fullName: [ "Events", "Nested", "test1" ], - suiteName: "Nested" + name: 'test1', + fullName: ['Events', 'Nested', 'test1'], + suiteName: 'Nested' }; var test2Start = { - name: "test2", - fullName: [ "Events", "test2" ], - suiteName: "Events" + name: 'test2', + fullName: ['Events', 'test2'], + suiteName: 'Events' }; var test3Start = { - name: "test3", - fullName: [ "Events", "test3" ], - suiteName: "Events" + name: 'test3', + fullName: ['Events', 'test3'], + suiteName: 'Events' }; var suite2Start = { - name: "Nested", - fullName: [ "Events", "Nested" ], - childSuites: [], - testCounts: { - total: 1 - }, - tests: [ - test1Start - ] + name: 'Nested', + fullName: ['Events', 'Nested'], + childSuites: [], + testCounts: { + total: 1 + }, + tests: [ + test1Start + ] }; var suite1Start = { - name: "Events", - fullName: [ "Events" ], - childSuites: [ - suite2Start - ], - testCounts: { - total: 3 - }, - tests: [ - test2Start, - test3Start - ] + name: 'Events', + fullName: ['Events'], + childSuites: [ + suite2Start + ], + testCounts: { + total: 3 + }, + tests: [ + test2Start, + test3Start + ] }; var assertion1 = { - passed: false, - message: "failing assertion", - todo: true + passed: false, + message: 'failing assertion', + todo: true }; var assertion2 = { - passed: true, - message: "passing assertion", - todo: false + passed: true, + message: 'passing assertion', + todo: false }; var test1End = { - name: "test1", - fullName: [ "Events", "Nested", "test1" ], - suiteName: "Nested", - status: "todo", - errors: [ - assertion1 - ], - assertions: [ - assertion1 - ] + name: 'test1', + fullName: ['Events', 'Nested', 'test1'], + suiteName: 'Nested', + status: 'todo', + errors: [ + assertion1 + ], + assertions: [ + assertion1 + ] }; var test2End = { - name: "test2", - fullName: [ "Events", "test2" ], - suiteName: "Events", - status: "passed", - errors: [], - assertions: [ - assertion2 - ] + name: 'test2', + fullName: ['Events', 'test2'], + suiteName: 'Events', + status: 'passed', + errors: [], + assertions: [ + assertion2 + ] }; var test3End = { - name: "test3", - fullName: [ "Events", "test3" ], - suiteName: "Events", - status: "skipped", - errors: [], - assertions: [] + name: 'test3', + fullName: ['Events', 'test3'], + suiteName: 'Events', + status: 'skipped', + errors: [], + assertions: [] }; var suite2End = { - name: "Nested", - fullName: [ "Events", "Nested" ], - status: "todo", - childSuites: [], - testCounts: { - skipped: 0, - passed: 0, - failed: 0, - todo: 1, - total: 1 - }, - tests: [ - test1End - ] + name: 'Nested', + fullName: ['Events', 'Nested'], + status: 'todo', + childSuites: [], + testCounts: { + skipped: 0, + passed: 0, + failed: 0, + todo: 1, + total: 1 + }, + tests: [ + test1End + ] }; var suite1End = { - name: "Events", - fullName: [ "Events" ], - status: "passed", - childSuites: [ - suite2End - ], - testCounts: { - skipped: 1, - passed: 1, - failed: 0, - todo: 1, - total: 3 - }, - tests: [ - test2End, - test3End - ] + name: 'Events', + fullName: ['Events'], + status: 'passed', + childSuites: [ + suite2End + ], + testCounts: { + skipped: 1, + passed: 1, + failed: 0, + todo: 1, + total: 3 + }, + tests: [ + test2End, + test3End + ] }; -function removeUnstableProperties( obj ) { - if ( typeof obj !== "object" ) { - return obj; - } +function removeUnstableProperties (obj) { + if (typeof obj !== 'object') { + return obj; + } - delete obj.runtime; - delete obj.stack; + delete obj.runtime; + delete obj.stack; - Object.keys( obj ).forEach( function( key ) { - if ( Array.isArray( obj[ key ] ) ) { - obj[ key ].forEach( removeUnstableProperties ); - } else if ( typeof obj[ key ] === "object" ) { - removeUnstableProperties( obj[ key ] ); - } - } ); + Object.keys(obj).forEach(function (key) { + if (Array.isArray(obj[key])) { + obj[key].forEach(removeUnstableProperties); + } else if (typeof obj[key] === 'object') { + removeUnstableProperties(obj[key]); + } + }); - return obj; + return obj; } // After all the tests run, we verify the events were fired in the correct order // with the correct data -QUnit.module( "Events", function() { - - QUnit.test( "verify callback order and data at end of test", async assert => { - - await myQunitRun; - - assert.deepEqual( invokedHooks, [ - "runStart", - "suiteStart", - "suiteStart", - "testStart", - "assertion1", - "assertion2", - "testEnd", - "suiteEnd", - "testStart", - "assertion1", - "assertion2", - "testEnd", - "testStart", - "testEnd", - "suiteEnd", - "runEnd" - ], "event callbacks are called in correct order" ); - - assert.deepEqual( - invokedHookDetails.suiteStart[ 0 ], - suite1Start, - "start of suite with tests and child suites data is correct" - ); - assert.deepEqual( - invokedHookDetails.suiteStart[ 1 ], - suite2Start, - "start of child suite with tests data is correct" - ); - - assert.deepEqual( - invokedHookDetails.testStart[ 0 ], - test1Start, - "regular testStart data is correct" - ); - assert.deepEqual( - invokedHookDetails.testStart[ 1 ], - test2Start, - "skipped testStart data is correct" - ); - assert.deepEqual( - invokedHookDetails.testStart[ 2 ], - test3Start, - "todo testStart data is correct" - ); - - assert.deepEqual( - removeUnstableProperties( invokedHookDetails.assertion1[ 0 ] ), - assertion1, - "failing assertion data is correct" - ); - assert.deepEqual( - removeUnstableProperties( invokedHookDetails.assertion1[ 1 ] ), - assertion2, - "passing assertion data is correct" - ); - - // These are pushed in reverse order of the starts - assert.deepEqual( - removeUnstableProperties( invokedHookDetails.testEnd[ 0 ] ), - test1End, - "todo testEnd data is correct" - ); - assert.deepEqual( - removeUnstableProperties( invokedHookDetails.testEnd[ 1 ] ), - test2End, - "regular testEnd data is correct" - ); - assert.deepEqual( - removeUnstableProperties( invokedHookDetails.testEnd[ 2 ] ), - test3End, - "skipped testEnd data is correct" - ); - - assert.deepEqual( - removeUnstableProperties( invokedHookDetails.suiteEnd[ 0 ] ), - suite2End, - "end of child suite with tests data is correct" - ); - assert.deepEqual( - removeUnstableProperties( invokedHookDetails.suiteEnd[ 1 ] ), - suite1End, - "end of suite with tests and child suites data is correct" - ); - } ); -} ); +QUnit.module('Events', function () { + QUnit.test('verify callback order and data at end of test', async assert => { + await myQunitRun; + + assert.deepEqual(invokedHooks, [ + 'runStart', + 'suiteStart', + 'suiteStart', + 'testStart', + 'assertion1', + 'assertion2', + 'testEnd', + 'suiteEnd', + 'testStart', + 'assertion1', + 'assertion2', + 'testEnd', + 'testStart', + 'testEnd', + 'suiteEnd', + 'runEnd' + ], 'event callbacks are called in correct order'); + + assert.deepEqual( + invokedHookDetails.suiteStart[0], + suite1Start, + 'start of suite with tests and child suites data is correct' + ); + assert.deepEqual( + invokedHookDetails.suiteStart[1], + suite2Start, + 'start of child suite with tests data is correct' + ); + + assert.deepEqual( + invokedHookDetails.testStart[0], + test1Start, + 'regular testStart data is correct' + ); + assert.deepEqual( + invokedHookDetails.testStart[1], + test2Start, + 'skipped testStart data is correct' + ); + assert.deepEqual( + invokedHookDetails.testStart[2], + test3Start, + 'todo testStart data is correct' + ); + + assert.deepEqual( + removeUnstableProperties(invokedHookDetails.assertion1[0]), + assertion1, + 'failing assertion data is correct' + ); + assert.deepEqual( + removeUnstableProperties(invokedHookDetails.assertion1[1]), + assertion2, + 'passing assertion data is correct' + ); + + // These are pushed in reverse order of the starts + assert.deepEqual( + removeUnstableProperties(invokedHookDetails.testEnd[0]), + test1End, + 'todo testEnd data is correct' + ); + assert.deepEqual( + removeUnstableProperties(invokedHookDetails.testEnd[1]), + test2End, + 'regular testEnd data is correct' + ); + assert.deepEqual( + removeUnstableProperties(invokedHookDetails.testEnd[2]), + test3End, + 'skipped testEnd data is correct' + ); + + assert.deepEqual( + removeUnstableProperties(invokedHookDetails.suiteEnd[0]), + suite2End, + 'end of child suite with tests data is correct' + ); + assert.deepEqual( + removeUnstableProperties(invokedHookDetails.suiteEnd[1]), + suite1End, + 'end of suite with tests and child suites data is correct' + ); + }); +}); diff --git a/test/cli/fixtures/assert-expect/failing-expect.js b/test/cli/fixtures/assert-expect/failing-expect.js index 2799d0885..8d31bffb8 100644 --- a/test/cli/fixtures/assert-expect/failing-expect.js +++ b/test/cli/fixtures/assert-expect/failing-expect.js @@ -1,4 +1,4 @@ -QUnit.test( "failing test", assert => { - assert.expect( 2 ); - assert.true( true ); -} ); +QUnit.test('failing test', assert => { + assert.expect(2); + assert.true(true); +}); diff --git a/test/cli/fixtures/assert-expect/no-assertions.js b/test/cli/fixtures/assert-expect/no-assertions.js index edd8a800d..160802d88 100644 --- a/test/cli/fixtures/assert-expect/no-assertions.js +++ b/test/cli/fixtures/assert-expect/no-assertions.js @@ -1,2 +1,2 @@ -QUnit.test( "test with no assertions", () => { -} ); +QUnit.test('test with no assertions', () => { +}); diff --git a/test/cli/fixtures/assert-expect/require-expects.js b/test/cli/fixtures/assert-expect/require-expects.js index 671bf0412..a1598a31f 100644 --- a/test/cli/fixtures/assert-expect/require-expects.js +++ b/test/cli/fixtures/assert-expect/require-expects.js @@ -1,5 +1,5 @@ QUnit.config.requireExpects = true; -QUnit.test( "passing test", assert => { - assert.true( true ); -} ); +QUnit.test('passing test', assert => { + assert.true(true); +}); diff --git a/test/cli/fixtures/async-module-warning/promise-test.js b/test/cli/fixtures/async-module-warning/promise-test.js index 9474de11b..46f3691fe 100644 --- a/test/cli/fixtures/async-module-warning/promise-test.js +++ b/test/cli/fixtures/async-module-warning/promise-test.js @@ -1,6 +1,6 @@ -QUnit.module( "module manually returning a promise", function() { - QUnit.test( "has a test", function( assert ) { - assert.true( true ); - } ); - return Promise.resolve( 1 ); -} ); +QUnit.module('module manually returning a promise', function () { + QUnit.test('has a test', function (assert) { + assert.true(true); + }); + return Promise.resolve(1); +}); diff --git a/test/cli/fixtures/async-module-warning/test.js b/test/cli/fixtures/async-module-warning/test.js index e28ae64d3..1580b8aa3 100644 --- a/test/cli/fixtures/async-module-warning/test.js +++ b/test/cli/fixtures/async-module-warning/test.js @@ -1,10 +1,10 @@ // eslint-disable-next-line qunit/no-async-module-callbacks -QUnit.module( "module with async callback", async function() { - await Promise.resolve( 1 ); +QUnit.module('module with async callback', async function () { + await Promise.resolve(1); - QUnit.test( "has a test", function( assert ) { - assert.true( true ); - } ); -} ); + QUnit.test('has a test', function (assert) { + assert.true(true); + }); +}); -QUnit.module( "resulting parent module" ); +QUnit.module('resulting parent module'); diff --git a/test/cli/fixtures/bad-callbacks/begin-throw.js b/test/cli/fixtures/bad-callbacks/begin-throw.js index a74df5196..f074c7e64 100644 --- a/test/cli/fixtures/bad-callbacks/begin-throw.js +++ b/test/cli/fixtures/bad-callbacks/begin-throw.js @@ -1,9 +1,9 @@ -QUnit.begin( () => { - throw new Error( "No dice" ); -} ); +QUnit.begin(() => { + throw new Error('No dice'); +}); -QUnit.module( "module1", () => { - QUnit.test( "test1", assert => { - assert.true( true ); - } ); -} ); +QUnit.module('module1', () => { + QUnit.test('test1', assert => { + assert.true(true); + }); +}); diff --git a/test/cli/fixtures/bad-callbacks/done-throw.js b/test/cli/fixtures/bad-callbacks/done-throw.js index b0af05361..351420468 100644 --- a/test/cli/fixtures/bad-callbacks/done-throw.js +++ b/test/cli/fixtures/bad-callbacks/done-throw.js @@ -1,9 +1,9 @@ -QUnit.done( () => { - throw new Error( "No dice" ); -} ); +QUnit.done(() => { + throw new Error('No dice'); +}); -QUnit.module( "module1", () => { - QUnit.test( "test1", assert => { - assert.true( true ); - } ); -} ); +QUnit.module('module1', () => { + QUnit.test('test1', assert => { + assert.true(true); + }); +}); diff --git a/test/cli/fixtures/bad-callbacks/moduleDone-throw.js b/test/cli/fixtures/bad-callbacks/moduleDone-throw.js index 3ba38a0bc..95ed1e527 100644 --- a/test/cli/fixtures/bad-callbacks/moduleDone-throw.js +++ b/test/cli/fixtures/bad-callbacks/moduleDone-throw.js @@ -1,15 +1,15 @@ -QUnit.moduleDone( details => { - throw new Error( "No dice for " + details.name ); -} ); +QUnit.moduleDone(details => { + throw new Error('No dice for ' + details.name); +}); -QUnit.module( "module1", () => { - QUnit.test( "test1", assert => { - assert.true( true ); - } ); -} ); +QUnit.module('module1', () => { + QUnit.test('test1', assert => { + assert.true(true); + }); +}); -QUnit.module( "module2", () => { - QUnit.test( "test2", assert => { - assert.true( true ); - } ); -} ); +QUnit.module('module2', () => { + QUnit.test('test2', assert => { + assert.true(true); + }); +}); diff --git a/test/cli/fixtures/bad-callbacks/testStart-throw.js b/test/cli/fixtures/bad-callbacks/testStart-throw.js index 31588164f..5a2372e9a 100644 --- a/test/cli/fixtures/bad-callbacks/testStart-throw.js +++ b/test/cli/fixtures/bad-callbacks/testStart-throw.js @@ -1,15 +1,15 @@ -QUnit.testStart( details => { - throw new Error( "No dice for " + details.name ); -} ); +QUnit.testStart(details => { + throw new Error('No dice for ' + details.name); +}); -QUnit.module( "module1", () => { - QUnit.test( "test1", assert => { - assert.true( true ); - } ); -} ); +QUnit.module('module1', () => { + QUnit.test('test1', assert => { + assert.true(true); + }); +}); -QUnit.module( "module2", () => { - QUnit.test( "test2", assert => { - assert.true( true ); - } ); -} ); +QUnit.module('module2', () => { + QUnit.test('test2', assert => { + assert.true(true); + }); +}); diff --git a/test/cli/fixtures/callbacks-promises.js b/test/cli/fixtures/callbacks-promises.js index bebed7e84..3db4cf720 100644 --- a/test/cli/fixtures/callbacks-promises.js +++ b/test/cli/fixtures/callbacks-promises.js @@ -1,63 +1,63 @@ -function timeoutPromiseCallback( callback, timeout ) { - return new Promise( function( resolve ) { - setTimeout( function() { - callback(); - resolve(); - }, timeout ); - } ); +function timeoutPromiseCallback (callback, timeout) { + return new Promise(function (resolve) { + setTimeout(function () { + callback(); + resolve(); + }, timeout); + }); } -QUnit.begin( function() { - return timeoutPromiseCallback( function() { - console.warn( "CALLBACK: begin" ); - }, 100 ); -} ); -QUnit.begin( function() { - return timeoutPromiseCallback( function() { - console.warn( "CALLBACK: begin2" ); - }, 10 ); -} ); -QUnit.moduleStart( function() { - return timeoutPromiseCallback( function() { - console.warn( "CALLBACK: moduleStart" ); - }, 100 ); -} ); -QUnit.testStart( function( cb ) { - return timeoutPromiseCallback( function() { - console.warn( "CALLBACK: testStart - " + cb.name ); - }, 100 ); -} ); +QUnit.begin(function () { + return timeoutPromiseCallback(function () { + console.warn('CALLBACK: begin'); + }, 100); +}); +QUnit.begin(function () { + return timeoutPromiseCallback(function () { + console.warn('CALLBACK: begin2'); + }, 10); +}); +QUnit.moduleStart(function () { + return timeoutPromiseCallback(function () { + console.warn('CALLBACK: moduleStart'); + }, 100); +}); +QUnit.testStart(function (cb) { + return timeoutPromiseCallback(function () { + console.warn('CALLBACK: testStart - ' + cb.name); + }, 100); +}); -QUnit.testDone( function( cb ) { - return timeoutPromiseCallback( function() { - console.warn( "CALLBACK: testDone - " + cb.name ); - }, 100 ); -} ); -QUnit.moduleDone( function( cb ) { - return timeoutPromiseCallback( function() { - console.warn( "CALLBACK: moduleDone - " + cb.name ); - }, 100 ); -} ); -QUnit.done( function() { - return timeoutPromiseCallback( function() { - console.warn( "CALLBACK: done" ); - }, 100 ); -} ); +QUnit.testDone(function (cb) { + return timeoutPromiseCallback(function () { + console.warn('CALLBACK: testDone - ' + cb.name); + }, 100); +}); +QUnit.moduleDone(function (cb) { + return timeoutPromiseCallback(function () { + console.warn('CALLBACK: moduleDone - ' + cb.name); + }, 100); +}); +QUnit.done(function () { + return timeoutPromiseCallback(function () { + console.warn('CALLBACK: done'); + }, 100); +}); -QUnit.module( "module1", function() { - QUnit.module( "nestedModule1", function() { - QUnit.test( "test1", function( assert ) { - assert.true( true ); - } ); - } ); +QUnit.module('module1', function () { + QUnit.module('nestedModule1', function () { + QUnit.test('test1', function (assert) { + assert.true(true); + }); + }); - QUnit.test( "test2", function( assert ) { - assert.true( true ); - } ); + QUnit.test('test2', function (assert) { + assert.true(true); + }); - QUnit.module( "nestedModule2", function() { - QUnit.test( "test3", function( assert ) { - assert.true( true ); - } ); - } ); -} ); + QUnit.module('nestedModule2', function () { + QUnit.test('test3', function (assert) { + assert.true(true); + }); + }); +}); diff --git a/test/cli/fixtures/callbacks.js b/test/cli/fixtures/callbacks.js index a543cf462..a7a0b68c6 100644 --- a/test/cli/fixtures/callbacks.js +++ b/test/cli/fixtures/callbacks.js @@ -1,75 +1,75 @@ -function callback( name ) { - return function() { - console.warn( "CALLBACK: " + name ); - }; +function callback (name) { + return function () { + console.warn('CALLBACK: ' + name); + }; } -QUnit.begin( callback( "begin1" ) ); -QUnit.begin( callback( "begin2" ) ); -QUnit.moduleStart( callback( "moduleStart1" ) ); -QUnit.moduleStart( callback( "moduleStart2" ) ); -QUnit.testStart( callback( "testStart1" ) ); -QUnit.testStart( callback( "testStart2" ) ); +QUnit.begin(callback('begin1')); +QUnit.begin(callback('begin2')); +QUnit.moduleStart(callback('moduleStart1')); +QUnit.moduleStart(callback('moduleStart2')); +QUnit.testStart(callback('testStart1')); +QUnit.testStart(callback('testStart2')); -QUnit.log( callback( "log1" ) ); -QUnit.log( callback( "log2" ) ); +QUnit.log(callback('log1')); +QUnit.log(callback('log2')); -QUnit.testDone( callback( "testDone1" ) ); -QUnit.testDone( callback( "testDone2" ) ); -QUnit.moduleDone( callback( "moduleDone1" ) ); -QUnit.moduleDone( callback( "moduleDone2" ) ); -QUnit.done( callback( "done1" ) ); -QUnit.done( callback( "done2" ) ); +QUnit.testDone(callback('testDone1')); +QUnit.testDone(callback('testDone2')); +QUnit.moduleDone(callback('moduleDone1')); +QUnit.moduleDone(callback('moduleDone2')); +QUnit.done(callback('done1')); +QUnit.done(callback('done2')); -QUnit.module( "module1", { - before: callback( "module1 > before" ), - beforeEach: callback( "module1 > beforeEach" ), - afterEach: callback( "module1 > afterEach" ), - after: callback( "module1 > after" ) -}, function() { - QUnit.test( "test1", function( assert ) { - console.warn( "TEST: module1 > test1" ); - assert.true( true ); - } ); +QUnit.module('module1', { + before: callback('module1 > before'), + beforeEach: callback('module1 > beforeEach'), + afterEach: callback('module1 > afterEach'), + after: callback('module1 > after') +}, function () { + QUnit.test('test1', function (assert) { + console.warn('TEST: module1 > test1'); + assert.true(true); + }); - QUnit.module( "module2", { - before: callback( "module2 > before" ), - beforeEach: callback( "module2 > beforeEach" ), - afterEach: callback( "module2 > afterEach" ), - after: callback( "module2 > after" ) - }, function() { - QUnit.test( "test1", function( assert ) { - console.warn( "TEST: module2 > test1" ); - assert.true( true ); - } ); - } ); + QUnit.module('module2', { + before: callback('module2 > before'), + beforeEach: callback('module2 > beforeEach'), + afterEach: callback('module2 > afterEach'), + after: callback('module2 > after') + }, function () { + QUnit.test('test1', function (assert) { + console.warn('TEST: module2 > test1'); + assert.true(true); + }); + }); - QUnit.module( "module3", { - before: callback( "module3 > before" ), - beforeEach: callback( "module3 > beforeEach" ), - afterEach: callback( "module3 > afterEach" ), - after: callback( "module3 > after" ) - }, function() { - QUnit.test( "test1", function( assert ) { - console.warn( "TEST: module3 > test1" ); - assert.true( true ); - } ); - } ); + QUnit.module('module3', { + before: callback('module3 > before'), + beforeEach: callback('module3 > beforeEach'), + afterEach: callback('module3 > afterEach'), + after: callback('module3 > after') + }, function () { + QUnit.test('test1', function (assert) { + console.warn('TEST: module3 > test1'); + assert.true(true); + }); + }); - QUnit.test( "test2", function( assert ) { - console.warn( "TEST: module1 > test2" ); - assert.true( true ); - } ); + QUnit.test('test2', function (assert) { + console.warn('TEST: module1 > test2'); + assert.true(true); + }); - QUnit.module( "module4", { - before: callback( "module4 > before" ), - beforeEach: callback( "module4 > beforeEach" ), - afterEach: callback( "module4 > afterEach" ), - after: callback( "module4 > after" ) - }, function() { - QUnit.test( "test1", function( assert ) { - console.warn( "TEST: module4 > test1" ); - assert.true( true ); - } ); - } ); -} ); + QUnit.module('module4', { + before: callback('module4 > before'), + beforeEach: callback('module4 > beforeEach'), + afterEach: callback('module4 > afterEach'), + after: callback('module4 > after') + }, function () { + QUnit.test('test1', function (assert) { + console.warn('TEST: module4 > test1'); + assert.true(true); + }); + }); +}); diff --git a/test/cli/fixtures/config-filter-regex-exclude.js b/test/cli/fixtures/config-filter-regex-exclude.js index e01bf48ee..1074a3908 100644 --- a/test/cli/fixtures/config-filter-regex-exclude.js +++ b/test/cli/fixtures/config-filter-regex-exclude.js @@ -1,20 +1,20 @@ // regular expression (case-sensitive), inverted -QUnit.config.filter = "!/Foo|bar/"; +QUnit.config.filter = '!/Foo|bar/'; -QUnit.module( "filter" ); +QUnit.module('filter'); -QUnit.test( "foo test", function( assert ) { - assert.true( true ); -} ); +QUnit.test('foo test', function (assert) { + assert.true(true); +}); -QUnit.test( "Foo test", function( assert ) { - assert.true( false, "Foo is excluded" ); -} ); +QUnit.test('Foo test', function (assert) { + assert.true(false, 'Foo is excluded'); +}); -QUnit.test( "bar test", function( assert ) { - assert.true( false, "bar is excluded" ); -} ); +QUnit.test('bar test', function (assert) { + assert.true(false, 'bar is excluded'); +}); -QUnit.test( "Bar test", function( assert ) { - assert.true( true ); -} ); +QUnit.test('Bar test', function (assert) { + assert.true(true); +}); diff --git a/test/cli/fixtures/config-filter-regex.js b/test/cli/fixtures/config-filter-regex.js index d9f01c164..a1abacc0d 100644 --- a/test/cli/fixtures/config-filter-regex.js +++ b/test/cli/fixtures/config-filter-regex.js @@ -1,24 +1,24 @@ // regular expression (case-insensitive) -QUnit.config.filter = "/foo|bar/i"; +QUnit.config.filter = '/foo|bar/i'; -QUnit.module( "filter" ); +QUnit.module('filter'); -QUnit.test( "foo test", function( assert ) { - assert.true( true ); -} ); +QUnit.test('foo test', function (assert) { + assert.true(true); +}); -QUnit.test( "FOO test", function( assert ) { - assert.true( true ); -} ); +QUnit.test('FOO test', function (assert) { + assert.true(true); +}); -QUnit.test( "boo test", function( assert ) { - assert.true( false, "boo does not match" ); -} ); +QUnit.test('boo test', function (assert) { + assert.true(false, 'boo does not match'); +}); -QUnit.test( "bar test", function( assert ) { - assert.true( true ); -} ); +QUnit.test('bar test', function (assert) { + assert.true(true); +}); -QUnit.test( "baz test", function( assert ) { - assert.true( false, "baz does not match" ); -} ); +QUnit.test('baz test', function (assert) { + assert.true(false, 'baz does not match'); +}); diff --git a/test/cli/fixtures/config-filter-string.js b/test/cli/fixtures/config-filter-string.js index 1666d6be2..3a0e1662f 100644 --- a/test/cli/fixtures/config-filter-string.js +++ b/test/cli/fixtures/config-filter-string.js @@ -1,16 +1,16 @@ // case-insensitive string that looks like a regex but isn't, inverted -QUnit.config.filter = "!foo|bar"; +QUnit.config.filter = '!foo|bar'; -QUnit.module( "filter" ); +QUnit.module('filter'); -QUnit.test( "foo test", function( assert ) { - assert.true( true ); -} ); +QUnit.test('foo test', function (assert) { + assert.true(true); +}); -QUnit.test( "bar test", function( assert ) { - assert.true( true ); -} ); +QUnit.test('bar test', function (assert) { + assert.true(true); +}); -QUnit.test( "foo|bar test", function( assert ) { - assert.true( false, "'foo|bar' is excluded" ); -} ); +QUnit.test('foo|bar test', function (assert) { + assert.true(false, "'foo|bar' is excluded"); +}); diff --git a/test/cli/fixtures/config-module.js b/test/cli/fixtures/config-module.js index a26b678ba..05c00d72d 100644 --- a/test/cli/fixtures/config-module.js +++ b/test/cli/fixtures/config-module.js @@ -1,19 +1,19 @@ -QUnit.config.module = "MODULE b"; +QUnit.config.module = 'MODULE b'; -QUnit.module( "Module A", () => { - QUnit.test( "Test A", assert => { - assert.true( false ); - } ); -} ); +QUnit.module('Module A', () => { + QUnit.test('Test A', assert => { + assert.true(false); + }); +}); -QUnit.module( "Module B", () => { - QUnit.test( "Test B", assert => { - assert.true( true ); - } ); -} ); +QUnit.module('Module B', () => { + QUnit.test('Test B', assert => { + assert.true(true); + }); +}); -QUnit.module( "Module C", () => { - QUnit.test( "Test C", assert => { - assert.true( false ); - } ); -} ); +QUnit.module('Module C', () => { + QUnit.test('Test C', assert => { + assert.true(false); + }); +}); diff --git a/test/cli/fixtures/config-moduleId.js b/test/cli/fixtures/config-moduleId.js index ace0de9e1..800674979 100644 --- a/test/cli/fixtures/config-moduleId.js +++ b/test/cli/fixtures/config-moduleId.js @@ -1,53 +1,53 @@ -QUnit.config.moduleId = [ "127c21dd", "c1d6ebd4", "7d89af3b" ]; - -QUnit.test( "global test", function( assert ) { - assert.true( false ); -} ); - -QUnit.module( "module A scoped", function() { - QUnit.test( "test A1", function( assert ) { - assert.true( false ); - } ); - - QUnit.module( "module B nested", function() { - QUnit.test( "test B1", function( assert ) { - assert.true( false ); - } ); - } ); - - QUnit.module( "module C nested", function() { - QUnit.test( "test C1", function( assert ) { - assert.true( true, "run module C" ); - } ); - } ); - - QUnit.test( "test A2", function( assert ) { - assert.true( false ); - } ); -} ); - -QUnit.module( "module D scoped", function() { - QUnit.test( "test D1", function( assert ) { - assert.true( true, "run module D" ); - } ); - - QUnit.module( "module E nested", function() { - QUnit.test( "test E1", function( assert ) { - assert.true( true, "run module D" ); - } ); - } ); - - QUnit.test( "test D2", function( assert ) { - assert.true( true, "run module D" ); - } ); -} ); - -QUnit.module( "module E flat" ); -QUnit.test( "test E1", function( assert ) { - assert.true( false ); -} ); - -QUnit.module( "module F flat" ); -QUnit.test( "test F1", function( assert ) { - assert.true( true, "run module F" ); -} ); +QUnit.config.moduleId = ['127c21dd', 'c1d6ebd4', '7d89af3b']; + +QUnit.test('global test', function (assert) { + assert.true(false); +}); + +QUnit.module('module A scoped', function () { + QUnit.test('test A1', function (assert) { + assert.true(false); + }); + + QUnit.module('module B nested', function () { + QUnit.test('test B1', function (assert) { + assert.true(false); + }); + }); + + QUnit.module('module C nested', function () { + QUnit.test('test C1', function (assert) { + assert.true(true, 'run module C'); + }); + }); + + QUnit.test('test A2', function (assert) { + assert.true(false); + }); +}); + +QUnit.module('module D scoped', function () { + QUnit.test('test D1', function (assert) { + assert.true(true, 'run module D'); + }); + + QUnit.module('module E nested', function () { + QUnit.test('test E1', function (assert) { + assert.true(true, 'run module D'); + }); + }); + + QUnit.test('test D2', function (assert) { + assert.true(true, 'run module D'); + }); +}); + +QUnit.module('module E flat'); +QUnit.test('test E1', function (assert) { + assert.true(false); +}); + +QUnit.module('module F flat'); +QUnit.test('test F1', function (assert) { + assert.true(true, 'run module F'); +}); diff --git a/test/cli/fixtures/config-testId.js b/test/cli/fixtures/config-testId.js index b17875c9a..beedaebbb 100644 --- a/test/cli/fixtures/config-testId.js +++ b/test/cli/fixtures/config-testId.js @@ -1,44 +1,44 @@ -QUnit.config.testId = [ "94e5e740", "9e89c63c", "066af31c", "c7ae85af" ]; - -QUnit.test( "test 1", function( assert ) { - assert.true( false ); -} ); - -QUnit.test( "test 2", function( assert ) { - assert.true( true, "run global test 2" ); -} ); - -QUnit.module( "module A", function() { - QUnit.test( "test 1", function( assert ) { - assert.true( false ); - } ); - - QUnit.module( "module B", function() { - QUnit.test( "test 1", function( assert ) { - assert.true( true, "run AB test 1" ); - } ); - } ); - - QUnit.module( "module C", function() { - QUnit.test( "test 1", function( assert ) { - assert.true( false ); - } ); - QUnit.test( "test 2", function( assert ) { - assert.true( true, "run AC test 2" ); - } ); - } ); - - QUnit.test( "test 2", function( assert ) { - assert.true( false ); - } ); -} ); - -QUnit.test( "test 3", function( assert ) { - assert.true( false ); -} ); - -QUnit.module( "module D", function() { - QUnit.test( "test 1", function( assert ) { - assert.true( true, "run D test 1" ); - } ); -} ); +QUnit.config.testId = ['94e5e740', '9e89c63c', '066af31c', 'c7ae85af']; + +QUnit.test('test 1', function (assert) { + assert.true(false); +}); + +QUnit.test('test 2', function (assert) { + assert.true(true, 'run global test 2'); +}); + +QUnit.module('module A', function () { + QUnit.test('test 1', function (assert) { + assert.true(false); + }); + + QUnit.module('module B', function () { + QUnit.test('test 1', function (assert) { + assert.true(true, 'run AB test 1'); + }); + }); + + QUnit.module('module C', function () { + QUnit.test('test 1', function (assert) { + assert.true(false); + }); + QUnit.test('test 2', function (assert) { + assert.true(true, 'run AC test 2'); + }); + }); + + QUnit.test('test 2', function (assert) { + assert.true(false); + }); +}); + +QUnit.test('test 3', function (assert) { + assert.true(false); +}); + +QUnit.module('module D', function () { + QUnit.test('test 1', function (assert) { + assert.true(true, 'run D test 1'); + }); +}); diff --git a/test/cli/fixtures/config-testTimeout.js b/test/cli/fixtures/config-testTimeout.js index 57ccb90e4..a412fcbe7 100644 --- a/test/cli/fixtures/config-testTimeout.js +++ b/test/cli/fixtures/config-testTimeout.js @@ -1,9 +1,9 @@ -process.on( "unhandledRejection", ( reason ) => { - console.log( "Unhandled Rejection:", reason ); -} ); +process.on('unhandledRejection', (reason) => { + console.log('Unhandled Rejection:', reason); +}); QUnit.config.testTimeout = 10; -QUnit.test( "slow", () => { - return new Promise( resolve => setTimeout( resolve, 20 ) ); -} ); +QUnit.test('slow', () => { + return new Promise(resolve => setTimeout(resolve, 20)); +}); diff --git a/test/cli/fixtures/done-after-timeout.js b/test/cli/fixtures/done-after-timeout.js index 30a17e393..6a29b4320 100644 --- a/test/cli/fixtures/done-after-timeout.js +++ b/test/cli/fixtures/done-after-timeout.js @@ -1,6 +1,6 @@ -QUnit.test( "times out before scheduled done is called", assert => { - assert.timeout( 10 ); - const done = assert.async(); - assert.true( true ); - setTimeout( done, 100 ); -} ); +QUnit.test('times out before scheduled done is called', assert => { + assert.timeout(10); + const done = assert.async(); + assert.true(true); + setTimeout(done, 100); +}); diff --git a/test/cli/fixtures/double.js b/test/cli/fixtures/double.js index 2eea64e16..a23c446f4 100644 --- a/test/cli/fixtures/double.js +++ b/test/cli/fixtures/double.js @@ -1,9 +1,9 @@ -QUnit.module( "Double", function() { - QUnit.test( "has a test", function( assert ) { - assert.true( true ); - } ); +QUnit.module('Double', function () { + QUnit.test('has a test', function (assert) { + assert.true(true); + }); - QUnit.test( "has another test", function( assert ) { - assert.true( true ); - } ); -} ); + QUnit.test('has another test', function (assert) { + assert.true(true); + }); +}); diff --git a/test/cli/fixtures/drooling-done.js b/test/cli/fixtures/drooling-done.js index 22d53fbbc..8f9c49c2d 100644 --- a/test/cli/fixtures/drooling-done.js +++ b/test/cli/fixtures/drooling-done.js @@ -2,16 +2,16 @@ QUnit.config.reorder = false; let done; -QUnit.test( "Test A", assert => { - assert.ok( true ); - done = assert.async(); - throw new Error( "this is an intentional error" ); -} ); +QUnit.test('Test A', assert => { + assert.ok(true); + done = assert.async(); + throw new Error('this is an intentional error'); +}); -QUnit.test( "Test B", assert => { - assert.ok( true ); +QUnit.test('Test B', assert => { + assert.ok(true); - // This bad call is silently ignored because "Test A" already failed - // and we cancelled the async pauses. - done(); -} ); + // This bad call is silently ignored because "Test A" already failed + // and we cancelled the async pauses. + done(); +}); diff --git a/test/cli/fixtures/drooling-extra-done-outside.js b/test/cli/fixtures/drooling-extra-done-outside.js index 433226042..74e845c2d 100644 --- a/test/cli/fixtures/drooling-extra-done-outside.js +++ b/test/cli/fixtures/drooling-extra-done-outside.js @@ -1,11 +1,11 @@ -QUnit.test( "extra done scheduled outside any test", assert => { - assert.timeout( 10 ); - const done = assert.async(); - assert.true( true ); +QUnit.test('extra done scheduled outside any test', assert => { + assert.timeout(10); + const done = assert.async(); + assert.true(true); - // Later, boom! - setTimeout( done, 100 ); + // Later, boom! + setTimeout(done, 100); - // Passing, end of test - done(); -} ); + // Passing, end of test + done(); +}); diff --git a/test/cli/fixtures/drooling-extra-done.js b/test/cli/fixtures/drooling-extra-done.js index 95a20b2c7..c8fdbbf87 100644 --- a/test/cli/fixtures/drooling-extra-done.js +++ b/test/cli/fixtures/drooling-extra-done.js @@ -2,17 +2,17 @@ QUnit.config.reorder = false; let done; -QUnit.test( "Test A", assert => { - assert.ok( true ); - done = assert.async(); +QUnit.test('Test A', assert => { + assert.ok(true); + done = assert.async(); - // Passing. - done(); -} ); + // Passing. + done(); +}); -QUnit.test( "Test B", assert => { - assert.ok( true ); +QUnit.test('Test B', assert => { + assert.ok(true); - // Boom - done(); -} ); + // Boom + done(); +}); diff --git a/test/cli/fixtures/expected/tap-outputs.js b/test/cli/fixtures/expected/tap-outputs.js index a76a2c2d6..14e26b510 100644 --- a/test/cli/fixtures/expected/tap-outputs.js +++ b/test/cli/fixtures/expected/tap-outputs.js @@ -1,8 +1,8 @@ -"use strict"; +'use strict'; // Expected outputs from the TapReporter for the commands run in CLI tests module.exports = { - "qunit": + qunit: `TAP version 13 ok 1 First > 1 ok 2 Second > 1 @@ -12,7 +12,7 @@ ok 2 Second > 1 # todo 0 # fail 0`, - "qunit 'glob/**/*-test.js'": + "qunit 'glob/**/*-test.js'": `TAP version 13 ok 1 A-Test > derp ok 2 Nested-Test > herp @@ -22,7 +22,7 @@ ok 2 Nested-Test > herp # todo 0 # fail 0`, - "qunit single.js": + 'qunit single.js': `TAP version 13 ok 1 Single > has a test 1..1 @@ -31,7 +31,7 @@ ok 1 Single > has a test # todo 0 # fail 0`, - "qunit single.js double.js": + 'qunit single.js double.js': `TAP version 13 ok 1 Double > has a test ok 2 Double > has another test @@ -42,7 +42,7 @@ ok 3 Single > has a test # todo 0 # fail 0`, - "qunit test": + 'qunit test': `TAP version 13 ok 1 First > 1 ok 2 Second > 1 @@ -52,7 +52,7 @@ ok 2 Second > 1 # todo 0 # fail 0`, - "qunit fail/throws-match.js": + 'qunit fail/throws-match.js': `TAP version 13 not ok 1 Throws match > bad --- @@ -61,7 +61,7 @@ not ok 1 Throws match > bad actual : Error: Match me with a pattern expected: "/incorrect pattern/" stack: | - at /qunit/test/cli/fixtures/fail/throws-match.js:3:10 + at /qunit/test/cli/fixtures/fail/throws-match.js:3:12 ... 1..1 # pass 0 @@ -69,7 +69,7 @@ not ok 1 Throws match > bad # todo 0 # fail 1`, - "qunit test single.js 'glob/**/*-test.js'": + "qunit test single.js 'glob/**/*-test.js'": `TAP version 13 ok 1 A-Test > derp ok 2 Nested-Test > herp @@ -82,7 +82,7 @@ ok 5 Second > 1 # todo 0 # fail 0`, - "qunit --seed s33d test single.js 'glob/**/*-test.js'": `Running tests with seed: s33d + "qunit --seed s33d test single.js 'glob/**/*-test.js'": `Running tests with seed: s33d TAP version 13 ok 1 Second > 1 ok 2 Single > has a test @@ -95,18 +95,18 @@ ok 5 A-Test > derp # todo 0 # fail 0`, - "qunit --reporter npm-reporter": "Run ended!", - "qunit --reporter does-not-exist": `No reporter found matching "does-not-exist". + 'qunit --reporter npm-reporter': 'Run ended!', + 'qunit --reporter does-not-exist': `No reporter found matching "does-not-exist". Built-in reporters: console, tap Extra reporters found among package dependencies: npm-reporter`, - "qunit --reporter": `Built-in reporters: console, tap + 'qunit --reporter': `Built-in reporters: console, tap Extra reporters found among package dependencies: npm-reporter`, - "qunit hanging-test": `Error: Process exited before tests finished running + 'qunit hanging-test': `Error: Process exited before tests finished running Last test to run (hanging) has an async hold. Ensure all assert.async() callbacks are invoked and Promises resolve. You should also set a standard timeout via QUnit.config.testTimeout.`, - /* eslint-enable max-len */ - "qunit unhandled-rejection.js": + /* eslint-enable max-len */ + 'qunit unhandled-rejection.js': `not ok 1 global failure --- message: Error: outside of a test context @@ -128,7 +128,7 @@ not ok 2 Unhandled Rejections > test passes just fine, but has a rejected promis expected: undefined stack: | Error: Error thrown in non-returned promise! - at /qunit/test/cli/fixtures/unhandled-rejection.js:10:10 + at /qunit/test/cli/fixtures/unhandled-rejection.js:10:13 at internal ... 1..2 @@ -137,8 +137,8 @@ not ok 2 Unhandled Rejections > test passes just fine, but has a rejected promis # todo 0 # fail 2`, - // The last frame differs between Node 10 and 12+ (changes in processing of ticks) - "qunit no-tests": + // The last frame differs between Node 10 and 12+ (changes in processing of ticks) + 'qunit no-tests': `TAP version 13 not ok 1 global failure --- @@ -157,7 +157,7 @@ not ok 1 global failure # todo 0 # fail 1`, - "qunit sourcemap/source.js": + 'qunit sourcemap/source.js': `TAP version 13 ok 1 Example > good not ok 2 Example > bad @@ -167,7 +167,7 @@ not ok 2 Example > bad actual : false expected: true stack: | - at /qunit/test/cli/fixtures/sourcemap/source.js:7:14 + at /qunit/test/cli/fixtures/sourcemap/source.js:7:16 ... 1..2 # pass 1 @@ -175,7 +175,7 @@ not ok 2 Example > bad # todo 0 # fail 1`, - "qunit sourcemap/source.min.js": + 'qunit sourcemap/source.min.js': `TAP version 13 ok 1 Example > good not ok 2 Example > bad @@ -193,7 +193,7 @@ not ok 2 Example > bad # todo 0 # fail 1`, - "qunit ../../es2018/esm.mjs": + 'qunit ../../es2018/esm.mjs': `TAP version 13 ok 1 ESM test suite > sum() 1..1 @@ -202,7 +202,7 @@ ok 1 ESM test suite > sum() # todo 0 # fail 0`, - "qunit timeout": + 'qunit timeout': `TAP version 13 not ok 1 timeout > first --- @@ -220,7 +220,7 @@ ok 2 timeout > second # todo 0 # fail 1`, - "qunit hooks-global-context.js": + 'qunit hooks-global-context.js': `TAP version 13 ok 1 A > A1 ok 2 A > AB > AB1 @@ -231,7 +231,7 @@ ok 3 B # todo 0 # fail 0`, - "qunit zero-assertions.js": + 'qunit zero-assertions.js': `TAP version 13 ok 1 Zero assertions > has a test 1..1 @@ -240,7 +240,7 @@ ok 1 Zero assertions > has a test # todo 0 # fail 0`, - "qunit --filter 'no matches' test": + "qunit --filter 'no matches' test": `TAP version 13 not ok 1 global failure --- @@ -259,7 +259,7 @@ not ok 1 global failure # todo 0 # fail 1`, - "qunit single.js --require require-dep --require ./node_modules/require-dep/module.js": + 'qunit single.js --require require-dep --require ./node_modules/require-dep/module.js': `required require-dep/index.js required require-dep/module.js TAP version 13 @@ -270,7 +270,7 @@ ok 1 Single > has a test # todo 0 # fail 0`, - "node --expose-gc ../../../bin/qunit.js 'memory-leak/*.js'": + "node --expose-gc ../../../bin/qunit.js 'memory-leak/*.js'": `TAP version 13 ok 1 some nested module > can call method on foo ok 2 some nested module > child module > child test @@ -281,7 +281,7 @@ ok 3 later thing > has released all foos # todo 0 # fail 0`, - "node --expose-gc ../../../bin/qunit.js --filter !child 'memory-leak/*.js'": + "node --expose-gc ../../../bin/qunit.js --filter !child 'memory-leak/*.js'": `TAP version 13 ok 1 some nested module > can call method on foo ok 2 later thing > has released all foos @@ -291,7 +291,7 @@ ok 2 later thing > has released all foos # todo 0 # fail 0`, - "qunit only/test.js": + 'qunit only/test.js': `TAP version 13 ok 1 run this test ok 2 all tests with only run @@ -301,7 +301,7 @@ ok 2 all tests with only run # todo 0 # fail 0`, - "qunit only/module.js": + 'qunit only/module.js': `TAP version 13 not ok 1 # TODO module B > Only this module should run > a todo test --- @@ -310,7 +310,7 @@ not ok 1 # TODO module B > Only this module should run > a todo test actual : false expected: true stack: | - at /qunit/test/cli/fixtures/only/module.js:17:15 + at /qunit/test/cli/fixtures/only/module.js:17:18 ... ok 2 # SKIP module B > Only this module should run > implicitly skipped test ok 3 module B > Only this module should run > normal test @@ -323,7 +323,7 @@ ok 6 module E > test E # todo 1 # fail 0`, - "qunit only/module-then-test.js": + 'qunit only/module-then-test.js': `TAP version 13 ok 1 module A > module B > test B 1..2 @@ -332,8 +332,7 @@ ok 1 module A > module B > test B # todo 0 # fail 0`, - - "qunit only/module-flat.js": + 'qunit only/module-flat.js': `TAP version 13 not ok 1 # TODO module B > test B --- @@ -342,7 +341,7 @@ not ok 1 # TODO module B > test B actual : false expected: true stack: | - at /qunit/test/cli/fixtures/only/module-flat.js:9:13 + at /qunit/test/cli/fixtures/only/module-flat.js:8:14 ... ok 2 # SKIP module B > test C ok 3 module B > test D @@ -352,7 +351,7 @@ ok 3 module B > test D # todo 1 # fail 0`, - "qunit module-nested.js": + 'qunit module-nested.js': `TAP version 13 ok 1 module 1 > test in module 1 ok 2 module 3 > test in module 3 @@ -362,7 +361,7 @@ ok 2 module 3 > test in module 3 # todo 0 # fail 0`, - "qunit incorrect-hooks-warning/test.js": + 'qunit incorrect-hooks-warning/test.js': `TAP version 13 ok 1 module providing hooks > module not providing hooks > has a test 1..1 @@ -371,7 +370,7 @@ ok 1 module providing hooks > module not providing hooks > has a test # todo 0 # fail 0`, - "qunit async-module-warning/test.js": + 'qunit async-module-warning/test.js': `TAP version 13 ok 1 resulting parent module > has a test 1..1 @@ -380,7 +379,7 @@ ok 1 resulting parent module > has a test # todo 0 # fail 0`, - "qunit async-module-warning/promise-test.js": + 'qunit async-module-warning/promise-test.js': `TAP version 13 ok 1 module manually returning a promise > has a test 1..1 @@ -389,7 +388,7 @@ ok 1 module manually returning a promise > has a test # todo 0 # fail 0`, - "qunit config-filter-string.js": + 'qunit config-filter-string.js': `TAP version 13 ok 1 filter > foo test ok 2 filter > bar test @@ -399,7 +398,7 @@ ok 2 filter > bar test # todo 0 # fail 0`, - "qunit config-filter-regex.js": + 'qunit config-filter-regex.js': `TAP version 13 ok 1 filter > foo test ok 2 filter > FOO test @@ -410,7 +409,7 @@ ok 3 filter > bar test # todo 0 # fail 0`, - "qunit config-filter-regex-exclude.js": + 'qunit config-filter-regex-exclude.js': `TAP version 13 ok 1 filter > foo test ok 2 filter > Bar test @@ -420,7 +419,7 @@ ok 2 filter > Bar test # todo 0 # fail 0`, - "qunit config-module.js": + 'qunit config-module.js': `TAP version 13 ok 1 Module B > Test B 1..1 @@ -429,7 +428,7 @@ ok 1 Module B > Test B # todo 0 # fail 0`, - "qunit config-moduleId.js": + 'qunit config-moduleId.js': `TAP version 13 ok 1 module A scoped > module C nested > test C1 ok 2 module D scoped > test D1 @@ -442,7 +441,7 @@ ok 5 module F flat > test F1 # todo 0 # fail 0`, - "qunit config-testId.js": + 'qunit config-testId.js': `TAP version 13 ok 1 test 2 ok 2 module A > module B > test 1 @@ -454,7 +453,7 @@ ok 4 module D > test 1 # todo 0 # fail 0`, - "qunit config-testTimeout.js": + 'qunit config-testTimeout.js': `TAP version 13 not ok 1 slow --- @@ -471,7 +470,7 @@ not ok 1 slow # todo 0 # fail 1`, - "qunit bad-callbacks/begin-throw.js": + 'qunit bad-callbacks/begin-throw.js': `TAP version 13 not ok 1 global failure --- @@ -479,13 +478,13 @@ not ok 1 global failure severity: failed stack: | Error: No dice - at /qunit/test/cli/fixtures/bad-callbacks/begin-throw.js:2:8 + at /qunit/test/cli/fixtures/bad-callbacks/begin-throw.js:2:9 at qunit.js at internal ... Bail out! Error: No dice`, - "qunit bad-callbacks/done-throw.js": + 'qunit bad-callbacks/done-throw.js': `TAP version 13 ok 1 module1 > test1 1..1 @@ -499,12 +498,12 @@ Bail out! Error: No dice severity: failed stack: | Error: No dice - at /qunit/test/cli/fixtures/bad-callbacks/done-throw.js:2:8 + at /qunit/test/cli/fixtures/bad-callbacks/done-throw.js:2:9 at qunit.js at internal ...`, - "qunit done-after-timeout.js": + 'qunit done-after-timeout.js': `TAP version 13 not ok 1 times out before scheduled done is called --- @@ -521,7 +520,7 @@ not ok 1 times out before scheduled done is called # todo 0 # fail 1`, - "qunit drooling-done.js": + 'qunit drooling-done.js': `TAP version 13 not ok 1 Test A --- @@ -531,7 +530,7 @@ not ok 1 Test A expected: undefined stack: | Error: this is an intentional error - at /qunit/test/cli/fixtures/drooling-done.js:8:8 + at /qunit/test/cli/fixtures/drooling-done.js:8:9 ... ok 2 Test B 1..2 @@ -540,7 +539,7 @@ ok 2 Test B # todo 0 # fail 1`, - "qunit drooling-extra-done.js": + 'qunit drooling-extra-done.js': `TAP version 13 ok 1 Test A not ok 2 Test B @@ -561,7 +560,7 @@ not ok 2 Test B # todo 0 # fail 1`, - "qunit drooling-extra-done-outside.js": + 'qunit drooling-extra-done-outside.js': `TAP version 13 ok 1 extra done scheduled outside any test 1..1 @@ -582,7 +581,7 @@ Bail out! Error: Unexpected release of async pause after tests finished. at internal ...`, - "qunit too-many-done-calls.js": + 'qunit too-many-done-calls.js': `TAP version 13 not ok 1 Test A --- @@ -602,7 +601,7 @@ not ok 1 Test A # todo 0 # fail 1`, - "qunit assert-expect/no-tests.js": + 'qunit assert-expect/no-tests.js': `TAP version 13 1..0 # pass 0 diff --git a/test/cli/fixtures/expected/watch-tap-outputs.js b/test/cli/fixtures/expected/watch-tap-outputs.js index 5a5029e45..714de89bb 100644 --- a/test/cli/fixtures/expected/watch-tap-outputs.js +++ b/test/cli/fixtures/expected/watch-tap-outputs.js @@ -1,5 +1,5 @@ module.exports = { - "no-change": `TAP version 13 + 'no-change': `TAP version 13 ok 1 foo 1..1 # pass 1 @@ -8,7 +8,7 @@ ok 1 foo # fail 0 Stopping QUnit...`, - "change-file": `TAP version 13 + 'change-file': `TAP version 13 ok 1 foo 1..1 # pass 1 @@ -26,7 +26,7 @@ ok 1 bar # fail 0 Stopping QUnit...`, - "add-file": `TAP version 13 + 'add-file': `TAP version 13 ok 1 foo 1..1 # pass 1 @@ -45,7 +45,7 @@ ok 2 foo # fail 0 Stopping QUnit...`, - "remove-file": `TAP version 13 + 'remove-file': `TAP version 13 ok 1 bar ok 2 foo 1..2 @@ -64,7 +64,7 @@ ok 1 foo # fail 0 Stopping QUnit...`, - "file-extensions": `TAP version 13 + 'file-extensions': `TAP version 13 ok 1 foo 1..1 # pass 1 @@ -87,7 +87,7 @@ ok 1 foo2 # fail 0 Stopping QUnit...`, - "file-extension-ts": `TAP version 13 + 'file-extension-ts': `TAP version 13 ok 1 foo 1..1 # pass 1 @@ -108,7 +108,7 @@ ok 1 foo2 # fail 0 Stopping QUnit...`, - "change-file-mid-run": `TAP version 13 + 'change-file-mid-run': `TAP version 13 File update: watching/bar.js Finishing current test and restarting... ok 1 Foo > one @@ -127,7 +127,7 @@ ok 2 Foo > two # fail 0 Stopping QUnit...`, - "add-file-after-run": `TAP version 13 + 'add-file-after-run': `TAP version 13 ok 1 Module > Test 1..1 # pass 1 diff --git a/test/cli/fixtures/fail/failure.js b/test/cli/fixtures/fail/failure.js index 0fe6394f1..ecb7959ef 100644 --- a/test/cli/fixtures/fail/failure.js +++ b/test/cli/fixtures/fail/failure.js @@ -1,9 +1,9 @@ -QUnit.module( "Failure", function() { - QUnit.test( "bad", function( assert ) { - assert.true( false ); - } ); +QUnit.module('Failure', function () { + QUnit.test('bad', function (assert) { + assert.true(false); + }); - QUnit.test( "bad again", function( assert ) { - assert.true( false ); - } ); -} ); + QUnit.test('bad again', function (assert) { + assert.true(false); + }); +}); diff --git a/test/cli/fixtures/fail/throws-match.js b/test/cli/fixtures/fail/throws-match.js index dbcf648d2..b5d619a69 100644 --- a/test/cli/fixtures/fail/throws-match.js +++ b/test/cli/fixtures/fail/throws-match.js @@ -1,7 +1,7 @@ -QUnit.module( "Throws match", function() { - QUnit.test( "bad", function( assert ) { - assert.throws( function() { - throw new Error( "Match me with a pattern" ); - }, /incorrect pattern/, "match error" ); - } ); -} ); +QUnit.module('Throws match', function () { + QUnit.test('bad', function (assert) { + assert.throws(function () { + throw new Error('Match me with a pattern'); + }, /incorrect pattern/, 'match error'); + }); +}); diff --git a/test/cli/fixtures/glob/a-test.js b/test/cli/fixtures/glob/a-test.js index f659f4b46..3ef94a9a2 100644 --- a/test/cli/fixtures/glob/a-test.js +++ b/test/cli/fixtures/glob/a-test.js @@ -1,5 +1,5 @@ -QUnit.module( "A-Test", function() { - QUnit.test( "derp", function( assert ) { - assert.true( true ); - } ); -} ); +QUnit.module('A-Test', function () { + QUnit.test('derp', function (assert) { + assert.true(true); + }); +}); diff --git a/test/cli/fixtures/glob/not-a.js b/test/cli/fixtures/glob/not-a.js index e6d3d289b..30dab9e6d 100644 --- a/test/cli/fixtures/glob/not-a.js +++ b/test/cli/fixtures/glob/not-a.js @@ -1,5 +1,5 @@ -QUnit.module( "Not-A", function() { - QUnit.test( "1", function( assert ) { - assert.true( true ); - } ); -} ); +QUnit.module('Not-A', function () { + QUnit.test('1', function (assert) { + assert.true(true); + }); +}); diff --git a/test/cli/fixtures/glob/sub/nested-test.js b/test/cli/fixtures/glob/sub/nested-test.js index 54fd01379..33c536fdb 100644 --- a/test/cli/fixtures/glob/sub/nested-test.js +++ b/test/cli/fixtures/glob/sub/nested-test.js @@ -1,5 +1,5 @@ -QUnit.module( "Nested-Test", function() { - QUnit.test( "herp", function( assert ) { - assert.true( true ); - } ); -} ); +QUnit.module('Nested-Test', function () { + QUnit.test('herp', function (assert) { + assert.true(true); + }); +}); diff --git a/test/cli/fixtures/hanging-test/index.js b/test/cli/fixtures/hanging-test/index.js index 88ad12195..bf9b62f9c 100644 --- a/test/cli/fixtures/hanging-test/index.js +++ b/test/cli/fixtures/hanging-test/index.js @@ -1,3 +1,3 @@ -QUnit.test( "hanging", function( assert ) { - assert.async(); -} ); +QUnit.test('hanging', function (assert) { + assert.async(); +}); diff --git a/test/cli/fixtures/hard-error-in-hook.js b/test/cli/fixtures/hard-error-in-hook.js index 84ba88fa9..038686226 100644 --- a/test/cli/fixtures/hard-error-in-hook.js +++ b/test/cli/fixtures/hard-error-in-hook.js @@ -1,8 +1,8 @@ -QUnit.module( "contains a hard error in hook", hooks => { - hooks.before( () => { - throw new Error( "expected error thrown in hook" ); - } ); - QUnit.test( "contains a hard error", assert => { - assert.true( true ); - } ); -} ); +QUnit.module('contains a hard error in hook', hooks => { + hooks.before(() => { + throw new Error('expected error thrown in hook'); + }); + QUnit.test('contains a hard error', assert => { + assert.true(true); + }); +}); diff --git a/test/cli/fixtures/hard-error-in-test-with-no-async-handler.js b/test/cli/fixtures/hard-error-in-test-with-no-async-handler.js index 36994c6a7..cff04e9d8 100644 --- a/test/cli/fixtures/hard-error-in-test-with-no-async-handler.js +++ b/test/cli/fixtures/hard-error-in-test-with-no-async-handler.js @@ -1,8 +1,8 @@ -QUnit.test( "contains a hard error after using `assert.async`", assert => { - assert.async(); - assert.true( true ); - throw new Error( "expected error thrown in test" ); +QUnit.test('contains a hard error after using `assert.async`', assert => { + assert.async(); + assert.true(true); + throw new Error('expected error thrown in test'); - // the "done" callback from `assert.async` should be called later, - // but the hard-error prevents the test from reaching that -} ); + // the "done" callback from `assert.async` should be called later, + // but the hard-error prevents the test from reaching that +}); diff --git a/test/cli/fixtures/hooks-global-context.js b/test/cli/fixtures/hooks-global-context.js index b38d32f84..987dd77c5 100644 --- a/test/cli/fixtures/hooks-global-context.js +++ b/test/cli/fixtures/hooks-global-context.js @@ -1,49 +1,48 @@ -QUnit.hooks.beforeEach( function() { - this.x = 1; - this.fromGlobal = true; -} ); -QUnit.hooks.afterEach( function() { - this.x = 100; -} ); +QUnit.hooks.beforeEach(function () { + this.x = 1; + this.fromGlobal = true; +}); +QUnit.hooks.afterEach(function () { + this.x = 100; +}); -QUnit.module( "A", function( hooks ) { - hooks.beforeEach( function() { - this.x = 2; - this.fromModule = true; - } ); - hooks.afterEach( function() { - this.x = 20; - } ); +QUnit.module('A', function (hooks) { + hooks.beforeEach(function () { + this.x = 2; + this.fromModule = true; + }); + hooks.afterEach(function () { + this.x = 20; + }); - QUnit.test( "A1", function( assert ) { - assert.equal( this.x, 2 ); - assert.strictEqual( this.fromGlobal, true ); - assert.strictEqual( this.fromModule, true ); - assert.strictEqual( this.fromNested, undefined ); - } ); + QUnit.test('A1', function (assert) { + assert.equal(this.x, 2); + assert.strictEqual(this.fromGlobal, true); + assert.strictEqual(this.fromModule, true); + assert.strictEqual(this.fromNested, undefined); + }); - QUnit.module( "AB", function( hooks ) { - hooks.beforeEach( function() { - this.x = 3; - this.fromNested = true; - } ); - hooks.afterEach( function() { - this.x = 30; - } ); + QUnit.module('AB', function (hooks) { + hooks.beforeEach(function () { + this.x = 3; + this.fromNested = true; + }); + hooks.afterEach(function () { + this.x = 30; + }); - QUnit.test( "AB1", function( assert ) { - assert.strictEqual( this.x, 3 ); - assert.strictEqual( this.fromGlobal, true ); - assert.strictEqual( this.fromModule, true ); - assert.strictEqual( this.fromNested, true ); - } ); - } ); -} ); - -QUnit.test( "B", function( assert ) { - assert.strictEqual( this.x, 1 ); - assert.strictEqual( this.fromGlobal, true ); - assert.strictEqual( this.fromModule, undefined ); - assert.strictEqual( this.fromNested, undefined ); -} ); + QUnit.test('AB1', function (assert) { + assert.strictEqual(this.x, 3); + assert.strictEqual(this.fromGlobal, true); + assert.strictEqual(this.fromModule, true); + assert.strictEqual(this.fromNested, true); + }); + }); +}); +QUnit.test('B', function (assert) { + assert.strictEqual(this.x, 1); + assert.strictEqual(this.fromGlobal, true); + assert.strictEqual(this.fromModule, undefined); + assert.strictEqual(this.fromNested, undefined); +}); diff --git a/test/cli/fixtures/hooks-global-order.js b/test/cli/fixtures/hooks-global-order.js index 193573f12..e5121cfdb 100644 --- a/test/cli/fixtures/hooks-global-order.js +++ b/test/cli/fixtures/hooks-global-order.js @@ -1,56 +1,55 @@ -function callback( label ) { - return function() { - console.warn( `HOOK: ${QUnit.config.current.testName} @ ${label}` ); - }; +function callback (label) { + return function () { + console.warn(`HOOK: ${QUnit.config.current.testName} @ ${label}`); + }; } -QUnit.hooks.beforeEach( callback( "global beforeEach-1" ) ); -QUnit.hooks.beforeEach( callback( "global beforeEach-2" ) ); -QUnit.hooks.afterEach( callback( "global afterEach-1" ) ); -QUnit.hooks.afterEach( callback( "global afterEach-2" ) ); - -QUnit.test( "A1", assert => { - assert.true( true ); -} ); - -QUnit.module( "B", hooks => { - hooks.before( callback( "B before" ) ); - hooks.beforeEach( callback( "B beforeEach" ) ); - hooks.afterEach( callback( "B afterEach" ) ); - hooks.after( callback( "B after" ) ); - - QUnit.test( "B1", assert => { - assert.true( true ); - } ); - - QUnit.test( "B2", assert => { - assert.true( true ); - } ); - - QUnit.module( "BC", hooks => { - hooks.before( callback( "BC before" ) ); - hooks.beforeEach( callback( "BC beforeEach" ) ); - hooks.afterEach( callback( "BC afterEach" ) ); - hooks.after( callback( "BC after" ) ); - - QUnit.test( "BC1", assert => { - assert.true( true ); - } ); - - QUnit.test( "BC2", assert => { - assert.true( true ); - } ); - - QUnit.module( "BCD", hooks => { - hooks.before( callback( "BCD before" ) ); - hooks.beforeEach( callback( "BCD beforeEach" ) ); - hooks.afterEach( callback( "BCD afterEach" ) ); - hooks.after( callback( "BCD after" ) ); - - QUnit.test( "BCD1", assert => { - assert.true( true ); - } ); - } ); - } ); -} ); - +QUnit.hooks.beforeEach(callback('global beforeEach-1')); +QUnit.hooks.beforeEach(callback('global beforeEach-2')); +QUnit.hooks.afterEach(callback('global afterEach-1')); +QUnit.hooks.afterEach(callback('global afterEach-2')); + +QUnit.test('A1', assert => { + assert.true(true); +}); + +QUnit.module('B', hooks => { + hooks.before(callback('B before')); + hooks.beforeEach(callback('B beforeEach')); + hooks.afterEach(callback('B afterEach')); + hooks.after(callback('B after')); + + QUnit.test('B1', assert => { + assert.true(true); + }); + + QUnit.test('B2', assert => { + assert.true(true); + }); + + QUnit.module('BC', hooks => { + hooks.before(callback('BC before')); + hooks.beforeEach(callback('BC beforeEach')); + hooks.afterEach(callback('BC afterEach')); + hooks.after(callback('BC after')); + + QUnit.test('BC1', assert => { + assert.true(true); + }); + + QUnit.test('BC2', assert => { + assert.true(true); + }); + + QUnit.module('BCD', hooks => { + hooks.before(callback('BCD before')); + hooks.beforeEach(callback('BCD beforeEach')); + hooks.afterEach(callback('BCD afterEach')); + hooks.after(callback('BCD after')); + + QUnit.test('BCD1', assert => { + assert.true(true); + }); + }); + }); +}); diff --git a/test/cli/fixtures/incorrect-hooks-warning/test.js b/test/cli/fixtures/incorrect-hooks-warning/test.js index 292fe1fee..89a276e9e 100644 --- a/test/cli/fixtures/incorrect-hooks-warning/test.js +++ b/test/cli/fixtures/incorrect-hooks-warning/test.js @@ -1,9 +1,9 @@ -QUnit.module( "module providing hooks", function( hooks ) { - QUnit.module( "module not providing hooks", function() { - // eslint-disable-next-line qunit/no-hooks-from-ancestor-modules - hooks.beforeEach( function() {} ); - QUnit.test( "has a test", function( assert ) { - assert.true( true ); - } ); - } ); -} ); +QUnit.module('module providing hooks', function (hooks) { + QUnit.module('module not providing hooks', function () { + // eslint-disable-next-line qunit/no-hooks-from-ancestor-modules + hooks.beforeEach(function () {}); + QUnit.test('has a test', function (assert) { + assert.true(true); + }); + }); +}); diff --git a/test/cli/fixtures/memory-leak/.eslintrc.json b/test/cli/fixtures/memory-leak/.eslintrc.json deleted file mode 100644 index a9802bea3..000000000 --- a/test/cli/fixtures/memory-leak/.eslintrc.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "parserOptions": { - "ecmaVersion": 2017 - } -} diff --git a/test/cli/fixtures/memory-leak/index.js b/test/cli/fixtures/memory-leak/index.js index f5809329e..f817d6a84 100644 --- a/test/cli/fixtures/memory-leak/index.js +++ b/test/cli/fixtures/memory-leak/index.js @@ -1,6 +1,6 @@ /* globals gc */ -const v8 = require( "v8" ); +const v8 = require('v8'); // Hold explicit references as well so that V8 will consistently // not be able to GC them until we ask it to. This allows us to @@ -8,80 +8,79 @@ const v8 = require( "v8" ); // presence and absence. const foos = new Set(); class Foo { - constructor() { - this.id = `FooNum${foos.size}`; - foos.add( this ); - } - - getId() { - return this.id.slice( 0, -1 ); - } + constructor () { + this.id = `FooNum${foos.size}`; + foos.add(this); + } + + getId () { + return this.id.slice(0, -1); + } } -function streamToString( stream ) { - const chunks = []; - return new Promise( ( resolve, reject ) => { - stream.on( "data", chunk => chunks.push( chunk ) ); - stream.on( "error", reject ); - stream.on( "end", () => resolve( Buffer.concat( chunks ).toString( "utf8" ) ) ); - } ); +function streamToString (stream) { + const chunks = []; + return new Promise((resolve, reject) => { + stream.on('data', chunk => chunks.push(chunk)); + stream.on('error', reject); + stream.on('end', () => resolve(Buffer.concat(chunks).toString('utf8'))); + }); } -QUnit.module( "some nested module", function( hooks ) { - let foo1, foo2; - - hooks.beforeEach( function() { - foo1 = new Foo(); - } ); - - hooks.afterEach( function( assert ) { - foo2 = new Foo(); - assert.equal( foo1.getId(), "FooNum" ); - assert.equal( foo2.getId(), "FooNum" ); - } ); - - QUnit.test( "can call method on foo", function( assert ) { - assert.equal( foo1.getId(), "FooNum" ); - } ); - - QUnit.module( "child module", function( hooks ) { - let foo3; - - hooks.beforeEach( function() { - foo3 = foo1; - } ); - - QUnit.test( "child test", function( assert ) { - assert.ok( foo3 ); - } ); - } ); -} ); - -QUnit.module( "later thing", function() { - QUnit.test( "has released all foos", async function( assert ) { - - // The snapshot is expected to contain entries like this: - // > "FooNum" - // It is important that the regex uses \d and that the above - // comment doesn't include a number, as otherwise we will also - // get matches for the memory of this function's source code. - const reHeap = /^.*FooNum\d.*$/gm; - - let snapshot = await streamToString( v8.getHeapSnapshot() ); - let matches = snapshot.match( reHeap ) || []; - assert.notEqual( matches.length, 0, "the before heap" ); - - snapshot = matches = null; - assert.notEqual( foos.size, 0, "foos in Set" ); - - // Comment out the below to test the failure mode - foos.clear(); - - // Requires `--expose-gc` flag to function properly. - gc(); - - snapshot = await streamToString( v8.getHeapSnapshot() ); - matches = snapshot.match( reHeap ); - assert.strictEqual( matches, null, "the after heap" ); - } ); -} ); +QUnit.module('some nested module', function (hooks) { + let foo1, foo2; + + hooks.beforeEach(function () { + foo1 = new Foo(); + }); + + hooks.afterEach(function (assert) { + foo2 = new Foo(); + assert.equal(foo1.getId(), 'FooNum'); + assert.equal(foo2.getId(), 'FooNum'); + }); + + QUnit.test('can call method on foo', function (assert) { + assert.equal(foo1.getId(), 'FooNum'); + }); + + QUnit.module('child module', function (hooks) { + let foo3; + + hooks.beforeEach(function () { + foo3 = foo1; + }); + + QUnit.test('child test', function (assert) { + assert.ok(foo3); + }); + }); +}); + +QUnit.module('later thing', function () { + QUnit.test('has released all foos', async function (assert) { + // The snapshot is expected to contain entries like this: + // > "FooNum" + // It is important that the regex uses \d and that the above + // comment doesn't include a number, as otherwise we will also + // get matches for the memory of this function's source code. + const reHeap = /^.*FooNum\d.*$/gm; + + let snapshot = await streamToString(v8.getHeapSnapshot()); + let matches = snapshot.match(reHeap) || []; + assert.notEqual(matches.length, 0, 'the before heap'); + + snapshot = matches = null; + assert.notEqual(foos.size, 0, 'foos in Set'); + + // Comment out the below to test the failure mode + foos.clear(); + + // Requires `--expose-gc` flag to function properly. + gc(); + + snapshot = await streamToString(v8.getHeapSnapshot()); + matches = snapshot.match(reHeap); + assert.strictEqual(matches, null, 'the after heap'); + }); +}); diff --git a/test/cli/fixtures/module-nested.js b/test/cli/fixtures/module-nested.js index ec79cf7f4..3bf1da596 100644 --- a/test/cli/fixtures/module-nested.js +++ b/test/cli/fixtures/module-nested.js @@ -1,37 +1,35 @@ try { - QUnit.module( "module 1", () => { - QUnit.test( "test in module 1", assert => { - assert.true( true ); - } ); - } ); -} catch ( e ) { - - // Ignore + QUnit.module('module 1', () => { + QUnit.test('test in module 1', assert => { + assert.true(true); + }); + }); +} catch (e) { + + // Ignore } try { - QUnit.module( "module 2", () => { - - // trigger an error in executeNow - undefined(); + QUnit.module('module 2', () => { + // trigger an error in executeNow + undefined(); - QUnit.test( "test in module 2", assert => { - assert.true( true ); - } ); - } ); -} catch ( e ) { + QUnit.test('test in module 2', assert => { + assert.true(true); + }); + }); +} catch (e) { - // Ignore + // Ignore } - try { - QUnit.module( "module 3", () => { - QUnit.test( "test in module 3", assert => { - assert.true( true ); - } ); - } ); -} catch ( e ) { - - // Ignore + QUnit.module('module 3', () => { + QUnit.test('test in module 3', assert => { + assert.true(true); + }); + }); +} catch (e) { + + // Ignore } diff --git a/test/cli/fixtures/noglobals/add-global.js b/test/cli/fixtures/noglobals/add-global.js index 72f71c943..5bcf4869e 100644 --- a/test/cli/fixtures/noglobals/add-global.js +++ b/test/cli/fixtures/noglobals/add-global.js @@ -1,6 +1,6 @@ QUnit.config.noglobals = true; -QUnit.test( "adds global var", assert => { - global.dummyGlobal = "hello"; - assert.true( true ); -} ); +QUnit.test('adds global var', assert => { + global.dummyGlobal = 'hello'; + assert.true(true); +}); diff --git a/test/cli/fixtures/noglobals/ignored.js b/test/cli/fixtures/noglobals/ignored.js index 2afef4abb..ea5463779 100644 --- a/test/cli/fixtures/noglobals/ignored.js +++ b/test/cli/fixtures/noglobals/ignored.js @@ -1,6 +1,6 @@ QUnit.config.noglobals = true; -QUnit.test( "adds global var", assert => { - global[ "qunit-test-output-dummy" ] = "hello"; - assert.true( true ); -} ); +QUnit.test('adds global var', assert => { + global['qunit-test-output-dummy'] = 'hello'; + assert.true(true); +}); diff --git a/test/cli/fixtures/noglobals/remove-global.js b/test/cli/fixtures/noglobals/remove-global.js index f8724e8b7..e421485a7 100644 --- a/test/cli/fixtures/noglobals/remove-global.js +++ b/test/cli/fixtures/noglobals/remove-global.js @@ -1,8 +1,8 @@ QUnit.config.noglobals = true; -global.dummyGlobal = "hello"; +global.dummyGlobal = 'hello'; -QUnit.test( "deletes global var", assert => { - delete global.dummyGlobal; - assert.true( true ); -} ); +QUnit.test('deletes global var', assert => { + delete global.dummyGlobal; + assert.true(true); +}); diff --git a/test/cli/fixtures/notrycatch/returns-rejection-in-hook.js b/test/cli/fixtures/notrycatch/returns-rejection-in-hook.js index 79e361e85..2363925d9 100644 --- a/test/cli/fixtures/notrycatch/returns-rejection-in-hook.js +++ b/test/cli/fixtures/notrycatch/returns-rejection-in-hook.js @@ -1,18 +1,17 @@ -"use strict"; +'use strict'; -process.on( "unhandledRejection", ( reason ) => { - console.log( "Unhandled Rejection:", reason ); -} ); +process.on('unhandledRejection', (reason) => { + console.log('Unhandled Rejection:', reason); +}); QUnit.config.notrycatch = true; -QUnit.module( "notrycatch", function( hooks ) { +QUnit.module('notrycatch', function (hooks) { + hooks.beforeEach(() => { + return Promise.reject('bad things happen sometimes'); + }); - hooks.beforeEach( () => { - return Promise.reject( "bad things happen sometimes" ); - } ); - - QUnit.test( "passing test", assert => { - assert.true( true ); - } ); -} ); + QUnit.test('passing test', assert => { + assert.true(true); + }); +}); diff --git a/test/cli/fixtures/notrycatch/returns-rejection.js b/test/cli/fixtures/notrycatch/returns-rejection.js index 7750448bd..0e920a87e 100644 --- a/test/cli/fixtures/notrycatch/returns-rejection.js +++ b/test/cli/fixtures/notrycatch/returns-rejection.js @@ -1,20 +1,20 @@ -"use strict"; +'use strict'; -process.on( "unhandledRejection", ( reason ) => { - console.log( "Unhandled Rejection:", reason ); -} ); +process.on('unhandledRejection', (reason) => { + console.log('Unhandled Rejection:', reason); +}); -QUnit.module( "notrycatch", function( hooks ) { - hooks.beforeEach( function() { - this.originalNotrycatch = QUnit.config.notrycatch; - QUnit.config.notrycatch = true; - } ); +QUnit.module('notrycatch', function (hooks) { + hooks.beforeEach(function () { + this.originalNotrycatch = QUnit.config.notrycatch; + QUnit.config.notrycatch = true; + }); - hooks.afterEach( function() { - QUnit.config.notrycatch = this.originalNotrycatch; - } ); + hooks.afterEach(function () { + QUnit.config.notrycatch = this.originalNotrycatch; + }); - QUnit.test( "returns a rejected promise", function() { - return Promise.reject( "bad things happen sometimes" ); - } ); -} ); + QUnit.test('returns a rejected promise', function () { + return Promise.reject('bad things happen sometimes'); + }); +}); diff --git a/test/cli/fixtures/npm-reporter/index.js b/test/cli/fixtures/npm-reporter/index.js index 8c2001cdb..587321c8c 100644 --- a/test/cli/fixtures/npm-reporter/index.js +++ b/test/cli/fixtures/npm-reporter/index.js @@ -1,13 +1,13 @@ -function NPMReporter( runner ) { - runner.on( "runEnd", this.onRunEnd ); +function NPMReporter (runner) { + runner.on('runEnd', this.onRunEnd); } -NPMReporter.init = function( runner ) { - return new NPMReporter( runner ); +NPMReporter.init = function (runner) { + return new NPMReporter(runner); }; -NPMReporter.prototype.onRunEnd = function() { - console.log( "Run ended!" ); +NPMReporter.prototype.onRunEnd = function () { + console.log('Run ended!'); }; module.exports = NPMReporter; diff --git a/test/cli/fixtures/only/module-flat.js b/test/cli/fixtures/only/module-flat.js index d292533f7..f5b87c6f0 100644 --- a/test/cli/fixtures/only/module-flat.js +++ b/test/cli/fixtures/only/module-flat.js @@ -1,27 +1,26 @@ +QUnit.module('module A'); +QUnit.test('test A', function (assert) { + assert.true(false, 'this test should not run'); +}); -QUnit.module( "module A" ); -QUnit.test( "test A", function( assert ) { - assert.true( false, "this test should not run" ); -} ); +QUnit.module.only('module B'); +QUnit.todo('test B', function (assert) { + assert.true(false, 'not implemented yet'); +}); +QUnit.skip('test C', function (assert) { + assert.true(false, 'test should be skipped'); +}); +QUnit.test('test D', function (assert) { + assert.true(true, 'this test should run'); +}); -QUnit.module.only( "module B" ); -QUnit.todo( "test B", function( assert ) { - assert.true( false, "not implemented yet" ); -} ); -QUnit.skip( "test C", function( assert ) { - assert.true( false, "test should be skipped" ); -} ); -QUnit.test( "test D", function( assert ) { - assert.true( true, "this test should run" ); -} ); - -QUnit.module( "module C" ); -QUnit.todo( "test E", function( assert ) { - assert.true( false, "not implemented yet" ); -} ); -QUnit.skip( "test F", function( assert ) { - assert.true( false, "test should be skipped" ); -} ); -QUnit.test( "test G", function( assert ) { - assert.true( false, "this test should not run" ); -} ); +QUnit.module('module C'); +QUnit.todo('test E', function (assert) { + assert.true(false, 'not implemented yet'); +}); +QUnit.skip('test F', function (assert) { + assert.true(false, 'test should be skipped'); +}); +QUnit.test('test G', function (assert) { + assert.true(false, 'this test should not run'); +}); diff --git a/test/cli/fixtures/only/module-then-test.js b/test/cli/fixtures/only/module-then-test.js index 58186aecb..90a31184e 100644 --- a/test/cli/fixtures/only/module-then-test.js +++ b/test/cli/fixtures/only/module-then-test.js @@ -1,16 +1,16 @@ // https://github.com/qunitjs/qunit/issues/1645 -QUnit.module( "module A", function() { - QUnit.test( "test A1", function( assert ) { - assert.true( false, "not run" ); - } ); +QUnit.module('module A', function () { + QUnit.test('test A1', function (assert) { + assert.true(false, 'not run'); + }); - QUnit.module.only( "module B", function() { - QUnit.test( "test B", function( assert ) { - assert.true( true, "run" ); - } ); - } ); + QUnit.module.only('module B', function () { + QUnit.test('test B', function (assert) { + assert.true(true, 'run'); + }); + }); - QUnit.test( "test A2", function( assert ) { - assert.true( false, "not run" ); - } ); -} ); + QUnit.test('test A2', function (assert) { + assert.true(false, 'not run'); + }); +}); diff --git a/test/cli/fixtures/only/module.js b/test/cli/fixtures/only/module.js index 37044ad75..2700d73e7 100644 --- a/test/cli/fixtures/only/module.js +++ b/test/cli/fixtures/only/module.js @@ -1,84 +1,84 @@ -QUnit.module( "module A", function() { - QUnit.test( "test A", function( assert ) { - assert.true( false, "this test should not run" ); - } ); -} ); +QUnit.module('module A', function () { + QUnit.test('test A', function (assert) { + assert.true(false, 'this test should not run'); + }); +}); -QUnit.module( "module B", function() { - QUnit.module( "This module should not run", function() { - QUnit.test( "normal test", function( assert ) { - assert.true( false, "this test should not run" ); - } ); - } ); +QUnit.module('module B', function () { + QUnit.module('This module should not run', function () { + QUnit.test('normal test', function (assert) { + assert.true(false, 'this test should not run'); + }); + }); - QUnit.module.only( "Only this module should run", function() { - QUnit.todo( "a todo test", function( assert ) { - assert.true( false, "not implemented yet" ); - } ); + QUnit.module.only('Only this module should run', function () { + QUnit.todo('a todo test', function (assert) { + assert.true(false, 'not implemented yet'); + }); - QUnit.skip( "implicitly skipped test", function( assert ) { - assert.true( false, "test should be skipped" ); - } ); + QUnit.skip('implicitly skipped test', function (assert) { + assert.true(false, 'test should be skipped'); + }); - QUnit.test( "normal test", function( assert ) { - assert.true( true, "this test should run" ); - } ); - } ); + QUnit.test('normal test', function (assert) { + assert.true(true, 'this test should run'); + }); + }); - QUnit.module( "This also should not run", function() { - QUnit.test( "normal test", function( assert ) { - assert.true( false, "this test should not run" ); - } ); - } ); -} ); + QUnit.module('This also should not run', function () { + QUnit.test('normal test', function (assert) { + assert.true(false, 'this test should not run'); + }); + }); +}); -QUnit.module( "module C", function() { - QUnit.test( "test C", function( assert ) { - assert.true( false, "this test should not run" ); - } ); -} ); +QUnit.module('module C', function () { + QUnit.test('test C', function (assert) { + assert.true(false, 'this test should not run'); + }); +}); -QUnit.module.only( "module D", function() { - QUnit.test( "test D", function( assert ) { - assert.true( true, "this test should run as well" ); - } ); -} ); +QUnit.module.only('module D', function () { + QUnit.test('test D', function (assert) { + assert.true(true, 'this test should run as well'); + }); +}); -QUnit.module( "module D1", function() { - QUnit.module( "module D2", function() { - QUnit.test( "test D3", function( assert ) { - assert.true( false, "this test should not run" ); - } ); - } ); -} ); +QUnit.module('module D1', function () { + QUnit.module('module D2', function () { + QUnit.test('test D3', function (assert) { + assert.true(false, 'this test should not run'); + }); + }); +}); -QUnit.module.only( "module E", function() { - QUnit.module( "module F", function() { - QUnit.test( "test F", function( assert ) { - assert.true( true, "this test should run as well" ); - } ); - } ); +QUnit.module.only('module E', function () { + QUnit.module('module F', function () { + QUnit.test('test F', function (assert) { + assert.true(true, 'this test should run as well'); + }); + }); - QUnit.test( "test E", function( assert ) { - assert.true( true, "this test should run as well" ); - } ); -} ); + QUnit.test('test E', function (assert) { + assert.true(true, 'this test should run as well'); + }); +}); -QUnit.module.skip( "module G", function() { - QUnit.module.only( "module H", function() { - QUnit.test( "test H", function( assert ) { - assert.true( false, "this test should not run" ); - } ); - } ); +QUnit.module.skip('module G', function () { + QUnit.module.only('module H', function () { + QUnit.test('test H', function (assert) { + assert.true(false, 'this test should not run'); + }); + }); - QUnit.test( "test G", function( assert ) { - assert.true( false, "this test should not run" ); - } ); -} ); + QUnit.test('test G', function (assert) { + assert.true(false, 'this test should not run'); + }); +}); -QUnit.module.todo( "module I", function() { - QUnit.test( "test I", function( assert ) { - assert.true( false, "this test should not run" ); - } ); -} ); +QUnit.module.todo('module I', function () { + QUnit.test('test I', function (assert) { + assert.true(false, 'this test should not run'); + }); +}); diff --git a/test/cli/fixtures/only/test.js b/test/cli/fixtures/only/test.js index 5bbd2029e..39b3fb848 100644 --- a/test/cli/fixtures/only/test.js +++ b/test/cli/fixtures/only/test.js @@ -1,15 +1,15 @@ -QUnit.test( "implicitly skipped test", function( assert ) { - assert.true( false, "test should be skipped" ); -} ); +QUnit.test('implicitly skipped test', function (assert) { + assert.true(false, 'test should be skipped'); +}); -QUnit.only( "run this test", function( assert ) { - assert.true( true, "only this test should run" ); -} ); +QUnit.only('run this test', function (assert) { + assert.true(true, 'only this test should run'); +}); -QUnit.test( "another implicitly skipped test", function( assert ) { - assert.true( false, "test should be skipped" ); -} ); +QUnit.test('another implicitly skipped test', function (assert) { + assert.true(false, 'test should be skipped'); +}); -QUnit.only( "all tests with only run", function( assert ) { - assert.true( true, "this test should run as well" ); -} ); +QUnit.only('all tests with only run', function (assert) { + assert.true(true, 'this test should run as well'); +}); diff --git a/test/cli/fixtures/single.js b/test/cli/fixtures/single.js index 82678c447..862c3f43b 100644 --- a/test/cli/fixtures/single.js +++ b/test/cli/fixtures/single.js @@ -1,5 +1,5 @@ -QUnit.module( "Single", function() { - QUnit.test( "has a test", function( assert ) { - assert.true( true ); - } ); -} ); +QUnit.module('Single', function () { + QUnit.test('has a test', function (assert) { + assert.true(true); + }); +}); diff --git a/test/cli/fixtures/sourcemap/source.js b/test/cli/fixtures/sourcemap/source.js index a1b960ed1..e13e088bc 100644 --- a/test/cli/fixtures/sourcemap/source.js +++ b/test/cli/fixtures/sourcemap/source.js @@ -1,9 +1,9 @@ -QUnit.module( "Example", function() { - QUnit.test( "good", function( assert ) { - assert.true( true ); - } ); +QUnit.module('Example', function () { + QUnit.test('good', function (assert) { + assert.true(true); + }); - QUnit.test( "bad", function( assert ) { - assert.true( false ); - } ); -} ); + QUnit.test('bad', function (assert) { + assert.true(false); + }); +}); diff --git a/test/cli/fixtures/test/first.js b/test/cli/fixtures/test/first.js index d781e555d..18f566ee3 100644 --- a/test/cli/fixtures/test/first.js +++ b/test/cli/fixtures/test/first.js @@ -1,5 +1,5 @@ -QUnit.module( "First", function() { - QUnit.test( "1", function( assert ) { - assert.true( true ); - } ); -} ); +QUnit.module('First', function () { + QUnit.test('1', function (assert) { + assert.true(true); + }); +}); diff --git a/test/cli/fixtures/test/nested/second.js b/test/cli/fixtures/test/nested/second.js index 6ce20db3a..75b61920d 100644 --- a/test/cli/fixtures/test/nested/second.js +++ b/test/cli/fixtures/test/nested/second.js @@ -1,5 +1,5 @@ -QUnit.module( "Second", function() { - QUnit.test( "1", function( assert ) { - assert.true( true ); - } ); -} ); +QUnit.module('Second', function () { + QUnit.test('1', function (assert) { + assert.true(true); + }); +}); diff --git a/test/cli/fixtures/timeout/index.js b/test/cli/fixtures/timeout/index.js index a9a440a52..428716f19 100644 --- a/test/cli/fixtures/timeout/index.js +++ b/test/cli/fixtures/timeout/index.js @@ -1,14 +1,14 @@ -QUnit.module( "timeout", function() { - QUnit.test( "first", function( assert ) { - assert.timeout( 10 ); +QUnit.module('timeout', function () { + QUnit.test('first', function (assert) { + assert.timeout(10); - return new Promise( resolve => setTimeout( resolve, 20 ) ); - } ); + return new Promise(resolve => setTimeout(resolve, 20)); + }); - QUnit.test( "second", function( assert ) { - return new Promise( resolve => setTimeout( resolve, 20 ) ) - .then( () => { - assert.true( true, "This promise resolved" ); - } ); - } ); -} ); + QUnit.test('second', function (assert) { + return new Promise(resolve => setTimeout(resolve, 20)) + .then(() => { + assert.true(true, 'This promise resolved'); + }); + }); +}); diff --git a/test/cli/fixtures/too-many-done-calls.js b/test/cli/fixtures/too-many-done-calls.js index f250617ed..0261b95b8 100644 --- a/test/cli/fixtures/too-many-done-calls.js +++ b/test/cli/fixtures/too-many-done-calls.js @@ -1,6 +1,6 @@ -QUnit.test( "Test A", assert => { - assert.ok( true ); - const done = assert.async(); - done(); - done(); -} ); +QUnit.test('Test A', assert => { + assert.ok(true); + const done = assert.async(); + done(); + done(); +}); diff --git a/test/cli/fixtures/unhandled-rejection.js b/test/cli/fixtures/unhandled-rejection.js index 006117ca3..6481c4560 100644 --- a/test/cli/fixtures/unhandled-rejection.js +++ b/test/cli/fixtures/unhandled-rejection.js @@ -1,18 +1,18 @@ -"use strict"; +'use strict'; -QUnit.module( "Unhandled Rejections", function() { - QUnit.test( "test passes just fine, but has a rejected promise", function( assert ) { - assert.true( true ); +QUnit.module('Unhandled Rejections', function () { + QUnit.test('test passes just fine, but has a rejected promise', function (assert) { + assert.true(true); - const done = assert.async(); + const done = assert.async(); - Promise.resolve().then( function() { - throw new Error( "Error thrown in non-returned promise!" ); - } ); + Promise.resolve().then(function () { + throw new Error('Error thrown in non-returned promise!'); + }); - // prevent test from exiting before unhandled rejection fires - setTimeout( done, 10 ); - } ); + // prevent test from exiting before unhandled rejection fires + setTimeout(done, 10); + }); - Promise.reject( new Error( "outside of a test context" ) ); -} ); + Promise.reject(new Error('outside of a test context')); +}); diff --git a/test/cli/fixtures/zero-assertions.js b/test/cli/fixtures/zero-assertions.js index 1d3bda370..c802d9dd0 100644 --- a/test/cli/fixtures/zero-assertions.js +++ b/test/cli/fixtures/zero-assertions.js @@ -1,8 +1,8 @@ -QUnit.module( "Zero assertions", function() { - QUnit.test( "has a test", function( assert ) { - assert.expect( 0 ); +QUnit.module('Zero assertions', function () { + QUnit.test('has a test', function (assert) { + assert.expect(0); - // A test may expect zero assertions if its main purpose - // is to ensure there are no no run-time exceptions. - } ); -} ); + // A test may expect zero assertions if its main purpose + // is to ensure there are no no run-time exceptions. + }); +}); diff --git a/test/cli/helpers/execute.js b/test/cli/helpers/execute.js index 8c09a0c1c..2ea55f68e 100644 --- a/test/cli/helpers/execute.js +++ b/test/cli/helpers/execute.js @@ -1,59 +1,50 @@ -"use strict"; +'use strict'; -const path = require( "path" ); -const cp = require( "child_process" ); +const path = require('path'); +const cp = require('child_process'); const reEscape = /([\\{}()|.?*+\-^$[\]])/g; // Apply light normalization to CLI output to allow strict string // comparison across Node versions and OS platforms against the // expected output in fixtures/. -function normalize( actual ) { - const dir = path.join( __dirname, "..", "..", ".." ); - const reDir = new RegExp( dir.replace( reEscape, "\\$1" ), "g" ); - const reSep = new RegExp( path.sep.replace( reEscape, "\\$1" ), "g" ); - - return actual - .replace( reDir, "/qunit" ) - - // Replace backslashes (\) in stack traces on Windows to POSIX - .replace( reSep, "/" ) - - // Convert "at processModule (/qunit/qunit/qunit.js:1:2)" to "at qunit.js" - // Convert "at /qunit/qunit/qunit.js:1:2" to "at qunit.js" - .replace( /^(\s+at ).*\/qunit\/qunit\/qunit\.js.*$/gm, "$1qunit.js" ) - - // Convert any "/qunit/qunit/qunit.js:1:2" to "/qunit/qunit/qunit.js" - .replace( /(\/qunit\/qunit\/qunit\.js):\d+:\d+/g, "$1" ) - - // Strip inferred names for anonymous test closures (as Node 10 did), - // to match the output of Node 12+. - // Convert "at QUnit.done (/qunit/test/foo.js:1:2)" to "at /qunit/test/foo.js:1:2" - .replace( /\b(at )\S+ \((\/qunit\/test\/[^:]+:\d+:\d+)\)/g, "$1$2" ) - - // convert sourcemap'ed traces from Node 14 and earlier to the - // standard format used by Node 15+. - // https://github.com/nodejs/node/commit/15804e0b3f - // https://github.com/nodejs/node/pull/37252 - // Convert "at foo (/min.js:1)\n -> /src.js:2" to "at foo (/src.js:2)" - .replace( /\b(at [^(]+\s\()[^)]+(\))\n\s+-> ([^\n]+)/g, "$1$3$2" ) - - // CJS-style internal traces: - // Convert "at load (internal/modules/cjs/loader.js:7)" to "at internal" - // - // ESM-style internal traces from Node 14+: - // Convert "at wrap (node:internal/modules/cjs/loader:1)" to "at internal" - .replace( / {2}at .+\([^/)][^)]*\)/g, " at internal" ) - - // Strip frames from indirect nyc dependencies that are specific - // to code coverage jobs: - // Convert "at load (/qunit/node_modules/append-transform/index.js:6" to "at internal" - .replace( / {2}at .+\/.*node_modules\/append-transform\/.*\)/g, " at internal" ) - - // Consolidate subsequent qunit.js frames - .replace( /^(\s+at qunit\.js$)(\n\s+at qunit\.js$)+/gm, "$1" ) - - // Consolidate subsequent internal frames - .replace( /^(\s+at internal$)(\n\s+at internal$)+/gm, "$1" ); +function normalize (actual) { + const dir = path.join(__dirname, '..', '..', '..'); + const reDir = new RegExp(dir.replace(reEscape, '\\$1'), 'g'); + const reSep = new RegExp(path.sep.replace(reEscape, '\\$1'), 'g'); + + return actual + .replace(reDir, '/qunit') + // Replace backslashes (\) in stack traces on Windows to POSIX + .replace(reSep, '/') + // Convert "at processModule (/qunit/qunit/qunit.js:1:2)" to "at qunit.js" + // Convert "at /qunit/qunit/qunit.js:1:2" to "at qunit.js" + .replace(/^(\s+at ).*\/qunit\/qunit\/qunit\.js.*$/gm, '$1qunit.js') + // Convert any "/qunit/qunit/qunit.js:1:2" to "/qunit/qunit/qunit.js" + .replace(/(\/qunit\/qunit\/qunit\.js):\d+:\d+/g, '$1') + // Strip inferred names for anonymous test closures (as Node 10 did), + // to match the output of Node 12+. + // Convert "at QUnit.done (/qunit/test/foo.js:1:2)" to "at /qunit/test/foo.js:1:2" + .replace(/\b(at )\S+ \((\/qunit\/test\/[^:]+:\d+:\d+)\)/g, '$1$2') + // Convert sourcemap'ed traces from Node 14 and earlier to the + // standard format used by Node 15+. + // https://github.com/nodejs/node/commit/15804e0b3f + // https://github.com/nodejs/node/pull/37252 + // Convert "at foo (/min.js:1)\n -> /src.js:2" to "at foo (/src.js:2)" + .replace(/\b(at [^(]+\s\()[^)]+(\))\n\s+-> ([^\n]+)/g, '$1$3$2') + // CJS-style internal traces: + // Convert "at load (internal/modules/cjs/loader.js:7)" to "at internal" + // + // ESM-style internal traces from Node 14+: + // Convert "at wrap (node:internal/modules/cjs/loader:1)" to "at internal" + .replace(/ {2}at .+\([^/)][^)]*\)/g, ' at internal') + // Strip frames from indirect nyc dependencies that are specific + // to code coverage jobs: + // Convert "at load (/qunit/node_modules/append-transform/index.js:6" to "at internal" + .replace(/ {2}at .+\/.*node_modules\/append-transform\/.*\)/g, ' at internal') + // Consolidate subsequent qunit.js frames + .replace(/^(\s+at qunit\.js$)(\n\s+at qunit\.js$)+/gm, '$1') + // Consolidate subsequent internal frames + .replace(/^(\s+at internal$)(\n\s+at internal$)+/gm, '$1'); } /** @@ -68,72 +59,72 @@ function normalize( actual ) { * @param {Object} [options.env] * @param {Function} [hook] */ -module.exports.execute = async function execute( command, options = {}, hook ) { - options.cwd = path.join( __dirname, "..", "fixtures" ); - - // Inherit no environment by default - // Without this, tests may fail from inheriting FORCE_COLOR=1 - options.env = options.env || {}; - - // Avoid Windows-specific issue where otherwise 'foo/bar' is seen as a directory - // named "'foo/" (including the single quote). - options.windowsVerbatimArguments = true; - - let cmd = command[ 0 ]; - const args = command.slice( 1 ); - if ( cmd === "qunit" ) { - cmd = "../../../bin/qunit.js"; - args.unshift( cmd ); - cmd = process.execPath; - } - if ( cmd === "node" ) { - cmd = process.execPath; - } - - const spawned = cp.spawn( cmd, args, options ); - - if ( hook ) { - hook( spawned ); - } - - const result = { - code: null, - stdout: "", - stderr: "" - }; - spawned.stdout.on( "data", data => { - result.stdout += data; - } ); - spawned.stderr.on( "data", data => { - result.stderr += data; - } ); - const execPromise = new Promise( ( resolve, reject ) => { - spawned.on( "error", error => { - reject( error ); - } ); - spawned.on( "exit", ( exitCode, _signal ) => { - result.code = exitCode; - const stderr = normalize( String( result.stderr ).trimEnd() ); - if ( exitCode !== 0 ) { - reject( new Error( "Error code " + exitCode + "\n" + ( stderr || result.stdout ) ) ); - } else { - resolve(); - } - } ); - } ); - - try { - await execPromise; - result.stdout = normalize( String( result.stdout ).trimEnd() ); - result.stderr = String( result.stderr ).trimEnd(); - return result; - } catch ( e ) { - e.pid = result.pid; - e.code = result.code; - e.stdout = normalize( String( result.stdout ).trimEnd() ); - e.stderr = normalize( String( result.stderr ).trimEnd() ); - throw e; - } +module.exports.execute = async function execute (command, options = {}, hook) { + options.cwd = path.join(__dirname, '..', 'fixtures'); + + // Inherit no environment by default + // Without this, tests may fail from inheriting FORCE_COLOR=1 + options.env = options.env || {}; + + // Avoid Windows-specific issue where otherwise 'foo/bar' is seen as a directory + // named "'foo/" (including the single quote). + options.windowsVerbatimArguments = true; + + let cmd = command[0]; + const args = command.slice(1); + if (cmd === 'qunit') { + cmd = '../../../bin/qunit.js'; + args.unshift(cmd); + cmd = process.execPath; + } + if (cmd === 'node') { + cmd = process.execPath; + } + + const spawned = cp.spawn(cmd, args, options); + + if (hook) { + hook(spawned); + } + + const result = { + code: null, + stdout: '', + stderr: '' + }; + spawned.stdout.on('data', data => { + result.stdout += data; + }); + spawned.stderr.on('data', data => { + result.stderr += data; + }); + const execPromise = new Promise((resolve, reject) => { + spawned.on('error', error => { + reject(error); + }); + spawned.on('exit', (exitCode, _signal) => { + result.code = exitCode; + const stderr = normalize(String(result.stderr).trimEnd()); + if (exitCode !== 0) { + reject(new Error('Error code ' + exitCode + '\n' + (stderr || result.stdout))); + } else { + resolve(); + } + }); + }); + + try { + await execPromise; + result.stdout = normalize(String(result.stdout).trimEnd()); + result.stderr = String(result.stderr).trimEnd(); + return result; + } catch (e) { + e.pid = result.pid; + e.code = result.code; + e.stdout = normalize(String(result.stdout).trimEnd()); + e.stderr = normalize(String(result.stderr).trimEnd()); + throw e; + } }; module.exports.normalize = normalize; @@ -141,10 +132,9 @@ module.exports.normalize = normalize; // Very loose command formatter. // Not for the purpose of execution, but for the purpose // of formatting the string key in fixtures/ files. -module.exports.prettyPrintCommand = function prettyPrintCommand( command ) { - return command.map( arg => { - - // Quote spaces and stars - return /[ *]/.test( arg ) ? `'${ arg }'` : arg; - } ).join( " " ); +module.exports.prettyPrintCommand = function prettyPrintCommand (command) { + return command.map(arg => { + // Quote spaces and stars + return /[ *]/.test(arg) ? `'${arg}'` : arg; + }).join(' '); }; diff --git a/test/cli/require-qunit-test.js b/test/cli/require-qunit-test.js index 276579caa..f666641ee 100644 --- a/test/cli/require-qunit-test.js +++ b/test/cli/require-qunit-test.js @@ -1,46 +1,45 @@ -const proxyquire = require( "proxyquire" ).noCallThru(); -const path = require( "path" ); -const resolveStub = ( path ) => { - return path; +const proxyquire = require('proxyquire').noCallThru(); +const path = require('path'); +const resolveStub = (path) => { + return path; }; -QUnit.module( "requireQUnit", function( hooks ) { - const cwd = process.cwd(); - - hooks.before( () => { - process.chdir( cwd ); - } ); - hooks.afterEach( () => { - process.chdir( cwd ); - } ); - - QUnit.test( "finds QUnit package in the current working directory", function( assert ) { - const requireQUnit = require( "../../src/cli/require-qunit" ); - process.chdir( path.join( __dirname, "./fixtures/require-from-cwd" ) ); - - assert.propEqual( requireQUnit(), { version: "from-cwd" } ); - } ); - - // For development mode invoked locally, - // or for global install without local dependency installed. - QUnit.test( "finds relative self", function( assert ) { - const globalQUnit = { - "version": "from-global" - }; - const requireQUnit = proxyquire( "../../src/cli/require-qunit", { - "qunit": null, - "../../qunit/qunit": globalQUnit - } ); - assert.strictEqual( requireQUnit( resolveStub ), globalQUnit ); - } ); - - QUnit.test( "throws error if none of the modules are found", function( assert ) { - const requireQUnit = proxyquire( "../../src/cli/require-qunit", { - "qunit": null, - "../../qunit/qunit": null - } ); - - assert.throws( requireQUnit, /Cannot find module/ ); - } ); -} ); - +QUnit.module('requireQUnit', function (hooks) { + const cwd = process.cwd(); + + hooks.before(() => { + process.chdir(cwd); + }); + hooks.afterEach(() => { + process.chdir(cwd); + }); + + QUnit.test('finds QUnit package in the current working directory', function (assert) { + const requireQUnit = require('../../src/cli/require-qunit'); + process.chdir(path.join(__dirname, './fixtures/require-from-cwd')); + + assert.propEqual(requireQUnit(), { version: 'from-cwd' }); + }); + + // For development mode invoked locally, + // or for global install without local dependency installed. + QUnit.test('finds relative self', function (assert) { + const globalQUnit = { + version: 'from-global' + }; + const requireQUnit = proxyquire('../../src/cli/require-qunit', { + qunit: null, + '../../qunit/qunit': globalQUnit + }); + assert.strictEqual(requireQUnit(resolveStub), globalQUnit); + }); + + QUnit.test('throws error if none of the modules are found', function (assert) { + const requireQUnit = proxyquire('../../src/cli/require-qunit', { + qunit: null, + '../../qunit/qunit': null + }); + + assert.throws(requireQUnit, /Cannot find module/); + }); +}); diff --git a/test/cli/structure.js b/test/cli/structure.js index a55f4a2d4..3cc797254 100644 --- a/test/cli/structure.js +++ b/test/cli/structure.js @@ -1,84 +1,83 @@ -const fs = require( "fs" ); -const glob = require( "tiny-glob/sync" ); +const fs = require('fs'); +const path = require('path'); + +const glob = require('tiny-glob/sync'); // This is a meta test to validate structural expectations of our // tests, such as checking for fixture files that aren't used or // missing from one of the test targets. -QUnit.module( "structure", () => { - - QUnit.module( "test/main/*.js", () => { - const files = fs.readdirSync( __dirname + "/../main/" ) - .map( file => `main/${file}` ); - - QUnit.test( "files", assert => { - assert.true( files.length > 5, "found files" ); - assert.deepEqual( - files.filter( file => file.endsWith( ".js" ) ), - files, - "js files" - ); - } ); - - QUnit.test( "test/index.html", assert => { - const contents = fs.readFileSync( __dirname + "/../index.html", "utf8" ); - files.forEach( file => { - assert.true( contents.includes( file ), file ); - } ); - } ); - - QUnit.test( "Gruntfile#test-on-node", assert => { - const raw = fs.readFileSync( __dirname + "/../../Gruntfile.js", "utf8" ); - const contents = raw.match( /test-on-node.*?\{.*?\}/s )[ 0 ]; - - files.forEach( file => { - assert.true( contents.includes( file ), file ); - } ); - } ); - - QUnit.test( "test/mozjs", assert => { - const contents = fs.readFileSync( __dirname + "/../mozjs.js", "utf8" ); - files.forEach( file => { - assert.true( contents.includes( file ), file ); - } ); - } ); - - QUnit.test( "test/webWorker-worker.js", assert => { - const contents = fs.readFileSync( __dirname + "/../webWorker-worker.js", "utf8" ); - files.forEach( file => { - assert.true( contents.includes( file ), file ); - } ); - } ); - } ); - - QUnit.module( "test/**.html", () => { - - // Get a list of the HTML files, including in subdirectories (e.g. "test/reporter-html/"). - const files = glob( "**/*.html", { - cwd: __dirname + "/../", - filesOnly: true - } ) - - // Normalize Windows-style line endings as yielded by glob. - // The expected HTML paths use Unix-style line ending, as per HTTP. - .map( file => file.replace( /\\/g, "/" ) ) - - // Ignore file names containing "--", which are subresources (e.g. iframes). - // Ignore integration/grunt-contrib-qunit, which is managed separately. - .filter( file => !file.includes( "--" ) && !file.includes( "integration/" ) ) - .map( file => `test/${file}` ); - - QUnit.test( "files", assert => { - assert.true( files.length > 5, "found files" ); - } ); - - QUnit.test( "Gruntfile", assert => { - const raw = fs.readFileSync( __dirname + "/../../Gruntfile.js", "utf8" ); - const contents = raw.match( /@HTML_FILES.*?\[.*?\]/s )[ 0 ]; - - files.forEach( file => { - assert.true( contents.includes( file ), file ); - } ); - } ); - } ); -} ); - +QUnit.module('structure', () => { + QUnit.module('test/main/*.js', () => { + const files = fs.readdirSync(path.join(__dirname, '..', 'main')) + .map(file => `main/${file}`); + + QUnit.test('files', assert => { + assert.true(files.length > 5, 'found files'); + assert.deepEqual( + files.filter(file => file.endsWith('.js')), + files, + 'js files' + ); + }); + + QUnit.test('test/index.html', assert => { + const contents = fs.readFileSync(path.join(__dirname, '..', 'index.html'), 'utf8'); + files.forEach(file => { + assert.true(contents.includes(file), file); + }); + }); + + QUnit.test('Gruntfile#test-on-node', assert => { + const raw = fs.readFileSync(path.join(__dirname, '..', '..', 'Gruntfile.js'), 'utf8'); + const contents = raw.match(/test-on-node.*?\{.*?\}/s)[0]; + + files.forEach(file => { + assert.true(contents.includes(file), file); + }); + }); + + QUnit.test('test/mozjs', assert => { + const contents = fs.readFileSync(path.join(__dirname, '..', 'mozjs.js'), 'utf8'); + files.forEach(file => { + assert.true(contents.includes(file), file); + }); + }); + + QUnit.test('test/webWorker-worker.js', assert => { + const contents = fs.readFileSync(path.join(__dirname, '..', 'webWorker-worker.js'), 'utf8'); + files.forEach(file => { + assert.true(contents.includes(file), file); + }); + }); + }); + + QUnit.module('test/**.html', () => { + // Get a list of the HTML files, including in subdirectories (e.g. "test/reporter-html/"). + const files = glob('**/*.html', { + cwd: path.join(__dirname, '..'), + filesOnly: true + }) + + // Normalize Windows-style line endings as yielded by glob. + // The expected HTML paths use Unix-style line ending, as per HTTP. + .map(file => file.replace(/\\/g, '/')) + + // Ignore file names containing "--", which are subresources (e.g. iframes). + // Ignore integration/grunt-contrib-qunit, which is managed separately. + .filter(file => !file.includes('--') && !file.includes('integration')) + .map(file => `test/${file}`); + + QUnit.test('files', assert => { + assert.true(files.length > 5, 'found files'); + }); + + QUnit.test('Gruntfile', assert => { + const raw = fs.readFileSync(path.join(__dirname, '..', '..', 'Gruntfile.js'), 'utf8'); + const contents = raw.match(/@HTML_FILES.*?\[.*?\]/s)[0]; + + files.forEach(file => { + assert.true(contents.includes(file), file); + }); + }); + }); +}); diff --git a/test/cli/utils.js b/test/cli/utils.js index b934e0f68..9f4dfc15f 100644 --- a/test/cli/utils.js +++ b/test/cli/utils.js @@ -1,27 +1,27 @@ -const { getIgnoreList } = require( "../../src/cli/utils" ); -const { normalize } = require( "./helpers/execute" ); +const { getIgnoreList } = require('../../src/cli/utils'); +const { normalize } = require('./helpers/execute'); -QUnit.module( "getIgnoreList", function() { - QUnit.test( "reads .gitignore", function( assert ) { - const ignoreList = getIgnoreList( "test/cli/fixtures" ); - assert.deepEqual( ignoreList, [ "/abcd", "/efgh" ] ); - } ); -} ); +QUnit.module('getIgnoreList', function () { + QUnit.test('reads .gitignore', function (assert) { + const ignoreList = getIgnoreList('test/cli/fixtures'); + assert.deepEqual(ignoreList, ['/abcd', '/efgh']); + }); +}); -QUnit.module( "execute", function() { - QUnit.test( "normalize strips append-transform", function( assert ) { - assert.equal( - normalize( ` +QUnit.module('execute', function () { + QUnit.test('normalize strips append-transform', function (assert) { + assert.equal( + normalize(` at Object. (/qunit/temp/foo.js:3) at internal at Module.replacementCompile (/qunit/node_modules/append-transform/index.js:60:13) at internal at Object. (/qunit/node_modules/append-transform/index.js:64:4) at internal` - ), - ` + ), + ` at Object. (/qunit/temp/foo.js:3) at internal` - ); - } ); -} ); + ); + }); +}); diff --git a/test/es2018/.eslintrc.json b/test/es2018/.eslintrc.json deleted file mode 100644 index f420f338f..000000000 --- a/test/es2018/.eslintrc.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "parserOptions": { - "ecmaVersion": 2018, - "sourceType": "module" - } -} diff --git a/test/es2018/async-functions.js b/test/es2018/async-functions.js index d12a7c3c1..04e88bcdd 100644 --- a/test/es2018/async-functions.js +++ b/test/es2018/async-functions.js @@ -1,34 +1,34 @@ -QUnit.module( "modules with async hooks", hooks => { - hooks.before( async assert => { assert.step( "before" ); } ); - hooks.beforeEach( async assert => { assert.step( "beforeEach" ); } ); - hooks.afterEach( async assert => { assert.step( "afterEach" ); } ); +QUnit.module('modules with async hooks', hooks => { + hooks.before(async assert => { assert.step('before'); }); + hooks.beforeEach(async assert => { assert.step('beforeEach'); }); + hooks.afterEach(async assert => { assert.step('afterEach'); }); - hooks.after( assert => { - assert.verifySteps( [ - "before", - "beforeEach", - "afterEach" - ] ); - } ); + hooks.after(assert => { + assert.verifySteps([ + 'before', + 'beforeEach', + 'afterEach' + ]); + }); - QUnit.test( "all hooks", assert => { - assert.expect( 4 ); - } ); -} ); + QUnit.test('all hooks', assert => { + assert.expect(4); + }); +}); -QUnit.module( "before/beforeEach/afterEach/after", { - before: async assert => { assert.step( "before" ); }, - beforeEach: async assert => { assert.step( "beforeEach" ); }, - afterEach: async assert => { assert.step( "afterEach" ); }, - after: async assert => { - assert.verifySteps( [ - "before", - "beforeEach", - "afterEach" - ] ); - } -} ); +QUnit.module('before/beforeEach/afterEach/after', { + before: async assert => { assert.step('before'); }, + beforeEach: async assert => { assert.step('beforeEach'); }, + afterEach: async assert => { assert.step('afterEach'); }, + after: async assert => { + assert.verifySteps([ + 'before', + 'beforeEach', + 'afterEach' + ]); + } +}); -QUnit.test( "async hooks order", assert => { - assert.expect( 4 ); -} ); +QUnit.test('async hooks order', assert => { + assert.expect(4); +}); diff --git a/test/es2018/esm.mjs b/test/es2018/esm.mjs index ec0bbfb58..ca17f7a3c 100644 --- a/test/es2018/esm.mjs +++ b/test/es2018/esm.mjs @@ -1,9 +1,7 @@ -import sum from "./sum.mjs"; +import sum from './sum.mjs'; -QUnit.module( "ESM test suite", () => { - - QUnit.test( "sum()", assert => { - assert.equal( 5, sum( 2, 3 ) ); - } ); - -} ); +QUnit.module('ESM test suite', () => { + QUnit.test('sum()', assert => { + assert.equal(5, sum(2, 3)); + }); +}); diff --git a/test/es2018/rejects.js b/test/es2018/rejects.js index 77474d65d..7039d6655 100644 --- a/test/es2018/rejects.js +++ b/test/es2018/rejects.js @@ -1,79 +1,77 @@ -QUnit.module( "assert" ); +QUnit.module('assert'); -function CustomError( message ) { - this.message = message; +function CustomError (message) { + this.message = message; } -CustomError.prototype.toString = function() { - return this.message; +CustomError.prototype.toString = function () { + return this.message; }; -QUnit.test( "rejects", function( assert ) { - assert.expect( 1 ); +QUnit.test('rejects', function (assert) { + assert.expect(1); - assert.rejects( - Promise.reject( new CustomError( "some error description" ) ), - err => { - return err instanceof CustomError && /description/.test( err ); - }, - "custom validation function" - ); -} ); + assert.rejects( + Promise.reject(new CustomError('some error description')), + err => { + return err instanceof CustomError && /description/.test(err); + }, + 'custom validation function' + ); +}); -QUnit.test( "rejects with expected class", function( assert ) { - assert.expect( 1 ); +QUnit.test('rejects with expected class', function (assert) { + assert.expect(1); - class CustomError extends Error {} + class CustomError extends Error {} - assert.rejects( - Promise.reject( new CustomError( "foo" ) ), - CustomError, - "Expected value is a class extending Error" - ); -} ); + assert.rejects( + Promise.reject(new CustomError('foo')), + CustomError, + 'Expected value is a class extending Error' + ); +}); -QUnit.module( "failing assertions", { - beforeEach: function( assert ) { - const original = assert.pushResult; - assert.pushResult = function( resultInfo ) { +QUnit.module('failing assertions', { + beforeEach: function (assert) { + const original = assert.pushResult; + assert.pushResult = function (resultInfo) { + // Inverts the result so we can test failing assertions + resultInfo.result = !resultInfo.result; + original.call(this, resultInfo); + }; + } +}, function () { + QUnit.test('rejects', function (assert) { + assert.rejects( + Promise.reject(new Error('foo')), + () => false, + 'rejects fails when expected function returns false' + ); + }); - // Inverts the result so we can test failing assertions - resultInfo.result = !resultInfo.result; - original.call( this, resultInfo ); - }; - } -}, function() { - QUnit.test( "rejects", function( assert ) { - assert.rejects( - Promise.reject(), - () => false, - "rejects fails when expected function returns false" - ); - } ); + QUnit.module('inspect expected values', { + beforeEach: function (assert) { + const original = assert.pushResult; + assert.pushResult = function (resultInfo) { + // avoid circular asserts and use if/throw to verify + if (resultInfo.expected !== "TypeError: Class constructor CustomError cannot be invoked without 'new'") { + throw new Error('Unexpected value: ' + resultInfo.expected); + } - QUnit.module( "inspect expected values", { - beforeEach: function( assert ) { - const original = assert.pushResult; - assert.pushResult = function( resultInfo ) { + // invoke the "outer" pushResult, which still inverts the result for negative testing + original.call(this, resultInfo); + }; + } + }, function () { + QUnit.test('does not die when class is expected', function (assert) { + class CustomError extends Error {} - // avoid circular asserts and use if/throw to verify - if ( resultInfo.expected !== "TypeError: Class constructor CustomError cannot be invoked without 'new'" ) { - throw new Error( "Unexpected value: " + resultInfo.expected ); - } - - // invoke the "outer" pushResult, which still inverts the result for negative testing - original.call( this, resultInfo ); - }; - } - }, function() { - QUnit.test( "does not die when class is expected", function( assert ) { - class CustomError extends Error {} - - assert.rejects( - Promise.reject( new Error( "foo" ) ), - CustomError, - "rejects fails gracefully when expected value class does not use 'new'" - ); - } ); - } ); -} ); + assert.rejects( + Promise.reject(new Error('foo')), + CustomError, + "rejects fails gracefully when expected value class does not use 'new'" + ); + }); + }); +}); diff --git a/test/es2018/sum.mjs b/test/es2018/sum.mjs index 1e2fbadce..8764a3b54 100644 --- a/test/es2018/sum.mjs +++ b/test/es2018/sum.mjs @@ -1,5 +1,5 @@ -function sum( a, b ) { - return a + b; +function sum (a, b) { + return a + b; } export default sum; diff --git a/test/es2018/throws.js b/test/es2018/throws.js index 07ca5e4bd..50cc374d2 100644 --- a/test/es2018/throws.js +++ b/test/es2018/throws.js @@ -1,88 +1,86 @@ -QUnit.module( "assert" ); +QUnit.module('assert'); -function CustomError( message ) { - this.message = message; +function CustomError (message) { + this.message = message; } -CustomError.prototype.toString = function() { - return this.message; +CustomError.prototype.toString = function () { + return this.message; }; -QUnit.test( "throws", function( assert ) { - assert.expect( 1 ); +QUnit.test('throws', function (assert) { + assert.expect(1); - assert.throws( - function() { - throw new CustomError( "some error description" ); - }, - err => { - return err instanceof CustomError && /description/.test( err ); - }, - "custom validation function" - ); -} ); + assert.throws( + function () { + throw new CustomError('some error description'); + }, + err => { + return err instanceof CustomError && /description/.test(err); + }, + 'custom validation function' + ); +}); -QUnit.test( "throws with expected class", function( assert ) { - assert.expect( 1 ); +QUnit.test('throws with expected class', function (assert) { + assert.expect(1); - class CustomError extends Error {} + class CustomError extends Error {} - assert.throws( - () => { - throw new CustomError( "foo" ); - }, - CustomError, - "Expected value is a class extending Error" - ); -} ); + assert.throws( + () => { + throw new CustomError('foo'); + }, + CustomError, + 'Expected value is a class extending Error' + ); +}); -QUnit.module( "failing assertions", { - beforeEach: function( assert ) { - const original = assert.pushResult; - assert.pushResult = function( resultInfo ) { +QUnit.module('failing assertions', { + beforeEach: function (assert) { + const original = assert.pushResult; + assert.pushResult = function (resultInfo) { + // Inverts the result so we can test failing assertions + resultInfo.result = !resultInfo.result; + original.call(this, resultInfo); + }; + } +}, function () { + QUnit.test('throws', function (assert) { + assert.throws( + function () { + throw new Error('foo'); + }, + () => false, + 'throws fails when expected function returns false' + ); + }); - // Inverts the result so we can test failing assertions - resultInfo.result = !resultInfo.result; - original.call( this, resultInfo ); - }; - } -}, function() { - QUnit.test( "throws", function( assert ) { - assert.throws( - function() { - throw "foo"; - }, - () => false, - "throws fails when expected function returns false" - ); - } ); + QUnit.module('inspect expected values', { + beforeEach: function (assert) { + const original = assert.pushResult; + assert.pushResult = function (resultInfo) { + // avoid circular asserts and use if/throw to verify + if (resultInfo.expected !== "TypeError: Class constructor CustomError cannot be invoked without 'new'") { + throw new Error('Unexpected value: ' + resultInfo.expected); + } - QUnit.module( "inspect expected values", { - beforeEach: function( assert ) { - const original = assert.pushResult; - assert.pushResult = function( resultInfo ) { + // invoke the "outer" pushResult, which still inverts the result for negative testing + original.call(this, resultInfo); + }; + } + }, function () { + QUnit.test('does not die when class is expected', function (assert) { + class CustomError extends Error {} - // avoid circular asserts and use if/throw to verify - if ( resultInfo.expected !== "TypeError: Class constructor CustomError cannot be invoked without 'new'" ) { - throw new Error( "Unexpected value: " + resultInfo.expected ); - } - - // invoke the "outer" pushResult, which still inverts the result for negative testing - original.call( this, resultInfo ); - }; - } - }, function() { - QUnit.test( "does not die when class is expected", function( assert ) { - class CustomError extends Error {} - - assert.throws( - () => { - throw new Error( "foo" ); - }, - CustomError, - "throws fails gracefully when expected value class does not use 'new'" - ); - } ); - } ); -} ); + assert.throws( + () => { + throw new Error('foo'); + }, + CustomError, + "throws fails gracefully when expected value class does not use 'new'" + ); + }); + }); +}); diff --git a/test/events-filters.html b/test/events-filters.html index 9749078b3..27c814bde 100644 --- a/test/events-filters.html +++ b/test/events-filters.html @@ -1,14 +1,14 @@ - - Events Filters - - - + + events-filters + + + -
        -
        +
        +
        diff --git a/test/events-filters.js b/test/events-filters.js index 18622702b..2ecd48bc8 100644 --- a/test/events-filters.js +++ b/test/events-filters.js @@ -4,103 +4,102 @@ */ // These are "module1", "module3", "module4" and "verify" -QUnit.config.moduleId = [ "1cf055b9", "46b9bb3b", "db9e6dfc", "c056c5ed" ]; -QUnit.config.filter = "!SKIPME"; +QUnit.config.moduleId = ['1cf055b9', '46b9bb3b', 'db9e6dfc', 'c056c5ed']; +QUnit.config.filter = '!SKIPME'; QUnit.config.reorder = false; -var invokedHooks = [], - done = false; +var invokedHooks = []; +var done = false; -function callback( name ) { - return function( details ) { - if ( done ) { - return; - } +function callback (name) { + return function (details) { + if (done) { + return; + } - invokedHooks.push( name + ": " + details.name ); - }; + invokedHooks.push(name + ': ' + details.name); + }; } -QUnit.on( "suiteStart", callback( "suiteStart" ) ); -QUnit.on( "testStart", callback( "testStart" ) ); -QUnit.on( "testEnd", callback( "testEnd" ) ); -QUnit.on( "suiteEnd", callback( "suiteEnd" ) ); - -QUnit.on( "suiteEnd", function( details ) { - if ( !done && details.name === "module4" ) { - done = true; - } -} ); +QUnit.on('suiteStart', callback('suiteStart')); +QUnit.on('testStart', callback('testStart')); +QUnit.on('testEnd', callback('testEnd')); +QUnit.on('suiteEnd', callback('suiteEnd')); +QUnit.on('suiteEnd', function (details) { + if (!done && details.name === 'module4') { + done = true; + } +}); // matches module filter -QUnit.module( "module1", function() { - QUnit.test( "test 1.1", function( assert ) { - assert.true( true ); - } ); - QUnit.test( "test 1.2", function( assert ) { - assert.true( true ); - } ); -} ); +QUnit.module('module1', function () { + QUnit.test('test 1.1', function (assert) { + assert.true(true); + }); + QUnit.test('test 1.2', function (assert) { + assert.true(true); + }); +}); // skipped by module filter -QUnit.module( "module2", function() { - QUnit.test( "test 2.1", function( assert ) { - assert.true( false ); - } ); - QUnit.test( "test 2.2", function( assert ) { - assert.true( false ); - } ); -} ); +QUnit.module('module2', function () { + QUnit.test('test 2.1', function (assert) { + assert.true(false); + }); + QUnit.test('test 2.2', function (assert) { + assert.true(false); + }); +}); // matches module filter -QUnit.module( "module3", function() { - QUnit.test( "test 3.1", function( assert ) { - assert.true( true ); - } ); +QUnit.module('module3', function () { + QUnit.test('test 3.1', function (assert) { + assert.true(true); + }); - // skipped by test filter - QUnit.test( "test 3.2 SKIPME", function( assert ) { - assert.true( false ); - } ); -} ); + // skipped by test filter + QUnit.test('test 3.2 SKIPME', function (assert) { + assert.true(false); + }); +}); // matches module filter -QUnit.module( "module4", function() { - QUnit.module( "module4-inner", function() { - QUnit.test( "test 4.1", function( assert ) { - assert.true( true ); - } ); - - // skipped by test filter - QUnit.test( "test 4.2 SKIPME", function( assert ) { - assert.true( false ); - } ); - } ); -} ); +QUnit.module('module4', function () { + QUnit.module('module4-inner', function () { + QUnit.test('test 4.1', function (assert) { + assert.true(true); + }); + + // skipped by test filter + QUnit.test('test 4.2 SKIPME', function (assert) { + assert.true(false); + }); + }); +}); // matches module filter -QUnit.module( "verify", function() { - QUnit.test( "events with active filters", function( assert ) { - assert.deepEqual( invokedHooks, [ - "suiteStart: module1", - "testStart: test 1.1", - "testEnd: test 1.1", - "testStart: test 1.2", - "testEnd: test 1.2", - "suiteEnd: module1", - - "suiteStart: module3", - "testStart: test 3.1", - "testEnd: test 3.1", - "suiteEnd: module3", - - "suiteStart: module4", - "suiteStart: module4-inner", - "testStart: test 4.1", - "testEnd: test 4.1", - "suiteEnd: module4-inner", - "suiteEnd: module4" - ] ); - } ); -} ); +QUnit.module('verify', function () { + QUnit.test('events with active filters', function (assert) { + assert.deepEqual(invokedHooks, [ + 'suiteStart: module1', + 'testStart: test 1.1', + 'testEnd: test 1.1', + 'testStart: test 1.2', + 'testEnd: test 1.2', + 'suiteEnd: module1', + + 'suiteStart: module3', + 'testStart: test 3.1', + 'testEnd: test 3.1', + 'suiteEnd: module3', + + 'suiteStart: module4', + 'suiteStart: module4-inner', + 'testStart: test 4.1', + 'testEnd: test 4.1', + 'suiteEnd: module4-inner', + 'suiteEnd: module4' + ]); + }); +}); diff --git a/test/events-in-test.html b/test/events-in-test.html index 07c08bb66..5e0457245 100644 --- a/test/events-in-test.html +++ b/test/events-in-test.html @@ -1,14 +1,14 @@ - - QUnit Events During Tests - - - + + events-in-test + + + -
        -
        +
        +
        diff --git a/test/events-in-test.js b/test/events-in-test.js index 073fbd3df..8a7579026 100644 --- a/test/events-in-test.js +++ b/test/events-in-test.js @@ -7,46 +7,46 @@ // These tests are order-dependent QUnit.config.reorder = false; -function removeUnstableProperties( obj ) { - if ( typeof obj !== "object" ) { - return obj; - } - - delete obj.runtime; - delete obj.stack; - - Object.keys( obj ).forEach( function( key ) { - if ( Array.isArray( obj[ key ] ) ) { - obj[ key ].forEach( removeUnstableProperties ); - } else if ( typeof obj[ key ] === "object" ) { - removeUnstableProperties( obj[ key ] ); - } - } ); - - return obj; +function removeUnstableProperties (obj) { + if (typeof obj !== 'object') { + return obj; + } + + delete obj.runtime; + delete obj.stack; + + Object.keys(obj).forEach(function (key) { + if (Array.isArray(obj[key])) { + obj[key].forEach(removeUnstableProperties); + } else if (typeof obj[key] === 'object') { + removeUnstableProperties(obj[key]); + } + }); + + return obj; } var assertion1 = { - message: "assertion1", - passed: true, - expected: true, - actual: true, - todo: false + message: 'assertion1', + passed: true, + expected: true, + actual: true, + todo: false }; -QUnit.on( "assertion", function( data ) { - if ( data.message === "assertion1" ) { - QUnit.config.current.assert.deepEqual( - removeUnstableProperties( data ), - assertion1, - "verifies assertion data contains the expected and actual values" - ); - } -} ); - -QUnit.module( "Events During Test", function() { - QUnit.test( "test1", function( assert ) { - assert.expect( 2 ); - assert.true( true, "assertion1" ); - } ); -} ); +QUnit.on('assertion', function (data) { + if (data.message === 'assertion1') { + QUnit.config.current.assert.deepEqual( + removeUnstableProperties(data), + assertion1, + 'verifies assertion data contains the expected and actual values' + ); + } +}); + +QUnit.module('Events During Test', function () { + QUnit.test('test1', function (assert) { + assert.expect(2); + assert.true(true, 'assertion1'); + }); +}); diff --git a/test/headless.html b/test/headless.html index 5e1ce6470..04635f443 100644 --- a/test/headless.html +++ b/test/headless.html @@ -1,34 +1,34 @@ - - Headless HTML Reporter - - - - - + + + + // For debugging + if (self.__grunt_contrib_qunit__ === undefined) { + var logs = ['runStart', 'testStart', 'testEnd', 'runEnd']; + for (var i = 0; i < logs.length; i++) { + createCallback(logs[i]); + } + } + }()); + - -
        test markup
        + +
        test markup
        diff --git a/test/index.html b/test/index.html index 04a1f5ba8..dbe4177b1 100644 --- a/test/index.html +++ b/test/index.html @@ -1,33 +1,33 @@ - - QUnit Main Test Suite - - + + QUnit Main Test Suite + + - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + - - - + + + -
        -
        test markup
        +
        +
        test markup
        diff --git a/test/integration/.eslintrc.json b/test/integration/.eslintrc.json index da21cfbd2..9fe66b559 100644 --- a/test/integration/.eslintrc.json +++ b/test/integration/.eslintrc.json @@ -1,4 +1,5 @@ { + "root": true, "parserOptions": { "ecmaVersion": 2018 }, diff --git a/test/logs.html b/test/logs.html index 99fc378a4..e4362f3a8 100644 --- a/test/logs.html +++ b/test/logs.html @@ -1,14 +1,14 @@ - - QUnit Logs Test Suite - - - + + logs + + + -
        -
        test markup
        +
        +
        test markup
        diff --git a/test/logs.js b/test/logs.js index 0c82ba9d8..dafec36c0 100644 --- a/test/logs.js +++ b/test/logs.js @@ -1,332 +1,332 @@ QUnit.config.reorder = false; -var totalTests, moduleContext, moduleDoneContext, testContext, testDoneContext, logContext, - beginModules, - module1Test1, module1Test2, module2Test1, module2Test2, - module2Test3, module2Test4, module2Test5, module2Test6, - begin = 0, - moduleStart = 0, - moduleDone = 0, - testStart = 0, - testDone = 0, - log = 0, - module1Context = { - name: "logs1", - tests: [ - ( module1Test1 = { - "name": "test1", - "testId": "646e9e25", - "skip": false - } ), - ( module1Test2 = { - "name": "test2", - "testId": "646e9e26", - "skip": false - } ) - ] - }, - module2Context = { - name: "logs2", - tests: [ - ( module2Test1 = { - "name": "test1", - "testId": "9954d966", - "skip": false - } ), - ( module2Test2 = { - "name": "test2", - "testId": "9954d967", - "skip": false - } ), - ( module2Test3 = { - "name": "a skipped test", - "testId": "3e797d3a", - "skip": true - } ), - ( module2Test4 = { - "name": "test the log for the skipped test", - "testId": "d3266148", - "skip": false - } ), - ( module2Test5 = { - "name": "a todo test", - "testId": "77a47174", - "skip": false - } ), - ( module2Test6 = { - "name": "test the log for the todo test", - "testId": "5f5ab826", - "skip": false - } ) - ] - }; - -QUnit.begin( function( args ) { - totalTests = args.totalTests; - beginModules = args.modules; - begin++; -} ); - -QUnit.moduleStart( function( context ) { - moduleStart++; - moduleContext = context; -} ); - -QUnit.moduleDone( function( context ) { - moduleDone++; - moduleDoneContext = context; -} ); - -QUnit.testStart( function( context ) { - testStart++; - testContext = context; -} ); - -QUnit.testDone( function( context ) { - testDone++; - testDoneContext = context; -} ); - -QUnit.log( function( context ) { - log++; - logContext = context; -} ); - -QUnit.module( module1Context.name ); - -QUnit.test( module1Test1.name, function( assert ) { - assert.expect( 18 ); - - assert.equal( - typeof totalTests, - "number", - "QUnit.begin should pass total amount of tests to callback" - ); - - while ( beginModules.length > 2 ) { - beginModules.pop(); - } - - assert.deepEqual( - beginModules, - [ module1Context, module2Context ], - "QUnit.begin details registered modules and their respective tests" - ); - - assert.equal( begin, 1, "QUnit.begin calls" ); - assert.equal( moduleStart, 1, "QUnit.moduleStart calls" ); - assert.equal( testStart, 1, "QUnit.testStart calls" ); - assert.equal( testDone, 0, "QUnit.testDone calls" ); - assert.equal( moduleDone, 0, "QUnit.moduleDone calls" ); - - assert.equal( - logContext.runtime >= 0 && logContext.runtime < 50, - true, - "log runtime was a reasonable number" - ); - - delete logContext.runtime; - assert.deepEqual( logContext, { - name: module1Test1.name, - module: module1Context.name, - result: true, - message: "log runtime was a reasonable number", - actual: true, - expected: true, - negative: false, - testId: module1Test1.testId, - todo: false - }, "log context after equal(actual, expected, message)" ); - - assert.equal( "foo", "foo" ); - - delete logContext.runtime; - assert.deepEqual( logContext, { - name: module1Test1.name, - module: module1Context.name, - result: true, - message: undefined, - actual: "foo", - expected: "foo", - negative: false, - testId: module1Test1.testId, - todo: false - }, "log context after equal(actual, expected)" ); - - assert.ok( true, "ok(true, message)" ); - - delete logContext.runtime; - assert.deepEqual( logContext, { - module: module1Context.name, - name: module1Test1.name, - result: true, - message: "ok(true, message)", - actual: true, - expected: true, - negative: false, - testId: module1Test1.testId, - todo: false - }, "log context after ok(true, message)" ); - - assert.strictEqual( testDoneContext, undefined, "testDone context" ); - assert.deepEqual( testContext, { - module: module1Context.name, - name: module1Test1.name, - testId: module1Test1.testId, - previousFailure: false - }, "test context" ); - - assert.strictEqual( moduleDoneContext, undefined, "moduleDone context" ); - assert.deepEqual( moduleContext, module1Context, "module context" ); - - assert.equal( log, 17, "QUnit.log calls" ); -} ); - -QUnit.test( module1Test2.name, function( assert ) { - assert.expect( 12 ); - assert.equal( begin, 1, "QUnit.begin calls" ); - assert.equal( moduleStart, 1, "QUnit.moduleStart calls" ); - assert.equal( testStart, 2, "QUnit.testStart calls" ); - assert.equal( testDone, 1, "QUnit.testDone calls" ); - assert.equal( moduleDone, 0, "QUnit.moduleDone calls" ); - - assert.equal( - testDoneContext.runtime >= 0 && testDoneContext.runtime < 1000, - true, - "test runtime was a reasonable number" - ); - - assert.true( testDoneContext.assertions instanceof Array, "testDone context: assertions" ); - - // TODO: more tests for testDoneContext.assertions - - delete testDoneContext.runtime; - - // Delete testDoneContext.assertions so we can easily jump to next assertion - delete testDoneContext.assertions; - - // Delete testDoneContext.source - delete testDoneContext.source; - - assert.deepEqual( testDoneContext, { - module: module1Context.name, - name: module1Test1.name, - failed: 0, - passed: 18, - total: 18, - testId: module1Test1.testId, - skipped: false, - todo: false - }, "testDone context" ); - assert.deepEqual( testContext, { - module: module1Context.name, - name: module1Test2.name, - testId: module1Test2.testId, - previousFailure: false - }, "test context" ); - - assert.strictEqual( moduleDoneContext, undefined, "moduleDone context" ); - assert.deepEqual( moduleContext, module1Context, "module context" ); - assert.equal( log, 29, "QUnit.log calls" ); -} ); - -QUnit.module( module2Context.name ); - -QUnit.test( module2Test1.name, function( assert ) { - assert.expect( 10 ); - assert.equal( begin, 1, "QUnit.begin calls" ); - assert.equal( moduleStart, 2, "QUnit.moduleStart calls" ); - assert.equal( testStart, 3, "QUnit.testStart calls" ); - assert.equal( testDone, 2, "QUnit.testDone calls" ); - assert.equal( moduleDone, 1, "QUnit.moduleDone calls" ); - - assert.deepEqual( testContext, { - module: module2Context.name, - name: module2Test1.name, - testId: module2Test1.testId, - previousFailure: false - }, "test context" ); - - assert.equal( - moduleDoneContext.runtime >= 0 && moduleDoneContext.runtime < 5000, - true, - "module runtime was a reasonable number" - ); - delete moduleDoneContext.runtime; - - assert.deepEqual( moduleDoneContext, { - name: module1Context.name, - tests: module1Context.tests, - failed: 0, - passed: 30, - total: 30 - }, "moduleDone context" ); - assert.deepEqual( moduleContext, module2Context, "module context" ); - - assert.equal( log, 39, "QUnit.log calls" ); -} ); - -QUnit.test( module2Test2.name, function( assert ) { - assert.expect( 8 ); - assert.equal( begin, 1, "QUnit.begin calls" ); - assert.equal( moduleStart, 2, "QUnit.moduleStart calls" ); - assert.equal( testStart, 4, "QUnit.testStart calls" ); - assert.equal( testDone, 3, "QUnit.testDone calls" ); - assert.equal( moduleDone, 1, "QUnit.moduleDone calls" ); - - assert.deepEqual( testContext, { - module: module2Context.name, - name: module2Test2.name, - testId: module2Test2.testId, - previousFailure: false - }, "test context" ); - assert.deepEqual( moduleContext, module2Context, "module context" ); - - assert.equal( log, 47, "QUnit.log calls" ); -} ); - -QUnit.skip( module2Test3.name ); - -QUnit.test( module2Test4.name, function( assert ) { - assert.expect( 1 ); - - delete testDoneContext.source; - - assert.deepEqual( testDoneContext, { - assertions: [], - module: module2Context.name, - name: module2Test3.name, - failed: 0, - passed: 0, - total: 0, - skipped: true, - todo: false, - testId: module2Test3.testId, - runtime: 0 - }, "testDone context" ); -} ); - -QUnit.todo( module2Test5.name, function( assert ) { - assert.true( false ); - assert.true( true ); -} ); - -QUnit.test( module2Test6.name, function( assert ) { - assert.expect( 1 ); - - delete testDoneContext.runtime; - delete testDoneContext.duration; - delete testDoneContext.source; - delete testDoneContext.assertions; - - assert.deepEqual( testDoneContext, { - module: module2Context.name, - name: module2Test5.name, - failed: 1, - passed: 1, - total: 2, - skipped: false, - todo: true, - testId: module2Test5.testId - }, "testDone context" ); -} ); +var totalTests; var moduleContext; var moduleDoneContext; var testContext; var testDoneContext; var logContext; +var beginModules; +var module1Test1; var module1Test2; var module2Test1; var module2Test2; +var module2Test3; var module2Test4; var module2Test5; var module2Test6; +var begin = 0; +var moduleStart = 0; +var moduleDone = 0; +var testStart = 0; +var testDone = 0; +var log = 0; +var module1Context = { + name: 'logs1', + tests: [ + (module1Test1 = { + name: 'test1', + testId: '646e9e25', + skip: false + }), + (module1Test2 = { + name: 'test2', + testId: '646e9e26', + skip: false + }) + ] +}; +var module2Context = { + name: 'logs2', + tests: [ + (module2Test1 = { + name: 'test1', + testId: '9954d966', + skip: false + }), + (module2Test2 = { + name: 'test2', + testId: '9954d967', + skip: false + }), + (module2Test3 = { + name: 'a skipped test', + testId: '3e797d3a', + skip: true + }), + (module2Test4 = { + name: 'test the log for the skipped test', + testId: 'd3266148', + skip: false + }), + (module2Test5 = { + name: 'a todo test', + testId: '77a47174', + skip: false + }), + (module2Test6 = { + name: 'test the log for the todo test', + testId: '5f5ab826', + skip: false + }) + ] +}; + +QUnit.begin(function (args) { + totalTests = args.totalTests; + beginModules = args.modules; + begin++; +}); + +QUnit.moduleStart(function (context) { + moduleStart++; + moduleContext = context; +}); + +QUnit.moduleDone(function (context) { + moduleDone++; + moduleDoneContext = context; +}); + +QUnit.testStart(function (context) { + testStart++; + testContext = context; +}); + +QUnit.testDone(function (context) { + testDone++; + testDoneContext = context; +}); + +QUnit.log(function (context) { + log++; + logContext = context; +}); + +QUnit.module(module1Context.name); + +QUnit.test(module1Test1.name, function (assert) { + assert.expect(18); + + assert.equal( + typeof totalTests, + 'number', + 'QUnit.begin should pass total amount of tests to callback' + ); + + while (beginModules.length > 2) { + beginModules.pop(); + } + + assert.deepEqual( + beginModules, + [module1Context, module2Context], + 'QUnit.begin details registered modules and their respective tests' + ); + + assert.equal(begin, 1, 'QUnit.begin calls'); + assert.equal(moduleStart, 1, 'QUnit.moduleStart calls'); + assert.equal(testStart, 1, 'QUnit.testStart calls'); + assert.equal(testDone, 0, 'QUnit.testDone calls'); + assert.equal(moduleDone, 0, 'QUnit.moduleDone calls'); + + assert.equal( + logContext.runtime >= 0 && logContext.runtime < 50, + true, + 'log runtime was a reasonable number' + ); + + delete logContext.runtime; + assert.deepEqual(logContext, { + name: module1Test1.name, + module: module1Context.name, + result: true, + message: 'log runtime was a reasonable number', + actual: true, + expected: true, + negative: false, + testId: module1Test1.testId, + todo: false + }, 'log context after equal(actual, expected, message)'); + + assert.equal('foo', 'foo'); + + delete logContext.runtime; + assert.deepEqual(logContext, { + name: module1Test1.name, + module: module1Context.name, + result: true, + message: undefined, + actual: 'foo', + expected: 'foo', + negative: false, + testId: module1Test1.testId, + todo: false + }, 'log context after equal(actual, expected)'); + + assert.ok(true, 'ok(true, message)'); + + delete logContext.runtime; + assert.deepEqual(logContext, { + module: module1Context.name, + name: module1Test1.name, + result: true, + message: 'ok(true, message)', + actual: true, + expected: true, + negative: false, + testId: module1Test1.testId, + todo: false + }, 'log context after ok(true, message)'); + + assert.strictEqual(testDoneContext, undefined, 'testDone context'); + assert.deepEqual(testContext, { + module: module1Context.name, + name: module1Test1.name, + testId: module1Test1.testId, + previousFailure: false + }, 'test context'); + + assert.strictEqual(moduleDoneContext, undefined, 'moduleDone context'); + assert.deepEqual(moduleContext, module1Context, 'module context'); + + assert.equal(log, 17, 'QUnit.log calls'); +}); + +QUnit.test(module1Test2.name, function (assert) { + assert.expect(12); + assert.equal(begin, 1, 'QUnit.begin calls'); + assert.equal(moduleStart, 1, 'QUnit.moduleStart calls'); + assert.equal(testStart, 2, 'QUnit.testStart calls'); + assert.equal(testDone, 1, 'QUnit.testDone calls'); + assert.equal(moduleDone, 0, 'QUnit.moduleDone calls'); + + assert.equal( + testDoneContext.runtime >= 0 && testDoneContext.runtime < 1000, + true, + 'test runtime was a reasonable number' + ); + + assert.true(testDoneContext.assertions instanceof Array, 'testDone context: assertions'); + + // TODO: more tests for testDoneContext.assertions + + delete testDoneContext.runtime; + + // Delete testDoneContext.assertions so we can easily jump to next assertion + delete testDoneContext.assertions; + + // Delete testDoneContext.source + delete testDoneContext.source; + + assert.deepEqual(testDoneContext, { + module: module1Context.name, + name: module1Test1.name, + failed: 0, + passed: 18, + total: 18, + testId: module1Test1.testId, + skipped: false, + todo: false + }, 'testDone context'); + assert.deepEqual(testContext, { + module: module1Context.name, + name: module1Test2.name, + testId: module1Test2.testId, + previousFailure: false + }, 'test context'); + + assert.strictEqual(moduleDoneContext, undefined, 'moduleDone context'); + assert.deepEqual(moduleContext, module1Context, 'module context'); + assert.equal(log, 29, 'QUnit.log calls'); +}); + +QUnit.module(module2Context.name); + +QUnit.test(module2Test1.name, function (assert) { + assert.expect(10); + assert.equal(begin, 1, 'QUnit.begin calls'); + assert.equal(moduleStart, 2, 'QUnit.moduleStart calls'); + assert.equal(testStart, 3, 'QUnit.testStart calls'); + assert.equal(testDone, 2, 'QUnit.testDone calls'); + assert.equal(moduleDone, 1, 'QUnit.moduleDone calls'); + + assert.deepEqual(testContext, { + module: module2Context.name, + name: module2Test1.name, + testId: module2Test1.testId, + previousFailure: false + }, 'test context'); + + assert.equal( + moduleDoneContext.runtime >= 0 && moduleDoneContext.runtime < 5000, + true, + 'module runtime was a reasonable number' + ); + delete moduleDoneContext.runtime; + + assert.deepEqual(moduleDoneContext, { + name: module1Context.name, + tests: module1Context.tests, + failed: 0, + passed: 30, + total: 30 + }, 'moduleDone context'); + assert.deepEqual(moduleContext, module2Context, 'module context'); + + assert.equal(log, 39, 'QUnit.log calls'); +}); + +QUnit.test(module2Test2.name, function (assert) { + assert.expect(8); + assert.equal(begin, 1, 'QUnit.begin calls'); + assert.equal(moduleStart, 2, 'QUnit.moduleStart calls'); + assert.equal(testStart, 4, 'QUnit.testStart calls'); + assert.equal(testDone, 3, 'QUnit.testDone calls'); + assert.equal(moduleDone, 1, 'QUnit.moduleDone calls'); + + assert.deepEqual(testContext, { + module: module2Context.name, + name: module2Test2.name, + testId: module2Test2.testId, + previousFailure: false + }, 'test context'); + assert.deepEqual(moduleContext, module2Context, 'module context'); + + assert.equal(log, 47, 'QUnit.log calls'); +}); + +QUnit.skip(module2Test3.name); + +QUnit.test(module2Test4.name, function (assert) { + assert.expect(1); + + delete testDoneContext.source; + + assert.deepEqual(testDoneContext, { + assertions: [], + module: module2Context.name, + name: module2Test3.name, + failed: 0, + passed: 0, + total: 0, + skipped: true, + todo: false, + testId: module2Test3.testId, + runtime: 0 + }, 'testDone context'); +}); + +QUnit.todo(module2Test5.name, function (assert) { + assert.true(false); + assert.true(true); +}); + +QUnit.test(module2Test6.name, function (assert) { + assert.expect(1); + + delete testDoneContext.runtime; + delete testDoneContext.duration; + delete testDoneContext.source; + delete testDoneContext.assertions; + + assert.deepEqual(testDoneContext, { + module: module2Context.name, + name: module2Test5.name, + failed: 1, + passed: 1, + total: 2, + skipped: false, + todo: true, + testId: module2Test5.testId + }, 'testDone context'); +}); diff --git a/test/main/assert-step.js b/test/main/assert-step.js index e8e43c254..3972aed7f 100644 --- a/test/main/assert-step.js +++ b/test/main/assert-step.js @@ -1,156 +1,153 @@ -QUnit.module( "assert.step", function() { +QUnit.module('assert.step', function () { + QUnit.test('pushes a failing assertion if no message is given', function (assert) { + var original = assert.pushResult; + var pushed = null; + assert.pushResult = function (resultInfo) { + pushed = resultInfo; + }; + + assert.step(); - QUnit.test( "pushes a failing assertion if no message is given", function( assert ) { - var original = assert.pushResult; - var pushed = null; - assert.pushResult = function( resultInfo ) { - pushed = resultInfo; - }; + assert.pushResult = original; + assert.false(pushed.result); + assert.equal(pushed.message, 'You must provide a message to assert.step'); - assert.step(); + assert.verifySteps([undefined]); + }); + + QUnit.test('pushes a failing assertion if empty message is given', function (assert) { + var original = assert.pushResult; + var pushed = null; + assert.pushResult = function (resultInfo) { + pushed = resultInfo; + }; + + assert.step(''); - assert.pushResult = original; - assert.false( pushed.result ); - assert.equal( pushed.message, "You must provide a message to assert.step" ); + assert.pushResult = original; + assert.false(pushed.result); + assert.equal(pushed.message, 'You must provide a message to assert.step'); - assert.verifySteps( [ undefined ] ); - } ); + assert.verifySteps(['']); + }); + + QUnit.test('pushes a failing assertion if a non string message is given', function (assert) { + var original = assert.pushResult; + var pushed = []; + assert.pushResult = function (resultInfo) { + pushed.push(resultInfo); + }; + + assert.step(1); + assert.step(null); + assert.step(false); + + assert.pushResult = original; + assert.deepEqual(pushed, [ + { result: false, message: 'You must provide a string value to assert.step' }, + { result: false, message: 'You must provide a string value to assert.step' }, + { result: false, message: 'You must provide a string value to assert.step' } + ]); + assert.verifySteps([1, null, false]); + }); + + QUnit.test('pushes a passing assertion if a message is given', function (assert) { + assert.step('One step'); + assert.step('Two step'); + + assert.verifySteps(['One step', 'Two step']); + }); + + QUnit.test('step() and verifySteps() count as assertions', function (assert) { + assert.expect(3); + + assert.step('One'); + assert.step('Two'); + + assert.verifySteps(['One', 'Two'], 'Three'); + }); + + QUnit.module('assert.verifySteps'); + + QUnit.test('verifies the order and value of steps', function (assert) { + assert.step('One step'); + assert.step('Two step'); + assert.step('Red step'); + assert.step('Blue step'); + + assert.verifySteps(['One step', 'Two step', 'Red step', 'Blue step']); + + assert.step('One step'); + assert.step('Two step'); + assert.step('Red step'); + assert.step('Blue step'); + + var original = assert.pushResult; + var pushed = null; + assert.pushResult = function (resultInfo) { + pushed = resultInfo; + }; + + assert.verifySteps(['One step', 'Red step', 'Two step', 'Blue step']); + assert.pushResult = original; + + assert.false(pushed.result); + }); + + QUnit.test('verifies the order and value of failed steps', function (assert) { + assert.step('One step'); + + var original = assert.pushResult; + assert.pushResult = function noop () {}; + assert.step(); + assert.step(''); + assert.pushResult = original; + + assert.step('Two step'); - QUnit.test( "pushes a failing assertion if empty message is given", function( assert ) { - var original = assert.pushResult; - var pushed = null; - assert.pushResult = function( resultInfo ) { - pushed = resultInfo; - }; + assert.verifySteps(['One step', undefined, '', 'Two step']); + }); + + QUnit.test('resets the step list after verification', function (assert) { + assert.step('one'); + assert.verifySteps(['one']); + + assert.step('two'); + assert.verifySteps(['two']); + }); + + QUnit.test('errors if not called when `assert.step` is used', function (assert) { + assert.expect(2); + assert.step('one'); + + var original = assert.test.pushFailure; + assert.test.pushFailure = function (message) { + assert.test.pushFailure = original; + + assert.equal(message, 'Expected assert.verifySteps() to be called before end of test after using assert.step(). Unverified steps: one'); + }; + }); + + // Testing to ensure steps array is not passed by reference: https://github.com/qunitjs/qunit/issues/1266 + QUnit.module('assert.verifySteps value reference', function () { + var loggedAssertions = {}; + + QUnit.log(function (details) { + if (details.message === 'verification-assertion') { + loggedAssertions[details.message] = details; + } + }); - assert.step( "" ); + QUnit.test('passing test to see if steps array is passed by reference to logging function', function (assert) { + assert.step('step one'); + assert.step('step two'); - assert.pushResult = original; - assert.false( pushed.result ); - assert.equal( pushed.message, "You must provide a message to assert.step" ); + assert.verifySteps(['step one', 'step two'], 'verification-assertion'); + }); - assert.verifySteps( [ "" ] ); - } ); - - QUnit.test( "pushes a failing assertion if a non string message is given", function( assert ) { - var original = assert.pushResult; - var pushed = []; - assert.pushResult = function( resultInfo ) { - pushed.push( resultInfo ); - }; - - assert.step( 1 ); - assert.step( null ); - assert.step( false ); - - assert.pushResult = original; - assert.deepEqual( pushed, [ - { result: false, message: "You must provide a string value to assert.step" }, - { result: false, message: "You must provide a string value to assert.step" }, - { result: false, message: "You must provide a string value to assert.step" } - ] ); - assert.verifySteps( [ 1, null, false ] ); - } ); - - QUnit.test( "pushes a passing assertion if a message is given", function( assert ) { - assert.step( "One step" ); - assert.step( "Two step" ); - - assert.verifySteps( [ "One step", "Two step" ] ); - } ); - - QUnit.test( "step() and verifySteps() count as assertions", function( assert ) { - assert.expect( 3 ); - - assert.step( "One" ); - assert.step( "Two" ); - - assert.verifySteps( [ "One", "Two" ], "Three" ); - } ); - - QUnit.module( "assert.verifySteps" ); - - QUnit.test( "verifies the order and value of steps", function( assert ) { - assert.step( "One step" ); - assert.step( "Two step" ); - assert.step( "Red step" ); - assert.step( "Blue step" ); - - assert.verifySteps( [ "One step", "Two step", "Red step", "Blue step" ] ); - - assert.step( "One step" ); - assert.step( "Two step" ); - assert.step( "Red step" ); - assert.step( "Blue step" ); - - var original = assert.pushResult; - var pushed = null; - assert.pushResult = function( resultInfo ) { - pushed = resultInfo; - }; - - assert.verifySteps( [ "One step", "Red step", "Two step", "Blue step" ] ); - assert.pushResult = original; - - assert.false( pushed.result ); - } ); - - QUnit.test( "verifies the order and value of failed steps", function( assert ) { - - assert.step( "One step" ); - - var original = assert.pushResult; - assert.pushResult = function noop() {}; - assert.step(); - assert.step( "" ); - assert.pushResult = original; - - assert.step( "Two step" ); - - assert.verifySteps( [ "One step", undefined, "", "Two step" ] ); - } ); - - QUnit.test( "resets the step list after verification", function( assert ) { - assert.step( "one" ); - assert.verifySteps( [ "one" ] ); - - assert.step( "two" ); - assert.verifySteps( [ "two" ] ); - } ); - - QUnit.test( "errors if not called when `assert.step` is used", function( assert ) { - assert.expect( 2 ); - assert.step( "one" ); - - var original = assert.test.pushFailure; - assert.test.pushFailure = function( message ) { - assert.test.pushFailure = original; - - assert.equal( message, "Expected assert.verifySteps() to be called before end of test after using assert.step(). Unverified steps: one" ); - }; - } ); - - // Testing to ensure steps array is not passed by reference: https://github.com/qunitjs/qunit/issues/1266 - QUnit.module( "assert.verifySteps value reference", function() { - - var loggedAssertions = {}; - - QUnit.log( function( details ) { - if ( details.message === "verification-assertion" ) { - loggedAssertions[ details.message ] = details; - } - } ); - - QUnit.test( "passing test to see if steps array is passed by reference to logging function", function( assert ) { - assert.step( "step one" ); - assert.step( "step two" ); - - assert.verifySteps( [ "step one", "step two" ], "verification-assertion" ); - } ); - - QUnit.test( "steps array should not be reset in logging function", function( assert ) { - var result = loggedAssertions[ "verification-assertion" ].actual; - assert.deepEqual( result, [ "step one", "step two" ] ); - } ); - } ); -} ); + QUnit.test('steps array should not be reset in logging function', function (assert) { + var result = loggedAssertions['verification-assertion'].actual; + assert.deepEqual(result, ['step one', 'step two']); + }); + }); +}); diff --git a/test/main/assert-timeout.js b/test/main/assert-timeout.js index 8bae7c50f..cb5dc569a 100644 --- a/test/main/assert-timeout.js +++ b/test/main/assert-timeout.js @@ -1,118 +1,118 @@ -QUnit.module( "assert.timeout", function() { - QUnit.test( "pushes a failure if test times out when using async", function( assert ) { - assert.timeout( 10 ); - assert.expect( 1 ); - - var original = assert.test.pushFailure; - assert.test.pushFailure = function( message ) { - assert.test.pushFailure = original; - - assert.equal( message, "Test took longer than 10ms; test timed out." ); - }; - - assert.async(); - } ); - - QUnit.test( "pushes a failure if test times out when using a promise", function( assert ) { - assert.timeout( 10 ); - assert.expect( 1 ); - - var original = assert.test.pushFailure; - assert.test.pushFailure = function( message ) { - assert.test.pushFailure = original; - - assert.equal( message, "Test took longer than 10ms; test timed out." ); - }; - - // Return a "thenable" to serve as a mock Promise - return { - then: function() {} - }; - } ); - - QUnit.test( "does not push a failure if test is synchronous", function( assert ) { - assert.timeout( 1 ); - - var wait = Date.now() + 10; - while ( Date.now() < wait ) {} // eslint-disable-line no-empty - - assert.true( true ); - } ); - - QUnit.test( "throws an error if a non-numeric value is passed as duration", function( assert ) { - assert.throws( function() { - assert.timeout( null ); - }, /You must pass a number as the duration to assert.timeout/ ); - } ); - - QUnit.test( "reset a timeout if an existing timeout has been set", function( assert ) { - assert.timeout( 50 ); - assert.expect( 1 ); - - var done = assert.async(); - setTimeout( function() { - assert.timeout( 10 ); - var original = assert.test.pushFailure; - assert.test.pushFailure = function( message ) { - assert.test.pushFailure = original; - - assert.equal( message, "Test took longer than 10ms; test timed out." ); - done(); - }; - } ); - } ); - - QUnit.module( "a value of zero", function() { - function stubPushFailure( assert ) { - var original = assert.test.pushFailure; - assert.test.pushFailure = function( message ) { - assert.test.pushFailure = original; - - assert.equal( - message, - "Test did not finish synchronously even though assert.timeout( 0 ) was used." - ); - }; - } - - QUnit.test( "does not fail a synchronous test using assert.async", function( assert ) { - assert.timeout( 0 ); - var done = assert.async(); - assert.true( true ); - done(); - } ); - - QUnit.test( "fails a test using assert.async and a setTimeout of 0", function( assert ) { - assert.timeout( 0 ); - assert.expect( 1 ); - - stubPushFailure( assert ); - - var done = assert.async(); - setTimeout( done, 0 ); - } ); - - if ( typeof Promise !== "undefined" ) { - QUnit.test( "fails a test returning an immediately resolved Promise", function( assert ) { - assert.timeout( 0 ); - assert.expect( 1 ); - - stubPushFailure( assert ); - - return Promise.resolve(); - } ); - - QUnit.test( "fails a test using assert.async and an immediately resolved Promise", function( assert ) { - assert.timeout( 0 ); - assert.expect( 1 ); - - stubPushFailure( assert ); - - var done = assert.async(); - Promise.resolve().then( done ); - } ); - } - } ); -} ); - - +/* global setTimeout */ +QUnit.module('assert.timeout', function () { + QUnit.test('pushes a failure if test times out when using async', function (assert) { + assert.timeout(10); + assert.expect(1); + + var original = assert.test.pushFailure; + assert.test.pushFailure = function (message) { + assert.test.pushFailure = original; + + assert.equal(message, 'Test took longer than 10ms; test timed out.'); + }; + + assert.async(); + }); + + QUnit.test('pushes a failure if test times out when using a promise', function (assert) { + assert.timeout(10); + assert.expect(1); + + var original = assert.test.pushFailure; + assert.test.pushFailure = function (message) { + assert.test.pushFailure = original; + + assert.equal(message, 'Test took longer than 10ms; test timed out.'); + }; + + // Return a "thenable" to serve as a mock Promise + return { + then: function () {} + }; + }); + + QUnit.test('does not push a failure if test is synchronous', function (assert) { + assert.timeout(1); + + var wait = Date.now() + 10; + while (Date.now() < wait) {} // eslint-disable-line no-empty + + assert.true(true); + }); + + QUnit.test('throws an error if a non-numeric value is passed as duration', function (assert) { + assert.throws(function () { + assert.timeout(null); + }, /You must pass a number as the duration to assert.timeout/); + }); + + QUnit.test('reset a timeout if an existing timeout has been set', function (assert) { + assert.timeout(50); + assert.expect(1); + + var done = assert.async(); + setTimeout(function () { + assert.timeout(10); + var original = assert.test.pushFailure; + assert.test.pushFailure = function (message) { + assert.test.pushFailure = original; + + assert.equal(message, 'Test took longer than 10ms; test timed out.'); + done(); + }; + }); + }); + + QUnit.module('a value of zero', function () { + function stubPushFailure (assert) { + var original = assert.test.pushFailure; + assert.test.pushFailure = function (message) { + assert.test.pushFailure = original; + + assert.equal( + message, + 'Test did not finish synchronously even though assert.timeout( 0 ) was used.' + ); + }; + } + + QUnit.test('does not fail a synchronous test using assert.async', function (assert) { + assert.timeout(0); + var done = assert.async(); + assert.true(true); + done(); + }); + + QUnit.test('fails a test using assert.async and a setTimeout of 0', function (assert) { + assert.timeout(0); + assert.expect(1); + + stubPushFailure(assert); + + var done = assert.async(); + setTimeout(done, 0); + }); + + /* global Promise */ + if (typeof Promise !== 'undefined') { + QUnit.test('fails a test returning an immediately resolved Promise', function (assert) { + assert.timeout(0); + assert.expect(1); + + stubPushFailure(assert); + + return Promise.resolve(); + }); + + QUnit.test('fails a test using assert.async and an immediately resolved Promise', function (assert) { + assert.timeout(0); + assert.expect(1); + + stubPushFailure(assert); + + var done = assert.async(); + Promise.resolve().then(done); + }); + } + }); +}); diff --git a/test/main/assert.js b/test/main/assert.js index 8e834cf06..2255aabc3 100644 --- a/test/main/assert.js +++ b/test/main/assert.js @@ -1,939 +1,947 @@ -function buildMockPromise( settledValue, shouldFulfill ) { - - // Support IE 9: Promise not supported, test MUST NOT load polyfil globally. - // Support SpiderMonkey: setTimeout is not supported, but native Promise is. - var defer = typeof setTimeout !== "undefined" ? setTimeout : function( fn ) { - Promise.resolve().then( fn ); - }; - - var thenable = { - then: function( fulfilledCallback, rejectedCallback ) { - defer( function() { - return shouldFulfill ? - fulfilledCallback.call( thenable, settledValue ) : - rejectedCallback.call( thenable, settledValue ); - } ); - - // returning another thenable for easy confirmation of return value - return buildMockPromise( "final promise", true ); - } - }; - return thenable; +function buildMockPromise (settledValue, shouldFulfill) { + // Support IE 9: Promise not supported, test MUST NOT load polyfil globally. + // Support SpiderMonkey: setTimeout is not supported, but native Promise is. + var defer = typeof setTimeout !== 'undefined' + // eslint-disable-next-line no-undef + ? setTimeout + : function (fn) { + // eslint-disable-next-line no-undef, compat/compat + Promise.resolve().then(fn); + }; + + var thenable = { + then: function (fulfilledCallback, rejectedCallback) { + defer(function () { + return shouldFulfill + ? fulfilledCallback.call(thenable, settledValue) + : rejectedCallback.call(thenable, settledValue); + }); + + // returning another thenable for easy confirmation of return value + return buildMockPromise('final promise', true); + } + }; + return thenable; } -QUnit.module( "assert" ); - -QUnit.test( "ok", function( assert ) { - assert.ok( true ); - assert.ok( 1 ); - assert.ok( "1" ); - assert.ok( Infinity ); - assert.ok( {} ); - assert.ok( [] ); - - assert.ok( true, "with message" ); -} ); - -QUnit.test( "notOk", function( assert ) { - assert.notOk( false ); - assert.notOk( 0 ); - assert.notOk( "" ); - assert.notOk( null ); - assert.notOk( undefined ); - assert.notOk( NaN ); - - assert.notOk( false, "with message" ); -} ); - -QUnit.test( "true", function( assert ) { - function functionThatReturnsTrue() { - return true; - } - assert.true( true ); - assert.true( functionThatReturnsTrue() ); -} ); - -QUnit.test( "false", function( assert ) { - function functionThatReturnsFalse() { - return false; - } - assert.false( false ); - assert.false( functionThatReturnsFalse() ); -} ); - -QUnit.test( "equal", function( assert ) { - assert.equal( 1, 1 ); - assert.equal( "foo", "foo" ); - assert.equal( "foo", [ "foo" ] ); - assert.equal( "foo", { toString: function() { return "foo"; } } ); - assert.equal( 0, [ 0 ] ); -} ); - -QUnit.test( "notEqual", function( assert ) { - assert.notEqual( 1, 2 ); - assert.notEqual( "foo", "bar" ); - assert.notEqual( {}, {} ); - assert.notEqual( [], [] ); -} ); - -QUnit.test( "strictEqual", function( assert ) { - assert.strictEqual( 1, 1 ); - assert.strictEqual( "foo", "foo" ); -} ); - -QUnit.test( "notStrictEqual", function( assert ) { - assert.notStrictEqual( 1, 2 ); - assert.notStrictEqual( "foo", "bar" ); - assert.notStrictEqual( "foo", [ "foo" ] ); - assert.notStrictEqual( "1", 1 ); - assert.notStrictEqual( "foo", { toString: function() { return "foo"; } } ); -} ); - -QUnit.test( "propEqual", function( assert ) { - function Foo( x, y, z ) { - this.x = x; - this.y = y; - this.z = z; - } - Foo.prototype.doA = function() {}; - Foo.prototype.bar = "non-function"; - - function Bar() { - } - Bar.prototype = Object.create( Foo.prototype ); - Bar.prototype.constructor = Bar; - - assert.propEqual( - new Foo( 1, "2", [] ), - { - x: 1, - y: "2", - z: [] - } - ); - - assert.notPropEqual( - new Foo( "1", 2, 3 ), - { - x: 1, - y: "2", - z: 3 - }, - "Primitive values are strictly compared" - ); - - assert.notPropEqual( - new Foo( 1, "2", [] ), - { - x: 1, - y: "2", - z: {} - }, - "Array type is preserved" - ); - - assert.notPropEqual( - new Foo( 1, "2", {} ), - { - x: 1, - y: "2", - z: [] - }, - "Empty array is not the same as empty object" - ); - - assert.propEqual( - new Foo( 1, "2", new Foo( [ 3 ], new Bar(), null ) ), - { - x: 1, - y: "2", - z: { - x: [ 3 ], - y: {}, - z: null - } - }, - "Complex nesting of different types, inheritance and constructors" - ); -} ); - -QUnit.test( "propContains", function( assert ) { - function Foo( x, y, z ) { - this.x = x; - this.y = y; - this.z = z; - } - Foo.prototype.doA = function() {}; - Foo.prototype.bar = "non-function"; - - function Bar( x ) { - this.x = x; - } - Bar.prototype = Object.create( Foo.prototype ); - Bar.prototype.constructor = Bar; - - assert.propContains( - { a: 0, b: "something", c: true }, - { a: 0, b: "something", c: true } - ); - assert.propContains( - { a: 0, b: "something", c: true }, - { a: 0, c: true }, - "match object subset" - ); - assert.propContains( - [ "a", "b" ], - { 1: "b" }, - "match array subset via plain object" - ); - assert.propContains( - [], - {}, - "empty array contains empty object" - ); - assert.propContains( - {}, - [], - "empty object contains empty array" - ); - assert.propContains( - new Foo( 1, "2", [] ), - new Foo( 1, "2", [] ), - "deeply equal class instances" - ); - assert.propContains( - new Foo( 1, "2", [] ), - { - x: 1, - y: "2", - z: [] - }, - "match different constructor via plain object" - ); - assert.propContains( - new Foo( 1, "2", [] ), - { - x: 1 - }, - "match different constructor subset via plain object" - ); - assert.propContains( - new Foo( 1, "2", [ "x" ] ), - new Foo( 1, "2", { 0: "x" } ), - "match nested array via plain object" - ); - assert.propContains( - new Foo( 1, [ "a", "b" ], new Foo( [ "c", "d" ], new Bar(), null ) ), - { - x: 1, - y: [ "a", "b" ], - z: { - x: { 1: "d" } - } - }, - "match nested array subset via plain object" - ); - assert.propContains( - new Foo( 1, "2" ), - new Bar( 1 ), - "match subset via different constructor" - ); -} ); - -QUnit.test( "notPropContains", function( assert ) { - function Foo( x, y, z ) { - this.x = x; - this.y = y; - this.z = z; - } - Foo.prototype.doA = function() {}; - Foo.prototype.bar = "non-function"; - - function Bar( x ) { - this.x = x; - } - Bar.prototype = Object.create( Foo.prototype ); - Bar.prototype.constructor = Bar; - - assert.notPropContains( - { a: 0, b: "something", c: true }, - { a: 0, b: "different", c: true } - ); - assert.notPropContains( - { a: 0, b: "something", c: true }, - { a: 0, c: false } - ); - assert.notPropContains( - { a: 0, b: "something", c: true }, - { e: "missing" } - ); - assert.notPropContains( - new Foo( 1, "2", [] ), - { - x: 1, - y: "2", - z: [], - e: "missing" - }, - "matching and missing properties" - ); - assert.notPropContains( - new Foo( 1, "2", [] ), - { - e: "missing" - }, - "missing property" - ); - assert.notPropContains( - new Foo( 1, [], new Foo( [], new Bar(), "something" ) ), - new Foo( 1, [], new Foo( [], new Bar(), "different" ) ), - "difference in nested value" - ); - assert.notPropContains( - new Foo( 1, "2", new Foo( [ 3 ], new Bar(), null ) ), - { - x: 1, - y: "2", - z: { - e: "missing" - } - }, - "nested object with missing property" - ); - assert.notPropContains( - new Foo( 1, "2" ), - new Bar( 2 ), - "different property value via different constructor" - ); -} ); - -QUnit.test( "throws", function( assert ) { - function CustomError( message ) { - this.message = message; - } - - CustomError.prototype.toString = function() { - return this.message; - }; - - assert.throws( - function() { - throw "my error"; - } - ); - - assert.throws( - function() { - throw "my error"; - }, - "simple string throw, no 'expected' value given" - ); - - assert.throws( function() { - // eslint-disable-next-line qunit/no-throws-string - assert.throws( - undefined, // irrelevant - errors before even verifying this - "expected is a string", - "message is non-null" - ); - }, /^Error: assert\.throws does not accept a string value for the expected argument/ ); - - // This test is for IE 7 and prior which does not properly - // implement Error.prototype.toString - assert.throws( - function() { - throw new Error( "error message" ); - }, - /error message/, - "use regexp against instance of Error" - ); - - assert.throws( - function() { - throw new TypeError(); - }, - Error, - "thrown TypeError without a message is an instance of Error" - ); - - assert.throws( - function() { - throw new TypeError(); - }, - TypeError, - "thrown TypeError without a message is an instance of TypeError" - ); - - assert.throws( - function() { - throw new TypeError( "error message" ); - }, - Error, - "thrown TypeError with a message is an instance of Error" - ); - - // This test is for IE 8 and prior which goes against the standards - // by considering that the native Error constructors, such TypeError, - // are also instances of the Error constructor. As such, the assertion - // sometimes went down the wrong path. - assert.throws( - function() { - throw new TypeError( "error message" ); - }, - TypeError, - "thrown TypeError with a message is an instance of TypeError" - ); - - assert.throws( - function() { - throw new CustomError( "some error description" ); - }, - CustomError, - "thrown error is an instance of CustomError" - ); - - assert.throws( - function() { - throw new Error( "some error description" ); - }, - /description/, - "use a regex to match against the stringified error" - ); - - assert.throws( - function() { - throw new Error( "foo" ); - }, - new Error( "foo" ), - "thrown error object is similar to the expected Error object" - ); - - assert.throws( - function() { - throw new CustomError( "some error description" ); - }, - new CustomError( "some error description" ), - "thrown error object is similar to the expected CustomError object" - ); - - assert.throws( - function() { - throw { - name: "SomeName", - message: "some message" - }; - }, - { name: "SomeName", message: "some message" }, - "thrown object is similar to the expected plain object" - ); - - assert.throws( - function() { - throw { - name: "SomeName", - message: "some message" - }; - }, - /^SomeName: some message$/, - "thrown object matches formatted error message" - ); - - assert.throws( - function() { - throw { - name: true, - message: "some message" - }; - }, - /^true: some message$/, - "formatted string for Error object with non-string name property" - ); - - assert.throws( - function() { - throw {}; - }, - /^Error$/, - "thrown object with no name or message matches formatted error message" - ); - - assert.throws( - function() { - throw { - name: "SomeName" - }; - }, - /^SomeName$/, - "thrown object with name but no message matches formatted error message" - ); - - assert.throws( - function() { - throw { - message: "some message" - }; - }, - /^Error: some message$/, - "thrown object with message but no name matches formatted error message" - ); - - assert.throws( - function() { - throw new CustomError( "some error description" ); - }, - function( err ) { - return err instanceof CustomError && /description/.test( err ); - }, - "custom validation function" - ); - - assert.throws( - function() { - var execScript = window.execScript || function( data ) { - window.eval.call( window, data ); - }; - execScript( "throw 'error';" ); - }, - "globally-executed errors caught" - ); - - this.CustomError = CustomError; - - assert.throws( - function() { - throw new this.CustomError( "some error description" ); - }, - /description/, - "throw error from property of 'this' context" - ); - - // the following are nested assertions, validating that it - // initially throws due to an invalid expected value - - assert.throws( - function() { - assert.throws( - undefined, // irrelevant - 2 - ); - }, - /^Error: Invalid expected value type \(number\) provided to assert\.throws\.$/, - "throws errors when provided a number" - ); - - // note that "falsey" values are actually ok - assert.throws( - function() { - throw new this.CustomError( "some error description" ); - }, - 0, - "throws passes when expected is falsey (0)" - ); - - assert.throws( - function() { - assert.throws( - undefined, // irrelevant - true - ); - }, - /^Error: Invalid expected value type \(boolean\) provided to assert\.throws\.$/, - "throws errors when provided a boolean" - ); - - // note that "falsey" values are actually ok - assert.throws( - function() { - throw new this.CustomError( "some error description" ); - }, - false, - "throws passes when expected is falsey (false)" - ); - - assert.throws( - function() { - assert.throws( - undefined, // irrelevant - [] - ); - }, - /^Error: Invalid expected value type \(array\) provided to assert\.throws\.$/, - "throws errors when provided an array" - ); -} ); - -QUnit.test( "raises", function( assert ) { - assert.strictEqual( assert.raises, assert.throws, "alias for throws" ); -} ); - -QUnit.test( "rejects", function( assert ) { - function CustomError( message ) { - this.message = message; - } - - CustomError.prototype.toString = function() { - return this.message; - }; - - var rejectsReturnValue = assert.rejects( - buildMockPromise( "my error" ) - ); - - assert.equal( - typeof rejectsReturnValue.then, - "function", - "rejects returns a thennable" - ); - - assert.rejects( - buildMockPromise( "my error" ), - "simple string rejection, no 'expected' value given" - ); - - // This test is for IE 7 and prior which does not properly - // implement Error.prototype.toString - assert.rejects( - buildMockPromise( new Error( "error message" ) ), - /error message/, - "use regexp against instance of Error" - ); - - assert.rejects( - buildMockPromise( new TypeError() ), - Error, - "thrown TypeError without a message is an instance of Error" - ); - - assert.rejects( - buildMockPromise( new TypeError() ), - TypeError, - "thrown TypeError without a message is an instance of TypeError" - ); - - assert.rejects( - buildMockPromise( new TypeError( "error message" ) ), - Error, - "thrown TypeError with a message is an instance of Error" - ); - - // This test is for IE 8 and prior which goes against the standards - // by considering that the native Error constructors, such TypeError, - // are also instances of the Error constructor. As such, the assertion - // sometimes went down the wrong path. - assert.rejects( - buildMockPromise( new TypeError( "error message" ) ), - TypeError, - "thrown TypeError with a message is an instance of TypeError" - ); - - assert.rejects( - buildMockPromise( new CustomError( "some error description" ) ), - CustomError, - "thrown error is an instance of CustomError" - ); - - assert.rejects( - buildMockPromise( new Error( "some error description" ) ), - /description/, - "use a regex to match against the stringified error" - ); - - assert.rejects( - buildMockPromise( new Error( "foo" ) ), - new Error( "foo" ), - "thrown error object is similar to the expected Error object" - ); - - assert.rejects( - buildMockPromise( new CustomError( "some error description" ) ), - new CustomError( "some error description" ), - "thrown error object is similar to the expected CustomError object" - ); - - assert.rejects( - buildMockPromise( { - name: "SomeName", - message: "some message" - } ), - { name: "SomeName", message: "some message" }, - "thrown object is similar to the expected plain object" - ); - - assert.rejects( - buildMockPromise( new CustomError( "some error description" ) ), - function( err ) { - return err instanceof CustomError && /description/.test( err ); - }, - "custom validation function" - ); - - this.CustomError = CustomError; - - assert.rejects( - buildMockPromise( new this.CustomError( "some error description" ) ), - /description/, - "throw error from property of 'this' context" - ); - - assert.rejects( - buildMockPromise( undefined ), - "reject with undefined against no matcher" - ); - - // the following are nested assertions, validating that it - // initially throws due to an invalid expected value - - assert.throws( - function() { - assert.rejects( - undefined, // irrelevant - 2 - ); - }, - /^Error: Invalid expected value type \(number\) provided to assert\.rejects\.$/, - "rejects errors when provided a number" - ); - - // note that "falsey" values are actually ok - assert.rejects( - buildMockPromise( undefined ), - 0, - "rejects passes when expected is falsey (0)" - ); - - assert.throws( - function() { - assert.rejects( - undefined, // irrelevant - true - ); - }, - /^Error: Invalid expected value type \(boolean\) provided to assert\.rejects\.$/, - "rejects errors when provided a boolean" - ); - - // note that "falsey" values are actually ok - assert.rejects( - buildMockPromise( undefined ), - false, - "rejects passes when expected is falsey (false)" - ); - - assert.throws( - function() { - assert.rejects( - undefined, // irrelevant - [] - ); - }, - /^Error: Invalid expected value type \(array\) provided to assert\.rejects\.$/, - "rejects errors when provided an array" - ); - - assert.throws( - function() { - assert.rejects( - undefined, // irrelevant - "expected is a string", - "message is non-null" - ); - }, - /^Error: assert\.rejects does not accept a string value for the expected argument/, - "rejects errors when provided a string" - ); - - // should return a thenable - var returnValue = assert.rejects( - buildMockPromise( undefined ) - ); - assert.strictEqual( typeof returnValue, "object" ); - assert.strictEqual( typeof returnValue.then, "function" ); - -} ); - -QUnit.module( "assert - failing assertions", { - beforeEach: function( assert ) { - var originalPushResult = assert.pushResult; - assert.pushResult = function( resultInfo ) { - - // Inverts the result so we can test failing assertions - resultInfo.result = !resultInfo.result; - originalPushResult.call( this, resultInfo ); - }; - } -} ); - -QUnit.test( "ok", function( assert ) { - assert.ok( false ); - assert.ok( 0 ); - assert.ok( "" ); - assert.ok( null ); - assert.ok( undefined ); - assert.ok( NaN ); -} ); - -QUnit.test( "notOk", function( assert ) { - assert.notOk( true ); - assert.notOk( 1 ); - assert.notOk( "1" ); - assert.notOk( Infinity ); - assert.notOk( {} ); - assert.notOk( [] ); -} ); - -QUnit.test( "equal", function( assert ) { - assert.equal( 1, 2 ); - assert.equal( "foo", "bar" ); - assert.equal( {}, {} ); - assert.equal( [], [] ); -} ); - -QUnit.test( "notEqual", function( assert ) { - assert.notEqual( 1, 1 ); - assert.notEqual( "foo", "foo" ); - assert.notEqual( "foo", [ "foo" ] ); - assert.notEqual( "foo", { toString: function() { return "foo"; } } ); - assert.notEqual( 0, [ 0 ] ); -} ); - -QUnit.test( "strictEqual", function( assert ) { - assert.strictEqual( 1, 2 ); - assert.strictEqual( "foo", "bar" ); - assert.strictEqual( "foo", [ "foo" ] ); - assert.strictEqual( "1", 1 ); - assert.strictEqual( "foo", { toString: function() { return "foo"; } } ); -} ); - -QUnit.test( "notStrictEqual", function( assert ) { - assert.notStrictEqual( 1, 1 ); - assert.notStrictEqual( "foo", "foo" ); -} ); - -QUnit.test( "deepEqual", function( assert ) { - assert.deepEqual( [ "foo", "bar" ], [ "foo" ] ); -} ); - -QUnit.test( "notDeepEqual", function( assert ) { - assert.notDeepEqual( [ "foo", "bar" ], [ "foo", "bar" ] ); -} ); - -QUnit.test( "propEqual", function( assert ) { - function Foo( x, y, z ) { - this.x = x; - this.y = y; - this.z = z; - } - Foo.prototype.baz = function() {}; - Foo.prototype.bar = "prototype"; - - assert.propEqual( - new Foo( "1", 2, 3 ), - { - x: 1, - y: "2", - z: 3 - } - ); -} ); - -QUnit.test( "notPropEqual", function( assert ) { - function Foo( x, y, z ) { - this.x = x; - this.y = y; - this.z = z; - } - Foo.prototype.baz = function() {}; - Foo.prototype.bar = "prototype"; - - assert.notPropEqual( - new Foo( 1, "2", [] ), - { - x: 1, - y: "2", - z: [] - } - ); -} ); - -QUnit.test( "throws", function( assert ) { - assert.throws( - function() { - return; - }, - "throws fails without a thrown error" - ); - - assert.throws( - function() { - throw "foo"; - }, - /bar/, - "throws fail when regexp doesn't match the error message" - ); - - assert.throws( - function() { - throw "foo"; - }, - function() { - return false; - }, - "throws fail when expected function returns false" - ); - - // non-function actual values - assert.throws( - undefined, - "throws fails when actual value is undefined" ); - - assert.throws( - 2, - "throws fails when actual value is a number" ); - - assert.throws( - [], - "throws fails when actual value is an array" ); - - assert.throws( - "notafunction", - "throws fails when actual value is a string" ); - - assert.throws( - {}, - "throws fails when actual value is an object" ); -} ); - -QUnit.test( "rejects", function( assert ) { - assert.rejects( - buildMockPromise( "some random value", /* shouldResolve */ true ), - "fails when the provided promise fulfills" - ); - - assert.rejects( - buildMockPromise( "foo" ), - /bar/, - "rejects fails when regexp does not match" - ); - - assert.rejects( - buildMockPromise( new Error( "foo" ) ), - function RandomConstructor() { }, - "rejects fails when rejected value is not an instance of the provided constructor" - ); - - function SomeConstructor() { } - - assert.rejects( - buildMockPromise( new SomeConstructor() ), - function OtherRandomConstructor() { }, - "rejects fails when rejected value is not an instance of the provided constructor" - ); - - assert.rejects( - buildMockPromise( "some value" ), - function() { return false; }, - "rejects fails when the expected function returns false" - ); - - assert.rejects( null ); -} ); +QUnit.module('assert'); + +QUnit.test('ok', function (assert) { + assert.ok(true); + assert.ok(1); + assert.ok('1'); + assert.ok(Infinity); + assert.ok({}); + assert.ok([]); + + assert.ok(true, 'with message'); +}); + +QUnit.test('notOk', function (assert) { + assert.notOk(false); + assert.notOk(0); + assert.notOk(''); + assert.notOk(null); + assert.notOk(undefined); + assert.notOk(NaN); + + assert.notOk(false, 'with message'); +}); + +QUnit.test('true', function (assert) { + function functionThatReturnsTrue () { + return true; + } + assert.true(true); + assert.true(functionThatReturnsTrue()); +}); + +QUnit.test('false', function (assert) { + function functionThatReturnsFalse () { + return false; + } + assert.false(false); + assert.false(functionThatReturnsFalse()); +}); + +QUnit.test('equal', function (assert) { + assert.equal(1, 1); + assert.equal('foo', 'foo'); + assert.equal('foo', ['foo']); + assert.equal('foo', { toString: function () { return 'foo'; } }); + assert.equal(0, [0]); +}); + +QUnit.test('notEqual', function (assert) { + assert.notEqual(1, 2); + assert.notEqual('foo', 'bar'); + assert.notEqual({}, {}); + assert.notEqual([], []); +}); + +QUnit.test('strictEqual', function (assert) { + assert.strictEqual(1, 1); + assert.strictEqual('foo', 'foo'); +}); + +QUnit.test('notStrictEqual', function (assert) { + assert.notStrictEqual(1, 2); + assert.notStrictEqual('foo', 'bar'); + assert.notStrictEqual('foo', ['foo']); + assert.notStrictEqual('1', 1); + assert.notStrictEqual('foo', { toString: function () { return 'foo'; } }); +}); + +QUnit.test('propEqual', function (assert) { + function Foo (x, y, z) { + this.x = x; + this.y = y; + this.z = z; + } + Foo.prototype.doA = function () {}; + Foo.prototype.bar = 'non-function'; + + function Bar () { + } + Bar.prototype = Object.create(Foo.prototype); + Bar.prototype.constructor = Bar; + + assert.propEqual( + new Foo(1, '2', []), + { + x: 1, + y: '2', + z: [] + } + ); + + assert.notPropEqual( + new Foo('1', 2, 3), + { + x: 1, + y: '2', + z: 3 + }, + 'Primitive values are strictly compared' + ); + + assert.notPropEqual( + new Foo(1, '2', []), + { + x: 1, + y: '2', + z: {} + }, + 'Array type is preserved' + ); + + assert.notPropEqual( + new Foo(1, '2', {}), + { + x: 1, + y: '2', + z: [] + }, + 'Empty array is not the same as empty object' + ); + + assert.propEqual( + new Foo(1, '2', new Foo([3], new Bar(), null)), + { + x: 1, + y: '2', + z: { + x: [3], + y: {}, + z: null + } + }, + 'Complex nesting of different types, inheritance and constructors' + ); +}); + +QUnit.test('propContains', function (assert) { + function Foo (x, y, z) { + this.x = x; + this.y = y; + this.z = z; + } + Foo.prototype.doA = function () {}; + Foo.prototype.bar = 'non-function'; + + function Bar (x) { + this.x = x; + } + Bar.prototype = Object.create(Foo.prototype); + Bar.prototype.constructor = Bar; + + assert.propContains( + { a: 0, b: 'something', c: true }, + { a: 0, b: 'something', c: true } + ); + assert.propContains( + { a: 0, b: 'something', c: true }, + { a: 0, c: true }, + 'match object subset' + ); + assert.propContains( + ['a', 'b'], + { 1: 'b' }, + 'match array subset via plain object' + ); + assert.propContains( + [], + {}, + 'empty array contains empty object' + ); + assert.propContains( + {}, + [], + 'empty object contains empty array' + ); + assert.propContains( + new Foo(1, '2', []), + new Foo(1, '2', []), + 'deeply equal class instances' + ); + assert.propContains( + new Foo(1, '2', []), + { + x: 1, + y: '2', + z: [] + }, + 'match different constructor via plain object' + ); + assert.propContains( + new Foo(1, '2', []), + { + x: 1 + }, + 'match different constructor subset via plain object' + ); + assert.propContains( + new Foo(1, '2', ['x']), + new Foo(1, '2', { 0: 'x' }), + 'match nested array via plain object' + ); + assert.propContains( + new Foo(1, ['a', 'b'], new Foo(['c', 'd'], new Bar(), null)), + { + x: 1, + y: ['a', 'b'], + z: { + x: { 1: 'd' } + } + }, + 'match nested array subset via plain object' + ); + assert.propContains( + new Foo(1, '2'), + new Bar(1), + 'match subset via different constructor' + ); +}); + +QUnit.test('notPropContains', function (assert) { + function Foo (x, y, z) { + this.x = x; + this.y = y; + this.z = z; + } + Foo.prototype.doA = function () {}; + Foo.prototype.bar = 'non-function'; + + function Bar (x) { + this.x = x; + } + Bar.prototype = Object.create(Foo.prototype); + Bar.prototype.constructor = Bar; + + assert.notPropContains( + { a: 0, b: 'something', c: true }, + { a: 0, b: 'different', c: true } + ); + assert.notPropContains( + { a: 0, b: 'something', c: true }, + { a: 0, c: false } + ); + assert.notPropContains( + { a: 0, b: 'something', c: true }, + { e: 'missing' } + ); + assert.notPropContains( + new Foo(1, '2', []), + { + x: 1, + y: '2', + z: [], + e: 'missing' + }, + 'matching and missing properties' + ); + assert.notPropContains( + new Foo(1, '2', []), + { + e: 'missing' + }, + 'missing property' + ); + assert.notPropContains( + new Foo(1, [], new Foo([], new Bar(), 'something')), + new Foo(1, [], new Foo([], new Bar(), 'different')), + 'difference in nested value' + ); + assert.notPropContains( + new Foo(1, '2', new Foo([3], new Bar(), null)), + { + x: 1, + y: '2', + z: { + e: 'missing' + } + }, + 'nested object with missing property' + ); + assert.notPropContains( + new Foo(1, '2'), + new Bar(2), + 'different property value via different constructor' + ); +}); + +QUnit.test('throws', function (assert) { + function CustomError (message) { + this.message = message; + } + + CustomError.prototype.toString = function () { + return this.message; + }; + + assert.throws( + function () { + throw 'my error'; + } + ); + + assert.throws( + function () { + throw 'my error'; + }, + "simple string throw, no 'expected' value given" + ); + + assert.throws(function () { + // eslint-disable-next-line qunit/no-throws-string + assert.throws( + undefined, // irrelevant - errors before even verifying this + 'expected is a string', + 'message is non-null' + ); + }, /^Error: assert\.throws does not accept a string value for the expected argument/); + + // This test is for IE 7 and prior which does not properly + // implement Error.prototype.toString + assert.throws( + function () { + throw new Error('error message'); + }, + /error message/, + 'use regexp against instance of Error' + ); + + assert.throws( + function () { + throw new TypeError(); + }, + Error, + 'thrown TypeError without a message is an instance of Error' + ); + + assert.throws( + function () { + throw new TypeError(); + }, + TypeError, + 'thrown TypeError without a message is an instance of TypeError' + ); + + assert.throws( + function () { + throw new TypeError('error message'); + }, + Error, + 'thrown TypeError with a message is an instance of Error' + ); + + // This test is for IE 8 and prior which goes against the standards + // by considering that the native Error constructors, such TypeError, + // are also instances of the Error constructor. As such, the assertion + // sometimes went down the wrong path. + assert.throws( + function () { + throw new TypeError('error message'); + }, + TypeError, + 'thrown TypeError with a message is an instance of TypeError' + ); + + assert.throws( + function () { + throw new CustomError('some error description'); + }, + CustomError, + 'thrown error is an instance of CustomError' + ); + + assert.throws( + function () { + throw new Error('some error description'); + }, + /description/, + 'use a regex to match against the stringified error' + ); + + assert.throws( + function () { + throw new Error('foo'); + }, + new Error('foo'), + 'thrown error object is similar to the expected Error object' + ); + + assert.throws( + function () { + throw new CustomError('some error description'); + }, + new CustomError('some error description'), + 'thrown error object is similar to the expected CustomError object' + ); + + assert.throws( + function () { + throw { + name: 'SomeName', + message: 'some message' + }; + }, + { name: 'SomeName', message: 'some message' }, + 'thrown object is similar to the expected plain object' + ); + + assert.throws( + function () { + throw { + name: 'SomeName', + message: 'some message' + }; + }, + /^SomeName: some message$/, + 'thrown object matches formatted error message' + ); + + assert.throws( + function () { + throw { + name: true, + message: 'some message' + }; + }, + /^true: some message$/, + 'formatted string for Error object with non-string name property' + ); + + assert.throws( + function () { + throw {}; + }, + /^Error$/, + 'thrown object with no name or message matches formatted error message' + ); + + assert.throws( + function () { + throw { + name: 'SomeName' + }; + }, + /^SomeName$/, + 'thrown object with name but no message matches formatted error message' + ); + + assert.throws( + function () { + throw { + message: 'some message' + }; + }, + /^Error: some message$/, + 'thrown object with message but no name matches formatted error message' + ); + + assert.throws( + function () { + throw new CustomError('some error description'); + }, + function (err) { + return err instanceof CustomError && /description/.test(err); + }, + 'custom validation function' + ); + + this.CustomError = CustomError; + + assert.throws( + function () { + throw new this.CustomError('some error description'); + }, + /description/, + "throw error from property of 'this' context" + ); + + // the following are nested assertions, validating that it + // initially throws due to an invalid expected value + + assert.throws( + function () { + assert.throws( + undefined, // irrelevant + 2 + ); + }, + /^Error: Invalid expected value type \(number\) provided to assert\.throws\.$/, + 'throws errors when provided a number' + ); + + // note that "falsey" values are actually ok + assert.throws( + function () { + throw new this.CustomError('some error description'); + }, + 0, + 'throws passes when expected is falsey (0)' + ); + + assert.throws( + function () { + assert.throws( + undefined, // irrelevant + true + ); + }, + /^Error: Invalid expected value type \(boolean\) provided to assert\.throws\.$/, + 'throws errors when provided a boolean' + ); + + // note that "falsey" values are actually ok + assert.throws( + function () { + throw new this.CustomError('some error description'); + }, + false, + 'throws passes when expected is falsey (false)' + ); + + assert.throws( + function () { + assert.throws( + undefined, // irrelevant + [] + ); + }, + /^Error: Invalid expected value type \(array\) provided to assert\.throws\.$/, + 'throws errors when provided an array' + ); +}); + +QUnit.test('raises', function (assert) { + assert.strictEqual(assert.raises, assert.throws, 'alias for throws'); +}); + +QUnit.test('rejects', function (assert) { + function CustomError (message) { + this.message = message; + } + + CustomError.prototype.toString = function () { + return this.message; + }; + + var rejectsReturnValue = assert.rejects( + buildMockPromise('my error') + ); + + assert.equal( + typeof rejectsReturnValue.then, + 'function', + 'rejects returns a thennable' + ); + + assert.rejects( + buildMockPromise('my error'), + "simple string rejection, no 'expected' value given" + ); + + // This test is for IE 7 and prior which does not properly + // implement Error.prototype.toString + assert.rejects( + buildMockPromise(new Error('error message')), + /error message/, + 'use regexp against instance of Error' + ); + + assert.rejects( + buildMockPromise(new TypeError()), + Error, + 'thrown TypeError without a message is an instance of Error' + ); + + assert.rejects( + buildMockPromise(new TypeError()), + TypeError, + 'thrown TypeError without a message is an instance of TypeError' + ); + + assert.rejects( + buildMockPromise(new TypeError('error message')), + Error, + 'thrown TypeError with a message is an instance of Error' + ); + + // This test is for IE 8 and prior which goes against the standards + // by considering that the native Error constructors, such TypeError, + // are also instances of the Error constructor. As such, the assertion + // sometimes went down the wrong path. + assert.rejects( + buildMockPromise(new TypeError('error message')), + TypeError, + 'thrown TypeError with a message is an instance of TypeError' + ); + + assert.rejects( + buildMockPromise(new CustomError('some error description')), + CustomError, + 'thrown error is an instance of CustomError' + ); + + assert.rejects( + buildMockPromise(new Error('some error description')), + /description/, + 'use a regex to match against the stringified error' + ); + + assert.rejects( + buildMockPromise(new Error('foo')), + new Error('foo'), + 'thrown error object is similar to the expected Error object' + ); + + assert.rejects( + buildMockPromise(new CustomError('some error description')), + new CustomError('some error description'), + 'thrown error object is similar to the expected CustomError object' + ); + + assert.rejects( + buildMockPromise({ + name: 'SomeName', + message: 'some message' + }), + { name: 'SomeName', message: 'some message' }, + 'thrown object is similar to the expected plain object' + ); + + assert.rejects( + buildMockPromise(new CustomError('some error description')), + function (err) { + return err instanceof CustomError && /description/.test(err); + }, + 'custom validation function' + ); + + this.CustomError = CustomError; + + assert.rejects( + buildMockPromise(new this.CustomError('some error description')), + /description/, + "throw error from property of 'this' context" + ); + + assert.rejects( + buildMockPromise(undefined), + 'reject with undefined against no matcher' + ); + + // the following are nested assertions, validating that it + // initially throws due to an invalid expected value + + assert.throws( + function () { + assert.rejects( + undefined, // irrelevant + 2 + ); + }, + /^Error: Invalid expected value type \(number\) provided to assert\.rejects\.$/, + 'rejects errors when provided a number' + ); + + // note that "falsey" values are actually ok + assert.rejects( + buildMockPromise(undefined), + 0, + 'rejects passes when expected is falsey (0)' + ); + + assert.throws( + function () { + assert.rejects( + undefined, // irrelevant + true + ); + }, + /^Error: Invalid expected value type \(boolean\) provided to assert\.rejects\.$/, + 'rejects errors when provided a boolean' + ); + + // note that "falsey" values are actually ok + assert.rejects( + buildMockPromise(undefined), + false, + 'rejects passes when expected is falsey (false)' + ); + + assert.throws( + function () { + assert.rejects( + undefined, // irrelevant + [] + ); + }, + /^Error: Invalid expected value type \(array\) provided to assert\.rejects\.$/, + 'rejects errors when provided an array' + ); + + assert.throws( + function () { + assert.rejects( + undefined, // irrelevant + 'expected is a string', + 'message is non-null' + ); + }, + /^Error: assert\.rejects does not accept a string value for the expected argument/, + 'rejects errors when provided a string' + ); + + // should return a thenable + var returnValue = assert.rejects( + buildMockPromise(undefined) + ); + assert.strictEqual(typeof returnValue, 'object'); + assert.strictEqual(typeof returnValue.then, 'function'); +}); + +if (typeof window !== 'undefined') { + /* global window */ + QUnit.test('throws [global eval]', function (assert) { + assert.throws( + function () { + var execScript = window.execScript || function (data) { + // eslint-disable-next-line no-eval, no-useless-call + window.eval.call(window, data); + }; + // eslint-disable-next-line no-implied-eval + execScript("throw 'error';"); + }, + 'globally-executed errors caught' + ); + }); +} + +QUnit.module('assert - failing assertions', { + beforeEach: function (assert) { + var originalPushResult = assert.pushResult; + assert.pushResult = function (resultInfo) { + // Inverts the result so we can test failing assertions + resultInfo.result = !resultInfo.result; + originalPushResult.call(this, resultInfo); + }; + } +}); + +QUnit.test('ok', function (assert) { + assert.ok(false); + assert.ok(0); + assert.ok(''); + assert.ok(null); + assert.ok(undefined); + assert.ok(NaN); +}); + +QUnit.test('notOk', function (assert) { + assert.notOk(true); + assert.notOk(1); + assert.notOk('1'); + assert.notOk(Infinity); + assert.notOk({}); + assert.notOk([]); +}); + +QUnit.test('equal', function (assert) { + assert.equal(1, 2); + assert.equal('foo', 'bar'); + assert.equal({}, {}); + assert.equal([], []); +}); + +QUnit.test('notEqual', function (assert) { + assert.notEqual(1, 1); + assert.notEqual('foo', 'foo'); + assert.notEqual('foo', ['foo']); + assert.notEqual('foo', { toString: function () { return 'foo'; } }); + assert.notEqual(0, [0]); +}); + +QUnit.test('strictEqual', function (assert) { + assert.strictEqual(1, 2); + assert.strictEqual('foo', 'bar'); + assert.strictEqual('foo', ['foo']); + assert.strictEqual('1', 1); + assert.strictEqual('foo', { toString: function () { return 'foo'; } }); +}); + +QUnit.test('notStrictEqual', function (assert) { + assert.notStrictEqual(1, 1); + assert.notStrictEqual('foo', 'foo'); +}); + +QUnit.test('deepEqual', function (assert) { + assert.deepEqual(['foo', 'bar'], ['foo']); +}); + +QUnit.test('notDeepEqual', function (assert) { + assert.notDeepEqual(['foo', 'bar'], ['foo', 'bar']); +}); + +QUnit.test('propEqual', function (assert) { + function Foo (x, y, z) { + this.x = x; + this.y = y; + this.z = z; + } + Foo.prototype.baz = function () {}; + Foo.prototype.bar = 'prototype'; + + assert.propEqual( + new Foo('1', 2, 3), + { + x: 1, + y: '2', + z: 3 + } + ); +}); + +QUnit.test('notPropEqual', function (assert) { + function Foo (x, y, z) { + this.x = x; + this.y = y; + this.z = z; + } + Foo.prototype.baz = function () {}; + Foo.prototype.bar = 'prototype'; + + assert.notPropEqual( + new Foo(1, '2', []), + { + x: 1, + y: '2', + z: [] + } + ); +}); + +QUnit.test('throws', function (assert) { + assert.throws( + function () { + + }, + 'throws fails without a thrown error' + ); + + assert.throws( + function () { + throw 'foo'; + }, + /bar/, + "throws fail when regexp doesn't match the error message" + ); + + assert.throws( + function () { + throw 'foo'; + }, + function () { + return false; + }, + 'throws fail when expected function returns false' + ); + + // non-function actual values + assert.throws( + undefined, + 'throws fails when actual value is undefined'); + + assert.throws( + 2, + 'throws fails when actual value is a number'); + + assert.throws( + [], + 'throws fails when actual value is an array'); + + assert.throws( + 'notafunction', + 'throws fails when actual value is a string'); + + assert.throws( + {}, + 'throws fails when actual value is an object'); +}); + +QUnit.test('rejects', function (assert) { + assert.rejects( + buildMockPromise('some random value', /* shouldResolve */ true), + 'fails when the provided promise fulfills' + ); + + assert.rejects( + buildMockPromise('foo'), + /bar/, + 'rejects fails when regexp does not match' + ); + + assert.rejects( + buildMockPromise(new Error('foo')), + function RandomConstructor () { }, + 'rejects fails when rejected value is not an instance of the provided constructor' + ); + + function SomeConstructor () { } + + assert.rejects( + buildMockPromise(new SomeConstructor()), + function OtherRandomConstructor () { }, + 'rejects fails when rejected value is not an instance of the provided constructor' + ); + + assert.rejects( + buildMockPromise('some value'), + function () { return false; }, + 'rejects fails when the expected function returns false' + ); + + assert.rejects(null); +}); diff --git a/test/main/async.js b/test/main/async.js index 5878cfe2b..332fafb9a 100644 --- a/test/main/async.js +++ b/test/main/async.js @@ -1,334 +1,326 @@ -QUnit.module( "assert.async", function() { - - QUnit.test( "single call synchronously", function( assert ) { - var done; - - assert.expect( 1 ); - done = assert.async(); - - assert.true( true ); - done(); - } ); - - QUnit.test( "single call", function( assert ) { - var done = assert.async(); - - assert.expect( 1 ); - setTimeout( function() { - assert.true( true ); - done(); - } ); - } ); - - QUnit.test( "multiple calls", function( assert ) { - var done = assert.async( 4 ); - - assert.expect( 4 ); - setTimeout( function() { - assert.true( true ); - done(); - } ); - setTimeout( function() { - assert.true( true ); - done(); - } ); - setTimeout( function() { - assert.true( true ); - done(); - } ); - setTimeout( function() { - assert.true( true ); - done(); - } ); - - } ); - - QUnit.test( "parallel calls", function( assert ) { - var done1 = assert.async(), - done2 = assert.async(); - - assert.expect( 2 ); - setTimeout( function() { - assert.true( true ); - done1(); - } ); - setTimeout( function() { - assert.true( true ); - done2(); - } ); - } ); - - QUnit.test( "parallel calls of differing speeds", function( assert ) { - var done1 = assert.async(), - done2 = assert.async(); - - assert.expect( 2 ); - setTimeout( function() { - assert.true( true ); - done1(); - } ); - setTimeout( function() { - assert.true( true ); - done2(); - }, 100 ); - } ); - - QUnit.test( "waterfall calls", function( assert ) { - var done2, - done1 = assert.async(); - - assert.expect( 2 ); - setTimeout( function() { - assert.true( true, "first" ); - done1(); - done2 = assert.async(); - setTimeout( function() { - assert.true( true, "second" ); - done2(); - } ); - } ); - } ); - - QUnit.test( "waterfall calls of differing speeds", function( assert ) { - var done2, - done1 = assert.async(); - - assert.expect( 2 ); - setTimeout( function() { - assert.true( true, "first" ); - done1(); - done2 = assert.async(); - setTimeout( function() { - assert.true( true, "second" ); - done2(); - }, 100 ); - } ); - } ); - - QUnit.test( "fails if called more than once", function( assert ) { - - // Having an outer async flow in this test avoids the need to manually modify QUnit internals - // in order to avoid post-`done` assertions causing additional failures - var done = assert.async(); - - assert.expect( 1 ); - - var overDone = assert.async(); - overDone(); - - assert.throws( function() { - overDone(); - }, /Tried to release async pause that was already released/ ); - - done(); - } ); - - QUnit.test( "fails if called more than specified count", function( assert ) { - - // Having an outer async flow in this test avoids the need to manually modify QUnit internals - // in order to avoid post-`done` assertions causing additional failures - var done = assert.async(); - - assert.expect( 1 ); - - var overDone = assert.async( 3 ); - overDone(); - overDone(); - overDone(); - - assert.throws( function() { - overDone(); - }, /Tried to release async pause that was already released/ ); - - done(); - } ); - - ( function() { - var previousTestDone; - - QUnit.test( "errors if called after test finishes - part 1", function( assert ) { - assert.expect( 0 ); - previousTestDone = assert.async(); - previousTestDone(); - } ); - - QUnit.test( "errors if called after test finishes - part 2", function( assert ) { - assert.throws( - previousTestDone, - /Unexpected release of async pause during a different test.\n> Test: errors if called after test finishes - part 1/ - ); - } ); - }() ); - - QUnit.module( "overcalled in before hook", { - before: function( assert ) { - - // Having an outer async flow in this test avoids the need to manually modify QUnit - // internals in order to avoid post-`done` assertions causing additional failures - var done = assert.async(); - - var overDone = assert.async(); - overDone(); - - assert.throws( function() { - overDone(); - }, /Tried to release async pause that was already released/ ); - - done(); - } - }, function() { - QUnit.test( "test", function() {} ); - } ); - - QUnit.module( "overcalled in beforeEach hook", { - beforeEach: function( assert ) { - - // Having an outer async flow in this test avoids the need to manually modify QUnit - // internals in order to avoid post-`done` assertions causing additional failures - var done = assert.async(); - - var overDone = assert.async(); - overDone(); - - assert.throws( function() { - overDone(); - }, /Tried to release async pause that was already released/ ); - - done(); - } - }, function() { - QUnit.test( "test", function() {} ); - } ); - - QUnit.module( "overcalled in afterEach hook", { - afterEach: function( assert ) { - - // Having an outer async flow in this test avoids the need to manually modify QUnit - // internals in order to avoid post-`done` assertions causing additional failures - var done = assert.async(); - - var overDone = assert.async(); - overDone(); - - assert.throws( function() { - overDone(); - }, /Tried to release async pause that was already released/ ); - - done(); - } - }, function() { - QUnit.test( "test", function() {} ); - } ); - - QUnit.module( "overcalled in after hook", { - after: function( assert ) { - - // Having an outer async flow in this test avoids the need to manually modify QUnit - // internals in order to avoid post-`done` assertions causing additional failures - var done = assert.async(); - - var overDone = assert.async(); - overDone(); - - assert.throws( function() { - overDone(); - }, /Tried to release async pause that was already released/ ); - - done(); - } - }, function() { - QUnit.test( "test", function() {} ); - } ); - - QUnit.module( "in before hook", { - before: function( assert ) { - var done = assert.async(), - testContext = this; - setTimeout( function() { - testContext.state = "before"; - done(); - } ); - } - }, function() { - QUnit.test( "call order", function( assert ) { - assert.equal( this.state, "before", "called before test callback" ); - } ); - } ); - - QUnit.module( "in beforeEach hook", { - beforeEach: function( assert ) { - var done = assert.async(), - testContext = this; - setTimeout( function() { - testContext.state = "beforeEach"; - done(); - } ); - } - }, function() { - QUnit.test( "call order", function( assert ) { - assert.equal( this.state, "beforeEach", "called before test callback" ); - } ); - } ); - - QUnit.module( "in afterEach hook", { - afterEach: function( assert ) { - assert.equal( this.state, "done", "called after test callback" ); - assert.true( true, "called before expected assert count is validated" ); - } - }, function() { - QUnit.test( "call order", function( assert ) { - assert.expect( 2 ); - var done = assert.async(), - testContext = this; - setTimeout( function() { - testContext.state = "done"; - done(); - } ); - } ); - } ); - - QUnit.module( "in after hook", { - after: function( assert ) { - assert.equal( this.state, "done", "called after test callback" ); - assert.true( true, "called before expected assert count is validated" ); - } - }, function() { - QUnit.test( "call order", function( assert ) { - assert.expect( 2 ); - var done = assert.async(), - testContext = this; - setTimeout( function() { - testContext.state = "done"; - done(); - } ); - } ); - } ); - - QUnit.module( "assertions after final assert.async callback", { - before: function( assert ) { - assert.async()(); - assert.true( true, "before" ); - }, - - beforeEach: function( assert ) { - assert.async()(); - assert.true( true, "beforeEach" ); - }, - - afterEach: function( assert ) { - assert.async()(); - assert.true( true, "afterEach" ); - }, - - after: function( assert ) { - assert.async()(); - assert.true( true, "after" ); - } - } ); - - QUnit.test( "in any hook still pass", function( assert ) { - assert.expect( 5 ); - assert.true( true, "test callback" ); - } ); - -} ); +/* global setTimeout */ +QUnit.module('assert.async', function () { + QUnit.test('single call synchronously', function (assert) { + var done; + + assert.expect(1); + done = assert.async(); + + assert.true(true); + done(); + }); + + QUnit.test('single call', function (assert) { + var done = assert.async(); + + assert.expect(1); + setTimeout(function () { + assert.true(true); + done(); + }); + }); + + QUnit.test('multiple calls', function (assert) { + var done = assert.async(4); + + assert.expect(4); + setTimeout(function () { + assert.true(true); + done(); + }); + setTimeout(function () { + assert.true(true); + done(); + }); + setTimeout(function () { + assert.true(true); + done(); + }); + setTimeout(function () { + assert.true(true); + done(); + }); + }); + + QUnit.test('parallel calls', function (assert) { + var done1 = assert.async(); + var done2 = assert.async(); + + assert.expect(2); + setTimeout(function () { + assert.true(true); + done1(); + }); + setTimeout(function () { + assert.true(true); + done2(); + }); + }); + + QUnit.test('parallel calls of differing speeds', function (assert) { + var done1 = assert.async(); + var done2 = assert.async(); + + assert.expect(2); + setTimeout(function () { + assert.true(true); + done1(); + }); + setTimeout(function () { + assert.true(true); + done2(); + }, 100); + }); + + QUnit.test('waterfall calls', function (assert) { + var done2; + var done1 = assert.async(); + + assert.expect(2); + setTimeout(function () { + assert.true(true, 'first'); + done1(); + done2 = assert.async(); + setTimeout(function () { + assert.true(true, 'second'); + done2(); + }); + }); + }); + + QUnit.test('waterfall calls of differing speeds', function (assert) { + var done2; + var done1 = assert.async(); + + assert.expect(2); + setTimeout(function () { + assert.true(true, 'first'); + done1(); + done2 = assert.async(); + setTimeout(function () { + assert.true(true, 'second'); + done2(); + }, 100); + }); + }); + + QUnit.test('fails if called more than once', function (assert) { + // Having an outer async flow in this test avoids the need to manually modify QUnit internals + // in order to avoid post-`done` assertions causing additional failures + var done = assert.async(); + + assert.expect(1); + + var overDone = assert.async(); + overDone(); + + assert.throws(function () { + overDone(); + }, /Tried to release async pause that was already released/); + + done(); + }); + + QUnit.test('fails if called more than specified count', function (assert) { + // Having an outer async flow in this test avoids the need to manually modify QUnit internals + // in order to avoid post-`done` assertions causing additional failures + var done = assert.async(); + + assert.expect(1); + + var overDone = assert.async(3); + overDone(); + overDone(); + overDone(); + + assert.throws(function () { + overDone(); + }, /Tried to release async pause that was already released/); + + done(); + }); + + (function () { + var previousTestDone; + + QUnit.test('errors if called after test finishes - part 1', function (assert) { + assert.expect(0); + previousTestDone = assert.async(); + previousTestDone(); + }); + + QUnit.test('errors if called after test finishes - part 2', function (assert) { + assert.throws( + previousTestDone, + /Unexpected release of async pause during a different test.\n> Test: errors if called after test finishes - part 1/ + ); + }); + }()); + + QUnit.module('overcalled in before hook', { + before: function (assert) { + // Having an outer async flow in this test avoids the need to manually modify QUnit + // internals in order to avoid post-`done` assertions causing additional failures + var done = assert.async(); + + var overDone = assert.async(); + overDone(); + + assert.throws(function () { + overDone(); + }, /Tried to release async pause that was already released/); + + done(); + } + }, function () { + QUnit.test('test', function () {}); + }); + + QUnit.module('overcalled in beforeEach hook', { + beforeEach: function (assert) { + // Having an outer async flow in this test avoids the need to manually modify QUnit + // internals in order to avoid post-`done` assertions causing additional failures + var done = assert.async(); + + var overDone = assert.async(); + overDone(); + + assert.throws(function () { + overDone(); + }, /Tried to release async pause that was already released/); + + done(); + } + }, function () { + QUnit.test('test', function () {}); + }); + + QUnit.module('overcalled in afterEach hook', { + afterEach: function (assert) { + // Having an outer async flow in this test avoids the need to manually modify QUnit + // internals in order to avoid post-`done` assertions causing additional failures + var done = assert.async(); + + var overDone = assert.async(); + overDone(); + + assert.throws(function () { + overDone(); + }, /Tried to release async pause that was already released/); + + done(); + } + }, function () { + QUnit.test('test', function () {}); + }); + + QUnit.module('overcalled in after hook', { + after: function (assert) { + // Having an outer async flow in this test avoids the need to manually modify QUnit + // internals in order to avoid post-`done` assertions causing additional failures + var done = assert.async(); + + var overDone = assert.async(); + overDone(); + + assert.throws(function () { + overDone(); + }, /Tried to release async pause that was already released/); + + done(); + } + }, function () { + QUnit.test('test', function () {}); + }); + + QUnit.module('in before hook', { + before: function (assert) { + var done = assert.async(); + var testContext = this; + setTimeout(function () { + testContext.state = 'before'; + done(); + }); + } + }, function () { + QUnit.test('call order', function (assert) { + assert.equal(this.state, 'before', 'called before test callback'); + }); + }); + + QUnit.module('in beforeEach hook', { + beforeEach: function (assert) { + var done = assert.async(); + var testContext = this; + setTimeout(function () { + testContext.state = 'beforeEach'; + done(); + }); + } + }, function () { + QUnit.test('call order', function (assert) { + assert.equal(this.state, 'beforeEach', 'called before test callback'); + }); + }); + + QUnit.module('in afterEach hook', { + afterEach: function (assert) { + assert.equal(this.state, 'done', 'called after test callback'); + assert.true(true, 'called before expected assert count is validated'); + } + }, function () { + QUnit.test('call order', function (assert) { + assert.expect(2); + var done = assert.async(); + var testContext = this; + setTimeout(function () { + testContext.state = 'done'; + done(); + }); + }); + }); + + QUnit.module('in after hook', { + after: function (assert) { + assert.equal(this.state, 'done', 'called after test callback'); + assert.true(true, 'called before expected assert count is validated'); + } + }, function () { + QUnit.test('call order', function (assert) { + assert.expect(2); + var done = assert.async(); + var testContext = this; + setTimeout(function () { + testContext.state = 'done'; + done(); + }); + }); + }); + + QUnit.module('assertions after final assert.async callback', { + before: function (assert) { + assert.async()(); + assert.true(true, 'before'); + }, + + beforeEach: function (assert) { + assert.async()(); + assert.true(true, 'beforeEach'); + }, + + afterEach: function (assert) { + assert.async()(); + assert.true(true, 'afterEach'); + }, + + after: function (assert) { + assert.async()(); + assert.true(true, 'after'); + } + }); + + QUnit.test('in any hook still pass', function (assert) { + assert.expect(5); + assert.true(true, 'test callback'); + }); +}); diff --git a/test/main/deepEqual.js b/test/main/deepEqual.js index 357d1ea96..689da76fb 100644 --- a/test/main/deepEqual.js +++ b/test/main/deepEqual.js @@ -1,2011 +1,1993 @@ -/* eslint array-bracket-spacing:off, comma-spacing:off, max-len:off */ -/* globals Set:false, Map:false, Symbol:false */ - -QUnit.module( "equiv" ); - -QUnit.test( "Primitive types and constants", function( assert ) { - assert.equal( QUnit.equiv( null, null ), true, "null" ); - assert.equal( QUnit.equiv( null, {} ), false, "null" ); - assert.equal( QUnit.equiv( null, undefined ), false, "null" ); - assert.equal( QUnit.equiv( null, 0 ), false, "null" ); - assert.equal( QUnit.equiv( null, false ), false, "null" ); - assert.equal( QUnit.equiv( null, "" ), false, "null" ); - assert.equal( QUnit.equiv( null, [] ), false, "null" ); - - assert.equal( QUnit.equiv( undefined, undefined ), true, "undefined" ); - assert.equal( QUnit.equiv( undefined, null ), false, "undefined" ); - assert.equal( QUnit.equiv( undefined, 0 ), false, "undefined" ); - assert.equal( QUnit.equiv( undefined, false ), false, "undefined" ); - assert.equal( QUnit.equiv( undefined, {} ), false, "undefined" ); - assert.equal( QUnit.equiv( undefined, [] ), false, "undefined" ); - assert.equal( QUnit.equiv( undefined, "" ), false, "undefined" ); - - // Nan usually doest not equal to Nan using the '==' operator. - // Only isNaN() is able to do it. - assert.equal( QUnit.equiv( 0 / 0, 0 / 0 ), true, "NaN" ); // NaN VS NaN - assert.equal( QUnit.equiv( 1 / 0, 2 / 0 ), true, "Infinity" ); // Infinity VS Infinity - assert.equal( QUnit.equiv( -1 / 0, 2 / 0 ), false, "-Infinity, Infinity" ); // -Infinity VS Infinity - assert.equal( QUnit.equiv( -1 / 0, -2 / 0 ), true, "-Infinity, -Infinity" ); // -Infinity VS -Infinity - assert.equal( QUnit.equiv( 0 / 0, 1 / 0 ), false, "NaN, Infinity" ); // Nan VS Infinity - assert.equal( QUnit.equiv( 1 / 0, 0 / 0 ), false, "NaN, Infinity" ); // Nan VS Infinity - assert.equal( QUnit.equiv( 0 / 0, null ), false, "NaN" ); - assert.equal( QUnit.equiv( 0 / 0, undefined ), false, "NaN" ); - assert.equal( QUnit.equiv( 0 / 0, 0 ), false, "NaN" ); - assert.equal( QUnit.equiv( 0 / 0, false ), false, "NaN" ); - assert.equal( QUnit.equiv( 0 / 0, function() {} ), false, "NaN" ); - assert.equal( QUnit.equiv( 1 / 0, null ), false, "NaN, Infinity" ); - assert.equal( QUnit.equiv( 1 / 0, undefined ), false, "NaN, Infinity" ); - assert.equal( QUnit.equiv( 1 / 0, 0 ), false, "NaN, Infinity" ); - assert.equal( QUnit.equiv( 1 / 0, 1 ), false, "NaN, Infinity" ); - assert.equal( QUnit.equiv( 1 / 0, false ), false, "NaN, Infinity" ); - assert.equal( QUnit.equiv( 1 / 0, true ), false, "NaN, Infinity" ); - assert.equal( QUnit.equiv( 1 / 0, function() {} ), false, "NaN, Infinity" ); - - assert.equal( QUnit.equiv( 0, 0 ), true, "number" ); - assert.equal( QUnit.equiv( 0, 1 ), false, "number" ); - assert.equal( QUnit.equiv( 1, 0 ), false, "number" ); - assert.equal( QUnit.equiv( 1, 1 ), true, "number" ); - assert.equal( QUnit.equiv( 1.1, 1.1 ), true, "number" ); - assert.equal( QUnit.equiv( 0.0000005, 0.0000005 ), true, "number" ); - assert.equal( QUnit.equiv( 0, "" ), false, "number" ); - assert.equal( QUnit.equiv( 0, "0" ), false, "number" ); - assert.equal( QUnit.equiv( 1, "1" ), false, "number" ); - assert.equal( QUnit.equiv( 0, false ), false, "number" ); - assert.equal( QUnit.equiv( 1, true ), false, "number" ); - - assert.equal( QUnit.equiv( true, true ), true, "boolean" ); - assert.equal( QUnit.equiv( true, false ), false, "boolean" ); - assert.equal( QUnit.equiv( false, true ), false, "boolean" ); - assert.equal( QUnit.equiv( false, 0 ), false, "boolean" ); - assert.equal( QUnit.equiv( false, null ), false, "boolean" ); - assert.equal( QUnit.equiv( false, undefined ), false, "boolean" ); - assert.equal( QUnit.equiv( true, 1 ), false, "boolean" ); - assert.equal( QUnit.equiv( true, null ), false, "boolean" ); - assert.equal( QUnit.equiv( true, undefined ), false, "boolean" ); - - assert.equal( QUnit.equiv( "", "" ), true, "string" ); - assert.equal( QUnit.equiv( "a", "a" ), true, "string" ); - assert.equal( QUnit.equiv( "foobar", "foobar" ), true, "string" ); - assert.equal( QUnit.equiv( "foobar", "foo" ), false, "string" ); - assert.equal( QUnit.equiv( "", 0 ), false, "string" ); - assert.equal( QUnit.equiv( "", false ), false, "string" ); - assert.equal( QUnit.equiv( "", null ), false, "string" ); - assert.equal( QUnit.equiv( "", undefined ), false, "string" ); - - // Rename for lint validation. - // We know this is bad, we are asserting whether we can coop with bad code like this. - var SafeNumber = Number, - SafeString = String, - SafeBoolean = Boolean, - SafeObject = Object; - - // primitives vs. objects - - assert.equal( QUnit.equiv( 0, new SafeNumber() ), true, "number 0 primitive vs. object" ); - assert.equal( QUnit.equiv( new SafeNumber(), 0 ), true, "number 0 object vs. primitive" ); - assert.equal( QUnit.equiv( new SafeNumber(), new SafeNumber() ), true, "empty number objects" ); - assert.equal( QUnit.equiv( 1, new SafeNumber( 1 ) ), true, "number 1 primitive vs. object" ); - assert.equal( QUnit.equiv( new SafeNumber( 1 ), 1 ), true, "number 1 object vs. primitive" ); - assert.equal( QUnit.equiv( new SafeNumber( 1 ), new SafeNumber( 1 ) ), true, "number 1 objects" ); - assert.equal( QUnit.equiv( 0, new SafeNumber( 1 ) ), false, "differing number primitive vs. object" ); - assert.equal( QUnit.equiv( new SafeNumber( 0 ), 1 ), false, "differing number object vs. primitive" ); - - assert.equal( QUnit.equiv( "", new SafeString() ), true, "empty string primitive vs. object" ); - assert.equal( QUnit.equiv( new SafeString(), "" ), true, "empty string object vs. primitive" ); - assert.equal( QUnit.equiv( new SafeString(), new SafeString() ), true, "empty string objects" ); - assert.equal( QUnit.equiv( "My String", new SafeString( "My String" ) ), true, "nonempty string primitive vs. object" ); - assert.equal( QUnit.equiv( new SafeString( "My String" ), "My String" ), true, "nonempty string object vs. primitive" ); - assert.equal( QUnit.equiv( new SafeString( "My String" ), new SafeString( "My String" ) ), true, "nonempty string objects" ); - assert.equal( QUnit.equiv( "Bad String", new SafeString( "My String" ) ), false, "differing string primitive vs. object" ); - assert.equal( QUnit.equiv( new SafeString( "Bad String" ), "My String" ), false, "differing string object vs. primitive" ); - - assert.equal( QUnit.equiv( false, new SafeBoolean() ), true, "boolean false primitive vs. object" ); - assert.equal( QUnit.equiv( new SafeBoolean(), false ), true, "boolean empty object vs. primitive" ); - assert.equal( QUnit.equiv( new SafeBoolean(), new SafeBoolean() ), true, "empty boolean objects" ); - assert.equal( QUnit.equiv( true, new SafeBoolean( true ) ), true, "boolean true primitive vs. object" ); - assert.equal( QUnit.equiv( new SafeBoolean( true ), true ), true, "boolean true object vs. primitive" ); - assert.equal( QUnit.equiv( new SafeBoolean( true ), new SafeBoolean( true ) ), true, "boolean true objects" ); - assert.equal( QUnit.equiv( true, new SafeBoolean( 1 ) ), true, "boolean true primitive vs. truthy object" ); - assert.equal( QUnit.equiv( false, new SafeBoolean( false ) ), true, "boolean false primitive vs. false object" ); - assert.equal( QUnit.equiv( new SafeBoolean( false ), false ), true, "boolean false object vs. primitive" ); - assert.equal( QUnit.equiv( new SafeBoolean( false ), new SafeBoolean( false ) ), true, "boolean false objects" ); - assert.equal( QUnit.equiv( false, new SafeBoolean( 0 ) ), true, "boolean false primitive vs. 0 object" ); - assert.equal( QUnit.equiv( true, new SafeBoolean( false ) ), false, "differing boolean primitive vs. object" ); - assert.equal( QUnit.equiv( new SafeBoolean( false ), true ), false, "differing boolean object vs. primitive" ); - - assert.equal( QUnit.equiv( new SafeObject(), {} ), true, "empty object instantiation vs. literal" ); - assert.equal( QUnit.equiv( {}, new SafeObject() ), true, "empty object literal vs. instantiation" ); - assert.equal( QUnit.equiv( new SafeObject(), { a: 1 } ), false, "empty object instantiation vs. nonempty literal" ); - assert.equal( QUnit.equiv( { a: 1 }, new SafeObject() ), false, "nonempty object literal vs. empty instantiation" ); - assert.equal( QUnit.equiv( { a: undefined }, new SafeObject() ), false, "other nonempty object literal vs. empty instantiation" ); - assert.equal( QUnit.equiv( new SafeObject(), { a: undefined } ), false, "empty object instantiation vs. other nonempty literal" ); -} ); - -QUnit.test( "Consecutive argument pairs", function( assert ) { - - // degenerate cases with <2 inputs - assert.equal( QUnit.equiv( ), true ); - assert.equal( QUnit.equiv( 1 ), true ); - - // otherwise every "consecutive pair" must be equivalent - assert.equal( QUnit.equiv( 1, 1, 1 ), true ); - assert.equal( QUnit.equiv( 1, 1, 2 ), false ); - assert.equal( QUnit.equiv( 1, 1, 1, 1 ), true ); - assert.equal( QUnit.equiv( 1, 1, 1, 2 ), false ); - assert.equal( QUnit.equiv( 1, 1, 1, 1, 1 ), true ); -} ); - -QUnit.test( "Objects basics", function( assert ) { - assert.equal( QUnit.equiv( {}, {} ), true ); - assert.equal( QUnit.equiv( {}, null ), false ); - assert.equal( QUnit.equiv( {}, undefined ), false ); - assert.equal( QUnit.equiv( {}, 0 ), false ); - assert.equal( QUnit.equiv( {}, false ), false ); - - // This test is a hard one, it is very important - // REASONS: - // 1) They are of the same type "object" - // 2) [] instanceof Object is true - // 3) Their properties are the same (doesn't exists) - assert.equal( QUnit.equiv( {}, [] ), false ); - - assert.equal( QUnit.equiv( { a: 1 }, { a: 1 } ), true ); - assert.equal( QUnit.equiv( { a: 1 }, { a: "1" } ), false ); - assert.equal( QUnit.equiv( { a: [] }, { a: [] } ), true ); - assert.equal( QUnit.equiv( { a: {} }, { a: null } ), false ); - assert.equal( QUnit.equiv( { a: 1 }, {} ), false ); - assert.equal( QUnit.equiv( {}, { a: 1 } ), false ); - - // Hard ones - assert.equal( QUnit.equiv( { a: undefined }, {} ), false ); - assert.equal( QUnit.equiv( {}, { a: undefined } ), false ); - assert.equal( QUnit.equiv( - { - a: [ { bar: undefined } ] - }, - { - a: [ { bat: undefined } ] - } - ), false ); - -} ); - -QUnit.test( "Objects with null prototypes", function( assert ) { - var nonEmptyWithNoProto; - - // Objects with no prototype, created via Object.create(null), are used - // e.g. as dictionaries. - // Being able to test equivalence against object literals is quite useful. - assert.equal( - QUnit.equiv( Object.create( null ), {} ), - true, - "empty object without prototype VS empty object" - ); - - assert.equal( - QUnit.equiv( {}, Object.create( null ) ), - true, - "empty object VS empty object without prototype" - ); - - nonEmptyWithNoProto = Object.create( null ); - nonEmptyWithNoProto.foo = "bar"; - - assert.equal( - QUnit.equiv( nonEmptyWithNoProto, { foo: "bar" } ), - true, - "object without prototype VS object" - ); - - assert.equal( - QUnit.equiv( { foo: "bar" }, nonEmptyWithNoProto ), - true, - "object VS object without prototype" - ); -} ); - -QUnit.test( "Object prototype constructor is null", function( assert ) { - - // Ref https://github.com/qunitjs/qunit/issues/851 - // Instances of this custom class have similar characteristics to null-objects. - function NullObject() {} - NullObject.prototype = Object.create( null, { - constructor: { - value: null - } - } ); - - var a = new NullObject(); - a.foo = 1; - var b = { foo: 1 }; - - assert.true( QUnit.equiv( a, b ) ); - assert.true( QUnit.equiv( b, a ) ); -} ); - -QUnit.test( "Arrays basics", function( assert ) { - - assert.equal( QUnit.equiv( [], [] ), true ); - - // May be a hard one, can invoke a crash at execution. - // because their types are both "object" but null isn't - // like a true object, it doesn't have any property at all. - assert.equal( QUnit.equiv( [], null ), false ); - - assert.equal( QUnit.equiv( [], undefined ), false ); - assert.equal( QUnit.equiv( [], false ), false ); - assert.equal( QUnit.equiv( [], 0 ), false ); - assert.equal( QUnit.equiv( [], "" ), false ); - - // May be a hard one, but less hard - // than {} with [] (note the order) - assert.equal( QUnit.equiv( [], {} ), false ); - - assert.equal( QUnit.equiv( [ null ], [] ), false ); - assert.equal( QUnit.equiv( [ undefined ], [] ), false ); - assert.equal( QUnit.equiv( [], [ null ] ), false ); - assert.equal( QUnit.equiv( [], [ undefined ] ), false ); - assert.equal( QUnit.equiv( [ null ], [ undefined ] ), false ); - assert.equal( QUnit.equiv( [ [] ], [ [] ] ), true ); - assert.equal( QUnit.equiv( [ [], [], [] ], [ [], [], [] ] ), true ); - - assert.equal( - QUnit.equiv( - [[],[],[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]], - [[],[],[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]] ), - true - ); - - assert.equal( - QUnit.equiv( - [[],[],[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]], - [[],[],[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]] ), // shorter - false - ); - - assert.equal( - QUnit.equiv( - [[],[],[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[ {} ]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]], - [[],[],[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]] ), // deepest element not an array - false - ); - - // same multidimensional - assert.equal( - QUnit.equiv( - [ 1,2,3,4,5,6,7,8,9, [ - 1,2,3,4,5,6,7,8,9, [ - 1,2,3,4,5,[ - [ 6,7,8,9, [ - [ - 1,2,3,4,[ - 2,3,4,[ - 1,2,[ - 1,2,3,4,[ - 1,2,3,4,5,6,7,8,9,[ - 0 - ],1,2,3,4,5,6,7,8,9 - ],5,6,7,8,9 - ],4,5,6,7,8,9 - ],5,6,7,8,9 - ],5,6,7 - ] - ] - ] - ] - ]]], - [ 1,2,3,4,5,6,7,8,9, [ - 1,2,3,4,5,6,7,8,9, [ - 1,2,3,4,5,[ - [ 6,7,8,9, [ - [ - 1,2,3,4,[ - 2,3,4,[ - 1,2,[ - 1,2,3,4,[ - 1,2,3,4,5,6,7,8,9,[ - 0 - ],1,2,3,4,5,6,7,8,9 - ],5,6,7,8,9 - ],4,5,6,7,8,9 - ],5,6,7,8,9 - ],5,6,7 - ] - ] - ] - ] - ]]] - ), - true, - "Multidimensional" - ); - - // different multidimensional - assert.equal( - QUnit.equiv( - [ 1,2,3,4,5,6,7,8,9, [ - 1,2,3,4,5,6,7,8,9, [ - 1,2,3,4,5,[ - [ 6,7,8,9, [ - [ - 1,2,3,4,[ - 2,3,4,[ - 1,2,[ - 1,2,3,4,[ - 1,2,3,4,5,6,7,8,9,[ - 0 - ],1,2,3,4,5,6,7,8,9 - ],5,6,7,8,9 - ],4,5,6,7,8,9 - ],5,6,7,8,9 - ],5,6,7 - ] - ] - ] - ] - ]]], - [ 1,2,3,4,5,6,7,8,9, [ - 1,2,3,4,5,6,7,8,9, [ - 1,2,3,4,5,[ - [ 6,7,8,9, [ - [ - 1,2,3,4,[ - 2,3,4,[ - 1,2,[ - "1",2,3,4,[ // string instead of number - 1,2,3,4,5,6,7,8,9,[ - 0 - ],1,2,3,4,5,6,7,8,9 - ],5,6,7,8,9 - ],4,5,6,7,8,9 - ],5,6,7,8,9 - ],5,6,7 - ] - ] - ] - ] - ]]] - ), - false, - "Multidimensional" - ); - - // different multidimensional - assert.equal( - QUnit.equiv( - [ 1,2,3,4,5,6,7,8,9, [ - 1,2,3,4,5,6,7,8,9, [ - 1,2,3,4,5,[ - [ 6,7,8,9, [ - [ - 1,2,3,4,[ - 2,3,4,[ - 1,2,[ - 1,2,3,4,[ - 1,2,3,4,5,6,7,8,9,[ - 0 - ],1,2,3,4,5,6,7,8,9 - ],5,6,7,8,9 - ],4,5,6,7,8,9 - ],5,6,7,8,9 - ],5,6,7 - ] - ] - ] - ] - ]]], - [ 1,2,3,4,5,6,7,8,9, [ - 1,2,3,4,5,6,7,8,9, [ - 1,2,3,4,5,[ - [ 6,7,8,9, [ - [ - 1,2,3,4,[ - 2,3,[ // missing an element (4) - 1,2,[ - 1,2,3,4,[ - 1,2,3,4,5,6,7,8,9,[ - 0 - ],1,2,3,4,5,6,7,8,9 - ],5,6,7,8,9 - ],4,5,6,7,8,9 - ],5,6,7,8,9 - ],5,6,7 - ] - ] - ] - ] - ]]] - ), - false, - "Multidimensional" - ); -} ); - -QUnit.test( "Functions", function( assert ) { - var f0 = function() {}, - f1 = function() {}, - - // f2 and f3 have the same code, formatted differently - f2 = function() { return 0; }, - f3 = function() { - - // eslint-disable-next-line semi - return 0 // this comment and no semicoma as difference - }; - - assert.equal( QUnit.equiv( function() {}, function() {} ), false, "Anonymous functions" ); // exact source code - assert.equal( QUnit.equiv( function() {}, function() { - return true; - } ), false, "Anonymous functions" ); - - assert.equal( QUnit.equiv( f0, f0 ), true, "Function references" ); // same references - assert.equal( QUnit.equiv( f0, f1 ), false, "Function references" ); // exact source code, different references - assert.equal( QUnit.equiv( f2, f3 ), false, "Function references" ); // equivalent source code, different references - assert.equal( QUnit.equiv( f1, f2 ), false, "Function references" ); // different source code, different references - assert.equal( QUnit.equiv( function() {}, true ), false ); - assert.equal( QUnit.equiv( function() {}, undefined ), false ); - assert.equal( QUnit.equiv( function() {}, null ), false ); - assert.equal( QUnit.equiv( function() {}, {} ), false ); -} ); - -QUnit.test( "Date instances", function( assert ) { - - // Date, we don't need to test Date.parse() because it returns a number. - // Only test the Date instances by setting them a fix date. - // The date use is midnight January 1, 1970 - var d1, d2, d3; - - d1 = new Date(); - d1.setTime( 0 ); // fix the date - - d2 = new Date(); - d2.setTime( 0 ); // fix the date - - d3 = new Date(); // The very now - - // Anyway their types differs, just in case the code fails in the order in which it deals with date - assert.equal( QUnit.equiv( d1, 0 ), false ); // d1.valueOf() returns 0, but d1 and 0 are different - - // test same values date and different instances equality - assert.equal( QUnit.equiv( d1, d2 ), true ); - - // test different date and different instances difference - assert.equal( QUnit.equiv( d1, d3 ), false ); -} ); - -QUnit.test( "RegExp", function( assert ) { - - // Must test cases that imply those traps: - // var a = /./; - // a instanceof Object; // Oops - // a instanceof RegExp; // Oops - // typeof a === "function"; // Oops, false in IE and Opera, true in FF and Safari ("object") - - // Tests same regex with same modifiers in different order - var regex1, regex2, regex3, r3a, r3b, ru1, ru2, - r1 = /foo/, - r2 = /foo/gim, - r3 = /foo/gmi, - r4 = /foo/igm, - r5 = /foo/img, - r6 = /foo/mig, - r7 = /foo/mgi, - ri1 = /foo/i, - ri2 = /foo/i, - rm1 = /foo/m, - rm2 = /foo/m, - rg1 = /foo/g, - rg2 = /foo/g; - - assert.equal( QUnit.equiv( r2, r3 ), true, "Modifier order" ); - assert.equal( QUnit.equiv( r2, r4 ), true, "Modifier order" ); - assert.equal( QUnit.equiv( r2, r5 ), true, "Modifier order" ); - assert.equal( QUnit.equiv( r2, r6 ), true, "Modifier order" ); - assert.equal( QUnit.equiv( r2, r7 ), true, "Modifier order" ); - assert.equal( QUnit.equiv( r1, r2 ), false, "Modifier" ); - - assert.equal( QUnit.equiv( ri1, ri2 ), true, "Modifier" ); - assert.equal( QUnit.equiv( r1, ri1 ), false, "Modifier" ); - assert.equal( QUnit.equiv( ri1, rm1 ), false, "Modifier" ); - assert.equal( QUnit.equiv( r1, rm1 ), false, "Modifier" ); - assert.equal( QUnit.equiv( rm1, ri1 ), false, "Modifier" ); - assert.equal( QUnit.equiv( rm1, rm2 ), true, "Modifier" ); - assert.equal( QUnit.equiv( rg1, rm1 ), false, "Modifier" ); - assert.equal( QUnit.equiv( rm1, rg1 ), false, "Modifier" ); - assert.equal( QUnit.equiv( rg1, rg2 ), true, "Modifier" ); - - // Compare unicode modifier - // Support IE 11: Use RegExp constructor to avoid syntax error - try { - r2 = new RegExp( "foo", "umig" ); - r3 = new RegExp( "foo", "mgiu" ); - assert.equal( QUnit.equiv( r2, r3 ), true, "Modifier order" ); - assert.equal( QUnit.equiv( r1, r2 ), false, "Modifier" ); - - ru1 = new RegExp( "\\u{1D306}", "u" ); - ru2 = new RegExp( "\\u{1D306}", "u" ); - assert.equal( QUnit.equiv( ru1, rg1 ), false, "Modifier" ); - assert.equal( QUnit.equiv( rg1, ru1 ), false, "Modifier" ); - assert.equal( QUnit.equiv( ru1, ru2 ), true, "Modifier" ); - } catch ( e ) {} // eslint-disable-line no-empty - - // Different regex, same modifiers - r1 = /[a-z]/gi; - r2 = /[0-9]/gi; // oops! different - assert.equal( QUnit.equiv( r1, r2 ), false, "Regex pattern" ); - - r1 = /0/ig; - r2 = /"0"/ig; // oops! different - assert.equal( QUnit.equiv( r1, r2 ), false, "Regex pattern" ); - - r1 = /[\n\r\u2028\u2029]/g; - r2 = /[\n\r\u2028\u2029]/g; - r3 = /[\n\r\u2028\u2028]/g; // differs from r1 - r4 = /[\n\r\u2028\u2029]/; // differs from r1 - - assert.equal( QUnit.equiv( r1, r2 ), true, "Regex pattern" ); - assert.equal( QUnit.equiv( r1, r3 ), false, "Regex pattern" ); - assert.equal( QUnit.equiv( r1, r4 ), false, "Regex pattern" ); - - // More complex regex - regex1 = "^[-_.a-z0-9]+@([-_a-z0-9]+\\.)+([A-Za-z][A-Za-z]|[A-Za-z][A-Za-z][A-Za-z])|(([0-9][0-9]?|[0-1][0-9][0-9]|[2][0-4][0-9]|[2][5][0-5]))$"; - regex2 = "^[-_.a-z0-9]+@([-_a-z0-9]+\\.)+([A-Za-z][A-Za-z]|[A-Za-z][A-Za-z][A-Za-z])|(([0-9][0-9]?|[0-1][0-9][0-9]|[2][0-4][0-9]|[2][5][0-5]))$"; - - // regex 3 is different: '.' not escaped - regex3 = "^[-_.a-z0-9]+@([-_a-z0-9]+.)+([A-Za-z][A-Za-z]|[A-Za-z][A-Za-z][A-Za-z])|(([0-9][0-9]?|[0-1][0-9][0-9]|[2][0-4][0-9]|[2][5][0-5]))$"; - - r1 = new RegExp( regex1 ); - r2 = new RegExp( regex2 ); - r3 = new RegExp( regex3 ); // diff from r21, not same pattern - r3a = new RegExp( regex3, "gi" ); // diff from r23, not same modifier - r3b = new RegExp( regex3, "ig" ); // same as r23a - - assert.equal( QUnit.equiv( r1, r2 ), true, "Complex Regex" ); - assert.equal( QUnit.equiv( r1, r3 ), false, "Complex Regex" ); - assert.equal( QUnit.equiv( r3, r3a ), false, "Complex Regex" ); - assert.equal( QUnit.equiv( r3a, r3b ), true, "Complex Regex" ); - - // typeof r1 is "function" in some browsers and "object" in others so we must cover this test - r1 = / /; - assert.equal( QUnit.equiv( r1, function() {} ), false, "Regex internal" ); - assert.equal( QUnit.equiv( r1, {} ), false, "Regex internal" ); -} ); - -QUnit.test( "Complex objects", function( assert ) { - - function fn1() { - return "fn1"; - } - function fn2() { - return "fn2"; - } - - // Try to invert the order of some properties to make sure it is covered. - // It can failed when properties are compared between unsorted arrays. - assert.equal( QUnit.equiv( - { - a: 1, - b: null, - c: [ {} ], - d: { - a: 3.14159, - b: false, - c: { - e: fn1, - f: [[[]]], - g: { - j: { - k: { - n: { - r: "r", - s: [ 1, 2, 3 ], - t: undefined, - u: 0, - v: { - w: { - x: { - y: "Yahoo!", - z: null - } - } - } - }, - q: [], - p: 1 / 0, - o: 99 - }, - l: undefined, - m: null - } - }, - d: 0, - i: true, - h: "false" - } - }, - e: undefined, - g: "", - h: "h", - f: {}, - i: [] - }, - { - a: 1, - b: null, - c: [ {} ], - d: { - b: false, - a: 3.14159, - c: { - d: 0, - e: fn1, - f: [[[]]], - g: { - j: { - k: { - n: { - r: "r", - t: undefined, - u: 0, - s: [ 1, 2, 3 ], - v: { - w: { - x: { - z: null, - y: "Yahoo!" - } - } - } - }, - o: 99, - p: 1 / 0, - q: [] - }, - l: undefined, - m: null - } - }, - i: true, - h: "false" - } - }, - e: undefined, - g: "", - f: {}, - h: "h", - i: [] - } - ), true ); - - assert.equal( QUnit.equiv( - { - a: 1, - b: null, - c: [ {} ], - d: { - a: 3.14159, - b: false, - c: { - d: 0, - e: fn1, - f: [[[]]], - g: { - j: { - k: { - n: { - - //r: "r", // different: missing a property - s: [ 1, 2, 3 ], - t: undefined, - u: 0, - v: { - w: { - x: { - y: "Yahoo!", - z: null - } - } - } - }, - o: 99, - p: 1 / 0, - q: [] - }, - l: undefined, - m: null - } - }, - h: "false", - i: true - } - }, - e: undefined, - f: {}, - g: "", - h: "h", - i: [] - }, - { - a: 1, - b: null, - c: [ {} ], - d: { - a: 3.14159, - b: false, - c: { - d: 0, - e: fn1, - f: [[[]]], - g: { - j: { - k: { - n: { - r: "r", - s: [ 1, 2, 3 ], - t: undefined, - u: 0, - v: { - w: { - x: { - y: "Yahoo!", - z: null - } - } - } - }, - o: 99, - p: 1 / 0, - q: [] - }, - l: undefined, - m: null - } - }, - h: "false", - i: true - } - }, - e: undefined, - f: {}, - g: "", - h: "h", - i: [] - } - ), false ); - - assert.equal( QUnit.equiv( - { - a: 1, - b: null, - c: [ {} ], - d: { - a: 3.14159, - b: false, - c: { - d: 0, - e: fn1, - f: [[[]]], - g: { - j: { - k: { - n: { - r: "r", - s: [ 1, 2, 3 ], - t: undefined, - u: 0, - v: { - w: { - x: { - y: "Yahoo!", - z: null - } - } - } - }, - o: 99, - p: 1 / 0, - q: [] - }, - l: undefined, - m: null - } - }, - h: "false", - i: true - } - }, - e: undefined, - f: {}, - g: "", - h: "h", - i: [] - }, - { - a: 1, - b: null, - c: [ {} ], - d: { - a: 3.14159, - b: false, - c: { - d: 0, - e: fn1, - f: [[[]]], - g: { - j: { - k: { - n: { - r: "r", - s: [ 1, 2, 3 ], - - //t: undefined, // different: missing a property with an undefined value - u: 0, - v: { - w: { - x: { - y: "Yahoo!", - z: null - } - } - } - }, - o: 99, - p: 1 / 0, - q: [] - }, - l: undefined, - m: null - } - }, - h: "false", - i: true - } - }, - e: undefined, - f: {}, - g: "", - h: "h", - i: [] - } - ), false ); - - assert.equal( QUnit.equiv( - { - a: 1, - b: null, - c: [ {} ], - d: { - a: 3.14159, - b: false, - c: { - d: 0, - e: fn1, - f: [[[]]], - g: { - j: { - k: { - n: { - r: "r", - s: [ 1, 2, 3 ], - t: undefined, - u: 0, - v: { - w: { - x: { - y: "Yahoo!", - z: null - } - } - } - }, - o: 99, - p: 1 / 0, - q: [] - }, - l: undefined, - m: null - } - }, - h: "false", - i: true - } - }, - e: undefined, - f: {}, - g: "", - h: "h", - i: [] - }, - { - a: 1, - b: null, - c: [ {} ], - d: { - a: 3.14159, - b: false, - c: { - d: 0, - e: fn1, - f: [[[]]], - g: { - j: { - k: { - n: { - r: "r", - s: [ 1, 2, 3 ], - t: undefined, - u: 0, - v: { - w: { - x: { - y: "Yahoo!", - z: null - } - } - } - }, - o: 99, - p: 1 / 0, - q: {} // different was [] - }, - l: undefined, - m: null - } - }, - h: "false", - i: true - } - }, - e: undefined, - f: {}, - g: "", - h: "h", - i: [] - } - ), false ); - - var same1 = { - a: [ - "string", null, 0, "1", 1, { - prop: null, - foo: [ 1, 2, null, {}, [], [ 1, 2, 3 ] ], - bar: undefined - }, 3, "Hey!", "Κάνε πάντα γνωÏίζουμε ας των, μηχανής επιδιόÏθωσης επιδιοÏθώσεις ÏŽÏ‚ μια. Κλπ ας" - ], - unicode: "è€ æ±‰è¯ä¸å˜åœ¨ 港澳和海外的åŽäººåœˆä¸ 贵州 我去了书店 现在尚有争", - b: "b", - c: fn1 - }, - - same2 = { - a: [ - "string", null, 0, "1", 1, { - prop: null, - foo: [ 1, 2, null, {}, [], [ 1, 2, 3 ] ], - bar: undefined - }, 3, "Hey!", "Κάνε πάντα γνωÏίζουμε ας των, μηχανής επιδιόÏθωσης επιδιοÏθώσεις ÏŽÏ‚ μια. Κλπ ας" - ], - unicode: "è€ æ±‰è¯ä¸å˜åœ¨ 港澳和海外的åŽäººåœˆä¸ 贵州 我去了书店 现在尚有争", - b: "b", - c: fn1 - }, - - diff1 = { - a: [ - "string", null, 0, "1", 1, { - prop: null, - foo: [ 1, 2, null, {}, [], [ 1, 2, 3, 4 ] ], // different: 4 was add to the array - bar: undefined - }, 3, "Hey!", "Κάνε πάντα γνωÏίζουμε ας των, μηχανής επιδιόÏθωσης επιδιοÏθώσεις ÏŽÏ‚ μια. Κλπ ας" - ], - unicode: "è€ æ±‰è¯ä¸å˜åœ¨ 港澳和海外的åŽäººåœˆä¸ 贵州 我去了书店 现在尚有争", - b: "b", - c: fn1 - }, - - diff2 = { - a: [ - "string", null, 0, "1", 1, { - prop: null, - foo: [ 1, 2, null, {}, [], [ 1, 2, 3 ] ], - newprop: undefined, // different: newprop was added - bar: undefined - }, 3, "Hey!", "Κάνε πάντα γνωÏίζουμε ας των, μηχανής επιδιόÏθωσης επιδιοÏθώσεις ÏŽÏ‚ μια. Κλπ ας" - ], - unicode: "è€ æ±‰è¯ä¸å˜åœ¨ 港澳和海外的åŽäººåœˆä¸ 贵州 我去了书店 现在尚有争", - b: "b", - c: fn1 - }, - - diff3 = { - a: [ - "string", null, 0, "1", 1, { - prop: null, - foo: [ 1, 2, null, {}, [], [ 1, 2, 3 ] ], - bar: undefined - }, 3, "Hey!", "Κάνε πάντα γνωÏίζουμε ας των, μηχανής επιδιόÏθωσης επιδιοÏθώσεις ÏŽÏ‚ μια. Κλπ α" // different: missing last char - ], - unicode: "è€ æ±‰è¯ä¸å˜åœ¨ 港澳和海外的åŽäººåœˆä¸ 贵州 我去了书店 现在尚有争", - b: "b", - c: fn1 - }, - - diff4 = { - a: [ - "string", null, 0, "1", 1, { - prop: null, - foo: [ 1,2,undefined,{}, [], [ 1, 2, 3 ] ], // different: undefined instead of null - bar: undefined - }, 3, "Hey!", "Κάνε πάντα γνωÏίζουμε ας των, μηχανής επιδιόÏθωσης επιδιοÏθώσεις ÏŽÏ‚ μια. Κλπ ας" - ], - unicode: "è€ æ±‰è¯ä¸å˜åœ¨ 港澳和海外的åŽäººåœˆä¸ 贵州 我去了书店 现在尚有争", - b: "b", - c: fn1 - }, - - diff5 = { - a: [ - "string", null, 0, "1", 1, { - prop: null, - foo: [ 1, 2, null, {}, [], [ 1, 2, 3 ] ], - bat: undefined // different: property name not "bar" - }, 3, "Hey!", "Κάνε πάντα γνωÏίζουμε ας των, μηχανής επιδιόÏθωσης επιδιοÏθώσεις ÏŽÏ‚ μια. Κλπ ας" - ], - unicode: "è€ æ±‰è¯ä¸å˜åœ¨ 港澳和海外的åŽäººåœˆä¸ 贵州 我去了书店 现在尚有争", - b: "b", - c: fn1 - }, - - diff6 = { - a: [ - "string", null, 0, "1", 1, { - prop: null, - foo: [ 1, 2, null, {}, [], [ 1, 2, 3 ] ], - bar: undefined - }, 3, "Hey!", "Κάνε πάντα γνωÏίζουμε ας των, μηχανής επιδιόÏθωσης επιδιοÏθώσεις ÏŽÏ‚ μια. Κλπ ας" - ], - unicode: "è€ æ±‰è¯ä¸å˜åœ¨ 港澳和海外的åŽäººåœˆä¸ 贵州 我去了书店 现在尚有争", - b: "b", - c: fn2 // different: fn2 instead of fn1 - }; - - assert.equal( QUnit.equiv( same1, same2 ), true ); - assert.equal( QUnit.equiv( same2, same1 ), true ); - assert.equal( QUnit.equiv( same2, diff1 ), false ); - assert.equal( QUnit.equiv( diff1, same2 ), false ); - - assert.equal( QUnit.equiv( same1, diff1 ), false ); - assert.equal( QUnit.equiv( same1, diff2 ), false ); - assert.equal( QUnit.equiv( same1, diff3 ), false ); - assert.equal( QUnit.equiv( same1, diff3 ), false ); - assert.equal( QUnit.equiv( same1, diff4 ), false ); - assert.equal( QUnit.equiv( same1, diff5 ), false ); - assert.equal( QUnit.equiv( same1, diff6 ), false ); - assert.equal( QUnit.equiv( diff5, diff1 ), false ); -} ); - -QUnit.test( "Complex Arrays", function( assert ) { - - function fn() {} - - assert.equal( - QUnit.equiv( - [ 1, 2, 3, true, {}, null, [ - { - a: [ "", "1", 0 ] - }, - 5, 6, 7 - ], "foo" ], - [ 1, 2, 3, true, {}, null, [ - { - a: [ "", "1", 0 ] - }, - 5, 6, 7 - ], "foo" ] - ), - true - ); - - assert.equal( - QUnit.equiv( - [ 1, 2, 3, true, {}, null, [ - { - a: [ "", "1", 0 ] - }, - 5, 6, 7 - ], "foo" ], - [ 1, 2, 3, true, {}, null, [ - { - b: [ "", "1", 0 ] // not same property name - }, - 5, 6, 7 - ], "foo" ] - ), - false - ); - - var a = [ { - b: fn, - c: false, - "do": "reserved word", - "for": { - ar: [ 3, 5, 9, "hey!", [], { - ar: [ 1,[ - 3,4,6,9, null, [], [] - ] ], - e: fn, - f: undefined - } ] - }, - e: 0.43445 - }, 5, "string", 0, fn, false, null, undefined, 0, [ - 4,5,6,7,8,9,11,22,33,44,55,"66", null, [], [[[[[ 3 ]]]], "3" ], {}, 1 / 0 - ], [], [ [ [], "foo", null, { - n: 1 / 0, - z: { - a: [ 3, 4, 5, 6, "yep!", undefined, undefined ], - b: {} - } - }, {} ] ] ]; - - assert.equal( QUnit.equiv( a, - [ { - b: fn, - c: false, - "do": "reserved word", - "for": { - ar: [ 3, 5, 9, "hey!", [], { - ar: [ 1, [ - 3,4,6,9, null, [], [] - ]], - e: fn, - f: undefined - } ] - }, - e: 0.43445 - }, 5, "string", 0, fn, false, null, undefined, 0, [ - 4,5,6,7,8,9,11,22,33,44,55,"66", null, [], [[[[[ 3 ]]]], "3" ], {}, 1 / 0 - ], [], [[[], "foo", null, { - n: 1 / 0, - z: { - a: [ 3, 4, 5, 6, "yep!", undefined, undefined ], - b: {} - } - }, {}]]] ), true ); - - assert.equal( QUnit.equiv( a, - [ { - b: fn, - c: false, - "do": "reserved word", - "for": { - ar: [ 3, 5, 9, "hey!", [], { - ar: [ 1, [ - 3,4,6,9, null, [], [] - ]], - e: fn, - f: undefined - } ] - }, - e: 0.43445 - }, 5, "string", 0, fn, false, null, undefined, 0, [ - 4,5,6,7,8,9,11,22,33,44,55,"66", null, [], [[[[[ 2 ]]]], "3"], {}, 1 / 0 // different: [[[[[2]]]]] instead of [[[[[3]]]]] - ], [], [[[], "foo", null, { - n: 1 / 0, - z: { - a: [ 3, 4, 5, 6, "yep!", undefined, undefined ], - b: {} - } - }, {}]]] ), false ); - - assert.equal( QUnit.equiv( a, - [ { - b: fn, - c: false, - "do": "reserved word", - "for": { - ar: [ 3, 5, 9, "hey!", [], { - ar: [ 1, [ - 3,4,6,9, null, [], [] - ]], - e: fn, - f: undefined - } ] - }, - e: 0.43445 - }, 5, "string", 0, fn, false, null, undefined, 0, [ - 4,5,6,7,8,9,11,22,33,44,55,"66", null, [], [[[[[ 3 ]]]], "3" ], {}, 1 / 0 - ], [], [[[], "foo", null, { - n: -1 / 0, // different, -Infinity instead of Infinity - z: { - a: [ 3, 4, 5, 6, "yep!", undefined, undefined ], - b: {} - } - }, {}]]] ), false ); - - assert.equal( QUnit.equiv( a, - [ { - b: fn, - c: false, - "do": "reserved word", - "for": { - ar: [ 3, 5, 9, "hey!", [], { - ar: [ 1, [ - 3,4,6,9, null, [], [] - ]], - e: fn, - f: undefined - } ] - }, - e: 0.43445 - }, 5, "string", 0, fn, false, null, undefined, 0, [ - 4,5,6,7,8,9,11,22,33,44,55,"66", null, [], [[[[[ 3 ]]]], "3" ], {}, 1 / 0 - ], [], [[[], "foo", { // different: null is missing - n: 1 / 0, - z: { - a: [ 3, 4, 5, 6, "yep!", undefined, undefined ], - b: {} - } - }, {}]]] ), false ); - - assert.equal( QUnit.equiv( a, - [ { - b: fn, - c: false, - "do": "reserved word", - "for": { - ar: [ 3, 5, 9, "hey!", [], { - ar: [ 1, [ - 3,4,6,9, null, [], [] - ]], - e: fn - - // different: missing property f: undefined - } ] - }, - e: 0.43445 - }, 5, "string", 0, fn, false, null, undefined, 0, [ - 4,5,6,7,8,9,11,22,33,44,55,"66", null, [], [[[[[ 3 ]]]], "3" ], {}, 1 / 0 - ], [], [[[], "foo", null, { - n: 1 / 0, - z: { - a: [ 3, 4, 5, 6, "yep!", undefined, undefined ], - b: {} - } - }, {}]]] ), false ); -} ); - -QUnit.test( "Prototypal inheritance", function( assert ) { - function Gizmo( id ) { - this.id = id; - } - - function Hoozit( id ) { - this.id = id; - } - Hoozit.prototype = new Gizmo(); - - var gizmo = new Gizmo( "ok" ), - hoozit = new Hoozit( "ok" ); - - // Try this test many times after test on instances that hold function - // to make sure that our code does not mess with last object constructor memoization. - assert.equal( QUnit.equiv( function() {}, function() {} ), false ); - - // Hoozit inherit from Gizmo - // hoozit instanceof Hoozit; // true - // hoozit instanceof Gizmo; // true - assert.equal( QUnit.equiv( hoozit, gizmo ), true ); - - Gizmo.prototype.bar = true; // not a function just in case we skip them - - // Hoozit inherit from Gizmo - // They are equivalent - assert.equal( QUnit.equiv( hoozit, gizmo ), true ); - - // Make sure this is still true !important - // The reason for this is that I forgot to reset the last - // caller to where it were called from. - assert.equal( QUnit.equiv( function() {}, function() {} ), false ); - - // Make sure this is still true !important - assert.equal( QUnit.equiv( hoozit, gizmo ), true ); - - Hoozit.prototype.foo = true; // not a function just in case we skip them - - // Gizmo does not inherit from Hoozit - // gizmo instanceof Gizmo; // true - // gizmo instanceof Hoozit; // false - // They are not equivalent - assert.equal( QUnit.equiv( hoozit, gizmo ), false ); - - // Make sure this is still true !important - assert.equal( QUnit.equiv( function() {}, function() {} ), false ); -} ); - -QUnit.test( "Instances", function( assert ) { - var a1, a2, b1, b2, c1, c2, c3, car, carSame, carDiff, human; - - function A() {} - a1 = new A(); - a2 = new A(); - - function B() { - this.fn = function() {}; - } - b1 = new B(); - b2 = new B(); - - function C( fn ) { - this.fn = fn; - } - - c1 = new C( function() { return true; } ); - c2 = new C( function() { return false; } ); - c3 = new C( function() { return true; } ); - - assert.equal( QUnit.equiv( a1, a2 ), true, "Same property, same constructor" ); - - // b1.fn and b2.fn are functions but they are different references - // But we decided to skip function for instances. - assert.equal( QUnit.equiv( b1, b2 ), true, "Same property, same constructor" ); - - assert.equal( QUnit.equiv( c1, c2 ), false, "Same property with different reference and different function" ); - assert.equal( QUnit.equiv( c1, c3 ), true, "Same property with different reference but same function" ); - - // failed - assert.equal( QUnit.equiv( a1, b1 ), false, "Same properties but different constructor" ); - - function Car( year ) { - var privateVar = 0; - this.year = year; - this.isOld = function() { - return privateVar > 10; - }; - } - - function Human( year ) { - var privateVar = 1; - this.year = year; - this.isOld = function() { - return privateVar > 80; - }; - } - - car = new Car( 30 ); - carSame = new Car( 30 ); - carDiff = new Car( 10 ); - human = new Human( 30 ); - - /** - * difference: - * - year: 30 - * same: - * - year: 30, - * - isOld: function () {} - */ - - assert.equal( QUnit.equiv( car, car ), true ); - assert.equal( QUnit.equiv( car, carDiff ), false ); - assert.equal( QUnit.equiv( car, carSame ), true ); - assert.equal( QUnit.equiv( car, human ), false ); -} ); +QUnit.module('equiv'); + +QUnit.test('Primitive types and constants', function (assert) { + assert.equal(QUnit.equiv(null, null), true, 'null'); + assert.equal(QUnit.equiv(null, {}), false, 'null'); + assert.equal(QUnit.equiv(null, undefined), false, 'null'); + assert.equal(QUnit.equiv(null, 0), false, 'null'); + assert.equal(QUnit.equiv(null, false), false, 'null'); + assert.equal(QUnit.equiv(null, ''), false, 'null'); + assert.equal(QUnit.equiv(null, []), false, 'null'); + + assert.equal(QUnit.equiv(undefined, undefined), true, 'undefined'); + assert.equal(QUnit.equiv(undefined, null), false, 'undefined'); + assert.equal(QUnit.equiv(undefined, 0), false, 'undefined'); + assert.equal(QUnit.equiv(undefined, false), false, 'undefined'); + assert.equal(QUnit.equiv(undefined, {}), false, 'undefined'); + assert.equal(QUnit.equiv(undefined, []), false, 'undefined'); + assert.equal(QUnit.equiv(undefined, ''), false, 'undefined'); + + // Nan usually doest not equal to Nan using the '==' operator. + // Only isNaN() is able to do it. + assert.equal(QUnit.equiv(0 / 0, 0 / 0), true, 'NaN'); // NaN VS NaN + assert.equal(QUnit.equiv(1 / 0, 2 / 0), true, 'Infinity'); // Infinity VS Infinity + assert.equal(QUnit.equiv(-1 / 0, 2 / 0), false, '-Infinity, Infinity'); // -Infinity VS Infinity + assert.equal(QUnit.equiv(-1 / 0, -2 / 0), true, '-Infinity, -Infinity'); // -Infinity VS -Infinity + assert.equal(QUnit.equiv(0 / 0, 1 / 0), false, 'NaN, Infinity'); // Nan VS Infinity + assert.equal(QUnit.equiv(1 / 0, 0 / 0), false, 'NaN, Infinity'); // Nan VS Infinity + assert.equal(QUnit.equiv(0 / 0, null), false, 'NaN'); + assert.equal(QUnit.equiv(0 / 0, undefined), false, 'NaN'); + assert.equal(QUnit.equiv(0 / 0, 0), false, 'NaN'); + assert.equal(QUnit.equiv(0 / 0, false), false, 'NaN'); + assert.equal(QUnit.equiv(0 / 0, function () {}), false, 'NaN'); + assert.equal(QUnit.equiv(1 / 0, null), false, 'NaN, Infinity'); + assert.equal(QUnit.equiv(1 / 0, undefined), false, 'NaN, Infinity'); + assert.equal(QUnit.equiv(1 / 0, 0), false, 'NaN, Infinity'); + assert.equal(QUnit.equiv(1 / 0, 1), false, 'NaN, Infinity'); + assert.equal(QUnit.equiv(1 / 0, false), false, 'NaN, Infinity'); + assert.equal(QUnit.equiv(1 / 0, true), false, 'NaN, Infinity'); + assert.equal(QUnit.equiv(1 / 0, function () {}), false, 'NaN, Infinity'); + + assert.equal(QUnit.equiv(0, 0), true, 'number'); + assert.equal(QUnit.equiv(0, 1), false, 'number'); + assert.equal(QUnit.equiv(1, 0), false, 'number'); + assert.equal(QUnit.equiv(1, 1), true, 'number'); + assert.equal(QUnit.equiv(1.1, 1.1), true, 'number'); + assert.equal(QUnit.equiv(0.0000005, 0.0000005), true, 'number'); + assert.equal(QUnit.equiv(0, ''), false, 'number'); + assert.equal(QUnit.equiv(0, '0'), false, 'number'); + assert.equal(QUnit.equiv(1, '1'), false, 'number'); + assert.equal(QUnit.equiv(0, false), false, 'number'); + assert.equal(QUnit.equiv(1, true), false, 'number'); + + assert.equal(QUnit.equiv(true, true), true, 'boolean'); + assert.equal(QUnit.equiv(true, false), false, 'boolean'); + assert.equal(QUnit.equiv(false, true), false, 'boolean'); + assert.equal(QUnit.equiv(false, 0), false, 'boolean'); + assert.equal(QUnit.equiv(false, null), false, 'boolean'); + assert.equal(QUnit.equiv(false, undefined), false, 'boolean'); + assert.equal(QUnit.equiv(true, 1), false, 'boolean'); + assert.equal(QUnit.equiv(true, null), false, 'boolean'); + assert.equal(QUnit.equiv(true, undefined), false, 'boolean'); + + assert.equal(QUnit.equiv('', ''), true, 'string'); + assert.equal(QUnit.equiv('a', 'a'), true, 'string'); + assert.equal(QUnit.equiv('foobar', 'foobar'), true, 'string'); + assert.equal(QUnit.equiv('foobar', 'foo'), false, 'string'); + assert.equal(QUnit.equiv('', 0), false, 'string'); + assert.equal(QUnit.equiv('', false), false, 'string'); + assert.equal(QUnit.equiv('', null), false, 'string'); + assert.equal(QUnit.equiv('', undefined), false, 'string'); + + // Rename for lint validation. + // We know this is bad, we are asserting whether we can coop with bad code like this. + var SafeNumber = Number; + var SafeString = String; + var SafeBoolean = Boolean; + var SafeObject = Object; + + // primitives vs. objects + + assert.equal(QUnit.equiv(0, new SafeNumber()), true, 'number 0 primitive vs. object'); + assert.equal(QUnit.equiv(new SafeNumber(), 0), true, 'number 0 object vs. primitive'); + assert.equal(QUnit.equiv(new SafeNumber(), new SafeNumber()), true, 'empty number objects'); + assert.equal(QUnit.equiv(1, new SafeNumber(1)), true, 'number 1 primitive vs. object'); + assert.equal(QUnit.equiv(new SafeNumber(1), 1), true, 'number 1 object vs. primitive'); + assert.equal(QUnit.equiv(new SafeNumber(1), new SafeNumber(1)), true, 'number 1 objects'); + assert.equal(QUnit.equiv(0, new SafeNumber(1)), false, 'differing number primitive vs. object'); + assert.equal(QUnit.equiv(new SafeNumber(0), 1), false, 'differing number object vs. primitive'); + + assert.equal(QUnit.equiv('', new SafeString()), true, 'empty string primitive vs. object'); + assert.equal(QUnit.equiv(new SafeString(), ''), true, 'empty string object vs. primitive'); + assert.equal(QUnit.equiv(new SafeString(), new SafeString()), true, 'empty string objects'); + assert.equal(QUnit.equiv('My String', new SafeString('My String')), true, 'nonempty string primitive vs. object'); + assert.equal(QUnit.equiv(new SafeString('My String'), 'My String'), true, 'nonempty string object vs. primitive'); + assert.equal(QUnit.equiv(new SafeString('My String'), new SafeString('My String')), true, 'nonempty string objects'); + assert.equal(QUnit.equiv('Bad String', new SafeString('My String')), false, 'differing string primitive vs. object'); + assert.equal(QUnit.equiv(new SafeString('Bad String'), 'My String'), false, 'differing string object vs. primitive'); + + assert.equal(QUnit.equiv(false, new SafeBoolean()), true, 'boolean false primitive vs. object'); + assert.equal(QUnit.equiv(new SafeBoolean(), false), true, 'boolean empty object vs. primitive'); + assert.equal(QUnit.equiv(new SafeBoolean(), new SafeBoolean()), true, 'empty boolean objects'); + assert.equal(QUnit.equiv(true, new SafeBoolean(true)), true, 'boolean true primitive vs. object'); + assert.equal(QUnit.equiv(new SafeBoolean(true), true), true, 'boolean true object vs. primitive'); + assert.equal(QUnit.equiv(new SafeBoolean(true), new SafeBoolean(true)), true, 'boolean true objects'); + assert.equal(QUnit.equiv(true, new SafeBoolean(1)), true, 'boolean true primitive vs. truthy object'); + assert.equal(QUnit.equiv(false, new SafeBoolean(false)), true, 'boolean false primitive vs. false object'); + assert.equal(QUnit.equiv(new SafeBoolean(false), false), true, 'boolean false object vs. primitive'); + assert.equal(QUnit.equiv(new SafeBoolean(false), new SafeBoolean(false)), true, 'boolean false objects'); + assert.equal(QUnit.equiv(false, new SafeBoolean(0)), true, 'boolean false primitive vs. 0 object'); + assert.equal(QUnit.equiv(true, new SafeBoolean(false)), false, 'differing boolean primitive vs. object'); + assert.equal(QUnit.equiv(new SafeBoolean(false), true), false, 'differing boolean object vs. primitive'); + + assert.equal(QUnit.equiv(new SafeObject(), {}), true, 'empty object instantiation vs. literal'); + assert.equal(QUnit.equiv({}, new SafeObject()), true, 'empty object literal vs. instantiation'); + assert.equal(QUnit.equiv(new SafeObject(), { a: 1 }), false, 'empty object instantiation vs. nonempty literal'); + assert.equal(QUnit.equiv({ a: 1 }, new SafeObject()), false, 'nonempty object literal vs. empty instantiation'); + assert.equal(QUnit.equiv({ a: undefined }, new SafeObject()), false, 'other nonempty object literal vs. empty instantiation'); + assert.equal(QUnit.equiv(new SafeObject(), { a: undefined }), false, 'empty object instantiation vs. other nonempty literal'); +}); + +QUnit.test('Consecutive argument pairs', function (assert) { + // degenerate cases with <2 inputs + assert.equal(QUnit.equiv(), true); + assert.equal(QUnit.equiv(1), true); + + // otherwise every "consecutive pair" must be equivalent + assert.equal(QUnit.equiv(1, 1, 1), true); + assert.equal(QUnit.equiv(1, 1, 2), false); + assert.equal(QUnit.equiv(1, 1, 1, 1), true); + assert.equal(QUnit.equiv(1, 1, 1, 2), false); + assert.equal(QUnit.equiv(1, 1, 1, 1, 1), true); +}); + +QUnit.test('Objects basics', function (assert) { + assert.equal(QUnit.equiv({}, {}), true); + assert.equal(QUnit.equiv({}, null), false); + assert.equal(QUnit.equiv({}, undefined), false); + assert.equal(QUnit.equiv({}, 0), false); + assert.equal(QUnit.equiv({}, false), false); + + // This test is a hard one, it is very important + // REASONS: + // 1) They are of the same type "object" + // 2) [] instanceof Object is true + // 3) Their properties are the same (doesn't exists) + assert.equal(QUnit.equiv({}, []), false); + + assert.equal(QUnit.equiv({ a: 1 }, { a: 1 }), true); + assert.equal(QUnit.equiv({ a: 1 }, { a: '1' }), false); + assert.equal(QUnit.equiv({ a: [] }, { a: [] }), true); + assert.equal(QUnit.equiv({ a: {} }, { a: null }), false); + assert.equal(QUnit.equiv({ a: 1 }, {}), false); + assert.equal(QUnit.equiv({}, { a: 1 }), false); + + // Hard ones + assert.equal(QUnit.equiv({ a: undefined }, {}), false); + assert.equal(QUnit.equiv({}, { a: undefined }), false); + assert.equal(QUnit.equiv( + { + a: [{ bar: undefined }] + }, + { + a: [{ bat: undefined }] + } + ), false); +}); + +QUnit.test('Objects with null prototypes', function (assert) { + var nonEmptyWithNoProto; + + // Objects with no prototype, created via Object.create(null), are used + // e.g. as dictionaries. + // Being able to test equivalence against object literals is quite useful. + assert.equal( + QUnit.equiv(Object.create(null), {}), + true, + 'empty object without prototype VS empty object' + ); + + assert.equal( + QUnit.equiv({}, Object.create(null)), + true, + 'empty object VS empty object without prototype' + ); + + nonEmptyWithNoProto = Object.create(null); + nonEmptyWithNoProto.foo = 'bar'; + + assert.equal( + QUnit.equiv(nonEmptyWithNoProto, { foo: 'bar' }), + true, + 'object without prototype VS object' + ); + + assert.equal( + QUnit.equiv({ foo: 'bar' }, nonEmptyWithNoProto), + true, + 'object VS object without prototype' + ); +}); + +QUnit.test('Object prototype constructor is null', function (assert) { + // Ref https://github.com/qunitjs/qunit/issues/851 + // Instances of this custom class have similar characteristics to null-objects. + function NullObject () {} + NullObject.prototype = Object.create(null, { + constructor: { + value: null + } + }); + + var a = new NullObject(); + a.foo = 1; + var b = { foo: 1 }; + + assert.true(QUnit.equiv(a, b)); + assert.true(QUnit.equiv(b, a)); +}); + +QUnit.test('Arrays basics', function (assert) { + assert.equal(QUnit.equiv([], []), true); + + // May be a hard one, can invoke a crash at execution. + // because their types are both "object" but null isn't + // like a true object, it doesn't have any property at all. + assert.equal(QUnit.equiv([], null), false); + + assert.equal(QUnit.equiv([], undefined), false); + assert.equal(QUnit.equiv([], false), false); + assert.equal(QUnit.equiv([], 0), false); + assert.equal(QUnit.equiv([], ''), false); + + // May be a hard one, but less hard + // than {} with [] (note the order) + assert.equal(QUnit.equiv([], {}), false); + + assert.equal(QUnit.equiv([null], []), false); + assert.equal(QUnit.equiv([undefined], []), false); + assert.equal(QUnit.equiv([], [null]), false); + assert.equal(QUnit.equiv([], [undefined]), false); + assert.equal(QUnit.equiv([null], [undefined]), false); + assert.equal(QUnit.equiv([[]], [[]]), true); + assert.equal(QUnit.equiv([[], [], []], [[], [], []]), true); + + assert.equal( + QUnit.equiv( + [[], [], [[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]], + [[], [], [[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]), + true + ); + + assert.equal( + QUnit.equiv( + [[], [], [[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]], + [[], [], [[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]), // shorter + false + ); + + assert.equal( + QUnit.equiv( + [[], [], [[[[[[[[[[[[[[[[[[[[[[[[[[[[[[{}]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]], + [[], [], [[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]), // deepest element not an array + false + ); + + // same multidimensional + assert.equal( + QUnit.equiv( + [1, 2, 3, 4, 5, 6, 7, 8, 9, [ + 1, 2, 3, 4, 5, 6, 7, 8, 9, [ + 1, 2, 3, 4, 5, [ + [6, 7, 8, 9, [ + [ + 1, 2, 3, 4, [ + 2, 3, 4, [ + 1, 2, [ + 1, 2, 3, 4, [ + 1, 2, 3, 4, 5, 6, 7, 8, 9, [ + 0 + ], 1, 2, 3, 4, 5, 6, 7, 8, 9 + ], 5, 6, 7, 8, 9 + ], 4, 5, 6, 7, 8, 9 + ], 5, 6, 7, 8, 9 + ], 5, 6, 7 + ] + ] + ] + ] + ]]], + [1, 2, 3, 4, 5, 6, 7, 8, 9, [ + 1, 2, 3, 4, 5, 6, 7, 8, 9, [ + 1, 2, 3, 4, 5, [ + [6, 7, 8, 9, [ + [ + 1, 2, 3, 4, [ + 2, 3, 4, [ + 1, 2, [ + 1, 2, 3, 4, [ + 1, 2, 3, 4, 5, 6, 7, 8, 9, [ + 0 + ], 1, 2, 3, 4, 5, 6, 7, 8, 9 + ], 5, 6, 7, 8, 9 + ], 4, 5, 6, 7, 8, 9 + ], 5, 6, 7, 8, 9 + ], 5, 6, 7 + ] + ] + ] + ] + ]]] + ), + true, + 'Multidimensional' + ); + + // different multidimensional + assert.equal( + QUnit.equiv( + [1, 2, 3, 4, 5, 6, 7, 8, 9, [ + 1, 2, 3, 4, 5, 6, 7, 8, 9, [ + 1, 2, 3, 4, 5, [ + [6, 7, 8, 9, [ + [ + 1, 2, 3, 4, [ + 2, 3, 4, [ + 1, 2, [ + 1, 2, 3, 4, [ + 1, 2, 3, 4, 5, 6, 7, 8, 9, [ + 0 + ], 1, 2, 3, 4, 5, 6, 7, 8, 9 + ], 5, 6, 7, 8, 9 + ], 4, 5, 6, 7, 8, 9 + ], 5, 6, 7, 8, 9 + ], 5, 6, 7 + ] + ] + ] + ] + ]]], + [1, 2, 3, 4, 5, 6, 7, 8, 9, [ + 1, 2, 3, 4, 5, 6, 7, 8, 9, [ + 1, 2, 3, 4, 5, [ + [6, 7, 8, 9, [ + [ + 1, 2, 3, 4, [ + 2, 3, 4, [ + 1, 2, [ + '1', 2, 3, 4, [ // string instead of number + 1, 2, 3, 4, 5, 6, 7, 8, 9, [ + 0 + ], 1, 2, 3, 4, 5, 6, 7, 8, 9 + ], 5, 6, 7, 8, 9 + ], 4, 5, 6, 7, 8, 9 + ], 5, 6, 7, 8, 9 + ], 5, 6, 7 + ] + ] + ] + ] + ]]] + ), + false, + 'Multidimensional' + ); + + // different multidimensional + assert.equal( + QUnit.equiv( + [1, 2, 3, 4, 5, 6, 7, 8, 9, [ + 1, 2, 3, 4, 5, 6, 7, 8, 9, [ + 1, 2, 3, 4, 5, [ + [6, 7, 8, 9, [ + [ + 1, 2, 3, 4, [ + 2, 3, 4, [ + 1, 2, [ + 1, 2, 3, 4, [ + 1, 2, 3, 4, 5, 6, 7, 8, 9, [ + 0 + ], 1, 2, 3, 4, 5, 6, 7, 8, 9 + ], 5, 6, 7, 8, 9 + ], 4, 5, 6, 7, 8, 9 + ], 5, 6, 7, 8, 9 + ], 5, 6, 7 + ] + ] + ] + ] + ]]], + [1, 2, 3, 4, 5, 6, 7, 8, 9, [ + 1, 2, 3, 4, 5, 6, 7, 8, 9, [ + 1, 2, 3, 4, 5, [ + [6, 7, 8, 9, [ + [ + 1, 2, 3, 4, [ + 2, 3, [ // missing an element (4) + 1, 2, [ + 1, 2, 3, 4, [ + 1, 2, 3, 4, 5, 6, 7, 8, 9, [ + 0 + ], 1, 2, 3, 4, 5, 6, 7, 8, 9 + ], 5, 6, 7, 8, 9 + ], 4, 5, 6, 7, 8, 9 + ], 5, 6, 7, 8, 9 + ], 5, 6, 7 + ] + ] + ] + ] + ]]] + ), + false, + 'Multidimensional' + ); +}); + +QUnit.test('Functions', function (assert) { + var f0 = function () {}; + var f1 = function () {}; + + // f2 and f3 have the same code, formatted differently + var f2 = function () { return 0; }; + var f3 = function () { + // eslint-disable-next-line semi + return 0 // this comment and no semicoma as difference + }; + + assert.equal(QUnit.equiv(function () {}, function () {}), false, 'Anonymous functions'); // exact source code + assert.equal(QUnit.equiv(function () {}, function () { + return true; + }), false, 'Anonymous functions'); + + assert.equal(QUnit.equiv(f0, f0), true, 'Function references'); // same references + assert.equal(QUnit.equiv(f0, f1), false, 'Function references'); // exact source code, different references + assert.equal(QUnit.equiv(f2, f3), false, 'Function references'); // equivalent source code, different references + assert.equal(QUnit.equiv(f1, f2), false, 'Function references'); // different source code, different references + assert.equal(QUnit.equiv(function () {}, true), false); + assert.equal(QUnit.equiv(function () {}, undefined), false); + assert.equal(QUnit.equiv(function () {}, null), false); + assert.equal(QUnit.equiv(function () {}, {}), false); +}); + +QUnit.test('Date instances', function (assert) { + // Date, we don't need to test Date.parse() because it returns a number. + // Only test the Date instances by setting them a fix date. + // The date use is midnight January 1, 1970 + var d1, d2, d3; + + d1 = new Date(); + d1.setTime(0); // fix the date + + d2 = new Date(); + d2.setTime(0); // fix the date + + d3 = new Date(); // The very now + + // Anyway their types differs, just in case the code fails in the order in which it deals with date + assert.equal(QUnit.equiv(d1, 0), false); // d1.valueOf() returns 0, but d1 and 0 are different + + // test same values date and different instances equality + assert.equal(QUnit.equiv(d1, d2), true); + + // test different date and different instances difference + assert.equal(QUnit.equiv(d1, d3), false); +}); + +QUnit.test('RegExp', function (assert) { + // Must test cases that imply those traps: + // var a = /./; + // a instanceof Object; // Oops + // a instanceof RegExp; // Oops + // typeof a === "function"; // Oops, false in IE and Opera, true in FF and Safari ("object") + + // Tests same regex with same modifiers in different order + var regex1; var regex2; var regex3; var r3a; var r3b; var ru1; var ru2; + var r1 = /foo/; + var r2 = /foo/gim; + var r3 = /foo/gmi; + var r4 = /foo/igm; + var r5 = /foo/img; + var r6 = /foo/mig; + var r7 = /foo/mgi; + var ri1 = /foo/i; + var ri2 = /foo/i; + var rm1 = /foo/m; + var rm2 = /foo/m; + var rg1 = /foo/g; + var rg2 = /foo/g; + + assert.equal(QUnit.equiv(r2, r3), true, 'Modifier order'); + assert.equal(QUnit.equiv(r2, r4), true, 'Modifier order'); + assert.equal(QUnit.equiv(r2, r5), true, 'Modifier order'); + assert.equal(QUnit.equiv(r2, r6), true, 'Modifier order'); + assert.equal(QUnit.equiv(r2, r7), true, 'Modifier order'); + assert.equal(QUnit.equiv(r1, r2), false, 'Modifier'); + + assert.equal(QUnit.equiv(ri1, ri2), true, 'Modifier'); + assert.equal(QUnit.equiv(r1, ri1), false, 'Modifier'); + assert.equal(QUnit.equiv(ri1, rm1), false, 'Modifier'); + assert.equal(QUnit.equiv(r1, rm1), false, 'Modifier'); + assert.equal(QUnit.equiv(rm1, ri1), false, 'Modifier'); + assert.equal(QUnit.equiv(rm1, rm2), true, 'Modifier'); + assert.equal(QUnit.equiv(rg1, rm1), false, 'Modifier'); + assert.equal(QUnit.equiv(rm1, rg1), false, 'Modifier'); + assert.equal(QUnit.equiv(rg1, rg2), true, 'Modifier'); + + // Compare unicode modifier + // Support IE 11: Use RegExp constructor to avoid syntax error + try { + r2 = new RegExp('foo', 'umig'); + r3 = new RegExp('foo', 'mgiu'); + assert.equal(QUnit.equiv(r2, r3), true, 'Modifier order'); + assert.equal(QUnit.equiv(r1, r2), false, 'Modifier'); + + ru1 = new RegExp('\\u{1D306}', 'u'); + ru2 = new RegExp('\\u{1D306}', 'u'); + assert.equal(QUnit.equiv(ru1, rg1), false, 'Modifier'); + assert.equal(QUnit.equiv(rg1, ru1), false, 'Modifier'); + assert.equal(QUnit.equiv(ru1, ru2), true, 'Modifier'); + } catch (e) {} + + // Different regex, same modifiers + r1 = /[a-z]/gi; + r2 = /[0-9]/gi; // oops! different + assert.equal(QUnit.equiv(r1, r2), false, 'Regex pattern'); + + r1 = /0/ig; + r2 = /"0"/ig; // oops! different + assert.equal(QUnit.equiv(r1, r2), false, 'Regex pattern'); + + r1 = /[\n\r\u2028\u2029]/g; + r2 = /[\n\r\u2028\u2029]/g; + r3 = /[\n\r\u2028\u2028]/g; // differs from r1 + r4 = /[\n\r\u2028\u2029]/; // differs from r1 + + assert.equal(QUnit.equiv(r1, r2), true, 'Regex pattern'); + assert.equal(QUnit.equiv(r1, r3), false, 'Regex pattern'); + assert.equal(QUnit.equiv(r1, r4), false, 'Regex pattern'); + + // More complex regex + regex1 = '^[-_.a-z0-9]+@([-_a-z0-9]+\\.)+([A-Za-z][A-Za-z]|[A-Za-z][A-Za-z][A-Za-z])|(([0-9][0-9]?|[0-1][0-9][0-9]|[2][0-4][0-9]|[2][5][0-5]))$'; + regex2 = '^[-_.a-z0-9]+@([-_a-z0-9]+\\.)+([A-Za-z][A-Za-z]|[A-Za-z][A-Za-z][A-Za-z])|(([0-9][0-9]?|[0-1][0-9][0-9]|[2][0-4][0-9]|[2][5][0-5]))$'; + + // regex 3 is different: '.' not escaped + regex3 = '^[-_.a-z0-9]+@([-_a-z0-9]+.)+([A-Za-z][A-Za-z]|[A-Za-z][A-Za-z][A-Za-z])|(([0-9][0-9]?|[0-1][0-9][0-9]|[2][0-4][0-9]|[2][5][0-5]))$'; + + r1 = new RegExp(regex1); + r2 = new RegExp(regex2); + r3 = new RegExp(regex3); // diff from r21, not same pattern + r3a = new RegExp(regex3, 'gi'); // diff from r23, not same modifier + r3b = new RegExp(regex3, 'ig'); // same as r23a + + assert.equal(QUnit.equiv(r1, r2), true, 'Complex Regex'); + assert.equal(QUnit.equiv(r1, r3), false, 'Complex Regex'); + assert.equal(QUnit.equiv(r3, r3a), false, 'Complex Regex'); + assert.equal(QUnit.equiv(r3a, r3b), true, 'Complex Regex'); + + // typeof r1 is "function" in some browsers and "object" in others so we must cover this test + r1 = / /; + assert.equal(QUnit.equiv(r1, function () {}), false, 'Regex internal'); + assert.equal(QUnit.equiv(r1, {}), false, 'Regex internal'); +}); + +QUnit.test('Complex objects', function (assert) { + function fn1 () { + return 'fn1'; + } + function fn2 () { + return 'fn2'; + } + + // Try to invert the order of some properties to make sure it is covered. + // It can failed when properties are compared between unsorted arrays. + assert.equal(QUnit.equiv( + { + a: 1, + b: null, + c: [{}], + d: { + a: 3.14159, + b: false, + c: { + e: fn1, + f: [[[]]], + g: { + j: { + k: { + n: { + r: 'r', + s: [1, 2, 3], + t: undefined, + u: 0, + v: { + w: { + x: { + y: 'Yahoo!', + z: null + } + } + } + }, + q: [], + p: 1 / 0, + o: 99 + }, + l: undefined, + m: null + } + }, + d: 0, + i: true, + h: 'false' + } + }, + e: undefined, + g: '', + h: 'h', + f: {}, + i: [] + }, + { + a: 1, + b: null, + c: [{}], + d: { + b: false, + a: 3.14159, + c: { + d: 0, + e: fn1, + f: [[[]]], + g: { + j: { + k: { + n: { + r: 'r', + t: undefined, + u: 0, + s: [1, 2, 3], + v: { + w: { + x: { + z: null, + y: 'Yahoo!' + } + } + } + }, + o: 99, + p: 1 / 0, + q: [] + }, + l: undefined, + m: null + } + }, + i: true, + h: 'false' + } + }, + e: undefined, + g: '', + f: {}, + h: 'h', + i: [] + } + ), true); + + assert.equal(QUnit.equiv( + { + a: 1, + b: null, + c: [{}], + d: { + a: 3.14159, + b: false, + c: { + d: 0, + e: fn1, + f: [[[]]], + g: { + j: { + k: { + n: { + + // r: "r", // different: missing a property + s: [1, 2, 3], + t: undefined, + u: 0, + v: { + w: { + x: { + y: 'Yahoo!', + z: null + } + } + } + }, + o: 99, + p: 1 / 0, + q: [] + }, + l: undefined, + m: null + } + }, + h: 'false', + i: true + } + }, + e: undefined, + f: {}, + g: '', + h: 'h', + i: [] + }, + { + a: 1, + b: null, + c: [{}], + d: { + a: 3.14159, + b: false, + c: { + d: 0, + e: fn1, + f: [[[]]], + g: { + j: { + k: { + n: { + r: 'r', + s: [1, 2, 3], + t: undefined, + u: 0, + v: { + w: { + x: { + y: 'Yahoo!', + z: null + } + } + } + }, + o: 99, + p: 1 / 0, + q: [] + }, + l: undefined, + m: null + } + }, + h: 'false', + i: true + } + }, + e: undefined, + f: {}, + g: '', + h: 'h', + i: [] + } + ), false); + + assert.equal(QUnit.equiv( + { + a: 1, + b: null, + c: [{}], + d: { + a: 3.14159, + b: false, + c: { + d: 0, + e: fn1, + f: [[[]]], + g: { + j: { + k: { + n: { + r: 'r', + s: [1, 2, 3], + t: undefined, + u: 0, + v: { + w: { + x: { + y: 'Yahoo!', + z: null + } + } + } + }, + o: 99, + p: 1 / 0, + q: [] + }, + l: undefined, + m: null + } + }, + h: 'false', + i: true + } + }, + e: undefined, + f: {}, + g: '', + h: 'h', + i: [] + }, + { + a: 1, + b: null, + c: [{}], + d: { + a: 3.14159, + b: false, + c: { + d: 0, + e: fn1, + f: [[[]]], + g: { + j: { + k: { + n: { + r: 'r', + s: [1, 2, 3], + // t: undefined, // different: missing a property with an undefined value + u: 0, + v: { + w: { + x: { + y: 'Yahoo!', + z: null + } + } + } + }, + o: 99, + p: 1 / 0, + q: [] + }, + l: undefined, + m: null + } + }, + h: 'false', + i: true + } + }, + e: undefined, + f: {}, + g: '', + h: 'h', + i: [] + } + ), false); + + assert.equal(QUnit.equiv( + { + a: 1, + b: null, + c: [{}], + d: { + a: 3.14159, + b: false, + c: { + d: 0, + e: fn1, + f: [[[]]], + g: { + j: { + k: { + n: { + r: 'r', + s: [1, 2, 3], + t: undefined, + u: 0, + v: { + w: { + x: { + y: 'Yahoo!', + z: null + } + } + } + }, + o: 99, + p: 1 / 0, + q: [] + }, + l: undefined, + m: null + } + }, + h: 'false', + i: true + } + }, + e: undefined, + f: {}, + g: '', + h: 'h', + i: [] + }, + { + a: 1, + b: null, + c: [{}], + d: { + a: 3.14159, + b: false, + c: { + d: 0, + e: fn1, + f: [[[]]], + g: { + j: { + k: { + n: { + r: 'r', + s: [1, 2, 3], + t: undefined, + u: 0, + v: { + w: { + x: { + y: 'Yahoo!', + z: null + } + } + } + }, + o: 99, + p: 1 / 0, + q: {} // different was [] + }, + l: undefined, + m: null + } + }, + h: 'false', + i: true + } + }, + e: undefined, + f: {}, + g: '', + h: 'h', + i: [] + } + ), false); + + var same1 = { + a: [ + 'string', null, 0, '1', 1, { + prop: null, + foo: [1, 2, null, {}, [], [1, 2, 3]], + bar: undefined + }, 3, 'Hey!', 'Κάνε πάντα γνωÏίζουμε ας των, μηχανής επιδιόÏθωσης επιδιοÏθώσεις ÏŽÏ‚ μια. Κλπ ας' + ], + unicode: 'è€ æ±‰è¯ä¸å˜åœ¨ 港澳和海外的åŽäººåœˆä¸ 贵州 我去了书店 现在尚有争', + b: 'b', + c: fn1 + }; + + var same2 = { + a: [ + 'string', null, 0, '1', 1, { + prop: null, + foo: [1, 2, null, {}, [], [1, 2, 3]], + bar: undefined + }, 3, 'Hey!', 'Κάνε πάντα γνωÏίζουμε ας των, μηχανής επιδιόÏθωσης επιδιοÏθώσεις ÏŽÏ‚ μια. Κλπ ας' + ], + unicode: 'è€ æ±‰è¯ä¸å˜åœ¨ 港澳和海外的åŽäººåœˆä¸ 贵州 我去了书店 现在尚有争', + b: 'b', + c: fn1 + }; + + var diff1 = { + a: [ + 'string', null, 0, '1', 1, { + prop: null, + foo: [1, 2, null, {}, [], [1, 2, 3, 4]], // different: 4 was add to the array + bar: undefined + }, 3, 'Hey!', 'Κάνε πάντα γνωÏίζουμε ας των, μηχανής επιδιόÏθωσης επιδιοÏθώσεις ÏŽÏ‚ μια. Κλπ ας' + ], + unicode: 'è€ æ±‰è¯ä¸å˜åœ¨ 港澳和海外的åŽäººåœˆä¸ 贵州 我去了书店 现在尚有争', + b: 'b', + c: fn1 + }; + + var diff2 = { + a: [ + 'string', null, 0, '1', 1, { + prop: null, + foo: [1, 2, null, {}, [], [1, 2, 3]], + newprop: undefined, // different: newprop was added + bar: undefined + }, 3, 'Hey!', 'Κάνε πάντα γνωÏίζουμε ας των, μηχανής επιδιόÏθωσης επιδιοÏθώσεις ÏŽÏ‚ μια. Κλπ ας' + ], + unicode: 'è€ æ±‰è¯ä¸å˜åœ¨ 港澳和海外的åŽäººåœˆä¸ 贵州 我去了书店 现在尚有争', + b: 'b', + c: fn1 + }; + + var diff3 = { + a: [ + 'string', null, 0, '1', 1, { + prop: null, + foo: [1, 2, null, {}, [], [1, 2, 3]], + bar: undefined + }, 3, 'Hey!', 'Κάνε πάντα γνωÏίζουμε ας των, μηχανής επιδιόÏθωσης επιδιοÏθώσεις ÏŽÏ‚ μια. Κλπ α' // different: missing last char + ], + unicode: 'è€ æ±‰è¯ä¸å˜åœ¨ 港澳和海外的åŽäººåœˆä¸ 贵州 我去了书店 现在尚有争', + b: 'b', + c: fn1 + }; + + var diff4 = { + a: [ + 'string', null, 0, '1', 1, { + prop: null, + foo: [1, 2, undefined, {}, [], [1, 2, 3]], // different: undefined instead of null + bar: undefined + }, 3, 'Hey!', 'Κάνε πάντα γνωÏίζουμε ας των, μηχανής επιδιόÏθωσης επιδιοÏθώσεις ÏŽÏ‚ μια. Κλπ ας' + ], + unicode: 'è€ æ±‰è¯ä¸å˜åœ¨ 港澳和海外的åŽäººåœˆä¸ 贵州 我去了书店 现在尚有争', + b: 'b', + c: fn1 + }; + + var diff5 = { + a: [ + 'string', null, 0, '1', 1, { + prop: null, + foo: [1, 2, null, {}, [], [1, 2, 3]], + bat: undefined // different: property name not "bar" + }, 3, 'Hey!', 'Κάνε πάντα γνωÏίζουμε ας των, μηχανής επιδιόÏθωσης επιδιοÏθώσεις ÏŽÏ‚ μια. Κλπ ας' + ], + unicode: 'è€ æ±‰è¯ä¸å˜åœ¨ 港澳和海外的åŽäººåœˆä¸ 贵州 我去了书店 现在尚有争', + b: 'b', + c: fn1 + }; + + var diff6 = { + a: [ + 'string', null, 0, '1', 1, { + prop: null, + foo: [1, 2, null, {}, [], [1, 2, 3]], + bar: undefined + }, 3, 'Hey!', 'Κάνε πάντα γνωÏίζουμε ας των, μηχανής επιδιόÏθωσης επιδιοÏθώσεις ÏŽÏ‚ μια. Κλπ ας' + ], + unicode: 'è€ æ±‰è¯ä¸å˜åœ¨ 港澳和海外的åŽäººåœˆä¸ 贵州 我去了书店 现在尚有争', + b: 'b', + c: fn2 // different: fn2 instead of fn1 + }; + + assert.equal(QUnit.equiv(same1, same2), true); + assert.equal(QUnit.equiv(same2, same1), true); + assert.equal(QUnit.equiv(same2, diff1), false); + assert.equal(QUnit.equiv(diff1, same2), false); + + assert.equal(QUnit.equiv(same1, diff1), false); + assert.equal(QUnit.equiv(same1, diff2), false); + assert.equal(QUnit.equiv(same1, diff3), false); + assert.equal(QUnit.equiv(same1, diff3), false); + assert.equal(QUnit.equiv(same1, diff4), false); + assert.equal(QUnit.equiv(same1, diff5), false); + assert.equal(QUnit.equiv(same1, diff6), false); + assert.equal(QUnit.equiv(diff5, diff1), false); +}); + +QUnit.test('Complex Arrays', function (assert) { + function fn () {} + + assert.equal( + QUnit.equiv( + [1, 2, 3, true, {}, null, [ + { + a: ['', '1', 0] + }, + 5, 6, 7 + ], 'foo'], + [1, 2, 3, true, {}, null, [ + { + a: ['', '1', 0] + }, + 5, 6, 7 + ], 'foo'] + ), + true + ); + + assert.equal( + QUnit.equiv( + [1, 2, 3, true, {}, null, [ + { + a: ['', '1', 0] + }, + 5, 6, 7 + ], 'foo'], + [1, 2, 3, true, {}, null, [ + { + b: ['', '1', 0] // not same property name + }, + 5, 6, 7 + ], 'foo'] + ), + false + ); + + var a = [{ + b: fn, + c: false, + do: 'reserved word', + for: { + ar: [3, 5, 9, 'hey!', [], { + ar: [1, [ + 3, 4, 6, 9, null, [], [] + ]], + e: fn, + f: undefined + }] + }, + e: 0.43445 + }, 5, 'string', 0, fn, false, null, undefined, 0, [ + 4, 5, 6, 7, 8, 9, 11, 22, 33, 44, 55, '66', null, [], [[[[[3]]]], '3'], {}, 1 / 0 + ], [], [[[], 'foo', null, { + n: 1 / 0, + z: { + a: [3, 4, 5, 6, 'yep!', undefined, undefined], + b: {} + } + }, {}]]]; + + assert.equal(QUnit.equiv(a, + [{ + b: fn, + c: false, + do: 'reserved word', + for: { + ar: [3, 5, 9, 'hey!', [], { + ar: [1, [ + 3, 4, 6, 9, null, [], [] + ]], + e: fn, + f: undefined + }] + }, + e: 0.43445 + }, 5, 'string', 0, fn, false, null, undefined, 0, [ + 4, 5, 6, 7, 8, 9, 11, 22, 33, 44, 55, '66', null, [], [[[[[3]]]], '3'], {}, 1 / 0 + ], [], [[[], 'foo', null, { + n: 1 / 0, + z: { + a: [3, 4, 5, 6, 'yep!', undefined, undefined], + b: {} + } + }, {}]]]), true); + + assert.equal(QUnit.equiv(a, + [{ + b: fn, + c: false, + do: 'reserved word', + for: { + ar: [3, 5, 9, 'hey!', [], { + ar: [1, [ + 3, 4, 6, 9, null, [], [] + ]], + e: fn, + f: undefined + }] + }, + e: 0.43445 + }, 5, 'string', 0, fn, false, null, undefined, 0, [ + 4, 5, 6, 7, 8, 9, 11, 22, 33, 44, 55, '66', null, [], [[[[[2]]]], '3'], {}, 1 / 0 + // different: [[[[[2]]]]] instead of [[[[[3]]]]] + ], [], [[[], 'foo', null, { + n: 1 / 0, + z: { + a: [3, 4, 5, 6, 'yep!', undefined, undefined], + b: {} + } + }, {}]]]), false); + + assert.equal(QUnit.equiv(a, + [{ + b: fn, + c: false, + do: 'reserved word', + for: { + ar: [3, 5, 9, 'hey!', [], { + ar: [1, [ + 3, 4, 6, 9, null, [], [] + ]], + e: fn, + f: undefined + }] + }, + e: 0.43445 + }, 5, 'string', 0, fn, false, null, undefined, 0, [ + 4, 5, 6, 7, 8, 9, 11, 22, 33, 44, 55, '66', null, [], [[[[[3]]]], '3'], {}, 1 / 0 + ], [], [[[], 'foo', null, { + n: -1 / 0, // different, -Infinity instead of Infinity + z: { + a: [3, 4, 5, 6, 'yep!', undefined, undefined], + b: {} + } + }, {}]]]), false); + + assert.equal(QUnit.equiv(a, + [{ + b: fn, + c: false, + do: 'reserved word', + for: { + ar: [3, 5, 9, 'hey!', [], { + ar: [1, [ + 3, 4, 6, 9, null, [], [] + ]], + e: fn, + f: undefined + }] + }, + e: 0.43445 + }, 5, 'string', 0, fn, false, null, undefined, 0, [ + 4, 5, 6, 7, 8, 9, 11, 22, 33, 44, 55, '66', null, [], [[[[[3]]]], '3'], {}, 1 / 0 + ], [], [[[], 'foo', { // different: null is missing + n: 1 / 0, + z: { + a: [3, 4, 5, 6, 'yep!', undefined, undefined], + b: {} + } + }, {}]]]), false); + + assert.equal(QUnit.equiv(a, + [{ + b: fn, + c: false, + do: 'reserved word', + for: { + ar: [3, 5, 9, 'hey!', [], { + ar: [1, [ + 3, 4, 6, 9, null, [], [] + ]], + e: fn + // different: missing property f: undefined + }] + }, + e: 0.43445 + }, 5, 'string', 0, fn, false, null, undefined, 0, [ + 4, 5, 6, 7, 8, 9, 11, 22, 33, 44, 55, '66', null, [], [[[[[3]]]], '3'], {}, 1 / 0 + ], [], [[[], 'foo', null, { + n: 1 / 0, + z: { + a: [3, 4, 5, 6, 'yep!', undefined, undefined], + b: {} + } + }, {}]]]), false); +}); + +QUnit.test('Prototypal inheritance', function (assert) { + function Gizmo (id) { + this.id = id; + } + + function Hoozit (id) { + this.id = id; + } + Hoozit.prototype = new Gizmo(); + + var gizmo = new Gizmo('ok'); + var hoozit = new Hoozit('ok'); + + // Try this test many times after test on instances that hold function + // to make sure that our code does not mess with last object constructor memoization. + assert.equal(QUnit.equiv(function () {}, function () {}), false); + + // Hoozit inherit from Gizmo + // hoozit instanceof Hoozit; // true + // hoozit instanceof Gizmo; // true + assert.equal(QUnit.equiv(hoozit, gizmo), true); + + Gizmo.prototype.bar = true; // not a function just in case we skip them + + // Hoozit inherit from Gizmo + // They are equivalent + assert.equal(QUnit.equiv(hoozit, gizmo), true); + + // Make sure this is still true !important + // The reason for this is that I forgot to reset the last + // caller to where it were called from. + assert.equal(QUnit.equiv(function () {}, function () {}), false); + + // Make sure this is still true !important + assert.equal(QUnit.equiv(hoozit, gizmo), true); + + Hoozit.prototype.foo = true; // not a function just in case we skip them + + // Gizmo does not inherit from Hoozit + // gizmo instanceof Gizmo; // true + // gizmo instanceof Hoozit; // false + // They are not equivalent + assert.equal(QUnit.equiv(hoozit, gizmo), false); + + // Make sure this is still true !important + assert.equal(QUnit.equiv(function () {}, function () {}), false); +}); + +QUnit.test('Instances', function (assert) { + var a1, a2, b1, b2, c1, c2, c3, car, carSame, carDiff, human; + + function A () {} + a1 = new A(); + a2 = new A(); + + function B () { + this.fn = function () {}; + } + b1 = new B(); + b2 = new B(); + + function C (fn) { + this.fn = fn; + } + + c1 = new C(function () { return true; }); + c2 = new C(function () { return false; }); + c3 = new C(function () { return true; }); + + assert.equal(QUnit.equiv(a1, a2), true, 'Same property, same constructor'); + + // b1.fn and b2.fn are functions but they are different references + // But we decided to skip function for instances. + assert.equal(QUnit.equiv(b1, b2), true, 'Same property, same constructor'); + + assert.equal(QUnit.equiv(c1, c2), false, 'Same property with different reference and different function'); + assert.equal(QUnit.equiv(c1, c3), true, 'Same property with different reference but same function'); + + // failed + assert.equal(QUnit.equiv(a1, b1), false, 'Same properties but different constructor'); + + function Car (year) { + var privateVar = 0; + this.year = year; + this.isOld = function () { + return privateVar > 10; + }; + } + + function Human (year) { + var privateVar = 1; + this.year = year; + this.isOld = function () { + return privateVar > 80; + }; + } + + car = new Car(30); + carSame = new Car(30); + carDiff = new Car(10); + human = new Human(30); + + assert.equal(QUnit.equiv(car, car), true); + assert.equal(QUnit.equiv(car, carDiff), false); + assert.equal(QUnit.equiv(car, carSame), true); + assert.equal(QUnit.equiv(car, human), false); +}); QUnit.test( - "Complex instance nesting (with function values in literals and/or in nested instances)", - function( assert ) { - var a1, a2, b1, b2, c1, c2, d1, d2, e1, e2; - - function A( fn ) { - this.a = {}; - this.fn = fn; - this.b = { a: [] }; - this.o = {}; - this.fn1 = fn; - } - function B( fn ) { - this.fn = fn; - this.fn1 = function() {}; - this.a = new A( function() {} ); - } - - function fnOutside() { - } - - function C( fn ) { - function fnInside() { - } - this.x = 10; - this.fn = fn; - this.fn1 = function() {}; - this.fn2 = fnInside; - this.fn3 = { - a: true, - b: fnOutside // ok make reference to a function in all instances scope - }; - this.o1 = {}; - - // This function will be ignored. - // Even if it is not visible for all instances (e.g. locked in a closures), - // it is from a property that makes part of an instance (e.g. from the C constructor) - this.b1 = new B( function() {} ); - this.b2 = new B( { - x: { - b2: new B( function() {} ) - } - } ); - } - - function D( fn ) { - function fnInside() { - } - this.x = 10; - this.fn = fn; - this.fn1 = function() {}; - this.fn2 = fnInside; - this.fn3 = { - a: true, - b: fnOutside, // ok make reference to a function in all instances scope - - // This function won't be ignored. - // It isn't visible for all C instances - // and it is not in a property of an instance. - // (in an Object instances e.g. the object literal) - c: fnInside - }; - this.o1 = {}; - - // This function will be ignored. - // Even if it is not visible for all instances (e.g. locked in a closures), - // it is from a property that makes part of an instance (e.g. from the C constructor) - this.b1 = new B( function() {} ); - this.b2 = new B( { - x: { - b2: new B( function() {} ) - } - } ); - } - - function E( fn ) { - function fnInside() { - } - this.x = 10; - this.fn = fn; - this.fn1 = function() {}; - this.fn2 = fnInside; - this.fn3 = { - a: true, - b: fnOutside // ok make reference to a function in all instances scope - }; - this.o1 = {}; - - // This function will be ignored. - // Even if it is not visible for all instances (e.g. locked in a closures), - // it is from a property that makes part of an instance (e.g. from the C constructor) - this.b1 = new B( function() {} ); - this.b2 = new B( { - x: { - b1: new B( { a: function() {} } ), - b2: new B( function() {} ) - } - } ); - } - - a1 = new A( function() {} ); - a2 = new A( function() {} ); - assert.equal( QUnit.equiv( a1, a2 ), true ); - - assert.equal( QUnit.equiv( a1, a2 ), true ); // different instances - - b1 = new B( function() {} ); - b2 = new B( function() {} ); - assert.equal( QUnit.equiv( b1, b2 ), true ); - - c1 = new C( function() {} ); - c2 = new C( function() {} ); - assert.equal( QUnit.equiv( c1, c2 ), true ); - - d1 = new D( function() {} ); - d2 = new D( function() {} ); - assert.equal( QUnit.equiv( d1, d2 ), false ); - - e1 = new E( function() {} ); - e2 = new E( function() {} ); - assert.equal( QUnit.equiv( e1, e2 ), false ); - } + 'Complex instance nesting (with function values in literals and/or in nested instances)', + function (assert) { + var a1, a2, b1, b2, c1, c2, d1, d2, e1, e2; + + function A (fn) { + this.a = {}; + this.fn = fn; + this.b = { a: [] }; + this.o = {}; + this.fn1 = fn; + } + function B (fn) { + this.fn = fn; + this.fn1 = function () {}; + this.a = new A(function () {}); + } + + function fnOutside () { + } + + function C (fn) { + function fnInside () { + } + this.x = 10; + this.fn = fn; + this.fn1 = function () {}; + this.fn2 = fnInside; + this.fn3 = { + a: true, + b: fnOutside // ok make reference to a function in all instances scope + }; + this.o1 = {}; + + // This function will be ignored. + // Even if it is not visible for all instances (e.g. locked in a closures), + // it is from a property that makes part of an instance (e.g. from the C constructor) + this.b1 = new B(function () {}); + this.b2 = new B({ + x: { + b2: new B(function () {}) + } + }); + } + + function D (fn) { + function fnInside () { + } + this.x = 10; + this.fn = fn; + this.fn1 = function () {}; + this.fn2 = fnInside; + this.fn3 = { + a: true, + b: fnOutside, // ok make reference to a function in all instances scope + + // This function won't be ignored. + // It isn't visible for all C instances + // and it is not in a property of an instance. + // (in an Object instances e.g. the object literal) + c: fnInside + }; + this.o1 = {}; + + // This function will be ignored. + // Even if it is not visible for all instances (e.g. locked in a closures), + // it is from a property that makes part of an instance (e.g. from the C constructor) + this.b1 = new B(function () {}); + this.b2 = new B({ + x: { + b2: new B(function () {}) + } + }); + } + + function E (fn) { + function fnInside () { + } + this.x = 10; + this.fn = fn; + this.fn1 = function () {}; + this.fn2 = fnInside; + this.fn3 = { + a: true, + b: fnOutside // ok make reference to a function in all instances scope + }; + this.o1 = {}; + + // This function will be ignored. + // Even if it is not visible for all instances (e.g. locked in a closures), + // it is from a property that makes part of an instance (e.g. from the C constructor) + this.b1 = new B(function () {}); + this.b2 = new B({ + x: { + b1: new B({ a: function () {} }), + b2: new B(function () {}) + } + }); + } + + a1 = new A(function () {}); + a2 = new A(function () {}); + assert.equal(QUnit.equiv(a1, a2), true); + + assert.equal(QUnit.equiv(a1, a2), true); // different instances + + b1 = new B(function () {}); + b2 = new B(function () {}); + assert.equal(QUnit.equiv(b1, b2), true); + + c1 = new C(function () {}); + c2 = new C(function () {}); + assert.equal(QUnit.equiv(c1, c2), true); + + d1 = new D(function () {}); + d2 = new D(function () {}); + assert.equal(QUnit.equiv(d1, d2), false); + + e1 = new E(function () {}); + e2 = new E(function () {}); + assert.equal(QUnit.equiv(e1, e2), false); + } ); -QUnit.test( "Object with circular references", function( assert ) { - var circularA = { - abc: null - }, - circularB = { - abc: null - }; - circularA.abc = circularA; - circularB.abc = circularB; - assert.equal( QUnit.equiv( circularA, circularB ), true, - "Should not repeat test on object (ambiguous test)" - ); - - circularA.def = 1; - circularB.def = 1; - assert.equal( QUnit.equiv( circularA, circularB ), true, - "Should not repeat test on object (ambiguous test)" - ); - - circularA.def = 1; - circularB.def = 0; - assert.equal( QUnit.equiv( circularA, circularB ), false, - "Should not repeat test on object (unambiguous test)" - ); -} ); - -QUnit.test( "Array with circular references", function( assert ) { - var circularA = [], - circularB = []; - circularA.push( circularA ); - circularB.push( circularB ); - assert.equal( QUnit.equiv( circularA, circularB ), true, - "Should not repeat test on array (ambiguous test)" - ); - - circularA.push( "abc" ); - circularB.push( "abc" ); - assert.equal( QUnit.equiv( circularA, circularB ), true, - "Should not repeat test on array (ambiguous test)" - ); - - circularA.push( "hello" ); - circularB.push( "goodbye" ); - assert.equal( QUnit.equiv( circularA, circularB ), false, - "Should not repeat test on array (unambiguous test)" - ); -} ); - -QUnit.test( "Mixed object/array with references to self wont loop", function( assert ) { - var circularA = [ { - abc: null - } ], - circularB = [ { - abc: null - } ]; - circularA[ 0 ].abc = circularA; - circularB[ 0 ].abc = circularB; - - circularA.push( circularA ); - circularB.push( circularB ); - assert.equal( QUnit.equiv( circularA, circularB ), true, - "Should not repeat test on object/array (ambiguous test)" - ); - - circularA[ 0 ].def = 1; - circularB[ 0 ].def = 1; - assert.equal( QUnit.equiv( circularA, circularB ), true, - "Should not repeat test on object/array (ambiguous test)" - ); - - circularA[ 0 ].def = 1; - circularB[ 0 ].def = 0; - assert.equal( QUnit.equiv( circularA, circularB ), false, - "Should not repeat test on object/array (unambiguous test)" - ); -} ); - -QUnit.test( "Compare self-referent to tree", function( assert ) { - var temp, - circularA = [ 0 ], - treeA = [ 0, null ], - circularO = {}, - treeO = { - o: null - }; - - circularA[ 1 ] = circularA; - circularO.o = circularO; - - assert.equal( QUnit.equiv( circularA, treeA ), false, - "Array: Should not consider circular equal to tree" - ); - assert.equal( QUnit.equiv( circularO, treeO ), false, - "Object: Should not consider circular equal to tree" - ); - - temp = [ 0, circularA ]; - assert.equal( QUnit.equiv( circularA, temp ), true, - "Array: Reference is circular for one, but equal on other" - ); - assert.equal( QUnit.equiv( temp, circularA ), true, - "Array: Reference is circular for one, but equal on other" - ); - - temp = { - o: circularO - }; - assert.equal( QUnit.equiv( circularO, temp ), true, - "Object: Reference is circular for one, but equal on other" - ); - assert.equal( QUnit.equiv( temp, circularO ), true, - "Object: Reference is circular for one, but equal on other" - ); -} ); - -QUnit.test( "Compare differently self-referential structures", function( assert ) { - var x = [], - y = []; - - x[ 0 ] = x; - y[ 0 ] = [ y ]; - - // x is [ x ], y is [ [ y ] ] - // So both x and y look like [ [ [ [ ... ] ] ] ] - assert.equal( QUnit.equiv( x, y ), true, "Equivalent arrays" ); - y = []; - y[ 0 ] = [ y, 1 ]; - - // x looks like [ [ [ [ ... ] ] ] ] - // y looks like [ [ [ [ ... , 1 ] ], 1 ] ] - assert.equal( QUnit.equiv( x, y ), false, "Nonequivalent arrays" ); - x = {}; - y = {}; - x.val = x; - y.val = { val: y }; - - // Both x and y look like { val: { val: { ... } } } - assert.equal( QUnit.equiv( x, y ), true, "Equivalent objects" ); - - // x looks like { val: { val: { val: { val: { ... } } } } } - // y looks like { val: { val: { val: { val: { ... }, foo: 1 } }, foo: 1 } } - y.val = { val: y, foo: 1 }; - assert.equal( QUnit.equiv( x, y ), false, "Nonequivalent objects" ); -} ); - -QUnit.test( "Compare structures with multiple references to the same containers", function( assert ) { - var i, - x = {}, - y = {}; - for ( i = 0; i < 20; i++ ) { - x = { foo: x, bar: x, baz: x }; - y = { foo: y, bar: y, baz: y }; - } - assert.equal( QUnit.equiv( { big: x, z: [ true ] }, { big: y, z: [ true ] } ), true, "Equivalent structures" ); - assert.equal( QUnit.equiv( { big: x, z: [ true ] }, { big: y, z: [ false ] } ), false, "Nonequivalent structures" ); -} ); - -QUnit.test( "Test that must be done at the end because they extend some primitive's prototype", - function( assert ) { - - // Try that a function looks like our regular expression. - // This tests if we check that a and b are really both instance of RegExp - Function.prototype.global = true; - Function.prototype.multiline = true; - Function.prototype.ignoreCase = false; - Function.prototype.source = "my regex"; - var re = /my regex/gm; - assert.equal( QUnit.equiv( re, function() {} ), false, - "A function that looks that a regex isn't a regex" - ); - - // This test will ensures it works in both ways, - // and ALSO especially that we can make differences - // between RegExp and Function constructor because - // typeof on a RegExpt instance is "function" - assert.equal( QUnit.equiv( function() {}, re ), false, - "Same conversely, but ensures that function and regexp are distinct because their constructor are different" - ); - } +QUnit.test('Object with circular references', function (assert) { + var circularA = { + abc: null + }; + var circularB = { + abc: null + }; + circularA.abc = circularA; + circularB.abc = circularB; + assert.equal(QUnit.equiv(circularA, circularB), true, + 'Should not repeat test on object (ambiguous test)' + ); + + circularA.def = 1; + circularB.def = 1; + assert.equal(QUnit.equiv(circularA, circularB), true, + 'Should not repeat test on object (ambiguous test)' + ); + + circularA.def = 1; + circularB.def = 0; + assert.equal(QUnit.equiv(circularA, circularB), false, + 'Should not repeat test on object (unambiguous test)' + ); +}); + +QUnit.test('Array with circular references', function (assert) { + var circularA = []; + var circularB = []; + circularA.push(circularA); + circularB.push(circularB); + assert.equal(QUnit.equiv(circularA, circularB), true, + 'Should not repeat test on array (ambiguous test)' + ); + + circularA.push('abc'); + circularB.push('abc'); + assert.equal(QUnit.equiv(circularA, circularB), true, + 'Should not repeat test on array (ambiguous test)' + ); + + circularA.push('hello'); + circularB.push('goodbye'); + assert.equal(QUnit.equiv(circularA, circularB), false, + 'Should not repeat test on array (unambiguous test)' + ); +}); + +QUnit.test('Mixed object/array with references to self wont loop', function (assert) { + var circularA = [{ + abc: null + }]; + var circularB = [{ + abc: null + }]; + circularA[0].abc = circularA; + circularB[0].abc = circularB; + + circularA.push(circularA); + circularB.push(circularB); + assert.equal(QUnit.equiv(circularA, circularB), true, + 'Should not repeat test on object/array (ambiguous test)' + ); + + circularA[0].def = 1; + circularB[0].def = 1; + assert.equal(QUnit.equiv(circularA, circularB), true, + 'Should not repeat test on object/array (ambiguous test)' + ); + + circularA[0].def = 1; + circularB[0].def = 0; + assert.equal(QUnit.equiv(circularA, circularB), false, + 'Should not repeat test on object/array (unambiguous test)' + ); +}); + +QUnit.test('Compare self-referent to tree', function (assert) { + var temp; + var circularA = [0]; + var treeA = [0, null]; + var circularO = {}; + var treeO = { + o: null + }; + + circularA[1] = circularA; + circularO.o = circularO; + + assert.equal(QUnit.equiv(circularA, treeA), false, + 'Array: Should not consider circular equal to tree' + ); + assert.equal(QUnit.equiv(circularO, treeO), false, + 'Object: Should not consider circular equal to tree' + ); + + temp = [0, circularA]; + assert.equal(QUnit.equiv(circularA, temp), true, + 'Array: Reference is circular for one, but equal on other' + ); + assert.equal(QUnit.equiv(temp, circularA), true, + 'Array: Reference is circular for one, but equal on other' + ); + + temp = { + o: circularO + }; + assert.equal(QUnit.equiv(circularO, temp), true, + 'Object: Reference is circular for one, but equal on other' + ); + assert.equal(QUnit.equiv(temp, circularO), true, + 'Object: Reference is circular for one, but equal on other' + ); +}); + +QUnit.test('Compare differently self-referential structures', function (assert) { + var x = []; + var y = []; + + x[0] = x; + y[0] = [y]; + + // x is [ x ], y is [ [ y ] ] + // So both x and y look like [ [ [ [ ... ] ] ] ] + assert.equal(QUnit.equiv(x, y), true, 'Equivalent arrays'); + y = []; + y[0] = [y, 1]; + + // x looks like [ [ [ [ ... ] ] ] ] + // y looks like [ [ [ [ ... , 1 ] ], 1 ] ] + assert.equal(QUnit.equiv(x, y), false, 'Nonequivalent arrays'); + x = {}; + y = {}; + x.val = x; + y.val = { val: y }; + + // Both x and y look like { val: { val: { ... } } } + assert.equal(QUnit.equiv(x, y), true, 'Equivalent objects'); + + // x looks like { val: { val: { val: { val: { ... } } } } } + // y looks like { val: { val: { val: { val: { ... }, foo: 1 } }, foo: 1 } } + y.val = { val: y, foo: 1 }; + assert.equal(QUnit.equiv(x, y), false, 'Nonequivalent objects'); +}); + +QUnit.test('Compare structures with multiple references to the same containers', function (assert) { + var i; + var x = {}; + var y = {}; + for (i = 0; i < 20; i++) { + x = { foo: x, bar: x, baz: x }; + y = { foo: y, bar: y, baz: y }; + } + assert.equal(QUnit.equiv({ big: x, z: [true] }, { big: y, z: [true] }), true, 'Equivalent structures'); + assert.equal(QUnit.equiv({ big: x, z: [true] }, { big: y, z: [false] }), false, 'Nonequivalent structures'); +}); + +QUnit.test("Test that must be done at the end because they extend some primitive's prototype", + function (assert) { + // Try that a function looks like our regular expression. + // This tests if we check that a and b are really both instance of RegExp + /* eslint-disable no-extend-native */ + Function.prototype.global = true; + Function.prototype.multiline = true; + Function.prototype.ignoreCase = false; + Function.prototype.source = 'my regex'; + var re = /my regex/gm; + assert.equal(QUnit.equiv(re, function () {}), false, + "A function that looks that a regex isn't a regex" + ); + + // This test will ensures it works in both ways, + // and ALSO especially that we can make differences + // between RegExp and Function constructor because + // typeof on a RegExpt instance is "function" + assert.equal(QUnit.equiv(function () {}, re), false, + 'Same conversely, but ensures that function and regexp are distinct because their constructor are different' + ); + } ); -QUnit.test( "Number objects", function( assert ) { - var SafeNumber = Number; - - assert.true( QUnit.equiv( new SafeNumber( 1 ), new SafeNumber( 1 ) ), - "Number objects with same values are equivalent." - ); - assert.true( QUnit.equiv( new SafeNumber( 0 / 0 ), new SafeNumber( 0 / 0 ) ), - "NaN Number objects are equivalent." - ); - assert.true( QUnit.equiv( new SafeNumber( 1 / 0 ), new SafeNumber( 2 / 0 ) ), - "Infinite Number objects are equivalent." - ); - - assert.false( QUnit.equiv( new SafeNumber( 1 ), new SafeNumber( 2 ) ), - "Number objects with different values are not equivalent." - ); - assert.false( QUnit.equiv( new SafeNumber( 0 / 0 ), new SafeNumber( 1 / 0 ) ), - "NaN Number objects and infinite Number objects are not equivalent." - ); - assert.false( QUnit.equiv( new SafeNumber( 1 / 0 ), new SafeNumber( -1 / 0 ) ), - "Positive and negative infinite Number objects are not equivalent." - ); -} ); - -QUnit.test( "String objects", function( assert ) { - var SafeString = String; - - assert.true( QUnit.equiv( new SafeString( "foo" ), new SafeString( "foo" ) ), - "String objects with same values are equivalent." - ); - assert.true( QUnit.equiv( new SafeString( "" ), new SafeString( "" ) ), - "Empty String objects are equivalent." - ); - - assert.false( QUnit.equiv( new SafeString( "foo" ), new SafeString( "bar" ) ), - "String objects with different values are not equivalent." - ); - assert.false( QUnit.equiv( new SafeString( "" ), new SafeString( "foo" ) ), - "Empty and nonempty String objects are not equivalent." - ); -} ); - -QUnit.test( "Boolean objects", function( assert ) { - var SafeBoolean = Boolean; - - assert.true( QUnit.equiv( new SafeBoolean( true ), new SafeBoolean( true ) ), - "True Boolean objects are equivalent." - ); - assert.true( QUnit.equiv( new SafeBoolean( false ), new SafeBoolean( false ) ), - "False Boolean objects are equivalent." - ); - - assert.false( QUnit.equiv( new SafeBoolean( true ), new SafeBoolean( false ) ), - "Boolean objects with different values are not equivalent." - ); -} ); - -QUnit.test( "Getter", function( assert ) { - var a = { - get x() { return 2; } - }; - var a2 = { - get x() { return 2; } - }; - var b = { - get x() { return 4; } - }; - assert.false( QUnit.equiv( a, b ), "Getters with different values are not equivalent" ); - assert.true( QUnit.equiv( a, a2 ), "Same value from different getters are equivalent" ); - - // Neither the presence of a descriptor nor the descriptor itself is compared - assert.true( QUnit.equiv( b, { x: 4 } ), "Getter and literal value are equivalent" ); -} ); - -var hasES6Set = ( function() { - if ( typeof Set !== "function" ) { - return false; - } - - try { - - // some platforms don't support iterables in Set constructors - var s = new Set( [ 1, 2, 3 ] ); - if ( s.size !== 3 || !s.has( 2 ) ) { - return false; - } - - // in IE 11, QUnit.objectType( new Set() ) === "object" - return ( QUnit.objectType( s ) === "set" ); - } catch ( e ) { - return false; - } -}() ); - -var hasES6Map = ( function() { - if ( typeof Map !== "function" ) { - return false; - } - - try { - - // some platforms don't support array-like iterables in Map constructors - var m = new Map( [ [ 1, 2 ] ] ); - if ( m.size !== 1 || !m.has( 1 ) ) { - return false; - } - - // in IE 11, QUnit.objectType( new Map() ) === "object" - return ( QUnit.objectType( m ) === "map" ); - } catch ( e ) { - return false; - } -}() ); - -QUnit[ hasES6Set ? "test" : "skip" ]( "Sets", function( assert ) { - var s1, s2, s3, s4, o1, o2, o3, o4, m1, m2, m3; - - // Empty sets - s1 = new Set(); - s2 = new Set( [] ); - assert.equal( QUnit.equiv( s1, s2 ), true, "Empty sets" ); - - // Simple cases - s1 = new Set( [ 1 ] ); - s2 = new Set( [ 1 ] ); - s3 = new Set( [ 3 ] ); - assert.equal( QUnit.equiv( s1, s2 ), true, "Single element sets [1] vs [1]" ); - assert.equal( QUnit.equiv( s1, s3 ), false, "Single element sets [1] vs [3]" ); - - // Tricky values - s1 = new Set( [ false, undefined, null, 0, Infinity, NaN, -Infinity ] ); - s2 = new Set( [ undefined, null, false, 0, NaN, Infinity, -Infinity ] ); - assert.equal( QUnit.equiv( s1, s2 ), true, "Multiple-element sets of tricky values" ); - - s1 = new Set( [ 1, 3 ] ); - s2 = new Set( [ 2, 3 ] ); - assert.equal( QUnit.equiv( s1, s2 ), false, "Sets can 'short-circuit' for early failure" ); - - // Sets Containing objects - o1 = { foo: 0, bar: true }; - o2 = { foo: 0, bar: true }; - o3 = { foo: 1, bar: true }; - o4 = { foo: 1, bar: true }; - s1 = new Set( [ o1, o3 ] ); - s2 = new Set( [ o1, o3 ] ); - assert.equal( QUnit.equiv( s1, s2 ), true, "Sets containing same objects" ); - s1 = new Set( [ o1 ] ); - s2 = new Set( [ o2 ] ); - assert.equal( QUnit.equiv( s1, s2 ), true, "Sets containing deeply-equal objects" ); - s1 = new Set( [ o1, o3 ] ); - s2 = new Set( [ o4, o2 ] ); - assert.equal( QUnit.equiv( s1, s2 ), true, "Sets containing deeply-equal objects in different insertion order" ); - s1 = new Set( [ o1 ] ); - s2 = new Set( [ o3 ] ); - assert.equal( QUnit.equiv( s1, s2 ), false, "Sets containing different objects" ); - - // Sets containing sets - s1 = new Set( [ 1, 2, 3 ] ); - s2 = new Set( [ 1, 2, 3 ] ); - s3 = new Set( [ s1 ] ); - s4 = new Set( [ s2 ] ); - assert.equal( QUnit.equiv( s3, s4 ), true, "Sets containing deeply-equal sets" ); - - // Sets containing different sets - s1 = new Set( [ 1, 2, 3 ] ); - s2 = new Set( [ 1, 2, 3, 4 ] ); - s3 = new Set( [ s1 ] ); - s4 = new Set( [ s2 ] ); - assert.equal( QUnit.equiv( s3, s4 ), false, "Sets containing different sets" ); - - // Sets containing maps - m1 = new Map( [ [ 1, 1 ] ] ); - m2 = new Map( [ [ 1, 1 ] ] ); - m3 = new Map( [ [ 1, 3 ] ] ); - s3 = new Set( [ m1 ] ); - s4 = new Set( [ m2 ] ); - assert.equal( QUnit.equiv( s3, s4 ), true, "Sets containing different but deeply-equal maps" ); - s3 = new Set( [ m1 ] ); - s4 = new Set( [ m3 ] ); - assert.equal( QUnit.equiv( s3, s4 ), false, "Sets containing different maps" ); - - // Sets with different insertion order - s1 = new Set( [ 1, 2, 3 ] ); - s2 = new Set( [ 3, 2, 1 ] ); - assert.equal( QUnit.equiv( s1, s2 ), true, "Sets with different insertion orders" ); -} ); - -QUnit[ hasES6Map ? "test" : "skip" ]( "Maps", function( assert ) { - var m1, m2, m3, m4, o1, o2, o3, o4, s1, s2, s3; - - // Empty maps - m1 = new Map(); - m2 = new Map( [] ); - assert.equal( QUnit.equiv( m1, m2 ), true, "Empty maps" ); - - // Simple cases - m1 = new Map( [ [ 1, 1 ] ] ); - m2 = new Map( [ [ 1, 1 ] ] ); - m3 = new Map( [ [ 1, 3 ] ] ); - assert.equal( QUnit.equiv( m1, m2 ), true, "Single element maps [1,1] vs [1,1]" ); - assert.equal( QUnit.equiv( m1, m3 ), false, "Single element maps [1,1] vs [1,3]" ); - - // Mismatched sizes - m1 = new Map( [ [ 1, 2 ] ] ); - m2 = new Map( [ [ 3, 4], [ 5, 6 ] ] ); - assert.equal( QUnit.equiv( m1, m2 ), false, "Compare maps with mismatch sizes" ); - - // Tricky values - m1 = new Map( [ - [ false, false ], - [ null, null ], - [ 0, 0 ], - [ undefined, undefined ], - [ Infinity, Infinity ], - [ NaN, NaN ], - [ -Infinity, -Infinity ] - ] ); - m2 = new Map( [ - [ undefined, undefined ], - [ null, null ], - [ false, false ], - [ 0, 0 ], - [ NaN, NaN ], - [ Infinity, Infinity ], - [ -Infinity, -Infinity ] - ] ); - assert.equal( QUnit.equiv( m1, m2 ), true, "Multiple-element maps of tricky values" ); - - // Same keys, different values - m1 = new Map( [ - [ 1, "one" ], - [ 2, "two" ] - ] ); - m2 = new Map( [ - [ 1, 1 ], - [ 2, 2 ] - ] ); - assert.equal( QUnit.equiv( m1, m2 ), false, "Maps with same keys, different values" ); - - // Maps Containing objects - o1 = { foo: 0, bar: true }; - o2 = { foo: 0, bar: true }; - o3 = { foo: 1, bar: true }; - o4 = { foo: 1, bar: true }; - m1 = new Map( [ - [ 1, o1 ], - [ 2, o3 ] - ] ); - m2 = new Map( [ - [ 1, o1 ], - [ 2, o3 ] - ] ); - assert.equal( QUnit.equiv( m1, m2 ), true, "Maps containing same objects" ); - m1 = new Map( [ [ 1, o1 ] ] ); - m2 = new Map( [ [ 1, o2 ] ] ); - assert.equal( QUnit.equiv( m1, m2 ), true, "Maps containing different but deeply-equal objects" ); - m1 = new Map( [ [ 1, o1 ], [ 2, o3 ] ] ); - m2 = new Map( [ [ 2, o4 ], [ 1, o2 ] ] ); - assert.equal( QUnit.equiv( m1, m2 ), true, "Maps containing different but deeply-equal objects in different insertion order" ); - m1 = new Map( [ [ o1, 1 ] ] ); - m2 = new Map( [ [ o2, 1 ] ] ); - assert.equal( QUnit.equiv( m1, m2 ), true, "Maps containing different but deeply-equal objects as keys" ); - m1 = new Map( [ [ o1, 1 ], [ o3, 2 ] ] ); - m2 = new Map( [ [ o4, 2 ], [ o2, 1 ] ] ); - assert.equal( QUnit.equiv( m1, m2 ), true, "Maps containing different but deeply-equal objects as keys in different insertion order" ); - - // Maps containing different objects - m1 = new Map( [ [ 1, o1 ] ] ); - m2 = new Map( [ [ 1, o3 ] ] ); - assert.equal( QUnit.equiv( m1, m2 ), false, "Maps containing different objects" ); - - // Maps containing maps - m1 = new Map( [ [ 1, 1 ] ] ); - m2 = new Map( [ [ 1, 1 ] ] ); - m3 = new Map( [ [ "myMap", m1 ] ] ); - m4 = new Map( [ [ "myMap", m2 ] ] ); - assert.equal( QUnit.equiv( m3, m4 ), true, "Maps containing deeply-equal maps" ); - - // Maps containing different maps - m1 = new Map( [ [ 1, 1 ] ] ); - m2 = new Map( [ [ 1, 2 ] ] ); - m3 = new Map( [ [ "myMap", m1 ] ] ); - m4 = new Map( [ [ "myMap", m2 ] ] ); - assert.equal( QUnit.equiv( m3, m4 ), false, "Maps containing different maps" ); - - // Maps containing sets - s1 = new Set( [ 1, 2, 3 ] ); - s2 = new Set( [ 1, 2, 3 ] ); - s3 = new Set( [ 1, 2, 3, 4 ] ); - m1 = new Map( [ [ 1, s1 ] ] ); - m2 = new Map( [ [ 1, s2 ] ] ); - assert.equal( QUnit.equiv( m1, m2 ), true, "Maps containing different but deeply-equal sets" ); - - // Maps containing different sets - m1 = new Map( [ [ 1, s1 ] ] ); - m2 = new Map( [ [ 1, s3 ] ] ); - assert.equal( QUnit.equiv( m1, m2 ), false, "Maps containing different sets" ); -} ); - -var hasES6Symbol = ( function() { - return typeof Symbol === "function"; -}() ); - -QUnit[ hasES6Symbol ? "test" : "skip" ]( "Symbols", function( assert ) { - var a = Symbol( 1 ); - var b = Symbol( 1 ); - - assert.equal( QUnit.equiv( a, a ), true, "Same symbol is equivalent" ); - assert.equal( - QUnit.equiv( a, b ), false, - "Not equivalent to another similar symbol built on the same token" - ); -} ); +QUnit.test('Number objects', function (assert) { + var SafeNumber = Number; + + assert.true(QUnit.equiv(new SafeNumber(1), new SafeNumber(1)), + 'Number objects with same values are equivalent.' + ); + assert.true(QUnit.equiv(new SafeNumber(0 / 0), new SafeNumber(0 / 0)), + 'NaN Number objects are equivalent.' + ); + assert.true(QUnit.equiv(new SafeNumber(1 / 0), new SafeNumber(2 / 0)), + 'Infinite Number objects are equivalent.' + ); + + assert.false(QUnit.equiv(new SafeNumber(1), new SafeNumber(2)), + 'Number objects with different values are not equivalent.' + ); + assert.false(QUnit.equiv(new SafeNumber(0 / 0), new SafeNumber(1 / 0)), + 'NaN Number objects and infinite Number objects are not equivalent.' + ); + assert.false(QUnit.equiv(new SafeNumber(1 / 0), new SafeNumber(-1 / 0)), + 'Positive and negative infinite Number objects are not equivalent.' + ); +}); + +QUnit.test('String objects', function (assert) { + var SafeString = String; + + assert.true(QUnit.equiv(new SafeString('foo'), new SafeString('foo')), + 'String objects with same values are equivalent.' + ); + assert.true(QUnit.equiv(new SafeString(''), new SafeString('')), + 'Empty String objects are equivalent.' + ); + + assert.false(QUnit.equiv(new SafeString('foo'), new SafeString('bar')), + 'String objects with different values are not equivalent.' + ); + assert.false(QUnit.equiv(new SafeString(''), new SafeString('foo')), + 'Empty and nonempty String objects are not equivalent.' + ); +}); + +QUnit.test('Boolean objects', function (assert) { + var SafeBoolean = Boolean; + + assert.true(QUnit.equiv(new SafeBoolean(true), new SafeBoolean(true)), + 'True Boolean objects are equivalent.' + ); + assert.true(QUnit.equiv(new SafeBoolean(false), new SafeBoolean(false)), + 'False Boolean objects are equivalent.' + ); + + assert.false(QUnit.equiv(new SafeBoolean(true), new SafeBoolean(false)), + 'Boolean objects with different values are not equivalent.' + ); +}); + +QUnit.test('Getter', function (assert) { + var a = { + get x () { return 2; } + }; + var a2 = { + get x () { return 2; } + }; + var b = { + get x () { return 4; } + }; + assert.false(QUnit.equiv(a, b), 'Getters with different values are not equivalent'); + assert.true(QUnit.equiv(a, a2), 'Same value from different getters are equivalent'); + + // Neither the presence of a descriptor nor the descriptor itself is compared + assert.true(QUnit.equiv(b, { x: 4 }), 'Getter and literal value are equivalent'); +}); + +/* global Set, Map */ +/* eslint-disable compat/compat */ + +var hasES6Set = (function () { + if (typeof Set !== 'function') { + return false; + } + + try { + // some platforms don't support iterables in Set constructors + var s = new Set([1, 2, 3]); + if (s.size !== 3 || !s.has(2)) { + return false; + } + + // in IE 11, QUnit.objectType( new Set() ) === "object" + return (QUnit.objectType(s) === 'set'); + } catch (e) { + return false; + } +}()); + +var hasES6Map = (function () { + if (typeof Map !== 'function') { + return false; + } + + try { + // some platforms don't support array-like iterables in Map constructors + var m = new Map([[1, 2]]); + if (m.size !== 1 || !m.has(1)) { + return false; + } + + // in IE 11, QUnit.objectType( new Map() ) === "object" + return (QUnit.objectType(m) === 'map'); + } catch (e) { + return false; + } +}()); + +QUnit[hasES6Set ? 'test' : 'skip']('Sets', function (assert) { + var s1, s2, s3, s4, o1, o2, o3, o4, m1, m2, m3; + + // Empty sets + s1 = new Set(); + s2 = new Set([]); + assert.equal(QUnit.equiv(s1, s2), true, 'Empty sets'); + + // Simple cases + s1 = new Set([1]); + s2 = new Set([1]); + s3 = new Set([3]); + assert.equal(QUnit.equiv(s1, s2), true, 'Single element sets [1] vs [1]'); + assert.equal(QUnit.equiv(s1, s3), false, 'Single element sets [1] vs [3]'); + + // Tricky values + s1 = new Set([false, undefined, null, 0, Infinity, NaN, -Infinity]); + s2 = new Set([undefined, null, false, 0, NaN, Infinity, -Infinity]); + assert.equal(QUnit.equiv(s1, s2), true, 'Multiple-element sets of tricky values'); + + s1 = new Set([1, 3]); + s2 = new Set([2, 3]); + assert.equal(QUnit.equiv(s1, s2), false, "Sets can 'short-circuit' for early failure"); + + // Sets Containing objects + o1 = { foo: 0, bar: true }; + o2 = { foo: 0, bar: true }; + o3 = { foo: 1, bar: true }; + o4 = { foo: 1, bar: true }; + s1 = new Set([o1, o3]); + s2 = new Set([o1, o3]); + assert.equal(QUnit.equiv(s1, s2), true, 'Sets containing same objects'); + s1 = new Set([o1]); + s2 = new Set([o2]); + assert.equal(QUnit.equiv(s1, s2), true, 'Sets containing deeply-equal objects'); + s1 = new Set([o1, o3]); + s2 = new Set([o4, o2]); + assert.equal(QUnit.equiv(s1, s2), true, 'Sets containing deeply-equal objects in different insertion order'); + s1 = new Set([o1]); + s2 = new Set([o3]); + assert.equal(QUnit.equiv(s1, s2), false, 'Sets containing different objects'); + + // Sets containing sets + s1 = new Set([1, 2, 3]); + s2 = new Set([1, 2, 3]); + s3 = new Set([s1]); + s4 = new Set([s2]); + assert.equal(QUnit.equiv(s3, s4), true, 'Sets containing deeply-equal sets'); + + // Sets containing different sets + s1 = new Set([1, 2, 3]); + s2 = new Set([1, 2, 3, 4]); + s3 = new Set([s1]); + s4 = new Set([s2]); + assert.equal(QUnit.equiv(s3, s4), false, 'Sets containing different sets'); + + // Sets containing maps + m1 = new Map([[1, 1]]); + m2 = new Map([[1, 1]]); + m3 = new Map([[1, 3]]); + s3 = new Set([m1]); + s4 = new Set([m2]); + assert.equal(QUnit.equiv(s3, s4), true, 'Sets containing different but deeply-equal maps'); + s3 = new Set([m1]); + s4 = new Set([m3]); + assert.equal(QUnit.equiv(s3, s4), false, 'Sets containing different maps'); + + // Sets with different insertion order + s1 = new Set([1, 2, 3]); + s2 = new Set([3, 2, 1]); + assert.equal(QUnit.equiv(s1, s2), true, 'Sets with different insertion orders'); +}); + +QUnit[hasES6Map ? 'test' : 'skip']('Maps', function (assert) { + var m1, m2, m3, m4, o1, o2, o3, o4, s1, s2, s3; + + // Empty maps + m1 = new Map(); + m2 = new Map([]); + assert.equal(QUnit.equiv(m1, m2), true, 'Empty maps'); + + // Simple cases + m1 = new Map([[1, 1]]); + m2 = new Map([[1, 1]]); + m3 = new Map([[1, 3]]); + assert.equal(QUnit.equiv(m1, m2), true, 'Single element maps [1,1] vs [1,1]'); + assert.equal(QUnit.equiv(m1, m3), false, 'Single element maps [1,1] vs [1,3]'); + + // Mismatched sizes + m1 = new Map([[1, 2]]); + m2 = new Map([[3, 4], [5, 6]]); + assert.equal(QUnit.equiv(m1, m2), false, 'Compare maps with mismatch sizes'); + + // Tricky values + m1 = new Map([ + [false, false], + [null, null], + [0, 0], + [undefined, undefined], + [Infinity, Infinity], + [NaN, NaN], + [-Infinity, -Infinity] + ]); + m2 = new Map([ + [undefined, undefined], + [null, null], + [false, false], + [0, 0], + [NaN, NaN], + [Infinity, Infinity], + [-Infinity, -Infinity] + ]); + assert.equal(QUnit.equiv(m1, m2), true, 'Multiple-element maps of tricky values'); + + // Same keys, different values + m1 = new Map([ + [1, 'one'], + [2, 'two'] + ]); + m2 = new Map([ + [1, 1], + [2, 2] + ]); + assert.equal(QUnit.equiv(m1, m2), false, 'Maps with same keys, different values'); + + // Maps Containing objects + o1 = { foo: 0, bar: true }; + o2 = { foo: 0, bar: true }; + o3 = { foo: 1, bar: true }; + o4 = { foo: 1, bar: true }; + m1 = new Map([ + [1, o1], + [2, o3] + ]); + m2 = new Map([ + [1, o1], + [2, o3] + ]); + assert.equal(QUnit.equiv(m1, m2), true, 'Maps containing same objects'); + m1 = new Map([[1, o1]]); + m2 = new Map([[1, o2]]); + assert.equal(QUnit.equiv(m1, m2), true, 'Maps containing different but deeply-equal objects'); + m1 = new Map([[1, o1], [2, o3]]); + m2 = new Map([[2, o4], [1, o2]]); + assert.equal(QUnit.equiv(m1, m2), true, 'Maps containing different but deeply-equal objects in different insertion order'); + m1 = new Map([[o1, 1]]); + m2 = new Map([[o2, 1]]); + assert.equal(QUnit.equiv(m1, m2), true, 'Maps containing different but deeply-equal objects as keys'); + m1 = new Map([[o1, 1], [o3, 2]]); + m2 = new Map([[o4, 2], [o2, 1]]); + assert.equal(QUnit.equiv(m1, m2), true, 'Maps containing different but deeply-equal objects as keys in different insertion order'); + + // Maps containing different objects + m1 = new Map([[1, o1]]); + m2 = new Map([[1, o3]]); + assert.equal(QUnit.equiv(m1, m2), false, 'Maps containing different objects'); + + // Maps containing maps + m1 = new Map([[1, 1]]); + m2 = new Map([[1, 1]]); + m3 = new Map([['myMap', m1]]); + m4 = new Map([['myMap', m2]]); + assert.equal(QUnit.equiv(m3, m4), true, 'Maps containing deeply-equal maps'); + + // Maps containing different maps + m1 = new Map([[1, 1]]); + m2 = new Map([[1, 2]]); + m3 = new Map([['myMap', m1]]); + m4 = new Map([['myMap', m2]]); + assert.equal(QUnit.equiv(m3, m4), false, 'Maps containing different maps'); + + // Maps containing sets + s1 = new Set([1, 2, 3]); + s2 = new Set([1, 2, 3]); + s3 = new Set([1, 2, 3, 4]); + m1 = new Map([[1, s1]]); + m2 = new Map([[1, s2]]); + assert.equal(QUnit.equiv(m1, m2), true, 'Maps containing different but deeply-equal sets'); + + // Maps containing different sets + m1 = new Map([[1, s1]]); + m2 = new Map([[1, s3]]); + assert.equal(QUnit.equiv(m1, m2), false, 'Maps containing different sets'); +}); + +/* global Symbol */ + +var hasES6Symbol = (function () { + return typeof Symbol === 'function'; +}()); + +QUnit[hasES6Symbol ? 'test' : 'skip']('Symbols', function (assert) { + var a = Symbol(1); + var b = Symbol(1); + + assert.equal(QUnit.equiv(a, a), true, 'Same symbol is equivalent'); + assert.equal( + QUnit.equiv(a, b), false, + 'Not equivalent to another similar symbol built on the same token' + ); +}); diff --git a/test/main/dump.js b/test/main/dump.js index 0ae030f8f..725e8cdcc 100644 --- a/test/main/dump.js +++ b/test/main/dump.js @@ -1,342 +1,338 @@ -/* globals Symbol:false */ +/* globals document, Symbol */ /* eslint-disable qunit/no-conditional-assertions */ -QUnit.module( "dump", { - afterEach: function() { - - // Restore default - QUnit.dump.maxDepth = null; - } -} ); - -QUnit.test( "dump output", function( assert ) { - assert.equal( QUnit.dump.parse( [ 1, 2 ] ), "[\n 1,\n 2\n]" ); - assert.equal( QUnit.dump.parse( { top: 5, left: 0 } ), "{\n \"left\": 0,\n \"top\": 5\n}" ); - if ( typeof document !== "undefined" && document.getElementById( "qunit-header" ) ) { - assert.equal( - QUnit.dump.parse( document.getElementById( "qunit-header" ) ), - "

        " - ); - assert.equal( - QUnit.dump.parse( document.getElementsByTagName( "h1" ) ), - "[\n

        \n]" - ); - } - - if ( typeof Symbol === "function" ) { - var sym = Symbol( "QUnit" ); - assert.equal( QUnit.dump.parse( sym ), "Symbol(QUnit)", "parse is correct for symbols" ); - } -} ); - -QUnit.test( "dump output, shallow", function( assert ) { - var obj = { - top: { - middle: { - bottom: 0 - } - }, - left: 0 - }; - QUnit.dump.maxDepth = 1; - assert.equal( QUnit.dump.parse( obj ), "{\n \"left\": 0,\n \"top\": [object Object]\n}" ); - - QUnit.dump.maxDepth = 2; - assert.equal( - QUnit.dump.parse( obj ), - "{\n \"left\": 0,\n \"top\": {\n \"middle\": [object Object]\n }\n}" - ); - - QUnit.dump.maxDepth = 3; - assert.equal( - QUnit.dump.parse( obj ), - "{\n \"left\": 0,\n \"top\": {\n \"middle\": {\n \"bottom\": 0\n }\n }\n}" - ); - - QUnit.dump.maxDepth = 5; - assert.equal( - QUnit.dump.parse( obj ), - "{\n \"left\": 0,\n \"top\": {\n \"middle\": {\n \"bottom\": 0\n }\n }\n}" - ); -} ); - -QUnit.test( "dump, TypeError properties", function( assert ) { - function CustomError( message ) { - this.message = message; - } - - CustomError.prototype.toString = function() { - return this.message; - }; - var customError = new CustomError( "sad puppy" ), - typeError = new TypeError( "crying kitten" ), - expectedCustomMessage = "\"message\": \"sad puppy\"", - expectedTypeMessage = "\"message\": \"crying kitten\"", - expectedTypeName = "\"name\": \"TypeError\"", - - dumpedCustomError = QUnit.dump.parse( customError ), - dumpedTypeError = QUnit.dump.parse( typeError ), - - dumpedTypeErrorWithEnumerable; - - // Test when object has some enumerable properties by adding one - typeError.hasCheeseburger = true; - - dumpedTypeErrorWithEnumerable = QUnit.dump.parse( typeError ); - - assert.pushResult( { - result: dumpedCustomError.indexOf( expectedCustomMessage ) >= 0, - actual: dumpedCustomError, - expected: expectedCustomMessage, - message: "custom error contains message field" - } ); - assert.pushResult( { - result: dumpedTypeError.indexOf( expectedTypeMessage ) >= 0, - actual: dumpedTypeError, - expected: expectedTypeMessage, - message: "type error contains message field" - } ); - assert.pushResult( { - result: dumpedTypeError.indexOf( expectedTypeName ) >= 0, - actual: dumpedTypeError, - expected: expectedTypeName, - message: "type error contains name field" - } ); - assert.pushResult( { - result: dumpedTypeErrorWithEnumerable.indexOf( expectedTypeMessage ) >= 0, - actual: dumpedTypeErrorWithEnumerable, - expected: expectedTypeMessage, - message: "type error with enumerable field contains message field" - } ); -} ); - -QUnit.test( "maximum array depth", function( assert ) { - QUnit.dump.maxDepth = 1; - assert.equal( - QUnit.dump.parse( [ [ ] ] ), - "[\n [object Array]\n]" ); -} ); - -QUnit.test( "regex", function( assert ) { - assert.equal( QUnit.dump.parse( /foo/ ), "/foo/" ); -} ); - -QUnit.test( "date", function( assert ) { - var date = new Date( "2020-11-10T03:24:00" ); - assert.equal( QUnit.dump.parse( date ), "\"" + date + "\"" ); -} ); - -QUnit.test( "error", function( assert ) { - assert.equal( - QUnit.dump.parse( new Error( "foo" ) ), - "Error(\"foo\")" ); -} ); - -QUnit.test( "named function", function( assert ) { - var f = function foo() {}; - assert.equal( QUnit.dump.parse( f ), "function foo(){\n [code]\n}" ); -} ); - -QUnit.test( "function args", function( assert ) { - // eslint-disable-next-line no-unused-vars - var f = function f( foo, bar ) {}; - assert.equal( QUnit.dump.parse( f ), "function f( a, b ){\n [code]\n}" ); -} ); - -QUnit.test( "unnamed function", function( assert ) { - // eslint-disable-next-line no-unused-vars - var f = function( foo, bar ) {}; - - // Modern browsers are very smart, they are usually able to - // come up with a name based on the assignment. To test the - // code path for where there is no name, we will force it. - delete f.name; - - assert.equal( QUnit.dump.parse( f ), "function( a, b ){\n [code]\n}" ); -} ); - -QUnit.test( "multiline", function( assert ) { - QUnit.dump.multiline = false; - - assert.equal( QUnit.dump.parse( [ [] ] ), "[ [] ]" ); - - QUnit.dump.multiline = true; -} ); - -QUnit.test( "HTML", function( assert ) { - QUnit.dump.HTML = true; - - assert.equal( - QUnit.dump.parse( [ 1, 2 ] ), - "[
          1,
          2
        ]" ); - - QUnit.dump.multiline = false; - assert.equal( - QUnit.dump.parse( [ 1, 2 ] ), - "[ 1, 2 ]" ); - - QUnit.dump.multiline = true; - QUnit.dump.HTML = false; -} ); - -QUnit.test( "HTML Nodes", function( assert ) { - - var fakeWindow = { - setInterval: [], - document: {}, - nodeType: undefined - }; - assert.equal( QUnit.dump.parse( fakeWindow ), "[Window]" ); - - var fakeDocument = { nodeType: 9 }; - assert.equal( QUnit.dump.parse( fakeDocument ), "[Document]" ); - - var fakeTextNode = { - nodeType: 3, - nodeName: "fakeTextNode", - nodeValue: "fakeValue" - }; - assert.equal( - QUnit.dump.parse( fakeTextNode ), - "fakeValue" ); - - QUnit.dump.HTML = true; - assert.equal( - QUnit.dump.parse( fakeTextNode ), - "<faketextnode>fakeValue</faketextnode>" ); - - QUnit.dump.HTML = false; -} ); - -QUnit.test( "Custom parser", function( assert ) { - - var parser = "dummy value"; - QUnit.dump.setParser( "CustomObject", parser ); - - assert.equal( QUnit.dump.parsers.CustomObject, parser ); -} ); - -function WrapFn( x ) { - this.wrap = x; - if ( x === undefined ) { - this.first = true; - } +QUnit.module('dump', { + afterEach: function () { + // Restore default + QUnit.dump.maxDepth = null; + } +}); + +QUnit.test('dump output', function (assert) { + assert.equal(QUnit.dump.parse([1, 2]), '[\n 1,\n 2\n]'); + assert.equal(QUnit.dump.parse({ top: 5, left: 0 }), '{\n "left": 0,\n "top": 5\n}'); + if (typeof document !== 'undefined' && document.getElementById('qunit-header')) { + assert.equal( + QUnit.dump.parse(document.getElementById('qunit-header')), + '

        ' + ); + assert.equal( + QUnit.dump.parse(document.getElementsByTagName('h1')), + '[\n

        \n]' + ); + } + + if (typeof Symbol === 'function') { + var sym = Symbol('QUnit'); + assert.equal(QUnit.dump.parse(sym), 'Symbol(QUnit)', 'parse is correct for symbols'); + } +}); + +QUnit.test('dump output, shallow', function (assert) { + var obj = { + top: { + middle: { + bottom: 0 + } + }, + left: 0 + }; + QUnit.dump.maxDepth = 1; + assert.equal(QUnit.dump.parse(obj), '{\n "left": 0,\n "top": [object Object]\n}'); + + QUnit.dump.maxDepth = 2; + assert.equal( + QUnit.dump.parse(obj), + '{\n "left": 0,\n "top": {\n "middle": [object Object]\n }\n}' + ); + + QUnit.dump.maxDepth = 3; + assert.equal( + QUnit.dump.parse(obj), + '{\n "left": 0,\n "top": {\n "middle": {\n "bottom": 0\n }\n }\n}' + ); + + QUnit.dump.maxDepth = 5; + assert.equal( + QUnit.dump.parse(obj), + '{\n "left": 0,\n "top": {\n "middle": {\n "bottom": 0\n }\n }\n}' + ); +}); + +QUnit.test('dump, TypeError properties', function (assert) { + function CustomError (message) { + this.message = message; + } + + CustomError.prototype.toString = function () { + return this.message; + }; + var customError = new CustomError('sad puppy'); + var typeError = new TypeError('crying kitten'); + var expectedCustomMessage = '"message": "sad puppy"'; + var expectedTypeMessage = '"message": "crying kitten"'; + var expectedTypeName = '"name": "TypeError"'; + + var dumpedCustomError = QUnit.dump.parse(customError); + var dumpedTypeError = QUnit.dump.parse(typeError); + + var dumpedTypeErrorWithEnumerable; + + // Test when object has some enumerable properties by adding one + typeError.hasCheeseburger = true; + + dumpedTypeErrorWithEnumerable = QUnit.dump.parse(typeError); + + assert.pushResult({ + result: dumpedCustomError.indexOf(expectedCustomMessage) >= 0, + actual: dumpedCustomError, + expected: expectedCustomMessage, + message: 'custom error contains message field' + }); + assert.pushResult({ + result: dumpedTypeError.indexOf(expectedTypeMessage) >= 0, + actual: dumpedTypeError, + expected: expectedTypeMessage, + message: 'type error contains message field' + }); + assert.pushResult({ + result: dumpedTypeError.indexOf(expectedTypeName) >= 0, + actual: dumpedTypeError, + expected: expectedTypeName, + message: 'type error contains name field' + }); + assert.pushResult({ + result: dumpedTypeErrorWithEnumerable.indexOf(expectedTypeMessage) >= 0, + actual: dumpedTypeErrorWithEnumerable, + expected: expectedTypeMessage, + message: 'type error with enumerable field contains message field' + }); +}); + +QUnit.test('maximum array depth', function (assert) { + QUnit.dump.maxDepth = 1; + assert.equal( + QUnit.dump.parse([[]]), + '[\n [object Array]\n]'); +}); + +QUnit.test('regex', function (assert) { + assert.equal(QUnit.dump.parse(/foo/), '/foo/'); +}); + +QUnit.test('date', function (assert) { + var date = new Date('2020-11-10T03:24:00'); + assert.equal(QUnit.dump.parse(date), '"' + date + '"'); +}); + +QUnit.test('error', function (assert) { + assert.equal( + QUnit.dump.parse(new Error('foo')), + 'Error("foo")'); +}); + +QUnit.test('named function', function (assert) { + var f = function foo () {}; + assert.equal(QUnit.dump.parse(f), 'function foo(){\n [code]\n}'); +}); + +QUnit.test('function args', function (assert) { + // eslint-disable-next-line no-unused-vars + var f = function f (foo, bar) {}; + assert.equal(QUnit.dump.parse(f), 'function f( a, b ){\n [code]\n}'); +}); + +QUnit.test('unnamed function', function (assert) { + // eslint-disable-next-line no-unused-vars + var f = function (foo, bar) {}; + + // Modern browsers are very smart, they are usually able to + // come up with a name based on the assignment. To test the + // code path for where there is no name, we will force it. + delete f.name; + + assert.equal(QUnit.dump.parse(f), 'function( a, b ){\n [code]\n}'); +}); + +QUnit.test('multiline', function (assert) { + QUnit.dump.multiline = false; + + assert.equal(QUnit.dump.parse([[]]), '[ [] ]'); + + QUnit.dump.multiline = true; +}); + +QUnit.test('HTML', function (assert) { + QUnit.dump.HTML = true; + + assert.equal( + QUnit.dump.parse([1, 2]), + '[
          1,
          2
        ]'); + + QUnit.dump.multiline = false; + assert.equal( + QUnit.dump.parse([1, 2]), + '[ 1, 2 ]'); + + QUnit.dump.multiline = true; + QUnit.dump.HTML = false; +}); + +QUnit.test('HTML Nodes', function (assert) { + var fakeWindow = { + setInterval: [], + document: {}, + nodeType: undefined + }; + assert.equal(QUnit.dump.parse(fakeWindow), '[Window]'); + + var fakeDocument = { nodeType: 9 }; + assert.equal(QUnit.dump.parse(fakeDocument), '[Document]'); + + var fakeTextNode = { + nodeType: 3, + nodeName: 'fakeTextNode', + nodeValue: 'fakeValue' + }; + assert.equal( + QUnit.dump.parse(fakeTextNode), + 'fakeValue'); + + QUnit.dump.HTML = true; + assert.equal( + QUnit.dump.parse(fakeTextNode), + '<faketextnode>fakeValue</faketextnode>'); + + QUnit.dump.HTML = false; +}); + +QUnit.test('Custom parser', function (assert) { + var parser = 'dummy value'; + QUnit.dump.setParser('CustomObject', parser); + + assert.equal(QUnit.dump.parsers.CustomObject, parser); +}); + +function WrapFn (x) { + this.wrap = x; + if (x === undefined) { + this.first = true; + } } -function chainwrap( depth, first, prev ) { - depth = depth || 0; - var last = prev || new WrapFn(); - first = first || last; +function chainwrap (depth, first, prev) { + depth = depth || 0; + var last = prev || new WrapFn(); + first = first || last; - if ( depth === 1 ) { - first.wrap = last; - } - if ( depth > 1 ) { - last = chainwrap( depth - 1, first, new WrapFn( last ) ); - } + if (depth === 1) { + first.wrap = last; + } + if (depth > 1) { + last = chainwrap(depth - 1, first, new WrapFn(last)); + } - return last; + return last; } -QUnit.test( "Check dump recursion", function( assert ) { - var noref, nodump, selfref, selfdump, parentref, parentdump, circref, circdump; - - // QUnit.dump.maxDepth = 20; - - noref = chainwrap( 0 ); - nodump = QUnit.dump.parse( noref ); - assert.equal( nodump, "{\n \"first\": true,\n \"wrap\": undefined\n}" ); - - selfref = chainwrap( 1 ); - selfdump = QUnit.dump.parse( selfref ); - assert.equal( selfdump, "{\n \"first\": true,\n \"wrap\": recursion(-1)\n}" ); - - parentref = chainwrap( 2 ); - parentdump = QUnit.dump.parse( parentref ); - assert.equal( parentdump, - "{\n \"wrap\": {\n \"first\": true,\n \"wrap\": recursion(-2)\n }\n}" - ); - - circref = chainwrap( 10 ); - circdump = QUnit.dump.parse( circref ); - assert.true( new RegExp( "recursion\\(-10\\)" ).test( circdump ), - "(" + circdump + ") should show -10 recursion level" - ); -} ); - -QUnit.test( "Check equal/deepEqual recursion", function( assert ) { - var noRecursion, selfref, circref; - - // QUnit.dump.maxDepth = 20; - - noRecursion = chainwrap( 0 ); - assert.equal( noRecursion, noRecursion, "I should be equal to me." ); - assert.deepEqual( noRecursion, noRecursion, "... and so in depth." ); - - selfref = chainwrap( 1 ); - assert.equal( selfref, selfref, "Even so if I nest myself." ); - assert.deepEqual( selfref, selfref, "... into the depth." ); - - circref = chainwrap( 10 ); - assert.equal( circref, circref, "Or hide that through some levels of indirection." ); - assert.deepEqual( circref, circref, "... and checked on all levels!" ); -} ); - -QUnit.test( "Circular reference with arrays", function( assert ) { - var arr, arrdump, obj, childarr, objdump, childarrdump; - - // Pure array self-ref - arr = []; - arr.push( arr ); - - arrdump = QUnit.dump.parse( arr ); - - assert.equal( arrdump, "[\n recursion(-1)\n]" ); - assert.equal( arr, arr[ 0 ], "no endless stack when trying to dump arrays with circular ref" ); - - // Mix obj-arr circular ref - obj = {}; - childarr = [ obj ]; - obj.childarr = childarr; - - objdump = QUnit.dump.parse( obj ); - childarrdump = QUnit.dump.parse( childarr ); - - assert.equal( objdump, "{\n \"childarr\": [\n recursion(-2)\n ]\n}" ); - assert.equal( childarrdump, "[\n {\n \"childarr\": recursion(-2)\n }\n]" ); - - assert.equal( obj.childarr, childarr, - "no endless stack when trying to dump array/object mix with circular ref" - ); - assert.equal( childarr[ 0 ], obj, - "no endless stack when trying to dump array/object mix with circular ref" - ); - -} ); - -QUnit.test( "Circular reference - test reported by soniciq in #105", function( assert ) { - var a, b, barr, - MyObject = function() {}; - MyObject.prototype.parent = function( obj ) { - if ( obj === undefined ) { - return this._parent; - } - this._parent = obj; - }; - MyObject.prototype.children = function( obj ) { - if ( obj === undefined ) { - return this._children; - } - this._children = obj; - }; - - a = new MyObject(); - b = new MyObject(); - - barr = [ b ]; - a.children( barr ); - b.parent( a ); - - assert.equal( a.children(), barr ); - assert.deepEqual( a.children(), [ b ] ); -} ); +QUnit.test('Check dump recursion', function (assert) { + var noref, nodump, selfref, selfdump, parentref, parentdump, circref, circdump; + + // QUnit.dump.maxDepth = 20; + + noref = chainwrap(0); + nodump = QUnit.dump.parse(noref); + assert.equal(nodump, '{\n "first": true,\n "wrap": undefined\n}'); + + selfref = chainwrap(1); + selfdump = QUnit.dump.parse(selfref); + assert.equal(selfdump, '{\n "first": true,\n "wrap": recursion(-1)\n}'); + + parentref = chainwrap(2); + parentdump = QUnit.dump.parse(parentref); + assert.equal(parentdump, + '{\n "wrap": {\n "first": true,\n "wrap": recursion(-2)\n }\n}' + ); + + circref = chainwrap(10); + circdump = QUnit.dump.parse(circref); + assert.true(new RegExp('recursion\\(-10\\)').test(circdump), + '(' + circdump + ') should show -10 recursion level' + ); +}); + +QUnit.test('Check equal/deepEqual recursion', function (assert) { + var noRecursion, selfref, circref; + + // QUnit.dump.maxDepth = 20; + + noRecursion = chainwrap(0); + assert.equal(noRecursion, noRecursion, 'I should be equal to me.'); + assert.deepEqual(noRecursion, noRecursion, '... and so in depth.'); + + selfref = chainwrap(1); + assert.equal(selfref, selfref, 'Even so if I nest myself.'); + assert.deepEqual(selfref, selfref, '... into the depth.'); + + circref = chainwrap(10); + assert.equal(circref, circref, 'Or hide that through some levels of indirection.'); + assert.deepEqual(circref, circref, '... and checked on all levels!'); +}); + +QUnit.test('Circular reference with arrays', function (assert) { + var arr, arrdump, obj, childarr, objdump, childarrdump; + + // Pure array self-ref + arr = []; + arr.push(arr); + + arrdump = QUnit.dump.parse(arr); + + assert.equal(arrdump, '[\n recursion(-1)\n]'); + assert.equal(arr, arr[0], 'no endless stack when trying to dump arrays with circular ref'); + + // Mix obj-arr circular ref + obj = {}; + childarr = [obj]; + obj.childarr = childarr; + + objdump = QUnit.dump.parse(obj); + childarrdump = QUnit.dump.parse(childarr); + + assert.equal(objdump, '{\n "childarr": [\n recursion(-2)\n ]\n}'); + assert.equal(childarrdump, '[\n {\n "childarr": recursion(-2)\n }\n]'); + + assert.equal(obj.childarr, childarr, + 'no endless stack when trying to dump array/object mix with circular ref' + ); + assert.equal(childarr[0], obj, + 'no endless stack when trying to dump array/object mix with circular ref' + ); +}); + +QUnit.test('Circular reference - test reported by soniciq in #105', function (assert) { + var a; var b; var barr; + var MyObject = function () {}; + MyObject.prototype.parent = function (obj) { + if (obj === undefined) { + return this._parent; + } + this._parent = obj; + }; + MyObject.prototype.children = function (obj) { + if (obj === undefined) { + return this._children; + } + this._children = obj; + }; + + a = new MyObject(); + b = new MyObject(); + + barr = [b]; + a.children(barr); + b.parent(a); + + assert.equal(a.children(), barr); + assert.deepEqual(a.children(), [b]); +}); diff --git a/test/main/each.js b/test/main/each.js index e829bce7a..cc5d5037f 100644 --- a/test/main/each.js +++ b/test/main/each.js @@ -1,49 +1,48 @@ -QUnit.module( "test.each", function() { - QUnit.test.each( "test.each", [ [ 1, 2, 3 ], [ 1, 1, 2 ] ], function( assert, data ) { - assert.strictEqual( data[ 0 ] + data[ 1 ], data[ 2 ] ); - } ); - QUnit.test.each( "test.each 1D", [ 1, [], "some" ], function( assert, value ) { - assert.true( Boolean( value ) ); - } ); - QUnit.test.each( "test.each with object", { caseFoo: [ 1, 2, 3 ], caseBar: [ 1, 1, 2 ] }, function( assert, data ) { - assert.strictEqual( data[ 0 ] + data[ 1 ], data[ 2 ] ); - } ); - QUnit.test.each( "test.each fails with non-array input", [ "something", 1, undefined, null ], function( assert, value ) { - assert.throws( function() { - QUnit.test.each( "test.each 1D", value, function() { } ); - } ); - } ); +QUnit.module('test.each', function () { + QUnit.test.each('test.each', [[1, 2, 3], [1, 1, 2]], function (assert, data) { + assert.strictEqual(data[0] + data[1], data[2]); + }); + QUnit.test.each('test.each 1D', [1, [], 'some'], function (assert, value) { + assert.true(Boolean(value)); + }); + QUnit.test.each('test.each with object', { caseFoo: [1, 2, 3], caseBar: [1, 1, 2] }, function (assert, data) { + assert.strictEqual(data[0] + data[1], data[2]); + }); + QUnit.test.each('test.each fails with non-array input', ['something', 1, undefined, null], function (assert, value) { + assert.throws(function () { + QUnit.test.each('test.each 1D', value, function () { }); + }); + }); - // Promise support for test.each() is tested in test/main/promise.js. + // Promise support for test.each() is tested in test/main/promise.js. - QUnit.module( "arguments", function( hooks ) { - var todoArgs; - hooks.after( function( assert ) { - assert.strictEqual( todoArgs, 2, "test.each.todo() callback args" ); - } ); + QUnit.module('arguments', function (hooks) { + var todoArgs; + hooks.after(function (assert) { + assert.strictEqual(todoArgs, 2, 'test.each.todo() callback args'); + }); - QUnit.test.each( "test.each() callback", [ 1 ], function( assert ) { - assert.strictEqual( arguments.length, 2 ); - } ); - QUnit.test.each( "test.each() callback with undefined", [ undefined ], function( assert ) { - assert.strictEqual( arguments.length, 2 ); - } ); - QUnit.test.todo.each( "test.each.todo() callback", [ 1 ], function( assert ) { - - // Captured and asserted later since todo() is expected to fail - todoArgs = arguments.length; - assert.true( false ); - } ); - } ); -} ); -QUnit.module( "test.skip.each", function() { - QUnit.test( "do run", function( assert ) { assert.true( true ); } ); - QUnit.test.skip.each( "test.skip.each", [ [ 1, 2, 3 ], [ 1, 1, 2 ] ], function( assert ) { - assert.true( false ); - } ); -} ); -QUnit.module( "test.todo.each", function() { - QUnit.test.todo.each( "test.todo.each", [ [ 1, 2, 3 ], [ 1, 1, 2 ] ], function( assert ) { - assert.true( false ); - } ); -} ); + QUnit.test.each('test.each() callback', [1], function (assert) { + assert.strictEqual(arguments.length, 2); + }); + QUnit.test.each('test.each() callback with undefined', [undefined], function (assert) { + assert.strictEqual(arguments.length, 2); + }); + QUnit.test.todo.each('test.each.todo() callback', [1], function (assert) { + // Captured and asserted later since todo() is expected to fail + todoArgs = arguments.length; + assert.true(false); + }); + }); +}); +QUnit.module('test.skip.each', function () { + QUnit.test('do run', function (assert) { assert.true(true); }); + QUnit.test.skip.each('test.skip.each', [[1, 2, 3], [1, 1, 2]], function (assert) { + assert.true(false); + }); +}); +QUnit.module('test.todo.each', function () { + QUnit.test.todo.each('test.todo.each', [[1, 2, 3], [1, 1, 2]], function (assert) { + assert.true(false); + }); +}); diff --git a/test/main/modules.js b/test/main/modules.js index c123c05d7..2ab037912 100644 --- a/test/main/modules.js +++ b/test/main/modules.js @@ -1,422 +1,422 @@ -QUnit.module( "QUnit.module", function() { - - QUnit.module( "before/beforeEach/afterEach/after", { - before: function() { - this.lastHook = "module-before"; - }, - beforeEach: function( assert ) { - assert.strictEqual( this.lastHook, "module-before", - "Module's beforeEach runs after before" ); - this.lastHook = "module-beforeEach"; - }, - afterEach: function( assert ) { - assert.strictEqual( this.lastHook, "test-block", - "Module's afterEach runs after current test block" ); - this.lastHook = "module-afterEach"; - }, - after: function( assert ) { - assert.strictEqual( this.lastHook, "module-afterEach", - "Module's afterEach runs before after" ); - this.lastHook = "module-after"; - } - } ); - - QUnit.test( "hooks order", function( assert ) { - assert.expect( 4 ); - - assert.strictEqual( this.lastHook, "module-beforeEach", - "Module's beforeEach runs before current test block" ); - this.lastHook = "test-block"; - } ); - - QUnit.module( "before", { - before: function( assert ) { - assert.true( true, "before hook ran" ); - - if ( typeof this.beforeCount === "undefined" ) { - this.beforeCount = 0; - } - - this.beforeCount++; - } - } ); - - QUnit.test( "runs before first test", function( assert ) { - assert.expect( 2 ); - assert.equal( this.beforeCount, 1, "beforeCount should be one" ); - } ); - - QUnit.test( "does not run before subsequent tests", function( assert ) { - assert.expect( 1 ); - assert.equal( this.beforeCount, 1, "beforeCount did not increase from last test" ); - } ); - - QUnit.module( "before (skip)", { - before: function( assert ) { - assert.true( true, "before hook ran" ); - } - } ); - - QUnit.skip( "first test in module is skipped" ); - - QUnit.test( "runs before first unskipped test", function( assert ) { - assert.expect( 1 ); - } ); - - QUnit.module( "after", { - after: function( assert ) { - assert.true( true, "after hook ran" ); - } - } ); - - QUnit.test( "does not run after initial tests", function( assert ) { - assert.expect( 0 ); - } ); - - QUnit.test( "runs after final test", function( assert ) { - assert.expect( 1 ); - } ); - - QUnit.module( "after (skip)", { - after: function( assert ) { - assert.true( true, "after hook ran" ); - } - } ); - - QUnit.test( "does not run after initial tests", function( assert ) { - assert.expect( 0 ); - } ); - - QUnit.test( "runs after final unskipped test", function( assert ) { - assert.expect( 1 ); - } ); - - QUnit.skip( "last test in module is skipped" ); - - QUnit.module( "before/after with all tests skipped", { - before: function( assert ) { - assert.true( false, "should not occur" ); - }, - after: function( assert ) { - assert.true( false, "should not occur" ); - } - } ); - - QUnit.skip( "verifier" ); - - QUnit.module( "Test context object", { - beforeEach: function( assert ) { - var key, - keys = []; - - for ( key in this ) { - keys.push( key ); - } - assert.deepEqual( keys, [ "helper" ] ); - }, - afterEach: function() {}, - helper: function() {} - } ); - - QUnit.test( "keys", function( assert ) { - assert.expect( 1 ); - this.contextTest = true; - } ); - - QUnit.module( "afterEach and assert.async", { - beforeEach: function() { - this.state = false; - }, - afterEach: function( assert ) { - assert.strictEqual( this.state, true, "Test afterEach." ); - } - } ); - - QUnit.test( "afterEach must be called after test ended", function( assert ) { - var testContext = this; - var done = assert.async(); - assert.expect( 1 ); - setTimeout( function() { - testContext.state = true; - done(); - } ); - } ); - - QUnit.module( "async beforeEach test", { - beforeEach: function( assert ) { - var done = assert.async(); - setTimeout( function() { - assert.true( true ); - done(); - } ); - } - } ); - - QUnit.test( "module with async beforeEach", function( assert ) { - assert.expect( 2 ); - assert.true( true ); - } ); - - QUnit.module( "async afterEach test", { - afterEach: function( assert ) { - var done = assert.async(); - setTimeout( function() { - assert.true( true ); - done(); - } ); - } - } ); - - QUnit.test( "module with async afterEach", function( assert ) { - assert.expect( 2 ); - assert.true( true ); - } ); - - QUnit.module( "save scope", { - foo: "foo", - beforeEach: function( assert ) { - assert.deepEqual( this.foo, "foo" ); - this.foo = "bar"; - }, - afterEach: function( assert ) { - assert.deepEqual( this.foo, "foobar" ); - } - } ); - - QUnit.test( "scope check", function( assert ) { - assert.expect( 3 ); - assert.deepEqual( this.foo, "bar" ); - this.foo = "foobar"; - } ); - - QUnit.module( "nested modules", function() { - QUnit.module( "first outer", { - afterEach: function( assert ) { - assert.true( true, "first outer module afterEach called" ); - }, - beforeEach: function( assert ) { - assert.true( true, "first outer beforeEach called" ); - } - }, function() { - QUnit.module( "first inner", { - afterEach: function( assert ) { - assert.true( true, "first inner module afterEach called" ); - }, - beforeEach: function( assert ) { - assert.true( true, "first inner module beforeEach called" ); - } - }, function() { - QUnit.test( "in module, before-/afterEach called in out-in-out order", function( assert ) { - var module = assert.test.module; - assert.equal( module.name, - "QUnit.module > nested modules > first outer > first inner" ); - assert.expect( 5 ); - } ); - } ); - - QUnit.test( "test after nested module is processed", function( assert ) { - var module = assert.test.module; - assert.equal( module.name, "QUnit.module > nested modules > first outer" ); - assert.expect( 3 ); - } ); - - QUnit.module( "second inner" ); - - QUnit.test( "test after non-nesting module declared", function( assert ) { - var module = assert.test.module; - assert.equal( module.name, "QUnit.module > nested modules > first outer > second inner" ); - assert.expect( 3 ); - } ); - } ); - - QUnit.module( "second outer" ); - - QUnit.test( "test after all nesting modules processed and new module declared", function( assert ) { - var module = assert.test.module; - assert.equal( module.name, "QUnit.module > nested modules > second outer" ); - } ); - } ); - - QUnit.test( "modules with nested functions does not spread beyond", function( assert ) { - assert.equal( assert.test.module.name, "QUnit.module" ); - } ); - - QUnit.module( "contained suite arguments", function( hooks ) { - QUnit.test( "hook functions", function( assert ) { - assert.strictEqual( typeof hooks.beforeEach, "function" ); - assert.strictEqual( typeof hooks.afterEach, "function" ); - } ); - - QUnit.module( "outer hooks", function( hooks ) { - var beforeEach = hooks.beforeEach; - var afterEach = hooks.afterEach; - - beforeEach( function( assert ) { - assert.true( true, "beforeEach called" ); - } ); - - afterEach( function( assert ) { - assert.true( true, "afterEach called" ); - } ); - - QUnit.test( "call hooks", function( assert ) { - assert.expect( 2 ); - } ); - - QUnit.module( "stacked inner hooks", function( hooks ) { - var beforeEach = hooks.beforeEach; - var afterEach = hooks.afterEach; - - beforeEach( function( assert ) { - assert.true( true, "nested beforeEach called" ); - } ); - - afterEach( function( assert ) { - assert.true( true, "nested afterEach called" ); - } ); - - QUnit.test( "call hooks", function( assert ) { - assert.expect( 4 ); - } ); - } ); - } ); - } ); - - QUnit.module( "contained suite `this`", function( hooks ) { - this.outer = 1; - - hooks.beforeEach( function() { - this.outer++; - } ); - - hooks.afterEach( function( assert ) { - assert.equal( - this.outer, 42, - "in-test environment modifications are visible by afterEach callbacks" - ); - } ); - - QUnit.test( "`this` is shared from modules to the tests", function( assert ) { - assert.equal( this.outer, 2 ); - this.outer = 42; - } ); - - QUnit.test( "sibling tests don't share environments", function( assert ) { - assert.equal( this.outer, 2 ); - this.outer = 42; - } ); - - QUnit.module( "nested suite `this`", function( hooks ) { - this.inner = true; - - hooks.beforeEach( function( assert ) { - assert.strictEqual( this.outer, 2 ); - assert.true( this.inner ); - } ); - - hooks.afterEach( function( assert ) { - assert.strictEqual( this.outer, 2 ); - assert.true( this.inner ); - - // This change affects the outermodule afterEach assertion. - this.outer = 42; - } ); - - QUnit.test( "inner modules share outer environments", function( assert ) { - assert.strictEqual( this.outer, 2 ); - assert.true( this.inner ); - } ); - } ); - - QUnit.test( "tests can't see environments from nested modules", function( assert ) { - assert.strictEqual( this.inner, undefined ); - this.outer = 42; - } ); - } ); - - QUnit.module( "nested modules before/after", { - before: function( assert ) { - assert.true( true, "before hook ran" ); - this.lastHook = "before"; - }, - after: function( assert ) { - assert.strictEqual( this.lastHook, "outer-after" ); - } - }, function() { - QUnit.test( "should run before", function( assert ) { - assert.expect( 2 ); - assert.strictEqual( this.lastHook, "before" ); - } ); - - QUnit.module( "outer", { - before: function( assert ) { - assert.true( true, "outer before hook ran" ); - this.lastHook = "outer-before"; - }, - after: function( assert ) { - assert.strictEqual( this.lastHook, "outer-test" ); - this.lastHook = "outer-after"; - } - }, function() { - QUnit.module( "inner", { - before: function( assert ) { - assert.strictEqual( this.lastHook, "outer-before" ); - this.lastHook = "inner-before"; - }, - after: function( assert ) { - assert.strictEqual( this.lastHook, "inner-test" ); - } - }, function() { - QUnit.test( "should run outer-before and inner-before", function( assert ) { - assert.expect( 3 ); - assert.strictEqual( this.lastHook, "inner-before" ); - } ); - - QUnit.test( "should run inner-after", function( assert ) { - assert.expect( 1 ); - this.lastHook = "inner-test"; - } ); - } ); - - QUnit.test( "should run outer-after and after", function( assert ) { - assert.expect( 2 ); - this.lastHook = "outer-test"; - } ); - } ); - } ); - - QUnit.module( "multiple hooks", function( hooks ) { - hooks.before( function( assert ) { assert.step( "before1" ); } ); - hooks.before( function( assert ) { assert.step( "before2" ); } ); - - hooks.beforeEach( function( assert ) { assert.step( "beforeEach1" ); } ); - hooks.beforeEach( function( assert ) { assert.step( "beforeEach2" ); } ); - - hooks.afterEach( function( assert ) { assert.step( "afterEach1" ); } ); - hooks.afterEach( function( assert ) { assert.step( "afterEach2" ); } ); - - hooks.after( function( assert ) { - assert.verifySteps( [ - - // before/beforeEach execute in FIFO order - "before1", - "before2", - "beforeEach1", - "beforeEach2", - - // after/afterEach execute in LIFO order - "afterEach2", - "afterEach1", - "after2", - "after1" - ] ); - } ); - - hooks.after( function( assert ) { assert.step( "after1" ); } ); - hooks.after( function( assert ) { assert.step( "after2" ); } ); - - QUnit.test( "all hooks", function( assert ) { - assert.expect( 9 ); - } ); - } ); -} ); +/* global setTimeout */ +QUnit.module('QUnit.module', function () { + QUnit.module('before/beforeEach/afterEach/after', { + before: function () { + this.lastHook = 'module-before'; + }, + beforeEach: function (assert) { + assert.strictEqual(this.lastHook, 'module-before', + "Module's beforeEach runs after before"); + this.lastHook = 'module-beforeEach'; + }, + afterEach: function (assert) { + assert.strictEqual(this.lastHook, 'test-block', + "Module's afterEach runs after current test block"); + this.lastHook = 'module-afterEach'; + }, + after: function (assert) { + assert.strictEqual(this.lastHook, 'module-afterEach', + "Module's afterEach runs before after"); + this.lastHook = 'module-after'; + } + }); + + QUnit.test('hooks order', function (assert) { + assert.expect(4); + + assert.strictEqual(this.lastHook, 'module-beforeEach', + "Module's beforeEach runs before current test block"); + this.lastHook = 'test-block'; + }); + + QUnit.module('before', { + before: function (assert) { + assert.true(true, 'before hook ran'); + + if (typeof this.beforeCount === 'undefined') { + this.beforeCount = 0; + } + + this.beforeCount++; + } + }); + + QUnit.test('runs before first test', function (assert) { + assert.expect(2); + assert.equal(this.beforeCount, 1, 'beforeCount should be one'); + }); + + QUnit.test('does not run before subsequent tests', function (assert) { + assert.expect(1); + assert.equal(this.beforeCount, 1, 'beforeCount did not increase from last test'); + }); + + QUnit.module('before (skip)', { + before: function (assert) { + assert.true(true, 'before hook ran'); + } + }); + + QUnit.skip('first test in module is skipped'); + + QUnit.test('runs before first unskipped test', function (assert) { + assert.expect(1); + }); + + QUnit.module('after', { + after: function (assert) { + assert.true(true, 'after hook ran'); + } + }); + + QUnit.test('does not run after initial tests', function (assert) { + assert.expect(0); + }); + + QUnit.test('runs after final test', function (assert) { + assert.expect(1); + }); + + QUnit.module('after (skip)', { + after: function (assert) { + assert.true(true, 'after hook ran'); + } + }); + + QUnit.test('does not run after initial tests', function (assert) { + assert.expect(0); + }); + + QUnit.test('runs after final unskipped test', function (assert) { + assert.expect(1); + }); + + QUnit.skip('last test in module is skipped'); + + QUnit.module('before/after with all tests skipped', { + before: function (assert) { + assert.true(false, 'should not occur'); + }, + after: function (assert) { + assert.true(false, 'should not occur'); + } + }); + + QUnit.skip('verifier'); + + QUnit.module('Test context object', { + beforeEach: function (assert) { + var key; + var keys = []; + + for (key in this) { + keys.push(key); + } + assert.deepEqual(keys, ['helper']); + }, + afterEach: function () {}, + helper: function () {} + }); + + QUnit.test('keys', function (assert) { + assert.expect(1); + this.contextTest = true; + }); + + QUnit.module('afterEach and assert.async', { + beforeEach: function () { + this.state = false; + }, + afterEach: function (assert) { + assert.strictEqual(this.state, true, 'Test afterEach.'); + } + }); + + QUnit.test('afterEach must be called after test ended', function (assert) { + var testContext = this; + var done = assert.async(); + assert.expect(1); + setTimeout(function () { + testContext.state = true; + done(); + }); + }); + + QUnit.module('async beforeEach test', { + beforeEach: function (assert) { + var done = assert.async(); + setTimeout(function () { + assert.true(true); + done(); + }); + } + }); + + QUnit.test('module with async beforeEach', function (assert) { + assert.expect(2); + assert.true(true); + }); + + QUnit.module('async afterEach test', { + afterEach: function (assert) { + var done = assert.async(); + setTimeout(function () { + assert.true(true); + done(); + }); + } + }); + + QUnit.test('module with async afterEach', function (assert) { + assert.expect(2); + assert.true(true); + }); + + QUnit.module('save scope', { + foo: 'foo', + beforeEach: function (assert) { + assert.deepEqual(this.foo, 'foo'); + this.foo = 'bar'; + }, + afterEach: function (assert) { + assert.deepEqual(this.foo, 'foobar'); + } + }); + + QUnit.test('scope check', function (assert) { + assert.expect(3); + assert.deepEqual(this.foo, 'bar'); + this.foo = 'foobar'; + }); + + QUnit.module('nested modules', function () { + QUnit.module('first outer', { + afterEach: function (assert) { + assert.true(true, 'first outer module afterEach called'); + }, + beforeEach: function (assert) { + assert.true(true, 'first outer beforeEach called'); + } + }, function () { + QUnit.module('first inner', { + afterEach: function (assert) { + assert.true(true, 'first inner module afterEach called'); + }, + beforeEach: function (assert) { + assert.true(true, 'first inner module beforeEach called'); + } + }, function () { + QUnit.test('in module, before-/afterEach called in out-in-out order', function (assert) { + var module = assert.test.module; + assert.equal(module.name, + 'QUnit.module > nested modules > first outer > first inner'); + assert.expect(5); + }); + }); + + QUnit.test('test after nested module is processed', function (assert) { + var module = assert.test.module; + assert.equal(module.name, 'QUnit.module > nested modules > first outer'); + assert.expect(3); + }); + + QUnit.module('second inner'); + + QUnit.test('test after non-nesting module declared', function (assert) { + var module = assert.test.module; + assert.equal(module.name, 'QUnit.module > nested modules > first outer > second inner'); + assert.expect(3); + }); + }); + + QUnit.module('second outer'); + + QUnit.test('test after all nesting modules processed and new module declared', function (assert) { + var module = assert.test.module; + assert.equal(module.name, 'QUnit.module > nested modules > second outer'); + }); + }); + + QUnit.test('modules with nested functions does not spread beyond', function (assert) { + assert.equal(assert.test.module.name, 'QUnit.module'); + }); + + QUnit.module('contained suite arguments', function (hooks) { + QUnit.test('hook functions', function (assert) { + assert.strictEqual(typeof hooks.beforeEach, 'function'); + assert.strictEqual(typeof hooks.afterEach, 'function'); + }); + + QUnit.module('outer hooks', function (hooks) { + var beforeEach = hooks.beforeEach; + var afterEach = hooks.afterEach; + + beforeEach(function (assert) { + assert.true(true, 'beforeEach called'); + }); + + afterEach(function (assert) { + assert.true(true, 'afterEach called'); + }); + + QUnit.test('call hooks', function (assert) { + assert.expect(2); + }); + + QUnit.module('stacked inner hooks', function (hooks) { + var beforeEach = hooks.beforeEach; + var afterEach = hooks.afterEach; + + beforeEach(function (assert) { + assert.true(true, 'nested beforeEach called'); + }); + + afterEach(function (assert) { + assert.true(true, 'nested afterEach called'); + }); + + QUnit.test('call hooks', function (assert) { + assert.expect(4); + }); + }); + }); + }); + + QUnit.module('contained suite `this`', function (hooks) { + this.outer = 1; + + hooks.beforeEach(function () { + this.outer++; + }); + + hooks.afterEach(function (assert) { + assert.equal( + this.outer, 42, + 'in-test environment modifications are visible by afterEach callbacks' + ); + }); + + QUnit.test('`this` is shared from modules to the tests', function (assert) { + assert.equal(this.outer, 2); + this.outer = 42; + }); + + QUnit.test("sibling tests don't share environments", function (assert) { + assert.equal(this.outer, 2); + this.outer = 42; + }); + + QUnit.module('nested suite `this`', function (hooks) { + this.inner = true; + + hooks.beforeEach(function (assert) { + assert.strictEqual(this.outer, 2); + assert.true(this.inner); + }); + + hooks.afterEach(function (assert) { + assert.strictEqual(this.outer, 2); + assert.true(this.inner); + + // This change affects the outermodule afterEach assertion. + this.outer = 42; + }); + + QUnit.test('inner modules share outer environments', function (assert) { + assert.strictEqual(this.outer, 2); + assert.true(this.inner); + }); + }); + + QUnit.test("tests can't see environments from nested modules", function (assert) { + assert.strictEqual(this.inner, undefined); + this.outer = 42; + }); + }); + + QUnit.module('nested modules before/after', { + before: function (assert) { + assert.true(true, 'before hook ran'); + this.lastHook = 'before'; + }, + after: function (assert) { + assert.strictEqual(this.lastHook, 'outer-after'); + } + }, function () { + QUnit.test('should run before', function (assert) { + assert.expect(2); + assert.strictEqual(this.lastHook, 'before'); + }); + + QUnit.module('outer', { + before: function (assert) { + assert.true(true, 'outer before hook ran'); + this.lastHook = 'outer-before'; + }, + after: function (assert) { + assert.strictEqual(this.lastHook, 'outer-test'); + this.lastHook = 'outer-after'; + } + }, function () { + QUnit.module('inner', { + before: function (assert) { + assert.strictEqual(this.lastHook, 'outer-before'); + this.lastHook = 'inner-before'; + }, + after: function (assert) { + assert.strictEqual(this.lastHook, 'inner-test'); + } + }, function () { + QUnit.test('should run outer-before and inner-before', function (assert) { + assert.expect(3); + assert.strictEqual(this.lastHook, 'inner-before'); + }); + + QUnit.test('should run inner-after', function (assert) { + assert.expect(1); + this.lastHook = 'inner-test'; + }); + }); + + QUnit.test('should run outer-after and after', function (assert) { + assert.expect(2); + this.lastHook = 'outer-test'; + }); + }); + }); + + QUnit.module('multiple hooks', function (hooks) { + hooks.before(function (assert) { assert.step('before1'); }); + hooks.before(function (assert) { assert.step('before2'); }); + + hooks.beforeEach(function (assert) { assert.step('beforeEach1'); }); + hooks.beforeEach(function (assert) { assert.step('beforeEach2'); }); + + hooks.afterEach(function (assert) { assert.step('afterEach1'); }); + hooks.afterEach(function (assert) { assert.step('afterEach2'); }); + + hooks.after(function (assert) { + assert.verifySteps([ + + // before/beforeEach execute in FIFO order + 'before1', + 'before2', + 'beforeEach1', + 'beforeEach2', + + // after/afterEach execute in LIFO order + 'afterEach2', + 'afterEach1', + 'after2', + 'after1' + ]); + }); + + hooks.after(function (assert) { assert.step('after1'); }); + hooks.after(function (assert) { assert.step('after2'); }); + + QUnit.test('all hooks', function (assert) { + assert.expect(9); + }); + }); +}); diff --git a/test/main/onError.js b/test/main/onError.js index f8a774538..0e75235ae 100644 --- a/test/main/onError.js +++ b/test/main/onError.js @@ -1,74 +1,73 @@ -QUnit.module( "QUnit.onError", function() { - QUnit.test( "inside a test", function( assert ) { - assert.expect( 2 ); +QUnit.module('QUnit.onError', function () { + QUnit.test('inside a test', function (assert) { + assert.expect(2); - var original = assert.pushResult; - var pushed = null; - assert.pushResult = function( resultInfo ) { - pushed = resultInfo; - }; + var original = assert.pushResult; + var pushed = null; + assert.pushResult = function (resultInfo) { + pushed = resultInfo; + }; - var suppressed = QUnit.onError( { - message: "Error message", - fileName: "filePath.js", - lineNumber: 1 - } ); + var suppressed = QUnit.onError({ + message: 'Error message', + fileName: 'filePath.js', + lineNumber: 1 + }); - assert.pushResult = original; + assert.pushResult = original; - assert.strictEqual( suppressed, false, "onError should allow other error handlers to run" ); - assert.propEqual( pushed, { - result: false, - message: "global failure: Error: Error message", - source: "filePath.js:1" - }, "pushed result" ); - } ); + assert.strictEqual(suppressed, false, 'onError should allow other error handlers to run'); + assert.propEqual(pushed, { + result: false, + message: 'global failure: Error: Error message', + source: 'filePath.js:1' + }, 'pushed result'); + }); - QUnit.test( "use stacktrace argument", function( assert ) { - assert.expect( 2 ); + QUnit.test('use stacktrace argument', function (assert) { + assert.expect(2); - var original = assert.pushResult; - var pushed = null; - assert.pushResult = function( result ) { - pushed = result; - assert.pushResult = original; - }; + var original = assert.pushResult; + var pushed = null; + assert.pushResult = function (result) { + pushed = result; + assert.pushResult = original; + }; - var suppressed = QUnit.onError( { - message: "Error message", - fileName: "filePath.js", - lineNumber: 1, - stacktrace: "DummyError\nfilePath.js:1 foo()\nfilePath.js:2 bar()" - } ); + var suppressed = QUnit.onError({ + message: 'Error message', + fileName: 'filePath.js', + lineNumber: 1, + stacktrace: 'DummyError\nfilePath.js:1 foo()\nfilePath.js:2 bar()' + }); - assert.strictEqual( suppressed, false, "onError should allow other error handlers to run" ); - assert.propEqual( pushed, { - result: false, - message: "global failure: Error: Error message", - source: "DummyError\nfilePath.js:1 foo()\nfilePath.js:2 bar()" - }, "pushed result" ); - } ); + assert.strictEqual(suppressed, false, 'onError should allow other error handlers to run'); + assert.propEqual(pushed, { + result: false, + message: 'global failure: Error: Error message', + source: 'DummyError\nfilePath.js:1 foo()\nfilePath.js:2 bar()' + }, 'pushed result'); + }); + QUnit.test('ignore failure when ignoreGlobalErrors is enabled', function (assert) { + assert.expect(2); - QUnit.test( "ignore failure when ignoreGlobalErrors is enabled", function( assert ) { - assert.expect( 2 ); + var original = assert.pushResult; + var pushed = null; + assert.pushResult = function (result) { + pushed = result; + }; + assert.test.ignoreGlobalErrors = true; - var original = assert.pushResult; - var pushed = null; - assert.pushResult = function( result ) { - pushed = result; - }; - assert.test.ignoreGlobalErrors = true; + var suppressed = QUnit.onError({ + message: 'Error message', + fileName: 'filePath.js', + lineNumber: 1 + }); - var suppressed = QUnit.onError( { - message: "Error message", - fileName: "filePath.js", - lineNumber: 1 - } ); + assert.pushResult = original; - assert.pushResult = original; - - assert.strictEqual( pushed, null, "No error should be pushed" ); - assert.strictEqual( suppressed, true, "onError should not allow other error handlers to run" ); - } ); -} ); + assert.strictEqual(pushed, null, 'No error should be pushed'); + assert.strictEqual(suppressed, true, 'onError should not allow other error handlers to run'); + }); +}); diff --git a/test/main/onUncaughtException.js b/test/main/onUncaughtException.js index e20b1c4df..1a37caf33 100644 --- a/test/main/onUncaughtException.js +++ b/test/main/onUncaughtException.js @@ -1,31 +1,31 @@ -QUnit.module( "QUnit.onUncaughtException", function() { - QUnit.test( "inside a test", function( assert ) { - assert.expect( 1 ); +QUnit.module('QUnit.onUncaughtException', function () { + QUnit.test('inside a test', function (assert) { + assert.expect(1); - var original = assert.pushResult; - var pushed = null; - assert.pushResult = function( result ) { - pushed = result; - }; + var original = assert.pushResult; + var pushed = null; + assert.pushResult = function (result) { + pushed = result; + }; - var error = new TypeError( "Message here" ); - error.stack = "filePath.js:1"; - QUnit.onUncaughtException( error ); + var error = new TypeError('Message here'); + error.stack = 'filePath.js:1'; + QUnit.onUncaughtException(error); - assert.pushResult = original; + assert.pushResult = original; - assert.propEqual( pushed, { - result: false, - message: "global failure: TypeError: Message here", - source: "filePath.js:1" - }, "pushed result" ); - } ); + assert.propEqual(pushed, { + result: false, + message: 'global failure: TypeError: Message here', + source: 'filePath.js:1' + }, 'pushed result'); + }); - // The "outside a test" scenario is not covered by explicitly calling onUncaughtException(). - // Instead, this is covered by the more standalone CLI test cases. - // - // It didn't seem worth the mess to test this since all onUncaughtException() does is - // increment numbers and call emit(). We'd have to both observe those happening in a - // private space, and then subsequently reverse the side-effects, in order to still - // have this test "pass". -} ); + // The "outside a test" scenario is not covered by explicitly calling onUncaughtException(). + // Instead, this is covered by the more standalone CLI test cases. + // + // It didn't seem worth the mess to test this since all onUncaughtException() does is + // increment numbers and call emit(). We'd have to both observe those happening in a + // private space, and then subsequently reverse the side-effects, in order to still + // have this test "pass". +}); diff --git a/test/main/promise.js b/test/main/promise.js index 73956278c..8745e7da9 100644 --- a/test/main/promise.js +++ b/test/main/promise.js @@ -1,253 +1,250 @@ // Support IE 9: Promise not supported, test MUST NOT load polyfil globally. // Support SpiderMonkey: setTimeout is not supported, but native Promise is. -var defer = typeof setTimeout !== "undefined" ? setTimeout : function( fn ) { - Promise.resolve().then( fn ); -}; +var defer = typeof setTimeout !== 'undefined' + // eslint-disable-next-line no-undef + ? setTimeout + : function (fn) { + // eslint-disable-next-line no-undef, compat/compat + Promise.resolve().then(fn); + }; // NOTE: Adds 1 assertion -function createMockPromise( assert, reject, value ) { - if ( arguments.length < 3 ) { - value = {}; - } - var thenable = { - then: function( fulfilledCallback, rejectedCallback ) { - assert.strictEqual( this, thenable, "`then` invoked with our Promise as thisValue" ); - defer( function() { - return reject ? - rejectedCallback.call( thenable, value ) : - fulfilledCallback.call( thenable, value ); - } ); - } - }; - return thenable; +function createMockPromise (assert, reject, value) { + if (arguments.length < 3) { + value = {}; + } + var thenable = { + then: function (fulfilledCallback, rejectedCallback) { + assert.strictEqual(this, thenable, '`then` invoked with our Promise as thisValue'); + defer(function () { + return reject + ? rejectedCallback.call(thenable, value) + : fulfilledCallback.call(thenable, value); + }); + } + }; + return thenable; } -QUnit.module( "Support for Promise", function() { - - QUnit.module( "before hook with non-Promise", { - before: function( assert ) { - assert.true( true ); - return {}; - } - } ); - QUnit.test( "test", function( assert ) { - assert.expect( 1 ); - } ); - - QUnit.module( "before hook with Promise", { - before: function( assert ) { - - // Adds 1 assertion - return createMockPromise( assert ); - } - } ); - QUnit.test( "test", function( assert ) { - assert.expect( 1 ); - } ); - - QUnit.module( "beforeEach hook with non-Promise", { - beforeEach: function( assert ) { - assert.true( true ); - return {}; - } - } ); - QUnit.test( "test", function( assert ) { - assert.expect( 1 ); - } ); - - QUnit.module( "beforeEach hook with Promise", { - beforeEach: function( assert ) { - - // Adds 1 assertion - return createMockPromise( assert ); - } - } ); - QUnit.test( "test", function( assert ) { - assert.expect( 1 ); - } ); - - QUnit.module( "afterEach hook with non-Promise", { - afterEach: function( assert ) { - assert.true( true ); - return {}; - } - } ); - QUnit.test( "test", function( assert ) { - assert.expect( 1 ); - } ); - - QUnit.module( "afterEach hook with Promise", { - afterEach: function( assert ) { - - // Adds 1 assertion - return createMockPromise( assert ); - } - } ); - QUnit.test( "test", function( assert ) { - assert.expect( 1 ); - } ); - - QUnit.module( "after hook with non-Promise ", { - after: function( assert ) { - assert.true( true ); - return {}; - } - } ); - QUnit.test( "test", function( assert ) { - assert.expect( 1 ); - } ); - - QUnit.module( "after hook with Promise", { - after: function( assert ) { - - // Adds 1 assertion - return createMockPromise( assert ); - } - } ); - QUnit.test( "test", function( assert ) { - assert.expect( 1 ); - } ); - - QUnit.module( "all hooks with Promise", { - before: function( assert ) { - return createMockPromise( assert ); - }, - beforeEach: function( assert ) { - return createMockPromise( assert ); - }, - afterEach: function( assert ) { - return createMockPromise( assert ); - }, - after: function( assert ) { - return createMockPromise( assert ); - } - } ); - QUnit.test( "test", function( assert ) { - assert.expect( 5 ); - return createMockPromise( assert ); - } ); - - QUnit.module( "tests", { - afterEach: function( assert ) { - - // Restore - if ( this.pushFailure ) { - assert.test.pushFailure = this.pushFailure; - } - } - } ); - - QUnit.test( "non-Promise", function( assert ) { - assert.expect( 0 ); - return {}; - } ); - - QUnit.test( "fulfilled Promise", function( assert ) { - assert.expect( 1 ); - - // Adds 1 assertion - return createMockPromise( assert ); - } ); - - QUnit.test( "fulfilled Promise and assert.async()", function( assert ) { - assert.expect( 2 ); - - var done = assert.async(); - defer( function() { - assert.true( true ); - done(); - } ); - - // Adds 1 assertion - return createMockPromise( assert ); - } ); - - QUnit.test( "rejected Promise with undefined value", function( assert ) { - assert.expect( 2 ); - - this.pushFailure = assert.test.pushFailure; - assert.test.pushFailure = function( message ) { - assert.strictEqual( - message, - "Promise rejected during \"rejected Promise with undefined value\": undefined" - ); - }; - - return createMockPromise( assert, true, undefined ); - } ); - - QUnit.test( "rejected Promise with error value", function( assert ) { - assert.expect( 2 ); - - this.pushFailure = assert.test.pushFailure; - assert.test.pushFailure = function( message ) { - assert.strictEqual( - message, - "Promise rejected during \"rejected Promise with error value\": this is an error" - ); - }; - - return createMockPromise( assert, true, new Error( "this is an error" ) ); - } ); - - QUnit.test( "rejected Promise with string value", function( assert ) { - assert.expect( 2 ); - - this.pushFailure = assert.test.pushFailure; - assert.test.pushFailure = function( message ) { - assert.strictEqual( - message, - "Promise rejected during \"rejected Promise with string value\": this is an error" - ); - }; - - return createMockPromise( assert, true, "this is an error" ); - } ); - - QUnit.test( "rejected Promise with async pause", function( assert ) { - assert.expect( 2 ); - - assert.async(); // Important! We don't explicitly release the async pause - - this.pushFailure = assert.test.pushFailure; - assert.test.pushFailure = function( message ) { - assert.strictEqual( - message, - "Promise rejected during \"rejected Promise with async pause\": this is an error" - ); - }; - - return createMockPromise( assert, true, "this is an error" ); - } ); - - QUnit.module( "test.each()", { - afterEach: function( assert ) { - - // Restore - if ( this.pushFailure ) { - assert.test.pushFailure = this.pushFailure; - } - } - } ); - - QUnit.test.each( "fulfilled Promise", [ 1 ], function( assert, _data ) { - assert.expect( 1 ); - - // Adds 1 assertion - return createMockPromise( assert ); - } ); - - QUnit.test.each( "rejected Promise with Error", [ 1 ], function( assert, _data ) { - assert.expect( 2 ); - - this.pushFailure = assert.test.pushFailure; - assert.test.pushFailure = function( message ) { - assert.strictEqual( - message, - "Promise rejected during \"rejected Promise with Error [0]\": this is an error" - ); - }; - - return createMockPromise( assert, true, new Error( "this is an error" ) ); - } ); -} ); +QUnit.module('Support for Promise', function () { + QUnit.module('before hook with non-Promise', { + before: function (assert) { + assert.true(true); + return {}; + } + }); + QUnit.test('test', function (assert) { + assert.expect(1); + }); + + QUnit.module('before hook with Promise', { + before: function (assert) { + // Adds 1 assertion + return createMockPromise(assert); + } + }); + QUnit.test('test', function (assert) { + assert.expect(1); + }); + + QUnit.module('beforeEach hook with non-Promise', { + beforeEach: function (assert) { + assert.true(true); + return {}; + } + }); + QUnit.test('test', function (assert) { + assert.expect(1); + }); + + QUnit.module('beforeEach hook with Promise', { + beforeEach: function (assert) { + // Adds 1 assertion + return createMockPromise(assert); + } + }); + QUnit.test('test', function (assert) { + assert.expect(1); + }); + + QUnit.module('afterEach hook with non-Promise', { + afterEach: function (assert) { + assert.true(true); + return {}; + } + }); + QUnit.test('test', function (assert) { + assert.expect(1); + }); + + QUnit.module('afterEach hook with Promise', { + afterEach: function (assert) { + // Adds 1 assertion + return createMockPromise(assert); + } + }); + QUnit.test('test', function (assert) { + assert.expect(1); + }); + + QUnit.module('after hook with non-Promise ', { + after: function (assert) { + assert.true(true); + return {}; + } + }); + QUnit.test('test', function (assert) { + assert.expect(1); + }); + + QUnit.module('after hook with Promise', { + after: function (assert) { + // Adds 1 assertion + return createMockPromise(assert); + } + }); + QUnit.test('test', function (assert) { + assert.expect(1); + }); + + QUnit.module('all hooks with Promise', { + before: function (assert) { + return createMockPromise(assert); + }, + beforeEach: function (assert) { + return createMockPromise(assert); + }, + afterEach: function (assert) { + return createMockPromise(assert); + }, + after: function (assert) { + return createMockPromise(assert); + } + }); + QUnit.test('test', function (assert) { + assert.expect(5); + return createMockPromise(assert); + }); + + QUnit.module('tests', { + afterEach: function (assert) { + // Restore + if (this.pushFailure) { + assert.test.pushFailure = this.pushFailure; + } + } + }); + + QUnit.test('non-Promise', function (assert) { + assert.expect(0); + return {}; + }); + + QUnit.test('fulfilled Promise', function (assert) { + assert.expect(1); + + // Adds 1 assertion + return createMockPromise(assert); + }); + + QUnit.test('fulfilled Promise and assert.async()', function (assert) { + assert.expect(2); + + var done = assert.async(); + defer(function () { + assert.true(true); + done(); + }); + + // Adds 1 assertion + return createMockPromise(assert); + }); + + QUnit.test('rejected Promise with undefined value', function (assert) { + assert.expect(2); + + this.pushFailure = assert.test.pushFailure; + assert.test.pushFailure = function (message) { + assert.strictEqual( + message, + 'Promise rejected during "rejected Promise with undefined value": undefined' + ); + }; + + return createMockPromise(assert, true, undefined); + }); + + QUnit.test('rejected Promise with error value', function (assert) { + assert.expect(2); + + this.pushFailure = assert.test.pushFailure; + assert.test.pushFailure = function (message) { + assert.strictEqual( + message, + 'Promise rejected during "rejected Promise with error value": this is an error' + ); + }; + + return createMockPromise(assert, true, new Error('this is an error')); + }); + + QUnit.test('rejected Promise with string value', function (assert) { + assert.expect(2); + + this.pushFailure = assert.test.pushFailure; + assert.test.pushFailure = function (message) { + assert.strictEqual( + message, + 'Promise rejected during "rejected Promise with string value": this is an error' + ); + }; + + return createMockPromise(assert, true, 'this is an error'); + }); + + QUnit.test('rejected Promise with async pause', function (assert) { + assert.expect(2); + + assert.async(); // Important! We don't explicitly release the async pause + + this.pushFailure = assert.test.pushFailure; + assert.test.pushFailure = function (message) { + assert.strictEqual( + message, + 'Promise rejected during "rejected Promise with async pause": this is an error' + ); + }; + + return createMockPromise(assert, true, 'this is an error'); + }); + + QUnit.module('test.each()', { + afterEach: function (assert) { + // Restore + if (this.pushFailure) { + assert.test.pushFailure = this.pushFailure; + } + } + }); + + QUnit.test.each('fulfilled Promise', [1], function (assert, _data) { + assert.expect(1); + + // Adds 1 assertion + return createMockPromise(assert); + }); + + QUnit.test.each('rejected Promise with Error', [1], function (assert, _data) { + assert.expect(2); + + this.pushFailure = assert.test.pushFailure; + assert.test.pushFailure = function (message) { + assert.strictEqual( + message, + 'Promise rejected during "rejected Promise with Error [0]": this is an error' + ); + }; + + return createMockPromise(assert, true, new Error('this is an error')); + }); +}); diff --git a/test/main/setTimeout.js b/test/main/setTimeout.js index aa061ecce..4b00f71dc 100644 --- a/test/main/setTimeout.js +++ b/test/main/setTimeout.js @@ -1,24 +1,22 @@ -( function( globalThis ) { +(function (globalThis) { + QUnit.module('Support for mocked setTimeout', { + beforeEach: function () { + this.setTimeout = globalThis.setTimeout; + globalThis.setTimeout = function () {}; + }, - QUnit.module( "Support for mocked setTimeout", { - beforeEach: function() { - this.setTimeout = globalThis.setTimeout; - globalThis.setTimeout = function() {}; - }, + afterEach: function () { + globalThis.setTimeout = this.setTimeout; + } + }); - afterEach: function() { - globalThis.setTimeout = this.setTimeout; - } - } ); + QUnit.test('test one', function (assert) { + assert.true(true); + }); - QUnit.test( "test one", function( assert ) { - assert.true( true ); - } ); - - QUnit.test( "test two", function( assert ) { - assert.true( true ); - } ); - -}( ( function() { - return this; -}() ) ) ); + QUnit.test('test two', function (assert) { + assert.true(true); + }); +}((function () { + return this; +}()))); diff --git a/test/main/stack.js b/test/main/stack.js index 21767841d..c5ea6b434 100644 --- a/test/main/stack.js +++ b/test/main/stack.js @@ -1,14 +1,14 @@ -( function() { - var stack = QUnit.stack(); +(function () { + var stack = QUnit.stack(); - QUnit.module( "QUnit.stack" ); + QUnit.module('QUnit.stack'); - // Flag this test as skipped on browsers that doesn't support stack trace - QUnit[ stack ? "test" : "skip" ]( "returns the proper stack line", function( assert ) { - assert.true( /(\/|\\)test(\/|\\)main(\/|\\)stack\.js/.test( stack ) ); + // Flag this test as skipped on browsers that doesn't support stack trace + QUnit[stack ? 'test' : 'skip']('returns the proper stack line', function (assert) { + assert.true(/(\/|\\)test(\/|\\)main(\/|\\)stack\.js/.test(stack)); - stack = QUnit.stack( 2 ); - assert.true( /(\/|\\)qunit(\/|\\)qunit\.js/.test( stack ), "can use offset argument to return a different stacktrace line" ); - assert.false( /\/test\/main\/stack\.js/.test( stack ), "stack with offset argument" ); - } ); -}() ); + stack = QUnit.stack(2); + assert.true(/(\/|\\)qunit(\/|\\)qunit\.js/.test(stack), 'can use offset argument to return a different stacktrace line'); + assert.false(/\/test\/main\/stack\.js/.test(stack), 'stack with offset argument'); + }); +}()); diff --git a/test/main/test.js b/test/main/test.js index 819e611a6..fdd64c7e2 100644 --- a/test/main/test.js +++ b/test/main/test.js @@ -1,281 +1,275 @@ -QUnit.module( "test", function() { - - QUnit.test( "read and change assert.expect() count", function( assert ) { - assert.expect( 2 ); - assert.true( true ); - var expected = assert.expect(); - assert.equal( expected, 2 ); - assert.expect( expected + 1 ); - assert.true( true ); - } ); - - ( typeof document !== "undefined" ? QUnit.module : QUnit.module.skip )( "fixture management", function( hooks ) { - var failure = false, - values = [ - - /* initial value (see unshift below), */ - /* initial value (see unshift below), */ - "ar", - "

        bc

        ", - undefined - ], - originalValue; - - hooks.before( function() { - originalValue = QUnit.config.fixture; - values.unshift( originalValue ); - values.unshift( originalValue ); - - var customFixtureNode = document.createElement( "span" ); - customFixtureNode.setAttribute( "id", "qunit-fixture" ); - customFixtureNode.setAttribute( "data-baz", "huzzah!" ); - values.push( customFixtureNode ); - } ); - - hooks.beforeEach( function( assert ) { - assert.fixtureEquals = function fixtureEquals( options ) { - var expectedTagName = options.tagName || "div"; - var expectedAttributes = options.attributes || {}; - var expectedContent = options.content || ""; - - var element = document.getElementById( "qunit-fixture" ); - - this.pushResult( { - result: element.tagName === expectedTagName.toUpperCase(), - actual: element.tagName.toLowerCase(), - expected: expectedTagName, - message: "tagName" - } ); - - var actualAttributes = {}; - - for ( var i = 0, l = element.attributes.length; i < l; i++ ) { - actualAttributes[ element.attributes[ i ].name ] = element.attributes[ i ].value; - } - - this.deepEqual( actualAttributes, expectedAttributes, "attributes" ); - this.strictEqual( element.innerHTML, expectedContent, "contents" ); - }; - - assert.hasFailingAssertions = function() { - for ( var i = 0; i < this.test.assertions.length; i++ ) { - if ( !this.test.assertions[ i ].result ) { - return true; - } - } - - return false; - }; - } ); - - // Set QUnit.config.fixture for the next test, propagating failures to recover the sequence - hooks.afterEach( function( assert ) { - failure = failure || assert.hasFailingAssertions(); - if ( failure ) { - assert.true( false, "prior failure" ); - QUnit.config.fixture = originalValue; - } else { - QUnit.config.fixture = values.shift(); - } - } ); - - QUnit.test( "setup", function( assert ) { - assert.equal( values.length, 6, "proper sequence" ); - - // setup for next test - document.getElementById( "qunit-fixture" ).innerHTML = "foo"; - } ); - - QUnit.test( "automatically reset", function( assert ) { - assert.fixtureEquals( { - tagName: "div", - attributes: { id: "qunit-fixture" }, - content: originalValue.innerHTML - } ); - assert.equal( values.length, 5, "proper sequence" ); - - // setup for next test - document.getElementById( "qunit-fixture" ).setAttribute( "data-foo", "blah" ); - } ); - - QUnit.test( "automatically reset after attribute value mutation", function( assert ) { - assert.fixtureEquals( { - tagName: "div", - attributes: { id: "qunit-fixture" }, - content: originalValue.innerHTML - } ); - assert.equal( values.length, 4, "proper sequence" ); - } ); - - QUnit.test( "user-specified string", function( assert ) { - assert.fixtureEquals( { - tagName: "div", - attributes: { id: "qunit-fixture" }, - content: "ar" - } ); - assert.equal( values.length, 3, "proper sequence" ); - - // setup for next test - document.getElementById( "qunit-fixture" ).setAttribute( "data-foo", "blah" ); - } ); - - QUnit.test( "user-specified string automatically resets attribute value mutation", function( assert ) { - assert.fixtureEquals( { - tagName: "div", - attributes: { id: "qunit-fixture" }, - content: "

        bc

        " - } ); - assert.equal( values.length, 2, "proper sequence" ); - - // setup for next test - document.getElementById( "qunit-fixture" ).innerHTML = "baz"; - } ); - - QUnit.test( "disabled", function( assert ) { - assert.fixtureEquals( { - tagName: "div", - attributes: { id: "qunit-fixture" }, - content: "baz" - } ); - assert.equal( values.length, 1, "proper sequence" ); - } ); - - QUnit.test( "user-specified DOM node", function( assert ) { - assert.fixtureEquals( { - tagName: "span", - attributes: { - id: "qunit-fixture", - "data-baz": "huzzah!" - }, - content: "" - } ); - assert.equal( values.length, 0, "proper sequence" ); - } ); - } ); - - QUnit.module( "arguments", function( hooks ) { - var testArgs; - var todoArgs; - hooks.after( function( assert ) { - assert.strictEqual( testArgs, 1, "test() callback args" ); - assert.strictEqual( todoArgs, 1, "test.todo() callback args" ); - } ); - - QUnit.test( "test() callback", function( assert ) { - testArgs = arguments.length; - assert.true( true ); - } ); - QUnit.test.todo( "test.todo() callback", function( assert ) { - - // Captured and asserted later since todo() is expected to fail - todoArgs = arguments.length; - assert.true( false ); - } ); - } ); - - QUnit.module( "custom assertions" ); - - QUnit.assert.mod2 = function( value, expected, message ) { - var actual = value % 2; - this.pushResult( { - result: actual === expected, - actual: actual, - expected: expected, - message: message - } ); - }; - - QUnit.assert.testForPush = function( value, expected, message ) { - this.push( true, value, expected, message, false ); - }; - - QUnit.test( "mod2", function( assert ) { - assert.mod2( 2, 0, "2 % 2 == 0" ); - assert.mod2( 3, 1, "3 % 2 == 1" ); - } ); - - QUnit.test( "testForPush", function( assert ) { - QUnit.log( function( detail ) { - if ( detail.message === "should be call pushResult" ) { - /* eslint-disable qunit/no-conditional-assertions */ - assert.equal( detail.result, true ); - assert.equal( detail.actual, 1 ); - assert.equal( detail.expected, 1 ); - assert.equal( detail.message, "should be call pushResult" ); - assert.equal( detail.negative, false ); - /* eslint-enable */ - } - } ); - assert.testForPush( 1, 1, "should be call pushResult" ); - } ); - - QUnit.module( "aliases" ); - - [ "todo", "skip", "only" ].forEach( function( flavor ) { - QUnit.test( "test." + flavor, function( assert ) { - assert.strictEqual( typeof QUnit.test[ flavor ], "function" ); - assert.strictEqual( QUnit[ flavor ], QUnit.test[ flavor ] ); - } ); - } ); - - QUnit.module( "test.skip", { - beforeEach: function( assert ) { - - // Skip test hooks for skipped tests - assert.true( false, "skipped function" ); - throw "Error"; - }, - afterEach: function( assert ) { - assert.true( false, "skipped function" ); - throw "Error"; - } - } ); - - QUnit.skip( "skip blocks are skipped", function( assert ) { - - // This test callback won't run, even with broken code - assert.expect( 1000 ); - throw "Error"; - } ); - - QUnit.skip( "skip without function" ); - - QUnit.module( "missing callbacks" ); - - QUnit.test( "QUnit.test without a callback logs a descriptive error", function( assert ) { - assert.throws( function() { - - // eslint-disable-next-line qunit/no-nested-tests - QUnit.test( "should throw an error" ); - }, /You must provide a callback to QUnit.test\("should throw an error"\)/ ); - } ); - - QUnit.test( "QUnit.todo without a callback logs a descriptive error", function( assert ) { - assert.throws( function() { - QUnit.todo( "should throw an error" ); - }, /You must provide a callback to QUnit.todo\("should throw an error"\)/ ); - } ); - - ( function() { - var previousTestAssert; - var firstTestName = "assertions after test finishes throws an error - part 1"; - - QUnit.module( "bad assertion context" ); - - QUnit.test( firstTestName, function( assert ) { - assert.expect( 0 ); - previousTestAssert = assert; - } ); - - QUnit.test( "assertions after test finishes throws an error - part 2", function( assert ) { - var error = "Assertion occurred after test finished.\n" + - "> Test: " + firstTestName + "\n" + - "> Message: message here\n"; - - assert.throws( function() { - previousTestAssert.true( true, "message here" ); - }, new Error( error ), "error contains test name and assertion message" ); - } ); - }() ); - -} ); +QUnit.module('test', function () { + QUnit.test('read and change assert.expect() count', function (assert) { + assert.expect(2); + assert.true(true); + var expected = assert.expect(); + assert.equal(expected, 2); + assert.expect(expected + 1); + assert.true(true); + }); + + (typeof document !== 'undefined' ? QUnit.module : QUnit.module.skip)('fixture management', function (hooks) { + /* global document */ + var failure = false; + var values = [ + // initial value (see unshift below) + // initial value (see unshift below) + 'ar', + '

        bc

        ', + undefined + ]; + var originalValue; + + hooks.before(function () { + originalValue = QUnit.config.fixture; + values.unshift(originalValue); + values.unshift(originalValue); + + var customFixtureNode = document.createElement('span'); + customFixtureNode.setAttribute('id', 'qunit-fixture'); + customFixtureNode.setAttribute('data-baz', 'huzzah!'); + values.push(customFixtureNode); + }); + + hooks.beforeEach(function (assert) { + assert.fixtureEquals = function fixtureEquals (options) { + var expectedTagName = options.tagName || 'div'; + var expectedAttributes = options.attributes || {}; + var expectedContent = options.content || ''; + + var element = document.getElementById('qunit-fixture'); + + this.pushResult({ + result: element.tagName === expectedTagName.toUpperCase(), + actual: element.tagName.toLowerCase(), + expected: expectedTagName, + message: 'tagName' + }); + + var actualAttributes = {}; + + for (var i = 0, l = element.attributes.length; i < l; i++) { + actualAttributes[element.attributes[i].name] = element.attributes[i].value; + } + + this.deepEqual(actualAttributes, expectedAttributes, 'attributes'); + this.strictEqual(element.innerHTML, expectedContent, 'contents'); + }; + + assert.hasFailingAssertions = function () { + for (var i = 0; i < this.test.assertions.length; i++) { + if (!this.test.assertions[i].result) { + return true; + } + } + + return false; + }; + }); + + // Set QUnit.config.fixture for the next test, propagating failures to recover the sequence + hooks.afterEach(function (assert) { + failure = failure || assert.hasFailingAssertions(); + if (failure) { + assert.true(false, 'prior failure'); + QUnit.config.fixture = originalValue; + } else { + QUnit.config.fixture = values.shift(); + } + }); + + QUnit.test('setup', function (assert) { + assert.equal(values.length, 6, 'proper sequence'); + + // setup for next test + document.getElementById('qunit-fixture').innerHTML = 'foo'; + }); + + QUnit.test('automatically reset', function (assert) { + assert.fixtureEquals({ + tagName: 'div', + attributes: { id: 'qunit-fixture' }, + content: originalValue.innerHTML + }); + assert.equal(values.length, 5, 'proper sequence'); + + // setup for next test + document.getElementById('qunit-fixture').setAttribute('data-foo', 'blah'); + }); + + QUnit.test('automatically reset after attribute value mutation', function (assert) { + assert.fixtureEquals({ + tagName: 'div', + attributes: { id: 'qunit-fixture' }, + content: originalValue.innerHTML + }); + assert.equal(values.length, 4, 'proper sequence'); + }); + + QUnit.test('user-specified string', function (assert) { + assert.fixtureEquals({ + tagName: 'div', + attributes: { id: 'qunit-fixture' }, + content: 'ar' + }); + assert.equal(values.length, 3, 'proper sequence'); + + // setup for next test + document.getElementById('qunit-fixture').setAttribute('data-foo', 'blah'); + }); + + QUnit.test('user-specified string automatically resets attribute value mutation', function (assert) { + assert.fixtureEquals({ + tagName: 'div', + attributes: { id: 'qunit-fixture' }, + content: '

        bc

        ' + }); + assert.equal(values.length, 2, 'proper sequence'); + + // setup for next test + document.getElementById('qunit-fixture').innerHTML = 'baz'; + }); + + QUnit.test('disabled', function (assert) { + assert.fixtureEquals({ + tagName: 'div', + attributes: { id: 'qunit-fixture' }, + content: 'baz' + }); + assert.equal(values.length, 1, 'proper sequence'); + }); + + QUnit.test('user-specified DOM node', function (assert) { + assert.fixtureEquals({ + tagName: 'span', + attributes: { + id: 'qunit-fixture', + 'data-baz': 'huzzah!' + }, + content: '' + }); + assert.equal(values.length, 0, 'proper sequence'); + }); + }); + + QUnit.module('arguments', function (hooks) { + var testArgs; + var todoArgs; + hooks.after(function (assert) { + assert.strictEqual(testArgs, 1, 'test() callback args'); + assert.strictEqual(todoArgs, 1, 'test.todo() callback args'); + }); + + QUnit.test('test() callback', function (assert) { + testArgs = arguments.length; + assert.true(true); + }); + QUnit.test.todo('test.todo() callback', function (assert) { + // Captured and asserted later since todo() is expected to fail + todoArgs = arguments.length; + assert.true(false); + }); + }); + + QUnit.module('custom assertions'); + + QUnit.assert.mod2 = function (value, expected, message) { + var actual = value % 2; + this.pushResult({ + result: actual === expected, + actual: actual, + expected: expected, + message: message + }); + }; + + QUnit.assert.testForPush = function (value, expected, message) { + this.push(true, value, expected, message, false); + }; + + QUnit.test('mod2', function (assert) { + assert.mod2(2, 0, '2 % 2 == 0'); + assert.mod2(3, 1, '3 % 2 == 1'); + }); + + QUnit.test('testForPush', function (assert) { + QUnit.log(function (detail) { + if (detail.message === 'should be call pushResult') { + /* eslint-disable qunit/no-conditional-assertions */ + assert.equal(detail.result, true); + assert.equal(detail.actual, 1); + assert.equal(detail.expected, 1); + assert.equal(detail.message, 'should be call pushResult'); + assert.equal(detail.negative, false); + /* eslint-enable */ + } + }); + assert.testForPush(1, 1, 'should be call pushResult'); + }); + + QUnit.module('aliases'); + + ['todo', 'skip', 'only'].forEach(function (flavor) { + QUnit.test('test.' + flavor, function (assert) { + assert.strictEqual(typeof QUnit.test[flavor], 'function'); + assert.strictEqual(QUnit[flavor], QUnit.test[flavor]); + }); + }); + + QUnit.module('test.skip', { + beforeEach: function (assert) { + // Skip test hooks for skipped tests + assert.true(false, 'skipped function'); + throw 'Error'; + }, + afterEach: function (assert) { + assert.true(false, 'skipped function'); + throw 'Error'; + } + }); + + QUnit.skip('skip blocks are skipped', function (assert) { + // This test callback won't run, even with broken code + assert.expect(1000); + throw 'Error'; + }); + + QUnit.skip('skip without function'); + + QUnit.module('missing callbacks'); + + QUnit.test('QUnit.test without a callback logs a descriptive error', function (assert) { + assert.throws(function () { + // eslint-disable-next-line qunit/no-nested-tests + QUnit.test('should throw an error'); + }, /You must provide a callback to QUnit.test\("should throw an error"\)/); + }); + + QUnit.test('QUnit.todo without a callback logs a descriptive error', function (assert) { + assert.throws(function () { + QUnit.todo('should throw an error'); + }, /You must provide a callback to QUnit.todo\("should throw an error"\)/); + }); + + (function () { + var previousTestAssert; + var firstTestName = 'assertions after test finishes throws an error - part 1'; + + QUnit.module('bad assertion context'); + + QUnit.test(firstTestName, function (assert) { + assert.expect(0); + previousTestAssert = assert; + }); + + QUnit.test('assertions after test finishes throws an error - part 2', function (assert) { + var error = 'Assertion occurred after test finished.\n' + + '> Test: ' + firstTestName + '\n' + + '> Message: message here\n'; + + assert.throws(function () { + previousTestAssert.true(true, 'message here'); + }, new Error(error), 'error contains test name and assertion message'); + }); + }()); +}); diff --git a/test/main/utilities.js b/test/main/utilities.js index 28a04edd7..5d9bf30bc 100644 --- a/test/main/utilities.js +++ b/test/main/utilities.js @@ -1,90 +1,89 @@ /* globals Map, Set, Symbol */ -QUnit.module( "utilities", function() { - - QUnit.module( "QUnit.objectType" ); - - function Foo() { } - - function validateObjectType( item, expected ) { - QUnit.test( "should properly detect " + expected, function( assert ) { - var actual = QUnit.objectType( item ); - assert.equal( actual, expected ); - } ); - } - - function maybeValidateObjectType( string, expected ) { - try { - var value = new Function( "return " + string + ";" )( ); - validateObjectType( value, expected ); - } catch ( e ) { - - // do nothing if parsing failed - if ( e.name === "SyntaxError" ) { - return; - } - - // do not hide other errors - throw e; - } - } - - validateObjectType( 1, "number" ); - validateObjectType( "", "string" ); - validateObjectType( "foo", "string" ); - validateObjectType( null, "null" ); - validateObjectType( true, "boolean" ); - validateObjectType( false, "boolean" ); - validateObjectType( [ ], "array" ); - validateObjectType( new Date( ), "date" ); - validateObjectType( /foo/, "regexp" ); - validateObjectType( NaN, "nan" ); - validateObjectType( undefined, "undefined" ); - validateObjectType( { }, "object" ); - validateObjectType( new Foo( ), "object" ); - - if ( typeof Symbol === "function" ) { - validateObjectType( Symbol( "HI!" ), "symbol" ); - } - - // Support IE 11: Skip on IE11's non-compliant implementation - if ( typeof Map === "function" && ( new Map ).toString() !== "[object Object]" ) { - validateObjectType( new Map( ), "map" ); - } - - // Support IE 11: Skip on IE11's non-compliant implementation - if ( typeof Set === "function" && ( new Set ).toString() !== "[object Object]" ) { - validateObjectType( new Set( ), "set" ); - } - - maybeValidateObjectType( "async function() { }", "function" ); - maybeValidateObjectType( "async () => { }", "function" ); - maybeValidateObjectType( "() => { }", "function" ); - maybeValidateObjectType( "function* { }", "function" ); - - QUnit.module( "QUnit.extend" ); - - QUnit.test( "appends to object", function( assert ) { - var base = { foo: 1 }; - var copy = QUnit.extend( base, { bar: 2 } ); - - assert.deepEqual( base, { foo: 1, bar: 2 } ); - assert.equal( copy, base, "returns mutated object" ); - } ); - - QUnit.test( "overwrites duplicate fields", function( assert ) { - var base = { foo: 1 }; - var copy = QUnit.extend( base, { foo: 2 } ); - - assert.deepEqual( base, { foo: 2 } ); - assert.equal( copy, base, "returns mutated object" ); - } ); - - QUnit.test( "removes undefined fields", function( assert ) { - var base = { foo: 1, bar: 2 }; - var copy = QUnit.extend( base, { bar: undefined } ); - - assert.false( "bar" in base, "Values specified as `undefined` are removed" ); - assert.equal( copy, base, "returns mutated object" ); - } ); -} ); +QUnit.module('utilities', function () { + QUnit.module('QUnit.objectType'); + + function Foo () { } + + function validateObjectType (item, expected) { + QUnit.test('should properly detect ' + expected, function (assert) { + var actual = QUnit.objectType(item); + assert.equal(actual, expected); + }); + } + + function maybeValidateObjectType (string, expected) { + try { + // eslint-disable-next-line no-new-func + var value = new Function('return ' + string + ';')(); + validateObjectType(value, expected); + } catch (e) { + // do nothing if parsing failed + if (e.name === 'SyntaxError') { + return; + } + + // do not hide other errors + throw e; + } + } + + validateObjectType(1, 'number'); + validateObjectType('', 'string'); + validateObjectType('foo', 'string'); + validateObjectType(null, 'null'); + validateObjectType(true, 'boolean'); + validateObjectType(false, 'boolean'); + validateObjectType([], 'array'); + validateObjectType(new Date(), 'date'); + validateObjectType(/foo/, 'regexp'); + validateObjectType(NaN, 'nan'); + validateObjectType(undefined, 'undefined'); + validateObjectType({ }, 'object'); + validateObjectType(new Foo(), 'object'); + + if (typeof Symbol === 'function') { + validateObjectType(Symbol('HI!'), 'symbol'); + } + + // Support IE 11: Skip on IE11's non-compliant implementation + if (typeof Map === 'function' && (new Map()).toString() !== '[object Object]') { + validateObjectType(new Map(), 'map'); + } + + // Support IE 11: Skip on IE11's non-compliant implementation + if (typeof Set === 'function' && (new Set()).toString() !== '[object Object]') { + validateObjectType(new Set(), 'set'); + } + + maybeValidateObjectType('async function() { }', 'function'); + maybeValidateObjectType('async () => { }', 'function'); + maybeValidateObjectType('() => { }', 'function'); + maybeValidateObjectType('function* { }', 'function'); + + QUnit.module('QUnit.extend'); + + QUnit.test('appends to object', function (assert) { + var base = { foo: 1 }; + var copy = QUnit.extend(base, { bar: 2 }); + + assert.deepEqual(base, { foo: 1, bar: 2 }); + assert.equal(copy, base, 'returns mutated object'); + }); + + QUnit.test('overwrites duplicate fields', function (assert) { + var base = { foo: 1 }; + var copy = QUnit.extend(base, { foo: 2 }); + + assert.deepEqual(base, { foo: 2 }); + assert.equal(copy, base, 'returns mutated object'); + }); + + QUnit.test('removes undefined fields', function (assert) { + var base = { foo: 1, bar: 2 }; + var copy = QUnit.extend(base, { bar: undefined }); + + assert.false('bar' in base, 'Values specified as `undefined` are removed'); + assert.equal(copy, base, 'returns mutated object'); + }); +}); diff --git a/test/module-skip.html b/test/module-skip.html index 0573a3302..2783a150e 100644 --- a/test/module-skip.html +++ b/test/module-skip.html @@ -1,13 +1,13 @@ - - - QUnit module Test Suite - - - - - -
        - + + + module-skip + + + + + +
        + diff --git a/test/module-skip.js b/test/module-skip.js index 48954d9d5..0bb21b179 100644 --- a/test/module-skip.js +++ b/test/module-skip.js @@ -2,58 +2,58 @@ QUnit.config.reorder = false; var tests = {}; -QUnit.testDone( function( details ) { - tests[ details.testId ] = { - skipped: details.skipped, - todo: details.todo - }; -} ); - -QUnit.module( "Parent module", function( hooks ) { - hooks.after( function( assert ) { - assert.deepEqual( tests, { - "1d56e5b5": { - skipped: false, - todo: false - }, - "d40f1738": { - skipped: true, - todo: false - }, - "acdd0267": { - skipped: true, - todo: false - }, - "8b1c454f": { - skipped: true, - todo: false - } - } ); - } ); - - QUnit.module( "A normal module", function() { - QUnit.test( "normal test", function( assert ) { - assert.true( true, "this test should run" ); - } ); - } ); - - QUnit.module.skip( "This module will be skipped", function() { - QUnit.test( "test will be treated as a skipped test", function( assert ) { - assert.true( false, "this test should not run" ); - } ); - - QUnit.todo( "a todo test that should be skipped", function( assert ) { - assert.true( false, "this test should not run" ); - } ); - - QUnit.skip( "a normal skipped test", function( assert ) { - assert.true( false, "this test should not run" ); - } ); - } ); - - // We need a test after the above skip, since hooks.after() runs after the - // last non-skipped test, and we want to include events from the skipped test. - QUnit.test( "another test", function( assert ) { - assert.true( true, "this test should run" ); - } ); -} ); +QUnit.testDone(function (details) { + tests[details.testId] = { + skipped: details.skipped, + todo: details.todo + }; +}); + +QUnit.module('Parent module', function (hooks) { + hooks.after(function (assert) { + assert.deepEqual(tests, { + '1d56e5b5': { + skipped: false, + todo: false + }, + d40f1738: { + skipped: true, + todo: false + }, + acdd0267: { + skipped: true, + todo: false + }, + '8b1c454f': { + skipped: true, + todo: false + } + }); + }); + + QUnit.module('A normal module', function () { + QUnit.test('normal test', function (assert) { + assert.true(true, 'this test should run'); + }); + }); + + QUnit.module.skip('This module will be skipped', function () { + QUnit.test('test will be treated as a skipped test', function (assert) { + assert.true(false, 'this test should not run'); + }); + + QUnit.todo('a todo test that should be skipped', function (assert) { + assert.true(false, 'this test should not run'); + }); + + QUnit.skip('a normal skipped test', function (assert) { + assert.true(false, 'this test should not run'); + }); + }); + + // We need a test after the above skip, since hooks.after() runs after the + // last non-skipped test, and we want to include events from the skipped test. + QUnit.test('another test', function (assert) { + assert.true(true, 'this test should run'); + }); +}); diff --git a/test/module-todo.html b/test/module-todo.html index 4730ab9d7..253f5fc78 100644 --- a/test/module-todo.html +++ b/test/module-todo.html @@ -1,13 +1,13 @@ - - - QUnit module Test Suite - - - - - -
        - + + + module-todo + + + + + +
        + diff --git a/test/module-todo.js b/test/module-todo.js index 4c3cad94d..8412ca3f4 100644 --- a/test/module-todo.js +++ b/test/module-todo.js @@ -2,57 +2,57 @@ QUnit.config.reorder = false; var tests = {}; -QUnit.testDone( function( details ) { - tests[ details.testId ] = { - skipped: details.skipped, - todo: details.todo - }; -} ); - -QUnit.module( "parent module", function( hooks ) { - hooks.after( function( assert ) { - assert.deepEqual( tests, { - "efa6d5f5": { - skipped: false, - todo: false - }, - "d394a378": { - skipped: false, - todo: true - }, - "ffd66a5e": { - skipped: true, - todo: false - }, - "951df7ad": { - skipped: false, - todo: true - } - } ); - } ); - - QUnit.module( "a normal module", function() { - QUnit.test( "normal test", function( assert ) { - assert.true( true, "this test should run" ); - } ); - } ); - - QUnit.module.todo( "a todo module", function() { - QUnit.todo( "a todo test", function( assert ) { - assert.true( false, "not implemented yet" ); - } ); - - QUnit.skip( "a skipped test that will be left intact", function( assert ) { - assert.true( false, "not implemented yet" ); - } ); - - QUnit.test( "a normal test that will be treated as a todo", function( assert ) { - assert.true( false, "not implemented yet" ); - } ); - } ); - - // We need one more test to ensure hooks.after() runs after the above has finished. - QUnit.test( "another test", function( assert ) { - assert.true( true, "this test should run" ); - } ); -} ); +QUnit.testDone(function (details) { + tests[details.testId] = { + skipped: details.skipped, + todo: details.todo + }; +}); + +QUnit.module('parent module', function (hooks) { + hooks.after(function (assert) { + assert.deepEqual(tests, { + efa6d5f5: { + skipped: false, + todo: false + }, + d394a378: { + skipped: false, + todo: true + }, + ffd66a5e: { + skipped: true, + todo: false + }, + '951df7ad': { + skipped: false, + todo: true + } + }); + }); + + QUnit.module('a normal module', function () { + QUnit.test('normal test', function (assert) { + assert.true(true, 'this test should run'); + }); + }); + + QUnit.module.todo('a todo module', function () { + QUnit.todo('a todo test', function (assert) { + assert.true(false, 'not implemented yet'); + }); + + QUnit.skip('a skipped test that will be left intact', function (assert) { + assert.true(false, 'not implemented yet'); + }); + + QUnit.test('a normal test that will be treated as a todo', function (assert) { + assert.true(false, 'not implemented yet'); + }); + }); + + // We need one more test to ensure hooks.after() runs after the above has finished. + QUnit.test('another test', function (assert) { + assert.true(true, 'this test should run'); + }); +}); diff --git a/test/mozjs.js b/test/mozjs.js index 8d498668c..525962bf1 100644 --- a/test/mozjs.js +++ b/test/mozjs.js @@ -1,56 +1,54 @@ -/* eslint-env es6 */ -/* eslint-disable lines-around-comment */ -/* global loadRelativeToScript */ +/* global loadRelativeToScript, print */ -loadRelativeToScript( "../qunit/qunit.js" ); +loadRelativeToScript('../qunit/qunit.js'); -QUnit.on( "runStart", () => { - print( "Running tests..." ); -} ); -QUnit.on( "testEnd", ( testEnd ) => { - if ( testEnd.status === "todo" ) { - return; - } - testEnd.errors.forEach( ( assertion ) => { - print( `\ntest: ${testEnd.name}\n` + - `module: ${testEnd.suiteName}\n` + - `message: ${assertion.message}\n${assertion.stack || ""}` ); - } ); -} ); -QUnit.on( "runEnd", ( suiteEnd ) => { - const stats = suiteEnd.testCounts; - if ( suiteEnd.status === "failed" ) { - print( `${stats.total} tests in ${suiteEnd.runtime}ms` + - `, ${stats.passed} passed` + - `, ${stats.failed} failed` + - `, ${stats.skipped} skipped` + - `, ${stats.todo} todo` + - "." - ); +QUnit.on('runStart', () => { + print('Running tests...'); +}); +QUnit.on('testEnd', (testEnd) => { + if (testEnd.status === 'todo') { + return; + } + testEnd.errors.forEach((assertion) => { + print(`\ntest: ${testEnd.name}\n` + + `module: ${testEnd.suiteName}\n` + + `message: ${assertion.message}\n${assertion.stack || ''}`); + }); +}); +QUnit.on('runEnd', (suiteEnd) => { + const stats = suiteEnd.testCounts; + if (suiteEnd.status === 'failed') { + print(`${stats.total} tests in ${suiteEnd.runtime}ms` + + `, ${stats.passed} passed` + + `, ${stats.failed} failed` + + `, ${stats.skipped} skipped` + + `, ${stats.todo} todo` + + '.' + ); - // There is no built-in function for sending a non-zero exit code, - // so throw an uncaught error to make this happen. - throw new Error( "Test run has failures." ); - } else { - print( `${stats.total} tests in ${suiteEnd.runtime}ms, all passed.` ); - } -} ); + // There is no built-in function for sending a non-zero exit code, + // so throw an uncaught error to make this happen. + throw new Error('Test run has failures.'); + } else { + print(`${stats.total} tests in ${suiteEnd.runtime}ms, all passed.`); + } +}); // Sync with test/index.html -loadRelativeToScript( "../test/main/assert.js" ); -loadRelativeToScript( "../test/main/assert-step.js" ); +loadRelativeToScript('../test/main/assert.js'); +loadRelativeToScript('../test/main/assert-step.js'); // loadRelativeToScript( "../test/main/assert-timeout.js" ); // Requires setTimeout // loadRelativeToScript( "../test/main/async.js" ); // Requires setTimeout -loadRelativeToScript( "../test/main/deepEqual.js" ); -loadRelativeToScript( "../test/main/dump.js" ); -loadRelativeToScript( "../test/main/each.js" ); +loadRelativeToScript('../test/main/deepEqual.js'); +loadRelativeToScript('../test/main/dump.js'); +loadRelativeToScript('../test/main/each.js'); // loadRelativeToScript( "../test/main/modules.js" ); // Requires setTimeout -loadRelativeToScript( "../test/main/onError.js" ); -loadRelativeToScript( "../test/main/onUncaughtException.js" ); -loadRelativeToScript( "../test/main/promise.js" ); -loadRelativeToScript( "../test/main/setTimeout.js" ); -loadRelativeToScript( "../test/main/stack.js" ); -loadRelativeToScript( "../test/main/test.js" ); -loadRelativeToScript( "../test/main/utilities.js" ); +loadRelativeToScript('../test/main/onError.js'); +loadRelativeToScript('../test/main/onUncaughtException.js'); +loadRelativeToScript('../test/main/promise.js'); +loadRelativeToScript('../test/main/setTimeout.js'); +loadRelativeToScript('../test/main/stack.js'); +loadRelativeToScript('../test/main/test.js'); +loadRelativeToScript('../test/main/utilities.js'); QUnit.start(); diff --git a/test/node/.eslintrc.json b/test/node/.eslintrc.json deleted file mode 100644 index f76749ed0..000000000 --- a/test/node/.eslintrc.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "env": { - "node": true, - "browser": false - } -} diff --git a/test/node/storage-1.js b/test/node/storage-1.js index 621e27798..ed58679dd 100644 --- a/test/node/storage-1.js +++ b/test/node/storage-1.js @@ -1,28 +1,28 @@ -var lastTest = "", - mockStorage = require( "./storage.js" ); +var lastTest = ''; +var mockStorage = require('./storage.js'); -mockStorage.setItem( "qunit-test-Storage #1-Should be run first", 1 ); +mockStorage.setItem('qunit-test-Storage #1-Should be run first', 1); QUnit.config.storage = mockStorage; -QUnit.module( "Storage #1", function() { - QUnit.test( "Passing test", function( assert ) { - assert.strictEqual( lastTest, "Should be run first", "test is run second" ); - mockStorage.setItem( "qunit-test-Storage #1-Passing test", true ); - } ); +QUnit.module('Storage #1', function () { + QUnit.test('Passing test', function (assert) { + assert.strictEqual(lastTest, 'Should be run first', 'test is run second'); + mockStorage.setItem('qunit-test-Storage #1-Passing test', true); + }); - QUnit.test( "Removes passing tests from storage", function( assert ) { - assert.strictEqual( mockStorage.getItem( "qunit-test-Storage #1-Passing test" ), null ); - } ); + QUnit.test('Removes passing tests from storage', function (assert) { + assert.strictEqual(mockStorage.getItem('qunit-test-Storage #1-Passing test'), null); + }); - // Verifies test reordering by storage values - QUnit.test( "Should be run first", function( assert ) { - assert.strictEqual( lastTest, "" ); - lastTest = "Should be run first"; - } ); -} ); + // Verifies test reordering by storage values + QUnit.test('Should be run first', function (assert) { + assert.strictEqual(lastTest, ''); + lastTest = 'Should be run first'; + }); +}); -QUnit.done( function() { - mockStorage.setItem( "qunit-test-should-remove", true ); - mockStorage.setItem( "should-not-remove", true ); - mockStorage.setItem( "qunit-test-should-also-remove", true ); -} ); +QUnit.done(function () { + mockStorage.setItem('qunit-test-should-remove', true); + mockStorage.setItem('should-not-remove', true); + mockStorage.setItem('qunit-test-should-also-remove', true); +}); diff --git a/test/node/storage-2.js b/test/node/storage-2.js index 759c72a03..56997c042 100644 --- a/test/node/storage-2.js +++ b/test/node/storage-2.js @@ -1,21 +1,21 @@ -var mockStorage = require( "./storage.js" ); +var mockStorage = require('./storage.js'); -QUnit.module( "Storage #2", function() { - QUnit.test( "clears all qunit-test-* items after a successful run", function( assert ) { - assert.strictEqual( - mockStorage.getItem( "should-not-remove" ), - true, - "state was persisted from last test" - ); - assert.strictEqual( - mockStorage.getItem( "qunit-test-should-remove" ), - null, - "removed first test" - ); - assert.strictEqual( - mockStorage.getItem( "qunit-test-should-also-remove" ), - null, - "removed second test" - ); - } ); -} ); +QUnit.module('Storage #2', function () { + QUnit.test('clears all qunit-test-* items after a successful run', function (assert) { + assert.strictEqual( + mockStorage.getItem('should-not-remove'), + true, + 'state was persisted from last test' + ); + assert.strictEqual( + mockStorage.getItem('qunit-test-should-remove'), + null, + 'removed first test' + ); + assert.strictEqual( + mockStorage.getItem('qunit-test-should-also-remove'), + null, + 'removed second test' + ); + }); +}); diff --git a/test/node/storage.js b/test/node/storage.js index b388bd0e9..3771c87c6 100644 --- a/test/node/storage.js +++ b/test/node/storage.js @@ -1,20 +1,20 @@ module.exports = { - _store: Object.create( null ), - setItem: function( key, value ) { - this._store[ key ] = value; - }, - getItem: function( key ) { - return key in this._store ? this._store[ key ] : null; - }, - removeItem: function( key ) { - if ( key in this._store ) { - delete this._store[ key ]; - } - }, - key: function( i ) { - return Object.keys( this._store )[ i ]; - }, - get length() { - return Object.keys( this._store ).length; - } + _store: Object.create(null), + setItem: function (key, value) { + this._store[key] = value; + }, + getItem: function (key) { + return key in this._store ? this._store[key] : null; + }, + removeItem: function (key) { + if (key in this._store) { + delete this._store[key]; + } + }, + key: function (i) { + return Object.keys(this._store)[i]; + }, + get length () { + return Object.keys(this._store).length; + } }; diff --git a/test/only-each.html b/test/only-each.html index 4a2c625cb..1f4d7df84 100644 --- a/test/only-each.html +++ b/test/only-each.html @@ -1,14 +1,14 @@ - - only-each - - - + + only-each + + + -
        -
        test markup
        +
        +
        test markup
        diff --git a/test/only-each.js b/test/only-each.js index 63460a597..bde373ade 100644 --- a/test/only-each.js +++ b/test/only-each.js @@ -1,9 +1,9 @@ -QUnit.module.only( "test.each.only", function() { - QUnit.test( "don't run", function( assert ) { assert.true( false ); } ); - QUnit.test.only.each( "test.each.only", [ [ 1, 2, 3 ], [ 1, 1, 2 ] ], function( assert, data ) { - assert.strictEqual( data[ 0 ] + data[ 1 ], data[ 2 ] ); - } ); - QUnit.test.only.each( "test.each.only 1D", [ 1, [], "some" ], function( assert, value ) { - assert.true( Boolean( value ) ); - } ); -} ); +QUnit.module.only('test.each.only', function () { + QUnit.test("don't run", function (assert) { assert.true(false); }); + QUnit.test.only.each('test.each.only', [[1, 2, 3], [1, 1, 2]], function (assert, data) { + assert.strictEqual(data[0] + data[1], data[2]); + }); + QUnit.test.only.each('test.each.only 1D', [1, [], 'some'], function (assert, value) { + assert.true(Boolean(value)); + }); +}); diff --git a/test/overload.html b/test/overload.html index bbc25f9cd..91de445ec 100644 --- a/test/overload.html +++ b/test/overload.html @@ -1,24 +1,24 @@ - - - QUnit Only Test Suite - - - + - - - -
        - + return false; + }; + + + + +
        + diff --git a/test/performance-mark.html b/test/performance-mark.html index 9f4bed7f6..2baa07727 100644 --- a/test/performance-mark.html +++ b/test/performance-mark.html @@ -2,7 +2,7 @@ - QUnit Performance Test Suite + performance-mark diff --git a/test/performance-mark.js b/test/performance-mark.js index 22905a329..d642579c0 100644 --- a/test/performance-mark.js +++ b/test/performance-mark.js @@ -1,7 +1,10 @@ -QUnit.module( "urlParams performance mark module", function() { - QUnit.test( "shouldn't fail if performance marks are cleared", function( assert ) { - performance.clearMarks(); +/* eslint-env browser */ +QUnit.module('urlParams performance mark module', function () { + QUnit.test("shouldn't fail if performance marks are cleared", function (assert) { + // Optional feature available in IE10+ and Safari 10+ + // eslint-disable-next-line compat/compat + performance.clearMarks(); - assert.true( true ); - } ); -} ); + assert.true(true); + }); +}); diff --git a/test/preconfigured.html b/test/preconfigured.html index e73c14ab9..1766aba60 100644 --- a/test/preconfigured.html +++ b/test/preconfigured.html @@ -1,21 +1,21 @@ - - QUnit Preconfigured Test Suite - - - - + + preconfigured + + + + -
        +
        diff --git a/test/preconfigured.js b/test/preconfigured.js index d95caa05b..096d8ff1c 100644 --- a/test/preconfigured.js +++ b/test/preconfigured.js @@ -1,21 +1,21 @@ -window.addEventListener( "load", function() { +/* eslint-env browser */ +window.addEventListener('load', function () { + // make sure QUnit has started if autostart would be true + setTimeout(function () { + QUnit.module('QUnit.preconfigured.asyncTests'); + QUnit.test('QUnit.config should have an expected default', function (assert) { + assert.true(QUnit.config.scrolltop, 'The scrolltop default is true'); + }); - // make sure QUnit has started if autostart would be true - setTimeout( function() { - QUnit.module( "QUnit.preconfigured.asyncTests" ); - QUnit.test( "QUnit.config should have an expected default", function( assert ) { - assert.true( QUnit.config.scrolltop, "The scrolltop default is true" ); - } ); + QUnit.test('Qunit.config.reorder default was overwritten', function (assert) { + assert.false(QUnit.config.reorder, 'reorder was overwritten'); + }); - QUnit.test( "Qunit.config.reorder default was overwritten", function( assert ) { - assert.false( QUnit.config.reorder, "reorder was overwritten" ); - } ); + QUnit.start(); + }, 100); +}); - QUnit.start(); - }, 100 ); -} ); - -QUnit.module( "QUnit.preconfigured" ); -QUnit.test( "Autostart is false", function( assert ) { - assert.false( QUnit.config.autostart, "The global autostart setting is applied" ); -} ); +QUnit.module('QUnit.preconfigured'); +QUnit.test('Autostart is false', function (assert) { + assert.false(QUnit.config.autostart, 'The global autostart setting is applied'); +}); diff --git a/test/reorder.html b/test/reorder.html index 760a765bc..205835c99 100644 --- a/test/reorder.html +++ b/test/reorder.html @@ -1,13 +1,13 @@ - - QUnit Reordering Functionality Works in Browser - - - + + reorder + + + -
        +
        diff --git a/test/reorder.js b/test/reorder.js index e75b9b7c3..0a9f94143 100644 --- a/test/reorder.js +++ b/test/reorder.js @@ -1,27 +1,28 @@ -window.sessionStorage.setItem( "qunit-test-Reorder-Second", 1 ); -window.sessionStorage.setItem( "qunit-test-Reorder-Third", 1 ); +/* eslint-env browser */ +window.sessionStorage.setItem('qunit-test-Reorder-Second', 1); +window.sessionStorage.setItem('qunit-test-Reorder-Third', 1); -var lastTest = ""; +var lastTest = ''; -QUnit.module( "Reorder" ); +QUnit.module('Reorder'); -QUnit.test( "First", function( assert ) { - assert.strictEqual( lastTest, "Third" ); - lastTest = "First"; -} ); +QUnit.test('First', function (assert) { + assert.strictEqual(lastTest, 'Third'); + lastTest = 'First'; +}); -QUnit.test( "Second", function( assert ) { - assert.strictEqual( lastTest, "" ); - lastTest = "Second"; +QUnit.test('Second', function (assert) { + assert.strictEqual(lastTest, ''); + lastTest = 'Second'; - // For some reason PhantomJS mutates config.reorder - QUnit.config.reorder = true; + // For some reason PhantomJS mutates config.reorder + QUnit.config.reorder = true; - // This test is "high priority" so it should execute before test "First" - // even though it is added to the queue late - // eslint-disable-next-line qunit/no-nested-tests - QUnit.test( "Third", function( assert ) { - assert.strictEqual( lastTest, "Second" ); - lastTest = "Third"; - } ); -} ); + // This test is "high priority" so it should execute before test "First" + // even though it is added to the queue late + // eslint-disable-next-line qunit/no-nested-tests + QUnit.test('Third', function (assert) { + assert.strictEqual(lastTest, 'Second'); + lastTest = 'Third'; + }); +}); diff --git a/test/reorderError1.html b/test/reorderError1.html index df99ce60e..642ac3f34 100644 --- a/test/reorderError1.html +++ b/test/reorderError1.html @@ -1,55 +1,55 @@ - - QUnit - Asserts it does not skip tests after reordering - - - + - // Delete QUnit global so we can reload it - delete window.QUnit; - - - - - + + + -
        +
        diff --git a/test/reorderError1.js b/test/reorderError1.js index 7b2fbacf5..f9020d282 100644 --- a/test/reorderError1.js +++ b/test/reorderError1.js @@ -1,7 +1,8 @@ -QUnit.module( "Test call count - first case" ); -QUnit[ window.sessionStorage ? "test" : "skip" ]( - "does not skip tests after reordering", - function( assert ) { - assert.equal( window.totalCount, 3 ); - } +/* eslint-env browser */ +QUnit.module('Test call count - first case'); +QUnit[window.sessionStorage ? 'test' : 'skip']( + 'does not skip tests after reordering', + function (assert) { + assert.equal(window.totalCount, 3); + } ); diff --git a/test/reorderError2.html b/test/reorderError2.html index a5ca6fd95..45e1e0e98 100644 --- a/test/reorderError2.html +++ b/test/reorderError2.html @@ -1,52 +1,52 @@ - - QUnit - Asserts it does not skip tests after reordering - - - + - // Delete QUnit global so we can reload it - delete window.QUnit; - - - - - + + + -
        +
        diff --git a/test/reorderError2.js b/test/reorderError2.js index e6977f900..d56a85c0c 100644 --- a/test/reorderError2.js +++ b/test/reorderError2.js @@ -1,7 +1,8 @@ -QUnit.module( "Test call count - second case" ); -QUnit[ window.sessionStorage ? "test" : "skip" ]( - "does not skip tests after reordering", - function( assert ) { - assert.equal( window.totalCount, 2 ); - } +/* eslint-env browser */ +QUnit.module('Test call count - second case'); +QUnit[window.sessionStorage ? 'test' : 'skip']( + 'does not skip tests after reordering', + function (assert) { + assert.equal(window.totalCount, 2); + } ); diff --git a/test/reporter-html/config-testId.html b/test/reporter-html/config-testId.html index 0bf659c62..30a7342df 100644 --- a/test/reporter-html/config-testId.html +++ b/test/reporter-html/config-testId.html @@ -1,13 +1,13 @@ - - QUnit - + + QUnit + -
        - - +
        + + diff --git a/test/reporter-html/config-testId.js b/test/reporter-html/config-testId.js index 417a70d8d..c7e1c4286 100644 --- a/test/reporter-html/config-testId.js +++ b/test/reporter-html/config-testId.js @@ -1,12 +1,13 @@ -QUnit.config.testId = [ "2e48c6fa", "9ccf6855" ]; +/* eslint-env browser */ +QUnit.config.testId = ['2e48c6fa', '9ccf6855']; -QUnit.test( "Check for changed header after running filtered test", function( assert ) { - var html = document.getElementById( "qunit-filteredTest" ).innerHTML; - var result = /Rerunning selected tests: 2e48c6fa, 9ccf6855/.test( html ); - assert.true( result ); -} ); +QUnit.test('Check for changed header after running filtered test', function (assert) { + var html = document.getElementById('qunit-filteredTest').innerHTML; + var result = /Rerunning selected tests: 2e48c6fa, 9ccf6855/.test(html); + assert.true(result); +}); -QUnit.test( "Check for link to clear filter", function( assert ) { - var html = document.getElementById( "qunit-clearFilter" ).innerHTML; - assert.strictEqual( html, "Run all tests" ); -} ); +QUnit.test('Check for link to clear filter', function (assert) { + var html = document.getElementById('qunit-clearFilter').innerHTML; + assert.strictEqual(html, 'Run all tests'); +}); diff --git a/test/reporter-html/diff.js b/test/reporter-html/diff.js index 996c620a5..af1a59c75 100644 --- a/test/reporter-html/diff.js +++ b/test/reporter-html/diff.js @@ -1,192 +1,191 @@ -QUnit.module( "diff" ); - -QUnit.test( "throws if arguments are not strings", function( assert ) { - assert.throws( function() { QUnit.diff( {}, "" ); } ); - assert.throws( function() { QUnit.diff( "", {} ); } ); -} ); - -QUnit.test( "different strings", function( assert ) { - var a = "abcd"; - var b = "xkcd"; - - assert.equal( - QUnit.diff( a, b ), - "abxkcd", - "QUnit.diff( 'abcd', 'xkcd' )" - ); - assert.equal( - QUnit.diff( b, a ), - "xkabcd", - "QUnit.diff( 'xkcd', 'abcd' )" - ); - - assert.equal( - QUnit.diff( a, "" ), - "abcd", - "QUnit.diff( 'abcd', '' )" - ); - assert.equal( - QUnit.diff( "", a ), - "abcd", - "QUnit.diff( '', 'abcd' )" - ); - - assert.equal( - QUnit.diff( "false", "true" ), - "falstrue", - "QUnit.diff( 'false', 'true' )" - ); - - assert.equal( - QUnit.diff( "true", "false" ), - "trufalse", - "QUnit.diff( 'true', 'false' )" - ); - - assert.equal( - QUnit.diff( a, "acd" ), - "a<b/>cd", - "QUnit.diff( 'abcd', 'acd' )" - ); - - assert.equal( - QUnit.diff( a, "a<b/>cd" ), - "a&lt;b/&gt;cd", - "QUnit.diff( 'abcd', 'a<b/>cd' )" - ); -} ); - -QUnit.test( "additions", function( assert ) { - var a = "do less!"; - var b = "do less, write more!"; - - assert.equal( - QUnit.diff( a, b ), - "do less, write more!", - "QUnit.diff( 'do less!', 'do less, write more!' )" - ); -} ); - -QUnit.test( "removals", function( assert ) { - var a = "do less, write more!"; - var b = "do less!"; - - assert.equal( - QUnit.diff( a, b ), - "do less, write more!", - "QUnit.diff( 'do less, write more!', 'do less!' )" - ); -} ); - -QUnit.test( "equality shifts", function( assert ) { - - // ABAC -> ABAC - var a = "AC"; - var b = "ABAC"; - - assert.equal( - QUnit.diff( a, b ), "ABAC" - ); -} ); - -QUnit.test( "test with line mode on long strings", function( assert ) { - var a = "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!"; - - var b = "QUnit is a very powerful, easy-to-use JavaScript unit testing framework. " + - "It's used by the jQuery Core, jQuery UI and jQuery Mobile projects and is " + - "capable of testing any JavaScript code, including itself!" + - "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. "; - - assert.equal( - QUnit.diff( a, b ), - "QUnit is a very powerful, easy-to-use " + - "JavaScript unit testing framework. It's used by the jQuery " + - "Core, jQuery UI and jQuery Mobile projects and is capable of" + - " testing any generic JavaScript code, including " + - "itself!" + - "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.test( "simplified diffs", function( assert ) { - assert.equal( - QUnit.diff( "BXYD", "AXYC" ), - "BXYDAXYC", - "return is not BAXYDC" - ); - - assert.equal( - QUnit.diff( "XD", "AXC" ), - "XDAXC", - "return is not AXDC" - ); - - assert.equal( - QUnit.diff( "A BC ", " B" ), - "A BC ", - "Swap insertions for deletions if diff is reversed" - ); - - assert.equal( - QUnit.diff( "abcxxx", "xxxdef" ), - "abcxxxdef" - ); - - assert.equal( - QUnit.diff( "xxxabc", "defxxx" ), - "defxxxabc" - ); -} ); - -QUnit.test( "equal values", function( assert ) { - assert.equal( - QUnit.diff( "abc", "abc" ), - "abc" - ); - - assert.equal( - QUnit.diff( "", "" ), - "" - ); -} ); - -QUnit.test( "Edge cases", function( assert ) { - assert.throws( - function() { - QUnit.diff( "abc", null ); - }, - /Error: Null input. \(DiffMain\)/ ); - - // this hits several hard-to-reach cases for the sake of code coverage - var X = repeat( "x", 100 ); - var Y = repeat( "y", 100 ); - assert.equal( - QUnit.diff( - "A\nB\n" + X + "\nD", - "A\nCCC\nB\nCCC\n" + Y + "\nD" ), - "A\nCCC\nB\n" + - "" + X + "CCC\n" + Y + "" + - "\nD" ); - - function repeat( substr, n ) { - if ( substr.repeat ) { - return substr.repeat( n ); - } - - // polyfill for IE - var r = ""; - for ( var i = 0; i < n; i++ ) { - r += substr; - } - return r; - } -} ); +QUnit.module('diff'); + +QUnit.test('throws if arguments are not strings', function (assert) { + assert.throws(function () { QUnit.diff({}, ''); }); + assert.throws(function () { QUnit.diff('', {}); }); +}); + +QUnit.test('different strings', function (assert) { + var a = 'abcd'; + var b = 'xkcd'; + + assert.equal( + QUnit.diff(a, b), + 'abxkcd', + "QUnit.diff( 'abcd', 'xkcd' )" + ); + assert.equal( + QUnit.diff(b, a), + 'xkabcd', + "QUnit.diff( 'xkcd', 'abcd' )" + ); + + assert.equal( + QUnit.diff(a, ''), + 'abcd', + "QUnit.diff( 'abcd', '' )" + ); + assert.equal( + QUnit.diff('', a), + 'abcd', + "QUnit.diff( '', 'abcd' )" + ); + + assert.equal( + QUnit.diff('false', 'true'), + 'falstrue', + "QUnit.diff( 'false', 'true' )" + ); + + assert.equal( + QUnit.diff('true', 'false'), + 'trufalse', + "QUnit.diff( 'true', 'false' )" + ); + + assert.equal( + QUnit.diff(a, 'acd'), + 'a<b/>cd', + "QUnit.diff( 'abcd', 'acd' )" + ); + + assert.equal( + QUnit.diff(a, 'a<b/>cd'), + 'a&lt;b/&gt;cd', + "QUnit.diff( 'abcd', 'a<b/>cd' )" + ); +}); + +QUnit.test('additions', function (assert) { + var a = 'do less!'; + var b = 'do less, write more!'; + + assert.equal( + QUnit.diff(a, b), + 'do less, write more!', + "QUnit.diff( 'do less!', 'do less, write more!' )" + ); +}); + +QUnit.test('removals', function (assert) { + var a = 'do less, write more!'; + var b = 'do less!'; + + assert.equal( + QUnit.diff(a, b), + 'do less, write more!', + "QUnit.diff( 'do less, write more!', 'do less!' )" + ); +}); + +QUnit.test('equality shifts', function (assert) { + // ABAC -> ABAC + var a = 'AC'; + var b = 'ABAC'; + + assert.equal( + QUnit.diff(a, b), 'ABAC' + ); +}); + +QUnit.test('test with line mode on long strings', function (assert) { + var a = '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!'; + + var b = 'QUnit is a very powerful, easy-to-use JavaScript unit testing framework. ' + + "It's used by the jQuery Core, jQuery UI and jQuery Mobile projects and is " + + 'capable of testing any JavaScript code, including itself!' + + '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. '; + + assert.equal( + QUnit.diff(a, b), + 'QUnit is a very powerful, easy-to-use ' + + 'JavaScript unit testing framework. It's used by the jQuery ' + + 'Core, jQuery UI and jQuery Mobile projects and is capable of' + + ' testing any generic JavaScript code, including ' + + 'itself!' + + '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.test('simplified diffs', function (assert) { + assert.equal( + QUnit.diff('BXYD', 'AXYC'), + 'BXYDAXYC', + 'return is not BAXYDC' + ); + + assert.equal( + QUnit.diff('XD', 'AXC'), + 'XDAXC', + 'return is not AXDC' + ); + + assert.equal( + QUnit.diff('A BC ', ' B'), + 'A BC ', + 'Swap insertions for deletions if diff is reversed' + ); + + assert.equal( + QUnit.diff('abcxxx', 'xxxdef'), + 'abcxxxdef' + ); + + assert.equal( + QUnit.diff('xxxabc', 'defxxx'), + 'defxxxabc' + ); +}); + +QUnit.test('equal values', function (assert) { + assert.equal( + QUnit.diff('abc', 'abc'), + 'abc' + ); + + assert.equal( + QUnit.diff('', ''), + '' + ); +}); + +QUnit.test('Edge cases', function (assert) { + assert.throws( + function () { + QUnit.diff('abc', null); + }, + /Error: Null input. \(DiffMain\)/); + + // this hits several hard-to-reach cases for the sake of code coverage + var X = repeat('x', 100); + var Y = repeat('y', 100); + assert.equal( + QUnit.diff( + 'A\nB\n' + X + '\nD', + 'A\nCCC\nB\nCCC\n' + Y + '\nD'), + 'A\nCCC\nB\n' + + '' + X + 'CCC\n' + Y + '' + + '\nD'); + + function repeat (substr, n) { + if (substr.repeat) { + return substr.repeat(n); + } + + // polyfill for IE + var r = ''; + for (var i = 0; i < n; i++) { + r += substr; + } + return r; + } +}); diff --git a/test/reporter-html/hidepassed.html b/test/reporter-html/hidepassed.html index 2592b64c7..d26b64601 100644 --- a/test/reporter-html/hidepassed.html +++ b/test/reporter-html/hidepassed.html @@ -1,13 +1,13 @@ - - QUnit - + + QUnit + -
        - - +
        + + diff --git a/test/reporter-html/hidepassed.js b/test/reporter-html/hidepassed.js index 880445894..d99aa66bc 100644 --- a/test/reporter-html/hidepassed.js +++ b/test/reporter-html/hidepassed.js @@ -1,33 +1,33 @@ -if ( !location.search ) { - location.replace( "?hidepassed=true" ); +/* eslint-env browser */ +if (!location.search) { + location.replace('?hidepassed=true'); } -QUnit.module( "hidepassed", function() { - QUnit.test( "passed 1", function( assert ) { - assert.true( true ); - } ); - QUnit.test( "passed 2", function( assert ) { - assert.true( true ); - } ); - QUnit.test( "passed 3", function( assert ) { - assert.true( true ); - } ); - QUnit.skip( "skipped 1", function( assert ) { - assert.true( false, "can't seem to get this working" ); - } ); - QUnit.test( "passed 4", function( assert ) { - - // We expect 1 rather than 0 because the current test is in progress, - // and in-progress tests are always rendered. - // It will become hidden once it passed - assert.strictEqual( document.getElementById( "qunit-tests" ).children.length, 1 ); - } ); - QUnit.test( "config parsed", function( assert ) { - assert.strictEqual( QUnit.config.hidepassed, "true" ); - } ); - QUnit.test( "interface", function( assert ) { - var node = document.getElementById( "qunit-urlconfig-hidepassed" ); - assert.strictEqual( node.nodeName, "INPUT" ); - assert.strictEqual( node.checked, true ); - } ); -} ); +QUnit.module('hidepassed', function () { + QUnit.test('passed 1', function (assert) { + assert.true(true); + }); + QUnit.test('passed 2', function (assert) { + assert.true(true); + }); + QUnit.test('passed 3', function (assert) { + assert.true(true); + }); + QUnit.skip('skipped 1', function (assert) { + assert.true(false, "can't seem to get this working"); + }); + QUnit.test('passed 4', function (assert) { + // We expect 1 rather than 0 because the current test is in progress, + // and in-progress tests are always rendered. + // It will become hidden once it passed + assert.strictEqual(document.getElementById('qunit-tests').children.length, 1); + }); + QUnit.test('config parsed', function (assert) { + assert.strictEqual(QUnit.config.hidepassed, 'true'); + }); + QUnit.test('interface', function (assert) { + var node = document.getElementById('qunit-urlconfig-hidepassed'); + assert.strictEqual(node.nodeName, 'INPUT'); + assert.strictEqual(node.checked, true); + }); +}); diff --git a/test/reporter-html/legacy-markup.html b/test/reporter-html/legacy-markup.html index d7fe97d74..cc5b823fc 100644 --- a/test/reporter-html/legacy-markup.html +++ b/test/reporter-html/legacy-markup.html @@ -1,17 +1,17 @@ - - QUnit HTML Reporter - Legacy Markup - - - + + legacy-markup + + + -

        Tests

        -

        -
        -

        -
          +

          Tests

          +

          +
          +

          +
            diff --git a/test/reporter-html/no-qunit-element.html b/test/reporter-html/no-qunit-element.html index 32e9ee9e2..19d17d2ff 100644 --- a/test/reporter-html/no-qunit-element.html +++ b/test/reporter-html/no-qunit-element.html @@ -1,26 +1,26 @@ - - QUnit HTML Reporter - No Markup - - - + + QUnit.done(function () { + clearTimeout(time); + }); + diff --git a/test/reporter-html/reporter-html.js b/test/reporter-html/reporter-html.js index 867e746f0..368c609c2 100644 --- a/test/reporter-html/reporter-html.js +++ b/test/reporter-html/reporter-html.js @@ -1,150 +1,148 @@ +/* eslint-env browser */ + // The following tests need to run on their respective order QUnit.config.reorder = false; -QUnit.module( "HTML Reporter", function() { - - QUnit.module( "", { - beforeEach: function() { - }, - afterEach: function( assert ) { - - // We can't use ok(false) inside script tags since some browsers - // don't evaluate script tags inserted through innerHTML after domready. - // Counting them before/after doesn't cover everything either as qunit-modulefilter - // is created before any test is ran. So use ids instead. - if ( document.getElementById( "qunit-unescaped-module" ) ) { - - // This can either be from in #qunit-modulefilter or #qunit-testresult - assert.true( false, "Unescaped module name" ); - } - if ( document.getElementById( "qunit-unescaped-test" ) ) { - assert.true( false, "Unescaped test name" ); - } - if ( document.getElementById( "qunit-unescaped-assertion" ) ) { - assert.true( false, "Unescaped test name" ); - } - } - } ); - - QUnit.test( "", function( assert ) { - assert.true( true, "" ); - } ); - - QUnit.module( "Display" ); - - QUnit.test( "use 'running' class", function( assert ) { - assert.equal( document.querySelectorAll( "#qunit-testresult-display.running" ).length, 1 ); - } ); - - QUnit.test( "running text", function( assert ) { - var display = document.getElementById( "qunit-testresult" ); - - assert.true( /running text/.test( display.innerHTML ), "test name" ); - assert.true( /Display/.test( display.innerHTML ), "module name" ); - } ); - - QUnit.test( "run progress", function( assert ) { - var display = document.getElementById( "qunit-testresult" ); - - var expected = /\d+ \/ \d+ tests completed/; - assert.true( - expected.test( display.innerHTML ), - "progress found in displayed text" - ); - } ); - - QUnit.module( "Duration", { - getPreviousTest: function( assert ) { - return document.getElementById( "qunit-test-output-" + assert.test.testId ) - .previousSibling; - }, - filterClass: function( elements ) { - var i; - for ( i = 0; i < elements.length; i++ ) { - if ( /(^| )runtime( |$)/.test( elements[ i ].className ) ) { - return elements[ i ]; - } - } - }, - afterEach: function( assert ) { - var done; - if ( this.delayNextSetup ) { - this.delayNextSetup = false; - done = assert.async(); - setTimeout( function() { - done(); - }, 101 ); - } - } - } ); - - QUnit.test( "setup", function( assert ) { - assert.expect( 0 ); - this.delayNextSetup = true; - } ); - - QUnit.test( "basics", function( assert ) { - assert.expect( 1 ); - var previous = this.getPreviousTest( assert ), - runtime = this.filterClass( previous.getElementsByTagName( "span" ) ); - - assert.true( /^\d+ ms$/.test( runtime.innerHTML ), "Runtime reported in ms" ); - } ); - - QUnit.test( "values", function( assert ) { - assert.expect( 2 ); - - var basics = this.getPreviousTest( assert ), - setup = basics.previousSibling; - - basics = this.filterClass( basics.getElementsByTagName( "span" ) ); - setup = this.filterClass( setup.getElementsByTagName( "span" ) ); - - assert.true( parseInt( basics.innerHTML, 10 ) < 100, - "Fast runtime for trivial test" - ); - assert.true( parseInt( setup.innerHTML, 10 ) > 100, - "Runtime includes beforeEach" - ); - } ); - - QUnit.module( "Stack trace" ); - - QUnit.test( "setup", function( assert ) { - assert.expect( 0 ); - } ); - - QUnit.test( "logs location", function( assert ) { - var previous = document.getElementById( "qunit-test-output-" + assert.test.testId ) - .previousSibling; - var source = previous.lastChild; - var err = new Error(); - - // Verify QUnit supported stack trace - if ( !err.stack ) { - // eslint-disable-next-line qunit/no-conditional-assertions - assert.false( - /(^| )qunit-source( |$)/.test( source.className ), - "Don't add source information on unsupported environments" - ); - - // eslint-disable-next-line qunit/no-early-return - return; - } - - assert.true( /(^| )qunit-source( |$)/.test( source.className ), "Source element exists" ); - assert.equal( source.firstChild.innerHTML, "Source: " ); - - // The file test/reporter-html/reporter-html.js is a direct reference to this test file - assert.true( /\/test\/reporter-html\/reporter-html\.js:\d+/.test( source.innerHTML ), - "Source references to the current file and line number" - ); - } ); - - QUnit.test( "disables autocomplete on module filter", function( assert ) { - var moduleFilterSearch = document.getElementById( "qunit-modulefilter-search" ); - - assert.equal( moduleFilterSearch.autocomplete, "off" ); - } ); - -} ); +QUnit.module('HTML Reporter', function () { + QUnit.module("", { + beforeEach: function () { + }, + afterEach: function (assert) { + // We can't use ok(false) inside script tags since some browsers + // don't evaluate script tags inserted through innerHTML after domready. + // Counting them before/after doesn't cover everything either as qunit-modulefilter + // is created before any test is ran. So use ids instead. + if (document.getElementById('qunit-unescaped-module')) { + // This can either be from in #qunit-modulefilter or #qunit-testresult + assert.true(false, 'Unescaped module name'); + } + if (document.getElementById('qunit-unescaped-test')) { + assert.true(false, 'Unescaped test name'); + } + if (document.getElementById('qunit-unescaped-assertion')) { + assert.true(false, 'Unescaped test name'); + } + } + }); + + QUnit.test("", function (assert) { + assert.true(true, ""); + }); + + QUnit.module('Display'); + + QUnit.test("use 'running' class", function (assert) { + assert.equal(document.querySelectorAll('#qunit-testresult-display.running').length, 1); + }); + + QUnit.test('running text', function (assert) { + var display = document.getElementById('qunit-testresult'); + + assert.true(/running text/.test(display.innerHTML), 'test name'); + assert.true(/Display/.test(display.innerHTML), 'module name'); + }); + + QUnit.test('run progress', function (assert) { + var display = document.getElementById('qunit-testresult'); + + var expected = /\d+ \/ \d+ tests completed/; + assert.true( + expected.test(display.innerHTML), + 'progress found in displayed text' + ); + }); + + QUnit.module('Duration', { + getPreviousTest: function (assert) { + return document.getElementById('qunit-test-output-' + assert.test.testId) + .previousSibling; + }, + filterClass: function (elements) { + var i; + for (i = 0; i < elements.length; i++) { + if (/(^| )runtime( |$)/.test(elements[i].className)) { + return elements[i]; + } + } + }, + afterEach: function (assert) { + var done; + if (this.delayNextSetup) { + this.delayNextSetup = false; + done = assert.async(); + setTimeout(function () { + done(); + }, 101); + } + } + }); + + QUnit.test('setup', function (assert) { + assert.expect(0); + this.delayNextSetup = true; + }); + + QUnit.test('basics', function (assert) { + assert.expect(1); + var previous = this.getPreviousTest(assert); + var runtime = this.filterClass(previous.getElementsByTagName('span')); + + assert.true(/^\d+ ms$/.test(runtime.innerHTML), 'Runtime reported in ms'); + }); + + QUnit.test('values', function (assert) { + assert.expect(2); + + var basics = this.getPreviousTest(assert); + var setup = basics.previousSibling; + + basics = this.filterClass(basics.getElementsByTagName('span')); + setup = this.filterClass(setup.getElementsByTagName('span')); + + assert.true(parseInt(basics.innerHTML, 10) < 100, + 'Fast runtime for trivial test' + ); + assert.true(parseInt(setup.innerHTML, 10) > 100, + 'Runtime includes beforeEach' + ); + }); + + QUnit.module('Stack trace'); + + QUnit.test('setup', function (assert) { + assert.expect(0); + }); + + QUnit.test('logs location', function (assert) { + var previous = document.getElementById('qunit-test-output-' + assert.test.testId) + .previousSibling; + var source = previous.lastChild; + var err = new Error(); + + // Verify QUnit supported stack trace + if (!err.stack) { + // eslint-disable-next-line qunit/no-conditional-assertions + assert.false( + /(^| )qunit-source( |$)/.test(source.className), + "Don't add source information on unsupported environments" + ); + + // eslint-disable-next-line qunit/no-early-return + return; + } + + assert.true(/(^| )qunit-source( |$)/.test(source.className), 'Source element exists'); + assert.equal(source.firstChild.innerHTML, 'Source: '); + + // The file test/reporter-html/reporter-html.js is a direct reference to this test file + assert.true(/\/test\/reporter-html\/reporter-html\.js:\d+/.test(source.innerHTML), + 'Source references to the current file and line number' + ); + }); + + QUnit.test('disables autocomplete on module filter', function (assert) { + var moduleFilterSearch = document.getElementById('qunit-modulefilter-search'); + + assert.equal(moduleFilterSearch.autocomplete, 'off'); + }); +}); diff --git a/test/reporter-html/unhandled-rejection.js b/test/reporter-html/unhandled-rejection.js index 7eda388bb..e2ce40484 100644 --- a/test/reporter-html/unhandled-rejection.js +++ b/test/reporter-html/unhandled-rejection.js @@ -1,29 +1,30 @@ +/* eslint-env browser */ +/* global Promise */ + // Detect if the current browser supports `onunhandledrejection` // (avoiding errors in browsers without the capability) -var HAS_UNHANDLED_REJECTION_HANDLER = "onunhandledrejection" in window; - -if ( HAS_UNHANDLED_REJECTION_HANDLER ) { - QUnit.module( "Unhandled Rejections inside test context", function( hooks ) { - hooks.beforeEach( function( assert ) { - var originalPushResult = assert.pushResult; - assert.pushResult = function( resultInfo ) { - - // Invert the result so we can test failing assertions - resultInfo.result = !resultInfo.result; - originalPushResult.call( this, resultInfo ); - }; - } ); +var HAS_UNHANDLED_REJECTION_HANDLER = 'onunhandledrejection' in window; - QUnit.test( "test passes just fine, but has a rejected promise", function( assert ) { - var done = assert.async(); +if (HAS_UNHANDLED_REJECTION_HANDLER) { + QUnit.module('Unhandled Rejections inside test context', function (hooks) { + hooks.beforeEach(function (assert) { + var originalPushResult = assert.pushResult; + assert.pushResult = function (resultInfo) { + // Invert the result so we can test failing assertions + resultInfo.result = !resultInfo.result; + originalPushResult.call(this, resultInfo); + }; + }); - Promise.resolve().then( function() { - throw new Error( "Error thrown in non-returned promise!" ); - } ); + QUnit.test('test passes just fine, but has a rejected promise', function (assert) { + var done = assert.async(); - // prevent test from exiting before unhandled rejection fires - setTimeout( done, 10 ); - } ); + Promise.resolve().then(function () { + throw new Error('Error thrown in non-returned promise!'); + }); - } ); + // prevent test from exiting before unhandled rejection fires + setTimeout(done, 10); + }); + }); } diff --git a/test/reporter-html/urlparams-filter.js b/test/reporter-html/urlparams-filter.js index 20e5a0aad..729c907fd 100644 --- a/test/reporter-html/urlparams-filter.js +++ b/test/reporter-html/urlparams-filter.js @@ -1,31 +1,33 @@ +/* eslint-env browser */ + // regular expression (case-sensitive), inverted -if ( !location.search ) { - location.replace( "?filter=!/Foo|bar/" ); +if (!location.search) { + location.replace('?filter=!/Foo|bar/'); } -QUnit.module( "filter" ); +QUnit.module('filter'); -QUnit.test( "config parsed", function( assert ) { - assert.strictEqual( QUnit.config.filter, "!/Foo|bar/" ); -} ); -QUnit.test( "interface", function( assert ) { - var node = document.getElementById( "qunit-filter-input" ); - assert.strictEqual( node.nodeName, "INPUT" ); - assert.strictEqual( node.value, "!/Foo|bar/" ); -} ); +QUnit.test('config parsed', function (assert) { + assert.strictEqual(QUnit.config.filter, '!/Foo|bar/'); +}); +QUnit.test('interface', function (assert) { + var node = document.getElementById('qunit-filter-input'); + assert.strictEqual(node.nodeName, 'INPUT'); + assert.strictEqual(node.value, '!/Foo|bar/'); +}); -QUnit.test( "foo test", function( assert ) { - assert.true( true ); -} ); +QUnit.test('foo test', function (assert) { + assert.true(true); +}); -QUnit.test( "Foo test", function( assert ) { - assert.true( false, "Foo is excluded" ); -} ); +QUnit.test('Foo test', function (assert) { + assert.true(false, 'Foo is excluded'); +}); -QUnit.test( "bar test", function( assert ) { - assert.true( false, "bar is excluded" ); -} ); +QUnit.test('bar test', function (assert) { + assert.true(false, 'bar is excluded'); +}); -QUnit.test( "Bar test", function( assert ) { - assert.true( true ); -} ); +QUnit.test('Bar test', function (assert) { + assert.true(true); +}); diff --git a/test/reporter-html/window-onerror-preexisting-handler.html b/test/reporter-html/window-onerror-preexisting-handler.html index a318cecd9..82b27b7cf 100644 --- a/test/reporter-html/window-onerror-preexisting-handler.html +++ b/test/reporter-html/window-onerror-preexisting-handler.html @@ -9,11 +9,10 @@ var onerrorReturnValue, onerrorCallingContext; // Create pre-existing window.onerror handler - window.onerror = function() { - onerrorCallingContext = this; - return onerrorReturnValue; + window.onerror = function () { + onerrorCallingContext = this; + return onerrorReturnValue; }; - diff --git a/test/reporter-html/window-onerror-preexisting-handler.js b/test/reporter-html/window-onerror-preexisting-handler.js index 49e88d9be..ecc894c7a 100644 --- a/test/reporter-html/window-onerror-preexisting-handler.js +++ b/test/reporter-html/window-onerror-preexisting-handler.js @@ -1,89 +1,89 @@ +/* eslint-env browser */ /* global onerrorReturnValue: true, onerrorCallingContext: true */ -/* exported onerrorReturnValue */ -QUnit.module( "window.onerror (with preexisting handler)", function( hooks ) { - var originalOnUncaught; +QUnit.module('window.onerror (with preexisting handler)', function (hooks) { + var originalOnUncaught; - hooks.beforeEach( function() { - onerrorReturnValue = null; - onerrorCallingContext = null; + hooks.beforeEach(function () { + onerrorReturnValue = null; + onerrorCallingContext = null; - originalOnUncaught = QUnit.onUncaughtException; - } ); + originalOnUncaught = QUnit.onUncaughtException; + }); - hooks.afterEach( function() { - QUnit.onUncaughtException = originalOnUncaught; - } ); + hooks.afterEach(function () { + QUnit.onUncaughtException = originalOnUncaught; + }); - QUnit.test( "call QUnit.onUncaughtException if handler returns false", function( assert ) { - assert.expect( 1 ); + QUnit.test('call QUnit.onUncaughtException if handler returns false', function (assert) { + assert.expect(1); - onerrorReturnValue = false; + onerrorReturnValue = false; - QUnit.onUncaughtException = function() { - assert.true( true, "QUnit.onUncaughtException was called" ); - }; + QUnit.onUncaughtException = function () { + assert.true(true, 'QUnit.onUncaughtException was called'); + }; - window.onerror( "An error message", "filename.js", 1 ); - } ); + window.onerror('An error message', 'filename.js', 1); + }); - QUnit.test( "call QUnit.onUncaughtException if handler returns void 0", function( assert ) { - assert.expect( 1 ); + QUnit.test('call QUnit.onUncaughtException if handler returns undefined', function (assert) { + assert.expect(1); - onerrorReturnValue = void 0; + onerrorReturnValue = undefined; - QUnit.onUncaughtException = function() { - assert.true( true, "QUnit.onUncaughtException was called" ); - }; + QUnit.onUncaughtException = function () { + assert.true(true, 'QUnit.onUncaughtException was called'); + }; - window.onerror( "An error message", "filename.js", 1 ); - } ); + window.onerror('An error message', 'filename.js', 1); + }); - QUnit.test( "call QUnit.onUncaughtException if handler returns truthy", function( assert ) { - assert.expect( 1 ); + QUnit.test('call QUnit.onUncaughtException if handler returns truthy', function (assert) { + assert.expect(1); - onerrorReturnValue = "truthy value"; + onerrorReturnValue = 'truthy value'; - QUnit.onUncaughtException = function() { - assert.true( true, "QUnit.onUncaughtException was called" ); - }; + QUnit.onUncaughtException = function () { + assert.true(true, 'QUnit.onUncaughtException was called'); + }; - window.onerror( "An error message", "filename.js", 1 ); - } ); + window.onerror('An error message', 'filename.js', 1); + }); - QUnit.test( "ignore error if other handler returns true", function( assert ) { - assert.expect( 1 ); + QUnit.test('ignore error if other handler returns true', function (assert) { + assert.expect(1); - onerrorReturnValue = true; + onerrorReturnValue = true; - QUnit.onUncaughtException = function() { - assert.true( false, "QUnit.onUncaughtException should not have been called" ); - }; + QUnit.onUncaughtException = function () { + assert.true(false, 'QUnit.onUncaughtException should not have been called'); + }; - var result = window.onerror( "An error message", "filename.js", 1 ); + var result = window.onerror('An error message', 'filename.js', 1); - assert.strictEqual( result, true, "Our error handler should have returned true" ); - } ); + assert.strictEqual(result, true, 'Our error handler should have returned true'); + }); - QUnit.test( "window.onerror handler is called on window", function( assert ) { - assert.expect( 1 ); + QUnit.test('window.onerror handler is called on window', function (assert) { + assert.expect(1); - QUnit.onUncaughtException = function() {}; + QUnit.onUncaughtException = function () {}; - window.onerror( "An error message", "filename.js", 1 ); + window.onerror('An error message', 'filename.js', 1); - assert.strictEqual( onerrorCallingContext, window, "Handler called with correct context" ); - } ); + assert.strictEqual(onerrorCallingContext, window, 'Handler called with correct context'); + }); - QUnit.test( "forward return value of prexisting onerror", function( assert ) { - assert.expect( 1 ); + QUnit.test('forward return value of prexisting onerror', function (assert) { + assert.expect(1); - onerrorReturnValue = {}; + onerrorReturnValue = {}; - QUnit.onUncaughtException = function() {}; + QUnit.onUncaughtException = function () {}; - var result = window.onerror( "An error message", "filename.js", 1 ); + var result = window.onerror('An error message', 'filename.js', 1); - assert.strictEqual( result, onerrorReturnValue, "return value" ); - } ); -} ); + assert.strictEqual(result, onerrorReturnValue, 'return value'); + }); +}); diff --git a/test/reporter-html/window-onerror.js b/test/reporter-html/window-onerror.js index 77dd5f918..7d6aa238e 100644 --- a/test/reporter-html/window-onerror.js +++ b/test/reporter-html/window-onerror.js @@ -1,35 +1,36 @@ -QUnit.module( "window.onerror (no preexisting handler)", function( hooks ) { - var originalOnUncaught; +/* eslint-env browser */ +QUnit.module('window.onerror (no preexisting handler)', function (hooks) { + var originalOnUncaught; - hooks.beforeEach( function() { - originalOnUncaught = QUnit.onUncaughtException; - } ); + hooks.beforeEach(function () { + originalOnUncaught = QUnit.onUncaughtException; + }); - hooks.afterEach( function() { - QUnit.onUncaughtException = originalOnUncaught; - } ); + hooks.afterEach(function () { + QUnit.onUncaughtException = originalOnUncaught; + }); - QUnit.test( "call QUnit.onUncaughtException", function( assert ) { - assert.expect( 1 ); + QUnit.test('call QUnit.onUncaughtException', function (assert) { + assert.expect(1); - QUnit.onUncaughtException = function() { - assert.true( true, "called" ); - }; + QUnit.onUncaughtException = function () { + assert.true(true, 'called'); + }; - window.onerror( "An error message", "filename.js", 1 ); - } ); + window.onerror('An error message', 'filename.js', 1); + }); - QUnit.test( "extract stacktrace", function( assert ) { - assert.expect( 1 ); + QUnit.test('extract stacktrace', function (assert) { + assert.expect(1); - var errorObj = { - stack: "dummy.js:1 top()\ndummy.js:2 middle()\ndummy.js:3 bottom()" - }; + var errorObj = { + stack: 'dummy.js:1 top()\ndummy.js:2 middle()\ndummy.js:3 bottom()' + }; - QUnit.onUncaughtException = function( error ) { - assert.equal( error.stack, errorObj.stack, "stack" ); - }; + QUnit.onUncaughtException = function (error) { + assert.equal(error.stack, errorObj.stack, 'stack'); + }; - window.onerror( "An error message", "filename.js", 1, 1, errorObj ); - } ); -} ); + window.onerror('An error message', 'filename.js', 1, 1, errorObj); + }); +}); diff --git a/test/reporter-html/xhtml-escape-details-source.js b/test/reporter-html/xhtml-escape-details-source.js index 1f09c9f3d..dc599baf6 100644 --- a/test/reporter-html/xhtml-escape-details-source.js +++ b/test/reporter-html/xhtml-escape-details-source.js @@ -1,8 +1,8 @@ -QUnit.module( "outer module", function() { - QUnit.module( "inner module", function() { - QUnit.test( "test name with a special char > after char", function( assert ) { - assert.expect( 1 ); - assert.true( true, "dummy test" ); - } ); - } ); -} ); +QUnit.module('outer module', function () { + QUnit.module('inner module', function () { + QUnit.test('test name with a special char > after char', function (assert) { + assert.expect(1); + assert.true(true, 'dummy test'); + }); + }); +}); diff --git a/test/reporter-urlparams.html b/test/reporter-urlparams.html index c73ba5f3b..8cecc7085 100644 --- a/test/reporter-urlparams.html +++ b/test/reporter-urlparams.html @@ -1,13 +1,13 @@ - - - QUnit URL Parameters Test Suite - - - - - -
            - + + + reporter-urlparams + + + + + +
            + diff --git a/test/reporter-urlparams.js b/test/reporter-urlparams.js index b3ac84d6b..d1753ef8f 100644 --- a/test/reporter-urlparams.js +++ b/test/reporter-urlparams.js @@ -1,40 +1,41 @@ -if ( !location.search ) { - location.replace( "?implicit&explicit=yes&array=A&array=B&escaped%20name&toString=string&" + - "module=urlParams+module&filter=urlParams%20module¬rycatch&" + - "custom&customArray=a&customArray=b" ); +/* eslint-env browser */ +if (!location.search) { + location.replace('?implicit&explicit=yes&array=A&array=B&escaped%20name&toString=string&' + + 'module=urlParams+module&filter=urlParams%20module¬rycatch&' + + 'custom&customArray=a&customArray=b'); } -QUnit.config.urlConfig.push( "custom", "customArray" ); +QUnit.config.urlConfig.push('custom', 'customArray'); // Don't change this module name without also changing the module parameter when loading this suite -QUnit.module( "urlParams module", function() { - QUnit.test( "parsing", function( assert ) { - assert.strictEqual( QUnit.urlParams.implicit, true, "implicit true value" ); - assert.strictEqual( QUnit.urlParams.explicit, "yes", "explicit value" ); - assert.deepEqual( QUnit.urlParams.array, [ "A", "B" ], "multiple values" ); - assert.true( QUnit.urlParams[ "escaped name" ], "escape sequences in name" ); - assert.strictEqual( QUnit.urlParams.toString, "string", "Object.prototype property" ); - assert.strictEqual( QUnit.urlParams.module, "urlParams module", "escaped space as +" ); - assert.strictEqual( QUnit.urlParams.filter, "urlParams module", "escaped space as %20" ); - } ); +QUnit.module('urlParams module', function () { + QUnit.test('parsing', function (assert) { + assert.strictEqual(QUnit.urlParams.implicit, true, 'implicit true value'); + assert.strictEqual(QUnit.urlParams.explicit, 'yes', 'explicit value'); + assert.deepEqual(QUnit.urlParams.array, ['A', 'B'], 'multiple values'); + assert.true(QUnit.urlParams['escaped name'], 'escape sequences in name'); + assert.strictEqual(QUnit.urlParams.toString, 'string', 'Object.prototype property'); + assert.strictEqual(QUnit.urlParams.module, 'urlParams module', 'escaped space as +'); + assert.strictEqual(QUnit.urlParams.filter, 'urlParams module', 'escaped space as %20'); + }); - QUnit.module( "QUnit.config properties", function() { - QUnit.test( "setting standard properties", function( assert ) { - assert.strictEqual( QUnit.config.module, "urlParams module", "module" ); - assert.strictEqual( QUnit.config.filter, "urlParams module", "filter" ); - assert.strictEqual( QUnit.config.notrycatch, true, "notrycatch" ); - } ); + QUnit.module('QUnit.config properties', function () { + QUnit.test('setting standard properties', function (assert) { + assert.strictEqual(QUnit.config.module, 'urlParams module', 'module'); + assert.strictEqual(QUnit.config.filter, 'urlParams module', 'filter'); + assert.strictEqual(QUnit.config.notrycatch, true, 'notrycatch'); + }); - QUnit.test( "setting custom properties", function( assert ) { - assert.strictEqual( QUnit.config.custom, true, "implicit boolean" ); - assert.deepEqual( QUnit.config.customArray, [ "a", "b" ], "multiple values" ); - } ); + QUnit.test('setting custom properties', function (assert) { + assert.strictEqual(QUnit.config.custom, true, 'implicit boolean'); + assert.deepEqual(QUnit.config.customArray, ['a', 'b'], 'multiple values'); + }); - QUnit.test( "ignoring non-config parameters", function( assert ) { - assert.strictEqual( QUnit.config.implicit, undefined, "implicit boolean" ); - assert.strictEqual( QUnit.config.explicit, undefined, "explicit value" ); - assert.strictEqual( QUnit.config.array, undefined, "multiple values" ); - assert.strictEqual( QUnit.config[ "escaped name" ], undefined, "escaped name" ); - } ); - } ); -} ); + QUnit.test('ignoring non-config parameters', function (assert) { + assert.strictEqual(QUnit.config.implicit, undefined, 'implicit boolean'); + assert.strictEqual(QUnit.config.explicit, undefined, 'explicit value'); + assert.strictEqual(QUnit.config.array, undefined, 'multiple values'); + assert.strictEqual(QUnit.config['escaped name'], undefined, 'escaped name'); + }); + }); +}); diff --git a/test/sandboxed-iframe--contents.html b/test/sandboxed-iframe--contents.html index b140e04aa..d76864976 100644 --- a/test/sandboxed-iframe--contents.html +++ b/test/sandboxed-iframe--contents.html @@ -1,14 +1,14 @@ - - - sandboxed iframe contents - - - - - - -
            - + + + sandboxed-iframe-contents + + + + + + +
            + diff --git a/test/sandboxed-iframe.html b/test/sandboxed-iframe.html index 73f0d6a41..b39cfa7e7 100644 --- a/test/sandboxed-iframe.html +++ b/test/sandboxed-iframe.html @@ -1,10 +1,10 @@ - - sandboxed iframe + + sandboxed-iframe - + diff --git a/test/sandboxed-iframe.js b/test/sandboxed-iframe.js index 3ba60339b..a05fb1e74 100644 --- a/test/sandboxed-iframe.js +++ b/test/sandboxed-iframe.js @@ -1,34 +1,36 @@ -// For debugging -if ( self.__grunt_contrib_qunit__ === undefined ) { - self.__grunt_contrib_qunit__ = function() { - var args = [].slice.call( arguments ); - args.unshift( "[grunt-contrib-qunit]" ); - console.log.apply( console, args ); - }; +/* eslint-env browser */ + +if (self.__grunt_contrib_qunit__ === undefined) { + // For debugging + self.__grunt_contrib_qunit__ = function () { + var args = [].slice.call(arguments); + args.unshift('[grunt-contrib-qunit]'); + console.log.apply(console, args); + }; } -QUnit.module( "QUnit.only", function( hooks ) { - var testsRun = 0; +QUnit.module('QUnit.only', function (hooks) { + var testsRun = 0; - hooks.after( function( assert ) { - assert.strictEqual( testsRun, 2 ); - } ); + hooks.after(function (assert) { + assert.strictEqual(testsRun, 2); + }); - QUnit.test( "implicitly skipped test", function( assert ) { - assert.true( false, "test should be skipped" ); - } ); + QUnit.test('implicitly skipped test', function (assert) { + assert.true(false, 'test should be skipped'); + }); - QUnit.only( "run this test", function( assert ) { - testsRun += 1; - assert.true( true, "only this test should run" ); - } ); + QUnit.only('run this test', function (assert) { + testsRun += 1; + assert.true(true, 'only this test should run'); + }); - QUnit.test( "another implicitly skipped test", function( assert ) { - assert.true( false, "test should be skipped" ); - } ); + QUnit.test('another implicitly skipped test', function (assert) { + assert.true(false, 'test should be skipped'); + }); - QUnit.only( "all tests with only run", function( assert ) { - testsRun += 1; - assert.true( true, "this test should run as well" ); - } ); -} ); + QUnit.only('all tests with only run', function (assert) { + testsRun += 1; + assert.true(true, 'this test should run as well'); + }); +}); diff --git a/test/seed.html b/test/seed.html index d76805694..60cf5b8d0 100644 --- a/test/seed.html +++ b/test/seed.html @@ -2,7 +2,7 @@ - QUnit Randomization Test Suite + seed diff --git a/test/seed.js b/test/seed.js index 55f3f8af7..8b950462a 100644 --- a/test/seed.js +++ b/test/seed.js @@ -1,25 +1,25 @@ -QUnit.config.seed = "7x9"; +QUnit.config.seed = '7x9'; -var lastTest = ""; +var lastTest = ''; -QUnit.module( "QUnit.config.seed" ); +QUnit.module('QUnit.config.seed'); -QUnit.test( "1", function( assert ) { - assert.equal( lastTest, "2", "runs third" ); - lastTest = "1"; -} ); +QUnit.test('1', function (assert) { + assert.equal(lastTest, '2', 'runs third'); + lastTest = '1'; +}); -QUnit.test( "2", function( assert ) { - assert.equal( lastTest, "3", "runs second" ); - lastTest = "2"; -} ); +QUnit.test('2', function (assert) { + assert.equal(lastTest, '3', 'runs second'); + lastTest = '2'; +}); -QUnit.test( "3", function( assert ) { - assert.equal( lastTest, "", "runs first" ); - lastTest = "3"; -} ); +QUnit.test('3', function (assert) { + assert.equal(lastTest, '', 'runs first'); + lastTest = '3'; +}); -QUnit.test( "4", function( assert ) { - assert.equal( lastTest, "1", "runs last" ); - lastTest = "4"; -} ); +QUnit.test('4', function (assert) { + assert.equal(lastTest, '1', 'runs last'); + lastTest = '4'; +}); diff --git a/test/startError.html b/test/startError.html index 0fefa2a33..6a9be8d84 100644 --- a/test/startError.html +++ b/test/startError.html @@ -1,13 +1,13 @@ - - QUnit Start Error Test Suite - + + startError + -
            - - +
            + + diff --git a/test/startError.js b/test/startError.js index d8b95e0d9..acc52cdcf 100644 --- a/test/startError.js +++ b/test/startError.js @@ -1,32 +1,34 @@ +/* eslint-env browser */ + try { - QUnit.config.autostart = true; - QUnit.start(); -} catch ( thrownError ) { - window.autostartStartError = thrownError; + QUnit.config.autostart = true; + QUnit.start(); +} catch (thrownError) { + window.autostartStartError = thrownError; } try { - QUnit.start(); -} catch ( thrownError ) { - window.tooManyStartsError = thrownError; + QUnit.start(); +} catch (thrownError) { + window.tooManyStartsError = thrownError; } -QUnit.module( "global start unrecoverable errors" ); +QUnit.module('global start unrecoverable errors'); -QUnit.test( "start() throws when QUnit.config.autostart === true", function( assert ) { - assert.equal( window.autostartStartError.message, - "Called start() outside of a test context when QUnit.config.autostart was true" ); -} ); +QUnit.test('start() throws when QUnit.config.autostart === true', function (assert) { + assert.equal(window.autostartStartError.message, + 'Called start() outside of a test context when QUnit.config.autostart was true'); +}); -QUnit.test( "Throws after calling start() too many times outside of a test context", function( assert ) { - assert.equal( window.tooManyStartsError.message, - "Called start() outside of a test context too many times" ); -} ); +QUnit.test('Throws after calling start() too many times outside of a test context', function (assert) { + assert.equal(window.tooManyStartsError.message, + 'Called start() outside of a test context too many times'); +}); -QUnit.test( "QUnit.start cannot be called inside a test context.", function( assert ) { - assert.throws( function() { - // eslint-disable-next-line qunit/no-qunit-start-in-tests - QUnit.start(); - }, - /QUnit\.start cannot be called inside a test context\./ ); -} ); +QUnit.test('QUnit.start cannot be called inside a test context.', function (assert) { + assert.throws(function () { + // eslint-disable-next-line qunit/no-qunit-start-in-tests + QUnit.start(); + }, + /QUnit\.start cannot be called inside a test context\./); +}); diff --git a/test/urlparams-module.html b/test/urlparams-module.html index c31f528a1..8dab54292 100644 --- a/test/urlparams-module.html +++ b/test/urlparams-module.html @@ -1,13 +1,13 @@ - - QUnit - + + QUnit + -
            - - +
            + + diff --git a/test/urlparams-module.js b/test/urlparams-module.js index c9c72cca1..3ef75fc77 100644 --- a/test/urlparams-module.js +++ b/test/urlparams-module.js @@ -1,16 +1,17 @@ -if ( !location.search ) { - location.replace( "?module=Foo" ); +/* eslint-env browser */ +if (!location.search) { + location.replace('?module=Foo'); } -QUnit.module( "Foo" ); +QUnit.module('Foo'); -QUnit.test( "Foo test", function( assert ) { - assert.true( true, "run module Foo" ); - assert.strictEqual( QUnit.config.module, "Foo", "parsed config" ); -} ); +QUnit.test('Foo test', function (assert) { + assert.true(true, 'run module Foo'); + assert.strictEqual(QUnit.config.module, 'Foo', 'parsed config'); +}); -QUnit.module( "Bar" ); +QUnit.module('Bar'); -QUnit.test( "Bar test", function( assert ) { - assert.true( false ); -} ); +QUnit.test('Bar test', function (assert) { + assert.true(false); +}); diff --git a/test/urlparams-moduleId.html b/test/urlparams-moduleId.html index a26b69e07..7ded76f66 100644 --- a/test/urlparams-moduleId.html +++ b/test/urlparams-moduleId.html @@ -1,13 +1,13 @@ - - QUnit - + + QUnit + -
            - - +
            + + diff --git a/test/urlparams-moduleId.js b/test/urlparams-moduleId.js index 64f3dc826..6d4fb933d 100644 --- a/test/urlparams-moduleId.js +++ b/test/urlparams-moduleId.js @@ -1,34 +1,35 @@ -if ( !location.search ) { - location.replace( "?moduleId=4dea3fbe&moduleId=9bf7d15c" ); +/* eslint-env browser */ +if (!location.search) { + location.replace('?moduleId=4dea3fbe&moduleId=9bf7d15c'); } -QUnit.test( "global test", function( assert ) { - assert.true( false ); -} ); +QUnit.test('global test', function (assert) { + assert.true(false); +}); -QUnit.module( "module A scoped", function() { - QUnit.test( "test A1", function( assert ) { - assert.true( false ); - } ); +QUnit.module('module A scoped', function () { + QUnit.test('test A1', function (assert) { + assert.true(false); + }); - QUnit.module( "module B nested", function() { - QUnit.test( "test B1", function( assert ) { - assert.true( true, "run module B" ); - } ); - } ); -} ); + QUnit.module('module B nested', function () { + QUnit.test('test B1', function (assert) { + assert.true(true, 'run module B'); + }); + }); +}); -QUnit.module( "module D flat" ); -QUnit.test( "test D1", function( assert ) { - assert.true( false ); -} ); +QUnit.module('module D flat'); +QUnit.test('test D1', function (assert) { + assert.true(false); +}); -QUnit.module( "module E flat" ); -QUnit.test( "test E1", function( assert ) { - assert.true( true, "run module E" ); - assert.deepEqual( - QUnit.config.moduleId, - [ "4dea3fbe", "9bf7d15c" ], - "parsed config" - ); -} ); +QUnit.module('module E flat'); +QUnit.test('test E1', function (assert) { + assert.true(true, 'run module E'); + assert.deepEqual( + QUnit.config.moduleId, + ['4dea3fbe', '9bf7d15c'], + 'parsed config' + ); +}); diff --git a/test/urlparams-testId.html b/test/urlparams-testId.html index 20a68ad9f..d279f2eb8 100644 --- a/test/urlparams-testId.html +++ b/test/urlparams-testId.html @@ -1,13 +1,13 @@ - - QUnit - + + QUnit + -
            - - +
            + + diff --git a/test/urlparams-testId.js b/test/urlparams-testId.js index 12ee126c4..5047cf46c 100644 --- a/test/urlparams-testId.js +++ b/test/urlparams-testId.js @@ -1,21 +1,21 @@ -if ( !location.search ) { - location.replace( "?testId=94e5e740" ); +/* eslint-env browser */ +if (!location.search) { + location.replace('?testId=94e5e740'); } -QUnit.test( "test 1", function( assert ) { - assert.true( false ); -} ); +QUnit.test('test 1', function (assert) { + assert.true(false); +}); -QUnit.test( "test 2", function( assert ) { - assert.true( true, "run global test 2" ); - assert.deepEqual( - QUnit.config.testId, - [ "94e5e740" ], - "parsed config" - ); -} ); - -QUnit.test( "test 3", function( assert ) { - assert.true( false ); -} ); +QUnit.test('test 2', function (assert) { + assert.true(true, 'run global test 2'); + assert.deepEqual( + QUnit.config.testId, + ['94e5e740'], + 'parsed config' + ); +}); +QUnit.test('test 3', function (assert) { + assert.true(false); +}); diff --git a/test/webWorker-worker.js b/test/webWorker-worker.js index 290f08852..1879b1a16 100644 --- a/test/webWorker-worker.js +++ b/test/webWorker-worker.js @@ -1,30 +1,30 @@ // This worker script gets run via test/webWorker.html -/* global importScripts */ -/* eslint-env es6 */ +/* eslint-env worker */ + importScripts( - "../qunit/qunit.js", + '../qunit/qunit.js', - // Sync with test/index.html - "main/assert.js", - "main/assert-step.js", - "main/assert-timeout.js", - "main/async.js", - "main/deepEqual.js", - "main/dump.js", - "main/each.js", - "main/modules.js", - "main/onError.js", - "main/onUncaughtException.js", - "main/promise.js", - "main/setTimeout.js", - "main/stack.js", - "main/test.js", - "main/utilities.js" + // Sync with test/index.html + 'main/assert.js', + 'main/assert-step.js', + 'main/assert-timeout.js', + 'main/async.js', + 'main/deepEqual.js', + 'main/dump.js', + 'main/each.js', + 'main/modules.js', + 'main/onError.js', + 'main/onUncaughtException.js', + 'main/promise.js', + 'main/setTimeout.js', + 'main/stack.js', + 'main/test.js', + 'main/utilities.js' ); -QUnit.on( "runEnd", ( data ) => { - postMessage( data ); -} ); +QUnit.on('runEnd', function (data) { + postMessage(data); +}); QUnit.start(); diff --git a/test/webWorker.html b/test/webWorker.html index a0440b0e8..645be73e0 100644 --- a/test/webWorker.html +++ b/test/webWorker.html @@ -1,13 +1,13 @@ - - QUnit Web Worker Test Suite - - - + + webWorker + + + -
            +
            diff --git a/test/webWorker.js b/test/webWorker.js index 061df1d6c..530c3e2e1 100644 --- a/test/webWorker.js +++ b/test/webWorker.js @@ -1,14 +1,17 @@ -QUnit.module( "Web Worker" ); +/* eslint-env browser */ +QUnit.module('Web Worker'); -var testMethod = window.Worker ? "test" : "skip"; +// Support: IE 9 +/* eslint-disable compat/compat */ +var testMethod = window.Worker ? 'test' : 'skip'; -QUnit[ testMethod ]( "main tests", function( assert ) { - assert.expect( 1 ); - var done = assert.async(); - var worker = new Worker( "webWorker-worker.js" ); +QUnit[testMethod]('main tests', function (assert) { + assert.expect(1); + var done = assert.async(); + var worker = new Worker('webWorker-worker.js'); - worker.onmessage = function( event ) { - assert.equal( event.data.status, "passed", "runEnd.status" ); - done(); - }; -} ); + worker.onmessage = function (event) { + assert.equal(event.data.status, 'passed', 'runEnd.status'); + done(); + }; +});