Skip to content

Commit e4fef0c

Browse files
authored
Rudimentary support for loading TypeScript test files
* Generalize providers rather than supporting just the Babel one * Add rudimentary TypeScript support
1 parent 91a0086 commit e4fef0c

File tree

10 files changed

+158
-112
lines changed

10 files changed

+158
-112
lines changed

docs/06-configuration.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,8 @@ Note that providing files on the CLI overrides the `files` option.
6161

6262
Provide the `babel` option (and install [`@ava/babel`](https://github.com/avajs/babel) as an additional dependency) to enable Babel compilation.
6363

64+
Provide the `typescript` option (and install [`@ava/typescript`](https://github.com/avajs/typescript) as an additional dependency) to enable (rudimentary) TypeScript support.
65+
6466
## Using `ava.config.*` files
6567

6668
Rather than specifying the configuration in the `package.json` file you can use `ava.config.js` or `ava.config.cjs` files.

docs/recipes/typescript.md

Lines changed: 71 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -4,84 +4,15 @@ Translations: [Español](https://github.com/avajs/ava-docs/blob/master/es_ES/doc
44

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

7-
This guide assumes you've already set up TypeScript for your project. Note that AVA's definition has been tested with version 3.7.5.
8-
9-
## Configuring AVA to compile TypeScript files on the fly
10-
11-
You can configure AVA to recognize TypeScript files. Then, with `ts-node` installed, you can compile them on the fly.
12-
13-
`package.json`:
7+
Out of the box AVA does not load TypeScript test files, however. Rudimentary support is available via the [`@ava/typescript`] package. You can also use AVA with [`ts-node`]. Read on for details.
148

15-
```json
16-
{
17-
"ava": {
18-
"extensions": [
19-
"ts"
20-
],
21-
"require": [
22-
"ts-node/register"
23-
]
24-
}
25-
}
26-
```
27-
28-
It's worth noting that with this configuration tests will fail if there are TypeScript build errors. If you want to test while ignoring these errors you can use `ts-node/register/transpile-only` instead of `ts-node/register`.
29-
30-
### Using module path mapping
31-
32-
`ts-node` [does not support module path mapping](https://github.com/TypeStrong/ts-node/issues/138), however you can use [`tsconfig-paths`](https://github.com/dividab/tsconfig-paths#readme).
33-
34-
Once installed, add the `tsconfig-paths/register` entry to the `require` section of AVA's config:
35-
36-
`package.json`:
37-
38-
```json
39-
{
40-
"ava": {
41-
"extensions": [
42-
"ts"
43-
],
44-
"require": [
45-
"ts-node/register",
46-
"tsconfig-paths/register"
47-
]
48-
}
49-
}
50-
```
51-
52-
Then you can start using module aliases:
53-
54-
`tsconfig.json`:
55-
```json
56-
{
57-
"baseUrl": ".",
58-
"paths": {
59-
"@helpers/*": ["helpers/*"]
60-
}
61-
}
62-
```
63-
64-
Test:
65-
66-
```ts
67-
import myHelper from '@helpers/myHelper';
68-
69-
// Rest of the file
70-
```
71-
72-
## Compiling TypeScript files before running AVA
9+
This guide assumes you've already set up TypeScript for your project. Note that AVA's definition has been tested with version 3.7.5.
7310

74-
Add a `test` script in the `package.json` file. It will compile the project first and then run AVA.
11+
## Enabling AVA's TypeScript support
7512

76-
```json
77-
{
78-
"scripts": {
79-
"test": "tsc && ava"
80-
}
81-
}
82-
```
13+
Currently, AVA's TypeScript support is designed to work for projects that precompile TypeScript. Please see [`@ava/typescript`] for setup instructions.
8314

84-
Make sure that AVA runs your built TypeScript files.
15+
Read on until the end to learn how to use [`ts-node`] with AVA.
8516

8617
## Writing tests
8718

@@ -221,3 +152,69 @@ test('throwsAsync', async t => {
221152
```
222153

223154
Note that, despite the typing, the assertion returns `undefined` if it fails. Typing the assertions as returning `Error | undefined` didn't seem like the pragmatic choice.
155+
156+
## On the fly compilation using `ts-node`
157+
158+
If [`@ava/typescript`] doesn't do the trick you can use [`ts-node`]. Make sure it's installed and then configure AVA to recognize TypeScript files and register [`ts-node`]:
159+
160+
`package.json`:
161+
162+
```json
163+
{
164+
"ava": {
165+
"extensions": [
166+
"ts"
167+
],
168+
"require": [
169+
"ts-node/register"
170+
]
171+
}
172+
}
173+
```
174+
175+
It's worth noting that with this configuration tests will fail if there are TypeScript build errors. If you want to test while ignoring these errors you can use `ts-node/register/transpile-only` instead of `ts-node/register`.
176+
177+
### Using module path mapping
178+
179+
`ts-node` [does not support module path mapping](https://github.com/TypeStrong/ts-node/issues/138), however you can use [`tsconfig-paths`](https://github.com/dividab/tsconfig-paths#readme).
180+
181+
Once installed, add the `tsconfig-paths/register` entry to the `require` section of AVA's config:
182+
183+
`package.json`:
184+
185+
```json
186+
{
187+
"ava": {
188+
"extensions": [
189+
"ts"
190+
],
191+
"require": [
192+
"ts-node/register",
193+
"tsconfig-paths/register"
194+
]
195+
}
196+
}
197+
```
198+
199+
Then you can start using module aliases:
200+
201+
`tsconfig.json`:
202+
```json
203+
{
204+
"baseUrl": ".",
205+
"paths": {
206+
"@helpers/*": ["helpers/*"]
207+
}
208+
}
209+
```
210+
211+
Test:
212+
213+
```ts
214+
import myHelper from '@helpers/myHelper';
215+
216+
// Rest of the file
217+
```
218+
219+
[`@ava/typescript`]: https://github.com/avajs/typescript
220+
[`ts-node`]: https://www.npmjs.com/package/ts-node

eslint-plugin-helper.js

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
'use strict';
2-
const babelManager = require('./lib/babel-manager');
32
const normalizeExtensions = require('./lib/extensions');
43
const {classify, hasExtension, isHelperish, matches, normalizeFileForMatching, normalizeGlobs, normalizePatterns} = require('./lib/globs');
54
const loadConfig = require('./lib/load-config');
5+
const providerManager = require('./lib/provider-manager');
66

77
const configCache = new Map();
88
const helperCache = new Map();
@@ -14,22 +14,33 @@ function load(projectDir, overrides) {
1414
}
1515

1616
let conf;
17-
let babelProvider;
17+
let providers;
1818
if (configCache.has(projectDir)) {
19-
({conf, babelProvider} = configCache.get(projectDir));
19+
({conf, providers} = configCache.get(projectDir));
2020
} else {
2121
conf = loadConfig({resolveFrom: projectDir});
2222

23+
providers = [];
2324
if (Reflect.has(conf, 'babel')) {
24-
babelProvider = babelManager({projectDir}).main({config: conf.babel});
25+
providers.push({
26+
type: 'babel',
27+
main: providerManager.babel(projectDir).main({config: conf.babel})
28+
});
2529
}
2630

27-
configCache.set(projectDir, {conf, babelProvider});
31+
if (Reflect.has(conf, 'typescript')) {
32+
providers.push({
33+
type: 'typescript',
34+
main: providerManager.typescript(projectDir).main({config: conf.typescript})
35+
});
36+
}
37+
38+
configCache.set(projectDir, {conf, providers});
2839
}
2940

3041
const extensions = overrides && overrides.extensions ?
3142
normalizeExtensions(overrides.extensions) :
32-
normalizeExtensions(conf.extensions, babelProvider);
43+
normalizeExtensions(conf.extensions, providers);
3344

3445
let helperPatterns = [];
3546
if (overrides && overrides.helpers !== undefined) {

lib/api.js

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -185,8 +185,11 @@ class Api extends Emittery {
185185
}
186186
});
187187

188-
const {babelProvider} = this.options;
189-
const babelState = babelProvider === undefined ? null : await babelProvider.compile({cacheDir, files: testFiles});
188+
const {providers = []} = this.options;
189+
const providerStates = (await Promise.all(providers.map(async ({type, main}) => {
190+
const state = await main.compile({cacheDir, files: testFiles});
191+
return state === null ? null : {type, state};
192+
}))).filter(state => state !== null);
190193

191194
// Resolve the correct concurrency value.
192195
let concurrency = Math.min(os.cpus().length, isCi ? 2 : Infinity);
@@ -208,7 +211,7 @@ class Api extends Emittery {
208211

209212
const options = {
210213
...apiOptions,
211-
babelState,
214+
providerStates,
212215
recordNewSnapshots: !isCi,
213216
// If we're looking for matches, run every single test process in exclusive-only mode
214217
runOnlyExclusive: apiOptions.match.length > 0 || runtimeOptions.runOnlyExclusive === true

lib/cli.js

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -256,11 +256,11 @@ exports.run = async () => { // eslint-disable-line complexity
256256
const MiniReporter = require('./reporters/mini');
257257
const TapReporter = require('./reporters/tap');
258258
const Watcher = require('./watcher');
259-
const babelManager = require('./babel-manager');
260259
const normalizeExtensions = require('./extensions');
261260
const {normalizeGlobs, normalizePatterns} = require('./globs');
262261
const normalizeNodeArguments = require('./node-arguments');
263262
const validateEnvironmentVariables = require('./environment-variables');
263+
const providerManager = require('./provider-manager');
264264

265265
let pkg;
266266
try {
@@ -279,10 +279,24 @@ exports.run = async () => { // eslint-disable-line complexity
279279
js: defaultModuleType
280280
};
281281

282-
let babelProvider;
282+
const providers = [];
283283
if (Reflect.has(conf, 'babel')) {
284284
try {
285-
babelProvider = babelManager({projectDir}).main({config: conf.babel});
285+
providers.push({
286+
type: 'babel',
287+
main: providerManager.babel(projectDir).main({config: conf.babel})
288+
});
289+
} catch (error) {
290+
exit(error.message);
291+
}
292+
}
293+
294+
if (Reflect.has(conf, 'typescript')) {
295+
try {
296+
providers.push({
297+
type: 'typescript',
298+
main: providerManager.typescript(projectDir).main({config: conf.typescript})
299+
});
286300
} catch (error) {
287301
exit(error.message);
288302
}
@@ -297,7 +311,7 @@ exports.run = async () => { // eslint-disable-line complexity
297311

298312
let extensions;
299313
try {
300-
extensions = normalizeExtensions(conf.extensions, babelProvider);
314+
extensions = normalizeExtensions(conf.extensions, providers);
301315
} catch (error) {
302316
exit(error.message);
303317
}
@@ -328,22 +342,22 @@ exports.run = async () => { // eslint-disable-line complexity
328342
const filter = normalizePatterns(input.map(fileOrPattern => path.relative(projectDir, path.resolve(process.cwd(), fileOrPattern))));
329343

330344
const api = new Api({
331-
babelProvider,
332345
cacheEnabled: combined.cache !== false,
333346
chalkOptions,
334347
concurrency: combined.concurrency || 0,
335348
debug,
349+
environmentVariables,
336350
experiments,
337351
extensions,
338352
failFast: combined.failFast,
339353
failWithoutAssertions: combined.failWithoutAssertions !== false,
340354
globs,
341-
moduleTypes,
342-
environmentVariables,
343355
match,
356+
moduleTypes,
344357
nodeArguments,
345358
parallelRuns,
346359
projectDir,
360+
providers,
347361
ranFromCli: true,
348362
require: arrify(combined.require),
349363
serial: combined.serial,

lib/extensions.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
module.exports = (configuredExtensions, babelProvider) => {
1+
module.exports = (configuredExtensions, providers = []) => {
22
// Combine all extensions possible for testing. Remove duplicate extensions.
33
const duplicates = new Set();
44
const seen = new Set();
@@ -16,15 +16,15 @@ module.exports = (configuredExtensions, babelProvider) => {
1616
combine(configuredExtensions);
1717
}
1818

19-
if (babelProvider !== undefined) {
20-
combine(babelProvider.extensions);
19+
for (const {main} of providers) {
20+
combine(main.extensions);
2121
}
2222

2323
if (duplicates.size > 0) {
2424
throw new Error(`Unexpected duplicate extensions in options: '${[...duplicates].join('\', \'')}'.`);
2525
}
2626

27-
// Unless the default was used by `babelProvider`, as long as the extensions aren't explicitly set, set the default.
27+
// Unless the default was used by providers, as long as the extensions aren't explicitly set, set the default.
2828
if (configuredExtensions === undefined) {
2929
if (!seen.has('cjs')) {
3030
seen.add('cjs');

lib/babel-manager.js renamed to lib/provider-manager.js

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
const pkg = require('../package.json');
22
const globs = require('./globs');
33

4-
module.exports = ({projectDir}) => {
4+
function load(providerModule, projectDir) {
55
const ava = {version: pkg.version};
6-
const makeProvider = require('@ava/babel');
6+
const makeProvider = require(providerModule);
77

88
let fatal;
99
const provider = makeProvider({
1010
negotiateProtocol(identifiers, {version}) {
1111
if (!identifiers.includes('ava-3')) {
12-
fatal = new Error(`This version of AVA (${ava.version}) is not compatible with@ava/babel@${version}`);
12+
fatal = new Error(`This version of AVA (${ava.version}) is not compatible with ${providerModule}@${version}`);
1313
return null;
1414
}
1515

@@ -30,4 +30,7 @@ module.exports = ({projectDir}) => {
3030
}
3131

3232
return provider;
33-
};
33+
}
34+
35+
exports.babel = projectDir => load('@ava/babel', projectDir);
36+
exports.typescript = projectDir => load('@ava/typescript', projectDir);

0 commit comments

Comments
 (0)