Alex Rickabaugh 643c96184c refactor(language-service): introduce DisplayParts abstraction for Ivy (#39505)
This commit refactors the QuickInfo abstraction shared between the VE and
Ivy services and used to implement hover tooltips (quick info), which was
extracted from the VE code in commit faa81dc. The new DisplayParts
abstraction is more general and can be used to extract information needed by
various LS functions (e.g. autocompletion).

This commit effectively reverts faa81dc, returning the original code to the
VE implementation as the Ivy code is now diverged.

PR Close #39505
2020-11-02 10:29:50 -08:00

124 lines
4.1 KiB
TypeScript

/**
* @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
*/
import * as ng from '@angular/compiler';
import * as ts from 'typescript';
import {getClassDeclFromDecoratorProp, getDirectiveClassLike} from '../src/ts_utils';
import {getPathToNodeAtPosition} from '../src/utils';
import {MockTypescriptHost} from './test_utils';
describe('getDirectiveClassLike', () => {
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();
const {decoratorId, classId} = result!;
expect(decoratorId.kind).toBe(ts.SyntaxKind.Identifier);
expect(decoratorId.text).toBe('NgModule');
expect(classId.text).toBe('AppModule');
});
});
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);
});
it('should capture element', () => {
// Try to get a path to an element
// <|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);
});
it('should capture attribute', () => {
// Try to get a path to an attribute
// <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);
});
it('should capture attribute before cursor', () => {
// Try to get a path to an attribute
// <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);
});
});
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');
});
});