Skip to content

Commit

Permalink
Add *.cts versions of type tests
Browse files Browse the repository at this point in the history
  • Loading branch information
novemberborn committed Jun 5, 2022
1 parent 4514fdd commit 70b5cf4
Show file tree
Hide file tree
Showing 11 changed files with 406 additions and 0 deletions.
40 changes: 40 additions & 0 deletions test-d/assertions-as-type-guards.cts
@@ -0,0 +1,40 @@
import test from 'ava';
import {expectType} from 'tsd';

type Expected = {foo: 'bar'};
const expected: Expected = {foo: 'bar'};

test('deepEqual', t => {
const actual: unknown = {};
if (t.deepEqual(actual, expected)) {
expectType<Expected>(actual);
}
});

test('like', t => {
const actual: unknown = {};
if (t.like(actual, expected)) {
expectType<Expected>(actual);
}
});

test('is', t => {
const actual: unknown = 2;
if (t.is(actual, 3 as const)) {
expectType<3>(actual);
}
});

test('false', t => {
const actual: unknown = true;
if (t.false(actual)) {
expectType<false>(actual);
}
});

test('true', t => {
const actual: unknown = false;
if (t.true(actual)) {
expectType<true>(actual);
}
});
33 changes: 33 additions & 0 deletions test-d/context.cts
@@ -0,0 +1,33 @@
/* eslint-disable @typescript-eslint/no-empty-function */
import anyTest, {ExecutionContext, TestFn} from 'ava';
import {expectError, expectType} from 'tsd';

interface Context {
foo: string;
}

const test = anyTest as TestFn<Context>;

const macro = test.macro((t, _expected: number) => {
expectType<string>(t.context.foo);
});

test.beforeEach(t => {
expectType<Context>(t.context);
});

// @ts-expect-error TS2769
expectError(test('foo is bar', macro, 'bar')); // eslint-disable-line @typescript-eslint/no-confusing-void-expression

anyTest('default context is unknown', t => {
expectType<unknown>(t.context);
});

// See https://github.com/avajs/ava/issues/2253
interface Covariant extends Context {
bar: number;
}

const test2 = anyTest as TestFn<Covariant>;
const hook = (_t: ExecutionContext<Context>) => {};
test2.beforeEach(hook);
29 changes: 29 additions & 0 deletions test-d/deep-equal.cts
@@ -0,0 +1,29 @@
import test from 'ava';
import {expectType} from 'tsd';

test('actual extends expected', t => {
type Expected = {foo: [1, 2, 3]};
const expected: Expected = {foo: [1, 2, 3]};
const actual = {foo: [1, 2, 3]};
if (t.deepEqual(actual, expected)) {
expectType<Expected>(actual);
}
});

test('expected extends actual', t => {
type Expected = {foo: Array<number | string>};
type Actual = {foo: number[]};
const expected: Expected = {foo: [1, 2, 3]};
const actual: Actual = {foo: [1, 2, 3]};
if (t.deepEqual(actual, expected)) {
expectType<Actual>(expected);
}
});

test('neither extends the each other', t => {
type Expected = {readonly foo: readonly [1, 2, 3]};
type Actual = {foo: number[]};
const expected: Expected = {foo: [1, 2, 3]};
const actual: Actual = {foo: [1, 2, 3]};
t.deepEqual(actual, expected);
});
20 changes: 20 additions & 0 deletions test-d/implementation-result.cts
@@ -0,0 +1,20 @@
/* eslint-disable @typescript-eslint/no-empty-function */
import test from 'ava';

test.after('return anything else', _t => ({
foo: 'bar',
subscribe() {},
then() {}, // eslint-disable-line unicorn/no-thenable
}));

test('return a promise-like', _t => ({
then(resolve) { // eslint-disable-line unicorn/no-thenable
resolve?.(); // eslint-disable-line @typescript-eslint/no-floating-promises
},
}));

test('return a subscribable', _t => ({
subscribe({complete}) {
complete();
},
}));
25 changes: 25 additions & 0 deletions test-d/like.cts
@@ -0,0 +1,25 @@
import test from 'ava';

test('like', t => {
t.like({
map: new Map([['foo', 'bar']]),
nested: {
baz: 'thud',
qux: 'quux',
},
}, {
map: new Map([['foo', 'bar']]),
nested: {
baz: 'thud',
},
});

type Foo = {
foo?: 'foo';
bar?: 'bar';
};

const foo: Foo = {bar: 'bar'};
const {foo: _, ...expected} = foo;
t.like(expected, {bar: 'bar'});
});
126 changes: 126 additions & 0 deletions test-d/macros.cts
@@ -0,0 +1,126 @@
/* eslint-disable no-lone-blocks */
import test, {ExecutionContext} from 'ava';
import {expectType} from 'tsd';

// Typed arguments through generics.
{
const hasLength = test.macro<[string, number]>((t, input, expected) => {
expectType<string>(input);
expectType<number>(expected);
// @ts-expect-error TS2345
t.is(input, expected);
});

test('bar has length 3', hasLength, 'bar', 3);
}

{
const hasLength = test.macro<[string, number]>({
exec(t, input, expected) {
expectType<string>(input);
expectType<number>(expected);
// @ts-expect-error TS2345
t.is(input, expected);
},
title(_providedTitle, input, expected) {
expectType<string>(input);
expectType<number>(expected);
return 'title';
},
});

test('bar has length 3', hasLength, 'bar', 3);
}

