-
Notifications
You must be signed in to change notification settings - Fork 1.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Make test context optionally type aware for TypeScript #1298
Conversation
Also add 'contextualize' function to main module exports, which will allow seeding the context, and return a test function which will give the context property the correct type.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Clever!
Would it be possible to do:
function contextualize<T>(getContext: () => T): ava.ITest<T> {
(Without repeating the { context }
bit?
Perhaps @despairblue, @ivogabe, @SamVerschueren could provide input as well? 😇
.gitignore
Outdated
@@ -3,3 +3,6 @@ node_modules | |||
coverage | |||
bench/.results | |||
types/generated.d.ts | |||
|
|||
# VS Code configuration | |||
.vscode |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You should add this to your machine's .gitignore
, rather than adding it in this repo.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure. I didn't want to do that initially because I have other repos that I want .vscode
files checked in for, but maybe I can use .git/info/exclude
instead.
types/base.d.ts
Outdated
export function test(run: ContextualTest): void; | ||
export function test(name: string, run: Macros<ContextualTestContext>, ...args: any[]): void; | ||
export function test(run: Macros<ContextualTestContext>, ...args: any[]): void; | ||
export interface ITest<T> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's just use Test<T>
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can do - I think my C# is showing 😳
export type Test = (t: TestContext) => PromiseLike<void> | Iterator<any> | Observable | void; | ||
export type ContextualTest = (t: ContextualTestContext) => PromiseLike<void> | Iterator<any> | Observable | void; | ||
export type GenericTest<T> = (t: GenericTestContext<T>) => PromiseLike<void> | Iterator<any> | Observable | void; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add export type ContextualTest = GenericTest<{ context: any }>;
, otherwise this might break some users. Same for ContextualCallbackTest
Overall this looks good. Could you add some comments to |
@novemberborn re whether we could do: function contextualize<T>(getContext: () => T): ava.ITest<T> { ... } Maybe what I could do is export two types - export type GenericContextualTest<T> = Test<{ context: T }>; The name Anyway, this way people wouldn't have to constantly repeat the |
Gets rid of a lot of repeated definitions, and should make it easier to make the whole thing properly generic.
@novemberborn @ivogabe updated with suggested changes, and improved a couple of other things: Usage example: import * as ava from 'ava'
function contextualize<T>(getContext: () => T): ava.ContextualTestFunction<T> {
ava.test.beforeEach(t => {
Object.assign(t.context, getContext());
});
return ava.test;
}
const test = contextualize(() => ({ foo: 'bar' }));
test.beforeEach(t => {
t.context.foo = 123; // error: Type '123' is not assignable to type 'string'
});
test.after.always.failing.cb.serial('very long chains are properly typed', t => {
t.context.fooo = 'a value'; // error: Property 'fooo' does not exist on type '{ foo: string }'
});
test('an actual test', t => {
t.deepEqual(t.context.foo.map(c => c), ['b', 'a', 'r']); // error: Property 'map' does not exist on type 'string'
}); In the above example, This would allow people to extend ava with things like their favourite assertion/mocking libraries, and have strongly typed, convenient test setups: import MyClass from '../MyClass';
import calc = require('npm-calculator');
import chai = require('chai');
import sinon = require('sinon');
import contextualize from './ava-contextualize'; // same contextualize method as above as a module
const test = contextualize(() => {
const myClass = new MyClass();
const sandbox = sinon.sandbox.create();
const expect = chai.expect;
return { myClass, sandbox, expect };
});
test.afterEach(t => t.sandbox.restore());
test('times uses npm-calculator multiply', t => {
t.context.sandbox.stub(calc, 'multiply').returns(42);
const sixTimesNine = t.context.myClass.times(6, 9);
t.context.expect(calc.multiply).to.be.calledOnce;
t.context.expect(sixTimesNine).to.equal(42);
}); |
Here's a gist of the generated types file, since it can't be seen on the PR. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the gist, the generated file looks good to me. Using underscores is not very common, but is probably the best way to go. With caps we could have some problems, for instance testAfterEach
could then be after.each
and afterEach
. Though I think that we should rename the first part of the names, since types should start with an upper case letter. So maybe one of these:
- Rename
TestFunction(Core)
toRegister(Base)
, andtest_**
toRegister_**
- Rename
TestFunction(Core)
toAVA(Core)
, andtest_**
toAVA_
.
types/make.js
Outdated
// Returns the declarations for the 'test' function and core types | ||
function testFunctionDeclarations() { | ||
return ( | ||
`export default test; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Move this to base.d.ts
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I do, base.d.ts
will no longer compile because ContextualTestFunction
and TestFunction
(or Register
and RegisterContextual
once they're renamed) don't exist in base.d.ts.
Or do you think that doesn't matter so much because base.d.ts
isn't used for anything except seeding generated.d.ts
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's not an issue, base.d.ts
is indeed only used by the generation script to write generated.d.ts
.
types/base.d.ts
Outdated
export type ContextualCallbackTest = (t: ContextualCallbackTestContext) => void; | ||
export type GenericCallbackTest<T> = (t: GenericCallbackTestContext<T>) => void; | ||
|
||
interface Context<T> { context: T } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add export
types/base.d.ts
Outdated
export function test(run: ContextualTest): void; | ||
export function test(name: string, run: Macros<ContextualTestContext>, ...args: any[]): void; | ||
export function test(run: Macros<ContextualTestContext>, ...args: any[]): void; | ||
interface TestFunctionCore<T> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we think of a better name here? Technically this is not the function that does the tests, it only registers a test. So maybe something like RegisterBase<T>
, and rename TestFunction
Register<T>
?
And add export
here
Also rename test_* interfaces to Register_*.
This should be documented somewhere, probably here: https://github.com/avajs/ava/blob/master/docs/recipes/typescript.md |
@sindresorhus added the above usage example. |
Looks good to me, I've tested it on one of my projects and it worked just fine. This does however not work with TypeScript 2.0, 2.1 is required (because of the use of |
readme.md
Outdated
@@ -578,6 +578,8 @@ Keep in mind that the `beforeEach` and `afterEach` hooks run just before and aft | |||
|
|||
Remember that AVA runs each test file in its own process. You may not have to clean up global state in a `after`-hook since that's only called right before the process exits. | |||
|
|||
### Test context |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
###
=> ####
Thank you @mmkal. This is a great improvement! :) |
Thanks @sindresorhus @novemberborn @ivogabe ! Does it get auto-published to NPM? Or is it done ad-hoc? |
@mmkal No, releases are manually published. Was planning to do a new one next week (It's a lot of work to manually test and write release notes). |
This sets out to solve the problem highlighted in #1291
Usage example: see comment further down.