/** * @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 {initMockFileSystem} from '@angular/compiler-cli/src/ngtsc/file_system/testing'; import {FixIdForCodeFixesAll} from '../src/codefixes/utils'; import {createModuleAndProjectWithDeclarations, LanguageServiceTestEnv} from '../testing'; describe('code fixes', () => { let env: LanguageServiceTestEnv; beforeEach(() => { initMockFileSystem('Native'); env = LanguageServiceTestEnv.setup(); }); it('should fix error when property does not exist on type', () => { const files = { 'app.ts': ` import {Component, NgModule} from '@angular/core'; @Component({ templateUrl: './app.html' }) export class AppComponent { title1 = ''; } `, 'app.html': `{{title}}` }; const project = createModuleAndProjectWithDeclarations(env, 'test', files); const diags = project.getDiagnosticsForFile('app.html'); const appFile = project.openFile('app.html'); appFile.moveCursorToText('title¦'); const codeActions = project.getCodeFixesAtPosition('app.html', appFile.cursor, appFile.cursor, [diags[0].code]); expectIncludeReplacementText({ codeActions, content: appFile.contents, text: 'title', newText: 'title1', fileName: 'app.html' }); const appTsFile = project.openFile('app.ts'); appTsFile.moveCursorToText(`title1 = '';\n¦`); expectIncludeAddText( {codeActions, position: appTsFile.cursor, text: 'title: any;\n', fileName: 'app.ts'}); }); it('should fix a missing method when property does not exist on type', () => { const files = { 'app.ts': ` import {Component, NgModule} from '@angular/core'; @Component({ templateUrl: './app.html' }) export class AppComponent { } `, 'app.html': `{{title('Angular')}}` }; const project = createModuleAndProjectWithDeclarations(env, 'test', files); const diags = project.getDiagnosticsForFile('app.html'); const appFile = project.openFile('app.html'); appFile.moveCursorToText('title¦'); const codeActions = project.getCodeFixesAtPosition('app.html', appFile.cursor, appFile.cursor, [diags[0].code]); const appTsFile = project.openFile('app.ts'); appTsFile.moveCursorToText(`class AppComponent {¦`); expectIncludeAddText({ codeActions, position: appTsFile.cursor, text: `\ntitle(arg0: string) {\nthrow new Error('Method not implemented.');\n}`, fileName: 'app.ts' }); }); it('should not show fix all errors when there is only one diagnostic in the template but has two or more diagnostics in TCB', () => { const files = { 'app.ts': ` import {Component, NgModule} from '@angular/core'; @Component({ templateUrl: './app.html' }) export class AppComponent { title1 = ''; } `, 'app.html': `
` }; const project = createModuleAndProjectWithDeclarations(env, 'test', files); const diags = project.getDiagnosticsForFile('app.html'); const appFile = project.openFile('app.html'); appFile.moveCursorToText('title¦'); const codeActions = project.getCodeFixesAtPosition( 'app.html', appFile.cursor, appFile.cursor, [diags[0].code]); expectNotIncludeFixAllInfo(codeActions); }); it('should fix all errors when property does not exist on type', () => { const files = { 'app.ts': ` import {Component, NgModule} from '@angular/core'; @Component({ template: '{{tite}}{{bannr}}', }) export class AppComponent { title = ''; banner = ''; } `, }; const project = createModuleAndProjectWithDeclarations(env, 'test', files); const appFile = project.openFile('app.ts'); const fixesAllSpelling = project.getCombinedCodeFix('app.ts', 'fixSpelling' as string); expectIncludeReplacementTextForFileTextChange({ fileTextChanges: fixesAllSpelling.changes, content: appFile.contents, text: 'tite', newText: 'title', fileName: 'app.ts' }); expectIncludeReplacementTextForFileTextChange({ fileTextChanges: fixesAllSpelling.changes, content: appFile.contents, text: 'bannr', newText: 'banner', fileName: 'app.ts' }); const fixAllMissingMember = project.getCombinedCodeFix('app.ts', 'fixMissingMember' as string); appFile.moveCursorToText(`banner = '';\n¦`); expectIncludeAddTextForFileTextChange({ fileTextChanges: fixAllMissingMember.changes, position: appFile.cursor, text: 'tite: any;\n', fileName: 'app.ts' }); expectIncludeAddTextForFileTextChange({ fileTextChanges: fixAllMissingMember.changes, position: appFile.cursor, text: 'bannr: any;\n', fileName: 'app.ts' }); }); it('should fix invalid banana-in-box error', () => { const files = { 'app.ts': ` import {Component, NgModule} from '@angular/core'; @Component({ templateUrl: './app.html' }) export class AppComponent { title = ''; } `, 'app.html': ``, }; const project = createModuleAndProjectWithDeclarations(env, 'test', files); const diags = project.getDiagnosticsForFile('app.html'); const appFile = project.openFile('app.html'); appFile.moveCursorToText('¦([ngModel'); const codeActions = project.getCodeFixesAtPosition('app.html', appFile.cursor, appFile.cursor, [diags[0].code]); expectIncludeReplacementText({ codeActions, content: appFile.contents, text: `([ngModel])="title"`, newText: `[(ngModel)]="title"`, fileName: 'app.html', description: `fix invalid banana-in-box for '([ngModel])="title"'` }); }); it('should fix all invalid banana-in-box errors', () => { const files = { 'app.ts': ` import {Component, NgModule} from '@angular/core'; @Component({ template: '', }) export class AppComponent { title = ''; banner = ''; } `, }; const project = createModuleAndProjectWithDeclarations(env, 'test', files); const appFile = project.openFile('app.ts'); const fixesAllActions = project.getCombinedCodeFix('app.ts', FixIdForCodeFixesAll.FIX_INVALID_BANANA_IN_BOX); expectIncludeReplacementTextForFileTextChange({ fileTextChanges: fixesAllActions.changes, content: appFile.contents, text: `([ngModel])="title"`, newText: `[(ngModel)]="title"`, fileName: 'app.ts' }); expectIncludeReplacementTextForFileTextChange({ fileTextChanges: fixesAllActions.changes, content: appFile.contents, text: `([value])="title"`, newText: `[(value)]="title"`, fileName: 'app.ts' }); }); describe('should fix missing selector imports', () => { const files = {}; const standaloneFiles = { 'foo.ts': ` import {CommonModule} from '@angular/common'; import {Component} from '@angular/core'; @Component({ selector: 'foo', template: '