// Typed arguments in execution function.
{
const hasLength = test.macro((t, input: string, expected: number) => {
// @ts-expect-error TS2345
t.is(input, expected);
});

test('bar has length 3', hasLength, 'bar', 3);
}

{
const hasLength = test.macro({
exec(t, input: string, expected: number) {
// @ts-expect-error TS2345
t.is(input, expected);
},
title(_providedTitle, input, expected) {
expectType<string>(input);
expectType<number>(expected);
return 'title';
},
});

test('bar has length 3', hasLength, 'bar', 3);
}

// Untyped arguments
{
const hasLength = test.macro((t, input, expected) => {
expectType<unknown>(input);
expectType<unknown>(expected);
t.is(input, expected);
});

test('bar has length 3', hasLength, 'bar', 3);
}

// Usable without title, even if the macro lacks a title function.
{
const hasLength = test.macro<[string, number]>((t, input, expected) => {
// @ts-expect-error TS2345
t.is(input, expected);
});

test(hasLength, 'bar', 3);
}

// No arguments
{
const pass = test.macro<[]>({ // eslint-disable-line @typescript-eslint/ban-types
exec(_t, ...args) {
expectType<[]>(args); // eslint-disable-line @typescript-eslint/ban-types
},
title(providedTitle, ...args) {
expectType<string | undefined>(providedTitle);
expectType<[]>(args); // eslint-disable-line @typescript-eslint/ban-types
return '';
},
});

test(pass);
}

// Without test.macro()
{
const hasLength = (t: ExecutionContext, input: string, expected: number) => {
t.is(input.length, expected);
};

test('bar has length 3', hasLength, 'bar', 3);
}

// Inline function with explicit argument types.
test('has length 3', (t: ExecutionContext, input: string, expected: number) => {
// @ts-expect-error TS2345
t.is(input, expected);
}, 'bar', 3);

// Completely inferred arguments for inline functions.
test('has length 3', (t, input, expected) => {
expectType<string>(input);
expectType<number>(expected);
// @ts-expect-error TS2345
t.is(input, expected);
}, 'foo', 3);

test.skip('skip', (t, input, expected) => {
expectType<string>(input);
expectType<number>(expected);
// @ts-expect-error TS2345
t.is(input, expected);
}, 'foo', 3);
18 changes: 18 additions & 0 deletions test-d/plugin.cts
@@ -0,0 +1,18 @@
import plugin from 'ava/plugin';
import {expectType} from 'tsd';

expectType<plugin.SharedWorker.Plugin.Protocol>(plugin.registerSharedWorker({filename: '', supportedProtocols: ['ava-4']}));

const factory: plugin.SharedWorker.Factory = ({negotiateProtocol}) => {
const protocol = negotiateProtocol(['ava-4']);
expectType<plugin.SharedWorker.Protocol>(protocol);

(async () => {
for await (const w of protocol.testWorkers()) {
expectType<() => Promise<void>>(w.teardown(() => {})); // eslint-disable-line @typescript-eslint/no-empty-function
expectType<() => Promise<void>>(w.teardown(async () => {})); // eslint-disable-line @typescript-eslint/no-empty-function
}
})();
};

export default factory;
16 changes: 16 additions & 0 deletions test-d/snapshot.cts
@@ -0,0 +1,16 @@
import test from 'ava';
import {expectError} from 'tsd';

test('snapshot', t => {
t.snapshot({foo: 'bar'});
t.snapshot(null, 'a snapshot with a message');
// @ts-expect-error TS2345
expectError(t.snapshot('hello world', null)); // eslint-disable-line @typescript-eslint/no-confusing-void-expression
});

test('snapshot.skip', t => {
t.snapshot.skip({foo: 'bar'});
t.snapshot.skip(null, 'a snapshot with a message');
// @ts-expect-error TS2345
expectError(t.snapshot.skip('hello world', null)); // eslint-disable-line @typescript-eslint/no-confusing-void-expression
});
6 changes: 6 additions & 0 deletions test-d/teardown.cts
@@ -0,0 +1,6 @@
import test from 'ava';

test('test', t => {
t.teardown(() => {}); // eslint-disable-line @typescript-eslint/no-empty-function
t.teardown(async () => {}); // eslint-disable-line @typescript-eslint/no-empty-function
});
26 changes: 26 additions & 0 deletions test-d/throws.cts
@@ -0,0 +1,26 @@
/* eslint-disable @typescript-eslint/no-empty-function */
import test from 'ava';
import {expectType} from 'tsd';

class CustomError extends Error {
foo: string;

constructor() {
super();
this.foo = 'foo';
}
}

test('throws', t => {
expectType<Error | undefined>(t.throws(() => {}));
const error2: CustomError | undefined = t.throws(() => {});
expectType<CustomError | undefined>(error2);
expectType<CustomError | undefined>(t.throws<CustomError>(() => {}));
});

test('throwsAsync', async t => {
expectType<Error | undefined>(await t.throwsAsync(async () => {}));
expectType<CustomError | undefined>(await t.throwsAsync<CustomError>(async () => {}));
expectType<Error | undefined>(await t.throwsAsync(Promise.reject()));
expectType<CustomError | undefined>(await t.throwsAsync<CustomError>(Promise.reject()));
});

0 comments on commit 70b5cf4

Please sign in to comment.