UNPKG

ts-snippet

Version:

A TypeScript snippet testing library for any test framework

88 lines (87 loc) 3.22 kB
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()); }