mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
feat(core): add migration to opt out existing apps from new test module teardown behavior (#43353)
Since the `destroyAfterEach` teardown behavior is enabled by default now, existing tests that depended on the old behavior can start to fail. These changes add an automated migration that explicitly adds `destroyAfterEach: false` to existing tests. The migration works by looking for `initTestEnvironment` calls across the entire app and adding the flag to them. If no calls were found, the migration will add the flag to all `configureTestingModule` and `withModule` calls instead. PR Close #43353
This commit is contained in:
committed by
Dylan Hunn
parent
94ba59bc9d
commit
81c7eb813c
@@ -30,6 +30,7 @@ pkg_npm(
|
||||
"//packages/core/schematics/migrations/router-preserve-query-params",
|
||||
"//packages/core/schematics/migrations/static-queries",
|
||||
"//packages/core/schematics/migrations/template-var-assignment",
|
||||
"//packages/core/schematics/migrations/testbed-teardown",
|
||||
"//packages/core/schematics/migrations/undecorated-classes-with-decorated-fields",
|
||||
"//packages/core/schematics/migrations/undecorated-classes-with-di",
|
||||
"//packages/core/schematics/migrations/wait-for-async",
|
||||
|
||||
@@ -104,6 +104,11 @@
|
||||
"version": "13.0.0-beta",
|
||||
"description": "Migrates `[routerLink]=\"\"` in templates to `[routerLink]=\"[]\"` because these links are likely intended to route to the current page with updated fragment/query params.",
|
||||
"factory": "./migrations/router-link-empty-expression/index"
|
||||
},
|
||||
"migration-v13-testbed-teardown": {
|
||||
"version": "13.0.0-beta",
|
||||
"description": "In Angular version 13, the `teardown` flag in `TestBed` will be enabled by default. This migration automatically opts out existing apps from the new teardown behavior.",
|
||||
"factory": "./migrations/testbed-teardown/index"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ ts_library(
|
||||
"//packages/core/schematics/migrations/renderer-to-renderer2",
|
||||
"//packages/core/schematics/migrations/static-queries",
|
||||
"//packages/core/schematics/migrations/template-var-assignment",
|
||||
"//packages/core/schematics/migrations/testbed-teardown",
|
||||
"//packages/core/schematics/migrations/undecorated-classes-with-decorated-fields",
|
||||
"//packages/core/schematics/migrations/undecorated-classes-with-decorated-fields/google3",
|
||||
"//packages/core/schematics/migrations/wait-for-async",
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
/**
|
||||
* @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 {Replacement, RuleFailure, Rules} from 'tslint';
|
||||
import ts from 'typescript';
|
||||
import {findInitTestEnvironmentCalls, findTestModuleMetadataNodes, InitTestEnvironmentAnalysis, migrateInitTestEnvironment, migrateTestModuleMetadataLiteral} from '../testbed-teardown/util';
|
||||
|
||||
/** TSLint rule that adds the `teardown` flag to `TestBed` calls. */
|
||||
export class Rule extends Rules.TypedRule {
|
||||
private _analysis = new Map<ts.Program, InitTestEnvironmentAnalysis>();
|
||||
|
||||
override applyWithProgram(sourceFile: ts.SourceFile, program: ts.Program): RuleFailure[] {
|
||||
const typeChecker = program.getTypeChecker();
|
||||
const printer = ts.createPrinter();
|
||||
let initTestEnvironmentResult = this._analysis.get(program);
|
||||
|
||||
// The analysis for `initTestEnvironment` calls only needs to run once per program.
|
||||
if (!initTestEnvironmentResult) {
|
||||
const sourceFiles = program.getSourceFiles().filter(
|
||||
s => !s.isDeclarationFile && !program.isSourceFileFromExternalLibrary(s));
|
||||
initTestEnvironmentResult = findInitTestEnvironmentCalls(typeChecker, sourceFiles);
|
||||
this._analysis.set(program, initTestEnvironmentResult);
|
||||
}
|
||||
|
||||
return this._migrateFile(typeChecker, printer, sourceFile, initTestEnvironmentResult);
|
||||
}
|
||||
|
||||
private _migrateFile(
|
||||
typeChecker: ts.TypeChecker, printer: ts.Printer, sourceFile: ts.SourceFile,
|
||||
initTestEnvironmentResult: InitTestEnvironmentAnalysis): RuleFailure[] {
|
||||
const failures: RuleFailure[] = [];
|
||||
|
||||
// If we identified at least one call to `initTestEnvironment` (can be migrated or unmigrated),
|
||||
// we don't need to migrate `configureTestingModule` or `withModule` calls, because they'll take
|
||||
// the default teardown behavior from the environment. This is preferrable, because it'll result
|
||||
// in the least number of changes to users' code.
|
||||
if (initTestEnvironmentResult.totalCalls > 0) {
|
||||
// Migrate all of the unmigrated calls `initTestEnvironment` in this file. This could be zero
|
||||
// if the user has already opted into the new teardown behavior themselves.
|
||||
initTestEnvironmentResult.callsToMigrate.forEach(call => {
|
||||
// This analysis is global so we need to check that the call is within this file.
|
||||
if (call.getSourceFile() === sourceFile) {
|
||||
failures.push(this._getFailure(call, migrateInitTestEnvironment, printer));
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Otherwise migrate the metadata passed into the `configureTestingModule` and `withModule`
|
||||
// calls. This scenario is less likely, but it could happen if `initTestEnvironment` has been
|
||||
// abstracted away or is inside a .js file.
|
||||
findTestModuleMetadataNodes(typeChecker, sourceFile).forEach(literal => {
|
||||
failures.push(this._getFailure(literal, migrateTestModuleMetadataLiteral, printer));
|
||||
});
|
||||
}
|
||||
|
||||
return failures;
|
||||
}
|
||||
|
||||
private _getFailure<T extends ts.Node>(node: T, migrator: (node: T) => T, printer: ts.Printer) {
|
||||
const sourceFile = node.getSourceFile();
|
||||
const migrated = migrator(node);
|
||||
const replacementText = printer.printNode(ts.EmitHint.Unspecified, migrated, sourceFile);
|
||||
|
||||
return new RuleFailure(
|
||||
sourceFile, node.getStart(), node.getEnd(), 'Teardown behavior has to be configured.',
|
||||
this.ruleName, new Replacement(node.getStart(), node.getWidth(), replacementText));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
load("//tools:defaults.bzl", "ts_library")
|
||||
|
||||
ts_library(
|
||||
name = "testbed-teardown",
|
||||
srcs = glob(["**/*.ts"]),
|
||||
tsconfig = "//packages/core/schematics:tsconfig.json",
|
||||
visibility = [
|
||||
"//packages/core/schematics:__pkg__",
|
||||
"//packages/core/schematics/migrations/google3:__pkg__",
|
||||
"//packages/core/schematics/test:__pkg__",
|
||||
],
|
||||
deps = [
|
||||
"//packages/core/schematics/utils",
|
||||
"@npm//@angular-devkit/schematics",
|
||||
"@npm//@types/node",
|
||||
"@npm//typescript",
|
||||
],
|
||||
)
|
||||
@@ -0,0 +1,32 @@
|
||||
## TestBed teardown behavior migration
|
||||
|
||||
In Angular version 13, the `teardown` flag in `TestBed` will be enabled by default. This migration
|
||||
automatically opts out existing apps from the new teardown behavior. It works by looking through
|
||||
the entire project for `initTestEnvironment` calls and adding the `teardown` flag to them.
|
||||
|
||||
If no `initTestEnvironment` calls were found, the migration will add the `teardown` flag to all
|
||||
`configureTestingModule` and `withModule` calls instead.
|
||||
|
||||
#### Before
|
||||
```ts
|
||||
import { getTestBed } from '@angular/core/testing';
|
||||
import {
|
||||
BrowserDynamicTestingModule,
|
||||
platformBrowserDynamicTesting
|
||||
} from '@angular/platform-browser-dynamic/testing';
|
||||
|
||||
getTestBed().initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting());
|
||||
```
|
||||
|
||||
#### After
|
||||
```ts
|
||||
import { getTestBed } from '@angular/core/testing';
|
||||
import {
|
||||
BrowserDynamicTestingModule,
|
||||
platformBrowserDynamicTesting
|
||||
} from '@angular/platform-browser-dynamic/testing';
|
||||
|
||||
getTestBed().initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting(), {
|
||||
teardown: { destroyAfterEach: false }
|
||||
});
|
||||
```
|
||||
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
* @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 {Rule, SchematicsException, Tree} from '@angular-devkit/schematics';
|
||||
import {relative} from 'path';
|
||||
import ts from 'typescript';
|
||||
|
||||
import {getProjectTsConfigPaths} from '../../utils/project_tsconfig_paths';
|
||||
import {canMigrateFile, createMigrationProgram} from '../../utils/typescript/compiler_host';
|
||||
import {findInitTestEnvironmentCalls, findTestModuleMetadataNodes, migrateInitTestEnvironment, migrateTestModuleMetadataLiteral} from './util';
|
||||
|
||||
|
||||
/** Migration that adds the `teardown` flag to `TestBed` calls. */
|
||||
export default function(): Rule {
|
||||
return async (tree: Tree) => {
|
||||
const {buildPaths, testPaths} = await getProjectTsConfigPaths(tree);
|
||||
const basePath = process.cwd();
|
||||
const allPaths = [...buildPaths, ...testPaths];
|
||||
|
||||
if (!allPaths.length) {
|
||||
throw new SchematicsException(
|
||||
'Could not find any tsconfig file. Cannot add `teardown` flag to `TestBed`.');
|
||||
}
|
||||
|
||||
for (const tsconfigPath of allPaths) {
|
||||
runTestbedTeardownMigration(tree, tsconfigPath, basePath);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function runTestbedTeardownMigration(tree: Tree, tsconfigPath: string, basePath: string) {
|
||||
const {program} = createMigrationProgram(tree, tsconfigPath, basePath);
|
||||
const typeChecker = program.getTypeChecker();
|
||||
const sourceFiles =
|
||||
program.getSourceFiles().filter(sourceFile => canMigrateFile(basePath, sourceFile, program));
|
||||
const initTestEnvironmentResult = findInitTestEnvironmentCalls(typeChecker, sourceFiles);
|
||||
const printer = ts.createPrinter();
|
||||
|
||||
// If we identified at least one call to `initTestEnvironment` (can be migrated or unmigrated),
|
||||
// we don't need to migrate `configureTestingModule` or `withModule` calls, because they'll take
|
||||
// the default teardown behavior from the environment. This is preferrable, because it'll result
|
||||
// in the least number of changes to users' code.
|
||||
if (initTestEnvironmentResult.totalCalls > 0) {
|
||||
// Migrate all of the unmigrated calls `initTestEnvironment`. This could be zero
|
||||
// if the user has already opted into the new teardown behavior themselves.
|
||||
initTestEnvironmentResult.callsToMigrate.forEach(call => {
|
||||
migrate(call, migrateInitTestEnvironment, tree, basePath, printer);
|
||||
});
|
||||
} else {
|
||||
// Otherwise migrate the metadata passed into the `configureTestingModule` and `withModule`
|
||||
// calls. This scenario is less likely, but it could happen if `initTestEnvironment` has been
|
||||
// abstracted away or is inside a .js file.
|
||||
sourceFiles.forEach(sourceFile => {
|
||||
findTestModuleMetadataNodes(typeChecker, sourceFile).forEach(literal => {
|
||||
migrate(literal, migrateTestModuleMetadataLiteral, tree, basePath, printer);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function migrate<T extends ts.Node>(
|
||||
node: T, migrator: (node: T) => T, tree: Tree, basePath: string, printer: ts.Printer) {
|
||||
const migrated = migrator(node);
|
||||
const update = tree.beginUpdate(relative(basePath, node.getSourceFile().fileName));
|
||||
update.remove(node.getStart(), node.getWidth());
|
||||
update.insertRight(
|
||||
node.getStart(), printer.printNode(ts.EmitHint.Unspecified, migrated, node.getSourceFile()));
|
||||
tree.commitUpdate(update);
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
/**
|
||||
* @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 ts from 'typescript';
|
||||
|
||||
import {getImportSpecifier} from '../../utils/typescript/imports';
|
||||
import {isReferenceToImport} from '../../utils/typescript/symbol';
|
||||
|
||||
/** Result of a full-program analysis looking for `initTestEnvironment` calls. */
|
||||
export interface InitTestEnvironmentAnalysis {
|
||||
/** Total number of calls that were found. */
|
||||
totalCalls: number;
|
||||
/** Calls that need to be migrated. */
|
||||
callsToMigrate: ts.CallExpression[];
|
||||
}
|
||||
|
||||
/** Finds the `initTestEnvironment` calls that need to be migrated. */
|
||||
export function findInitTestEnvironmentCalls(
|
||||
typeChecker: ts.TypeChecker, allSourceFiles: ts.SourceFile[]): InitTestEnvironmentAnalysis {
|
||||
const callsToMigrate = new Set<ts.CallExpression>();
|
||||
let totalCalls = 0;
|
||||
|
||||
allSourceFiles.forEach(sourceFile => {
|
||||
sourceFile.forEachChild(function walk(node: ts.Node) {
|
||||
if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression) &&
|
||||
ts.isIdentifier(node.expression.name) &&
|
||||
node.expression.name.text === 'initTestEnvironment' &&
|
||||
isTestBedAccess(typeChecker, node.expression)) {
|
||||
totalCalls++;
|
||||
if (shouldMigrateInitTestEnvironment(node)) {
|
||||
callsToMigrate.add(node);
|
||||
}
|
||||
}
|
||||
|
||||
node.forEachChild(walk);
|
||||
});
|
||||
});
|
||||
|
||||
return {
|
||||
// Sort the nodes so that they will be migrated in reverse source order (nodes at the end of
|
||||
// the file are migrated first). This avoids issues where a migrated node will offset the
|
||||
// bounds of all nodes that come after it. Note that the nodes here are from all of the
|
||||
// passed in source files, but that doesn't matter since the later nodes will still appear
|
||||
// after the earlier ones.
|
||||
callsToMigrate: sortInReverseSourceOrder(Array.from(callsToMigrate)),
|
||||
totalCalls
|
||||
};
|
||||
}
|
||||
|
||||
/** Finds the `configureTestingModule` and `withModule` calls that need to be migrated. */
|
||||
export function findTestModuleMetadataNodes(
|
||||
typeChecker: ts.TypeChecker, sourceFile: ts.SourceFile) {
|
||||
const testModuleMetadataLiterals = new Set<ts.ObjectLiteralExpression>();
|
||||
const withModuleImport = getImportSpecifier(sourceFile, '@angular/core/testing', 'withModule');
|
||||
|
||||
sourceFile.forEachChild(function walk(node: ts.Node) {
|
||||
if (ts.isCallExpression(node)) {
|
||||
const isConfigureTestingModuleCall = ts.isPropertyAccessExpression(node.expression) &&
|
||||
ts.isIdentifier(node.expression.name) &&
|
||||
node.expression.name.text === 'configureTestingModule' &&
|
||||
isTestBedAccess(typeChecker, node.expression) && shouldMigrateModuleConfigCall(node);
|
||||
const isWithModuleCall = withModuleImport && ts.isIdentifier(node.expression) &&
|
||||
isReferenceToImport(typeChecker, node.expression, withModuleImport) &&
|
||||
shouldMigrateModuleConfigCall(node);
|
||||
|
||||
if (isConfigureTestingModuleCall || isWithModuleCall) {
|
||||
testModuleMetadataLiterals.add(node.arguments[0] as ts.ObjectLiteralExpression);
|
||||
}
|
||||
}
|
||||
|
||||
node.forEachChild(walk);
|
||||
});
|
||||
|
||||
// Sort the nodes so that they will be migrated in reverse source order (nodes at the end of
|
||||
// the file are migrated first). This avoids issues where a migrated node will offset the
|
||||
// bounds of all nodes that come after it.
|
||||
return sortInReverseSourceOrder(Array.from(testModuleMetadataLiterals));
|
||||
}
|
||||
|
||||
/** Migrates a call to `TestBed.initTestEnvironment`. */
|
||||
export function migrateInitTestEnvironment(node: ts.CallExpression): ts.CallExpression {
|
||||
const literalProperties: ts.ObjectLiteralElementLike[] = [];
|
||||
|
||||
if (node.arguments.length > 2) {
|
||||
if (isFunction(node.arguments[2])) {
|
||||
// If the last argument is a function, add the function as the `aotSummaries` property.
|
||||
literalProperties.push(ts.createPropertyAssignment('aotSummaries', node.arguments[2]));
|
||||
} else if (ts.isObjectLiteralExpression(node.arguments[2])) {
|
||||
// If the property is an object literal, copy over all the properties.
|
||||
literalProperties.push(...node.arguments[2].properties);
|
||||
}
|
||||
}
|
||||
|
||||
// Finally push the teardown object so that it appears last.
|
||||
literalProperties.push(createTeardownAssignment());
|
||||
|
||||
return ts.createCall(
|
||||
node.expression, node.typeArguments,
|
||||
[...node.arguments.slice(0, 2), ts.createObjectLiteral(literalProperties, true)]);
|
||||
}
|
||||
|
||||
/** Migrates an object literal that is passed into `configureTestingModule` or `withModule`. */
|
||||
export function migrateTestModuleMetadataLiteral(node: ts.ObjectLiteralExpression):
|
||||
ts.ObjectLiteralExpression {
|
||||
return ts.createObjectLiteral(
|
||||
[...node.properties, createTeardownAssignment()], node.properties.length > 0);
|
||||
}
|
||||
|
||||
/** Returns whether a property access points to `TestBed`. */
|
||||
function isTestBedAccess(typeChecker: ts.TypeChecker, node: ts.PropertyAccessExpression): boolean {
|
||||
const symbolName = typeChecker.getTypeAtLocation(node.expression)?.getSymbol()?.getName();
|
||||
return symbolName === 'TestBed' || symbolName === 'TestBedStatic';
|
||||
}
|
||||
|
||||
/** Whether a call to `initTestEnvironment` should be migrated. */
|
||||
function shouldMigrateInitTestEnvironment(node: ts.CallExpression): boolean {
|
||||
// If there is no third argument, we definitely have to migrate it.
|
||||
if (node.arguments.length === 2) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// This is technically a type error so we shouldn't mess with it.
|
||||
if (node.arguments.length < 2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Otherwise we need to figure out if the `teardown` flag is set on the last argument.
|
||||
const lastArg = node.arguments[2];
|
||||
|
||||
// Note: the checks below will identify something like `initTestEnvironment(..., ..., {})`,
|
||||
// but they'll ignore a variable being passed in as the last argument like `const config = {};
|
||||
// initTestEnvironment(..., ..., config)`. While we can resolve the variable to its declaration
|
||||
// using `typeChecker.getTypeAtLocation(lastArg).getSymbol()?.valueDeclaration`, we deliberately
|
||||
// don't, because it introduces some complexity and we may end up breaking user code. E.g.
|
||||
// the `config` from the example above may be passed in to other functions or the `teardown`
|
||||
// flag could be added later on by a function call.
|
||||
|
||||
// If the argument is an object literal and there are no
|
||||
// properties called `teardown`, we have to migrate it.
|
||||
if (isObjectLiteralWithoutTeardown(lastArg)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// If the last argument is an `aotSummaries` function, we also have to migrate.
|
||||
if (isFunction(lastArg)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Otherwise don't migrate if we couldn't identify the last argument.
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether a call to a module configuration function should be migrated. This covers
|
||||
* `TestBed.configureTestingModule` and `withModule` since they both accept `TestModuleMetadata`
|
||||
* as their first argument.
|
||||
*/
|
||||
function shouldMigrateModuleConfigCall(node: ts.CallExpression): node is ts.CallExpression&
|
||||
{arguments: [ts.ObjectLiteralExpression, ...ts.Expression[]]} {
|
||||
return node.arguments.length > 0 && isObjectLiteralWithoutTeardown(node.arguments[0]);
|
||||
}
|
||||
|
||||
/** Returns whether a node is a function literal. */
|
||||
function isFunction(node: ts.Node): node is ts.ArrowFunction|ts.FunctionExpression|
|
||||
ts.FunctionDeclaration {
|
||||
return ts.isArrowFunction(node) || ts.isFunctionExpression(node) ||
|
||||
ts.isFunctionDeclaration(node);
|
||||
}
|
||||
|
||||
/** Checks whether a node is an object literal that doesn't contain a property called `teardown`. */
|
||||
function isObjectLiteralWithoutTeardown(node: ts.Node): node is ts.ObjectLiteralExpression {
|
||||
return ts.isObjectLiteralExpression(node) && !node.properties.find(prop => {
|
||||
return prop.name?.getText() === 'teardown';
|
||||
});
|
||||
}
|
||||
|
||||
/** Creates a teardown configuration property assignment. */
|
||||
function createTeardownAssignment(): ts.PropertyAssignment {
|
||||
// `teardown: {destroyAfterEach: false}`
|
||||
return ts.createPropertyAssignment(
|
||||
'teardown',
|
||||
ts.createObjectLiteral([ts.createPropertyAssignment('destroyAfterEach', ts.createFalse())]));
|
||||
}
|
||||
|
||||
/** Sorts an array of AST nodes in reverse source order. */
|
||||
function sortInReverseSourceOrder<T extends ts.Node>(nodes: T[]): T[] {
|
||||
return nodes.sort((a, b) => b.getEnd() - a.getEnd());
|
||||
}
|
||||
@@ -25,6 +25,7 @@ ts_library(
|
||||
"//packages/core/schematics/migrations/router-preserve-query-params",
|
||||
"//packages/core/schematics/migrations/static-queries",
|
||||
"//packages/core/schematics/migrations/template-var-assignment",
|
||||
"//packages/core/schematics/migrations/testbed-teardown",
|
||||
"//packages/core/schematics/migrations/undecorated-classes-with-decorated-fields",
|
||||
"//packages/core/schematics/migrations/undecorated-classes-with-di",
|
||||
"//packages/core/schematics/migrations/wait-for-async",
|
||||
|
||||
@@ -0,0 +1,707 @@
|
||||
/**
|
||||
* @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 {readFileSync, writeFileSync} from 'fs';
|
||||
import {dirname, join} from 'path';
|
||||
import * as shx from 'shelljs';
|
||||
import {Configuration, Linter} from 'tslint';
|
||||
|
||||
describe('Google3 TestBed teardown TSLint rule', () => {
|
||||
const rulesDirectory = dirname(require.resolve('../../migrations/google3/testbedTeardownRule'));
|
||||
let tmpDir: string;
|
||||
|
||||
beforeEach(() => {
|
||||
tmpDir = join(process.env['TEST_TMPDIR']!, 'google3-test');
|
||||
shx.mkdir('-p', tmpDir);
|
||||
|
||||
// We need to declare the Angular symbols we're testing for, otherwise type checking won't work.
|
||||
// Note that the declarations here are a little more convoluted than they need to be. It's
|
||||
// because they've been copied over directly from `node_modules/@angular/core/testing.d.ts`,
|
||||
// except for removing some methods that are irrelevant to these tests.
|
||||
writeFile('testing.d.ts', `
|
||||
export declare const getTestBed: () => TestBed;
|
||||
|
||||
export declare interface TestBed {
|
||||
initTestEnvironment(ngModule: any, platform: any, options?: any): void;
|
||||
configureTestingModule(moduleDef: any): void;
|
||||
}
|
||||
|
||||
export declare const TestBed: TestBedStatic;
|
||||
|
||||
export declare interface TestBedStatic {
|
||||
new (...args: any[]): TestBed;
|
||||
initTestEnvironment(ngModule: any, platform: any, options?: any): TestBed;
|
||||
configureTestingModule(moduleDef: any): TestBedStatic;
|
||||
}
|
||||
|
||||
export declare function withModule(moduleDef: any, fn: Function): () => any;
|
||||
`);
|
||||
|
||||
writeFile('tsconfig.json', JSON.stringify({
|
||||
compilerOptions: {
|
||||
module: 'es2015',
|
||||
baseUrl: './',
|
||||
paths: {
|
||||
'@angular/core/testing': ['testing.d.ts'],
|
||||
}
|
||||
},
|
||||
}));
|
||||
});
|
||||
|
||||
afterEach(() => shx.rm('-r', tmpDir));
|
||||
|
||||
function runTSLint(fix: boolean) {
|
||||
const program = Linter.createProgram(join(tmpDir, 'tsconfig.json'));
|
||||
const linter = new Linter({fix, rulesDirectory: [rulesDirectory]}, program);
|
||||
const config = Configuration.parseConfigFile({rules: {'testbedTeardown': true}});
|
||||
|
||||
program.getRootFileNames().forEach(fileName => {
|
||||
linter.lint(fileName, program.getSourceFile(fileName)!.getFullText(), config);
|
||||
});
|
||||
|
||||
return linter;
|
||||
}
|
||||
|
||||
function writeFile(fileName: string, content: string) {
|
||||
writeFileSync(join(tmpDir, fileName), content);
|
||||
}
|
||||
|
||||
function getFile(fileName: string) {
|
||||
return readFileSync(join(tmpDir, fileName), 'utf8');
|
||||
}
|
||||
|
||||
|
||||
function stripWhitespace(contents: string) {
|
||||
return contents.replace(/\s/g, '');
|
||||
}
|
||||
|
||||
it('should flag initTestEnvironment calls', () => {
|
||||
writeFile('/index.ts', `
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import {
|
||||
BrowserDynamicTestingModule,
|
||||
platformBrowserDynamicTesting
|
||||
} from '@angular/platform-browser-dynamic/testing';
|
||||
|
||||
TestBed.initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting());
|
||||
`);
|
||||
|
||||
const linter = runTSLint(false);
|
||||
const failures = linter.getResult().failures.map(failure => failure.getFailure());
|
||||
expect(failures.length).toBe(1);
|
||||
expect(failures[0]).toMatch(/Teardown behavior has to be configured\./);
|
||||
});
|
||||
|
||||
it('should flag configureTestingModule and withModule calls', () => {
|
||||
writeFile('/index.ts', `
|
||||
import { Component } from '@angular/core';
|
||||
import { TestBed, withModule } from '@angular/core/testing';
|
||||
|
||||
@Component({template: ''})
|
||||
class Comp {}
|
||||
|
||||
it('should work', () => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [Comp]
|
||||
});
|
||||
|
||||
const fixture = TestBed.createComponent(Comp);
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should also work', withModule({
|
||||
declarations: [Comp],
|
||||
}, () => {
|
||||
TestBed.createComponent(Comp);
|
||||
}));
|
||||
`);
|
||||
|
||||
const linter = runTSLint(false);
|
||||
const failures = linter.getResult().failures.map(failure => failure.getFailure());
|
||||
expect(failures.length).toBe(2);
|
||||
expect(failures[0]).toMatch(/Teardown behavior has to be configured\./);
|
||||
expect(failures[1]).toMatch(/Teardown behavior has to be configured\./);
|
||||
});
|
||||
|
||||
it('should migrate calls to initTestEnvironment', () => {
|
||||
writeFile('/index.ts', `
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import {
|
||||
BrowserDynamicTestingModule,
|
||||
platformBrowserDynamicTesting
|
||||
} from '@angular/platform-browser-dynamic/testing';
|
||||
|
||||
TestBed.initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting());
|
||||
`);
|
||||
|
||||
runTSLint(true);
|
||||
|
||||
expect(stripWhitespace(getFile('/index.ts'))).toContain(stripWhitespace(`
|
||||
TestBed.initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting(), {
|
||||
teardown: { destroyAfterEach: false }
|
||||
});
|
||||
`));
|
||||
});
|
||||
|
||||
it('should migrate calls to initTestEnvironment going through getTestBed()', () => {
|
||||
writeFile('/index.ts', `
|
||||
import { getTestBed } from '@angular/core/testing';
|
||||
import {
|
||||
BrowserDynamicTestingModule,
|
||||
platformBrowserDynamicTesting
|
||||
} from '@angular/platform-browser-dynamic/testing';
|
||||
|
||||
getTestBed().initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting());
|
||||
`);
|
||||
|
||||
runTSLint(true);
|
||||
|
||||
expect(stripWhitespace(getFile('/index.ts'))).toContain(stripWhitespace(`
|
||||
getTestBed().initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting(), {
|
||||
teardown: { destroyAfterEach: false }
|
||||
});
|
||||
`));
|
||||
});
|
||||
|
||||
it('should migrate calls to initTestEnvironment going through a variable', () => {
|
||||
writeFile('/index.ts', `
|
||||
import { getTestBed } from '@angular/core/testing';
|
||||
import {
|
||||
BrowserDynamicTestingModule,
|
||||
platformBrowserDynamicTesting
|
||||
} from '@angular/platform-browser-dynamic/testing';
|
||||
|
||||
const tb = getTestBed();
|
||||
|
||||
tb.initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting());
|
||||
`);
|
||||
|
||||
runTSLint(true);
|
||||
|
||||
expect(stripWhitespace(getFile('/index.ts'))).toContain(stripWhitespace(`
|
||||
tb.initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting(), {
|
||||
teardown: { destroyAfterEach: false }
|
||||
});
|
||||
`));
|
||||
});
|
||||
|
||||
it('should migrate not calls to initTestEnvironment that already specify a teardown behavior',
|
||||
() => {
|
||||
writeFile('/index.ts', `
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import {
|
||||
BrowserDynamicTestingModule,
|
||||
platformBrowserDynamicTesting
|
||||
} from '@angular/platform-browser-dynamic/testing';
|
||||
|
||||
TestBed.initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting(), {
|
||||
teardown: {destroyAfterEach: true}
|
||||
});
|
||||
`);
|
||||
|
||||
runTSLint(true);
|
||||
|
||||
expect(stripWhitespace(getFile('/index.ts'))).toContain(stripWhitespace(`
|
||||
TestBed.initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting(), {
|
||||
teardown: {destroyAfterEach: true}
|
||||
});
|
||||
`));
|
||||
});
|
||||
|
||||
it('should migrate calls to initTestEnvironment that pass in aotSummaries as an arrow function',
|
||||
() => {
|
||||
writeFile('/index.ts', `
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import {
|
||||
BrowserDynamicTestingModule,
|
||||
platformBrowserDynamicTesting
|
||||
} from '@angular/platform-browser-dynamic/testing';
|
||||
|
||||
class Foo {}
|
||||
|
||||
TestBed.initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting(), () => [Foo]);
|
||||
`);
|
||||
|
||||
runTSLint(true);
|
||||
|
||||
expect(stripWhitespace(getFile('/index.ts'))).toContain(stripWhitespace(`
|
||||
TestBed.initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting(), {
|
||||
aotSummaries: () => [Foo],
|
||||
teardown: { destroyAfterEach: false }
|
||||
});
|
||||
`));
|
||||
});
|
||||
|
||||
it('should migrate calls to initTestEnvironment that pass in aotSummaries as an anonymous function',
|
||||
() => {
|
||||
writeFile('/index.ts', `
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import {
|
||||
BrowserDynamicTestingModule,
|
||||
platformBrowserDynamicTesting
|
||||
} from '@angular/platform-browser-dynamic/testing';
|
||||
|
||||
class Foo {}
|
||||
|
||||
TestBed.initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting(), function() {
|
||||
return [Foo];
|
||||
});
|
||||
`);
|
||||
|
||||
runTSLint(true);
|
||||
|
||||
expect(stripWhitespace(getFile('/index.ts'))).toContain(stripWhitespace(`
|
||||
TestBed.initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting(), {
|
||||
aotSummaries: function() {
|
||||
return [Foo];
|
||||
},
|
||||
teardown: { destroyAfterEach: false }
|
||||
});
|
||||
`));
|
||||
});
|
||||
|
||||
it('should migrate calls to initTestEnvironment that pass in aotSummaries via an object literal',
|
||||
() => {
|
||||
writeFile('/index.ts', `
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import {
|
||||
BrowserDynamicTestingModule,
|
||||
platformBrowserDynamicTesting
|
||||
} from '@angular/platform-browser-dynamic/testing';
|
||||
|
||||
class Foo {}
|
||||
|
||||
TestBed.initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting(), {
|
||||
aotSummaries: () => [Foo]
|
||||
});
|
||||
`);
|
||||
|
||||
runTSLint(true);
|
||||
|
||||
expect(stripWhitespace(getFile('/index.ts'))).toContain(stripWhitespace(`
|
||||
TestBed.initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting(), {
|
||||
aotSummaries: () => [Foo],
|
||||
teardown: { destroyAfterEach: false }
|
||||
});
|
||||
`));
|
||||
});
|
||||
|
||||
|
||||
it('should migrate initTestEnvironment calls across multiple files', () => {
|
||||
writeFile('/test-init.ts', `
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import {
|
||||
BrowserDynamicTestingModule,
|
||||
platformBrowserDynamicTesting
|
||||
} from '@angular/platform-browser-dynamic/testing';
|
||||
|
||||
TestBed.initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting());
|
||||
`);
|
||||
|
||||
writeFile('/other-test-init.ts', `
|
||||
import { getTestBed } from '@angular/core/testing';
|
||||
import {
|
||||
BrowserDynamicTestingModule,
|
||||
platformBrowserDynamicTesting
|
||||
} from '@angular/platform-browser-dynamic/testing';
|
||||
|
||||
getTestBed().initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting());
|
||||
`);
|
||||
|
||||
runTSLint(true);
|
||||
|
||||
expect(stripWhitespace(getFile('/test-init.ts'))).toContain(stripWhitespace(`
|
||||
TestBed.initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting(), {
|
||||
teardown: { destroyAfterEach: false }
|
||||
});
|
||||
`));
|
||||
|
||||
expect(stripWhitespace(getFile('/other-test-init.ts'))).toContain(stripWhitespace(`
|
||||
getTestBed().initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting(), {
|
||||
teardown: { destroyAfterEach: false }
|
||||
});
|
||||
`));
|
||||
});
|
||||
|
||||
it('should not migrate calls to initTestEnvironment that pass in a variable', () => {
|
||||
writeFile('/index.ts', `
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import {
|
||||
BrowserDynamicTestingModule,
|
||||
platformBrowserDynamicTesting
|
||||
} from '@angular/platform-browser-dynamic/testing';
|
||||
|
||||
const config = {aotSummaries: () => []};
|
||||
|
||||
TestBed.initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting(), config);
|
||||
`);
|
||||
|
||||
runTSLint(true);
|
||||
|
||||
const content = stripWhitespace(getFile('/index.ts'));
|
||||
expect(content).toContain(stripWhitespace(`const config = {aotSummaries: () => []};`));
|
||||
expect(content).toContain(stripWhitespace(`
|
||||
TestBed.initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting(), config);
|
||||
`));
|
||||
});
|
||||
|
||||
it('should not migrate invalid initTestEnvironment calls', () => {
|
||||
writeFile('/index.ts', `
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
TestBed.initTestEnvironment();
|
||||
`);
|
||||
|
||||
runTSLint(true);
|
||||
|
||||
expect(stripWhitespace(getFile('/index.ts')))
|
||||
.toContain(stripWhitespace(`TestBed.initTestEnvironment();`));
|
||||
});
|
||||
|
||||
it('should not migrate calls to initTestEnvironment not coming from Angular', () => {
|
||||
writeFile('/index.ts', `
|
||||
import { TestBed } from '@not-angular/core/testing';
|
||||
import {
|
||||
BrowserDynamicTestingModule,
|
||||
platformBrowserDynamicTesting
|
||||
} from '@angular/platform-browser-dynamic/testing';
|
||||
|
||||
TestBed.initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting());
|
||||
`);
|
||||
|
||||
runTSLint(true);
|
||||
|
||||
expect(stripWhitespace(getFile('/index.ts')))
|
||||
.toContain(stripWhitespace(
|
||||
`TestBed.initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting());`));
|
||||
});
|
||||
|
||||
it('should migrate calls to initTestEnvironment when TestBed is aliased', () => {
|
||||
writeFile('/index.ts', `
|
||||
import { TestBed as AliasOfTestBed } from '@angular/core/testing';
|
||||
import {
|
||||
BrowserDynamicTestingModule,
|
||||
platformBrowserDynamicTesting
|
||||
} from '@angular/platform-browser-dynamic/testing';
|
||||
|
||||
AliasOfTestBed.initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting());
|
||||
`);
|
||||
|
||||
runTSLint(true);
|
||||
|
||||
expect(stripWhitespace(getFile('/index.ts'))).toContain(stripWhitespace(`
|
||||
AliasOfTestBed.initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting(), {
|
||||
teardown: { destroyAfterEach: false }
|
||||
});
|
||||
`));
|
||||
});
|
||||
|
||||
it('should migrate withModule calls', () => {
|
||||
writeFile('/index.spec.ts', `
|
||||
import { Component } from '@angular/core';
|
||||
import { withModule, TestBed } from '@angular/core/testing';
|
||||
|
||||
@Component({template: ''})
|
||||
class Comp {}
|
||||
|
||||
it('should work', withModule({
|
||||
declarations: [Comp],
|
||||
}, () => {
|
||||
TestBed.createComponent(Comp);
|
||||
}));
|
||||
`);
|
||||
|
||||
runTSLint(true);
|
||||
|
||||
expect(stripWhitespace(getFile('/index.spec.ts'))).toContain(stripWhitespace(`
|
||||
it('should work', withModule({
|
||||
declarations: [Comp],
|
||||
teardown: {destroyAfterEach: false}
|
||||
}, () => {
|
||||
TestBed.createComponent(Comp);
|
||||
}));
|
||||
`));
|
||||
});
|
||||
|
||||
it('should not migrate withModule calls that already pass in the teardown flag', () => {
|
||||
writeFile('/index.spec.ts', `
|
||||
import { Component } from '@angular/core';
|
||||
import { withModule, TestBed } from '@angular/core/testing';
|
||||
|
||||
@Component({template: ''})
|
||||
class Comp {}
|
||||
|
||||
it('should work', withModule({
|
||||
teardown: {destroyAfterEach: true},
|
||||
declarations: [Comp],
|
||||
}, () => {
|
||||
TestBed.createComponent(Comp);
|
||||
}));
|
||||
`);
|
||||
|
||||
runTSLint(true);
|
||||
|
||||
expect(stripWhitespace(getFile('/index.spec.ts'))).toContain(stripWhitespace(`
|
||||
it('should work', withModule({
|
||||
teardown: {destroyAfterEach: true},
|
||||
declarations: [Comp],
|
||||
}, () => {
|
||||
TestBed.createComponent(Comp);
|
||||
}));
|
||||
`));
|
||||
});
|
||||
|
||||
it('should not migrate withModule calls that do not come from Angular', () => {
|
||||
writeFile('/index.spec.ts', `
|
||||
import { Component } from '@angular/core';
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
function withModule(...args: any[]) {}
|
||||
|
||||
@Component({template: ''})
|
||||
class Comp {}
|
||||
|
||||
it('should work', withModule({
|
||||
declarations: [Comp],
|
||||
}, () => {
|
||||
TestBed.createComponent(Comp);
|
||||
}));
|
||||
`);
|
||||
|
||||
runTSLint(true);
|
||||
|
||||
expect(stripWhitespace(getFile('/index.spec.ts'))).toContain(stripWhitespace(`
|
||||
it('should work', withModule({
|
||||
declarations: [Comp],
|
||||
}, () => {
|
||||
TestBed.createComponent(Comp);
|
||||
}));
|
||||
`));
|
||||
});
|
||||
|
||||
it('should migrate aliased withModule calls', () => {
|
||||
writeFile('/index.spec.ts', `
|
||||
import { Component } from '@angular/core';
|
||||
import { withModule as aliasOfWithModule, TestBed } from '@angular/core/testing';
|
||||
|
||||
@Component({template: ''})
|
||||
class Comp {}
|
||||
|
||||
it('should work', aliasOfWithModule({
|
||||
declarations: [Comp],
|
||||
}, () => {
|
||||
TestBed.createComponent(Comp);
|
||||
}));
|
||||
`);
|
||||
|
||||
runTSLint(true);
|
||||
|
||||
expect(stripWhitespace(getFile('/index.spec.ts'))).toContain(stripWhitespace(`
|
||||
it('should work', aliasOfWithModule({
|
||||
declarations: [Comp],
|
||||
teardown: {destroyAfterEach: false}
|
||||
}, () => {
|
||||
TestBed.createComponent(Comp);
|
||||
}));
|
||||
`));
|
||||
});
|
||||
|
||||
it('should migrate configureTestingModule calls', () => {
|
||||
writeFile('/index.spec.ts', `
|
||||
import { Component } from '@angular/core';
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
@Component({template: ''})
|
||||
class Comp {}
|
||||
|
||||
it('should work', () => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [Comp]
|
||||
});
|
||||
|
||||
const fixture = TestBed.createComponent(Comp);
|
||||
fixture.detectChanges();
|
||||
});
|
||||
`);
|
||||
|
||||
runTSLint(true);
|
||||
|
||||
expect(stripWhitespace(getFile('/index.spec.ts'))).toContain(stripWhitespace(`
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [Comp],
|
||||
teardown: {destroyAfterEach: false}
|
||||
});
|
||||
`));
|
||||
});
|
||||
|
||||
it('should migrate multiple configureTestingModule calls within the same file', () => {
|
||||
writeFile('/index.spec.ts', `
|
||||
import { Component } from '@angular/core';
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
@Component({template: ''})
|
||||
class Comp {}
|
||||
|
||||
@Component({template: ''})
|
||||
class BetterComp {}
|
||||
|
||||
it('should work', () => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [Comp]
|
||||
});
|
||||
|
||||
const fixture = TestBed.createComponent(Comp);
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should work better', () => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [BetterComp]
|
||||
});
|
||||
|
||||
const fixture = TestBed.createComponent(BetterComp);
|
||||
fixture.detectChanges();
|
||||
});
|
||||
`);
|
||||
|
||||
runTSLint(true);
|
||||
|
||||
const content = stripWhitespace(getFile('/index.spec.ts'));
|
||||
|
||||
expect(content).toContain(stripWhitespace(`
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [Comp],
|
||||
teardown: {destroyAfterEach: false}
|
||||
});
|
||||
`));
|
||||
|
||||
expect(content).toContain(stripWhitespace(`
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [BetterComp],
|
||||
teardown: {destroyAfterEach: false}
|
||||
});
|
||||
`));
|
||||
});
|
||||
|
||||
it('should migrate configureTestingModule calls through getTestBed()', () => {
|
||||
writeFile('/index.spec.ts', `
|
||||
import { Component } from '@angular/core';
|
||||
import { getTestBed } from '@angular/core/testing';
|
||||
|
||||
@Component({template: ''})
|
||||
class Comp {}
|
||||
|
||||
it('should work', () => {
|
||||
getTestBed().configureTestingModule({
|
||||
declarations: [Comp]
|
||||
});
|
||||
|
||||
const fixture = getTestBed().createComponent(Comp);
|
||||
fixture.detectChanges();
|
||||
});
|
||||
`);
|
||||
|
||||
runTSLint(true);
|
||||
|
||||
expect(stripWhitespace(getFile('/index.spec.ts'))).toContain(stripWhitespace(`
|
||||
getTestBed().configureTestingModule({
|
||||
declarations: [Comp],
|
||||
teardown: {destroyAfterEach: false}
|
||||
});
|
||||
`));
|
||||
});
|
||||
|
||||
it('should not migrate configureTestingModule calls that already pass in the teardown flag',
|
||||
() => {
|
||||
writeFile('/index.spec.ts', `
|
||||
import { Component } from '@angular/core';
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
@Component({template: ''})
|
||||
class Comp {}
|
||||
|
||||
it('should work', () => {
|
||||
TestBed.configureTestingModule({
|
||||
teardown: {destroyAfterEach: true},
|
||||
declarations: [Comp]
|
||||
});
|
||||
|
||||
const fixture = TestBed.createComponent(Comp);
|
||||
fixture.detectChanges();
|
||||
});
|
||||
`);
|
||||
|
||||
runTSLint(true);
|
||||
|
||||
expect(stripWhitespace(getFile('/index.spec.ts'))).toContain(stripWhitespace(`
|
||||
TestBed.configureTestingModule({
|
||||
teardown: {destroyAfterEach: true},
|
||||
declarations: [Comp]
|
||||
});
|
||||
`));
|
||||
});
|
||||
|
||||
|
||||
it('should not migrate configureTestingModule or withModule calls if initTestEnvironment was migrated in another file',
|
||||
() => {
|
||||
writeFile('/test-init.ts', `
|
||||
import { TestBed, withModule } from '@angular/core/testing';
|
||||
import {
|
||||
BrowserDynamicTestingModule,
|
||||
platformBrowserDynamicTesting
|
||||
} from '@angular/platform-browser-dynamic/testing';
|
||||
|
||||
TestBed.initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting());
|
||||
`);
|
||||
|
||||
writeFile('/comp.spec.ts', `
|
||||
import { Component } from '@angular/core';
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
@Component({template: ''})
|
||||
class Comp {}
|
||||
|
||||
it('should work', () => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [Comp]
|
||||
});
|
||||
|
||||
const fixture = TestBed.createComponent(Comp);
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should also work', withModule({
|
||||
declarations: [Comp],
|
||||
}, () => {
|
||||
TestBed.createComponent(Comp);
|
||||
}));
|
||||
`);
|
||||
|
||||
runTSLint(true);
|
||||
|
||||
expect(stripWhitespace(getFile('/test-init.ts'))).toContain(stripWhitespace(`
|
||||
TestBed.initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting(), {
|
||||
teardown: { destroyAfterEach: false }
|
||||
});
|
||||
`));
|
||||
|
||||
expect(stripWhitespace(getFile('/comp.spec.ts'))).toContain(stripWhitespace(`
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [Comp]
|
||||
});
|
||||
`));
|
||||
|
||||
expect(stripWhitespace(getFile('/comp.spec.ts'))).toContain(stripWhitespace(`
|
||||
it('should also work', withModule({
|
||||
declarations: [Comp],
|
||||
}, () => {
|
||||
TestBed.createComponent(Comp);
|
||||
}));
|
||||
`));
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,664 @@
|
||||
/**
|
||||
* @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 {getSystemPath, normalize, virtualFs} from '@angular-devkit/core';
|
||||
import {TempScopedNodeJsSyncHost} from '@angular-devkit/core/node/testing';
|
||||
import {HostTree} from '@angular-devkit/schematics';
|
||||
import {SchematicTestRunner, UnitTestTree} from '@angular-devkit/schematics/testing';
|
||||
import * as shx from 'shelljs';
|
||||
|
||||
|
||||
describe('TestBed teardown migration', () => {
|
||||
let runner: SchematicTestRunner;
|
||||
let host: TempScopedNodeJsSyncHost;
|
||||
let tree: UnitTestTree;
|
||||
let tmpDirPath: string;
|
||||
let previousWorkingDir: string;
|
||||
|
||||
beforeEach(() => {
|
||||
runner = new SchematicTestRunner('test', require.resolve('../migrations.json'));
|
||||
host = new TempScopedNodeJsSyncHost();
|
||||
tree = new UnitTestTree(new HostTree(host));
|
||||
|
||||
writeFile('/tsconfig.json', JSON.stringify({
|
||||
compilerOptions: {
|
||||
lib: ['es2015'],
|
||||
strictNullChecks: true,
|
||||
},
|
||||
}));
|
||||
writeFile('/angular.json', JSON.stringify({
|
||||
version: 1,
|
||||
projects: {t: {architect: {build: {options: {tsConfig: './tsconfig.json'}}}}}
|
||||
}));
|
||||
|
||||
// We need to declare the Angular symbols we're testing for, otherwise type checking won't work.
|
||||
// Note that the declarations here are a little more convoluted than they need to be. It's
|
||||
// because they've been copied over directly from `node_modules/@angular/core/testing.d.ts`,
|
||||
// except for removing some methods that are irrelevant to these tests.
|
||||
writeFile('/node_modules/@angular/core/testing.d.ts', `
|
||||
export declare const getTestBed: () => TestBed;
|
||||
|
||||
export declare interface TestBed {
|
||||
initTestEnvironment(ngModule: any, platform: any, options?: any): void;
|
||||
configureTestingModule(moduleDef: any): void;
|
||||
}
|
||||
|
||||
export declare const TestBed: TestBedStatic;
|
||||
|
||||
export declare interface TestBedStatic {
|
||||
new (...args: any[]): TestBed;
|
||||
initTestEnvironment(ngModule: any, platform: any, options?: any): TestBed;
|
||||
configureTestingModule(moduleDef: any): TestBedStatic;
|
||||
}
|
||||
|
||||
export declare function withModule(moduleDef: any, fn: Function): () => any;
|
||||
`);
|
||||
|
||||
previousWorkingDir = shx.pwd();
|
||||
tmpDirPath = getSystemPath(host.root);
|
||||
|
||||
// Switch into the temporary directory path. This allows us to run
|
||||
// the schematic against our custom unit test tree.
|
||||
shx.cd(tmpDirPath);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
shx.cd(previousWorkingDir);
|
||||
shx.rm('-r', tmpDirPath);
|
||||
});
|
||||
|
||||
it('should migrate calls to initTestEnvironment', async () => {
|
||||
writeFile('/index.ts', `
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import {
|
||||
BrowserDynamicTestingModule,
|
||||
platformBrowserDynamicTesting
|
||||
} from '@angular/platform-browser-dynamic/testing';
|
||||
|
||||
TestBed.initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting());
|
||||
`);
|
||||
|
||||
await runMigration();
|
||||
|
||||
expect(stripWhitespace(tree.readContent('/index.ts'))).toContain(stripWhitespace(`
|
||||
TestBed.initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting(), {
|
||||
teardown: { destroyAfterEach: false }
|
||||
});
|
||||
`));
|
||||
});
|
||||
|
||||
it('should migrate calls to initTestEnvironment going through getTestBed()', async () => {
|
||||
writeFile('/index.ts', `
|
||||
import { getTestBed } from '@angular/core/testing';
|
||||
import {
|
||||
BrowserDynamicTestingModule,
|
||||
platformBrowserDynamicTesting
|
||||
} from '@angular/platform-browser-dynamic/testing';
|
||||
|
||||
getTestBed().initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting());
|
||||
`);
|
||||
|
||||
await runMigration();
|
||||
|
||||
expect(stripWhitespace(tree.readContent('/index.ts'))).toContain(stripWhitespace(`
|
||||
getTestBed().initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting(), {
|
||||
teardown: { destroyAfterEach: false }
|
||||
});
|
||||
`));
|
||||
});
|
||||
|
||||
it('should migrate calls to initTestEnvironment going through a variable', async () => {
|
||||
writeFile('/index.ts', `
|
||||
import { getTestBed } from '@angular/core/testing';
|
||||
import {
|
||||
BrowserDynamicTestingModule,
|
||||
platformBrowserDynamicTesting
|
||||
} from '@angular/platform-browser-dynamic/testing';
|
||||
|
||||
const tb = getTestBed();
|
||||
|
||||
tb.initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting());
|
||||
`);
|
||||
|
||||
await runMigration();
|
||||
|
||||
expect(stripWhitespace(tree.readContent('/index.ts'))).toContain(stripWhitespace(`
|
||||
tb.initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting(), {
|
||||
teardown: { destroyAfterEach: false }
|
||||
});
|
||||
`));
|
||||
});
|
||||
|
||||
it('should migrate not calls to initTestEnvironment that already specify a teardown behavior',
|
||||
async () => {
|
||||
writeFile('/index.ts', `
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import {
|
||||
BrowserDynamicTestingModule,
|
||||
platformBrowserDynamicTesting
|
||||
} from '@angular/platform-browser-dynamic/testing';
|
||||
|
||||
TestBed.initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting(), {
|
||||
teardown: {destroyAfterEach: true}
|
||||
});
|
||||
`);
|
||||
|
||||
await runMigration();
|
||||
|
||||
expect(stripWhitespace(tree.readContent('/index.ts'))).toContain(stripWhitespace(`
|
||||
TestBed.initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting(), {
|
||||
teardown: {destroyAfterEach: true}
|
||||
});
|
||||
`));
|
||||
});
|
||||
|
||||
it('should migrate calls to initTestEnvironment that pass in aotSummaries as an arrow function',
|
||||
async () => {
|
||||
writeFile('/index.ts', `
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import {
|
||||
BrowserDynamicTestingModule,
|
||||
platformBrowserDynamicTesting
|
||||
} from '@angular/platform-browser-dynamic/testing';
|
||||
|
||||
class Foo {}
|
||||
|
||||
TestBed.initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting(), () => [Foo]);
|
||||
`);
|
||||
|
||||
await runMigration();
|
||||
|
||||
expect(stripWhitespace(tree.readContent('/index.ts'))).toContain(stripWhitespace(`
|
||||
TestBed.initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting(), {
|
||||
aotSummaries: () => [Foo],
|
||||
teardown: { destroyAfterEach: false }
|
||||
});
|
||||
`));
|
||||
});
|
||||
|
||||
it('should migrate calls to initTestEnvironment that pass in aotSummaries as an anonymous function',
|
||||
async () => {
|
||||
writeFile('/index.ts', `
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import {
|
||||
BrowserDynamicTestingModule,
|
||||
platformBrowserDynamicTesting
|
||||
} from '@angular/platform-browser-dynamic/testing';
|
||||
|
||||
class Foo {}
|
||||
|
||||
TestBed.initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting(), function() {
|
||||
return [Foo];
|
||||
});
|
||||
`);
|
||||
|
||||
await runMigration();
|
||||
|
||||
expect(stripWhitespace(tree.readContent('/index.ts'))).toContain(stripWhitespace(`
|
||||
TestBed.initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting(), {
|
||||
aotSummaries: function() {
|
||||
return [Foo];
|
||||
},
|
||||
teardown: { destroyAfterEach: false }
|
||||
});
|
||||
`));
|
||||
});
|
||||
|
||||
it('should migrate calls to initTestEnvironment that pass in aotSummaries via an object literal',
|
||||
async () => {
|
||||
writeFile('/index.ts', `
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import {
|
||||
BrowserDynamicTestingModule,
|
||||
platformBrowserDynamicTesting
|
||||
} from '@angular/platform-browser-dynamic/testing';
|
||||
|
||||
class Foo {}
|
||||
|
||||
TestBed.initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting(), {
|
||||
aotSummaries: () => [Foo]
|
||||
});
|
||||
`);
|
||||
|
||||
await runMigration();
|
||||
|
||||
expect(stripWhitespace(tree.readContent('/index.ts'))).toContain(stripWhitespace(`
|
||||
TestBed.initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting(), {
|
||||
aotSummaries: () => [Foo],
|
||||
teardown: { destroyAfterEach: false }
|
||||
});
|
||||
`));
|
||||
});
|
||||
|
||||
|
||||
it('should migrate initTestEnvironment calls across multiple files', async () => {
|
||||
writeFile('/test-init.ts', `
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import {
|
||||
BrowserDynamicTestingModule,
|
||||
platformBrowserDynamicTesting
|
||||
} from '@angular/platform-browser-dynamic/testing';
|
||||
|
||||
TestBed.initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting());
|
||||
`);
|
||||
|
||||
writeFile('/other-test-init.ts', `
|
||||
import { getTestBed } from '@angular/core/testing';
|
||||
import {
|
||||
BrowserDynamicTestingModule,
|
||||
platformBrowserDynamicTesting
|
||||
} from '@angular/platform-browser-dynamic/testing';
|
||||
|
||||
getTestBed().initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting());
|
||||
`);
|
||||
|
||||
await runMigration();
|
||||
|
||||
expect(stripWhitespace(tree.readContent('/test-init.ts'))).toContain(stripWhitespace(`
|
||||
TestBed.initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting(), {
|
||||
teardown: { destroyAfterEach: false }
|
||||
});
|
||||
`));
|
||||
|
||||
expect(stripWhitespace(tree.readContent('/other-test-init.ts'))).toContain(stripWhitespace(`
|
||||
getTestBed().initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting(), {
|
||||
teardown: { destroyAfterEach: false }
|
||||
});
|
||||
`));
|
||||
});
|
||||
|
||||
it('should not migrate calls to initTestEnvironment that pass in a variable', async () => {
|
||||
writeFile('/index.ts', `
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import {
|
||||
BrowserDynamicTestingModule,
|
||||
platformBrowserDynamicTesting
|
||||
} from '@angular/platform-browser-dynamic/testing';
|
||||
|
||||
const config = {aotSummaries: () => []};
|
||||
|
||||
TestBed.initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting(), config);
|
||||
`);
|
||||
|
||||
await runMigration();
|
||||
|
||||
const content = stripWhitespace(tree.readContent('/index.ts'));
|
||||
expect(content).toContain(stripWhitespace(`const config = {aotSummaries: () => []};`));
|
||||
expect(content).toContain(stripWhitespace(`
|
||||
TestBed.initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting(), config);
|
||||
`));
|
||||
});
|
||||
|
||||
it('should not migrate invalid initTestEnvironment calls', async () => {
|
||||
writeFile('/index.ts', `
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
TestBed.initTestEnvironment();
|
||||
`);
|
||||
|
||||
await runMigration();
|
||||
|
||||
expect(stripWhitespace(tree.readContent('/index.ts')))
|
||||
.toContain(stripWhitespace(`TestBed.initTestEnvironment();`));
|
||||
});
|
||||
|
||||
it('should not migrate calls to initTestEnvironment not coming from Angular', async () => {
|
||||
writeFile('/index.ts', `
|
||||
import { TestBed } from '@not-angular/core/testing';
|
||||
import {
|
||||
BrowserDynamicTestingModule,
|
||||
platformBrowserDynamicTesting
|
||||
} from '@angular/platform-browser-dynamic/testing';
|
||||
|
||||
TestBed.initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting());
|
||||
`);
|
||||
|
||||
await runMigration();
|
||||
|
||||
expect(stripWhitespace(tree.readContent('/index.ts')))
|
||||
.toContain(stripWhitespace(
|
||||
`TestBed.initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting());`));
|
||||
});
|
||||
|
||||
it('should migrate calls to initTestEnvironment when TestBed is aliased', async () => {
|
||||
writeFile('/index.ts', `
|
||||
import { TestBed as AliasOfTestBed } from '@angular/core/testing';
|
||||
import {
|
||||
BrowserDynamicTestingModule,
|
||||
platformBrowserDynamicTesting
|
||||
} from '@angular/platform-browser-dynamic/testing';
|
||||
|
||||
AliasOfTestBed.initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting());
|
||||
`);
|
||||
|
||||
await runMigration();
|
||||
|
||||
expect(stripWhitespace(tree.readContent('/index.ts'))).toContain(stripWhitespace(`
|
||||
AliasOfTestBed.initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting(), {
|
||||
teardown: { destroyAfterEach: false }
|
||||
});
|
||||
`));
|
||||
});
|
||||
|
||||
it('should migrate withModule calls', async () => {
|
||||
writeFile('/index.spec.ts', `
|
||||
import { Component } from '@angular/core';
|
||||
import { withModule, TestBed } from '@angular/core/testing';
|
||||
|
||||
@Component({template: ''})
|
||||
class Comp {}
|
||||
|
||||
it('should work', withModule({
|
||||
declarations: [Comp],
|
||||
}, () => {
|
||||
TestBed.createComponent(Comp);
|
||||
}));
|
||||
`);
|
||||
|
||||
await runMigration();
|
||||
|
||||
expect(stripWhitespace(tree.readContent('/index.spec.ts'))).toContain(stripWhitespace(`
|
||||
it('should work', withModule({
|
||||
declarations: [Comp],
|
||||
teardown: {destroyAfterEach: false}
|
||||
}, () => {
|
||||
TestBed.createComponent(Comp);
|
||||
}));
|
||||
`));
|
||||
});
|
||||
|
||||
it('should not migrate withModule calls that already pass in the teardown flag', async () => {
|
||||
writeFile('/index.spec.ts', `
|
||||
import { Component } from '@angular/core';
|
||||
import { withModule, TestBed } from '@angular/core/testing';
|
||||
|
||||
@Component({template: ''})
|
||||
class Comp {}
|
||||
|
||||
it('should work', withModule({
|
||||
teardown: {destroyAfterEach: true},
|
||||
declarations: [Comp],
|
||||
}, () => {
|
||||
TestBed.createComponent(Comp);
|
||||
}));
|
||||
`);
|
||||
|
||||
await runMigration();
|
||||
|
||||
expect(stripWhitespace(tree.readContent('/index.spec.ts'))).toContain(stripWhitespace(`
|
||||
it('should work', withModule({
|
||||
teardown: {destroyAfterEach: true},
|
||||
declarations: [Comp],
|
||||
}, () => {
|
||||
TestBed.createComponent(Comp);
|
||||
}));
|
||||
`));
|
||||
});
|
||||
|
||||
it('should not migrate withModule calls that do not come from Angular', async () => {
|
||||
writeFile('/index.spec.ts', `
|
||||
import { Component } from '@angular/core';
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
function withModule(...args: any[]) {}
|
||||
|
||||
@Component({template: ''})
|
||||
class Comp {}
|
||||
|
||||
it('should work', withModule({
|
||||
declarations: [Comp],
|
||||
}, () => {
|
||||
TestBed.createComponent(Comp);
|
||||
}));
|
||||
`);
|
||||
|
||||
await runMigration();
|
||||
|
||||
expect(stripWhitespace(tree.readContent('/index.spec.ts'))).toContain(stripWhitespace(`
|
||||
it('should work', withModule({
|
||||
declarations: [Comp],
|
||||
}, () => {
|
||||
TestBed.createComponent(Comp);
|
||||
}));
|
||||
`));
|
||||
});
|
||||
|
||||
it('should migrate aliased withModule calls', async () => {
|
||||
writeFile('/index.spec.ts', `
|
||||
import { Component } from '@angular/core';
|
||||
import { withModule as aliasOfWithModule, TestBed } from '@angular/core/testing';
|
||||
|
||||
@Component({template: ''})
|
||||
class Comp {}
|
||||
|
||||
it('should work', aliasOfWithModule({
|
||||
declarations: [Comp],
|
||||
}, () => {
|
||||
TestBed.createComponent(Comp);
|
||||
}));
|
||||
`);
|
||||
|
||||
await runMigration();
|
||||
|
||||
expect(stripWhitespace(tree.readContent('/index.spec.ts'))).toContain(stripWhitespace(`
|
||||
it('should work', aliasOfWithModule({
|
||||
declarations: [Comp],
|
||||
teardown: {destroyAfterEach: false}
|
||||
}, () => {
|
||||
TestBed.createComponent(Comp);
|
||||
}));
|
||||
`));
|
||||
});
|
||||
|
||||
it('should migrate configureTestingModule calls', async () => {
|
||||
writeFile('/index.spec.ts', `
|
||||
import { Component } from '@angular/core';
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
@Component({template: ''})
|
||||
class Comp {}
|
||||
|
||||
it('should work', () => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [Comp]
|
||||
});
|
||||
|
||||
const fixture = TestBed.createComponent(Comp);
|
||||
fixture.detectChanges();
|
||||
});
|
||||
`);
|
||||
|
||||
await runMigration();
|
||||
|
||||
expect(stripWhitespace(tree.readContent('/index.spec.ts'))).toContain(stripWhitespace(`
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [Comp],
|
||||
teardown: {destroyAfterEach: false}
|
||||
});
|
||||
`));
|
||||
});
|
||||
|
||||
it('should migrate multiple configureTestingModule calls within the same file', async () => {
|
||||
writeFile('/index.spec.ts', `
|
||||
import { Component } from '@angular/core';
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
@Component({template: ''})
|
||||
class Comp {}
|
||||
|
||||
@Component({template: ''})
|
||||
class BetterComp {}
|
||||
|
||||
it('should work', () => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [Comp]
|
||||
});
|
||||
|
||||
const fixture = TestBed.createComponent(Comp);
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should work better', () => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [BetterComp]
|
||||
});
|
||||
|
||||
const fixture = TestBed.createComponent(BetterComp);
|
||||
fixture.detectChanges();
|
||||
});
|
||||
`);
|
||||
|
||||
await runMigration();
|
||||
|
||||
const content = stripWhitespace(tree.readContent('/index.spec.ts'));
|
||||
|
||||
expect(content).toContain(stripWhitespace(`
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [Comp],
|
||||
teardown: {destroyAfterEach: false}
|
||||
});
|
||||
`));
|
||||
|
||||
expect(content).toContain(stripWhitespace(`
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [BetterComp],
|
||||
teardown: {destroyAfterEach: false}
|
||||
});
|
||||
`));
|
||||
});
|
||||
|
||||
it('should migrate configureTestingModule calls through getTestBed()', async () => {
|
||||
writeFile('/index.spec.ts', `
|
||||
import { Component } from '@angular/core';
|
||||
import { getTestBed } from '@angular/core/testing';
|
||||
|
||||
@Component({template: ''})
|
||||
class Comp {}
|
||||
|
||||
it('should work', () => {
|
||||
getTestBed().configureTestingModule({
|
||||
declarations: [Comp]
|
||||
});
|
||||
|
||||
const fixture = getTestBed().createComponent(Comp);
|
||||
fixture.detectChanges();
|
||||
});
|
||||
`);
|
||||
|
||||
await runMigration();
|
||||
|
||||
expect(stripWhitespace(tree.readContent('/index.spec.ts'))).toContain(stripWhitespace(`
|
||||
getTestBed().configureTestingModule({
|
||||
declarations: [Comp],
|
||||
teardown: {destroyAfterEach: false}
|
||||
});
|
||||
`));
|
||||
});
|
||||
|
||||
it('should not migrate configureTestingModule calls that already pass in the teardown flag',
|
||||
async () => {
|
||||
writeFile('/index.spec.ts', `
|
||||
import { Component } from '@angular/core';
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
@Component({template: ''})
|
||||
class Comp {}
|
||||
|
||||
it('should work', () => {
|
||||
TestBed.configureTestingModule({
|
||||
teardown: {destroyAfterEach: true},
|
||||
declarations: [Comp]
|
||||
});
|
||||
|
||||
const fixture = TestBed.createComponent(Comp);
|
||||
fixture.detectChanges();
|
||||
});
|
||||
`);
|
||||
|
||||
await runMigration();
|
||||
|
||||
expect(stripWhitespace(tree.readContent('/index.spec.ts'))).toContain(stripWhitespace(`
|
||||
TestBed.configureTestingModule({
|
||||
teardown: {destroyAfterEach: true},
|
||||
declarations: [Comp]
|
||||
});
|
||||
`));
|
||||
});
|
||||
|
||||
|
||||
it('should not migrate configureTestingModule or withModule calls if initTestEnvironment was migrated in another file',
|
||||
async () => {
|
||||
writeFile('/test-init.ts', `
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import {
|
||||
BrowserDynamicTestingModule,
|
||||
platformBrowserDynamicTesting
|
||||
} from '@angular/platform-browser-dynamic/testing';
|
||||
|
||||
TestBed.initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting());
|
||||
`);
|
||||
|
||||
writeFile('/comp.spec.ts', `
|
||||
import { Component } from '@angular/core';
|
||||
import { TestBed, withModule } from '@angular/core/testing';
|
||||
|
||||
@Component({template: ''})
|
||||
class Comp {}
|
||||
|
||||
it('should work', () => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [Comp]
|
||||
});
|
||||
|
||||
const fixture = TestBed.createComponent(Comp);
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should also work', withModule({
|
||||
declarations: [Comp],
|
||||
}, () => {
|
||||
TestBed.createComponent(Comp);
|
||||
}));
|
||||
`);
|
||||
|
||||
await runMigration();
|
||||
|
||||
expect(stripWhitespace(tree.readContent('/test-init.ts'))).toContain(stripWhitespace(`
|
||||
TestBed.initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting(), {
|
||||
teardown: { destroyAfterEach: false }
|
||||
});
|
||||
`));
|
||||
|
||||
expect(stripWhitespace(tree.readContent('/comp.spec.ts'))).toContain(stripWhitespace(`
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [Comp]
|
||||
});
|
||||
`));
|
||||
|
||||
expect(stripWhitespace(tree.readContent('/comp.spec.ts'))).toContain(stripWhitespace(`
|
||||
it('should also work', withModule({
|
||||
declarations: [Comp],
|
||||
}, () => {
|
||||
TestBed.createComponent(Comp);
|
||||
}));
|
||||
`));
|
||||
});
|
||||
|
||||
|
||||
function writeFile(filePath: string, contents: string) {
|
||||
host.sync.write(normalize(filePath), virtualFs.stringToFileBuffer(contents));
|
||||
}
|
||||
|
||||
function runMigration() {
|
||||
return runner.runSchematicAsync('migration-v13-testbed-teardown', {}, tree).toPromise();
|
||||
}
|
||||
|
||||
function stripWhitespace(contents: string) {
|
||||
return contents.replace(/\s/g, '');
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user