diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 9f4a8c00..e6d3ddb8 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -64,3 +64,26 @@ jobs:
run: yarn install --frozen-lockfile
- name: test
run: node_modules/.bin/ember try:one ${{ matrix.ember-try-scenario }} --skip-cleanup
+
+ types:
+ runs-on: ubuntu-latest
+
+ needs: test
+
+ strategy:
+ fail-fast: false
+ matrix:
+ ts-version:
+ - 4.8
+ - 4.9
+ - next
+
+ steps:
+ - uses: actions/checkout@v2
+ - uses: volta-cli/action@v4
+ - name: install dependencies
+ run: yarn install --frozen-lockfile
+ - name: install TS version
+ run: yarn install --dev typescript@${{matrix.ts-version}}
+ - name: test types
+ run: yarn test:ts
diff --git a/README.md b/README.md
index 65a9d6e0..85f6449f 100644
--- a/README.md
+++ b/README.md
@@ -20,6 +20,9 @@ Requirements
* Ember.js v3.28 or above
* Ember CLI v3.28 or above
* Node.js v14 or above
+- TypeScript 4.8 and 4.9
+ - SemVer policy: [simple majors](https://www.semver-ts.org/#simple-majors)
+ - The public API is defined by the [Usage][#usage] section below.
If you need support for Node 13 or older Ember CLI versions please use v4.x
of this addon.
diff --git a/docs/migration.md b/docs/migration.md
index 8e8300e9..1db27f29 100644
--- a/docs/migration.md
+++ b/docs/migration.md
@@ -2,6 +2,52 @@
Migration Guide
==============================================================================
+Migrating to native TypeScript support in v6.1.0
+------------------------------------------------------------------------------
+
+The types for the QUnit `TestContext` provided by the `ember-qunit` and `@ember/test-helpers` types on DefinitelyTyped made a choice to prioritize convenience over robustness when it came to what methods and values were available on `this` in any given test: they made *all* methods availabe regardless of what your setup actually involved. For example, this totally invalid code would have passed the type checker:
+
+```ts
+import { module, test } from 'qunit';
+import { setupTest } from 'ember-qunit';
+import { hbs } from 'ember-cli-htmlbars';
+
+module('bad times', function (hooks) {
+ setupTest(hooks);
+
+ test('this will not *run* correctly', async function (assert) {
+ await this.render(hbs`
whoopsie
`);
+ });
+})
+```
+
+To resolve this, you need to explicitly specify what `this` is for different kinds of tests:
+
+```ts
+import { module, test } from 'qunit';
+import { setupTest } from 'ember-qunit';
+import type { RenderingTextContext } from '@ember/test-helpers';
+import { hbs } from 'ember-cli-htmlbars';
+
+module('better times', function (hooks) {
+ setupTest(hooks);
+
+ test(
+ 'this will not *run* correctly',
+ async function (this: RenderingTextContext, assert) {
+ await this.render(hbs`whoopsie
`);
+ }
+ );
+})
+```
+
+While annoying, this is accurate and prevents the annoying mismatch. Combined with support for using local scope with `` (see [Ember RFC 0785][rfc-0785]), available since v2.8 of `@ember/test-helpers`, the need to specify the `this` will go away entirely over time.
+
+[rfc-0785]: https://rfcs.emberjs.com/id/0785-remove-set-get-in-tests
+
+To use these public types, you also will need to add `@glimmer/interfaces` and `@glimmer/reference` to your `devDependencies`, since of `@ember/test-helpers` uses them (indirectly) in its public APIs, and `ember-qunit` uses `@ember/test-helpers` in turn.
+
+
Upgrading from v4.x to v5.0.0
------------------------------------------------------------------------------
diff --git a/package.json b/package.json
index 2179007a..4a009140 100644
--- a/package.json
+++ b/package.json
@@ -21,6 +21,7 @@
"doc": "doc",
"test": "tests"
},
+ "types": "types/index.d.ts",
"scripts": {
"build": "ember build --environment=production",
"lint": "npm-run-all --print-name --aggregate-output --continue-on-error --parallel \"lint:!(fix)\"",
@@ -29,6 +30,7 @@
"lint:js:fix": "eslint . --fix",
"start": "ember serve",
"test": "npm-run-all --print-name \"lint\" \"test:*\"",
+ "test:types": "tsc --noEmit --project types",
"test:ember": "ember test",
"test:ember-compatibility": "ember try:each"
},
@@ -47,9 +49,14 @@
"@babel/core": "^7.20.5",
"@babel/eslint-parser": "^7.19.1",
"@ember/optional-features": "^2.0.0",
- "@ember/test-helpers": "^2.8.1",
+ "@ember/test-helpers": "^2.9.1",
"@embroider/test-setup": "^2.0.2",
"@glimmer/component": "^1.1.2",
+ "@glimmer/interfaces": "^0.84.2",
+ "@glimmer/reference": "^0.84.2",
+ "@tsconfig/ember": "^1.1.0",
+ "@types/qunit": "^2.19.3",
+ "@types/rsvp": "^4.0.4",
"ember-angle-bracket-invocation-polyfill": "^3.0.2",
"ember-cli": "~4.8.0",
"ember-cli-dependency-checker": "^3.3.1",
@@ -59,7 +66,7 @@
"ember-disable-prototype-extensions": "^1.1.3",
"ember-load-initializers": "^2.1.2",
"ember-resolver": "^8.0.3",
- "ember-source": "~4.9.1",
+ "ember-source": "~4.8.3",
"ember-source-channel-url": "^3.0.0",
"ember-try": "^2.0.0",
"eslint": "^8.29.0",
@@ -67,18 +74,47 @@
"eslint-plugin-disable-features": "^0.1.3",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-prettier": "^4.2.1",
+ "expect-type": "^0.15.0",
"loader.js": "^4.7.0",
"npm-run-all": "^4.1.5",
"prettier": "2.8.0",
"qunit": "^2.19.2",
"release-it": "^15.5.0",
"release-it-lerna-changelog": "^5.0.0",
+ "typescript": "^4.9.4",
"webpack": "^5.75.0"
},
"peerDependencies": {
"@ember/test-helpers": "^2.4.0",
+ "@glimmer/interfaces": "^0.84.2",
+ "@glimmer/reference": "^0.84.2",
+ "@types/ember-resolver": "^5.0.13",
+ "@types/ember__test": "^4.0.1",
+ "@types/ember__test-helpers": "^2.8.2",
+ "@types/rsvp": "^4.0.4",
+ "ember-source": "^3.28 || ^4.0",
"qunit": "^2.13.0"
},
+ "peerDependenciesMeta": {
+ "@types/ember__test": {
+ "optional": true
+ },
+ "@types/ember-resolver": {
+ "optional": true
+ },
+ "@types/ember__test-helpers": {
+ "optional": true
+ },
+ "@types/rsvp": {
+ "optional": true
+ },
+ "@glimmer/interfaces": {
+ "optional": true
+ },
+ "@glimmer/reference": {
+ "optional": true
+ }
+ },
"engines": {
"node": "14.* || 16.* || >= 18"
},
diff --git a/types/index.d.ts b/types/index.d.ts
new file mode 100644
index 00000000..d11b83d5
--- /dev/null
+++ b/types/index.d.ts
@@ -0,0 +1,272 @@
+// 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';
+
+/**
+ * Sets a Resolver globally which will be used to look up objects from each test's container.
+ */
+export function setResolver(resolver: Resolver): void;
+
+/**
+ * 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;
+}
+
+/**
+ * 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: NestedHooks,
+ options?: SetupTestOptions
+): void;
+
+/**
+ * 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: NestedHooks,
+ options?: SetupTestOptions
+): void;
+
+/**
+ * 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;
+
+export class QUnitAdapter extends EmberTestAdapter {}
+
+export { module, test, skip, only, todo } from 'qunit';
+
+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;
+}
+
+export function start(options?: QUnitStartOptions): void;
+
+// 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
+// `` feature.
+
+declare global {
+ // NOTE: disables `no-unnecessary-generics` inline because, unfortunately,
+ // the design of Ember's test tooling (and indeed *QUnit's* test system)
+ // requires that we allow users to update the type of the context of the
+ // test. This is indeed strictly *wrong*! However, changing it will require
+ // changing how Ember handles testing. See [the PR][pr] for further details.
+ //
+ // [pr]: https://github.com/DefinitelyTyped/DefinitelyTyped/pull/56494
+
+ interface NestedHooks {
+ /**
+ * Runs after the last test. If additional tests are defined after the
+ * module's queue has emptied, it will not run this hook again.
+ */
+ after(
+ fn: (this: TC, assert: Assert) => void | Promise
+ ): void;
+
+ /**
+ * Runs after each test.
+ */
+ afterEach(
+ fn: (this: TC, assert: Assert) => void | Promise
+ ): void;
+
+ /**
+ * Runs before the first test.
+ */
+ // SAFETY: this is just wildly, impossibly unsafe. QUnit cannot -- ever! --
+ before(
+ fn: (this: TC, assert: Assert) => void | Promise
+ ): void;
+
+ /**
+ * Runs before each test.
+ */
+ // SAFETY: this is just wildly, impossibly unsafe. QUnit cannot -- ever! --
+ beforeEach(
+ fn: (this: TC, assert: Assert) => void | Promise
+ ): void;
+ }
+
+ interface QUnit {
+ /**
+ * Add a test to run.
+ *
+ * Add a test to run using `QUnit.test()`.
+ *
+ * The `assert` argument to the callback contains all of QUnit's assertion
+ * methods. Use this argument to call your test assertions.
+ *
+ * `QUnit.test()` can automatically handle the asynchronous resolution of a
+ * Promise on your behalf if you return a thenable Promise as the result of
+ * your callback function.
+ *
+ * @param name Title of unit being tested
+ * @param callback Function to close over assertions
+ */
+ // SAFETY: this is just wildly, impossibly unsafe. QUnit cannot -- ever! --
+ // provide this guarantee. However, it's also the only way to support TS
+ // in tests in Ember until we move the community over entirely to using
+ // `` and local scope.
+ test(
+ name: string,
+ callback: (this: TC, assert: Assert) => void | Promise
+ ): void;
+
+ /**
+ * Adds a test to exclusively run, preventing all other tests from running.
+ *
+ * Use this method to focus your test suite on a specific test. QUnit.only
+ * will cause any other tests in your suite to be ignored.
+ *
+ * Note, that if more than one QUnit.only is present only the first instance
+ * will run.
+ *
+ * This is an alternative to filtering tests to run in the HTML reporter. It
+ * is especially useful when you use a console reporter or in a codebase
+ * with a large set of long running tests.
+ *
+ * @param name Title of unit being tested
+ * @param callback Function to close over assertions
+ */
+ // SAFETY: this is just wildly, impossibly unsafe. QUnit cannot -- ever! --
+ // provide this guarantee. However, it's also the only way to support TS
+ // in tests in Ember until we move the community over entirely to using
+ // `` and local scope.
+ only(
+ name: string,
+ callback: (this: TC, assert: Assert) => void | Promise
+ ): void;
+
+ /**
+ * Use this method to test a unit of code which is still under development (in a “todo” state).
+ * The test will pass as long as one failing assertion is present.
+ *
+ * If all assertions pass, then the test will fail signaling that `QUnit.todo` should
+ * be replaced by `QUnit.test`.
+ *
+ * @param name Title of unit being tested
+ * @param callback Function to close over assertions
+ */
+ // SAFETY: this is just wildly, impossibly unsafe. QUnit cannot -- ever! --
+ // provide this guarantee. However, it's also the only way to support TS
+ // in tests in Ember until we move the community over entirely to using
+ // `` and local scope.
+ todo(
+ name: string,
+ callback: (this: TC, assert: Assert) => void | Promise
+ ): void;
+
+ /**
+ * Adds a test like object to be skipped.
+ *
+ * Use this method to replace QUnit.test() instead of commenting out entire
+ * tests.
+ *
+ * This test's prototype will be listed on the suite as a skipped test,
+ * ignoring the callback argument and the respective global and module's
+ * hooks.
+ *
+ * @param name Title of unit being tested
+ * @param callback Function to close over assertions
+ */
+ // SAFETY: this is just wildly, impossibly unsafe. QUnit cannot -- ever! --
+ // provide this guarantee. However, it's also the only way to support TS
+ // in tests in Ember until we move the community over entirely to using
+ // `` and local scope.
+ skip(
+ name: string,
+ callback?: (this: TC, assert: Assert) => void | Promise
+ ): void;
+ }
+}
diff --git a/types/local-types.d.ts b/types/local-types.d.ts
new file mode 100644
index 00000000..2109bac6
--- /dev/null
+++ b/types/local-types.d.ts
@@ -0,0 +1,2 @@
+import 'ember-source/types';
+import 'ember-source/types/preview';
diff --git a/types/tests.ts b/types/tests.ts
new file mode 100644
index 00000000..ad423c5f
--- /dev/null
+++ b/types/tests.ts
@@ -0,0 +1,344 @@
+// These tests were ported directly from DefinitelyTyped and are unlikely to be
+// 100% desireable for the future.
+import hbs from 'htmlbars-inline-precompile';
+import { module } from 'qunit';
+import {
+ start,
+ test,
+ skip,
+ only,
+ todo,
+ setResolver,
+ setupRenderingTest,
+ setupTest,
+ SetupTestOptions,
+ setupApplicationTest,
+} from 'ember-qunit';
+import { render, RenderingTestContext, TestContext } from '@ember/test-helpers';
+import EmberResolver from 'ember-resolver';
+import EmberObject from '@ember/object';
+
+// if you don't have a custom resolver, do it like this:
+setResolver(EmberResolver.create());
+
+// (modified) tests ported from ember-test-helpers
+module('rendering', function (hooks) {
+ setupRenderingTest(hooks);
+
+ test('it renders', function (this: RenderingTestContext, assert) {
+ assert.expect(2);
+
+ // setup the outer context
+ this.set('value', 'cat');
+
+ // render the component
+ this.render(hbs`
+ {{ x-foo value=value action="result" }}
+ `);
+
+ // has to be a template
+ // @ts-expect-error
+ this.render();
+ // @ts-expect-error
+ this.render('{{ x-foo value=value action="result" }}');
+ // @ts-expect-error
+ this.render(['{{ x-foo value=value action="result" }}']);
+
+ const el = this.element.querySelector('div');
+ assert.equal(el?.innerText, 'cat', 'The component shows the correct value');
+
+ this.element.querySelector('button')?.click();
+ });
+
+ test('it renders', async function (this: RenderingTestContext, assert) {
+ assert.expect(1);
+
+ // creates the component instance
+ await render(hbs``);
+
+ await render(hbs``);
+
+ const { inputFormat } = this.setProperties({
+ inputFormat: 'M/D/YY',
+ outputFormat: 'MMMM D, YYYY',
+ date: '5/3/10',
+ });
+
+ const { inputFormat: if2, outputFormat } = this.getProperties(
+ 'inputFormat',
+ 'outputFormat'
+ );
+
+ const inputFormat2 = this.get('inputFormat');
+
+ // render the component on the page
+ this.render(hbs`bar
`);
+ assert.equal(this.element.querySelector('div')?.innerText, 'bar');
+ });
+});
+
+module('misc and async', function (hooks) {
+ hooks.beforeEach(async function (assert) {
+ assert.ok(true, 'hooks can be async');
+ });
+
+ test('It can calculate the result', function (assert) {
+ assert.expect(1);
+
+ interface Foo extends EmberObject {
+ value: string;
+ result: string;
+ }
+
+ const subject = this.owner.lookup('foo:bar') as Foo;
+
+ subject.set('value', 'foo');
+ assert.equal(subject.get('result'), 'bar');
+ });
+
+ // This test is intended to ensure the appropriate behavior for @typescript-eslint/no-misused-promises.
+ // However, we don't actually use typescript-eslint in this project and tslint has no equivalent,
+ // so we can't properly test it.
+ test('it can be async', async function (this: RenderingTestContext, assert) {
+ assert.expect(1);
+
+ await this.render(hbs`Hello
`);
+
+ assert.ok(true, 'rendered');
+ });
+
+ skip('disabled test');
+
+ skip('disabled test', function (assert) {});
+
+ // This test is intended to ensure the appropriate behavior for @typescript-eslint/no-misused-promises.
+ // However, we don't actually use typescript-eslint in this project and tslint has no equivalent,
+ // so we can't properly test it.
+ skip('it can skip async', async function (this: RenderingTestContext, assert) {
+ assert.expect(1);
+
+ await this.render(hbs`Hello
`);
+
+ assert.ok(true, 'rendered');
+ });
+
+ // This test is intended to ensure the appropriate behavior for @typescript-eslint/no-misused-promises.
+ // However, we don't actually use typescript-eslint in this project and tslint has no equivalent,
+ // so we can't properly test it.
+ only(
+ 'it can only run async',
+ async function (this: RenderingTestContext, assert) {
+ assert.expect(1);
+
+ await this.render(hbs`Hello
`);
+
+ assert.ok(true, 'rendered');
+ }
+ );
+
+ // This test is intended to ensure the appropriate behavior for @typescript-eslint/no-misused-promises.
+ // However, we don't actually use typescript-eslint in this project and tslint has no equivalent,
+ // so we can't properly test it.
+ todo(
+ 'it can have an async todo',
+ async function (this: RenderingTestContext, assert) {
+ assert.expect(1);
+
+ await this.render(hbs`Hello
`);
+
+ assert.ok(true, 'rendered');
+ }
+ );
+});
+// end tests ported from ember-test-helpers
+
+module('returning a promise', function () {
+ test('it can return Promise', function (this: TestContext, assert) {
+ return Promise.resolve();
+ });
+
+ test('it can return a non-empty Promise', function (this: TestContext, assert) {
+ return Promise.resolve('foo');
+ });
+});
+
+// https://github.com/emberjs/rfcs/blob/master/text/0232-simplify-qunit-testing-api.md#qunit-nested-modules-api
+QUnit.module('some description', function (hooks) {
+ hooks.before(() => {});
+ hooks.beforeEach(() => {});
+ hooks.afterEach(() => {});
+ hooks.after(() => {});
+
+ QUnit.test('it blends', function (assert) {
+ assert.ok(true, 'of course!');
+ });
+});
+
+// http://rwjblue.com/2017/10/23/ember-qunit-simplication/#setuprenderingtest
+module('x-foo', function (hooks) {
+ setupRenderingTest(hooks);
+});
+
+// http://rwjblue.com/2017/10/23/ember-qunit-simplication/#setuptest
+module('foo service', function (hooks) {
+ setupTest(hooks);
+});
+
+// RFC-232 equivalent of https://github.com/ember-engines/ember-engines#unitintegration-testing-for-in-repo-engines
+module('engine foo component', function (hooks) {
+ setupTest(hooks, {
+ resolver: EmberResolver.create(),
+ });
+});
+
+module('all the hooks', function (hooks) {
+ setupTest(hooks);
+
+ hooks.after(function () {
+ this.owner.lookup('service:store');
+ });
+
+ hooks.afterEach(function () {
+ this.owner.lookup('service:store');
+ });
+
+ hooks.before(function () {
+ this.owner.lookup('service:store');
+ });
+
+ hooks.beforeEach(function () {
+ this.owner.lookup('service:store');
+ });
+});
+
+module.only('exclusive module with hooks', function (hooks) {
+ setupTest(hooks);
+
+ hooks.after(function () {
+ this.owner.lookup('service:store');
+ });
+
+ hooks.afterEach(function () {
+ this.owner.lookup('service:store');
+ });
+
+ hooks.before(function () {
+ this.owner.lookup('service:store');
+ });
+
+ hooks.beforeEach(function () {
+ this.owner.lookup('service:store');
+ });
+});
+
+module('extending TestContext works', function () {
+ interface Context extends TestContext {
+ someProp: string;
+ anotherProp: boolean;
+ }
+
+ module('it works with non-async', function (nonAsyncHooks) {
+ nonAsyncHooks.before(function (this: Context) {
+ this.someProp = 'hello';
+ });
+
+ nonAsyncHooks.beforeEach(function (this: Context) {
+ this.anotherProp = true;
+ });
+
+ nonAsyncHooks.after(function (this: Context) {
+ this.someProp = 'goodbye';
+ });
+
+ nonAsyncHooks.afterEach(function (this: Context) {
+ this.anotherProp = false;
+ });
+
+ test('it works with tests', function (this: Context, assert) {
+ this.someProp = this.someProp + ' cool person';
+ assert.true(this.anotherProp);
+ });
+
+ skip('it works with skip', function (this: Context, assert) {
+ this.someProp = 'wahoo';
+ assert.ok(typeof this.someProp === 'string');
+ });
+
+ only('it works with only', function (this: Context, assert) {
+ this.someProp = 'crazy pants';
+ assert.ok(typeof this.someProp === 'string');
+ });
+
+ todo('it works with todo', function (this: Context, assert) {
+ this.someProp = 'tada';
+ assert.ok(typeof this.someProp === 'string');
+ });
+ });
+
+ module('it works with async, too', function (asyncHooks) {
+ asyncHooks.before(async function (this: Context) {
+ this.someProp = 'hello';
+ await Promise.resolve(this.someProp);
+ });
+
+ asyncHooks.beforeEach(async function (this: Context) {
+ this.anotherProp = true;
+ await Promise.resolve(this.anotherProp);
+ });
+
+ asyncHooks.after(async function (this: Context) {
+ this.someProp = 'goodbye';
+ await Promise.resolve(this.someProp);
+ });
+
+ asyncHooks.afterEach(async function (this: Context) {
+ this.anotherProp = false;
+ await Promise.resolve(this.anotherProp);
+ });
+
+ test('it works with tests', async function (this: Context, assert) {
+ this.someProp = this.someProp + ' cool person';
+ assert.true(this.anotherProp);
+ await Promise.resolve('cool');
+ });
+
+ skip('it works with skip', async function (this: Context, assert) {
+ this.someProp = 'wahoo';
+ const result = await Promise.resolve(this.someProp);
+ assert.ok(typeof result === 'string');
+ });
+
+ only('it works with only', async function (this: Context, assert) {
+ this.someProp = 'crazy pants';
+ const result = await Promise.resolve(this.someProp);
+ assert.ok(typeof result === 'string');
+ });
+
+ todo('it works with todo', async function (this: Context, assert) {
+ this.someProp = 'tada';
+ const result = await Promise.resolve(this.someProp);
+ assert.ok(typeof result === 'string');
+ });
+ });
+});
+
+start();
+
+module('with setup options', function (hooks) {
+ // $ExpectType SetupTestOptions | undefined
+ type SetupTestOptions = Parameters[1];
+ // $ExpectType SetupTestOptions | undefined
+ type SetupRenderingTestOptions = Parameters[1];
+ // $ExpectType SetupTestOptions | undefined
+ type SetupApplicationTestOptions = Parameters[1];
+
+ const resolver = EmberResolver.create();
+
+ setupTest(hooks, {});
+ setupRenderingTest(hooks, {});
+ setupApplicationTest(hooks, {});
+
+ setupTest(hooks, { resolver });
+ setupRenderingTest(hooks, { resolver });
+ setupApplicationTest(hooks, { resolver });
+});
diff --git a/types/tsconfig.json b/types/tsconfig.json
new file mode 100644
index 00000000..9b0a3352
--- /dev/null
+++ b/types/tsconfig.json
@@ -0,0 +1,11 @@
+{
+ "extends": "@tsconfig/ember",
+ "compilerOptions": {
+ "baseUrl": ".",
+ "paths": {
+ "ember-qunit": [
+ "./index.d.ts"
+ ],
+ }
+ }
+}
diff --git a/yarn.lock b/yarn.lock
index bd0b12db..e9817e09 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1047,18 +1047,18 @@
mkdirp "^1.0.4"
silent-error "^1.1.1"
-"@ember/test-helpers@^2.8.1":
- version "2.8.1"
- resolved "https://registry.yarnpkg.com/@ember/test-helpers/-/test-helpers-2.8.1.tgz#20f2e30d48172c2ff713e1db7fbec5352f918d4e"
- integrity sha512-jbsYwWyAdhL/pdPu7Gb3SG1gvIXY70FWMtC/Us0Kmvk82Y+5YUQ1SOC0io75qmOGYQmH7eQrd/bquEVd+4XtdQ==
+"@ember/test-helpers@^2.9.1":
+ version "2.9.1"
+ resolved "https://registry.yarnpkg.com/@ember/test-helpers/-/test-helpers-2.9.1.tgz#142a8d5175fc79bb328b7af0cd36755c10181050"
+ integrity sha512-1ZFZCnNfkXcQOf6Vxep/vbZMwFLfD+8heiLiQ6LSB5SY9F3VCF1yNslfgtDqmyQZXhAbbhRTDhy+rHuzzpd+yA==
dependencies:
"@ember/test-waiters" "^3.0.0"
- "@embroider/macros" "^1.6.0"
- "@embroider/util" "^1.6.0"
+ "@embroider/macros" "^1.10.0"
+ "@embroider/util" "^1.9.0"
broccoli-debug "^0.6.5"
broccoli-funnel "^3.0.8"
- ember-cli-babel "^7.26.6"
- ember-cli-htmlbars "^5.7.1"
+ ember-cli-babel "^7.26.11"
+ ember-cli-htmlbars "^6.1.1"
ember-destroyable-polyfill "^2.0.3"
"@ember/test-waiters@^3.0.0":
@@ -1071,7 +1071,7 @@
ember-cli-version-checker "^5.1.2"
semver "^7.3.5"
-"@embroider/macros@^1.0.0", "@embroider/macros@^1.6.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==
@@ -1085,6 +1085,20 @@
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==
+ dependencies:
+ "@embroider/shared-internals" "2.0.0"
+ 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/shared-internals@1.8.3", "@embroider/shared-internals@^1.0.0":
version "1.8.3"
resolved "https://registry.yarnpkg.com/@embroider/shared-internals/-/shared-internals-1.8.3.tgz#52d868dc80016e9fe983552c0e516f437bf9b9f9"
@@ -1099,7 +1113,7 @@
semver "^7.3.5"
typescript-memoize "^1.0.1"
-"@embroider/shared-internals@^2.0.0":
+"@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"
integrity sha512-qZ2/xky9mWm5YC6noOa6AiAwgISEQ78YTZNv4SNu2PFgEK/H+Ha/3ddngzGSsnXkVnIHZyxIBzhxETonQYHY9g==
@@ -1121,7 +1135,7 @@
lodash "^4.17.21"
resolve "^1.20.0"
-"@embroider/util@^1.6.0":
+"@embroider/util@^1.9.0":
version "1.9.0"
resolved "https://registry.yarnpkg.com/@embroider/util/-/util-1.9.0.tgz#331c46bdf106c44cb1dd6baaa9030d322c13cfca"
integrity sha512-9I63iJK6N01OHJafmS/BX0msUkTlmhFMIEmDl/SRNACVi0nS6QfNyTgTTeji1P/DALf6eobg/9t/N4VhS9G9QA==
@@ -1175,16 +1189,58 @@
resolved "https://registry.yarnpkg.com/@glimmer/di/-/di-0.1.11.tgz#a6878c07a13a2c2c76fcde598a5c97637bfc4280"
integrity sha512-moRwafNDwHTnTHzyyZC9D+mUSvYrs1Ak0tRPjjmCghdoHHIvMshVbEnwKb/1WmW5CUlKc2eL9rlAV32n3GiItg==
-"@glimmer/env@^0.1.7":
+"@glimmer/env@0.1.7", "@glimmer/env@^0.1.7":
version "0.1.7"
resolved "https://registry.yarnpkg.com/@glimmer/env/-/env-0.1.7.tgz#fd2d2b55a9029c6b37a6c935e8c8871ae70dfa07"
integrity sha512-JKF/a9I9jw6fGoz8kA7LEQslrwJ5jms5CXhu/aqkBWk+PmZ6pTl8mlb/eJ/5ujBGTiQzBhy5AIWF712iA+4/mw==
+"@glimmer/global-context@0.84.2":
+ version "0.84.2"
+ resolved "https://registry.yarnpkg.com/@glimmer/global-context/-/global-context-0.84.2.tgz#cd4612925dbd68787b9270e91b213691150c307f"
+ integrity sha512-6FycLh/Eq0P3LA94bJL6WHPJyOTKeQD4KBWhowZ9TbeO3p4/WUr+POKPVEyfIx6YHybhpL9MGj45Y2r0hqVigw==
+ dependencies:
+ "@glimmer/env" "^0.1.7"
+
+"@glimmer/interfaces@0.84.2", "@glimmer/interfaces@^0.84.2":
+ version "0.84.2"
+ resolved "https://registry.yarnpkg.com/@glimmer/interfaces/-/interfaces-0.84.2.tgz#764cf92c954adcd1a851e5dc68ec1f6b654dc3bd"
+ integrity sha512-tMZxQpOddUVmHEOuripkNqVR7ba0K4doiYnFd4WyswqoHPlxqpBujbIamQ+bWCWEF0U4yxsXKa31ekS/JHkiBQ==
+ dependencies:
+ "@simple-dom/interface" "^1.4.0"
+
+"@glimmer/reference@^0.84.2":
+ version "0.84.2"
+ resolved "https://registry.yarnpkg.com/@glimmer/reference/-/reference-0.84.2.tgz#c8d91a3ba0b92a9430b6023d7b6f39dd56c79af1"
+ integrity sha512-hH0VD76OXMsGSHbqaqD64u1aBEqy//jhZtIaHGwAHNpTEX+zDtW3ka298KbAn2CZyDDrNAnuc2U1Vy4COR3zlA==
+ dependencies:
+ "@glimmer/env" "^0.1.7"
+ "@glimmer/global-context" "0.84.2"
+ "@glimmer/interfaces" "0.84.2"
+ "@glimmer/util" "0.84.2"
+ "@glimmer/validator" "0.84.2"
+
+"@glimmer/util@0.84.2":
+ version "0.84.2"
+ resolved "https://registry.yarnpkg.com/@glimmer/util/-/util-0.84.2.tgz#2711ba40f25f44b2ea309cad49f5c2622c6211bc"
+ integrity sha512-VbhzE2s4rmU+qJF3gGBTL1IDjq+/G2Th51XErS8MQVMCmE4CU2pdwSzec8PyOowqCGUOrVIWuMzEI6VoPM4L4w==
+ dependencies:
+ "@glimmer/env" "0.1.7"
+ "@glimmer/interfaces" "0.84.2"
+ "@simple-dom/interface" "^1.4.0"
+
"@glimmer/util@^0.44.0":
version "0.44.0"
resolved "https://registry.yarnpkg.com/@glimmer/util/-/util-0.44.0.tgz#45df98d73812440206ae7bda87cfe04aaae21ed9"
integrity sha512-duAsm30uVK9jSysElCbLyU6QQYO2X9iLDLBIBUcCqck9qN1o3tK2qWiHbGK5d6g8E2AJ4H88UrfElkyaJlGrwg==
+"@glimmer/validator@0.84.2":
+ version "0.84.2"
+ resolved "https://registry.yarnpkg.com/@glimmer/validator/-/validator-0.84.2.tgz#29394d262cf8373fe20f4e225c1adc9857a4164b"
+ integrity sha512-9tpSmwiktsJDqriNEiFfyP+9prMSdk08THA6Ik71xS/sudBKxoDpul678uvyEYST/+Z23F8MxwKccC+QxCMXNA==
+ dependencies:
+ "@glimmer/env" "^0.1.7"
+ "@glimmer/global-context" "0.84.2"
+
"@glimmer/vm-babel-plugins@0.84.2":
version "0.84.2"
resolved "https://registry.yarnpkg.com/@glimmer/vm-babel-plugins/-/vm-babel-plugins-0.84.2.tgz#653ce82a6656b4396d87a479d8699450d35a17f0"
@@ -1436,6 +1492,11 @@
"@pnpm/network.ca-file" "^1.0.1"
config-chain "^1.1.11"
+"@simple-dom/interface@^1.4.0":
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/@simple-dom/interface/-/interface-1.4.0.tgz#e8feea579232017f89b0138e2726facda6fbb71f"
+ integrity sha512-l5qumKFWU0S+4ZzMaLXFU8tQZsicHEMEyAxI5kDFGhJsRqDwe0a7/iPA/GdxlGyDKseQQAgIz5kzU7eXTrlSpA==
+
"@sindresorhus/is@^0.14.0":
version "0.14.0"
resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea"
@@ -1470,6 +1531,11 @@
resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82"
integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==
+"@tsconfig/ember@^1.1.0":
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/@tsconfig/ember/-/ember-1.1.0.tgz#43b50df65d3e1236306aa5857e2daa4a1298387e"
+ integrity sha512-VzIrPO7ZpnIEmU+dJe3ubEPhxUIyavwIh2vxg8rXrwSnB99hdVcq0ZFPQ4KRP0LrSNzaPI1QA2sATIPwnBYPQg==
+
"@types/body-parser@*":
version "1.19.2"
resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.2.tgz#aea2059e28b7658639081347ac4fab3de166e6f0"
@@ -1641,6 +1707,11 @@
resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb"
integrity sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==
+"@types/qunit@^2.19.3":
+ version "2.19.3"
+ resolved "https://registry.yarnpkg.com/@types/qunit/-/qunit-2.19.3.tgz#16e975469a36092929627f997c3dafca198a1aea"
+ integrity sha512-Vi47qmJ0viJoxW1kRDbhuYXGd2F0RREDfh69Hd4v/nlDV0YIjXPCAy6OebWKCZIZr680bQVQTJTL1OfhQoTvVw==
+
"@types/range-parser@*":
version "1.2.4"
resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc"
@@ -1654,6 +1725,11 @@
"@types/glob" "*"
"@types/node" "*"
+"@types/rsvp@^4.0.4":
+ version "4.0.4"
+ resolved "https://registry.yarnpkg.com/@types/rsvp/-/rsvp-4.0.4.tgz#55e93e7054027f1ad4b4ebc1e60e59eb091e2d32"
+ integrity sha512-J3Ol++HCC7/hwZhanDvggFYU/GtxHxE/e7cGRWxR04BF7Tt3TqJZ84BkzQgDxmX0uu8IagiyfmfoUlBACh2Ilg==
+
"@types/serve-static@*":
version "1.15.0"
resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.15.0.tgz#c7930ff61afb334e121a9da780aac0d9b8f34155"
@@ -2264,7 +2340,7 @@ babel-plugin-filter-imports@^4.0.0:
"@babel/types" "^7.7.2"
lodash "^4.17.15"
-babel-plugin-htmlbars-inline-precompile@^5.0.0, babel-plugin-htmlbars-inline-precompile@^5.2.1, babel-plugin-htmlbars-inline-precompile@^5.3.0:
+babel-plugin-htmlbars-inline-precompile@^5.2.1, babel-plugin-htmlbars-inline-precompile@^5.3.0:
version "5.3.1"
resolved "https://registry.yarnpkg.com/babel-plugin-htmlbars-inline-precompile/-/babel-plugin-htmlbars-inline-precompile-5.3.1.tgz#5ba272e2e4b6221522401f5f1d98a73b1de38787"
integrity sha512-QWjjFgSKtSRIcsBhJmEwS2laIdrA6na8HAlc/pEAhjHgQsah/gMiBFRZvbQTy//hWxR4BMwV7/Mya7q5H8uHeA==
@@ -3980,28 +4056,6 @@ ember-cli-get-component-path-option@^1.0.0:
resolved "https://registry.yarnpkg.com/ember-cli-get-component-path-option/-/ember-cli-get-component-path-option-1.0.0.tgz#0d7b595559e2f9050abed804f1d8eff1b08bc771"
integrity sha512-k47TDwcJ2zPideBCZE8sCiShSxQSpebY2BHcX2DdipMmBox5gsfyVrbKJWIHeSTTKyEUgmBIvQkqTOozEziCZA==
-ember-cli-htmlbars@^5.7.1:
- version "5.7.2"
- resolved "https://registry.yarnpkg.com/ember-cli-htmlbars/-/ember-cli-htmlbars-5.7.2.tgz#e0cd2fb3c20d85fe4c3e228e6f0590ee1c645ba8"
- integrity sha512-Uj6R+3TtBV5RZoJY14oZn/sNPnc+UgmC8nb5rI4P3fR/gYoyTFIZSXiIM7zl++IpMoIrocxOrgt+mhonKphgGg==
- dependencies:
- "@ember/edition-utils" "^1.2.0"
- babel-plugin-htmlbars-inline-precompile "^5.0.0"
- broccoli-debug "^0.6.5"
- broccoli-persistent-filter "^3.1.2"
- broccoli-plugin "^4.0.3"
- common-tags "^1.8.0"
- ember-cli-babel-plugin-helpers "^1.1.1"
- ember-cli-version-checker "^5.1.2"
- fs-tree-diff "^2.0.1"
- hash-for-dep "^1.5.1"
- heimdalljs-logger "^0.1.10"
- json-stable-stringify "^1.0.1"
- semver "^7.3.4"
- silent-error "^1.1.1"
- strip-bom "^4.0.0"
- walk-sync "^2.2.0"
-
ember-cli-htmlbars@^6.1.1:
version "6.1.1"
resolved "https://registry.yarnpkg.com/ember-cli-htmlbars/-/ember-cli-htmlbars-6.1.1.tgz#f5b588572a5d18ad087560122b8dabc90145173d"
@@ -4322,10 +4376,10 @@ ember-source-channel-url@^3.0.0:
dependencies:
node-fetch "^2.6.0"
-ember-source@~4.9.1:
- version "4.9.1"
- resolved "https://registry.yarnpkg.com/ember-source/-/ember-source-4.9.1.tgz#1b5d84d753ebeab7f372dbd7f39c98123e98cd41"
- integrity sha512-45dobRcQapTpWa6VWgDcAv6bP6iDxCVi5pJAf04NSRjDLHsjVGUCTdRslOl5rt3sX8dZJqakMnqYD2DwVjDf3A==
+ember-source@~4.8.3:
+ version "4.8.3"
+ resolved "https://registry.yarnpkg.com/ember-source/-/ember-source-4.8.3.tgz#ec2ac92fb804034401ae6c46bb148b4f4d7ccfca"
+ integrity sha512-q22H5/zW1fRASokVxQftHq++keg6gGRChn5JCQc8lVXi89GxKjFkX94+7EVsqIGKW2Zf/V8d/MfLCH0gI298LQ==
dependencies:
"@babel/helper-module-imports" "^7.16.7"
"@babel/plugin-transform-block-scoping" "^7.16.0"
@@ -4898,6 +4952,11 @@ expand-tilde@^2.0.0, expand-tilde@^2.0.2:
dependencies:
homedir-polyfill "^1.0.1"
+expect-type@^0.15.0:
+ version "0.15.0"
+ resolved "https://registry.yarnpkg.com/expect-type/-/expect-type-0.15.0.tgz#89f75e22c88554844ea2b2faf4ef5fc2e579d3b5"
+ integrity sha512-yWnriYB4e8G54M5/fAFj7rCIBiKs1HAACaY13kCz6Ku0dezjS9aMcfcdVK2X8Tv2tEV1BPz/wKfQ7WA4S/d8aA==
+
express@^4.10.7, express@^4.18.1:
version "4.18.2"
resolved "https://registry.yarnpkg.com/express/-/express-4.18.2.tgz#3fabe08296e930c796c19e3c516979386ba9fd59"
@@ -9942,11 +10001,6 @@ strip-bom@^3.0.0:
resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3"
integrity sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==
-strip-bom@^4.0.0:
- version "4.0.0"
- resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878"
- integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==
-
strip-eof@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf"
@@ -10383,6 +10437,11 @@ typescript-memoize@^1.0.0-alpha.3, typescript-memoize@^1.0.1:
resolved "https://registry.yarnpkg.com/typescript-memoize/-/typescript-memoize-1.1.1.tgz#02737495d5df6ebf72c07ba0d002e8f4cf5ccfa0"
integrity sha512-GQ90TcKpIH4XxYTI2F98yEQYZgjNMOGPpOgdjIBhaLaWji5HPWlRnZ4AeA1hfBxtY7bCGDJsqDDHk/KaHOl5bA==
+typescript@^4.9.4:
+ version "4.9.4"
+ resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.4.tgz#a2a3d2756c079abda241d75f149df9d561091e78"
+ integrity sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==
+
uc.micro@^1.0.1, uc.micro@^1.0.5:
version "1.0.6"
resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.6.tgz#9c411a802a409a91fc6cf74081baba34b24499ac"