2019-08-15 17:09:33 -04:00
|
|
|
/**
|
|
|
|
* @license
|
2020-05-19 15:08:49 -04:00
|
|
|
* Copyright Google LLC All Rights Reserved.
|
2019-08-15 17:09:33 -04:00
|
|
|
*
|
|
|
|
* 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 15:11:39 -05:00
|
|
|
import * as ng from '@angular/compiler';
|
2019-08-15 17:09:33 -04:00
|
|
|
import * as ts from 'typescript';
|
2019-12-16 15:11:39 -05:00
|
|
|
|
2020-10-09 14:22:03 -04:00
|
|
|
import {getClassDeclFromDecoratorProp} from '../common/ts_utils';
|
|
|
|
import {getDirectiveClassLike} from '../src/ts_utils';
|
2020-05-07 11:55:33 -04:00
|
|
|
import {getPathToNodeAtPosition} from '../src/utils';
|
2020-04-08 23:05:32 -04:00
|
|
|
import {MockTypescriptHost} from './test_utils';
|
2019-08-15 17:09:33 -04:00
|
|
|
|
2020-02-24 21:28:31 -05:00
|
|
|
describe('getDirectiveClassLike', () => {
|
2019-08-15 17:09:33 -04: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 23:57:39 -04:00
|
|
|
const {decoratorId, classId} = result!;
|
2019-08-15 17:09:33 -04:00
|
|
|
expect(decoratorId.kind).toBe(ts.SyntaxKind.Identifier);
|
2020-01-23 15:02:47 -05:00
|
|
|
expect(decoratorId.text).toBe('NgModule');
|
|
|
|
expect(classId.text).toBe('AppModule');
|
2019-08-15 17:09:33 -04:00
|
|
|
});
|
|
|
|
});
|
2019-12-16 15:11:39 -05: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 21:28:31 -05:00
|
|
|
it('should capture element', () => {
|
|
|
|
// Try to get a path to an element
|
2019-12-16 15:11:39 -05: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 21:28:31 -05:00
|
|
|
it('should capture attribute', () => {
|
|
|
|
// Try to get a path to an attribute
|
2019-12-16 15:11:39 -05: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 21:28:31 -05:00
|
|
|
it('should capture attribute before cursor', () => {
|
|
|
|
// Try to get a path to an attribute
|
2019-12-16 15:11:39 -05: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 23:05:32 -04: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');
|
|
|
|
});
|
|
|
|
});
|