diff --git a/.ember-cli b/.ember-cli index 4ccb4bf4..4adaaf6d 100644 --- a/.ember-cli +++ b/.ember-cli @@ -11,5 +11,5 @@ Setting `isTypeScriptProject` to true will force the blueprint generators to generate TypeScript rather than JavaScript by default, when a TypeScript version of a given blueprint is available. */ - "isTypeScriptProject": false + "isTypeScriptProject": true } 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/.eslintrc.js b/.eslintrc.js index 1dd2a810..20b911fc 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,5 +1,28 @@ 'use strict'; +const sharedTSOptions = { + extends: [ + 'eslint:recommended', + 'plugin:@typescript-eslint/recommended', + 'plugin:@typescript-eslint/recommended-requiring-type-checking', + 'plugin:@typescript-eslint/strict', + ], + parser: '@typescript-eslint/parser', + 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', + '@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/promise-function-async': 'error', + }, +}; + module.exports = { root: true, extends: ['eslint:recommended', 'plugin:prettier/recommended'], @@ -45,6 +68,30 @@ module.exports = { extends: ['plugin:node/recommended'], }, + // ts files + { + files: ['**/*.ts'], + parserOptions: { + tsconfigRootDir: __dirname, + project: ['./tsconfig.json'], + }, + ...sharedTSOptions, + }, + + // ts-tests files + { + files: ['type-tests/**/*.ts'], + parserOptions: { + tsconfigRootDir: __dirname, + project: ['./type-tests/tsconfig.json'], + }, + ...sharedTSOptions, + rules: { + '@typescript-eslint/no-empty-function': 'off', + '@typescript-eslint/no-unused-vars': 'off', + }, + }, + // test files { files: ['tests/**/*.js'], 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/addon-test-support/adapter.js b/addon-test-support/adapter.js deleted file mode 100644 index 7673f392..00000000 --- a/addon-test-support/adapter.js +++ /dev/null @@ -1,80 +0,0 @@ -import Ember from 'ember'; -import * as QUnit from 'qunit'; -import hasEmberVersion from '@ember/test-helpers/has-ember-version'; - -function unhandledRejectionAssertion(current, error) { - let message, source; - - if (typeof error === 'object' && error !== null) { - message = error.message; - source = error.stack; - } else if (typeof error === 'string') { - message = error; - source = 'unknown source'; - } else { - message = 'unhandledRejection occured, but it had no message'; - source = 'unknown source'; - } - - current.assert.pushResult({ - result: false, - actual: false, - expected: true, - message: message, - source: source, - }); -} - -export function nonTestDoneCallback() {} - -let Adapter = Ember.Test.Adapter.extend({ - init() { - this.doneCallbacks = []; - this.qunit = this.qunit || QUnit; - }, - - asyncStart() { - let currentTest = this.qunit.config.current; - let done = - currentTest && currentTest.assert - ? currentTest.assert.async() - : nonTestDoneCallback; - this.doneCallbacks.push({ test: currentTest, done }); - }, - - asyncEnd() { - let currentTest = this.qunit.config.current; - - if (this.doneCallbacks.length === 0) { - throw new Error( - 'Adapter asyncEnd called when no async was expected. Please create an issue in ember-qunit.' - ); - } - - let { test, done } = this.doneCallbacks.pop(); - - // 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) { - done(); - } - }, - - // clobber default implementation of `exception` will be added back for Ember - // < 2.17 just below... - exception: null, -}); - -// 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)) { - Adapter = Adapter.extend({ - exception(error) { - unhandledRejectionAssertion(QUnit.config.current, error); - }, - }); -} - -export default Adapter; diff --git a/addon-test-support/adapter.ts b/addon-test-support/adapter.ts new file mode 100644 index 00000000..ed3c2a58 --- /dev/null +++ b/addon-test-support/adapter.ts @@ -0,0 +1,106 @@ +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'; + +import * as QUnit from 'qunit'; + +import { isRecord, isTest } from './types/util'; + +function unhandledRejectionAssertion(current: unknown, error: unknown): void { + let message: string; + let source: string | undefined; + + 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 occurred, but it had no message'; + source = 'unknown 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, + source, + }); +} + +export function nonTestDoneCallback(): void { + // no-op +} + +interface QUnitAdapter extends EmberTestAdapter { + doneCallbacks: Array<{ test: unknown; done: () => void }>; + qunit: QUnit; +} + +// @ts-expect-error `extend` does not exist on Adapter +// eslint-disable-next-line @typescript-eslint/no-unsafe-call +let Adapter = Ember.Test.Adapter.extend({ + init(this: QUnitAdapter) { + this.doneCallbacks = []; + this.qunit ??= QUnit; + }, + + asyncStart(this: QUnitAdapter) { + const currentTest: unknown = this.qunit.config.current; + const done = + isTest(currentTest) && 'assert' in currentTest + ? currentTest.assert.async() + : nonTestDoneCallback; + this.doneCallbacks.push({ test: currentTest, done }); + }, + + asyncEnd(this: QUnitAdapter) { + const currentCallback = this.doneCallbacks.pop(); + + if (!currentCallback) { + throw new Error( + 'Adapter asyncEnd called when no async was expected. Please create an issue in ember-qunit.' + ); + } + + 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 === this.qunit.config.current) { + done(); + } + }, + + // 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 `extend` does not exist on Adapter + // eslint-disable-next-line @typescript-eslint/no-unsafe-call + Adapter = Adapter.extend({ + exception(error: unknown) { + const currentTest: unknown = QUnit.config.current; + unhandledRejectionAssertion(currentTest, error); + }, + }) as QUnitAdapter; +} + +export default Adapter; diff --git a/addon-test-support/index.js b/addon-test-support/index.js deleted file mode 100644 index a5258ce4..00000000 --- a/addon-test-support/index.js +++ /dev/null @@ -1,217 +0,0 @@ -/* globals Testem */ - -export { default as QUnitAdapter, nonTestDoneCallback } from './adapter'; -export { loadTests } from './test-loader'; - -import './qunit-configuration'; - -if (typeof Testem !== 'undefined') { - 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 { - setupContext, - teardownContext, - setupRenderingContext, - setupApplicationContext, - validateErrorHandler, -} from '@ember/test-helpers'; -import { installTestNotIsolatedHook } from './test-isolation-validation'; - -let waitForSettled = true; - -export function setupTest(hooks, _options) { - let options = { waitForSettled, ..._options }; - - hooks.beforeEach(function (assert) { - let testMetadata = getTestMetadata(this); - testMetadata.framework = 'qunit'; - - return setupContext(this, options).then(() => { - let originalPauseTest = this.pauseTest; - this.pauseTest = function QUnit_pauseTest() { - 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. - clearTimeout(QUnit.config.timeout); - return originalPauseTest.call(this); - }; - }); - }); - - hooks.afterEach(function () { - return teardownContext(this, options); - }); -} - -export function setupRenderingTest(hooks, _options) { - let options = { waitForSettled, ..._options }; - - setupTest(hooks, options); - - hooks.beforeEach(function () { - return setupRenderingContext(this); - }); -} - -export function setupApplicationTest(hooks, _options) { - let options = { waitForSettled, ..._options }; - - setupTest(hooks, options); - - hooks.beforeEach(function () { - return setupApplicationContext(this); - }); -} - -/** - Uses current URL configuration to setup the test container. - - * If `?nocontainer` is set, the test container will be hidden. - * If `?devmode` or `?fullscreencontainer` is set, the test container will be - made full screen. - - @method setupTestContainer - */ -export function setupTestContainer() { - let testContainer = document.getElementById('ember-testing-container'); - if (!testContainer) { - return; - } - - let params = QUnit.urlParams; - - if (params.devmode || params.fullscreencontainer) { - testContainer.classList.add('ember-testing-container-full-screen'); - } - - if (params.nocontainer) { - testContainer.classList.add('ember-testing-container-hidden'); - } -} - -/** - Instruct QUnit to start the tests. - @method startTests - */ -export function startTests() { - QUnit.start(); -} - -/** - Sets up the `Ember.Test` adapter for usage with QUnit 2.x. - - @method setupTestAdapter - */ -export function setupTestAdapter() { - Ember.Test.adapter = QUnitAdapter.create(); -} - -/** - Ensures that `Ember.testing` is set to `true` before each test begins - (including `before` / `beforeEach`), and reset to `false` after each test is - completed. This is done via `QUnit.testStart` and `QUnit.testDone`. - - */ -export function setupEmberTesting() { - QUnit.testStart(() => { - Ember.testing = true; - }); - - QUnit.testDone(() => { - Ember.testing = false; - }); -} - -/** - Ensures that `Ember.onerror` (if present) is properly configured to re-throw - errors that occur while `Ember.testing` is `true`. -*/ -export function setupEmberOnerrorValidation() { - QUnit.module('ember-qunit: Ember.onerror validation', function () { - QUnit.test('Ember.onerror is functioning properly', function (assert) { - assert.expect(1); - let 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.` - ); - }); - }); -} - -export function setupResetOnerror() { - QUnit.testDone(resetOnerror); -} - -export function setupTestIsolationValidation(delay) { - waitForSettled = false; - _backburner.DEBUG = true; - QUnit.on('testStart', () => installTestNotIsolatedHook(delay)); -} - -/** - @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 = {}) { - if (options.loadTests !== false) { - loadTests(); - } - - if (options.setupTestContainer !== false) { - setupTestContainer(); - } - - if (options.setupTestAdapter !== false) { - setupTestAdapter(); - } - - if (options.setupEmberTesting !== false) { - setupEmberTesting(); - } - - if (options.setupEmberOnerrorValidation !== false) { - setupEmberOnerrorValidation(); - } - - if ( - typeof options.setupTestIsolationValidation !== 'undefined' && - options.setupTestIsolationValidation !== false - ) { - setupTestIsolationValidation(options.testIsolationValidationDelay); - } - - if (options.startTests !== false) { - startTests(); - } - - setupResetOnerror(); -} diff --git a/types/index.d.ts b/addon-test-support/index.ts similarity index 50% rename from types/index.d.ts rename to addon-test-support/index.ts index d11b83d5..4d6bebdb 100644 --- a/types/index.d.ts +++ b/addon-test-support/index.ts @@ -1,48 +1,108 @@ -// Type definitions for ember-qunit 5.0 -// Project: https://github.com/emberjs/ember-qunit#readme -// Definitions by: Dan Freeman -// Chris Krycho -// James C. Davis -// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped -// Minimum TypeScript Version: 4.4 - -import EmberTestAdapter from '@ember/test/adapter'; -import { Resolver } from '@ember/owner'; -import { TestContext } from '@ember/test-helpers'; +/* globals Testem */ -/** - * Sets a Resolver globally which will be used to look up objects from each test's container. - */ -export function setResolver(resolver: Resolver): void; +export { default as QUnitAdapter, nonTestDoneCallback } from './adapter'; +export { loadTests } from './test-loader'; + +import './qunit-configuration'; + +// @ts-expect-error Testem has no types +if (typeof Testem !== 'undefined') { + // @ts-expect-error Testem has no types + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call + Testem.hookIntoTestFramework(); +} + +import { _backburner } from '@ember/runloop'; +import type { + BaseContext, + SetupContextOptions, + TeardownContextOptions, + TestContext, +} from '@ember/test-helpers'; +import { + getTestMetadata, + resetOnerror, + setupApplicationContext, + setupContext, + setupRenderingContext, + 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; /** * 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; -} +export type SetupTestOptions = SetupContextOptions; + +type PrivateSetupOptions = SetupContextOptions & TeardownContextOptions; /** - * Sets up acceptance tests. + * Sets up tests that do not need to render snippets of templates. * - * The `setupApplicationTest` function is used for all acceptance tests. It - * is invoked in the callback scope of a QUnit module (aka "nested module"). + * 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` - 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. + * * 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 setupApplicationTest( +export function setupTest( hooks: NestedHooks, options?: SetupTestOptions -): void; +): void { + const allOptions: PrivateSetupOptions = { waitForSettled, ...options }; + + hooks.beforeEach(async function ( + this: BaseContext, + assert: Assert + ): Promise { + const testMetadata = getTestMetadata(this); + testMetadata['framework'] = 'qunit'; + + await setupContext(this, allOptions); + + // 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. + // https://github.com/DefinitelyTyped/DefinitelyTyped/pull/63805#issuecomment-1372408802 + const timeout = ( + QUnit.config as unknown as { + // https://github.com/qunitjs/qunit/blob/fc278e8c0d7e90ec42e47b47eee1cc85c9a9efaf/src/test.js#L752 + timeout: ReturnType | null | undefined; + } + ).timeout; + if (timeout !== null && timeout !== undefined) { + clearTimeout(timeout); + } + await originalPauseTest.call(this); + }; + }); + + hooks.afterEach(async function (this: TestContext): Promise { + await teardownContext(this, allOptions); + }); +} /** * Sets up tests that need to render snippets of templates. @@ -54,40 +114,142 @@ export function setupApplicationTest( * 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 + * * An importable render function * * 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: NestedHooks, options?: SetupTestOptions -): void; +): void { + const allOptions: PrivateSetupOptions = { waitForSettled, ...options }; + + setupTest(hooks, allOptions); + + hooks.beforeEach(async function (this: TestContext) { + await setupRenderingContext(this); + }); +} /** - * Sets up tests that do not need to render snippets of templates. + * Sets up acceptance tests. * - * 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"). + * 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 - 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. + * * `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 setupTest(hooks: NestedHooks, options?: SetupTestOptions): void; +export function setupApplicationTest( + hooks: NestedHooks, + options?: SetupTestOptions +): void { + const allOptions: PrivateSetupOptions = { waitForSettled, ...options }; -export class QUnitAdapter extends EmberTestAdapter {} + setupTest(hooks, allOptions); -export { module, test, skip, only, todo } from 'qunit'; + hooks.beforeEach(async function (this: TestContext) { + await setupApplicationContext(this); + }); +} -interface QUnitStartOptions { +/** + Uses current URL configuration to setup the test container. + + * If `?nocontainer` is set, the test container will be hidden. + * If `?devmode` or `?fullscreencontainer` is set, the test container will be + made full screen. + + @method setupTestContainer + */ +export function setupTestContainer(): void { + const testContainer = document.getElementById('ember-testing-container'); + if (!testContainer) { + return; + } + + const params = QUnit.urlParams; + + if (params['devmode'] || params['fullscreencontainer']) { + testContainer.classList.add('ember-testing-container-full-screen'); + } + + if (params['nocontainer']) { + testContainer.classList.add('ember-testing-container-hidden'); + } +} + +/** + Instruct QUnit to start the tests. + @method startTests + */ +export function startTests(): void { + QUnit.start(); +} + +/** + Sets up the `Ember.Test` adapter for usage with QUnit 2.x. + + @method setupTestAdapter + */ +export function setupTestAdapter(): void { + // @ts-expect-error Adapter doesn't have `create` + // eslint-disable-next-line @typescript-eslint/no-unsafe-call + Ember.Test.adapter = QUnitAdapter.create() as typeof QUnitAdapter; +} + +/** + Ensures that `Ember.testing` is set to `true` before each test begins + (including `before` / `beforeEach`), and reset to `false` after each test is + completed. This is done via `QUnit.testStart` and `QUnit.testDone`. + + */ +export function setupEmberTesting(): void { + QUnit.testStart(() => { + // @ts-expect-error Ember.testing type is readonly + Ember.testing = true; + }); + + QUnit.testDone(() => { + // @ts-expect-error Ember.testing type is readonly + Ember.testing = false; + }); +} + +/** + Ensures that `Ember.onerror` (if present) is properly configured to re-throw + errors that occur while `Ember.testing` is `true`. +*/ +export function setupEmberOnerrorValidation(): void { + QUnit.module('ember-qunit: Ember.onerror validation', function () { + QUnit.test('Ember.onerror is functioning properly', function (assert) { + assert.expect(1); + 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.` + ); + }); + }); +} + +export function setupResetOnerror(): void { + QUnit.testDone(resetOnerror); +} + +export function setupTestIsolationValidation(delay?: number | undefined): void { + waitForSettled = false; + _backburner.DEBUG = true; + QUnit.testStart(() => { + installTestNotIsolatedHook(delay); + }); +} + +/** Options to be used for enabling/disabling behaviors */ +export interface QUnitStartOptions { /** * If `false` tests will not be loaded automatically. */ @@ -125,9 +287,49 @@ interface QUnitStartOptions { * 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; } -export function start(options?: QUnitStartOptions): void; +export function start(options: QUnitStartOptions = {}): void { + if (options.loadTests !== false) { + loadTests(); + } + + if (options.setupTestContainer !== false) { + setupTestContainer(); + } + + if (options.setupTestAdapter !== false) { + setupTestAdapter(); + } + + if (options.setupEmberTesting !== false) { + setupEmberTesting(); + } + + if (options.setupEmberOnerrorValidation !== false) { + setupEmberOnerrorValidation(); + } + + if ( + typeof options.testIsolationValidationDelay !== 'undefined' && + options.setupTestIsolationValidation !== false + ) { + setupTestIsolationValidation(options.testIsolationValidationDelay); + } + + if (options.startTests !== false) { + startTests(); + } + + setupResetOnerror(); +} // SAFETY: all of the `TC extends TestContext` generics below are just wildly, // impossibly unsafe. QUnit cannot -- ever! -- guarantee that the test context @@ -136,6 +338,10 @@ export function start(options?: QUnitStartOptions): void; // which is slooooowly rolling out across the ecosystem in conjunction with the // `