test(language-service): Completions test should reuse existing host and services (#33478)

Reusing the single instance of MockHost and language services makes the
tests run much faster.

PR Close #33478
This commit is contained in:
Keen Yee Liau 2019-10-29 16:32:18 -07:00 committed by Andrew Kushnir
parent 300d7ca6da
commit 31dccab2da
1 changed files with 169 additions and 177 deletions

View File

@ -9,7 +9,7 @@
import * as ts from 'typescript'; import * as ts from 'typescript';
import {createLanguageService} from '../src/language_service'; import {createLanguageService} from '../src/language_service';
import {CompletionKind, LanguageService} from '../src/types'; import {CompletionKind} from '../src/types';
import {TypeScriptServiceHost} from '../src/typescript_host'; import {TypeScriptServiceHost} from '../src/typescript_host';
import {MockTypescriptHost} from './test_utils'; import {MockTypescriptHost} from './test_utils';
@ -25,6 +25,8 @@ describe('completions', () => {
const ngHost = new TypeScriptServiceHost(mockHost, tsLS); const ngHost = new TypeScriptServiceHost(mockHost, tsLS);
const ngLS = createLanguageService(ngHost); const ngLS = createLanguageService(ngHost);
beforeEach(() => { mockHost.reset(); });
it('should be able to get entity completions', () => { it('should be able to get entity completions', () => {
const marker = mockHost.getLocationMarkerFor(APP_COMPONENT, 'entity-amp'); const marker = mockHost.getLocationMarkerFor(APP_COMPONENT, 'entity-amp');
const completions = ngLS.getCompletionsAt(APP_COMPONENT, marker.start); const completions = ngLS.getCompletionsAt(APP_COMPONENT, marker.start);
@ -370,192 +372,182 @@ describe('completions', () => {
// expectContain(completions, CompletionKind.PROPERTY, ['innerText']); // expectContain(completions, CompletionKind.PROPERTY, ['innerText']);
// }); // });
}); });
});
describe('replace completions correctly', () => { describe('replacement span', () => {
const mockHost = new MockTypescriptHost(['/app/main.ts']); it('should not generate replacement entries for zero-length replacements', () => {
let ngLS: LanguageService; const fileName = mockHost.addCode(`
@Component({
selector: 'foo-component',
template: \`
<div>{{obj.~{key}}}</div>
\`,
})
export class FooComponent {
obj: {key: 'value'};
}
`);
const location = mockHost.getLocationMarkerFor(fileName, 'key');
const completions = ngLS.getCompletionsAt(fileName, location.start) !;
expect(completions).toBeDefined();
const completion = completions.entries.find(entry => entry.name === 'key') !;
expect(completion).toBeDefined();
expect(completion.kind).toBe('property');
expect(completion.replacementSpan).toBeUndefined();
});
beforeEach(() => { it('should work for start of template', () => {
mockHost.reset(); const fileName = mockHost.addCode(`
const tsLS = ts.createLanguageService(mockHost); @Component({
const ngHost = new TypeScriptServiceHost(mockHost, tsLS); selector: 'foo-component',
ngLS = createLanguageService(ngHost); template: \`~{start}abc\`,
}); })
export class FooComponent {}
`);
const location = mockHost.getLocationMarkerFor(fileName, 'start');
const completions = ngLS.getCompletionsAt(fileName, location.start) !;
expect(completions).toBeDefined();
const completion = completions.entries.find(entry => entry.name === 'acronym') !;
expect(completion).toBeDefined();
expect(completion.kind).toBe('html element');
expect(completion.replacementSpan).toEqual({start: location.start, length: 3});
});
it('should not generate replacement entries for zero-length replacements', () => { it('should work for end of template', () => {
const fileName = mockHost.addCode(` const fileName = mockHost.addCode(`
@Component({ @Component({
selector: 'foo-component', selector: 'foo-component',
template: \` template: \`acro~{end}\`,
<div>{{obj.~{key}}}</div> })
\`, export class FooComponent {}
}) `);
export class FooComponent { const location = mockHost.getLocationMarkerFor(fileName, 'end');
obj: {key: 'value'}; const completions = ngLS.getCompletionsAt(fileName, location.start) !;
} expect(completions).toBeDefined();
`); const completion = completions.entries.find(entry => entry.name === 'acronym') !;
const location = mockHost.getLocationMarkerFor(fileName, 'key'); expect(completion).toBeDefined();
const completions = ngLS.getCompletionsAt(fileName, location.start) !; expect(completion.kind).toBe('html element');
expect(completions).toBeDefined(); expect(completion.replacementSpan).toEqual({start: location.start - 4, length: 4});
const completion = completions.entries.find(entry => entry.name === 'key') !; });
expect(completion).toBeDefined();
expect(completion.kind).toBe('property');
expect(completion.replacementSpan).toBeUndefined();
});
it('should work for start of template', () => { it('should work for middle-word replacements', () => {
const fileName = mockHost.addCode(` const fileName = mockHost.addCode(`
@Component({ @Component({
selector: 'foo-component', selector: 'foo-component',
template: \`~{start}abc\`, template: \`
}) <div>{{obj.ke~{key}key}}</div>
export class FooComponent {} \`,
`); })
const location = mockHost.getLocationMarkerFor(fileName, 'start'); export class FooComponent {
const completions = ngLS.getCompletionsAt(fileName, location.start) !; obj: {key: 'value'};
expect(completions).toBeDefined(); }
const completion = completions.entries.find(entry => entry.name === 'acronym') !; `);
expect(completion).toBeDefined(); const location = mockHost.getLocationMarkerFor(fileName, 'key');
expect(completion.kind).toBe('html element'); const completions = ngLS.getCompletionsAt(fileName, location.start) !;
expect(completion.replacementSpan).toEqual({start: location.start, length: 3}); expect(completions).toBeDefined();
}); const completion = completions.entries.find(entry => entry.name === 'key') !;
expect(completion).toBeDefined();
expect(completion.kind).toBe('property');
expect(completion.replacementSpan).toEqual({start: location.start - 2, length: 5});
});
it('should work for end of template', () => { it('should work for all kinds of identifier characters', () => {
const fileName = mockHost.addCode(` const fileName = mockHost.addCode(`
@Component({ @Component({
selector: 'foo-component', selector: 'foo-component',
template: \`acro~{end}\`, template: \`
}) <div>{{~{field}$title_1}}</div>
export class FooComponent {} \`,
`); })
const location = mockHost.getLocationMarkerFor(fileName, 'end'); export class FooComponent {
const completions = ngLS.getCompletionsAt(fileName, location.start) !; $title_1: string;
expect(completions).toBeDefined(); }
const completion = completions.entries.find(entry => entry.name === 'acronym') !; `);
expect(completion).toBeDefined(); const location = mockHost.getLocationMarkerFor(fileName, 'field');
expect(completion.kind).toBe('html element'); const completions = ngLS.getCompletionsAt(fileName, location.start) !;
expect(completion.replacementSpan).toEqual({start: location.start - 4, length: 4}); expect(completions).toBeDefined();
}); const completion = completions.entries.find(entry => entry.name === '$title_1') !;
expect(completion).toBeDefined();
expect(completion.kind).toBe('property');
expect(completion.replacementSpan).toEqual({start: location.start, length: 8});
});
it('should work for middle-word replacements', () => { it('should work for attributes', () => {
const fileName = mockHost.addCode(` const fileName = mockHost.addCode(`
@Component({ @Component({
selector: 'foo-component', selector: 'foo-component',
template: \` template: \`
<div>{{obj.ke~{key}key}}</div> <div cl~{click}></div>
\`, \`,
}) })
export class FooComponent { export class FooComponent {}
obj: {key: 'value'}; `);
} const location = mockHost.getLocationMarkerFor(fileName, 'click');
`); const completions = ngLS.getCompletionsAt(fileName, location.start) !;
const location = mockHost.getLocationMarkerFor(fileName, 'key'); expect(completions).toBeDefined();
const completions = ngLS.getCompletionsAt(fileName, location.start) !; const completion = completions.entries.find(entry => entry.name === '(click)') !;
expect(completions).toBeDefined(); expect(completion).toBeDefined();
const completion = completions.entries.find(entry => entry.name === 'key') !; expect(completion.kind).toBe('attribute');
expect(completion).toBeDefined(); expect(completion.replacementSpan).toEqual({start: location.start - 2, length: 2});
expect(completion.kind).toBe('property'); });
expect(completion.replacementSpan).toEqual({start: location.start - 2, length: 5});
});
it('should work for all kinds of identifier characters', () => { it('should work for events', () => {
const fileName = mockHost.addCode(` const fileName = mockHost.addCode(`
@Component({ @Component({
selector: 'foo-component', selector: 'foo-component',
template: \` template: \`
<div>{{~{field}$title_1}}</div> <div (click)="han~{handleClick}"></div>
\`, \`,
}) })
export class FooComponent { export class FooComponent {
$title_1: string; handleClick() {}
} }
`); `);
const location = mockHost.getLocationMarkerFor(fileName, 'field'); const location = mockHost.getLocationMarkerFor(fileName, 'handleClick');
const completions = ngLS.getCompletionsAt(fileName, location.start) !; const completions = ngLS.getCompletionsAt(fileName, location.start) !;
expect(completions).toBeDefined(); expect(completions).toBeDefined();
const completion = completions.entries.find(entry => entry.name === '$title_1') !; const completion = completions.entries.find(entry => entry.name === 'handleClick') !;
expect(completion).toBeDefined(); expect(completion).toBeDefined();
expect(completion.kind).toBe('property'); expect(completion.kind).toBe('method');
expect(completion.replacementSpan).toEqual({start: location.start, length: 8}); expect(completion.replacementSpan).toEqual({start: location.start - 3, length: 3});
}); });
it('should work for attributes', () => { it('should work for element names', () => {
const fileName = mockHost.addCode(` const fileName = mockHost.addCode(`
@Component({ @Component({
selector: 'foo-component', selector: 'foo-component',
template: \` template: \`
<div cl~{click}></div> <di~{div}></div>
\`, \`,
}) })
export class FooComponent {} export class FooComponent {}
`); `);
const location = mockHost.getLocationMarkerFor(fileName, 'click'); const location = mockHost.getLocationMarkerFor(fileName, 'div');
const completions = ngLS.getCompletionsAt(fileName, location.start) !; const completions = ngLS.getCompletionsAt(fileName, location.start) !;
expect(completions).toBeDefined(); expect(completions).toBeDefined();
const completion = completions.entries.find(entry => entry.name === '(click)') !; const completion = completions.entries.find(entry => entry.name === 'div') !;
expect(completion).toBeDefined(); expect(completion).toBeDefined();
expect(completion.kind).toBe('attribute'); expect(completion.kind).toBe('html element');
expect(completion.replacementSpan).toEqual({start: location.start - 2, length: 2}); expect(completion.replacementSpan).toEqual({start: location.start - 2, length: 2});
}); });
it('should work for events', () => { it('should work for bindings', () => {
const fileName = mockHost.addCode(` const fileName = mockHost.addCode(`
@Component({ @Component({
selector: 'foo-component', selector: 'foo-component',
template: \` template: \`
<div (click)="han~{handleClick}"></div>
\`,
})
export class FooComponent {
handleClick() {}
}
`);
const location = mockHost.getLocationMarkerFor(fileName, 'handleClick');
const completions = ngLS.getCompletionsAt(fileName, location.start) !;
expect(completions).toBeDefined();
const completion = completions.entries.find(entry => entry.name === 'handleClick') !;
expect(completion).toBeDefined();
expect(completion.kind).toBe('method');
expect(completion.replacementSpan).toEqual({start: location.start - 3, length: 3});
});
it('should work for element names', () => {
const fileName = mockHost.addCode(`
@Component({
selector: 'foo-component',
template: \`
<di~{div}></div>
\`,
})
export class FooComponent {}
`);
const location = mockHost.getLocationMarkerFor(fileName, 'div');
const completions = ngLS.getCompletionsAt(fileName, location.start) !;
expect(completions).toBeDefined();
const completion = completions.entries.find(entry => entry.name === 'div') !;
expect(completion).toBeDefined();
expect(completion.kind).toBe('html element');
expect(completion.replacementSpan).toEqual({start: location.start - 2, length: 2});
});
it('should work for bindings', () => {
const fileName = mockHost.addCode(`
@Component({
selector: 'foo-component',
template: \`
<input ngMod~{model} /> <input ngMod~{model} />
\`, \`,
}) })
export class FooComponent {} export class FooComponent {}
`); `);
const location = mockHost.getLocationMarkerFor(fileName, 'model'); const location = mockHost.getLocationMarkerFor(fileName, 'model');
const completions = ngLS.getCompletionsAt(fileName, location.start) !; const completions = ngLS.getCompletionsAt(fileName, location.start) !;
expect(completions).toBeDefined(); expect(completions).toBeDefined();
const completion = completions.entries.find(entry => entry.name === '[(ngModel)]') !; const completion = completions.entries.find(entry => entry.name === '[(ngModel)]') !;
expect(completion).toBeDefined(); expect(completion).toBeDefined();
expect(completion.kind).toBe('attribute'); expect(completion.kind).toBe('attribute');
expect(completion.replacementSpan).toEqual({start: location.start - 5, length: 5}); expect(completion.replacementSpan).toEqual({start: location.start - 5, length: 5});
});
}); });
}); });