| 
									
										
										
										
											2019-08-15 14:09:33 -07: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
 | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-12-16 12:11:39 -08:00
										 |  |  | import * as ng from '@angular/compiler'; | 
					
						
							| 
									
										
										
										
											2019-08-15 14:09:33 -07:00
										 |  |  | import * as ts from 'typescript'; | 
					
						
							| 
									
										
										
										
											2019-12-16 12:11:39 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-05-07 08:55:33 -07:00
										 |  |  | import {getClassDeclFromDecoratorProp, getDirectiveClassLike} from '../src/ts_utils'; | 
					
						
							|  |  |  | import {getPathToNodeAtPosition} from '../src/utils'; | 
					
						
							| 
									
										
										
										
											2020-04-08 20:05:32 -07:00
										 |  |  | import {MockTypescriptHost} from './test_utils'; | 
					
						
							| 
									
										
										
										
											2019-08-15 14:09:33 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-24 18:28:31 -08:00
										 |  |  | describe('getDirectiveClassLike', () => { | 
					
						
							| 
									
										
										
										
											2019-08-15 14:09:33 -07:00
										 |  |  |   it('should return a directive class', () => { | 
					
						
							|  |  |  |     const sourceFile = ts.createSourceFile( | 
					
						
							|  |  |  |         'foo.ts', `
 | 
					
						
							|  |  |  |       @NgModule({ | 
					
						
							|  |  |  |         declarations: [], | 
					
						
							|  |  |  |       }) | 
					
						
							|  |  |  |       class AppModule {} | 
					
						
							|  |  |  |     `,
 | 
					
						
							|  |  |  |         ts.ScriptTarget.ES2015); | 
					
						
							|  |  |  |     const result = sourceFile.forEachChild(c => { | 
					
						
							|  |  |  |       const directive = getDirectiveClassLike(c); | 
					
						
							|  |  |  |       if (directive) { | 
					
						
							|  |  |  |         return directive; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |     expect(result).toBeTruthy(); | 
					
						
							| 
									
										
										
										
											2020-04-03 20:57:39 -07:00
										 |  |  |     const {decoratorId, classId} = result!; | 
					
						
							| 
									
										
										
										
											2019-08-15 14:09:33 -07:00
										 |  |  |     expect(decoratorId.kind).toBe(ts.SyntaxKind.Identifier); | 
					
						
							| 
									
										
										
										
											2020-01-23 12:02:47 -08:00
										 |  |  |     expect(decoratorId.text).toBe('NgModule'); | 
					
						
							|  |  |  |     expect(classId.text).toBe('AppModule'); | 
					
						
							| 
									
										
										
										
											2019-08-15 14:09:33 -07:00
										 |  |  |   }); | 
					
						
							|  |  |  | }); | 
					
						
							| 
									
										
										
										
											2019-12-16 12:11:39 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  | describe('getPathToNodeAtPosition', () => { | 
					
						
							|  |  |  |   const html = '<div c></div>'; | 
					
						
							|  |  |  |   const nodes: ng.Node[] = []; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   beforeAll(() => { | 
					
						
							|  |  |  |     const parser = new ng.HtmlParser(); | 
					
						
							|  |  |  |     const {rootNodes, errors} = parser.parse(html, 'url'); | 
					
						
							|  |  |  |     expect(errors.length).toBe(0); | 
					
						
							|  |  |  |     nodes.push(...rootNodes); | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-24 18:28:31 -08:00
										 |  |  |   it('should capture element', () => { | 
					
						
							|  |  |  |     // Try to get a path to an element
 | 
					
						
							| 
									
										
										
										
											2019-12-16 12:11:39 -08:00
										 |  |  |     // <|div c></div>
 | 
					
						
							|  |  |  |     //  ^ cursor is here
 | 
					
						
							|  |  |  |     const position = html.indexOf('div'); | 
					
						
							|  |  |  |     const path = getPathToNodeAtPosition(nodes, position); | 
					
						
							|  |  |  |     // There should be just 1 node in the path, the Element node
 | 
					
						
							|  |  |  |     expect(path.empty).toBe(false); | 
					
						
							|  |  |  |     expect(path.head instanceof ng.Element).toBe(true); | 
					
						
							|  |  |  |     expect(path.head).toBe(path.tail); | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-24 18:28:31 -08:00
										 |  |  |   it('should capture attribute', () => { | 
					
						
							|  |  |  |     // Try to get a path to an attribute
 | 
					
						
							| 
									
										
										
										
											2019-12-16 12:11:39 -08:00
										 |  |  |     // <div |c></div>
 | 
					
						
							|  |  |  |     //      ^ cusor is here, before the attribute
 | 
					
						
							|  |  |  |     const position = html.indexOf('c'); | 
					
						
							|  |  |  |     const path = getPathToNodeAtPosition(nodes, position); | 
					
						
							|  |  |  |     expect(path.empty).toBe(false); | 
					
						
							|  |  |  |     expect(path.head instanceof ng.Element).toBe(true); | 
					
						
							|  |  |  |     expect(path.tail instanceof ng.Attribute).toBe(true); | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-24 18:28:31 -08:00
										 |  |  |   it('should capture attribute before cursor', () => { | 
					
						
							|  |  |  |     // Try to get a path to an attribute
 | 
					
						
							| 
									
										
										
										
											2019-12-16 12:11:39 -08:00
										 |  |  |     // <div c|></div>
 | 
					
						
							|  |  |  |     //       ^ cursor is here, after the attribute
 | 
					
						
							|  |  |  |     const position = html.indexOf('c') + 1; | 
					
						
							|  |  |  |     const path = getPathToNodeAtPosition(nodes, position); | 
					
						
							|  |  |  |     expect(path.empty).toBe(false); | 
					
						
							|  |  |  |     expect(path.head instanceof ng.Element).toBe(true); | 
					
						
							|  |  |  |     expect(path.tail instanceof ng.Attribute).toBe(true); | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | }); | 
					
						
							| 
									
										
										
										
											2020-04-08 20:05:32 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | describe('getClassDeclFromTemplateNode', () => { | 
					
						
							|  |  |  |   it('should find class declaration in syntax-only mode', () => { | 
					
						
							|  |  |  |     const sourceFile = ts.createSourceFile( | 
					
						
							|  |  |  |         'foo.ts', `
 | 
					
						
							|  |  |  |         @Component({ | 
					
						
							|  |  |  |           template: '<div></div>' | 
					
						
							|  |  |  |         }) | 
					
						
							|  |  |  |         class MyComponent {}`,
 | 
					
						
							|  |  |  |         ts.ScriptTarget.ES2015, true /* setParentNodes */); | 
					
						
							|  |  |  |     function visit(node: ts.Node): ts.ClassDeclaration|undefined { | 
					
						
							|  |  |  |       if (ts.isPropertyAssignment(node)) { | 
					
						
							|  |  |  |         return getClassDeclFromDecoratorProp(node); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       return node.forEachChild(visit); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     const classDecl = sourceFile.forEachChild(visit); | 
					
						
							|  |  |  |     expect(classDecl).toBeTruthy(); | 
					
						
							|  |  |  |     expect(classDecl!.kind).toBe(ts.SyntaxKind.ClassDeclaration); | 
					
						
							|  |  |  |     expect((classDecl as ts.ClassDeclaration).name!.text).toBe('MyComponent'); | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   it('should return class declaration for AppComponent', () => { | 
					
						
							|  |  |  |     const host = new MockTypescriptHost(['/app/app.component.ts']); | 
					
						
							|  |  |  |     const tsLS = ts.createLanguageService(host); | 
					
						
							|  |  |  |     const sourceFile = tsLS.getProgram()!.getSourceFile('/app/app.component.ts'); | 
					
						
							|  |  |  |     expect(sourceFile).toBeTruthy(); | 
					
						
							|  |  |  |     const classDecl = sourceFile!.forEachChild(function visit(node): ts.Node|undefined { | 
					
						
							|  |  |  |       if (ts.isPropertyAssignment(node)) { | 
					
						
							|  |  |  |         return getClassDeclFromDecoratorProp(node); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       return node.forEachChild(visit); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |     expect(classDecl).toBeTruthy(); | 
					
						
							|  |  |  |     expect(ts.isClassDeclaration(classDecl!)).toBe(true); | 
					
						
							|  |  |  |     expect((classDecl as ts.ClassDeclaration).name!.text).toBe('AppComponent'); | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | }); |