| 
									
										
										
										
											2020-09-30 08:47:36 -07:00
										 |  |  | /** | 
					
						
							|  |  |  |  * @license | 
					
						
							|  |  |  |  * Copyright Google LLC 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
 | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-04 12:28:37 -08:00
										 |  |  | import {LanguageService} from '../../language_service'; | 
					
						
							| 
									
										
										
										
											2020-09-30 08:47:36 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-16 11:21:24 -07:00
										 |  |  | import {APP_COMPONENT, MockService, setup} from './mock_host'; | 
					
						
							| 
									
										
										
										
											2020-10-02 13:54:18 -07:00
										 |  |  | import {humanizeDefinitionInfo} from './test_utils'; | 
					
						
							| 
									
										
										
										
											2020-09-30 08:47:36 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | describe('definitions', () => { | 
					
						
							| 
									
										
										
										
											2020-10-16 11:21:24 -07:00
										 |  |  |   let service: MockService; | 
					
						
							|  |  |  |   let ngLS: LanguageService; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   beforeAll(() => { | 
					
						
							|  |  |  |     const {project, service: _service, tsLS} = setup(); | 
					
						
							|  |  |  |     service = _service; | 
					
						
							| 
									
										
										
										
											2021-03-02 15:46:11 -08:00
										 |  |  |     ngLS = new LanguageService(project, tsLS, {}); | 
					
						
							| 
									
										
										
										
											2020-10-16 11:21:24 -07:00
										 |  |  |   }); | 
					
						
							| 
									
										
										
										
											2020-09-30 08:47:36 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |   beforeEach(() => { | 
					
						
							|  |  |  |     service.reset(); | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   describe('elements', () => { | 
					
						
							| 
									
										
										
										
											2020-10-09 10:58:16 -07:00
										 |  |  |     it('should work for native elements', () => { | 
					
						
							|  |  |  |       const defs = getDefinitionsAndAssertBoundSpan({ | 
					
						
							|  |  |  |         templateOverride: `<butt¦on></button>`, | 
					
						
							|  |  |  |         expectedSpanText: `<button></button>`, | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  |       expect(defs.length).toEqual(2); | 
					
						
							|  |  |  |       expect(defs[0].fileName).toContain('lib.dom.d.ts'); | 
					
						
							|  |  |  |       expect(defs[0].contextSpan).toContain('interface HTMLButtonElement extends HTMLElement'); | 
					
						
							|  |  |  |       expect(defs[1].contextSpan).toContain('declare var HTMLButtonElement'); | 
					
						
							| 
									
										
										
										
											2020-09-30 08:47:36 -07:00
										 |  |  |     }); | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   describe('templates', () => { | 
					
						
							|  |  |  |     it('should return no definitions for ng-templates', () => { | 
					
						
							|  |  |  |       const {position} = | 
					
						
							|  |  |  |           service.overwriteInlineTemplate(APP_COMPONENT, `<ng-templ¦ate></ng-template>`); | 
					
						
							|  |  |  |       const definitionAndBoundSpan = ngLS.getDefinitionAndBoundSpan(APP_COMPONENT, position); | 
					
						
							| 
									
										
										
										
											2020-12-17 14:43:20 -08:00
										 |  |  |       expect(definitionAndBoundSpan).toBeUndefined(); | 
					
						
							| 
									
										
										
										
											2020-09-30 08:47:36 -07:00
										 |  |  |     }); | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   describe('directives', () => { | 
					
						
							|  |  |  |     it('should work for directives', () => { | 
					
						
							| 
									
										
										
										
											2020-10-09 10:58:16 -07:00
										 |  |  |       const defs = getDefinitionsAndAssertBoundSpan({ | 
					
						
							| 
									
										
										
										
											2020-09-30 08:47:36 -07:00
										 |  |  |         templateOverride: `<div string-model¦></div>`, | 
					
						
							|  |  |  |         expectedSpanText: 'string-model', | 
					
						
							|  |  |  |       }); | 
					
						
							| 
									
										
										
										
											2020-10-09 10:58:16 -07:00
										 |  |  |       expect(defs.length).toEqual(1); | 
					
						
							|  |  |  |       expect(defs[0].contextSpan).toContain('@Directive'); | 
					
						
							|  |  |  |       expect(defs[0].contextSpan).toContain('export class StringModel'); | 
					
						
							| 
									
										
										
										
											2020-09-30 08:47:36 -07:00
										 |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     it('should work for components', () => { | 
					
						
							|  |  |  |       const templateOverride = `
 | 
					
						
							|  |  |  |           <t¦est-comp> | 
					
						
							|  |  |  |             <div>some stuff in the middle</div> | 
					
						
							|  |  |  |           </test-comp>`; | 
					
						
							|  |  |  |       const definitions = getDefinitionsAndAssertBoundSpan({ | 
					
						
							|  |  |  |         templateOverride, | 
					
						
							|  |  |  |         expectedSpanText: templateOverride.replace('¦', '').trim(), | 
					
						
							|  |  |  |       }); | 
					
						
							| 
									
										
										
										
											2020-10-09 10:58:16 -07:00
										 |  |  |       expect(definitions.length).toEqual(1); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       expect(definitions.length).toEqual(1); | 
					
						
							|  |  |  |       expect(definitions[0].textSpan).toEqual('TestComponent'); | 
					
						
							|  |  |  |       expect(definitions[0].contextSpan).toContain('@Component'); | 
					
						
							| 
									
										
										
										
											2020-09-30 08:47:36 -07:00
										 |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-09 10:58:16 -07:00
										 |  |  |     it('should work for structural directives', () => { | 
					
						
							|  |  |  |       const definitions = getDefinitionsAndAssertBoundSpan({ | 
					
						
							|  |  |  |         templateOverride: `<div *¦ngFor="let item of heroes"></div>`, | 
					
						
							|  |  |  |         expectedSpanText: 'ngFor', | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  |       expect(definitions.length).toEqual(1); | 
					
						
							|  |  |  |       expect(definitions[0].fileName).toContain('ng_for_of.d.ts'); | 
					
						
							|  |  |  |       expect(definitions[0].textSpan).toEqual('NgForOf'); | 
					
						
							|  |  |  |       expect(definitions[0].contextSpan) | 
					
						
							|  |  |  |           .toContain( | 
					
						
							|  |  |  |               'export declare class NgForOf<T, U extends NgIterable<T> = NgIterable<T>> implements DoCheck'); | 
					
						
							|  |  |  |     }); | 
					
						
							| 
									
										
										
										
											2020-09-30 08:47:36 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |     it('should return binding for structural directive where key maps to a binding', () => { | 
					
						
							|  |  |  |       const definitions = getDefinitionsAndAssertBoundSpan({ | 
					
						
							|  |  |  |         templateOverride: `<div *ng¦If="anyValue"></div>`, | 
					
						
							|  |  |  |         expectedSpanText: 'ngIf', | 
					
						
							|  |  |  |       }); | 
					
						
							| 
									
										
										
										
											2020-10-12 12:48:56 -07:00
										 |  |  |       // Because the input is also part of the selector, the directive is also returned.
 | 
					
						
							|  |  |  |       expect(definitions!.length).toEqual(2); | 
					
						
							|  |  |  |       const [inputDef, directiveDef] = definitions; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       expect(inputDef.textSpan).toEqual('ngIf'); | 
					
						
							|  |  |  |       expect(inputDef.contextSpan).toEqual('set ngIf(condition: T);'); | 
					
						
							|  |  |  |       expect(directiveDef.textSpan).toEqual('NgIf'); | 
					
						
							|  |  |  |       expect(directiveDef.contextSpan).toContain('export declare class NgIf'); | 
					
						
							| 
									
										
										
										
											2020-09-30 08:47:36 -07:00
										 |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     it('should work for directives with compound selectors', () => { | 
					
						
							| 
									
										
										
										
											2020-10-09 10:58:16 -07:00
										 |  |  |       let defs = getDefinitionsAndAssertBoundSpan({ | 
					
						
							|  |  |  |         templateOverride: `<button com¦pound custom-button></button>`, | 
					
						
							|  |  |  |         expectedSpanText: 'compound', | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  |       expect(defs.length).toEqual(1); | 
					
						
							|  |  |  |       expect(defs[0].contextSpan).toContain('export class CompoundCustomButtonDirective'); | 
					
						
							|  |  |  |       defs = getDefinitionsAndAssertBoundSpan({ | 
					
						
							|  |  |  |         templateOverride: `<button compound cu¦stom-button></button>`, | 
					
						
							|  |  |  |         expectedSpanText: 'custom-button', | 
					
						
							| 
									
										
										
										
											2020-09-30 08:47:36 -07:00
										 |  |  |       }); | 
					
						
							| 
									
										
										
										
											2020-10-09 10:58:16 -07:00
										 |  |  |       expect(defs.length).toEqual(1); | 
					
						
							|  |  |  |       expect(defs[0].contextSpan).toContain('export class CompoundCustomButtonDirective'); | 
					
						
							| 
									
										
										
										
											2020-09-30 08:47:36 -07:00
										 |  |  |     }); | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   describe('bindings', () => { | 
					
						
							|  |  |  |     describe('inputs', () => { | 
					
						
							|  |  |  |       it('should work for input providers', () => { | 
					
						
							|  |  |  |         const definitions = getDefinitionsAndAssertBoundSpan({ | 
					
						
							|  |  |  |           templateOverride: `<test-comp [tcN¦ame]="name"></test-comp>`, | 
					
						
							|  |  |  |           expectedSpanText: 'tcName', | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  |         expect(definitions!.length).toEqual(1); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const [def] = definitions; | 
					
						
							|  |  |  |         expect(def.textSpan).toEqual('name'); | 
					
						
							|  |  |  |         expect(def.contextSpan).toEqual(`@Input('tcName') name = 'test';`); | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-12 12:48:56 -07:00
										 |  |  |       it('should work for text inputs', () => { | 
					
						
							|  |  |  |         const definitions = getDefinitionsAndAssertBoundSpan({ | 
					
						
							|  |  |  |           templateOverride: `<test-comp tcN¦ame="name"></test-comp>`, | 
					
						
							| 
									
										
										
										
											2020-11-09 10:25:25 -08:00
										 |  |  |           expectedSpanText: 'tcName', | 
					
						
							| 
									
										
										
										
											2020-10-12 12:48:56 -07:00
										 |  |  |         }); | 
					
						
							|  |  |  |         expect(definitions!.length).toEqual(1); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const [def] = definitions; | 
					
						
							|  |  |  |         expect(def.textSpan).toEqual('name'); | 
					
						
							|  |  |  |         expect(def.contextSpan).toEqual(`@Input('tcName') name = 'test';`); | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-30 08:47:36 -07:00
										 |  |  |       it('should work for structural directive inputs ngForTrackBy', () => { | 
					
						
							|  |  |  |         const definitions = getDefinitionsAndAssertBoundSpan({ | 
					
						
							|  |  |  |           templateOverride: `<div *ngFor="let item of heroes; tr¦ackBy: test;"></div>`, | 
					
						
							|  |  |  |           expectedSpanText: 'trackBy', | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  |         expect(definitions!.length).toEqual(2); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const [setterDef, getterDef] = definitions; | 
					
						
							|  |  |  |         expect(setterDef.fileName).toContain('ng_for_of.d.ts'); | 
					
						
							|  |  |  |         expect(setterDef.textSpan).toEqual('ngForTrackBy'); | 
					
						
							|  |  |  |         expect(setterDef.contextSpan).toEqual('set ngForTrackBy(fn: TrackByFunction<T>);'); | 
					
						
							|  |  |  |         expect(getterDef.textSpan).toEqual('ngForTrackBy'); | 
					
						
							|  |  |  |         expect(getterDef.contextSpan).toEqual('get ngForTrackBy(): TrackByFunction<T>;'); | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       it('should work for structural directive inputs ngForOf', () => { | 
					
						
							|  |  |  |         const definitions = getDefinitionsAndAssertBoundSpan({ | 
					
						
							|  |  |  |           templateOverride: `<div *ngFor="let item o¦f heroes"></div>`, | 
					
						
							|  |  |  |           expectedSpanText: 'of', | 
					
						
							|  |  |  |         }); | 
					
						
							| 
									
										
										
										
											2020-10-12 12:48:56 -07:00
										 |  |  |         // Because the input is also part of the selector ([ngFor][ngForOf]), the directive is also
 | 
					
						
							|  |  |  |         // returned.
 | 
					
						
							|  |  |  |         expect(definitions!.length).toEqual(2); | 
					
						
							|  |  |  |         const [inputDef, directiveDef] = definitions; | 
					
						
							| 
									
										
										
										
											2020-09-30 08:47:36 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-12 12:48:56 -07:00
										 |  |  |         expect(inputDef.textSpan).toEqual('ngForOf'); | 
					
						
							|  |  |  |         expect(inputDef.contextSpan) | 
					
						
							| 
									
										
										
										
											2020-09-30 08:47:36 -07:00
										 |  |  |             .toEqual('set ngForOf(ngForOf: U & NgIterable<T> | undefined | null);'); | 
					
						
							| 
									
										
										
										
											2020-10-12 12:48:56 -07:00
										 |  |  |         expect(directiveDef.textSpan).toEqual('NgForOf'); | 
					
						
							|  |  |  |         expect(directiveDef.contextSpan).toContain('export declare class NgForOf'); | 
					
						
							| 
									
										
										
										
											2020-09-30 08:47:36 -07:00
										 |  |  |       }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       it('should work for two-way binding providers', () => { | 
					
						
							|  |  |  |         const definitions = getDefinitionsAndAssertBoundSpan({ | 
					
						
							|  |  |  |           templateOverride: `<test-comp string-model [(mo¦del)]="title"></test-comp>`, | 
					
						
							|  |  |  |           expectedSpanText: 'model', | 
					
						
							|  |  |  |         }); | 
					
						
							| 
									
										
										
										
											2020-12-17 14:43:20 -08:00
										 |  |  |         expect(definitions!.length).toEqual(2); | 
					
						
							| 
									
										
										
										
											2020-09-30 08:47:36 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-17 14:43:20 -08:00
										 |  |  |         const [inputDef, outputDef] = definitions; | 
					
						
							|  |  |  |         expect(inputDef.textSpan).toEqual('model'); | 
					
						
							|  |  |  |         expect(inputDef.contextSpan).toEqual(`@Input() model: string = 'model';`); | 
					
						
							|  |  |  |         expect(outputDef.textSpan).toEqual('modelChange'); | 
					
						
							|  |  |  |         expect(outputDef.contextSpan) | 
					
						
							|  |  |  |             .toEqual(`@Output() modelChange: EventEmitter<string> = new EventEmitter();`); | 
					
						
							| 
									
										
										
										
											2020-09-30 08:47:36 -07:00
										 |  |  |       }); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     describe('outputs', () => { | 
					
						
							|  |  |  |       it('should work for event providers', () => { | 
					
						
							|  |  |  |         const definitions = getDefinitionsAndAssertBoundSpan({ | 
					
						
							|  |  |  |           templateOverride: `<test-comp (te¦st)="myClick($event)"></test-comp>`, | 
					
						
							| 
									
										
										
										
											2020-11-09 09:16:19 -08:00
										 |  |  |           expectedSpanText: 'test', | 
					
						
							| 
									
										
										
										
											2020-09-30 08:47:36 -07:00
										 |  |  |         }); | 
					
						
							|  |  |  |         expect(definitions!.length).toEqual(1); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const [def] = definitions; | 
					
						
							|  |  |  |         expect(def.textSpan).toEqual('testEvent'); | 
					
						
							|  |  |  |         expect(def.contextSpan).toEqual('@Output(\'test\') testEvent = new EventEmitter();'); | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       it('should return nothing for $event from EventEmitter', () => { | 
					
						
							|  |  |  |         const {position} = service.overwriteInlineTemplate( | 
					
						
							|  |  |  |             APP_COMPONENT, `<div string-model (modelChange)="myClick($e¦vent)"></div>`); | 
					
						
							|  |  |  |         const definitionAndBoundSpan = ngLS.getDefinitionAndBoundSpan(APP_COMPONENT, position); | 
					
						
							|  |  |  |         expect(definitionAndBoundSpan).toBeUndefined(); | 
					
						
							|  |  |  |       }); | 
					
						
							| 
									
										
										
										
											2020-10-12 12:48:56 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |       it('should return the directive when the event is part of the selector', () => { | 
					
						
							|  |  |  |         const definitions = getDefinitionsAndAssertBoundSpan({ | 
					
						
							|  |  |  |           templateOverride: `<div (eventSelect¦or)="title = ''"></div>`, | 
					
						
							| 
									
										
										
										
											2020-11-09 09:16:19 -08:00
										 |  |  |           expectedSpanText: `eventSelector`, | 
					
						
							| 
									
										
										
										
											2020-10-12 12:48:56 -07:00
										 |  |  |         }); | 
					
						
							|  |  |  |         expect(definitions!.length).toEqual(2); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const [inputDef, directiveDef] = definitions; | 
					
						
							|  |  |  |         expect(inputDef.textSpan).toEqual('eventSelector'); | 
					
						
							|  |  |  |         expect(inputDef.contextSpan).toEqual('@Output() eventSelector = new EventEmitter<void>();'); | 
					
						
							|  |  |  |         expect(directiveDef.textSpan).toEqual('EventSelectorDirective'); | 
					
						
							|  |  |  |         expect(directiveDef.contextSpan).toContain('export class EventSelectorDirective'); | 
					
						
							|  |  |  |       }); | 
					
						
							| 
									
										
										
										
											2020-10-16 17:17:15 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |       it('should work for $event from native element', () => { | 
					
						
							|  |  |  |         const definitions = getDefinitionsAndAssertBoundSpan({ | 
					
						
							|  |  |  |           templateOverride: `<div (cl¦ick)="myClick($event)"></div>`, | 
					
						
							|  |  |  |           expectedSpanText: 'click', | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  |         expect(definitions!.length).toEqual(1); | 
					
						
							|  |  |  |         expect(definitions[0].textSpan).toEqual('addEventListener'); | 
					
						
							|  |  |  |         expect(definitions[0].contextSpan) | 
					
						
							|  |  |  |             .toContain('addEventListener<K extends keyof HTMLElementEventMap>'); | 
					
						
							|  |  |  |         expect(definitions[0].fileName).toContain('lib.dom.d.ts'); | 
					
						
							|  |  |  |       }); | 
					
						
							| 
									
										
										
										
											2020-09-30 08:47:36 -07:00
										 |  |  |     }); | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   describe('references', () => { | 
					
						
							|  |  |  |     it('should work for element reference declarations', () => { | 
					
						
							| 
									
										
										
										
											2020-12-17 14:43:20 -08:00
										 |  |  |       const {position} = | 
					
						
							|  |  |  |           service.overwriteInlineTemplate(APP_COMPONENT, `<div #cha¦rt></div>{{chart}}`); | 
					
						
							|  |  |  |       const definitionAndBoundSpan = ngLS.getDefinitionAndBoundSpan(APP_COMPONENT, position); | 
					
						
							| 
									
										
										
										
											2020-09-30 08:47:36 -07:00
										 |  |  |       // We're already at the definition, so nothing is returned
 | 
					
						
							| 
									
										
										
										
											2020-12-17 14:43:20 -08:00
										 |  |  |       expect(definitionAndBoundSpan).toBeUndefined(); | 
					
						
							| 
									
										
										
										
											2020-09-30 08:47:36 -07:00
										 |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     it('should work for element reference uses', () => { | 
					
						
							|  |  |  |       const definitions = getDefinitionsAndAssertBoundSpan({ | 
					
						
							|  |  |  |         templateOverride: `<div #chart></div>{{char¦t}}`, | 
					
						
							|  |  |  |         expectedSpanText: 'chart', | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  |       expect(definitions!.length).toEqual(1); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       const [varDef] = definitions; | 
					
						
							| 
									
										
										
										
											2020-11-09 13:35:48 -08:00
										 |  |  |       expect(varDef.textSpan).toEqual('chart'); | 
					
						
							| 
									
										
										
										
											2020-09-30 08:47:36 -07:00
										 |  |  |     }); | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   describe('variables', () => { | 
					
						
							|  |  |  |     it('should work for array members', () => { | 
					
						
							|  |  |  |       const definitions = getDefinitionsAndAssertBoundSpan({ | 
					
						
							|  |  |  |         templateOverride: `<div *ngFor="let hero of heroes">{{her¦o}}</div>`, | 
					
						
							|  |  |  |         expectedSpanText: 'hero', | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  |       expect(definitions!.length).toEqual(2); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       const [templateDeclarationDef, contextDef] = definitions; | 
					
						
							|  |  |  |       expect(templateDeclarationDef.textSpan).toEqual('hero'); | 
					
						
							|  |  |  |       // `$implicit` is from the `NgForOfContext`:
 | 
					
						
							|  |  |  |       // https://github.com/angular/angular/blob/89c5255b8ca59eed27ede9e1fad69857ab0c6f4f/packages/common/src/directives/ng_for_of.ts#L15
 | 
					
						
							|  |  |  |       expect(contextDef.textSpan).toEqual('$implicit'); | 
					
						
							|  |  |  |       expect(contextDef.contextSpan).toContain('$implicit: T;'); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   describe('pipes', () => { | 
					
						
							|  |  |  |     it('should work for pipes', () => { | 
					
						
							|  |  |  |       const templateOverride = `<p>The hero's birthday is {{birthday | da¦te: "MM/dd/yy"}}</p>`; | 
					
						
							|  |  |  |       const definitions = getDefinitionsAndAssertBoundSpan({ | 
					
						
							|  |  |  |         templateOverride, | 
					
						
							|  |  |  |         expectedSpanText: 'date', | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  |       expect(definitions!.length).toEqual(1); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       const [def] = definitions; | 
					
						
							|  |  |  |       expect(def.textSpan).toEqual('transform'); | 
					
						
							|  |  |  |       expect(def.contextSpan).toContain('transform(value: Date | string | number, '); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   describe('expressions', () => { | 
					
						
							|  |  |  |     it('should find members in a text interpolation', () => { | 
					
						
							|  |  |  |       const definitions = getDefinitionsAndAssertBoundSpan({ | 
					
						
							|  |  |  |         templateOverride: `<div>{{ tit¦le }}</div>`, | 
					
						
							|  |  |  |         expectedSpanText: 'title', | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  |       expect(definitions!.length).toEqual(1); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       const [def] = definitions; | 
					
						
							|  |  |  |       expect(def.textSpan).toEqual('title'); | 
					
						
							|  |  |  |       expect(def.contextSpan).toEqual(`title = 'Tour of Heroes';`); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     it('should work for accessed property reads', () => { | 
					
						
							|  |  |  |       const definitions = getDefinitionsAndAssertBoundSpan({ | 
					
						
							|  |  |  |         templateOverride: `<div>{{title.len¦gth}}</div>`, | 
					
						
							|  |  |  |         expectedSpanText: 'length', | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  |       expect(definitions!.length).toEqual(1); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       const [def] = definitions; | 
					
						
							|  |  |  |       expect(def.textSpan).toEqual('length'); | 
					
						
							|  |  |  |       expect(def.contextSpan).toEqual('readonly length: number;'); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     it('should find members in an attribute interpolation', () => { | 
					
						
							|  |  |  |       const definitions = getDefinitionsAndAssertBoundSpan({ | 
					
						
							|  |  |  |         templateOverride: `<div string-model model="{{tit¦le}}"></div>`, | 
					
						
							|  |  |  |         expectedSpanText: 'title', | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  |       expect(definitions!.length).toEqual(1); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       const [def] = definitions; | 
					
						
							|  |  |  |       expect(def.textSpan).toEqual('title'); | 
					
						
							|  |  |  |       expect(def.contextSpan).toEqual(`title = 'Tour of Heroes';`); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     it('should find members of input binding', () => { | 
					
						
							|  |  |  |       const definitions = getDefinitionsAndAssertBoundSpan({ | 
					
						
							|  |  |  |         templateOverride: `<test-comp [tcName]="ti¦tle"></test-comp>`, | 
					
						
							|  |  |  |         expectedSpanText: 'title', | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  |       expect(definitions!.length).toEqual(1); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       const [def] = definitions; | 
					
						
							|  |  |  |       expect(def.textSpan).toEqual('title'); | 
					
						
							|  |  |  |       expect(def.contextSpan).toEqual(`title = 'Tour of Heroes';`); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     it('should find members of event binding', () => { | 
					
						
							|  |  |  |       const definitions = getDefinitionsAndAssertBoundSpan({ | 
					
						
							|  |  |  |         templateOverride: `<test-comp (test)="ti¦tle=$event"></test-comp>`, | 
					
						
							|  |  |  |         expectedSpanText: 'title', | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  |       expect(definitions!.length).toEqual(1); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       const [def] = definitions; | 
					
						
							|  |  |  |       expect(def.textSpan).toEqual('title'); | 
					
						
							|  |  |  |       expect(def.contextSpan).toEqual(`title = 'Tour of Heroes';`); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     it('should work for method calls', () => { | 
					
						
							|  |  |  |       const definitions = getDefinitionsAndAssertBoundSpan({ | 
					
						
							|  |  |  |         templateOverride: `<div (click)="setT¦itle('title')"></div>`, | 
					
						
							|  |  |  |         expectedSpanText: 'setTitle', | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  |       expect(definitions!.length).toEqual(1); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       const [def] = definitions; | 
					
						
							|  |  |  |       expect(def.textSpan).toEqual('setTitle'); | 
					
						
							|  |  |  |       expect(def.contextSpan).toContain('setTitle(newTitle: string)'); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     it('should work for accessed properties in writes', () => { | 
					
						
							|  |  |  |       const definitions = getDefinitionsAndAssertBoundSpan({ | 
					
						
							|  |  |  |         templateOverride: `<div (click)="hero.i¦d = 2"></div>`, | 
					
						
							|  |  |  |         expectedSpanText: 'id', | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  |       expect(definitions!.length).toEqual(1); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       const [def] = definitions; | 
					
						
							|  |  |  |       expect(def.textSpan).toEqual('id'); | 
					
						
							|  |  |  |       expect(def.contextSpan).toEqual('id: number;'); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     it('should work for method call arguments', () => { | 
					
						
							|  |  |  |       const definitions = getDefinitionsAndAssertBoundSpan({ | 
					
						
							|  |  |  |         templateOverride: `<div (click)="setTitle(hero.nam¦e)"></div>`, | 
					
						
							|  |  |  |         expectedSpanText: 'name', | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  |       expect(definitions!.length).toEqual(1); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       const [def] = definitions; | 
					
						
							|  |  |  |       expect(def.textSpan).toEqual('name'); | 
					
						
							|  |  |  |       expect(def.contextSpan).toEqual('name: string;'); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     it('should find members of two-way binding', () => { | 
					
						
							|  |  |  |       const definitions = getDefinitionsAndAssertBoundSpan({ | 
					
						
							|  |  |  |         templateOverride: `<input [(ngModel)]="ti¦tle" />`, | 
					
						
							|  |  |  |         expectedSpanText: 'title', | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  |       expect(definitions!.length).toEqual(1); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       const [def] = definitions; | 
					
						
							|  |  |  |       expect(def.textSpan).toEqual('title'); | 
					
						
							|  |  |  |       expect(def.contextSpan).toEqual(`title = 'Tour of Heroes';`); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     it('should find members in a structural directive', () => { | 
					
						
							|  |  |  |       const definitions = getDefinitionsAndAssertBoundSpan({ | 
					
						
							|  |  |  |         templateOverride: `<div *ngIf="anyV¦alue"></div>`, | 
					
						
							|  |  |  |         expectedSpanText: 'anyValue', | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  |       expect(definitions!.length).toEqual(1); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       const [def] = definitions; | 
					
						
							|  |  |  |       expect(def.textSpan).toEqual('anyValue'); | 
					
						
							|  |  |  |       expect(def.contextSpan).toEqual('anyValue: any;'); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     it('should work for variables in structural directives', () => { | 
					
						
							|  |  |  |       const definitions = getDefinitionsAndAssertBoundSpan({ | 
					
						
							|  |  |  |         templateOverride: `<div *ngFor="let item of heroes as her¦oes2; trackBy: test;"></div>`, | 
					
						
							|  |  |  |         expectedSpanText: 'heroes2', | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  |       expect(definitions!.length).toEqual(1); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       const [def] = definitions; | 
					
						
							|  |  |  |       expect(def.textSpan).toEqual('ngForOf'); | 
					
						
							|  |  |  |       expect(def.contextSpan).toEqual('ngForOf: U;'); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     it('should work for uses of members in structural directives', () => { | 
					
						
							|  |  |  |       const definitions = getDefinitionsAndAssertBoundSpan({ | 
					
						
							|  |  |  |         templateOverride: `<div *ngFor="let item of heroes as heroes2">{{her¦oes2}}</div>`, | 
					
						
							|  |  |  |         expectedSpanText: 'heroes2', | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  |       expect(definitions!.length).toEqual(2); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       const [def, contextDef] = definitions; | 
					
						
							|  |  |  |       expect(def.textSpan).toEqual('heroes2'); | 
					
						
							|  |  |  |       expect(def.contextSpan).toEqual('of heroes as heroes2'); | 
					
						
							|  |  |  |       expect(contextDef.textSpan).toEqual('ngForOf'); | 
					
						
							|  |  |  |       expect(contextDef.contextSpan).toEqual('ngForOf: U;'); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     it('should work for members in structural directives', () => { | 
					
						
							|  |  |  |       const definitions = getDefinitionsAndAssertBoundSpan({ | 
					
						
							|  |  |  |         templateOverride: `<div *ngFor="let item of her¦oes; trackBy: test;"></div>`, | 
					
						
							|  |  |  |         expectedSpanText: 'heroes', | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  |       expect(definitions!.length).toEqual(1); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       const [def] = definitions; | 
					
						
							|  |  |  |       expect(def.textSpan).toEqual('heroes'); | 
					
						
							|  |  |  |       expect(def.contextSpan).toEqual('heroes: Hero[] = [this.hero];'); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     it('should return nothing for the $any() cast function', () => { | 
					
						
							|  |  |  |       const {position} = | 
					
						
							|  |  |  |           service.overwriteInlineTemplate(APP_COMPONENT, `<div>{{$an¦y(title)}}</div>`); | 
					
						
							|  |  |  |       const definitionAndBoundSpan = ngLS.getDefinitionAndBoundSpan(APP_COMPONENT, position); | 
					
						
							|  |  |  |       expect(definitionAndBoundSpan).toBeUndefined(); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-09 11:22:03 -07:00
										 |  |  |   describe('external resources', () => { | 
					
						
							|  |  |  |     it('should be able to find a template from a url', () => { | 
					
						
							|  |  |  |       const {position, text} = service.overwrite(APP_COMPONENT, `
 | 
					
						
							|  |  |  |         import {Component} from '@angular/core'; | 
					
						
							|  |  |  | 	      @Component({ | 
					
						
							|  |  |  | 	        templateUrl: './tes¦t.ng', | 
					
						
							|  |  |  | 	      }) | 
					
						
							| 
									
										
										
										
											2020-10-21 11:01:10 -07:00
										 |  |  | 	      export class AppComponent {}`);
 | 
					
						
							| 
									
										
										
										
											2020-10-09 11:22:03 -07:00
										 |  |  |       const result = ngLS.getDefinitionAndBoundSpan(APP_COMPONENT, position); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       expect(result).toBeDefined(); | 
					
						
							|  |  |  |       const {textSpan, definitions} = result!; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       expect(text.substring(textSpan.start, textSpan.start + textSpan.length)).toEqual('./test.ng'); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       expect(definitions).toBeDefined(); | 
					
						
							|  |  |  |       expect(definitions!.length).toBe(1); | 
					
						
							|  |  |  |       const [def] = definitions!; | 
					
						
							|  |  |  |       expect(def.fileName).toContain('/app/test.ng'); | 
					
						
							|  |  |  |       expect(def.textSpan).toEqual({start: 0, length: 0}); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     it('should be able to find a stylesheet from a url', () => { | 
					
						
							|  |  |  |       const {position, text} = service.overwrite(APP_COMPONENT, `
 | 
					
						
							|  |  |  |         import {Component} from '@angular/core'; | 
					
						
							|  |  |  | 	      @Component({ | 
					
						
							|  |  |  | 	        template: 'empty', | 
					
						
							|  |  |  | 	        styleUrls: ['./te¦st.css'] | 
					
						
							|  |  |  | 	      }) | 
					
						
							| 
									
										
										
										
											2020-10-21 11:01:10 -07:00
										 |  |  | 	      export class AppComponent {}`);
 | 
					
						
							| 
									
										
										
										
											2020-10-09 11:22:03 -07:00
										 |  |  |       const result = ngLS.getDefinitionAndBoundSpan(APP_COMPONENT, position); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       expect(result).toBeDefined(); | 
					
						
							|  |  |  |       const {textSpan, definitions} = result!; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       expect(text.substring(textSpan.start, textSpan.start + textSpan.length)) | 
					
						
							|  |  |  |           .toEqual('./test.css'); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       expect(definitions).toBeDefined(); | 
					
						
							|  |  |  |       expect(definitions!.length).toBe(1); | 
					
						
							|  |  |  |       const [def] = definitions!; | 
					
						
							|  |  |  |       expect(def.fileName).toContain('/app/test.css'); | 
					
						
							|  |  |  |       expect(def.textSpan).toEqual({start: 0, length: 0}); | 
					
						
							|  |  |  |     }); | 
					
						
							| 
									
										
										
										
											2020-10-21 11:01:10 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |     xit('should be able to find a resource url with malformed component meta', () => { | 
					
						
							|  |  |  |       const {position, text} = service.overwrite(APP_COMPONENT, `
 | 
					
						
							|  |  |  |         import {Component} from '@angular/core'; | 
					
						
							|  |  |  | 	      @Component({ | 
					
						
							|  |  |  | 	        invalidProperty: '', | 
					
						
							|  |  |  | 	        styleUrls: ['./te¦st.css'] | 
					
						
							|  |  |  | 	      }) | 
					
						
							|  |  |  | 	      export class AppComponent {}`);
 | 
					
						
							|  |  |  |       const result = ngLS.getDefinitionAndBoundSpan(APP_COMPONENT, position); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       expect(result).toBeDefined(); | 
					
						
							|  |  |  |       const {textSpan, definitions} = result!; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       expect(text.substring(textSpan.start, textSpan.start + textSpan.length)) | 
					
						
							|  |  |  |           .toEqual('./test.css'); | 
					
						
							|  |  |  |       expect(definitions).toBeDefined(); | 
					
						
							|  |  |  |       expect(definitions![0].fileName).toContain('/app/test.css'); | 
					
						
							|  |  |  |     }); | 
					
						
							| 
									
										
										
										
											2020-10-09 11:22:03 -07:00
										 |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-30 08:47:36 -07:00
										 |  |  |   function getDefinitionsAndAssertBoundSpan( | 
					
						
							|  |  |  |       {templateOverride, expectedSpanText}: {templateOverride: string, expectedSpanText: string}): | 
					
						
							|  |  |  |       Array<{textSpan: string, contextSpan: string | undefined, fileName: string}> { | 
					
						
							|  |  |  |     const {position, text} = service.overwriteInlineTemplate(APP_COMPONENT, templateOverride); | 
					
						
							|  |  |  |     const definitionAndBoundSpan = ngLS.getDefinitionAndBoundSpan(APP_COMPONENT, position); | 
					
						
							|  |  |  |     expect(definitionAndBoundSpan).toBeTruthy(); | 
					
						
							|  |  |  |     const {textSpan, definitions} = definitionAndBoundSpan!; | 
					
						
							|  |  |  |     expect(text.substring(textSpan.start, textSpan.start + textSpan.length)) | 
					
						
							|  |  |  |         .toEqual(expectedSpanText); | 
					
						
							|  |  |  |     expect(definitions).toBeTruthy(); | 
					
						
							| 
									
										
										
										
											2020-10-02 13:54:18 -07:00
										 |  |  |     return definitions!.map(d => humanizeDefinitionInfo(d, service)); | 
					
						
							| 
									
										
										
										
											2020-09-30 08:47:36 -07:00
										 |  |  |   } | 
					
						
							|  |  |  | }); |