import { expect } from 'chai' ; import * as ts from 'typescript' ; import * as tspoon from '../../src' ; import { ValidatorConfig , VisitorContext , Visitor } from '../../src' ; import { MockModule } from '../../test-kit/mocks/resolution-hosts' ; import { readFileSync } from 'fs' ; function beforeVariable ( varName ) { return { insert ( code ) { return { filter : ( node ) => { if ( node . kind === ts . SyntaxKind . VariableDeclarationList ) { const declList = < ts . VariableDeclarationList > node ; return declList . declarations . some ( ( decl ) => decl . name . getText ( ) === varName ) ; } return false ; } , visit : ( node , context ) => { context . insertLine ( node . pos , code ) ; } } ; } } ; } describe ( 'tspoon.validateAll()' , function ( ) { it ( 'lets valid code pass' , function ( ) { const config = { resolutionHosts : [ new MockModule ( 'index.ts' , 'export const perfectlyValid: number = 666;' ) , new MockModule ( 'index2.ts' , 'export const perfectlyValid2: number = 777;' ) ] } ; expect ( tspoon . validateAll ( [ 'index.ts' , 'index2.ts' ] , config ) ) . to . pass ( ) ; } ) ; it ( 'makes invalid code fail' , function ( ) { const config = { resolutionHosts : [ new MockModule ( 'index.ts' , 'export const perfectlyValid: number = 666;' ) , new MockModule ( 'index2.ts' , `// This comment here
					// is just
					// to add some lines
					export const perfectlyValid: number = 666;
					export const perfectlyInvalid: SomeUndefinedType = 'HAHAHA';
				` ) ] } ; expect ( tspoon . validateAll ( [ 'index.ts' , 'index2.ts' ] , config ) ) . to . fail ( ) . withMessageCount ( 1 ) . withMessage ( /index2.ts -> 5:\d+ Cannot find name 'SomeUndefinedType'./ ) ; } ) ; it ( 'lets invalid code that was fixed by a visitor pass' , function ( ) { const config = { resolutionHosts : [ new MockModule ( 'index.ts' , `
					// This comment here
					// is just
					// to add some lines
					const perfectlyValid: number = 666;
					const perfectlyInvalid: SomeUndefinedType = 'HAHAHA';
				` ) ] , mutators : [ beforeVariable ( 'perfectlyInvalid' ) . insert ( '\ntype SomeUndefinedType = string;' ) ] } ; expect ( tspoon . validateAll ( [ 'index.ts' ] , config ) ) . to . pass ( ) ; } ) ; it ( 'preserves error lines despite the modifications' , function ( ) { const config = { resolutionHosts : [ new MockModule ( 'index.ts' , ` 									// line 1
					// This comment here     									// line 2
					// is just     									            // line 3
					// to add some lines     									// line 4
					const perfectlyValid: number = 666;     					// line 5
					const perfectlyInvalid: SomeUndefinedType = 'HAHAHA';     	// line 6
				` ) ] , mutators : [ beforeVariable ( 'perfectlyInvalid' ) . insert ( '\nconst anotherValidLine: number = 777;' ) ] } ; expect ( tspoon . validateAll ( [ 'index.ts' ] , config ) ) . to . fail ( ) . withMessageCount ( 1 ) . withMessage ( /.* -> 6:\d+ Cannot find name 'SomeUndefinedType'./ ) ; } ) ; it ( 'modifies a dependency of the validated file' , function ( ) { const config = { resolutionHosts : [ new MockModule ( 'index.ts' , `
					import Product from './Product';
					const product: Product = { title: 'Sample' };
				` ) , new MockModule ( 'Product.ts' , `
					interface Product { title: string; }
					const somethingUnrelated: string = 'what?';
				` ) ] , mutators : [ beforeVariable ( 'somethingUnrelated' ) . insert ( 'export default Product;' ) ] } ; expect ( tspoon . validateAll ( [ 'index.ts' ] , config ) ) . to . pass ( ) ; } ) ; it ( 'fails gracefully with syntactically incorrect input' , function ( ) { const config = { resolutionHosts : [ new MockModule ( 'index.ts' , `
					import {Product} from './Product';
					const product: Product = { title: 'Sample'
				` ) , new MockModule ( 'Product.ts' , `
					export class Product { title: string; }
					const somethingUnrelated: string = 'what?';
				` ) ] } ; expect ( tspoon . validateAll ( [ 'index.ts' ] , config ) ) . to . fail ( ) . withMessage ( /index.ts -> \d+:\d+ '}' expected./ ) ; } ) ; it ( 'fails gracefully with syntactically incorrect dependency' , function ( ) { const config = { resolutionHosts : [ new MockModule ( 'index.ts' , `
					import {Product} from './Product';
					const product: Product = { title: 'Sample' }
				` ) , new MockModule ( 'Product.ts' , `
					export class Product { title: string; }
					const somethingUnrelated: string = 'what?
				` ) ] } ; expect ( tspoon . validateAll ( [ 'index.ts' ] , config ) ) . to . fail ( ) . withMessage ( /Product.ts -> \d+:\d+ Unterminated string literal./ ) ; } ) ; it ( 'can access semantic information' , function ( ) { this . timeout ( 10000 ) ; class MockVisitor implements Visitor { public realTypeName : string ; filter ( node ) { return node . getSourceFile ( ) . fileName === 'index.ts' && node . kind === ts . SyntaxKind . VariableDeclaration ; } visit ( node , context ) { const ls = context . getLanguageService ( ) ; const x = ls . getTypeDefinitionAtPosition ( node . getSourceFile ( ) . fileName , node . getStart ( ) ) ; this . realTypeName = x [ 0 ] . name ; } } const visitor = new MockVisitor ( ) ; const config = { resolutionHosts : [ new MockModule ( 'a.ts' , `
					export default class Product {}
				` ) , new MockModule ( 'node_modules/lib/index.ts' , '' ) , new MockModule ( 'index.ts' , `
                    import * as lib from 'lib';
					import {default as SomeClass} from './a';
					const a: SomeClass = null;
				` ) , new MockModule ( 'lib.d.ts' , readFileSync ( require . resolve ( 'typescript/lib/lib.d.ts' ) , 'utf8' ) ) ] , mutators : [ visitor ] } ; tspoon . validateAll ( [ 'index.ts' ] , config ) ; expect ( visitor . realTypeName ) . to . equal ( 'Product' ) ; } ) ; } ) ;