ember-cli-typescript
ember-cli-typescript copied to clipboard
Enable extending TestContext interface with latest version of @ember/test-helpers
Which package(s) does this enhancement pertain to?
- [ ] @types/ember
- [ ] @types/ember__string
- [ ] @types/ember__polyfills
- [ ] @types/ember__object
- [ ] @types/ember__utils
- [ ] @types/ember__array
- [ ] @types/ember__engine
- [ ] @types/ember__debug
- [ ] @types/ember__runloop
- [ ] @types/ember__error
- [ ] @types/ember__controller
- [ ] @types/ember__component
- [ ] @types/ember__routing
- [ ] @types/ember__application
- [ ] @types/ember__test
- [x] @types/ember__test-helpers
- [ ] @types/ember__service
- [ ] @types/ember-data
- [ ] @types/rsvp
- [ ] Other
- [ ] I don't know
Please write a user story for this feature
As an engineer, I want to be able to extend @ember/test-helpers TestContext interface with additional properties using TypeScript's declaration merging (https://www.typescriptlang.org/docs/handbook/declaration-merging.html).
Example:
import { TestContext } from 'ember-test-helpers'
export function setupSomethingForApp(hooks: NestedHooks): void
declare module "ember-test-helpers" {
interface TestContext {
somethingElseOnTestContext: boolean;
}
}
But with newer version of @ember/test-helpers this is no longer possible.
Workaround for now:
type TestContext = import('ember-test-helpers').TestContext;
As a friendly reminder, there is a section about TestContext in the documentation:
https://docs.ember-cli-typescript.com/ember/testing#the-testcontext
If you’ve been around TypeScript a little, and you look up the type of the TestContext and realize its an interface, you might be tempted to reach for declaration merging here. Don’t! If you do that, every single test in your entire application will now have a user: User property on it!
And it makes sense.
But what you are looking for is still possible. I am working on app with this custom definition files:
// file: types/ember-cli-mirage/index.d.ts
declare module 'ember-cli-mirage/test-context' {
import type { Server } from 'miragejs'
import type { TestContext } from 'ember-test-helpers'
// Make `this.server` recognised in the tests where Mirage is needed
interface ITestContextMirage extends TestContext {
server: Server
}
}
Then in our tests we can reuse and extend this new interface:
// file: tests/integration/components/whatever.ts
import type { ITestContextMirage } from 'ember-cli-mirage/test-context'
interface Context extends ITestContextMirage {
yes?: string;
}
...
test('whatever', async function (this: Context, assert) {
this.server.create(...)
...
})
Yes, that is one of the solutions. But escape hatch, as it has been so far, would still nice. Because defining context one by one for thousands of tests is quite repetitive 🧐.
Nothing prevents you from doing this to get the interfaces merged:
// file: /types/your-app/index.d.ts
declare global {
interface TestContext {
yes: string
}
}
or similarly:
// file: /types/your-app/index.d.ts
declare global { ... }
declare module 'ember-test-helpers' {
interface TestContext {
somethingElseOnTestContext: boolean;
}
}
And you won't need to specify the this in every test-function, or to import anything in your tests.
Extending the TestContext works fine where it is still needed:
import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { render, type TestContext } from '@ember/test-helpers';
import { hbs } from 'ember-cli-htmlbars';
interface Ctx extends TestContext {
foo: string;
}
module('demo it', function (hooks) {
test('it works', async function (this: Ctx, assert) {
this.foo = 'hello';
await render(hbs`<p>{{this.foo}}</p>`);
// ...
});
});
Instead of using the this type like this, though, we now recommend that people use render’s ability to accept a component in conjunction with the fact that components authored using <template> (i.e. in “strict mode”, in .gjs or .gts files) have access to their JS lexical scope, and therefore you don’t need to set things on this anyway:
import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { render } from '@ember/test-helpers';
module('demo it', function (hooks) {
setupRenderingTest(hooks);
test('it works', async function (assert) {
let foo = 'hello';
await render(<template><p>{{foo}}</p></template>);
// ...
});
});
This “just works” with Glint, and has the massive upside of not needing to deal with the TestContext at all.
Thanks @chriskrycho , good to know.
Just to be sure, following what you said I assume your last example should be this?
- test('it works', async function (this: Ctx, assert) {
+ test('it works', async function (assert) {
Ah, yep, just a copy-paste error; edited the original for future readers too!