Skip to content

Commit

Permalink
Refactor TypeScript definition
Browse files Browse the repository at this point in the history
Remove the need for a build step.

Update the `throws` and `notThrows` assertions, adding `Observable` and
allowing the returned error to be typed.

Remove the inline documentation, to be added later.

Simplify typing of `t.context`. Reassigning the `test` method along with
a type cast is now sufficient.

Update to TypeScript 2.7.1.
  • Loading branch information
novemberborn committed Feb 4, 2018
1 parent 7c0bf9b commit bac3c11
Show file tree
Hide file tree
Showing 6 changed files with 257 additions and 388 deletions.
95 changes: 51 additions & 44 deletions docs/recipes/typescript.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,7 @@ Translations: [Español](https://github.com/avajs/ava-docs/blob/master/es_ES/doc

AVA comes bundled with a TypeScript definition file. This allows developers to leverage TypeScript for writing tests.


## Setup

First install [TypeScript](https://github.com/Microsoft/TypeScript) (if you already have it installed, make sure you use version 2.1 or greater).

```
$ npm install --save-dev typescript
```

Create a [`tsconfig.json`](http://www.typescriptlang.org/docs/handbook/tsconfig-json.html) file. This file specifies the compiler options required to compile the project or the test file.

```json
{
"compilerOptions": {
"module": "commonjs",
"target": "es2015",
"sourceMap": true
}
}
```
This guide assumes you've already set up TypeScript for your project. Note that AVA's definition has been tested with version 2.7.1.

Add a `test` script in the `package.json` file. It will compile the project first and then run AVA.

Expand All @@ -35,8 +16,9 @@ Add a `test` script in the `package.json` file. It will compile the project firs
}
```

Make sure that AVA runs your built TypeScript files.

## Add tests
## Writing tests

Create a `test.ts` file.

Expand All @@ -50,57 +32,82 @@ test(async (t) => {
});
```

## Working with [macros](https://github.com/avajs/ava#test-macros)
## Using [macros](https://github.com/avajs/ava#test-macros)

In order to be able to assign the `title` property to a macro:
In order to be able to assign the `title` property to a macro you need to type the function:

```ts
import test, { AssertContext, Macro } from 'ava';
import test, {Macro} from 'ava';

const macro: Macro<AssertContext> = (t, input, expected) => {
const macro: Macro = (t, input: string, expected: number) => {
t.is(eval(input), expected);
}

macro.title = (providedTitle, input, expected) => `${providedTitle} ${input} = ${expected}`.trim();
};
macro.title = (providedTitle: string, input: string, expected: number) => `${providedTitle} ${input} = ${expected}`.trim();

test(macro, '2 + 2', 4);
test(macro, '2 * 3', 6);
test('providedTitle', macro, '3 * 3', 9);
```

## Working with [`context`](https://github.com/avajs/ava#test-context)

By default, the type of `t.context` will be [`any`](https://www.typescriptlang.org/docs/handbook/basic-types.html#any). AVA exposes an interface `RegisterContextual<T>` which you can use to apply your own type to `t.context`. This can help you catch errors at compile-time:
You'll need a different type if you're expecting your macro to be used with a callback test:

```ts
import * as ava from 'ava';
import test, {CbMacro} from 'ava';

function contextualize<T>(getContext: () => T): ava.RegisterContextual<T> {
ava.test.beforeEach(t => {
Object.assign(t.context, getContext());
});
const macro: CbMacro = t => {
t.pass();
setTimeout(t.end, 100);
};

return ava.test;
}
test.cb(macro);
```

const test = contextualize(() => ({ foo: 'bar' }));
## Typing [`t.context`](https://github.com/avajs/ava#test-context)

By default, the type of `t.context` will be the empty object (`{}`). AVA exposes an interface `TestInterface<Context>` which you can use to apply your own type to `t.context`. This can help you catch errors at compile-time:

```ts
import anyTest, {TestInterface} from 'ava';

const test: TestInterface<{foo: string}> = anyTest;

test.beforeEach(t => {
t.context = {foo: 'bar'};
});

test.beforeEach(t => {
t.context.foo = 123; // error: Type '123' is not assignable to type 'string'
});

test.serial.failing.cb('very long chains are properly typed', t => {
t.context.fooo = 'a value'; // error: Property 'fooo' does not exist on type '{ foo: string }'
test.serial.cb.failing('very long chains are properly typed', t => {
t.context.fooo = 'a value'; // error: Property 'fooo' does not exist on type ''
});

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'
});
```

You can also type the context when creating macros:

## Execute the tests
```ts
import anyTest, {Macro, TestInterface} from 'ava';

interface Context {
foo: string
}

const test: TestInterface<Context> = anyTest;

const macro: Macro<Context> = (t, expected: string) => {
t.is(t.context.foo, expected);
};

test.beforeEach(t => {
t.context = {foo: 'bar'};
});

test('foo is bar', macro, 'bar');
```
$ npm test
```

Note that, despite the type cast above, when executing `t.context` is an empty object unless it's assigned.
199 changes: 199 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
export interface ObservableLike {
subscribe(observer: (value: any) => void): void;
}

export type ThrowsErrorValidator = (new (...args: Array<any>) => any) | RegExp | string | ((error: any) => boolean);

export interface SnapshotOptions {
id?: string;
}

export interface Assertions {
deepEqual<ValueType = any>(actual: ValueType, expected: ValueType, message?: string): void;
fail(message?: string): void;
false(actual: any, message?: string): void;
falsy(actual: any, message?: string): void;
ifError(error: any, message?: string): void;
is<ValueType = any>(actual: ValueType, expected: ValueType, message?: string): void;
not<ValueType = any>(actual: ValueType, expected: ValueType, message?: string): void;
notDeepEqual<ValueType = any>(actual: ValueType, expected: ValueType, message?: string): void;
notRegex(string: string, regex: RegExp, message?: string): void;
notThrows(value: () => never, message?: string): void;
notThrows(value: () => ObservableLike, message?: string): Promise<void>;
notThrows(value: () => PromiseLike<any>, message?: string): Promise<void>;
notThrows(value: () => any, message?: string): void;
notThrows(value: ObservableLike, message?: string): Promise<void>;
notThrows(value: PromiseLike<any>, message?: string): Promise<void>;
pass(message?: string): void;
regex(string: string, regex: RegExp, message?: string): void;
snapshot(expected: any, message?: string): void;
snapshot(expected: any, options: SnapshotOptions, message?: string): void;
throws(value: () => never, error?: ThrowsErrorValidator, message?: string): any;
throws(value: () => ObservableLike, error?: ThrowsErrorValidator, message?: string): Promise<any>;
throws(value: () => PromiseLike<any>, error?: ThrowsErrorValidator, message?: string): Promise<any>;
throws(value: () => any, error?: ThrowsErrorValidator, message?: string): any;
throws(value: ObservableLike, error?: ThrowsErrorValidator, message?: string): Promise<any>;
throws(value: PromiseLike<any>, error?: ThrowsErrorValidator, message?: string): Promise<any>;
true(actual: any, message?: string): void;
truthy(actual: any, message?: string): void;
}

export interface ExecutionContext<Context = {}> extends Assertions {
context: Context;
skip: Assertions;
title: string;
log(...values: Array<any>): void;
plan(count: number): void;
}

export interface CbExecutionContext<Context = {}> extends ExecutionContext<Context> {
end(): void;
}

export type ImplementationResult = PromiseLike<void> | ObservableLike | Iterator<any> | void;
export type Implementation<Context = {}> = (t: ExecutionContext<Context>) => ImplementationResult;
export type CbImplementation<Context = {}> = (t: CbExecutionContext<Context>) => ImplementationResult;

export interface Macro<Context = {}> {
(t: ExecutionContext<Context>, ...args: Array<any>): ImplementationResult;
title?: (providedTitle: string, ...args: Array<any>) => string;
}

export interface CbMacro<Context = {}> {
(t: CbExecutionContext<Context>, ...args: Array<any>): ImplementationResult;
title?: (providedTitle: string, ...args: Array<any>) => string;
}

export interface TestInterface<Context = {}> {
(title: string, implementation: Implementation<Context>): void;
(title: string, macro: Macro<Context> | Macro<Context>[], ...args: Array<any>): void;
(macro: Macro<Context> | Macro<Context>[], ...args: Array<any>): void;

after: AfterInterface<null>;
afterEach: AfterInterface<Context>;
before: BeforeInterface<null>;
beforeEach: BeforeInterface<Context>;
cb: CbInterface<Context>;
failing: FailingInterface<Context>;
only: OnlyInterface<Context>;
serial: SerialInterface<Context>;
skip: SkipInterface<Context>;
todo: TodoDeclaration;
}

export interface AfterInterface<Context = {}> {
(title: string, implementation: Implementation<Context>): void;
(title: string, macro: Macro<Context> | Macro<Context>[], ...args: Array<any>): void;
(macro: Macro<Context> | Macro<Context>[], ...args: Array<any>): void;

always: AlwaysInterface<Context>;
cb: HookCbInterface<Context>;
skip: SkipInterface<Context>;
}

export interface AlwaysInterface<Context = {}> {
(title: string, implementation: Implementation<Context>): void;
(title: string, macro: Macro<Context> | Macro<Context>[], ...args: Array<any>): void;
(macro: Macro<Context> | Macro<Context>[], ...args: Array<any>): void;

cb: HookCbInterface<Context>;
skip: SkipInterface<Context>;
}

export interface BeforeInterface<Context = {}> {
(title: string, implementation: Implementation<Context>): void;
(title: string, macro: Macro<Context> | Macro<Context>[], ...args: Array<any>): void;
(macro: Macro<Context> | Macro<Context>[], ...args: Array<any>): void;

cb: HookCbInterface<Context>;
skip: SkipInterface<Context>;
}

export interface CbInterface<Context = {}> {
(title: string, implementation: CbImplementation<Context>): void;
(title: string, macro: CbMacro<Context> | CbMacro<Context>[], ...args: Array<any>): void;
(macro: CbMacro<Context> | CbMacro<Context>[], ...args: Array<any>): void;

failing: CbFailingInterface<Context>;
only: CbOnlyInterface<Context>;
skip: CbSkipInterface<Context>;
}

export interface CbFailingInterface<Context = {}> {
(title: string, implementation: CbImplementation<Context>): void;
(title: string, macro: CbMacro<Context> | CbMacro<Context>[], ...args: Array<any>): void;
(macro: CbMacro<Context> | CbMacro<Context>[], ...args: Array<any>): void;

only: CbOnlyInterface<Context>;
skip: CbSkipInterface<Context>;
}

export interface CbOnlyInterface<Context = {}> {
(title: string, implementation: CbImplementation<Context>): void;
(title: string, macro: CbMacro<Context> | CbMacro<Context>[], ...args: Array<any>): void;
(macro: CbMacro<Context> | CbMacro<Context>[], ...args: Array<any>): void;
}

export interface CbSkipInterface<Context = {}> {
(title: string, implementation: CbImplementation<Context>): void;
(title: string, macro: CbMacro<Context> | CbMacro<Context>[], ...args: Array<any>): void;
(macro: CbMacro<Context> | CbMacro<Context>[], ...args: Array<any>): void;
}

export interface FailingInterface<Context = {}> {
(title: string, implementation: Implementation<Context>): void;
(title: string, macro: Macro<Context> | Macro<Context>[], ...args: Array<any>): void;
(macro: Macro<Context> | Macro<Context>[], ...args: Array<any>): void;

only: OnlyInterface<Context>;
skip: SkipInterface<Context>;
}

export interface HookCbInterface<Context = {}> {
(title: string, implementation: CbImplementation<Context>): void;
(title: string, macro: CbMacro<Context> | CbMacro<Context>[], ...args: Array<any>): void;
(macro: CbMacro<Context> | CbMacro<Context>[], ...args: Array<any>): void;

skip: CbSkipInterface<Context>;
}

export interface OnlyInterface<Context = {}> {
(title: string, implementation: Implementation<Context>): void;
(title: string, macro: Macro<Context> | Macro<Context>[], ...args: Array<any>): void;
(macro: Macro<Context> | Macro<Context>[], ...args: Array<any>): void;
}

export interface SerialInterface<Context = {}> {
(title: string, implementation: Implementation<Context>): void;
(title: string, macro: Macro<Context> | Macro<Context>[], ...args: Array<any>): void;
(macro: Macro<Context> | Macro<Context>[], ...args: Array<any>): void;

cb: CbInterface<Context>;
failing: FailingInterface<Context>;
only: OnlyInterface<Context>;
skip: SkipInterface<Context>;
todo: TodoDeclaration;
}

export interface SkipInterface<Context = {}> {
(title: string, implementation: Implementation<Context>): void;
(title: string, macro: Macro<Context> | Macro<Context>[], ...args: Array<any>): void;
(macro: Macro<Context> | Macro<Context>[], ...args: Array<any>): void;
}

export type TodoDeclaration = (title: string) => void;

declare const test: TestInterface;
export default test;

export {test};
export const after: AfterInterface<null>;
export const afterEach: AfterInterface;
export const before: BeforeInterface<null>;
export const beforeEach: BeforeInterface;
export const cb: CbInterface;
export const failing: FailingInterface;
export const only: OnlyInterface;
export const serial: SerialInterface;
export const skip: SkipInterface;
export const todo: TodoDeclaration;
12 changes: 3 additions & 9 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit bac3c11

Please sign in to comment.