Skip to content

Commit

Permalink
Cleanup test macros (#1724)
Browse files Browse the repository at this point in the history
* WIP tweaking repl test macros to be cleaner, follow simpler ava conventions

* test cleanup
  • Loading branch information
cspotcode committed Apr 19, 2022
1 parent 5d325b5 commit 1efabf4
Show file tree
Hide file tree
Showing 15 changed files with 202 additions and 179 deletions.
24 changes: 24 additions & 0 deletions CONTRIBUTING.md
Expand Up @@ -54,6 +54,30 @@ We have a debug configuration for VSCode.
Note that some tests might misbehave in the debugger. REPL tests in particular. I'm not sure why, but I think it is related to how `console` does not write to
stdout when in a debug session.

### Test Context

Ava has the concept of test "context", an object which can store reusable fields common across many tests.

By convention, any functions that setup reusable context start with `ctx`, making them easier to tab-complete and auto-import while writing tests.

See `ctxTsNode` for an example.

Context setup functions are re-executed for each test case. If you don't want this, wrap the context function in lodash `once()`. Each test will still get a unique context object, but the values placed onto each context will be identical.

Every `ctx*` function has a namespace with `Ctx` and `T` types. These make it easier/cleaner to write tests.

### Test Macros

Ava has the concept of test "macros", reusable functions which can declare a type of test many times with different inputs.

Macro functions are created with `test.macro()`.

By convention, if a macro function is meant to be imported and reused in multiple files, its name should start with `macro`.

Macros can also be declared to require a certain "context," thanks to the namespace types described in "Test Context" above.

See examples in `helpers.ts`.

## Documentation

Documentation is written in markdown in `website/docs` and rendered into a website by Docusaurus. The README is also generated from these markdown files.
Expand Down
4 changes: 2 additions & 2 deletions src/test/diagnostics.spec.ts
@@ -1,9 +1,9 @@
import type { TSError } from '..';
import { contextTsNodeUnderTest, ts } from './helpers';
import { ctxTsNode, ts } from './helpers';
import { context, expect } from './testlib';
import * as semver from 'semver';
import { once } from 'lodash';
const test = context(contextTsNodeUnderTest);
const test = context(ctxTsNode);

test.suite('TSError diagnostics', ({ context }) => {
const test = context(
Expand Down
8 changes: 4 additions & 4 deletions src/test/esm-loader.spec.ts
Expand Up @@ -10,7 +10,7 @@ import {
BIN_PATH_JS,
CMD_ESM_LOADER_WITHOUT_PROJECT,
CMD_TS_NODE_WITHOUT_PROJECT_FLAG,
contextTsNodeUnderTest,
ctxTsNode,
delay,
EXPERIMENTAL_MODULES_FLAG,
nodeSupportsEsmHooks,
Expand All @@ -26,7 +26,7 @@ import * as expect from 'expect';
import type { NodeLoaderHooksAPI2 } from '../';
import { pathToFileURL } from 'url';

const test = context(contextTsNodeUnderTest);
const test = context(ctxTsNode);

const exec = createExec({
cwd: TEST_DIR,
Expand Down Expand Up @@ -233,8 +233,8 @@ test.suite('esm', (test) => {
});
});

test.suite('unit test hooks', (_test) => {
const test = _test.context(async (t) => {
test.suite('unit test hooks', ({ context }) => {
const test = context(async (t) => {
const service = t.context.tsNodeUnderTest.create({
cwd: TEST_DIR,
});
Expand Down
7 changes: 6 additions & 1 deletion src/test/helpers.ts
Expand Up @@ -14,6 +14,7 @@ import type * as tsNodeTypes from '../index';
import type _createRequire from 'create-require';
import { has, mapValues, once } from 'lodash';
import semver = require('semver');
import type { ExecutionContext } from './testlib';
const createRequire: typeof _createRequire = require('create-require');
export { tsNodeTypes };

Expand Down Expand Up @@ -79,13 +80,17 @@ export const tsSupportsShowConfig = semver.gte(ts.version, '3.2.0');
export const xfs = new NodeFS(fs);

/** Pass to `test.context()` to get access to the ts-node API under test */
export const contextTsNodeUnderTest = once(async () => {
export const ctxTsNode = once(async () => {
await installTsNode();
const tsNodeUnderTest: typeof tsNodeTypes = testsDirRequire('ts-node');
return {
tsNodeUnderTest,
};
});
export namespace ctxTsNode {
export type Ctx = Awaited<ReturnType<typeof ctxTsNode>>;
export type T = ExecutionContext<Ctx>;
}

//#region install ts-node tarball
const ts_node_install_lock = process.env.ts_node_install_lock as string;
Expand Down
16 changes: 7 additions & 9 deletions src/test/index.spec.ts
@@ -1,4 +1,4 @@
import { _test } from './testlib';
import { context } from './testlib';
import * as expect from 'expect';
import { join, resolve, sep as pathSep } from 'path';
import { tmpdir } from 'os';
Expand All @@ -14,7 +14,6 @@ import {
import { lstatSync, mkdtempSync } from 'fs';
import { npath } from '@yarnpkg/fslib';
import type _createRequire from 'create-require';
import { pathToFileURL } from 'url';
import { createExec } from './exec-helpers';
import {
BIN_CWD_PATH,
Expand All @@ -26,18 +25,17 @@ import {
testsDirRequire,
tsNodeTypes,
xfs,
contextTsNodeUnderTest,
ctxTsNode,
CMD_TS_NODE_WITH_PROJECT_FLAG,
CMD_TS_NODE_WITHOUT_PROJECT_FLAG,
CMD_ESM_LOADER_WITHOUT_PROJECT,
EXPERIMENTAL_MODULES_FLAG,
} from './helpers';

const exec = createExec({
cwd: TEST_DIR,
});

const test = _test.context(contextTsNodeUnderTest);
const test = context(ctxTsNode);

test.suite('ts-node', (test) => {
test('should export the correct version', (t) => {
Expand Down Expand Up @@ -709,8 +707,8 @@ test.suite('ts-node', (test) => {

test.suite(
'should use implicit @tsconfig/bases config when one is not loaded from disk',
(_test) => {
const test = _test.context(async (t) => ({
({ context }) => {
const test = context(async (t) => ({
tempDir: mkdtempSync(join(tmpdir(), 'ts-node-spec')),
}));
if (
Expand Down Expand Up @@ -941,8 +939,8 @@ test.suite('ts-node', (test) => {
});
});

test.suite('create', (_test) => {
const test = _test.context(async (t) => {
test.suite('create', ({ context }) => {
const test = context(async (t) => {
return {
service: t.context.tsNodeUnderTest.create({
compilerOptions: { target: 'es5' },
Expand Down
80 changes: 43 additions & 37 deletions src/test/pluggable-dep-resolution.spec.ts
@@ -1,14 +1,14 @@
import { context } from './testlib';
import {
contextTsNodeUnderTest,
ctxTsNode,
resetNodeEnvironment,
TEST_DIR,
tsSupportsTsconfigInheritanceViaNodePackages,
} from './helpers';
import * as expect from 'expect';
import { resolve } from 'path';

const test = context(contextTsNodeUnderTest);
const test = context(ctxTsNode);

test.suite(
'Pluggable dependency (compiler, transpiler, swc backend) is require()d relative to the tsconfig file that declares it',
Expand All @@ -26,74 +26,80 @@ test.suite(
// ts-node should resolve ts-patch or @swc/core relative to the extended tsconfig
// to ensure we use the known working versions.

const macro = _macro.bind(null, test);
const macro = test.macro((config: string, expected: string) => [
`${config} uses ${expected}`,
async (t) => {
t.teardown(resetNodeEnvironment);

const output = t.context.tsNodeUnderTest
.create({
project: resolve(TEST_DIR, 'pluggable-dep-resolution', config),
})
.compile('', 'index.ts');

macro('tsconfig-custom-compiler.json', 'root custom compiler');
macro('tsconfig-custom-transpiler.json', 'root custom transpiler');
macro('tsconfig-swc-custom-backend.json', 'root custom swc backend');
macro('tsconfig-swc-core.json', 'root @swc/core');
macro('tsconfig-swc-wasm.json', 'root @swc/wasm');
macro('tsconfig-swc.json', 'root @swc/core');
expect(output).toContain(`emit from ${expected}\n`);
},
]);

macro(
test(macro, 'tsconfig-custom-compiler.json', 'root custom compiler');
test(macro, 'tsconfig-custom-transpiler.json', 'root custom transpiler');
test(macro, 'tsconfig-swc-custom-backend.json', 'root custom swc backend');
test(macro, 'tsconfig-swc-core.json', 'root @swc/core');
test(macro, 'tsconfig-swc-wasm.json', 'root @swc/wasm');
test(macro, 'tsconfig-swc.json', 'root @swc/core');

test(
macro,
'node_modules/shared-config/tsconfig-custom-compiler.json',
'shared-config custom compiler'
);
macro(
test(
macro,
'node_modules/shared-config/tsconfig-custom-transpiler.json',
'shared-config custom transpiler'
);
macro(
test(
macro,
'node_modules/shared-config/tsconfig-swc-custom-backend.json',
'shared-config custom swc backend'
);
macro(
test(
macro,
'node_modules/shared-config/tsconfig-swc-core.json',
'shared-config @swc/core'
);
macro(
test(
macro,
'node_modules/shared-config/tsconfig-swc-wasm.json',
'shared-config @swc/wasm'
);
macro(
test(
macro,
'node_modules/shared-config/tsconfig-swc.json',
'shared-config @swc/core'
);

test.suite('"extends"', (test) => {
test.runIf(tsSupportsTsconfigInheritanceViaNodePackages);

const macro = _macro.bind(null, test);

macro(
test(
macro,
'tsconfig-extend-custom-compiler.json',
'shared-config custom compiler'
);
macro(
test(
macro,
'tsconfig-extend-custom-transpiler.json',
'shared-config custom transpiler'
);
macro(
test(
macro,
'tsconfig-extend-swc-custom-backend.json',
'shared-config custom swc backend'
);
macro('tsconfig-extend-swc-core.json', 'shared-config @swc/core');
macro('tsconfig-extend-swc-wasm.json', 'shared-config @swc/wasm');
macro('tsconfig-extend-swc.json', 'shared-config @swc/core');
test(macro, 'tsconfig-extend-swc-core.json', 'shared-config @swc/core');
test(macro, 'tsconfig-extend-swc-wasm.json', 'shared-config @swc/wasm');
test(macro, 'tsconfig-extend-swc.json', 'shared-config @swc/core');
});

function _macro(_test: typeof test, config: string, expected: string) {
_test(`${config} uses ${expected}`, async (t) => {
t.teardown(resetNodeEnvironment);

const output = t.context.tsNodeUnderTest
.create({
project: resolve(TEST_DIR, 'pluggable-dep-resolution', config),
})
.compile('', 'index.ts');

expect(output).toContain(`emit from ${expected}\n`);
});
}
}
);
4 changes: 2 additions & 2 deletions src/test/register.spec.ts
@@ -1,6 +1,6 @@
import { once } from 'lodash';
import {
contextTsNodeUnderTest,
ctxTsNode,
PROJECT_TRANSPILE_ONLY,
resetNodeEnvironment,
TEST_DIR,
Expand All @@ -22,7 +22,7 @@ const createOptions: tsNodeTypes.CreateOptions = {
},
};

const test = context(contextTsNodeUnderTest).context(
const test = context(ctxTsNode).context(
once(async (t) => {
return {
moduleTestPath: resolve(__dirname, '../../tests/module.ts'),
Expand Down
6 changes: 3 additions & 3 deletions src/test/regression.spec.ts
Expand Up @@ -5,12 +5,12 @@ import { join } from 'path';
import { createExec, createExecTester } from './exec-helpers';
import {
CMD_TS_NODE_WITHOUT_PROJECT_FLAG,
contextTsNodeUnderTest,
ctxTsNode,
TEST_DIR,
} from './helpers';
import { test as _test } from './testlib';
import { context } from './testlib';

const test = _test.context(contextTsNodeUnderTest);
const test = context(ctxTsNode);
const exec = createExecTester({
exec: createExec({
cwd: TEST_DIR,
Expand Down

0 comments on commit 1efabf4

Please sign in to comment.