Skip to content

Commit

Permalink
Use ava's diff comparison view to report assertion mismatches during …
Browse files Browse the repository at this point in the history
…tests (#114)
  • Loading branch information
BendingBender committed Jun 1, 2021
1 parent f1a2057 commit 7fbb7a0
Show file tree
Hide file tree
Showing 4 changed files with 108 additions and 46 deletions.
5 changes: 3 additions & 2 deletions source/lib/rules/files-property.ts
Expand Up @@ -24,11 +24,12 @@ export default (context: Context): Diagnostic[] => {
return [];
}

const content = fs.readFileSync(path.join(context.cwd, 'package.json'), 'utf8');
const packageJsonFullPath = path.join(context.cwd, 'package.json');
const content = fs.readFileSync(packageJsonFullPath, 'utf8');

return [
{
fileName: 'package.json',
fileName: packageJsonFullPath,
message: `TypeScript type definition \`${normalizedTypingsFile}\` is not part of the \`files\` list.`,
severity: 'error',
...getJSONPropertyPosition(content, 'files')
Expand Down
5 changes: 3 additions & 2 deletions source/lib/rules/types-property.ts
Expand Up @@ -13,11 +13,12 @@ export default (context: Context): Diagnostic[] => {
const {pkg} = context;

if (!pkg.types && pkg.typings) {
const content = fs.readFileSync(path.join(context.cwd, 'package.json'), 'utf8');
const packageJsonFullPath = path.join(context.cwd, 'package.json');
const content = fs.readFileSync(packageJsonFullPath, 'utf8');

return [
{
fileName: 'package.json',
fileName: packageJsonFullPath,
message: 'Use property `types` instead of `typings`.',
severity: 'error',
...getJSONPropertyPosition(content, 'typings')
Expand Down
80 changes: 63 additions & 17 deletions source/test/fixtures/utils.ts
@@ -1,7 +1,21 @@
import * as path from 'path';
import {ExecutionContext} from 'ava';
import {Diagnostic} from '../../lib/interfaces';

type Expectation = [number, number, 'error' | 'warning', string, (string | RegExp)?];
type Expectation = [
line: number,
column: number,
severity: 'error' | 'warning',
message: string,
];

type ExpectationWithFilename = [
line: number,
column: number,
severity: 'error' | 'warning',
message: string,
fileName: string,
];

/**
* Verify a list of diagnostics.
Expand All @@ -11,20 +25,52 @@ type Expectation = [number, number, 'error' | 'warning', string, (string | RegEx
* @param expectations - Expected diagnostics.
*/
export const verify = (t: ExecutionContext, diagnostics: Diagnostic[], expectations: Expectation[]) => {
t.deepEqual(diagnostics.length, expectations.length, 'Received different count of diagnostics than expected!');

for (const [index, diagnostic] of diagnostics.entries()) {
t.is(diagnostic.line, expectations[index][0], `"line" for diagnostic ${index} doesn't match!`);
t.is(diagnostic.column, expectations[index][1], `"column" for diagnostic ${index} doesn't match!`);
t.is(diagnostic.severity, expectations[index][2], `"severity" for diagnostic ${index} doesn't match!`);
t.is(diagnostic.message, expectations[index][3], `"message" for diagnostic ${index} doesn't match!`);

const filename = expectations[index][4];

if (typeof filename === 'string') {
t.is(diagnostic.fileName, filename, `"fileName" for diagnostic ${index} doesn't match!`);
} else if (typeof filename === 'object') {
t.regex(diagnostic.fileName, filename, `"fileName" for diagnostic ${index} doesn't match!`);
}
}
const diagnosticObjs = diagnostics.map(({line, column, severity, message}) => ({
line,
column,
severity,
message,
}));

const expectationObjs = expectations.map(([line, column, severity, message]) => ({
line,
column,
severity,
message,
}));

t.deepEqual(diagnosticObjs, expectationObjs, 'Received diagnostics that are different from expectations!');
};

/**
* Verify a list of diagnostics including file paths.
*
* @param t - The AVA execution context.
* @param cwd - The working directory as passed to `tsd`.
* @param diagnostics - List of diagnostics to verify.
* @param expectations - Expected diagnostics.
*/
export const verifyWithFileName = (
t: ExecutionContext,
cwd: string,
diagnostics: Diagnostic[],
expectations: ExpectationWithFilename[]
) => {
const diagnosticObjs = diagnostics.map(({line, column, severity, message, fileName}) => ({
line,
column,
severity,
message,
fileName: path.relative(cwd, fileName),
}));

const expectationObjs = expectations.map(([line, column, severity, message, fileName]) => ({
line,
column,
severity,
message,
fileName,
}));

t.deepEqual(diagnosticObjs, expectationObjs, 'Received diagnostics that are different from expectations!');
};
64 changes: 39 additions & 25 deletions source/test/test.ts
@@ -1,6 +1,6 @@
import * as path from 'path';
import test from 'ava';
import {verify} from './fixtures/utils';
import {verify, verifyWithFileName} from './fixtures/utils';
import tsd from '..';
import {Diagnostic} from '../lib/interfaces';

Expand All @@ -21,18 +21,20 @@ test('return diagnostics', async t => {
});

test('return diagnostics from imported files as well', async t => {
const diagnostics = await tsd({cwd: path.join(__dirname, 'fixtures/failure-nested')});
const cwd = path.join(__dirname, 'fixtures/failure-nested');
const diagnostics = await tsd({cwd});

verify(t, diagnostics, [
[5, 19, 'error', 'Argument of type \'number\' is not assignable to parameter of type \'string\'.', /child.test-d.ts$/],
[6, 19, 'error', 'Argument of type \'number\' is not assignable to parameter of type \'string\'.', /index.test-d.ts$/]
verifyWithFileName(t, cwd, diagnostics, [
[5, 19, 'error', 'Argument of type \'number\' is not assignable to parameter of type \'string\'.', 'child.test-d.ts'],
[6, 19, 'error', 'Argument of type \'number\' is not assignable to parameter of type \'string\'.', 'index.test-d.ts'],
]);
});

test('fail if typings file is not part of `files` list', async t => {
const diagnostics = await tsd({cwd: path.join(__dirname, 'fixtures/no-files')});
const cwd = path.join(__dirname, 'fixtures/no-files');
const diagnostics = await tsd({cwd});

verify(t, diagnostics, [
verifyWithFileName(t, cwd, diagnostics, [
[3, 1, 'error', 'TypeScript type definition `index.d.ts` is not part of the `files` list.', 'package.json'],
]);
});
Expand All @@ -50,39 +52,51 @@ test('allow specifying glob patterns containing typings file in `files` list', a
});

test('fail if `typings` property is used instead of `types`', async t => {
const diagnostics = await tsd({cwd: path.join(__dirname, 'fixtures/types-property/typings')});
const cwd = path.join(__dirname, 'fixtures/types-property/typings');
const diagnostics = await tsd({cwd});

verify(t, diagnostics, [
verifyWithFileName(t, cwd, diagnostics, [
[3, 1, 'error', 'Use property `types` instead of `typings`.', 'package.json'],
]);
});

test('fail if tests don\'t pass in strict mode', async t => {
const diagnostics = await tsd({
cwd: path.join(__dirname, 'fixtures/failure-strict-null-checks')
});

verify(t, diagnostics, [
[4, 19, 'error', 'Argument of type \'number | null\' is not assignable to parameter of type \'number\'.\n Type \'null\' is not assignable to type \'number\'.', /failure-strict-null-checks\/index.test-d.ts$/],
const cwd = path.join(__dirname, 'fixtures/failure-strict-null-checks');
const diagnostics = await tsd({cwd});

verifyWithFileName(t, cwd, diagnostics, [
[
4,
19,
'error',
'Argument of type \'number | null\' is not assignable to parameter of type \'number\'.\n Type \'null\' is not assignable to type \'number\'.',
'index.test-d.ts',
],
]);
});

test('overridden config defaults to `strict` if `strict` is not explicitly overridden', async t => {
const diagnostics = await tsd({
cwd: path.join(__dirname, 'fixtures/strict-null-checks-as-default-config-value')
});

verify(t, diagnostics, [
[4, 19, 'error', 'Argument of type \'number | null\' is not assignable to parameter of type \'number\'.\n Type \'null\' is not assignable to type \'number\'.', /strict-null-checks-as-default-config-value\/index.test-d.ts$/],
const cwd = path.join(__dirname, 'fixtures/strict-null-checks-as-default-config-value');
const diagnostics = await tsd({cwd});

verifyWithFileName(t, cwd, diagnostics, [
[
4,
19,
'error',
'Argument of type \'number | null\' is not assignable to parameter of type \'number\'.\n Type \'null\' is not assignable to type \'number\'.',
'index.test-d.ts',
],
]);
});

test('fail if types are used from a lib that was not explicitly specified', async t => {
const diagnostics = await tsd({cwd: path.join(__dirname, 'fixtures/lib-config/failure-missing-lib')});
const cwd = path.join(__dirname, 'fixtures/lib-config/failure-missing-lib');
const diagnostics = await tsd({cwd});

verify(t, diagnostics, [
[1, 22, 'error', 'Cannot find name \'Window\'.', /failure-missing-lib\/index.d.ts$/],
[4, 11, 'error', 'Cannot find name \'Window\'.', /failure-missing-lib\/index.test-d.ts$/]
verifyWithFileName(t, cwd, diagnostics, [
[1, 22, 'error', 'Cannot find name \'Window\'.', 'index.d.ts'],
[4, 11, 'error', 'Cannot find name \'Window\'.', 'index.test-d.ts'],
]);
});

Expand Down

0 comments on commit 7fbb7a0

Please sign in to comment.