Skip to content

Commit

Permalink
init
Browse files Browse the repository at this point in the history
  • Loading branch information
szmarczak committed Apr 9, 2021
1 parent 96b4ae4 commit 32584f3
Show file tree
Hide file tree
Showing 14 changed files with 166 additions and 83 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/.nyc_output
/coverage
/node_modules
/test/fixtures/compiled
10 changes: 6 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

Adds rudimentary [TypeScript](https://www.typescriptlang.org/) support to [AVA](https://avajs.dev).

This is designed to work for projects that precompile their TypeScript code, including tests. It allows AVA to load the resulting JavaScript, while configuring AVA to use the TypeScript paths.
This is designed to work for projects that precompile TypeScript. It allows AVA to load the compiled JavaScript, while configuring AVA to treat the TypeScript files as test files.

In other words, say you have a test file at `src/test.ts`. You've configured TypeScript to output to `build/`. Using `@ava/typescript` you can run the `build/test.js` file using `npx ava src/test.ts`. AVA won't pick up any of the JavaScript files present in the `build/` directory, unless they have a TypeScript counterpart in `src/`.
In other words, say you have a test file at `src/test.ts`. You've configured TypeScript to output to `build/`. Using `@ava/typescript` you can run the test using `npx ava src/test.ts`.

## Enabling TypeScript support

Expand All @@ -24,13 +24,15 @@ Then, enable TypeScript support either in `package.json` or `ava.config.*`:
"typescript": {
"rewritePaths": {
"src/": "build/"
}
},
"compile": true
}
}
}
```

Both keys and values of the `rewritePaths` object must end with a `/`. Paths are relative to your project directory.
Both keys and values of the `rewritePaths` object must end with a `/`. Paths are relative to your project directory.\
You can enable compilation via the `compile` property. It is recommended to set it to `false` when running `ts` in a watcher mode.

Output files are expected to have the `.js` extension.

Expand Down
21 changes: 18 additions & 3 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
const path = require('path');

const escapeStringRegexp = require('escape-string-regexp');
const execa = require('execa');

const pkg = require('./package.json');

Expand All @@ -26,6 +27,14 @@ function isValidRewritePaths(rewritePaths) {
});
}

function isValidCompile(compile) {
return typeof compile === 'boolean';
}

async function compileTypeScript(projectDir) {
return execa('tsc', ['--incremental'], {preferLocal: true, cwd: projectDir});
}

module.exports = ({negotiateProtocol}) => {
const protocol = negotiateProtocol(['ava-3.2', 'ava-3'], {version: pkg.version});
if (protocol === null) {
Expand All @@ -37,10 +46,11 @@ module.exports = ({negotiateProtocol}) => {
let valid = false;
if (isPlainObject(config)) {
const keys = Object.keys(config);
if (keys.every(key => key === 'extensions' || key === 'rewritePaths')) {
if (keys.every(key => key === 'extensions' || key === 'rewritePaths' || key === 'compile')) {
valid =
(config.extensions === undefined || isValidExtensions(config.extensions)) &&
isValidRewritePaths(config.rewritePaths);
isValidRewritePaths(config.rewritePaths) &&
isValidCompile(config.compile);
}
}

Expand All @@ -50,7 +60,8 @@ module.exports = ({negotiateProtocol}) => {

const {
extensions = ['ts'],
rewritePaths: relativeRewritePaths
rewritePaths: relativeRewritePaths,
compile
} = config;

const rewritePaths = Object.entries(relativeRewritePaths).map(([from, to]) => [
Expand All @@ -61,6 +72,10 @@ module.exports = ({negotiateProtocol}) => {

return {
async compile() {
if (compile) {
await compileTypeScript(protocol.projectDir);
}

return {
extensions: extensions.slice(),
rewritePaths: rewritePaths.slice()
Expand Down
7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,11 @@
"escape-string-regexp": "^2.0.0"
},
"devDependencies": {
"ava": "^3.0.0",
"ava": "^3.15.0",
"del": "^6.0.0",
"execa": "^4.0.0",
"nyc": "^15.0.0",
"xo": "^0.25.3"
"nyc": "^15.1.0",
"xo": "^0.37.1"
},
"nyc": {
"reporter": [
Expand Down
21 changes: 21 additions & 0 deletions test/_with-provider.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
const path = require('path');
const pkg = require('../package.json');
const makeProvider = require('..');

const withProvider = (t, run, identifier = 'ava-3.2') => run(t, makeProvider({
negotiateProtocol(identifiers, {version}) {
t.true(identifiers.includes(identifier));
t.is(version, pkg.version);
return {
ava: {version: '3.2.0'},
identifier,
normalizeGlobPatterns: patterns => patterns,
async findFiles({patterns}) {
return patterns.map(file => path.join(__dirname, file));
},
projectDir: __dirname
};
}
}));

module.exports = withProvider;
51 changes: 51 additions & 0 deletions test/compilation.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
const path = require('path');
const test = require('ava');
const del = require('del');
const execa = require('execa');
const withProvider = require('./_with-provider');

test.before('deleting compiled files', async t => {
t.log(await del('test/fixtures/compiled'));
});

const compile = async provider => {
return {
state: await provider.main({
config: {
rewritePaths: {
'typescript/': 'fixtures/',
'compiled/': 'fixtures/compiled/'
},
compile: true
}
}).compile()
};
};

test('worker(): load rewritten paths files', withProvider, async (t, provider) => {
const {state} = await compile(provider);
const {stdout, stderr} = await execa.node(
path.join(__dirname, 'fixtures/install-and-load'),
['ava-3', JSON.stringify(state), path.join(__dirname, 'typescript', 'file.ts')],
{cwd: path.join(__dirname, 'fixtures')}
);
if (stderr.length > 0) {
t.log(stderr);
}

t.snapshot(stdout);
});

test('worker(): runs compiled files', withProvider, async (t, provider) => {
const {state} = await compile(provider);
const {stdout, stderr} = await execa.node(
path.join(__dirname, 'fixtures/install-and-load'),
['ava-3', JSON.stringify(state), path.join(__dirname, 'compiled', 'typescript.ts')],
{cwd: path.join(__dirname, 'fixtures')}
);
if (stderr.length > 0) {
t.log(stderr);
}

t.snapshot(stdout);
});
1 change: 1 addition & 0 deletions test/fixtures/typescript.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
console.log('logged in typescript.ts');
25 changes: 4 additions & 21 deletions test/protocol-ava-3.2.js
Original file line number Diff line number Diff line change
@@ -1,41 +1,24 @@
const path = require('path');
const test = require('ava');
const pkg = require('../package.json');
const makeProvider = require('..');

const withProvider = (t, run) => run(t, makeProvider({
negotiateProtocol(identifiers, {version}) {
t.true(identifiers.includes('ava-3.2'));
t.is(version, pkg.version);
return {
ava: {version: '3.2.0'},
identifier: 'ava-3.2',
normalizeGlobPatterns: patterns => patterns,
async findFiles({patterns}) {
return patterns.map(file => path.join(__dirname, file));
},
projectDir: __dirname
};
}
}));
const withProvider = require('./_with-provider');

test('negotiates ava-3.2 protocol', withProvider, t => t.plan(2));

test('main() ignoreChange()', withProvider, (t, provider) => {
const main = provider.main({config: {rewritePaths: {'src/': 'build/'}}});
const main = provider.main({config: {rewritePaths: {'src/': 'build/'}, compile: false}});
t.true(main.ignoreChange(path.join(__dirname, 'src/foo.ts')));
t.false(main.ignoreChange(path.join(__dirname, 'build/foo.js')));
});

test('main() resolveTestfile()', withProvider, (t, provider) => {
const main = provider.main({config: {rewritePaths: {'src/': 'build/'}}});
const main = provider.main({config: {rewritePaths: {'src/': 'build/'}, compile: false}});
t.is(main.resolveTestFile(path.join(__dirname, 'src/foo.ts')), path.join(__dirname, 'build/foo.js'));
t.is(main.resolveTestFile(path.join(__dirname, 'build/foo.js')), path.join(__dirname, 'build/foo.js'));
t.is(main.resolveTestFile(path.join(__dirname, 'foo/bar.ts')), path.join(__dirname, 'foo/bar.ts'));
});

test('main() updateGlobs()', withProvider, (t, provider) => {
const main = provider.main({config: {rewritePaths: {'src/': 'build/'}}});
const main = provider.main({config: {rewritePaths: {'src/': 'build/'}, compile: false}});
t.snapshot(main.updateGlobs({
filePatterns: ['src/test.ts'],
ignoredByWatcherPatterns: ['assets/**']
Expand Down
65 changes: 13 additions & 52 deletions test/protocol-ava-3.js
Original file line number Diff line number Diff line change
@@ -1,32 +1,14 @@
const path = require('path');
const test = require('ava');
const execa = require('execa');
const pkg = require('../package.json');
const makeProvider = require('..');

const withProvider = (t, run) => run(t, makeProvider({
negotiateProtocol(identifiers, {version}) {
t.true(identifiers.includes('ava-3'));
t.is(version, pkg.version);
return {
ava: {version: '3.0.0'},
identifier: 'ava-3',
normalizeGlobPatterns: patterns => patterns,
async findFiles({patterns}) {
return patterns.map(file => path.join(__dirname, file));
},
projectDir: __dirname
};
}
}));
const withProvider = require('./_with-provider');

const validateConfig = (t, provider, config) => {
const error = t.throws(() => provider.main({config}));
error.message = error.message.replace(`v${pkg.version}`, 'v${pkg.version}'); // eslint-disable-line no-template-curly-in-string
t.snapshot(error);
};

test('negotiates ava-3 protocol', withProvider, t => t.plan(2));
test('negotiates ava-3 protocol', withProvider, t => t.plan(2), 'ava-3');

test('main() config validation: throw when config is not a plain object', withProvider, (t, provider) => {
validateConfig(t, provider, false);
Expand All @@ -35,7 +17,7 @@ test('main() config validation: throw when config is not a plain object', withPr
validateConfig(t, provider, []);
});

test('main() config validation: throw when config contains keys other than \'extensions\' or \'rewritePaths\'', withProvider, (t, provider) => {
test('main() config validation: throw when config contains keys other than \'extensions\', \'rewritePaths\' or \'compile\'', withProvider, (t, provider) => {
validateConfig(t, provider, {foo: 1});
});

Expand All @@ -55,47 +37,26 @@ test('main() config validation: config may not be an empty object', withProvider
validateConfig(t, provider, {});
});

test('main() config validation: throw when config.compile is not a boolean', withProvider, (t, provider) => {
validateConfig(t, provider, {rewritePaths: {'src/': 'build/'}, compile: 1});
validateConfig(t, provider, {rewritePaths: {'src/': 'build/'}, compile: undefined});
});

test('main() config validation: rewrite paths must end in a /', withProvider, (t, provider) => {
validateConfig(t, provider, {rewritePaths: {src: 'build/'}});
validateConfig(t, provider, {rewritePaths: {'src/': 'build'}});
validateConfig(t, provider, {rewritePaths: {src: 'build/', compile: false}});
validateConfig(t, provider, {rewritePaths: {'src/': 'build', compile: false}});
});

test('main() extensions: defaults to [\'ts\']', withProvider, (t, provider) => {
t.deepEqual(provider.main({config: {rewritePaths: {'src/': 'build/'}}}).extensions, ['ts']);
t.deepEqual(provider.main({config: {rewritePaths: {'src/': 'build/'}, compile: false}}).extensions, ['ts']);
});

test('main() extensions: returns configured extensions', withProvider, (t, provider) => {
const extensions = ['tsx'];
t.deepEqual(provider.main({config: {extensions, rewritePaths: {'src/': 'build/'}}}).extensions, extensions);
t.deepEqual(provider.main({config: {extensions, rewritePaths: {'src/': 'build/'}, compile: false}}).extensions, extensions);
});

test('main() extensions: always returns new arrays', withProvider, (t, provider) => {
const main = provider.main({config: {rewritePaths: {'src/': 'build/'}}});
const main = provider.main({config: {rewritePaths: {'src/': 'build/'}, compile: false}});
t.not(main.extensions, main.extensions);
});

const compile = async provider => {
return {
state: await provider.main({
config: {
rewritePaths: {
'typescript/': 'fixtures/'
}
}
}).compile()
};
};

test('worker(): load rewritten paths files', withProvider, async (t, provider) => {
const {state} = await compile(provider);
const {stdout, stderr} = await execa.node(
path.join(__dirname, 'fixtures/install-and-load'),
['ava-3', JSON.stringify(state), path.join(__dirname, 'typescript', 'file.ts')],
{cwd: path.join(__dirname, 'fixtures')}
);
if (stderr.length > 0) {
t.log(stderr);
}

t.snapshot(stdout);
});
17 changes: 17 additions & 0 deletions test/snapshots/compilation.js.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Snapshot report for `test/compilation.js`

The actual snapshot is saved in `compilation.js.snap`.

Generated by [AVA](https://avajs.dev).

## worker(): load rewritten paths files

> Snapshot 1
'logged in file.js'

## worker(): runs compiled files

> Snapshot 1
'logged in typescript.ts'
Binary file added test/snapshots/compilation.js.snap
Binary file not shown.
22 changes: 22 additions & 0 deletions test/snapshots/protocol-ava-3.js.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,3 +89,25 @@ Generated by [AVA](https://avajs.dev).
> Snapshot 1
'logged in file.js'

## main() config validation: throw when config contains keys other than 'extensions', 'rewritePaths' or 'compile'

> Snapshot 1
Error {
message: 'Unexpected Typescript configuration for AVA. See https://github.com/avajs/typescript/blob/v${pkg.version}/README.md for allowed values.',
}

## main() config validation: throw when config.compile is not a boolean

> Snapshot 1
Error {
message: 'Unexpected Typescript configuration for AVA. See https://github.com/avajs/typescript/blob/v${pkg.version}/README.md for allowed values.',
}

> Snapshot 2
Error {
message: 'Unexpected Typescript configuration for AVA. See https://github.com/avajs/typescript/blob/v${pkg.version}/README.md for allowed values.',
}
Binary file modified test/snapshots/protocol-ava-3.js.snap
Binary file not shown.
8 changes: 8 additions & 0 deletions test/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"compilerOptions": {
"outDir": "fixtures/compiled"
},
"include": [
"fixtures"
]
}

0 comments on commit 32584f3

Please sign in to comment.