From c94d02bb75ac9626464f2b27625320e61c800f2c Mon Sep 17 00:00:00 2001 From: Krystan HuffMenne Date: Mon, 5 Dec 2022 14:23:36 -0800 Subject: [PATCH 01/34] REVERT ME: Dev setup --- .eslintignore | 2 ++ .gitignore | 3 +++ .prettierignore | 2 ++ yarn.lock | 68 +++++++++++++++++++++++++++++++++++++++++++++++-- 4 files changed, 73 insertions(+), 2 deletions(-) diff --git a/.eslintignore b/.eslintignore index d474a40b..788ec3d5 100644 --- a/.eslintignore +++ b/.eslintignore @@ -15,6 +15,8 @@ !.* .*/ .eslintcache +/.yalc* +/yalc.lock # ember-try /.node_modules.ember-try/ diff --git a/.gitignore b/.gitignore index 57f1ade9..06f20ec5 100644 --- a/.gitignore +++ b/.gitignore @@ -20,6 +20,9 @@ /npm-debug.log* /testem.log /yarn-error.log +/.yalc* +/yalc.lock +/.vscode* # ember-try /.node_modules.ember-try/ diff --git a/.prettierignore b/.prettierignore index 4178fd57..a2502fb6 100644 --- a/.prettierignore +++ b/.prettierignore @@ -15,6 +15,8 @@ !.* .eslintcache .lint-todo/ +/.yalc* +/yalc.lock # ember-try /.node_modules.ember-try/ diff --git a/yarn.lock b/yarn.lock index 1c24c17f..3e853631 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1124,7 +1124,21 @@ ember-cli-version-checker "^5.1.2" semver "^7.3.5" -"@embroider/macros@^1.0.0", "@embroider/macros@^1.10.0", "@embroider/macros@^1.9.0": +"@embroider/macros@^1.0.0", "@embroider/macros@^1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@embroider/macros/-/macros-1.9.0.tgz#0df2a56fdd93f11fddea450b6ca83cc2119b5008" + integrity sha512-12ElrRT+mX3aSixGHjHnfsnyoH1hw5nM+P+Ax0ITZdp6TaAvWZ8dENnVHltdnv4ssHiX0EsVEXmqbIIdMN4nLA== + dependencies: + "@embroider/shared-internals" "1.8.3" + assert-never "^1.2.1" + babel-import-util "^1.1.0" + ember-cli-babel "^7.26.6" + find-up "^5.0.0" + lodash "^4.17.21" + resolve "^1.20.0" + semver "^7.3.2" + +"@embroider/macros@^1.10.0": version "1.10.0" resolved "https://registry.yarnpkg.com/@embroider/macros/-/macros-1.10.0.tgz#af3844d5db48f001b85cfb096c76727c72ad6c1e" integrity sha512-LMbfQGk/a+f6xtvAv5fq/wf2LRxETnbgSCLUf/z6ebzmuskOUxrke+uP55chF/loWrARi9g6erFQ7RDOUoBMSg== @@ -1138,6 +1152,20 @@ resolve "^1.20.0" semver "^7.3.2" +"@embroider/shared-internals@1.8.3": + version "1.8.3" + resolved "https://registry.yarnpkg.com/@embroider/shared-internals/-/shared-internals-1.8.3.tgz#52d868dc80016e9fe983552c0e516f437bf9b9f9" + integrity sha512-N5Gho6Qk8z5u+mxLCcMYAoQMbN4MmH+z2jXwQHVs859bxuZTxwF6kKtsybDAASCtd2YGxEmzcc1Ja/wM28824w== + dependencies: + babel-import-util "^1.1.0" + ember-rfc176-data "^0.3.17" + fs-extra "^9.1.0" + js-string-escape "^1.0.1" + lodash "^4.17.21" + resolve-package-path "^4.0.1" + semver "^7.3.5" + typescript-memoize "^1.0.1" + "@embroider/shared-internals@2.0.0", "@embroider/shared-internals@^2.0.0": version "2.0.0" resolved "https://registry.yarnpkg.com/@embroider/shared-internals/-/shared-internals-2.0.0.tgz#d8205ec6944362aeecfbb51143db352430ced316" @@ -3904,7 +3932,43 @@ ember-angle-bracket-invocation-polyfill@^3.0.2: ember-compatibility-helpers "^1.0.2" silent-error "^1.1.1" -ember-auto-import@^2.4.1, ember-auto-import@^2.6.0: +ember-auto-import@^2.4.1: + version "2.5.0" + resolved "https://registry.yarnpkg.com/ember-auto-import/-/ember-auto-import-2.5.0.tgz#627607648e87d154f75cd3f70c435355ef7cced9" + integrity sha512-fKERUmpZLn4RJiCwTjS7D5zJxgnbF4E6GiSp1GYh53K96S+5UBs06r7ScDI52rq34z0+qdSrA6qiDJ3i/lWqKg== + dependencies: + "@babel/core" "^7.16.7" + "@babel/plugin-proposal-class-properties" "^7.16.7" + "@babel/plugin-proposal-decorators" "^7.16.7" + "@babel/preset-env" "^7.16.7" + "@embroider/macros" "^1.0.0" + "@embroider/shared-internals" "^2.0.0" + babel-loader "^8.0.6" + babel-plugin-ember-modules-api-polyfill "^3.5.0" + babel-plugin-htmlbars-inline-precompile "^5.2.1" + babel-plugin-syntax-dynamic-import "^6.18.0" + broccoli-debug "^0.6.4" + broccoli-funnel "^3.0.8" + broccoli-merge-trees "^4.2.0" + broccoli-plugin "^4.0.0" + broccoli-source "^3.0.0" + css-loader "^5.2.0" + debug "^4.3.1" + fs-extra "^10.0.0" + fs-tree-diff "^2.0.0" + handlebars "^4.3.1" + js-string-escape "^1.0.1" + lodash "^4.17.19" + mini-css-extract-plugin "^2.5.2" + parse5 "^6.0.1" + resolve "^1.20.0" + resolve-package-path "^4.0.3" + semver "^7.3.4" + style-loader "^2.0.0" + typescript-memoize "^1.0.0-alpha.3" + walk-sync "^3.0.0" + +ember-auto-import@^2.6.0: version "2.6.0" resolved "https://registry.yarnpkg.com/ember-auto-import/-/ember-auto-import-2.6.0.tgz#c7a2f799c9b700d74648cb02e35cf7bc1b44ac02" integrity sha512-xUyypxlaqWvrx2KSseLus0H8K7Dt+sXNCvcxtquT2EmIM6r67NuQUT9woiEMa9UBvqcaX2k9hNLeubDl78saig== From dbbeb9c9a2d03c36671e2ebf31d78a327eca140a Mon Sep 17 00:00:00 2001 From: Krystan HuffMenne Date: Mon, 5 Dec 2022 14:54:31 -0800 Subject: [PATCH 02/34] Add ember-cli-typescript --- package.json | 5 ++- tests/dummy/app/config/environment.d.ts | 14 +++++++++ tsconfig.json | 42 +++++++++++++++++++++++++ types/dummy/index.d.ts | 0 types/global.d.ts | 7 +++++ yarn.lock | 18 ++++++++++- 6 files changed, 84 insertions(+), 2 deletions(-) create mode 100644 tests/dummy/app/config/environment.d.ts create mode 100644 tsconfig.json create mode 100644 types/dummy/index.d.ts create mode 100644 types/global.d.ts diff --git a/package.json b/package.json index 81366758..943c8b7b 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,9 @@ "test": "npm-run-all --print-name \"lint\" \"test:*\"", "test:types": "tsc --noEmit --project types", "test:ember": "ember test", - "test:ember-compatibility": "ember try:each" + "test:ember-compatibility": "ember try:each", + "prepack": "ember ts:precompile", + "postpack": "ember ts:clean" }, "dependencies": { "broccoli-funnel": "^3.0.8", @@ -41,6 +43,7 @@ "ember-auto-import": "^2.6.0", "ember-cli-babel": "^7.26.11", "ember-cli-test-loader": "^3.0.0", + "ember-cli-typescript": "^5.2.1", "resolve-package-path": "^4.0.3", "silent-error": "^1.1.1", "validate-peer-dependencies": "^2.2.0" diff --git a/tests/dummy/app/config/environment.d.ts b/tests/dummy/app/config/environment.d.ts new file mode 100644 index 00000000..a8f92b9f --- /dev/null +++ b/tests/dummy/app/config/environment.d.ts @@ -0,0 +1,14 @@ +/** + * Type declarations for + * import config from 'my-app/config/environment' + */ +declare const config: { + environment: string; + modulePrefix: string; + podModulePrefix: string; + locationType: 'history' | 'hash' | 'none' | 'auto'; + rootURL: string; + APP: Record; +}; + +export default config; diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 00000000..4259d032 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,42 @@ +{ + "extends": "@tsconfig/ember/tsconfig.json", + "compilerOptions": { + + // The combination of `baseUrl` with `paths` allows Ember's classic package + // layout, which is not resolvable with the Node resolution algorithm, to + // work with TypeScript. + "baseUrl": ".", + "paths": { + "dummy/tests/*": [ + "tests/*" + ], + "dummy/*": [ + "tests/dummy/app/*", + "app/*" + ], + "ember-qunit": [ + "addon" + ], + "ember-qunit/*": [ + "addon/*" + ], + "ember-qunit/test-support": [ + "addon-test-support" + ], + "ember-qunit/test-support/*": [ + "addon-test-support/*" + ], + "*": [ + "types/*" + ] + } + }, + "include": [ + "app/**/*", + "addon/**/*", + "tests/**/*", + "types/**/*", + "test-support/**/*", + "addon-test-support/**/*" + ] +} diff --git a/types/dummy/index.d.ts b/types/dummy/index.d.ts new file mode 100644 index 00000000..e69de29b diff --git a/types/global.d.ts b/types/global.d.ts new file mode 100644 index 00000000..a9d80774 --- /dev/null +++ b/types/global.d.ts @@ -0,0 +1,7 @@ +// Types for compiled templates +declare module 'ember-qunit/templates/*' { + import { TemplateFactory } from 'ember-cli-htmlbars'; + + const tmpl: TemplateFactory; + export default tmpl; +} diff --git a/yarn.lock b/yarn.lock index 3e853631..23feeecf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2062,7 +2062,7 @@ ansi-styles@^6.1.0: resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.1.tgz#0e62320cf99c21afff3b3012192546aacbfb05c5" integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug== -ansi-to-html@^0.6.6: +ansi-to-html@^0.6.15, ansi-to-html@^0.6.6: version "0.6.15" resolved "https://registry.yarnpkg.com/ansi-to-html/-/ansi-to-html-0.6.15.tgz#ac6ad4798a00f6aa045535d7f6a9cb9294eebea7" integrity sha512-28ijx2aHJGdzbs+O5SNQF65r6rrKYnkuwTYm8lZlChuoJ9P1vVzIpWO20sQTqTPDXYp6NFwk326vApTtLVFXpQ== @@ -4203,6 +4203,22 @@ ember-cli-typescript@^2.0.2: stagehand "^1.0.0" walk-sync "^1.0.0" +ember-cli-typescript@^5.2.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/ember-cli-typescript/-/ember-cli-typescript-5.2.1.tgz#553030f1ce3e8958b8e4fc34909acd1218cb35f2" + integrity sha512-qqp5TAIuPHxHiGXJKL+78Euyhy0zSKQMovPh8sJpN/ZBYx0H90pONufHR3anaMcp1snVfx4B+mb9+7ijOik8ZA== + dependencies: + ansi-to-html "^0.6.15" + broccoli-stew "^3.0.0" + debug "^4.0.0" + execa "^4.0.0" + fs-extra "^9.0.1" + resolve "^1.5.0" + rsvp "^4.8.1" + semver "^7.3.2" + stagehand "^1.0.0" + walk-sync "^2.2.0" + ember-cli-version-checker@^2.1.2: version "2.2.0" resolved "https://registry.yarnpkg.com/ember-cli-version-checker/-/ember-cli-version-checker-2.2.0.tgz#47771b731fe0962705e27c8199a9e3825709f3b3" From 184ddd157a1d58ad8f651a7bc8cc271915a370af Mon Sep 17 00:00:00 2001 From: Krystan HuffMenne Date: Mon, 5 Dec 2022 14:56:31 -0800 Subject: [PATCH 03/34] Check tsc as lint step --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 943c8b7b..d799260c 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "lint:fix": "npm-run-all --print-name --aggregate-output --continue-on-error --parallel \"lint:*:fix\"", "lint:js": "eslint . --cache", "lint:js:fix": "eslint . --fix", + "lint:ts": "tsc --noEmit", "start": "ember serve", "test": "npm-run-all --print-name \"lint\" \"test:*\"", "test:types": "tsc --noEmit --project types", From 48958d6fecda625229643bf4a6581e8ec6d6e3c0 Mon Sep 17 00:00:00 2001 From: Krystan HuffMenne Date: Mon, 5 Dec 2022 15:03:16 -0800 Subject: [PATCH 04/34] Configure ts/eslint strictness --- .eslintrc.js | 29 ++++++++++++ package.json | 9 ++++ tsconfig.json | 45 +++++++++---------- yarn.lock | 120 ++++++++++++++++++++++++++++++++++++++++++++++++-- 4 files changed, 175 insertions(+), 28 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 1dd2a810..36102a1c 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -45,6 +45,35 @@ module.exports = { extends: ['plugin:node/recommended'], }, + // ts files + { + files: ['**/*.ts'], + extends: [ + 'eslint:recommended', + 'plugin:@typescript-eslint/recommended', + 'plugin:@typescript-eslint/recommended-requiring-type-checking', + 'plugin:@typescript-eslint/strict', + ], + parser: '@typescript-eslint/parser', + parserOptions: { + tsconfigRootDir: __dirname, + project: ['./tsconfig.json'], + }, + plugins: ['@typescript-eslint'], + rules: { + '@typescript-eslint/consistent-type-exports': 'error', + '@typescript-eslint/consistent-type-imports': 'error', + '@typescript-eslint/explicit-function-return-type': 'error', + '@typescript-eslint/method-signature-style': 'error', + '@typescript-eslint/no-confusing-void-expression': 'error', + '@typescript-eslint/no-redundant-type-constituents': 'error', + '@typescript-eslint/prefer-enum-initializers': 'error', + '@typescript-eslint/prefer-readonly': 'error', + '@typescript-eslint/prefer-readonly-parameter-types': 'error', + '@typescript-eslint/promise-function-async': 'error', + }, + }, + // test files { files: ['tests/**/*.js'], diff --git a/package.json b/package.json index d799260c..130e2f7e 100644 --- a/package.json +++ b/package.json @@ -59,6 +59,8 @@ "@tsconfig/ember": "^2.0.0", "@types/qunit": "^2.19.4", "@types/rsvp": "^4.0.4", + "@typescript-eslint/eslint-plugin": "^5.45.1", + "@typescript-eslint/parser": "^5.45.1", "ember-angle-bracket-invocation-polyfill": "^3.0.2", "ember-cli": "~4.10.0", "ember-cli-dependency-checker": "^3.3.1", @@ -131,5 +133,12 @@ "volta": { "node": "14.19.1", "yarn": "1.22.18" + }, + "typesVersions": { + "*": { + "*": [ + "test-support/*" + ] + } } } diff --git a/tsconfig.json b/tsconfig.json index 4259d032..b01885cf 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,34 +1,18 @@ { "extends": "@tsconfig/ember/tsconfig.json", "compilerOptions": { - // The combination of `baseUrl` with `paths` allows Ember's classic package // layout, which is not resolvable with the Node resolution algorithm, to // work with TypeScript. "baseUrl": ".", "paths": { - "dummy/tests/*": [ - "tests/*" - ], - "dummy/*": [ - "tests/dummy/app/*", - "app/*" - ], - "ember-qunit": [ - "addon" - ], - "ember-qunit/*": [ - "addon/*" - ], - "ember-qunit/test-support": [ - "addon-test-support" - ], - "ember-qunit/test-support/*": [ - "addon-test-support/*" - ], - "*": [ - "types/*" - ] + "dummy/tests/*": ["tests/*"], + "dummy/*": ["tests/dummy/app/*", "app/*"], + "ember-qunit": ["addon"], + "ember-qunit/*": ["addon/*"], + "ember-qunit/test-support": ["addon-test-support"], + "ember-qunit/test-support/*": ["addon-test-support/*"], + "*": ["types/*"] } }, "include": [ @@ -38,5 +22,18 @@ "types/**/*", "test-support/**/*", "addon-test-support/**/*" - ] + ], + + // type checking + "strict": true, + "allowUnreachableCode": false, + "allowUnusedLabels": false, + "exactOptionalPropertyTypes": true, + "noFallthroughCasesInSwitch": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noPropertyAccessFromIndexSignature": true, + "noUncheckedIndexedAccess": true, + "noUnusedLocals": true, + "noUnusedParameters": true } diff --git a/yarn.lock b/yarn.lock index 23feeecf..c3c8a5d4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1719,6 +1719,11 @@ resolved "https://registry.yarnpkg.com/@types/rsvp/-/rsvp-4.0.4.tgz#55e93e7054027f1ad4b4ebc1e60e59eb091e2d32" integrity sha512-J3Ol++HCC7/hwZhanDvggFYU/GtxHxE/e7cGRWxR04BF7Tt3TqJZ84BkzQgDxmX0uu8IagiyfmfoUlBACh2Ilg== +"@types/semver@^7.3.12": + version "7.3.13" + resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.3.13.tgz#da4bfd73f49bd541d28920ab0e2bf0ee80f71c91" + integrity sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw== + "@types/serve-static@*": version "1.15.0" resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.15.0.tgz#c7930ff61afb334e121a9da780aac0d9b8f34155" @@ -1737,6 +1742,89 @@ resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.6.tgz#250a7b16c3b91f672a24552ec64678eeb1d3a08d" integrity sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ== +"@typescript-eslint/eslint-plugin@^5.45.1": + version "5.45.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.45.1.tgz#ee5b51405f6c9ee7e60e4006d68c69450d3b4536" + integrity sha512-cOizjPlKEh0bXdFrBLTrI/J6B/QMlhwE9auOov53tgB+qMukH6/h8YAK/qw+QJGct/PTbdh2lytGyipxCcEtAw== + dependencies: + "@typescript-eslint/scope-manager" "5.45.1" + "@typescript-eslint/type-utils" "5.45.1" + "@typescript-eslint/utils" "5.45.1" + debug "^4.3.4" + ignore "^5.2.0" + natural-compare-lite "^1.4.0" + regexpp "^3.2.0" + semver "^7.3.7" + tsutils "^3.21.0" + +"@typescript-eslint/parser@^5.45.1": + version "5.45.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.45.1.tgz#6440ec283fa1373a12652d4e2fef4cb6e7b7e8c6" + integrity sha512-JQ3Ep8bEOXu16q0ztsatp/iQfDCtvap7sp/DKo7DWltUquj5AfCOpX2zSzJ8YkAVnrQNqQ5R62PBz2UtrfmCkA== + dependencies: + "@typescript-eslint/scope-manager" "5.45.1" + "@typescript-eslint/types" "5.45.1" + "@typescript-eslint/typescript-estree" "5.45.1" + debug "^4.3.4" + +"@typescript-eslint/scope-manager@5.45.1": + version "5.45.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.45.1.tgz#5b87d025eec7035d879b99c260f03be5c247883c" + integrity sha512-D6fCileR6Iai7E35Eb4Kp+k0iW7F1wxXYrOhX/3dywsOJpJAQ20Fwgcf+P/TDtvQ7zcsWsrJaglaQWDhOMsspQ== + dependencies: + "@typescript-eslint/types" "5.45.1" + "@typescript-eslint/visitor-keys" "5.45.1" + +"@typescript-eslint/type-utils@5.45.1": + version "5.45.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.45.1.tgz#cb7d300c3c95802cea9f87c7f8be363cf8f8538c" + integrity sha512-aosxFa+0CoYgYEl3aptLe1svP910DJq68nwEJzyQcrtRhC4BN0tJAvZGAe+D0tzjJmFXe+h4leSsiZhwBa2vrA== + dependencies: + "@typescript-eslint/typescript-estree" "5.45.1" + "@typescript-eslint/utils" "5.45.1" + debug "^4.3.4" + tsutils "^3.21.0" + +"@typescript-eslint/types@5.45.1": + version "5.45.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.45.1.tgz#8e1883041cee23f1bb7e1343b0139f97f6a17c14" + integrity sha512-HEW3U0E5dLjUT+nk7b4lLbOherS1U4ap+b9pfu2oGsW3oPu7genRaY9dDv3nMczC1rbnRY2W/D7SN05wYoGImg== + +"@typescript-eslint/typescript-estree@5.45.1": + version "5.45.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.45.1.tgz#b3dc37f0c4f0fe73e09917fc735e6f96eabf9ba4" + integrity sha512-76NZpmpCzWVrrb0XmYEpbwOz/FENBi+5W7ipVXAsG3OoFrQKJMiaqsBMbvGRyLtPotGqUfcY7Ur8j0dksDJDng== + dependencies: + "@typescript-eslint/types" "5.45.1" + "@typescript-eslint/visitor-keys" "5.45.1" + debug "^4.3.4" + globby "^11.1.0" + is-glob "^4.0.3" + semver "^7.3.7" + tsutils "^3.21.0" + +"@typescript-eslint/utils@5.45.1": + version "5.45.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.45.1.tgz#39610c98bde82c4792f2a858b29b7d0053448be2" + integrity sha512-rlbC5VZz68+yjAzQBc4I7KDYVzWG2X/OrqoZrMahYq3u8FFtmQYc+9rovo/7wlJH5kugJ+jQXV5pJMnofGmPRw== + dependencies: + "@types/json-schema" "^7.0.9" + "@types/semver" "^7.3.12" + "@typescript-eslint/scope-manager" "5.45.1" + "@typescript-eslint/types" "5.45.1" + "@typescript-eslint/typescript-estree" "5.45.1" + eslint-scope "^5.1.1" + eslint-utils "^3.0.0" + semver "^7.3.7" + +"@typescript-eslint/visitor-keys@5.45.1": + version "5.45.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.45.1.tgz#204428430ad6a830d24c5ac87c71366a1cfe1948" + integrity sha512-cy9ln+6rmthYWjH9fmx+5FU/JDpjQb586++x2FZlveq7GdGuLLW9a2Jcst2TGekH82bXpfmRNSwP9tyEs6RjvQ== + dependencies: + "@typescript-eslint/types" "5.45.1" + eslint-visitor-keys "^3.3.0" + "@webassemblyjs/ast@1.11.1": version "1.11.1" resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.11.1.tgz#2bfd767eae1a6996f432ff7e8d7fc75679c0b6a7" @@ -3699,7 +3787,7 @@ debug@2.6.9, debug@^2.1.0, debug@^2.1.1, debug@^2.1.3, debug@^2.2.0, debug@^2.3. dependencies: ms "2.0.0" -debug@4, debug@^4.0.0, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.3, debug@~4.3.1, debug@~4.3.2: +debug@4, debug@^4.0.0, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.3, debug@^4.3.4, debug@~4.3.1, debug@~4.3.2: version "4.3.4" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== @@ -4773,7 +4861,7 @@ eslint-plugin-prettier@^4.2.1: dependencies: prettier-linter-helpers "^1.0.0" -eslint-scope@5.1.1: +eslint-scope@5.1.1, eslint-scope@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== @@ -5134,7 +5222,7 @@ fast-diff@^1.1.2: resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.2.0.tgz#73ee11982d86caaf7959828d519cfe927fac5f03" integrity sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w== -fast-glob@^3.0.3, fast-glob@^3.2.11: +fast-glob@^3.0.3, fast-glob@^3.2.11, fast-glob@^3.2.9: version "3.2.12" resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.12.tgz#7f39ec99c2e6ab030337142da9e0c18f37afae80" integrity sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w== @@ -5858,6 +5946,18 @@ globby@13.1.2: merge2 "^1.4.1" slash "^4.0.0" +globby@^11.1.0: + version "11.1.0" + resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" + integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== + dependencies: + array-union "^2.1.0" + dir-glob "^3.0.1" + fast-glob "^3.2.9" + ignore "^5.2.0" + merge2 "^1.4.1" + slash "^3.0.0" + globrex@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/globrex/-/globrex-0.1.2.tgz#dd5d9ec826232730cd6793a5e33a9302985e6098" @@ -8077,6 +8177,11 @@ nanomatch@^1.2.9: snapdragon "^0.8.1" to-regex "^3.0.1" +natural-compare-lite@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz#17b09581988979fddafe0201e931ba933c96cbb4" + integrity sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g== + natural-compare@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" @@ -10574,7 +10679,7 @@ tree-sync@^2.0.0, tree-sync@^2.1.0: quick-temp "^0.1.5" walk-sync "^0.3.3" -tslib@^1.9.0: +tslib@^1.8.1, tslib@^1.9.0: version "1.14.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== @@ -10584,6 +10689,13 @@ tslib@^2.0.1, tslib@^2.1.0: resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.0.tgz#7cecaa7f073ce680a05847aa77be941098f36dc3" integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ== +tsutils@^3.21.0: + version "3.21.0" + resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623" + integrity sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA== + dependencies: + tslib "^1.8.1" + type-check@^0.4.0, type-check@~0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" From 2ac042f8f5965d0e7e939a87d44a8a4fd024362c Mon Sep 17 00:00:00 2001 From: Krystan HuffMenne Date: Mon, 5 Dec 2022 15:21:10 -0800 Subject: [PATCH 05/34] Rename adddon-test-support/adapter to TS --- addon-test-support/{adapter.js => adapter.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename addon-test-support/{adapter.js => adapter.ts} (100%) diff --git a/addon-test-support/adapter.js b/addon-test-support/adapter.ts similarity index 100% rename from addon-test-support/adapter.js rename to addon-test-support/adapter.ts From 874ba77213af05707ad5d98d1bf4b416d4ea4f80 Mon Sep 17 00:00:00 2001 From: Krystan HuffMenne Date: Mon, 5 Dec 2022 16:46:58 -0800 Subject: [PATCH 06/34] Fix type checking errors for adapter.ts --- .eslintrc.js | 2 +- addon-test-support/adapter.ts | 85 +++++++++++++++++++++----------- addon-test-support/types/util.ts | 20 ++++++++ types/global.d.ts | 2 +- 4 files changed, 77 insertions(+), 32 deletions(-) create mode 100644 addon-test-support/types/util.ts diff --git a/.eslintrc.js b/.eslintrc.js index 36102a1c..7a07c074 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -61,6 +61,7 @@ module.exports = { }, plugins: ['@typescript-eslint'], rules: { + '@typescript-eslint/array-type': ['error', { default: 'array-simple' }], '@typescript-eslint/consistent-type-exports': 'error', '@typescript-eslint/consistent-type-imports': 'error', '@typescript-eslint/explicit-function-return-type': 'error', @@ -69,7 +70,6 @@ module.exports = { '@typescript-eslint/no-redundant-type-constituents': 'error', '@typescript-eslint/prefer-enum-initializers': 'error', '@typescript-eslint/prefer-readonly': 'error', - '@typescript-eslint/prefer-readonly-parameter-types': 'error', '@typescript-eslint/promise-function-async': 'error', }, }, diff --git a/addon-test-support/adapter.ts b/addon-test-support/adapter.ts index 7673f392..36f81ed4 100644 --- a/addon-test-support/adapter.ts +++ b/addon-test-support/adapter.ts @@ -1,61 +1,84 @@ +// FIXME: Remove +import 'ember-source/types'; +import 'ember-source/types/preview'; + +import hasEmberVersion from '@ember/test-helpers/has-ember-version'; import Ember from 'ember'; +import { isRecord, isTest } from 'ember-qunit/test-support/types/util'; import * as QUnit from 'qunit'; -import hasEmberVersion from '@ember/test-helpers/has-ember-version'; -function unhandledRejectionAssertion(current, error) { - let message, source; +function unhandledRejectionAssertion(current: unknown, error: unknown): void { + let message: string; + let source: string | undefined; - if (typeof error === 'object' && error !== null) { - message = error.message; - source = error.stack; + if ( + isRecord(error) && + 'message' in error && + typeof error['message'] === 'string' + ) { + message = error['message']; + source = typeof error['stack'] === 'string' ? error['stack'] : undefined; } else if (typeof error === 'string') { message = error; source = 'unknown source'; } else { - message = 'unhandledRejection occured, but it had no message'; + message = 'unhandledRejection occurred, but it had no message'; source = 'unknown source'; } - current.assert.pushResult({ - result: false, - actual: false, - expected: true, - message: message, - source: source, - }); + if (isTest(current)) { + current.assert?.pushResult({ + result: false, + actual: false, + expected: true, + message: message, + // @ts-expect-error FIXME: Update qunit type https://github.com/qunitjs/qunit/blob/fc278e8c0d7e90ec42e47b47eee1cc85c9a9efaf/docs/callbacks/QUnit.log.md?plain=1#L32 + source, + }); + } } -export function nonTestDoneCallback() {} +export function nonTestDoneCallback(): void { + // no-op +} + +// FIXME: What is Ember.Test.QUnitAdapter? +interface QUnitAdapter { + doneCallbacks: Array<{ test: unknown; done: () => void }>; + qunit: QUnit; +} +// @ts-expect-error FIXME `extend` does not exist on Adapter +// eslint-disable-next-line @typescript-eslint/no-unsafe-call let Adapter = Ember.Test.Adapter.extend({ - init() { + init(this: QUnitAdapter) { this.doneCallbacks = []; - this.qunit = this.qunit || QUnit; + this.qunit ??= QUnit; }, - asyncStart() { - let currentTest = this.qunit.config.current; - let done = - currentTest && currentTest.assert + asyncStart(this: QUnitAdapter) { + const currentTest: unknown = this.qunit.config.current; + const done = + isTest(currentTest) && currentTest.assert ? currentTest.assert.async() : nonTestDoneCallback; this.doneCallbacks.push({ test: currentTest, done }); }, - asyncEnd() { - let currentTest = this.qunit.config.current; + asyncEnd(this: QUnitAdapter) { + const currentCallback = this.doneCallbacks.pop(); - if (this.doneCallbacks.length === 0) { + if (!currentCallback) { throw new Error( 'Adapter asyncEnd called when no async was expected. Please create an issue in ember-qunit.' ); } - let { test, done } = this.doneCallbacks.pop(); + const { test, done } = currentCallback; // In future, we should explore fixing this at a different level, specifically // addressing the pairing of asyncStart/asyncEnd behavior in a more consistent way. - if (test === currentTest) { + if (test === this.qunit.config.current) { done(); } }, @@ -63,18 +86,20 @@ let Adapter = Ember.Test.Adapter.extend({ // clobber default implementation of `exception` will be added back for Ember // < 2.17 just below... exception: null, -}); +}) as QUnitAdapter; // Ember 2.17 and higher do not require the test adapter to have an `exception` // method When `exception` is not present, the unhandled rejection is // automatically re-thrown and will therefore hit QUnit's own global error // handler (therefore appropriately causing test failure) if (!hasEmberVersion(2, 17)) { + // @ts-expect-error FIXME `extend` does not exist on Adapter + // eslint-disable-next-line @typescript-eslint/no-unsafe-call Adapter = Adapter.extend({ - exception(error) { - unhandledRejectionAssertion(QUnit.config.current, error); + exception(error: unknown) { + unhandledRejectionAssertion(QUnit.config.current as unknown, error); }, - }); + }) as QUnitAdapter; } export default Adapter; diff --git a/addon-test-support/types/util.ts b/addon-test-support/types/util.ts new file mode 100644 index 00000000..2f68ce84 --- /dev/null +++ b/addon-test-support/types/util.ts @@ -0,0 +1,20 @@ +import type { test } from 'qunit'; + +/** Checks if the given value is a `Record`. */ +export function isRecord(value: unknown): value is Record { + return value !== null && typeof value === 'object'; +} + +type TestBase = typeof test & { testId: string }; + +export type SkippedTest = TestBase & { skip: true; assert?: undefined }; + +export type AssertionTest = TestBase & { assert: Assert }; + +// FIXME: What is the appropriate Test type? There doesn't appear to be a test type exported from qunit +// https://github.com/qunitjs/qunit/blob/main/src/test.js#L23 +export type Test = SkippedTest | AssertionTest; + +export function isTest(value: unknown): value is Test { + return typeof value === 'function' && 'testId' in value; +} diff --git a/types/global.d.ts b/types/global.d.ts index a9d80774..e4e2ef2b 100644 --- a/types/global.d.ts +++ b/types/global.d.ts @@ -1,6 +1,6 @@ // Types for compiled templates declare module 'ember-qunit/templates/*' { - import { TemplateFactory } from 'ember-cli-htmlbars'; + import type { TemplateFactory } from 'ember-cli-htmlbars'; const tmpl: TemplateFactory; export default tmpl; From db410ef6a56d25ed261b12904729c134b365787a Mon Sep 17 00:00:00 2001 From: Krystan HuffMenne Date: Tue, 6 Dec 2022 11:28:08 -0800 Subject: [PATCH 07/34] Rename addon-test-support/test-isoliation-validation to TS --- ...{test-isolation-validation.js => test-isolation-validation.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename addon-test-support/{test-isolation-validation.js => test-isolation-validation.ts} (100%) diff --git a/addon-test-support/test-isolation-validation.js b/addon-test-support/test-isolation-validation.ts similarity index 100% rename from addon-test-support/test-isolation-validation.js rename to addon-test-support/test-isolation-validation.ts From 4c66f5c930640cac8352366205d199108ecedf53 Mon Sep 17 00:00:00 2001 From: Krystan HuffMenne Date: Fri, 9 Dec 2022 16:26:24 -0800 Subject: [PATCH 08/34] Fix type checking errors for test-isolation-validation.ts --- addon-test-support/adapter.ts | 32 ++++---- .../test-isolation-validation.ts | 60 +++++++++++---- addon-test-support/types/util.ts | 77 +++++++++++++++++-- 3 files changed, 133 insertions(+), 36 deletions(-) diff --git a/addon-test-support/adapter.ts b/addon-test-support/adapter.ts index 36f81ed4..c4db0fa7 100644 --- a/addon-test-support/adapter.ts +++ b/addon-test-support/adapter.ts @@ -2,11 +2,14 @@ import 'ember-source/types'; import 'ember-source/types/preview'; +import { assert } from '@ember/debug'; import hasEmberVersion from '@ember/test-helpers/has-ember-version'; import Ember from 'ember'; -import { isRecord, isTest } from 'ember-qunit/test-support/types/util'; + import * as QUnit from 'qunit'; +import { isRecord, isTest } from 'ember-qunit/test-support/types/util'; + function unhandledRejectionAssertion(current: unknown, error: unknown): void { let message: string; let source: string | undefined; @@ -26,16 +29,18 @@ function unhandledRejectionAssertion(current: unknown, error: unknown): void { source = 'unknown source'; } - if (isTest(current)) { - current.assert?.pushResult({ - result: false, - actual: false, - expected: true, - message: message, - // @ts-expect-error FIXME: Update qunit type https://github.com/qunitjs/qunit/blob/fc278e8c0d7e90ec42e47b47eee1cc85c9a9efaf/docs/callbacks/QUnit.log.md?plain=1#L32 - source, - }); - } + assert( + 'expected current test to have an assert', + isTest(current) && 'assert' in current + ); + current.assert.pushResult({ + result: false, + actual: false, + expected: true, + message: message, + // @ts-expect-error FIXME: Update qunit type https://github.com/qunitjs/qunit/blob/fc278e8c0d7e90ec42e47b47eee1cc85c9a9efaf/docs/callbacks/QUnit.log.md?plain=1#L32 + source, + }); } export function nonTestDoneCallback(): void { @@ -59,7 +64,7 @@ let Adapter = Ember.Test.Adapter.extend({ asyncStart(this: QUnitAdapter) { const currentTest: unknown = this.qunit.config.current; const done = - isTest(currentTest) && currentTest.assert + isTest(currentTest) && 'assert' in currentTest ? currentTest.assert.async() : nonTestDoneCallback; this.doneCallbacks.push({ test: currentTest, done }); @@ -97,7 +102,8 @@ if (!hasEmberVersion(2, 17)) { // eslint-disable-next-line @typescript-eslint/no-unsafe-call Adapter = Adapter.extend({ exception(error: unknown) { - unhandledRejectionAssertion(QUnit.config.current as unknown, error); + const currentTest: unknown = QUnit.config.current; + unhandledRejectionAssertion(currentTest, error); }, }) as QUnitAdapter; } diff --git a/addon-test-support/test-isolation-validation.ts b/addon-test-support/test-isolation-validation.ts index f0cebe51..fdf16e30 100644 --- a/addon-test-support/test-isolation-validation.ts +++ b/addon-test-support/test-isolation-validation.ts @@ -1,8 +1,19 @@ /* eslint-disable no-console */ -import * as QUnit from 'qunit'; + +import { assert } from '@ember/debug'; +// @ts-expect-error FIXME import { _cancelTimers as cancelTimers } from '@ember/runloop'; -import { waitUntil, isSettled, getSettledState } from '@ember/test-helpers'; -import { getDebugInfo } from '@ember/test-helpers'; +import { + getDebugInfo, + getSettledState, + isSettled, + waitUntil, +} from '@ember/test-helpers'; + +import * as QUnit from 'qunit'; + +import type { Test } from 'ember-qunit/test-support/types/util'; +import { isTest } from 'ember-qunit/test-support/types/util'; /** * Detects if a specific test isn't isolated. A test is considered @@ -18,18 +29,22 @@ import { getDebugInfo } from '@ember/test-helpers'; * @param {string} testInfo.module The name of the test module * @param {string} testInfo.name The test name */ -export function detectIfTestNotIsolated(test, message = '') { +export function detectIfTestNotIsolated(test: Test, message = ''): void { if (!isSettled()) { - let { debugInfo } = getSettledState(); + const { debugInfo } = getSettledState(); console.group(`${test.module.name}: ${test.testName}`); - debugInfo.toConsole(); + debugInfo?.toConsole(); console.groupEnd(); - test.expected++; + assert('detectIfTestNotIsolated called on skipped test', !test.skip); + + test.expected = (test.expected ?? 0) + 1; test.assert.pushResult({ result: false, message: `${message} \nMore information has been printed to the console. Please use that information to help in debugging.\n\n`, + actual: null, + expected: null, }); } } @@ -42,20 +57,29 @@ export function detectIfTestNotIsolated(test, message = '') { * @function installTestNotIsolatedHook * @param {number} delay the delay delay to use when checking for isolation validation */ -export function installTestNotIsolatedHook(delay = 50) { +export function installTestNotIsolatedHook(delay = 50): void { if (!getDebugInfo()) { return; } - let test = QUnit.config.current; - let finish = test.finish; - let pushFailure = test.pushFailure; + const test: unknown = QUnit.config.current; + + assert( + 'installTestNotIsolatedHook called with no current test', + isTest(test) + ); + + const finish = test.finish; + const pushFailure = test.pushFailure; - test.pushFailure = function (message) { - if (message.indexOf('Test took longer than') === 0) { + test.pushFailure = function ( + ...args: Parameters + ): ReturnType { + const [message] = args; + if (message.startsWith('Test took longer than')) { detectIfTestNotIsolated(this, message); } else { - return pushFailure.apply(this, arguments); + pushFailure.apply(this, args); } }; @@ -74,8 +98,10 @@ export function installTestNotIsolatedHook(delay = 50) { // - 'QUnit.done' // - it detaches the failure from the actual test that failed, making it // more confusing to the end user. - test.finish = function () { - let doFinish = () => finish.apply(this, arguments); + test.finish = function ( + ...args: Parameters + ): ReturnType { + const doFinish = (): ReturnType => finish.apply(this, args); if (isSettled()) { return doFinish(); @@ -96,6 +122,8 @@ export function installTestNotIsolatedHook(delay = 50) { // canceling timers here isn't perfect, but is as good as we can do // to attempt to prevent future tests from failing due to this test's // leakage + // FIXME + // eslint-disable-next-line @typescript-eslint/no-unsafe-call cancelTimers(); return doFinish(); diff --git a/addon-test-support/types/util.ts b/addon-test-support/types/util.ts index 2f68ce84..37287ff5 100644 --- a/addon-test-support/types/util.ts +++ b/addon-test-support/types/util.ts @@ -1,20 +1,83 @@ -import type { test } from 'qunit'; - /** Checks if the given value is a `Record`. */ export function isRecord(value: unknown): value is Record { return value !== null && typeof value === 'object'; } -type TestBase = typeof test & { testId: string }; +type Hook = (assert: Assert) => void | Promise; -export type SkippedTest = TestBase & { skip: true; assert?: undefined }; +type TestCallback = (assert: Assert) => void | Promise; -export type AssertionTest = TestBase & { assert: Assert }; +// https://github.com/qunitjs/qunit/blob/fc278e8c0d7e90ec42e47b47eee1cc85c9a9efaf/src/core/config.js#L84 +interface Module { + name: string; + tests: Array<{ name: string; id: string; skip: boolean }>; + childModules: Module[]; // FIXME: verify + testsRun: number; + testsIgnored: number; + hooks: { + before: Hook[]; + beforeEach: Hook[]; + afterEach: Hook[]; + after: Hook[]; + }; + skip?: boolean; + ignored?: boolean; +} // FIXME: What is the appropriate Test type? There doesn't appear to be a test type exported from qunit // https://github.com/qunitjs/qunit/blob/main/src/test.js#L23 -export type Test = SkippedTest | AssertionTest; +interface TestBase { + testId: string; + testName: string; + expected: null | number; + assertions: Array<{ result: boolean; message: string }>; + module: Module; + steps: unknown[]; + timeout: undefined; + data: unknown; + withData: boolean; + pauses: Map; + nextPauseId: 1; + stackOffset: 0 | 1 | 2 | 3 | 5; + errorForStack: Error; + testReport: unknown; + stack: string; + before: () => unknown; + run: () => unknown; + after: () => void; + queueGlobalHook: (hook: unknown, hookName: unknown) => () => unknown; + queueHook: ( + hook: unknown, + hookName: unknown, + hookOwner: unknown + ) => () => unknown; + hooks: (handler: unknown) => unknown; + finish: () => unknown; + preserveTestEnvironment: () => unknown; + queue: () => void; + pushResult: (resultInfo: unknown) => void; + pushFailure: (message: string, source: string, actual: unknown) => void; + skip?: true; + callback: TestCallback; + todo?: boolean; + async?: boolean; +} + +export interface AssertionTest extends TestBase { + assert: Assert; +} + +export interface SkipTest extends TestBase { + skip: true; + async: false; +} +export interface TodoTest extends TestBase { + todo: true; + assert: Assert; +} + +export type Test = AssertionTest | SkipTest | TodoTest; export function isTest(value: unknown): value is Test { - return typeof value === 'function' && 'testId' in value; + return isRecord(value) && 'testId' in value; } From 2744e02a34026acf4ecc67b55981c5257a2597f4 Mon Sep 17 00:00:00 2001 From: Krystan HuffMenne Date: Fri, 9 Dec 2022 16:27:22 -0800 Subject: [PATCH 09/34] Rename qunit-configuration to TS --- .../{qunit-configuration.js => qunit-configuration.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename addon-test-support/{qunit-configuration.js => qunit-configuration.ts} (100%) diff --git a/addon-test-support/qunit-configuration.js b/addon-test-support/qunit-configuration.ts similarity index 100% rename from addon-test-support/qunit-configuration.js rename to addon-test-support/qunit-configuration.ts From a11495e55eb3c861bbfed1d9f2120c41b3cb9184 Mon Sep 17 00:00:00 2001 From: Krystan HuffMenne Date: Fri, 9 Dec 2022 16:30:35 -0800 Subject: [PATCH 10/34] Fix type checking errors for qunit-configuration --- addon-test-support/qunit-configuration.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/addon-test-support/qunit-configuration.ts b/addon-test-support/qunit-configuration.ts index 20abdd0c..9c563b95 100644 --- a/addon-test-support/qunit-configuration.ts +++ b/addon-test-support/qunit-configuration.ts @@ -5,4 +5,6 @@ QUnit.config.urlConfig.push({ id: 'nocontainer', label: 'Hide container' }); QUnit.config.urlConfig.push({ id: 'nolint', label: 'Disable Linting' }); QUnit.config.urlConfig.push({ id: 'devmode', label: 'Development mode' }); +// @ts-expect-error FIXME qunit types are incorrect +// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access QUnit.config.testTimeout = QUnit.urlParams.devmode ? null : 60000; //Default Test Timeout 60 Seconds From 588c69fe078475d352a68edfa9ce1941f805f86e Mon Sep 17 00:00:00 2001 From: Krystan HuffMenne Date: Fri, 9 Dec 2022 16:31:39 -0800 Subject: [PATCH 11/34] Rename test-loader to TS --- addon-test-support/{test-loader.js => test-loader.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename addon-test-support/{test-loader.js => test-loader.ts} (100%) diff --git a/addon-test-support/test-loader.js b/addon-test-support/test-loader.ts similarity index 100% rename from addon-test-support/test-loader.js rename to addon-test-support/test-loader.ts From a9f42311dd30b0f766c7dd64aca24d48d858dd73 Mon Sep 17 00:00:00 2001 From: Krystan HuffMenne Date: Fri, 9 Dec 2022 17:00:35 -0800 Subject: [PATCH 12/34] Fix type checking errors on test-loader --- addon-test-support/test-loader.ts | 18 +++++++++++------- .../test-support/index.d.ts | 17 +++++++++++++++++ 2 files changed, 28 insertions(+), 7 deletions(-) create mode 100644 types/ember-cli-test-loader/test-support/index.d.ts diff --git a/addon-test-support/test-loader.ts b/addon-test-support/test-loader.ts index 1d111f4e..4eaf9475 100644 --- a/addon-test-support/test-loader.ts +++ b/addon-test-support/test-loader.ts @@ -1,26 +1,29 @@ -import * as QUnit from 'qunit'; import AbstractTestLoader, { addModuleExcludeMatcher, addModuleIncludeMatcher, } from 'ember-cli-test-loader/test-support/index'; +import * as QUnit from 'qunit'; addModuleExcludeMatcher(function (moduleName) { - return QUnit.urlParams.nolint && moduleName.match(/\.(jshint|lint-test)$/); + // @ts-expect-error FIXME Qunit types argh + // eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-member-access + return QUnit.urlParams.nolint && /\.(jshint|lint-test)$/.test(moduleName); }); addModuleIncludeMatcher(function (moduleName) { - return moduleName.match(/\.jshint$/); + return moduleName.endsWith('.jshint'); }); -let moduleLoadFailures = []; +let moduleLoadFailures: Array<{ stack: unknown }> = []; QUnit.done(function () { - let length = moduleLoadFailures.length; + const length = moduleLoadFailures.length; try { if (length === 0) { // do nothing } else if (length === 1) { + // eslint-disable-next-line @typescript-eslint/no-throw-literal throw moduleLoadFailures[0]; } else { throw new Error('\n' + moduleLoadFailures.join('\n')); @@ -32,11 +35,12 @@ QUnit.done(function () { }); export class TestLoader extends AbstractTestLoader { - moduleLoadFailure(moduleName, error) { + moduleLoadFailure(moduleName: string, error: { stack: unknown }): void { moduleLoadFailures.push(error); QUnit.module('TestLoader Failures'); QUnit.test(moduleName + ': could not be loaded', function () { + // eslint-disable-next-line @typescript-eslint/no-throw-literal throw error; }); } @@ -56,6 +60,6 @@ export class TestLoader extends AbstractTestLoader { @method loadTests */ -export function loadTests() { +export function loadTests(): void { new TestLoader().loadModules(); } diff --git a/types/ember-cli-test-loader/test-support/index.d.ts b/types/ember-cli-test-loader/test-support/index.d.ts new file mode 100644 index 00000000..c5ee5ca6 --- /dev/null +++ b/types/ember-cli-test-loader/test-support/index.d.ts @@ -0,0 +1,17 @@ +declare module 'ember-cli-test-loader/test-support/index' { + export default class TestLoader { + static load(): void; + shouldLoadModule(moduleName: string): boolean; + listModules(): string[]; + listTestModules(): string[]; + loadModules(): void; + require(moduleName: string): void; + unsee(moduleName: string): void; + moduleLoadFailure(moduleName: string, error: { stack: unknown }): void; + } + + type Matcher = (moduleName: string) => boolean; + + export function addModuleExcludeMatcher(matcher: Matcher): void; + export function addModuleIncludeMatcher(matcher: Matcher): void; +} From 88f8a5f1aed886d2c4a6def74b3d57119126d6bd Mon Sep 17 00:00:00 2001 From: Krystan HuffMenne Date: Fri, 9 Dec 2022 17:02:04 -0800 Subject: [PATCH 13/34] Rename index to TS --- addon-test-support/{index.js => index.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename addon-test-support/{index.js => index.ts} (100%) diff --git a/addon-test-support/index.js b/addon-test-support/index.ts similarity index 100% rename from addon-test-support/index.js rename to addon-test-support/index.ts From 77765f960d1926c944c87712db169cdbbaf33dab Mon Sep 17 00:00:00 2001 From: Krystan HuffMenne Date: Fri, 9 Dec 2022 17:45:04 -0800 Subject: [PATCH 14/34] Fix type checking errors on index --- addon-test-support/index.ts | 133 ++++++++++++++++++++++++------------ types/global.d.ts | 7 -- 2 files changed, 90 insertions(+), 50 deletions(-) diff --git a/addon-test-support/index.ts b/addon-test-support/index.ts index a5258ce4..bc0f6075 100644 --- a/addon-test-support/index.ts +++ b/addon-test-support/index.ts @@ -5,71 +5,97 @@ export { loadTests } from './test-loader'; import './qunit-configuration'; +// @ts-expect-error FIXME if (typeof Testem !== 'undefined') { + // @ts-expect-error FIXME + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call Testem.hookIntoTestFramework(); } import { _backburner } from '@ember/runloop'; -import { resetOnerror, getTestMetadata } from '@ember/test-helpers'; -import { loadTests } from './test-loader'; -import Ember from 'ember'; -import * as QUnit from 'qunit'; -import QUnitAdapter from './adapter'; +import type { BaseContext, TestContext } from '@ember/test-helpers'; import { + getTestMetadata, + resetOnerror, + setupApplicationContext, setupContext, - teardownContext, setupRenderingContext, - setupApplicationContext, + teardownContext, validateErrorHandler, } from '@ember/test-helpers'; +import Ember from 'ember'; + +import * as QUnit from 'qunit'; + +import QUnitAdapter from './adapter'; import { installTestNotIsolatedHook } from './test-isolation-validation'; +import { loadTests } from './test-loader'; let waitForSettled = true; -export function setupTest(hooks, _options) { - let options = { waitForSettled, ..._options }; +export type SetupOptions = Parameters[1]; + +type PrivateSetupOptions = SetupOptions & { + waitForSettled?: boolean; +}; + +export function setupTest(hooks: GlobalHooks, _options: SetupOptions): void { + const options: PrivateSetupOptions = { waitForSettled, ..._options }; - hooks.beforeEach(function (assert) { - let testMetadata = getTestMetadata(this); - testMetadata.framework = 'qunit'; + hooks.beforeEach(async function ( + this: BaseContext, + assert: Assert + ): Promise { + const testMetadata = getTestMetadata(this); + testMetadata['framework'] = 'qunit'; - return setupContext(this, options).then(() => { - let originalPauseTest = this.pauseTest; - this.pauseTest = function QUnit_pauseTest() { + await setupContext(this, options); + + // eslint-disable-next-line @typescript-eslint/unbound-method + const originalPauseTest = (this as TestContext).pauseTest; + (this as TestContext).pauseTest = + async function QUnit_pauseTest(): Promise { assert.timeout(-1); // prevent the test from timing out // This is a temporary work around for // https://github.com/emberjs/ember-qunit/issues/496 this clears the // timeout that would fail the test when it hits the global testTimeout // value. + // @ts-expect-error FIXME + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument clearTimeout(QUnit.config.timeout); - return originalPauseTest.call(this); + await originalPauseTest.call(this); }; - }); }); - hooks.afterEach(function () { - return teardownContext(this, options); + hooks.afterEach(async function (this: TestContext): Promise { + await teardownContext(this, options); }); } -export function setupRenderingTest(hooks, _options) { - let options = { waitForSettled, ..._options }; +export function setupRenderingTest( + hooks: GlobalHooks, + _options: SetupOptions +): void { + const options: PrivateSetupOptions = { waitForSettled, ..._options }; setupTest(hooks, options); - hooks.beforeEach(function () { - return setupRenderingContext(this); + hooks.beforeEach(async function (this: TestContext) { + await setupRenderingContext(this); }); } -export function setupApplicationTest(hooks, _options) { - let options = { waitForSettled, ..._options }; +export function setupApplicationTest( + hooks: GlobalHooks, + _options: SetupOptions +): void { + const options: PrivateSetupOptions = { waitForSettled, ..._options }; setupTest(hooks, options); - hooks.beforeEach(function () { - return setupApplicationContext(this); + hooks.beforeEach(async function (this: TestContext) { + await setupApplicationContext(this); }); } @@ -82,19 +108,21 @@ export function setupApplicationTest(hooks, _options) { @method setupTestContainer */ -export function setupTestContainer() { - let testContainer = document.getElementById('ember-testing-container'); +export function setupTestContainer(): void { + const testContainer = document.getElementById('ember-testing-container'); if (!testContainer) { return; } - let params = QUnit.urlParams; + // @ts-expect-error FIXME + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const params: Record = QUnit.urlParams; - if (params.devmode || params.fullscreencontainer) { + if (params['devmode'] || params['fullscreencontainer']) { testContainer.classList.add('ember-testing-container-full-screen'); } - if (params.nocontainer) { + if (params['nocontainer']) { testContainer.classList.add('ember-testing-container-hidden'); } } @@ -103,7 +131,7 @@ export function setupTestContainer() { Instruct QUnit to start the tests. @method startTests */ -export function startTests() { +export function startTests(): void { QUnit.start(); } @@ -112,8 +140,10 @@ export function startTests() { @method setupTestAdapter */ -export function setupTestAdapter() { - Ember.Test.adapter = QUnitAdapter.create(); +export function setupTestAdapter(): void { + // @ts-expect-error FIXME + // eslint-disable-next-line @typescript-eslint/no-unsafe-call + Ember.Test.adapter = QUnitAdapter.create() as typeof QUnitAdapter; } /** @@ -122,12 +152,14 @@ export function setupTestAdapter() { completed. This is done via `QUnit.testStart` and `QUnit.testDone`. */ -export function setupEmberTesting() { +export function setupEmberTesting(): void { QUnit.testStart(() => { + // @ts-expect-error FIXME Ember.testing = true; }); QUnit.testDone(() => { + // @ts-expect-error FIXME Ember.testing = false; }); } @@ -136,11 +168,11 @@ export function setupEmberTesting() { Ensures that `Ember.onerror` (if present) is properly configured to re-throw errors that occur while `Ember.testing` is `true`. */ -export function setupEmberOnerrorValidation() { +export function setupEmberOnerrorValidation(): void { QUnit.module('ember-qunit: Ember.onerror validation', function () { QUnit.test('Ember.onerror is functioning properly', function (assert) { assert.expect(1); - let result = validateErrorHandler(); + const result = validateErrorHandler(); assert.ok( result.isValid, `Ember.onerror handler with invalid testing behavior detected. An Ember.onerror handler _must_ rethrow exceptions when \`Ember.testing\` is \`true\` or the test suite is unreliable. See https://git.io/vbine for more details.` @@ -149,14 +181,29 @@ export function setupEmberOnerrorValidation() { }); } -export function setupResetOnerror() { +export function setupResetOnerror(): void { QUnit.testDone(resetOnerror); } -export function setupTestIsolationValidation(delay) { +export function setupTestIsolationValidation(delay?: number | undefined): void { waitForSettled = false; _backburner.DEBUG = true; - QUnit.on('testStart', () => installTestNotIsolatedHook(delay)); + // @ts-expect-error FIXME + // eslint-disable-next-line @typescript-eslint/no-unsafe-call + QUnit.on('testStart', () => { + installTestNotIsolatedHook(delay); + }); +} + +export interface StartOptions { + loadTests?: boolean; + setupTestContainer?: boolean; + startTests?: boolean; + setupTestAdapter?: boolean; + setupEmberTesting?: boolean; + setupEmberOnerrorValidation?: boolean; + setupTestIsolationValidation?: boolean; + testIsolationValidationDelay?: number; } /** @@ -181,7 +228,7 @@ export function setupTestIsolationValidation(delay) { time in milliseconds that is allowed _after_ the test is completed for all async to have been completed. The default value is 50. */ -export function start(options = {}) { +export function start(options: StartOptions = {}): void { if (options.loadTests !== false) { loadTests(); } @@ -203,7 +250,7 @@ export function start(options = {}) { } if ( - typeof options.setupTestIsolationValidation !== 'undefined' && + typeof options.testIsolationValidationDelay !== 'undefined' && options.setupTestIsolationValidation !== false ) { setupTestIsolationValidation(options.testIsolationValidationDelay); diff --git a/types/global.d.ts b/types/global.d.ts index e4e2ef2b..e69de29b 100644 --- a/types/global.d.ts +++ b/types/global.d.ts @@ -1,7 +0,0 @@ -// Types for compiled templates -declare module 'ember-qunit/templates/*' { - import type { TemplateFactory } from 'ember-cli-htmlbars'; - - const tmpl: TemplateFactory; - export default tmpl; -} From e2517308b617acf4226b02a5199645f2e6e3121d Mon Sep 17 00:00:00 2001 From: Krystan HuffMenne Date: Mon, 12 Dec 2022 15:10:12 -0800 Subject: [PATCH 15/34] Move ember-source/types imports --- addon-test-support/adapter.ts | 4 ---- types/dummy/index.d.ts | 2 ++ 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/addon-test-support/adapter.ts b/addon-test-support/adapter.ts index c4db0fa7..c5882168 100644 --- a/addon-test-support/adapter.ts +++ b/addon-test-support/adapter.ts @@ -1,7 +1,3 @@ -// FIXME: Remove -import 'ember-source/types'; -import 'ember-source/types/preview'; - import { assert } from '@ember/debug'; import hasEmberVersion from '@ember/test-helpers/has-ember-version'; import Ember from 'ember'; diff --git a/types/dummy/index.d.ts b/types/dummy/index.d.ts index e69de29b..2109bac6 100644 --- a/types/dummy/index.d.ts +++ b/types/dummy/index.d.ts @@ -0,0 +1,2 @@ +import 'ember-source/types'; +import 'ember-source/types/preview'; From 434068f157e8fc731939476cc369150cdcc2ea41 Mon Sep 17 00:00:00 2001 From: Krystan HuffMenne Date: Tue, 3 Jan 2023 13:14:54 -0800 Subject: [PATCH 16/34] Merge info from ambient types --- addon-test-support/adapter.ts | 4 +- addon-test-support/index.ts | 315 ++++++++++++++++++++++++---- docs/legacy.md | 2 + package.json | 3 +- types/tests.ts => type-tests/api.ts | 9 +- {types => type-tests}/tsconfig.json | 0 types/index.d.ts | 272 ------------------------ types/local-types.d.ts | 2 - 8 files changed, 277 insertions(+), 330 deletions(-) rename types/tests.ts => type-tests/api.ts (97%) rename {types => type-tests}/tsconfig.json (100%) delete mode 100644 types/index.d.ts delete mode 100644 types/local-types.d.ts diff --git a/addon-test-support/adapter.ts b/addon-test-support/adapter.ts index c5882168..4b421ca9 100644 --- a/addon-test-support/adapter.ts +++ b/addon-test-support/adapter.ts @@ -1,4 +1,5 @@ import { assert } from '@ember/debug'; +import type EmberTestAdapter from '@ember/test/adapter'; import hasEmberVersion from '@ember/test-helpers/has-ember-version'; import Ember from 'ember'; @@ -43,8 +44,7 @@ export function nonTestDoneCallback(): void { // no-op } -// FIXME: What is Ember.Test.QUnitAdapter? -interface QUnitAdapter { +interface QUnitAdapter extends EmberTestAdapter { doneCallbacks: Array<{ test: unknown; done: () => void }>; qunit: QUnit; } diff --git a/addon-test-support/index.ts b/addon-test-support/index.ts index bc0f6075..5c96e3c1 100644 --- a/addon-test-support/index.ts +++ b/addon-test-support/index.ts @@ -12,6 +12,7 @@ if (typeof Testem !== 'undefined') { Testem.hookIntoTestFramework(); } +import type { Resolver } from '@ember/owner'; import { _backburner } from '@ember/runloop'; import type { BaseContext, TestContext } from '@ember/test-helpers'; import { @@ -31,16 +32,44 @@ import QUnitAdapter from './adapter'; import { installTestNotIsolatedHook } from './test-isolation-validation'; import { loadTests } from './test-loader'; +// FIXME: What is this about? let waitForSettled = true; -export type SetupOptions = Parameters[1]; +/** + * Options for configuring the test runner. Normally, you will not need to + * customize this. It is exported primarily so that end user app code can name + * it when passing it back to the framework. + */ +export interface SetupTestOptions { + /** + * The resolver to use when instantiating container-managed entities in the test. + */ + resolver?: Resolver | undefined; +} -type PrivateSetupOptions = SetupOptions & { +type PrivateSetupOptions = SetupTestOptions & { + // FIXME: What is this about? waitForSettled?: boolean; }; -export function setupTest(hooks: GlobalHooks, _options: SetupOptions): void { - const options: PrivateSetupOptions = { waitForSettled, ..._options }; +/** + * Sets up tests that do not need to render snippets of templates. + * + * The `setupTest` method is used for all types of tests except for those + * that need to render snippets of templates. It is invoked in the callback + * scope of a QUnit module (aka "nested module"). + * + * Once invoked, all subsequent hooks.beforeEach and test invocations will + * have access to the following: + * * this.owner - This exposes the standard "owner API" for the test environment. + * * this.set / this.setProperties - Allows setting values on the test context. + * * this.get / this.getProperties - Retrieves values from the test context. + */ +export function setupTest( + hooks: NestedHooks, + options?: SetupTestOptions +): void { + const allOptions: PrivateSetupOptions = { waitForSettled, ...options }; hooks.beforeEach(async function ( this: BaseContext, @@ -49,7 +78,7 @@ export function setupTest(hooks: GlobalHooks, _options: SetupOptions): void { const testMetadata = getTestMetadata(this); testMetadata['framework'] = 'qunit'; - await setupContext(this, options); + await setupContext(this, allOptions); // eslint-disable-next-line @typescript-eslint/unbound-method const originalPauseTest = (this as TestContext).pauseTest; @@ -69,30 +98,61 @@ export function setupTest(hooks: GlobalHooks, _options: SetupOptions): void { }); hooks.afterEach(async function (this: TestContext): Promise { - await teardownContext(this, options); + await teardownContext(this, allOptions); }); } +/** + * Sets up tests that need to render snippets of templates. + * + * The setupRenderingTest method is used for tests that need to render + * snippets of templates. It is also invoked in the callback scope of a + * QUnit module (aka "nested module"). + * + * Once invoked, all subsequent hooks.beforeEach and test invocations will + * have access to the following: + * * All of the methods / properties listed for `setupTest` + * * this.render(...) - Renders the provided template snippet returning a + * promise that resolves once rendering has completed + * * An importable render function that de-sugars into this.render will be + * the default output of blueprints + * * this.element - Returns the native DOM element representing the element + * that was rendered via this.render + * * this.$(...) - When jQuery is present, executes a jQuery selector with + * the current this.element as its root + */ export function setupRenderingTest( - hooks: GlobalHooks, - _options: SetupOptions + hooks: NestedHooks, + options?: SetupTestOptions ): void { - const options: PrivateSetupOptions = { waitForSettled, ..._options }; + const allOptions: PrivateSetupOptions = { waitForSettled, ...options }; - setupTest(hooks, options); + setupTest(hooks, allOptions); hooks.beforeEach(async function (this: TestContext) { await setupRenderingContext(this); }); } +/** + * Sets up acceptance tests. + * + * The `setupApplicationTest` function is used for all acceptance tests. It + * is invoked in the callback scope of a QUnit module (aka "nested module"). + * + * Once invoked, all subsequent hooks.beforeEach and test invocations will + * have access to the following: + * * `this.owner` - the owner object that been set on the test context. + * * `this.pauseTest` and `this.resumeTest` - allow easy pausing/resuming of tests. + * * `this.element` which returns the DOM element representing the application's root element. + */ export function setupApplicationTest( - hooks: GlobalHooks, - _options: SetupOptions + hooks: NestedHooks, + options?: SetupTestOptions ): void { - const options: PrivateSetupOptions = { waitForSettled, ..._options }; + const allOptions: PrivateSetupOptions = { waitForSettled, ...options }; - setupTest(hooks, options); + setupTest(hooks, allOptions); hooks.beforeEach(async function (this: TestContext) { await setupApplicationContext(this); @@ -195,40 +255,55 @@ export function setupTestIsolationValidation(delay?: number | undefined): void { }); } -export interface StartOptions { - loadTests?: boolean; - setupTestContainer?: boolean; - startTests?: boolean; - setupTestAdapter?: boolean; - setupEmberTesting?: boolean; - setupEmberOnerrorValidation?: boolean; - setupTestIsolationValidation?: boolean; - testIsolationValidationDelay?: number; +/** Options to be used for enabling/disabling behaviors */ +export interface QUnitStartOptions { + /** + * If `false` tests will not be loaded automatically. + */ + loadTests?: boolean | undefined; + + /** + * If `false` the test container will not be setup based on `devmode`, + * `dockcontainer`, or `nocontainer` URL params. + */ + setupTestContainer?: boolean | undefined; + + /** + * If `false` tests will not be automatically started (you must run + * `QUnit.start()` to kick them off). + */ + startTests?: boolean | undefined; + + /** + * If `false` the default Ember.Test adapter will not be updated. + */ + setupTestAdapter?: boolean | undefined; + + /** + * `false` opts out of the default behavior of setting `Ember.testing` + * to `true` before all tests and back to `false` after each test will. + */ + setupEmberTesting?: boolean | undefined; + + /** + * If `false` validation of `Ember.onerror` will be disabled. + */ + setupEmberOnerrorValidation?: boolean | undefined; + + /** + * If `false` test isolation validation will be disabled. + */ + setupTestIsolationValidation?: boolean | undefined; + + /** + * When using setupTestIsolationValidation this number represents the maximum + * amount of time in milliseconds that is allowed _after_ the test is + * completed for all async to have been completed. The default value is 50. + */ + testIsolationValidationDelay?: number | undefined; } -/** - @method start - @param {Object} [options] Options to be used for enabling/disabling behaviors - @param {Boolean} [options.loadTests] If `false` tests will not be loaded automatically. - @param {Boolean} [options.setupTestContainer] If `false` the test container will not - be setup based on `devmode`, `dockcontainer`, or `nocontainer` URL params. - @param {Boolean} [options.startTests] If `false` tests will not be automatically started - (you must run `QUnit.start()` to kick them off). - @param {Boolean} [options.setupTestAdapter] If `false` the default Ember.Test adapter will - not be updated. - @param {Boolean} [options.setupEmberTesting] `false` opts out of the - default behavior of setting `Ember.testing` to `true` before all tests and - back to `false` after each test will. - @param {Boolean} [options.setupEmberOnerrorValidation] If `false` validation - of `Ember.onerror` will be disabled. - @param {Boolean} [options.setupTestIsolationValidation] If `false` test isolation validation - will be disabled. - @param {Number} [options.testIsolationValidationDelay] When using - setupTestIsolationValidation this number represents the maximum amount of - time in milliseconds that is allowed _after_ the test is completed for all - async to have been completed. The default value is 50. - */ -export function start(options: StartOptions = {}): void { +export function start(options: QUnitStartOptions = {}): void { if (options.loadTests !== false) { loadTests(); } @@ -262,3 +337,151 @@ export function start(options: StartOptions = {}): void { setupResetOnerror(); } + +// SAFETY: all of the `TC extends TestContext` generics below are just wildly, +// impossibly unsafe. QUnit cannot -- ever! -- guarantee that the test context +// is properly set up in a type-safe way to match this. However, it is the only +// way to handle setting state in a TS-visible way prior to Ember RFC 0785, +// which is slooooowly rolling out across the ecosystem in conjunction with the +// `