Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@


import {NgtscProgram} from '@angular/compiler-cli';
import {Reference, TemplateTypeChecker} from '@angular/compiler-cli/private/migrations';
import {TemplateTypeChecker} from '@angular/compiler-cli/private/migrations';
import {dirname, join} from 'path';
import ts from 'typescript';

Expand Down Expand Up @@ -45,6 +45,12 @@ export function toStandaloneBootstrap(
const testObjects = new Set<ts.ObjectLiteralExpression>();
const allDeclarations = new Set<ts.ClassDeclaration>();

// `bootstrapApplication` doesn't include Protractor support by default
// anymore so we have to opt the app in, if we detect it being used.
const additionalProviders = hasImport(program, rootFileNames, 'protractor') ?
new Map([['provideProtractorTestingSupport', '@angular/platform-browser']]) :
null;

for (const sourceFile of sourceFiles) {
sourceFile.forEachChild(function walk(node) {
if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression) &&
Expand All @@ -64,7 +70,8 @@ export function toStandaloneBootstrap(

for (const call of bootstrapCalls) {
call.declarations.forEach(decl => allDeclarations.add(decl));
migrateBootstrapCall(call, tracker, referenceResolver, typeChecker, printer);
migrateBootstrapCall(
call, tracker, additionalProviders, referenceResolver, typeChecker, printer);
}

// The previous migrations explicitly skip over bootstrapped
Expand Down Expand Up @@ -135,12 +142,15 @@ function analyzeBootstrapCall(
* Converts a `bootstrapModule` call to `bootstrapApplication`.
* @param analysis Analysis result of the call.
* @param tracker Tracker in which to register the changes.
* @param additionalFeatures Additional providers, apart from the auto-detected ones, that should
* be added to the bootstrap call.
* @param referenceResolver
* @param typeChecker
* @param printer
*/
function migrateBootstrapCall(
analysis: BootstrapCallAnalysis, tracker: ChangeTracker, referenceResolver: ReferenceResolver,
analysis: BootstrapCallAnalysis, tracker: ChangeTracker,
additionalProviders: Map<string, string>|null, referenceResolver: ReferenceResolver,
typeChecker: ts.TypeChecker, printer: ts.Printer) {
const sourceFile = analysis.call.getSourceFile();
const moduleSourceFile = analysis.metadata.getSourceFile();
Expand Down Expand Up @@ -177,6 +187,13 @@ function migrateBootstrapCall(
nodesToCopy, referenceResolver, typeChecker);
}

if (additionalProviders) {
additionalProviders.forEach((moduleSpecifier, name) => {
providersInNewCall.push(ts.factory.createCallExpression(
tracker.addImport(sourceFile, name, moduleSpecifier), undefined, undefined));
});
}

if (nodesToCopy.size > 0) {
let text = '\n\n';
nodesToCopy.forEach(node => {
Expand Down Expand Up @@ -703,3 +720,27 @@ function getLastImportEnd(sourceFile: ts.SourceFile): number {

return index;
}

/** Checks if any of the program's files has an import of a specific module. */
function hasImport(program: NgtscProgram, rootFileNames: string[], moduleName: string): boolean {
const tsProgram = program.getTsProgram();
const deepImportStart = moduleName + '/';

for (const fileName of rootFileNames) {
const sourceFile = tsProgram.getSourceFile(fileName);

if (!sourceFile) {
continue;
}

for (const statement of sourceFile.statements) {
if (ts.isImportDeclaration(statement) && ts.isStringLiteralLike(statement.moduleSpecifier) &&
(statement.moduleSpecifier.text === moduleName ||
statement.moduleSpecifier.text.startsWith(deepImportStart))) {
return true;
}
}
}

return false;
}
90 changes: 90 additions & 0 deletions packages/core/schematics/test/standalone_migration_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3647,4 +3647,94 @@ describe('standalone migration', () => {
}).catch(e => console.error(e));
`));
});

it('should add Protractor support if any tests are detected', async () => {
writeFile('main.ts', `
import {AppModule} from './app/app.module';
import {platformBrowser} from '@angular/platform-browser';

platformBrowser().bootstrapModule(AppModule).catch(e => console.error(e));
`);

writeFile('./app/app.module.ts', `
import {NgModule, Component} from '@angular/core';

@Component({selector: 'app', template: 'hello'})
export class AppComponent {}

@NgModule({declarations: [AppComponent], bootstrap: [AppComponent]})
export class AppModule {}
`);

writeFile('./app/app.e2e.spec.ts', `
import {browser, by, element} from 'protractor';

describe('app', () => {
beforeAll(async () => {
await browser.get(browser.params.testUrl);
});

it('should work', async () => {
const rootSelector = element(by.css('app'));
expect(await rootSelector.isPresent()).toBe(true);
});
});
`);

await runMigration('standalone-bootstrap');

expect(stripWhitespace(tree.readContent('main.ts'))).toBe(stripWhitespace(`
import {AppComponent} from './app/app.module';
import {platformBrowser, provideProtractorTestingSupport, bootstrapApplication} from '@angular/platform-browser';

bootstrapApplication(AppComponent, {
providers: [provideProtractorTestingSupport()]
}).catch(e => console.error(e));
`));
});

it('should add Protractor support if any tests with deep imports are detected', async () => {
writeFile('main.ts', `
import {AppModule} from './app/app.module';
import {platformBrowser} from '@angular/platform-browser';

platformBrowser().bootstrapModule(AppModule).catch(e => console.error(e));
`);

writeFile('./app/app.module.ts', `
import {NgModule, Component} from '@angular/core';

@Component({selector: 'app', template: 'hello'})
export class AppComponent {}

@NgModule({declarations: [AppComponent], bootstrap: [AppComponent]})
export class AppModule {}
`);

writeFile('./app/app.e2e.spec.ts', `
import {browser, by, element} from 'protractor/some/deep-import';

describe('app', () => {
beforeAll(async () => {
await browser.get(browser.params.testUrl);
});

it('should work', async () => {
const rootSelector = element(by.css('app'));
expect(await rootSelector.isPresent()).toBe(true);
});
});
`);

await runMigration('standalone-bootstrap');

expect(stripWhitespace(tree.readContent('main.ts'))).toBe(stripWhitespace(`
import {AppComponent} from './app/app.module';
import {platformBrowser, provideProtractorTestingSupport, bootstrapApplication} from '@angular/platform-browser';

bootstrapApplication(AppComponent, {
providers: [provideProtractorTestingSupport()]
}).catch(e => console.error(e));
`));
});
});