ts-snippet
Version:
A TypeScript snippet testing library for any test framework
88 lines (87 loc) • 3.22 kB
JavaScript
import * as tsutils from "tsutils";
import { Compiler } from "./compiler";
import { Expect } from "./expect";
export class Snippet {
constructor(_files, _compiler) {
this._files = _files;
this._compiler = _compiler;
this.assertFail = (message) => {
throw new Error(message);
};
this.assertPass = () => { };
this._program = _compiler.compile(_files);
}
expect(fileName) {
return new Expect(this.fail.bind(this, fileName), this.infer.bind(this, fileName), this.succeed.bind(this, fileName));
}
fail(fileName, expectedMessage) {
const diagnostics = this._getDiagnostics(fileName);
const messages = diagnostics.map(this._compiler.formatDiagnostic);
const matched = messages.some((message) => expectedMessage ? expectedMessage.test(message) : true);
if (!matched) {
const receivedMessages = new Set(messages);
this.assertFail(expectedMessage && receivedMessages.size > 0
? `Expected an error matching:
${expectedMessage}
but received:
${[...receivedMessages].join("\n")}`
: "Expected an error");
}
else {
this.assertPass();
}
}
infer(fileName, variableName, expectedType) {
this.succeed(fileName);
const sourceFile = this._program.getSourceFile(fileName);
const variables = getVariables(this._program, sourceFile);
const actualType = variables[variableName];
if (!actualType) {
this.assertFail(`Variable '${variableName}' not found`);
}
else if (!areEquivalentTypeStrings(expectedType, actualType)) {
this.assertFail(`Expected '${variableName}: ${actualType}' to be '${expectedType}'`);
}
else {
this.assertPass();
}
}
succeed(fileName) {
const diagnostics = this._getDiagnostics(fileName);
if (diagnostics.length) {
const [diagnostic] = diagnostics;
this.assertFail(this._compiler.formatDiagnostic(diagnostic));
}
else {
this.assertPass();
}
}
_getDiagnostics(fileName) {
return this._program
.getSemanticDiagnostics()
.concat(this._compiler.getDiagnostics(fileName));
}
}
export function areEquivalentTypeStrings(a, b) {
const spaces = /\s/g;
return a.replace(spaces, "") === b.replace(spaces, "");
}
export function getVariables(program, sourceFile) {
const typeChecker = program.getTypeChecker();
const variables = {};
const visitNode = (node) => {
if (tsutils.isVariableStatement(node)) {
tsutils.forEachDeclaredVariable(node.declarationList, (node) => {
variables[node.name.getText()] = typeChecker.typeToString(typeChecker.getTypeAtLocation(node));
});
}
else {
node.forEachChild(visitNode);
}
};
sourceFile.forEachChild(visitNode);
return variables;
}
export function snippet(files, compiler) {
return new Snippet(files, compiler || new Compiler());
}