2016-11-22 12:10:23 -05:00
/ * *
* @license
* Copyright Google Inc . All Rights Reserved .
*
* Use of this source code is governed by an MIT - style license that can be
* found in the LICENSE file at https : //angular.io/license
* /
import * as ts from 'typescript' ;
import { createLanguageService } from '../src/language_service' ;
import { TypeScriptServiceHost } from '../src/typescript_host' ;
2017-01-03 20:21:45 -05:00
import { MockTypescriptHost } from './test_utils' ;
2016-11-22 12:10:23 -05:00
describe ( 'completions' , ( ) = > {
2019-09-17 17:33:41 -04:00
let mockHost = new MockTypescriptHost ( [ '/app/main.ts' , '/app/parsing-cases.ts' ] ) ;
2019-08-28 13:18:18 -04:00
let service = ts . createLanguageService ( mockHost ) ;
2016-12-06 19:19:39 -05:00
let ngHost = new TypeScriptServiceHost ( mockHost , service ) ;
2016-11-22 12:10:23 -05:00
let ngService = createLanguageService ( ngHost ) ;
2019-09-17 17:33:41 -04:00
beforeEach ( ( ) = > { mockHost . reset ( ) ; } ) ;
2016-11-22 12:10:23 -05:00
it ( 'should be able to get entity completions' ,
2019-09-12 22:34:05 -04:00
( ) = > { expectContains ( '/app/test.ng' , 'entity-amp' , '&' , '>' , '<' , 'ι' ) ; } ) ;
2016-11-22 12:10:23 -05:00
it ( 'should be able to return html elements' , ( ) = > {
let htmlTags = [ 'h1' , 'h2' , 'div' , 'span' ] ;
let locations = [ 'empty' , 'start-tag-h1' , 'h1-content' , 'start-tag' , 'start-tag-after-h' ] ;
for ( let location of locations ) {
2019-09-12 22:34:05 -04:00
expectContains ( '/app/test.ng' , location , . . . htmlTags ) ;
2016-11-22 12:10:23 -05:00
}
} ) ;
it ( 'should be able to return element diretives' ,
2019-09-12 22:34:05 -04:00
( ) = > { expectContains ( '/app/test.ng' , 'empty' , 'my-app' ) ; } ) ;
2016-11-22 12:10:23 -05:00
it ( 'should be able to return h1 attributes' ,
2019-09-12 22:34:05 -04:00
( ) = > { expectContains ( '/app/test.ng' , 'h1-after-space' , 'id' , 'dir' , 'lang' , 'onclick' ) ; } ) ;
2016-11-22 12:10:23 -05:00
it ( 'should be able to find common angular attributes' ,
2019-09-12 22:34:05 -04:00
( ) = > { expectContains ( '/app/test.ng' , 'div-attributes' , '(click)' , '[ngClass]' ) ; } ) ;
2016-11-22 12:10:23 -05:00
it ( 'should be able to infer the type of a ngForOf' , ( ) = > {
2019-09-12 22:34:05 -04:00
const fileName = mockHost . addCode ( `
2016-11-22 12:10:23 -05:00
interface Person {
name : string ,
street : string
}
@Component ( { template : '<div *ngFor="let person of people">{{person.~{name}name}}</div' } )
export class MyComponent {
people : Person [ ]
2019-09-12 22:34:05 -04:00
} ` );
expectContains ( fileName , 'name' , 'name' , 'street' ) ;
2016-11-22 12:10:23 -05:00
} ) ;
2019-10-05 08:18:05 -04:00
it ( 'should be able to get completions for exported *ngIf variable' , ( ) = > {
const fileName = mockHost . addCode ( `
interface Person {
name : string ,
street : string
}
@Component ( { template : '<div *ngIf="promised_person | async as person">{{person.~{name}name}}</div' } )
export class MyComponent {
promised_person : Promise < Person >
} ` );
expectContains ( fileName , 'name' , 'name' , 'street' ) ;
} ) ;
2016-11-22 12:10:23 -05:00
it ( 'should be able to infer the type of a ngForOf with an async pipe' , ( ) = > {
2019-09-12 22:34:05 -04:00
const fileName = mockHost . addCode ( `
2016-11-22 12:10:23 -05:00
interface Person {
name : string ,
street : string
}
2019-09-17 17:33:41 -04:00
@Component ( { template : '<div *ngFor="let person of people | async">{{person.~{name}name}}</div>' } )
2016-11-22 12:10:23 -05:00
export class MyComponent {
people : Promise < Person [ ] > ;
2019-09-12 22:34:05 -04:00
} ` );
expectContains ( fileName , 'name' , 'name' , 'street' ) ;
2016-11-22 12:10:23 -05:00
} ) ;
describe ( 'with regression tests' , ( ) = > {
it ( 'should not crash with an incomplete component' , ( ) = > {
expect ( ( ) = > {
2019-09-12 22:34:05 -04:00
const fileName = mockHost . addCode ( `
@Component ( {
template : '~{inside-template}'
} )
export class MyComponent {
2019-09-17 17:33:41 -04:00
2019-09-12 22:34:05 -04:00
} ` );
expectContains ( fileName , 'inside-template' , 'h1' ) ;
2016-11-22 12:10:23 -05:00
} ) . not . toThrow ( ) ;
} ) ;
it ( 'should hot crash with an incomplete class' , ( ) = > {
expect ( ( ) = > {
2019-09-12 22:34:05 -04:00
mockHost . addCode ( '\nexport class' ) ;
ngHost . getAnalyzedModules ( ) ;
2016-11-22 12:10:23 -05:00
} ) . not . toThrow ( ) ;
} ) ;
} ) ;
2017-11-06 13:30:29 -05:00
it ( 'should respect paths configuration' , ( ) = > {
2019-09-19 20:04:02 -04:00
mockHost . overrideOptions ( {
baseUrl : '/app' ,
paths : { 'bar/*' : [ 'foo/bar/*' ] } ,
2017-11-06 13:30:29 -05:00
} ) ;
mockHost . addScript ( '/app/foo/bar/shared.ts' , `
export interface Node {
children : Node [ ] ;
}
` );
mockHost . addScript ( '/app/my.component.ts' , `
import { Component } from '@angular/core' ;
import { Node } from 'bar/shared' ;
@Component ( {
selector : 'my-component' ,
template : '{{tree.~{tree} }}'
} )
export class MyComponent {
tree : Node ;
}
` );
2019-08-05 22:37:30 -04:00
ngHost . getAnalyzedModules ( ) ;
2019-09-12 22:34:05 -04:00
expectContains ( '/app/my.component.ts' , 'tree' , 'children' ) ;
2017-11-06 13:30:29 -05:00
} ) ;
2019-01-30 13:34:36 -05:00
it ( 'should work with input and output' , ( ) = > {
2019-09-12 22:34:05 -04:00
const fileName = mockHost . addCode ( `
2019-01-30 13:34:36 -05:00
@Component ( {
selector : 'foo-component' ,
template : \ `
< div string - model ~ { stringMarker } = " text " > < / div >
< div number - model ~ { numberMarker } = " value " > < / div >
\ ` ,
} )
export class FooComponent {
text : string ;
value : number ;
}
2019-09-12 22:34:05 -04:00
` );
expectContains ( fileName , 'stringMarker' , '[model]' , '(model)' ) ;
expectContains ( fileName , 'numberMarker' , '[inputAlias]' , '(outputAlias)' ) ;
2019-01-30 13:34:36 -05:00
} ) ;
2019-09-12 22:34:05 -04:00
function expectContains ( fileName : string , locationMarker : string , . . . names : string [ ] ) {
2019-10-11 16:56:33 -04:00
const marker = mockHost . getLocationMarkerFor ( fileName , locationMarker ) ;
expectEntries ( locationMarker , ngService . getCompletionsAt ( fileName , marker . start ) , . . . names ) ;
2016-11-22 12:10:23 -05:00
}
} ) ;
2019-07-31 13:55:45 -04:00
function expectEntries (
2019-08-12 19:54:36 -04:00
locationMarker : string , completion : ts.CompletionInfo | undefined , . . . names : string [ ] ) {
2016-11-22 12:10:23 -05:00
let entries : { [ name : string ] : boolean } = { } ;
2019-08-12 19:54:36 -04:00
if ( ! completion ) {
2016-11-22 12:10:23 -05:00
throw new Error (
` Expected result from ${ locationMarker } to include ${ names . join ( ', ' ) } but no result provided ` ) ;
}
2019-08-12 19:54:36 -04:00
if ( ! completion . entries . length ) {
2016-11-22 12:10:23 -05:00
throw new Error (
` Expected result from ${ locationMarker } to include ${ names . join ( ', ' ) } an empty result provided ` ) ;
2019-08-12 19:54:36 -04:00
}
for ( const entry of completion . entries ) {
entries [ entry . name ] = true ;
}
let missing = names . filter ( name = > ! entries [ name ] ) ;
if ( missing . length ) {
throw new Error (
` Expected result from ${ locationMarker } to include at least one of the following, ${ missing . join ( ', ' ) } , in the list of entries ${ completion . entries . map ( entry = > entry . name ) . join ( ', ' ) } ` ) ;
2016-11-22 12:10:23 -05:00
}
}