mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
refactor(schematics): add test cases for cdk ng-update (#13517)
This commit is contained in:
committed by
Andrew Seguin
parent
d128fffcd6
commit
6afe3f557f
@@ -16,14 +16,20 @@ filegroup(
|
||||
ts_library(
|
||||
name = "schematics",
|
||||
module_name = "@angular/cdk/schematics",
|
||||
srcs = glob(["**/*.ts"], exclude=["**/files/**/*.ts", "**/*.spec.ts"]),
|
||||
srcs = glob(["**/*.ts"], exclude=[
|
||||
"**/files/**/*.ts",
|
||||
"**/*.spec.ts",
|
||||
"ng-update/test-cases/**/*",
|
||||
]),
|
||||
tsconfig = ":tsconfig.json",
|
||||
deps = [
|
||||
"@npm//@schematics/angular",
|
||||
"@npm//@angular-devkit/schematics",
|
||||
# TODO(devversion): Only include jasmine for test sources (See: tsconfig types).
|
||||
# TODO(devversion): Only include jasmine for test sources (See: tsconfig types).
|
||||
"@npm//@types/jasmine",
|
||||
"@npm//@types/node",
|
||||
# TODO(devversion): Similarly to "jasmine", we need to extract these into testing only.
|
||||
"@npm//fs-extra",
|
||||
"@npm//glob",
|
||||
"@npm//parse5",
|
||||
"@npm//rxjs",
|
||||
@@ -45,7 +51,7 @@ npm_package(
|
||||
jasmine_node_test(
|
||||
name = "unit_tests",
|
||||
srcs = [":schematics_test_sources"],
|
||||
data = [":schematics_assets"],
|
||||
data = [":schematics_assets", ":schematics_test_cases"],
|
||||
)
|
||||
|
||||
ts_library(
|
||||
@@ -64,3 +70,12 @@ ts_library(
|
||||
tsconfig = ":tsconfig.json",
|
||||
testonly = True,
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "schematics_test_cases",
|
||||
srcs = glob([
|
||||
"ng-update/test-cases/**/*_input.ts",
|
||||
"ng-update/test-cases/**/*_expected_output.ts"
|
||||
]),
|
||||
testonly = True,
|
||||
)
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
/** Path to the schematic collection that includes the migrations. */
|
||||
export const migrationCollection = require.resolve('../../migration.json');
|
||||
@@ -0,0 +1,16 @@
|
||||
import {migrationCollection} from '../index.spec';
|
||||
import {runTestCases} from '../../../testing';
|
||||
|
||||
describe('v6 method call checks', () => {
|
||||
|
||||
it('should properly report invalid method calls', async () => {
|
||||
const {logOutput} = await runTestCases('migration-v6', migrationCollection, {
|
||||
'method-call-checks': require.resolve('./method-call-checks_input.ts')
|
||||
});
|
||||
|
||||
expect(logOutput)
|
||||
.toMatch(/\[15,.*Found call to "FocusMonitor\.monitor".*renderer.*has been removed/);
|
||||
expect(logOutput)
|
||||
.toMatch(/\[16,.*Found call to "FocusMonitor\.monitor".*renderer.*has been removed/);
|
||||
});
|
||||
});
|
||||
+1
-1
@@ -15,4 +15,4 @@ class A implements AfterViewInit {
|
||||
this.focusMonitor.monitor(this.elementRef.nativeElement, this.renderer, true);
|
||||
this.self.a.monitor(this.elementRef.nativeElement, this.renderer, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
import {join} from 'path';
|
||||
import {readFileContent, runTestCases} from '../../testing';
|
||||
import {migrationCollection} from './index.spec';
|
||||
|
||||
describe('v6 upgrade test cases', () => {
|
||||
|
||||
/**
|
||||
* Name of test cases that will be used to verify that update schematics properly update
|
||||
* a developers application.
|
||||
*/
|
||||
const testCases = [
|
||||
'v6/attribute-selectors',
|
||||
'v6/class-names',
|
||||
'v6/input-names',
|
||||
'v6/property-names',
|
||||
];
|
||||
|
||||
let testCasesOutputPath: string;
|
||||
|
||||
beforeAll(async () => {
|
||||
const testCaseInputs = testCases.reduce((inputs, testCaseName) => {
|
||||
inputs[testCaseName] = require.resolve(`./${testCaseName}_input.ts`);
|
||||
return inputs;
|
||||
}, {} as {[name: string]: string});
|
||||
|
||||
const {tempPath} = await runTestCases('migration-v6', migrationCollection, testCaseInputs);
|
||||
|
||||
testCasesOutputPath = join(tempPath, 'projects/cdk-testing/src/test-cases/');
|
||||
});
|
||||
|
||||
// Iterates through every test case directory and generates a jasmine test block that will
|
||||
// verify that the update schematics properly updated the test input to the expected output.
|
||||
testCases.forEach(testCaseName => {
|
||||
const expectedOutputPath = require.resolve(`./${testCaseName}_expected_output.ts`);
|
||||
|
||||
it(`should apply update schematics to test case: ${testCaseName}`, () => {
|
||||
expect(readFileContent(join(testCasesOutputPath, `${testCaseName}.ts`)))
|
||||
.toBe(readFileContent(expectedOutputPath));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
import {Component} from '@angular/core';
|
||||
import {By} from '@angular/platform-browser';
|
||||
|
||||
const a = By.css('[cdkPortalOutlet]');
|
||||
const b = By.css('[cdkPortalOutlet]');
|
||||
|
||||
// These should not change because we only update string literals that are
|
||||
// inside of a call expression.
|
||||
const c = 'cdkPortalHost';
|
||||
const d = 'portalHost';
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<div cdkPortalOutlet="E"></div>
|
||||
<div [cdkPortalOutlet]="myPortal"></div>
|
||||
`,
|
||||
styles: [
|
||||
'[cdkPortalOutlet] {background: red}',
|
||||
'div[cdkPortalOutlet] {color: blue}'
|
||||
]
|
||||
})
|
||||
class E {}
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<div cdkPortalOutlet="F"></div>
|
||||
<div [cdkPortalOutlet]="myPortal"></div>
|
||||
`,
|
||||
styles: [
|
||||
'[cdkPortalOutlet] {background: red}',
|
||||
'div[cdkPortalOutlet] {color: blue}'
|
||||
]
|
||||
})
|
||||
class F {}
|
||||
@@ -0,0 +1,34 @@
|
||||
import {Component} from '@angular/core';
|
||||
import {By} from '@angular/platform-browser';
|
||||
|
||||
const a = By.css('[cdkPortalHost]');
|
||||
const b = By.css('[portalHost]');
|
||||
|
||||
// These should not change because we only update string literals that are
|
||||
// inside of a call expression.
|
||||
const c = 'cdkPortalHost';
|
||||
const d = 'portalHost';
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<div cdkPortalHost="E"></div>
|
||||
<div [cdkPortalHost]="myPortal"></div>
|
||||
`,
|
||||
styles: [
|
||||
'[cdkPortalHost] {background: red}',
|
||||
'div[cdkPortalHost] {color: blue}'
|
||||
]
|
||||
})
|
||||
class E {}
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<div portalHost="F"></div>
|
||||
<div [portalHost]="myPortal"></div>
|
||||
`,
|
||||
styles: [
|
||||
'[portalHost] {background: red}',
|
||||
'div[portalHost] {color: blue}'
|
||||
]
|
||||
})
|
||||
class F {}
|
||||
@@ -0,0 +1,8 @@
|
||||
import {CdkConnectedOverlay, CdkOverlayOrigin} from '@angular/cdk/overlay';
|
||||
import {CdkObserveContent} from '@angular/cdk/observers';
|
||||
import {CdkTrapFocus} from '@angular/cdk/a11y';
|
||||
|
||||
const a = new CdkConnectedOverlay();
|
||||
const b = new CdkOverlayOrigin();
|
||||
const c = new CdkObserveContent();
|
||||
const d = new CdkTrapFocus();
|
||||
@@ -0,0 +1,8 @@
|
||||
import {ConnectedOverlayDirective, OverlayOrigin} from '@angular/cdk/overlay';
|
||||
import {ObserveContent} from '@angular/cdk/observers';
|
||||
import {FocusTrapDirective} from '@angular/cdk/a11y';
|
||||
|
||||
const a = new ConnectedOverlayDirective();
|
||||
const b = new OverlayOrigin();
|
||||
const c = new ObserveContent();
|
||||
const d = new FocusTrapDirective();
|
||||
@@ -0,0 +1,21 @@
|
||||
import {Component} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<ng-template cdkConnectedOverlay
|
||||
[cdkConnectedOverlayOrigin]="myOrigin"
|
||||
[cdkConnectedOverlayPositions]="myPositions"
|
||||
[cdkConnectedOverlayOffsetX]="myOffsetX"
|
||||
[cdkConnectedOverlayOffsetY]="myOffsetY"
|
||||
[cdkConnectedOverlayWidth]="myWidth"
|
||||
[cdkConnectedOverlayHeight]="myHeight"
|
||||
[cdkConnectedOverlayMinWidth]="myMinWidth"
|
||||
[cdkConnectedOverlayMinHeight]="myMinHeight"
|
||||
[cdkConnectedOverlayBackdropClass]="myBackdropClass"
|
||||
[cdkConnectedOverlayScrollStrategy]="myScrollStrategy"
|
||||
[cdkConnectedOverlayOpen]="isOpen"
|
||||
[cdkConnectedOverlayHasBackdrop]="hasBackdrop">
|
||||
</ng-template>
|
||||
`
|
||||
})
|
||||
class A {}
|
||||
@@ -0,0 +1,21 @@
|
||||
import {Component} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<ng-template cdkConnectedOverlay
|
||||
[origin]="myOrigin"
|
||||
[positions]="myPositions"
|
||||
[offsetX]="myOffsetX"
|
||||
[offsetY]="myOffsetY"
|
||||
[width]="myWidth"
|
||||
[height]="myHeight"
|
||||
[minWidth]="myMinWidth"
|
||||
[minHeight]="myMinHeight"
|
||||
[backdropClass]="myBackdropClass"
|
||||
[scrollStrategy]="myScrollStrategy"
|
||||
[open]="isOpen"
|
||||
[hasBackdrop]="hasBackdrop">
|
||||
</ng-template>
|
||||
`
|
||||
})
|
||||
class A {}
|
||||
@@ -0,0 +1,64 @@
|
||||
class CdkConnectedOverlay {
|
||||
_deprecatedOrigin: any;
|
||||
_deprecatedPositions: any;
|
||||
_deprecatedOffsetX: any;
|
||||
_deprecatedOffsetY: any;
|
||||
_deprecatedWidth: any;
|
||||
_deprecatedHeight: any;
|
||||
_deprecatedMinWidth: any;
|
||||
_deprecatedMinHeight: any;
|
||||
_deprecatedBackdropClass: any;
|
||||
_deprecatedScrollStrategy: any;
|
||||
_deprecatedOpen: any;
|
||||
_deprecatedHasBackdrop: any;
|
||||
}
|
||||
|
||||
class CdkPortalOutlet {
|
||||
_deprecatedPortal: any;
|
||||
_deprecatedPortalHost: any;
|
||||
}
|
||||
|
||||
class A {
|
||||
self = {me: this};
|
||||
|
||||
constructor(private a: CdkConnectedOverlay) {}
|
||||
|
||||
onAngularClick() {
|
||||
this.a.origin = '1';
|
||||
this.a.positions = '2';
|
||||
this.a.offsetX = '3';
|
||||
this.a.offsetY = '4';
|
||||
this.a.height = '5';
|
||||
|
||||
console.log(this.a.width || 10);
|
||||
console.log(this.a.minWidth || this.a.minHeight);
|
||||
|
||||
this.self.me.a.backdropClass = ['a', 'b', 'c'];
|
||||
|
||||
const x = ({test: true} || this.a);
|
||||
|
||||
if (this.isConnectedOverlay(x)) {
|
||||
x.scrollStrategy = 'myScrollStrategy';
|
||||
x.open = false;
|
||||
x.hasBackdrop = true;
|
||||
}
|
||||
}
|
||||
|
||||
isConnectedOverlay(val: any): val is CdkConnectedOverlay {
|
||||
return val instanceof CdkConnectedOverlay;
|
||||
}
|
||||
}
|
||||
|
||||
class B {
|
||||
self = {me: this};
|
||||
|
||||
constructor(protected a: CdkPortalOutlet) {}
|
||||
|
||||
onClick() {
|
||||
this.a.portal = this.a.portal = 'myNewPortal';
|
||||
|
||||
if (this.self.me.a) {
|
||||
console.log(this.a.portal || this.self.me.a.portal);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
class CdkConnectedOverlay {
|
||||
_deprecatedOrigin: any;
|
||||
_deprecatedPositions: any;
|
||||
_deprecatedOffsetX: any;
|
||||
_deprecatedOffsetY: any;
|
||||
_deprecatedWidth: any;
|
||||
_deprecatedHeight: any;
|
||||
_deprecatedMinWidth: any;
|
||||
_deprecatedMinHeight: any;
|
||||
_deprecatedBackdropClass: any;
|
||||
_deprecatedScrollStrategy: any;
|
||||
_deprecatedOpen: any;
|
||||
_deprecatedHasBackdrop: any;
|
||||
}
|
||||
|
||||
class CdkPortalOutlet {
|
||||
_deprecatedPortal: any;
|
||||
_deprecatedPortalHost: any;
|
||||
}
|
||||
|
||||
class A {
|
||||
self = {me: this};
|
||||
|
||||
constructor(private a: CdkConnectedOverlay) {}
|
||||
|
||||
onAngularClick() {
|
||||
this.a._deprecatedOrigin = '1';
|
||||
this.a._deprecatedPositions = '2';
|
||||
this.a._deprecatedOffsetX = '3';
|
||||
this.a._deprecatedOffsetY = '4';
|
||||
this.a._deprecatedHeight = '5';
|
||||
|
||||
console.log(this.a._deprecatedWidth || 10);
|
||||
console.log(this.a._deprecatedMinWidth || this.a._deprecatedMinHeight);
|
||||
|
||||
this.self.me.a._deprecatedBackdropClass = ['a', 'b', 'c'];
|
||||
|
||||
const x = ({test: true} || this.a);
|
||||
|
||||
if (this.isConnectedOverlay(x)) {
|
||||
x._deprecatedScrollStrategy = 'myScrollStrategy';
|
||||
x._deprecatedOpen = false;
|
||||
x._deprecatedHasBackdrop = true;
|
||||
}
|
||||
}
|
||||
|
||||
isConnectedOverlay(val: any): val is CdkConnectedOverlay {
|
||||
return val instanceof CdkConnectedOverlay;
|
||||
}
|
||||
}
|
||||
|
||||
class B {
|
||||
self = {me: this};
|
||||
|
||||
constructor(protected a: CdkPortalOutlet) {}
|
||||
|
||||
onClick() {
|
||||
this.a._deprecatedPortal = this.a._deprecatedPortalHost = 'myNewPortal';
|
||||
|
||||
if (this.self.me.a) {
|
||||
console.log(this.a._deprecatedPortalHost || this.self.me.a._deprecatedPortal);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import {join} from 'path';
|
||||
import {readFileContent, runTestCases} from '../../testing';
|
||||
import {migrationCollection} from './index.spec';
|
||||
|
||||
describe('v7 upgrade test cases', () => {
|
||||
|
||||
/**
|
||||
* Name of test cases that will be used to verify that update schematics properly update
|
||||
* a developers application.
|
||||
*/
|
||||
const testCases = [
|
||||
'v7/property-names',
|
||||
];
|
||||
|
||||
let testCasesOutputPath: string;
|
||||
|
||||
beforeAll(async () => {
|
||||
const testCaseInputs = testCases.reduce((inputs, testCaseName) => {
|
||||
inputs[testCaseName] = require.resolve(`./${testCaseName}_input.ts`);
|
||||
return inputs;
|
||||
}, {} as {[name: string]: string});
|
||||
|
||||
const {tempPath} = await runTestCases('migration-v7', migrationCollection, testCaseInputs);
|
||||
|
||||
testCasesOutputPath = join(tempPath, 'projects/cdk-testing/src/test-cases/');
|
||||
});
|
||||
|
||||
// Iterates through every test case directory and generates a jasmine test block that will
|
||||
// verify that the update schematics properly updated the test input to the expected output.
|
||||
testCases.forEach(testCaseName => {
|
||||
const expectedOutputPath = require.resolve(`./${testCaseName}_expected_output.ts`);
|
||||
|
||||
it(`should apply update schematics to test case: ${testCaseName}`, () => {
|
||||
expect(readFileContent(join(testCasesOutputPath, `${testCaseName}.ts`)))
|
||||
.toBe(readFileContent(expectedOutputPath));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import {EventEmitter, OnInit} from '@angular/core';
|
||||
|
||||
class SelectionModel {
|
||||
onChange = new EventEmitter<void>();
|
||||
}
|
||||
|
||||
class CdkConnectedOverlay {
|
||||
flexibleDiemsions: boolean;
|
||||
}
|
||||
|
||||
/* Actual test case using the previously defined definitions. */
|
||||
|
||||
class A implements OnInit {
|
||||
self = {me: this};
|
||||
|
||||
constructor(private a: SelectionModel, private b: CdkConnectedOverlay) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.a.changed.subscribe(() => console.log('Changed'));
|
||||
this.self.me.a.changed.subscribe(() => console.log('Changed 2'));
|
||||
|
||||
if (this.b.flexibleDimensions) {
|
||||
console.log(this.b.flexibleDimensions ? 'true' : 'false');
|
||||
}
|
||||
|
||||
const _state = this.self.me.b.flexibleDimensions.toString() || 'not-defined';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import {EventEmitter, OnInit} from '@angular/core';
|
||||
|
||||
class SelectionModel {
|
||||
onChange = new EventEmitter<void>();
|
||||
}
|
||||
|
||||
class CdkConnectedOverlay {
|
||||
flexibleDiemsions: boolean;
|
||||
}
|
||||
|
||||
/* Actual test case using the previously defined definitions. */
|
||||
|
||||
class A implements OnInit {
|
||||
self = {me: this};
|
||||
|
||||
constructor(private a: SelectionModel, private b: CdkConnectedOverlay) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.a.onChange.subscribe(() => console.log('Changed'));
|
||||
this.self.me.a.onChange.subscribe(() => console.log('Changed 2'));
|
||||
|
||||
if (this.b.flexibleDiemsions) {
|
||||
console.log(this.b.flexibleDiemsions ? 'true' : 'false');
|
||||
}
|
||||
|
||||
const _state = this.self.me.b.flexibleDiemsions.toString() || 'not-defined';
|
||||
}
|
||||
}
|
||||
@@ -8,3 +8,4 @@
|
||||
|
||||
export * from './post-scheduled-tasks';
|
||||
export * from './test-app';
|
||||
export * from './test-case-setup';
|
||||
|
||||
@@ -17,5 +17,5 @@ export function createTestApp(runner: SchematicTestRunner, appOptions = {}): Uni
|
||||
});
|
||||
|
||||
return runner.runExternalSchematic('@schematics/angular', 'application',
|
||||
{...appOptions, name: 'material'}, workspaceTree);
|
||||
{name: 'material', ...appOptions}, workspaceTree);
|
||||
}
|
||||
|
||||
@@ -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 {getSystemPath, normalize} from '@angular-devkit/core';
|
||||
import {TempScopedNodeJsSyncHost} from '@angular-devkit/core/node/testing';
|
||||
import * as virtualFs from '@angular-devkit/core/src/virtual-fs/host';
|
||||
import {SchematicTestRunner} from '@angular-devkit/schematics/testing';
|
||||
import {readFileSync, writeFileSync, mkdirpSync} from 'fs-extra';
|
||||
import {join, dirname} from 'path';
|
||||
import {createTestApp, runPostScheduledTasks} from '../testing';
|
||||
|
||||
/** Reads the UTF8 content of the specified file. Normalizes the path and ensures that */
|
||||
export function readFileContent(filePath: string): string {
|
||||
return readFileSync(filePath, 'utf8');
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a test app schematic tree that will be copied over to a real filesystem location.
|
||||
* This is necessary because TSLint is not able to read from the virtual filesystem tree.
|
||||
*/
|
||||
export function createFileSystemTestApp(runner: SchematicTestRunner) {
|
||||
const tempFileSystemHost = new TempScopedNodeJsSyncHost();
|
||||
const appTree = createTestApp(runner, {name: 'cdk-testing'});
|
||||
const tempPath = getSystemPath(tempFileSystemHost.root);
|
||||
|
||||
// Since the TSLint fix task expects all files to be present on the real file system, we
|
||||
// map every file in the app tree to a temporary location on the file system.
|
||||
appTree.files.map(f => normalize(f)).forEach(f => {
|
||||
tempFileSystemHost.sync.write(f, virtualFs.stringToFileBuffer(appTree.readContent(f)));
|
||||
});
|
||||
|
||||
return {appTree, tempPath};
|
||||
}
|
||||
|
||||
export async function runTestCases(migrationName: string, collectionPath: string,
|
||||
inputs: {[name: string]: string}) {
|
||||
|
||||
const runner = new SchematicTestRunner('schematics', collectionPath);
|
||||
const inputNames = Object.keys(inputs);
|
||||
const initialWorkingDir = process.cwd();
|
||||
|
||||
let logOutput = '';
|
||||
runner.logger.subscribe(entry => logOutput += entry.message);
|
||||
|
||||
const {appTree, tempPath} = createFileSystemTestApp(runner);
|
||||
|
||||
// Write each test-case input to the file-system. This is necessary because otherwise
|
||||
// TSLint won't be able to pick up the test cases.
|
||||
inputNames.forEach(inputName => {
|
||||
const tempInputPath = join(tempPath, `projects/cdk-testing/src/test-cases/${inputName}.ts`);
|
||||
|
||||
mkdirpSync(dirname(tempInputPath));
|
||||
writeFileSync(tempInputPath, readFileContent(inputs[inputName]));
|
||||
});
|
||||
|
||||
runner.runSchematic(migrationName, {}, appTree);
|
||||
|
||||
// Switch to the new temporary directory because otherwise TSLint cannot read the files.
|
||||
process.chdir(tempPath);
|
||||
|
||||
// Run the scheduled TSLint fix task from the update schematic. This task is responsible for
|
||||
// identifying outdated code parts and performs the fixes. Since tasks won't run automatically
|
||||
// within a `SchematicTestRunner`, we manually need to run the scheduled task.
|
||||
await runPostScheduledTasks(runner, 'tslint-fix').toPromise();
|
||||
|
||||
// Switch back to the initial working directory.
|
||||
process.chdir(initialWorkingDir);
|
||||
|
||||
return {tempPath, logOutput};
|
||||
}
|
||||
@@ -19,7 +19,8 @@
|
||||
},
|
||||
"exclude": [
|
||||
"**/files/**/*",
|
||||
"**/*.spec.ts"
|
||||
"**/*.spec.ts",
|
||||
"ng-update/test-cases/**/*"
|
||||
],
|
||||
"bazelOptions": {
|
||||
"suppressTsconfigOverrideWarnings": true
|
||||
|
||||
@@ -57,7 +57,6 @@ ts_library(
|
||||
"@npm//@angular-devkit/schematics",
|
||||
"@npm//@types/jasmine",
|
||||
"@npm//@types/node",
|
||||
"@npm//fs-extra",
|
||||
],
|
||||
tsconfig = ":tsconfig.json",
|
||||
testonly = True,
|
||||
|
||||
@@ -1,83 +1,2 @@
|
||||
import {getSystemPath, normalize} from '@angular-devkit/core';
|
||||
import {TempScopedNodeJsSyncHost} from '@angular-devkit/core/node/testing';
|
||||
import * as virtualFs from '@angular-devkit/core/src/virtual-fs/host';
|
||||
import {SchematicTestRunner} from '@angular-devkit/schematics/testing';
|
||||
import {readFileSync, writeFileSync, mkdirpSync} from 'fs-extra';
|
||||
import {join, dirname} from 'path';
|
||||
import {createTestApp, runPostScheduledTasks} from '@angular/cdk/schematics';
|
||||
|
||||
/** Path to the schematic collection that includes the Angular Material migrations. */
|
||||
const migrationCollection = require.resolve('../../migration.json');
|
||||
|
||||
/** Module name suffix for data files of the `jasmine_node_test` Bazel rule. */
|
||||
const bazelModuleSuffix = 'angular_material/src/lib/schematics/ng-update/test-cases';
|
||||
|
||||
/** Reads the UTF8 content of the specified file. Normalizes the path and ensures that */
|
||||
export function readFileContent(filePath: string): string {
|
||||
return readFileSync(filePath, 'utf8');
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the original file path of the specified file that has been to the `data` of the
|
||||
* jasmine_node_test Bazel rule.
|
||||
*
|
||||
* Adding the test case files to the data of the `jasmine_node_test` Bazel rule does not mean
|
||||
* that the files are being copied over to the Bazel bin output. Bazel just patches the NodeJS
|
||||
* resolve function and maps the module paths to the original file location.
|
||||
*/
|
||||
export function resolveBazelDataFile(filePath: string) {
|
||||
return require.resolve(`${bazelModuleSuffix}/${filePath}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a test app schematic tree that will be copied over to a real filesystem location.
|
||||
* This is necessary because TSLint is not able to read from the virtual filesystem tree.
|
||||
*/
|
||||
export function createFileSystemTestApp(runner: SchematicTestRunner) {
|
||||
const tempFileSystemHost = new TempScopedNodeJsSyncHost();
|
||||
const appTree = createTestApp(runner);
|
||||
const tempPath = getSystemPath(tempFileSystemHost.root);
|
||||
|
||||
// Since the TSLint fix task expects all files to be present on the real file system, we
|
||||
// map every file in the app tree to a temporary location on the file system.
|
||||
appTree.files.map(f => normalize(f)).forEach(f => {
|
||||
tempFileSystemHost.sync.write(f, virtualFs.stringToFileBuffer(appTree.readContent(f)));
|
||||
});
|
||||
|
||||
return {appTree, tempPath};
|
||||
}
|
||||
|
||||
export async function runTestCases(migrationName: string, inputs: {[name: string]: string}) {
|
||||
const runner = new SchematicTestRunner('schematics', migrationCollection);
|
||||
const inputNames = Object.keys(inputs);
|
||||
const initialWorkingDir = process.cwd();
|
||||
|
||||
let logOutput = '';
|
||||
runner.logger.subscribe(entry => logOutput += entry.message);
|
||||
|
||||
const {appTree, tempPath} = createFileSystemTestApp(runner);
|
||||
|
||||
// Write each test-case input to the file-system. This is necessary because otherwise
|
||||
// TSLint won't be able to pick up the test cases.
|
||||
inputNames.forEach(inputName => {
|
||||
const tempInputPath = join(tempPath, `projects/material/src/test-cases/${inputName}.ts`);
|
||||
|
||||
mkdirpSync(dirname(tempInputPath));
|
||||
writeFileSync(tempInputPath, readFileContent(inputs[inputName]));
|
||||
});
|
||||
|
||||
runner.runSchematic(migrationName, {}, appTree);
|
||||
|
||||
// Switch to the new temporary directory because otherwise TSLint cannot read the files.
|
||||
process.chdir(tempPath);
|
||||
|
||||
// Run the scheduled TSLint fix task from the update schematic. This task is responsible for
|
||||
// identifying outdated code parts and performs the fixes. Since tasks won't run automatically
|
||||
// within a `SchematicTestRunner`, we manually need to run the scheduled task.
|
||||
await runPostScheduledTasks(runner, 'tslint-fix').toPromise();
|
||||
|
||||
// Switch back to the initial working directory.
|
||||
process.chdir(initialWorkingDir);
|
||||
|
||||
return {tempPath, logOutput};
|
||||
}
|
||||
export const migrationCollection = require.resolve('../../migration.json');
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import {resolveBazelDataFile, runTestCases} from '../index.spec';
|
||||
import {migrationCollection} from '../index.spec';
|
||||
import {runTestCases} from '@angular/cdk/schematics';
|
||||
|
||||
describe('constructor checks', () => {
|
||||
|
||||
it('should properly report invalid constructor expression signatures', async () => {
|
||||
const {logOutput} = await runTestCases('migration-v6', {
|
||||
'constructor-checks': resolveBazelDataFile(`misc/constructor-checks_input.ts`)
|
||||
const {logOutput} = await runTestCases('migration-v6', migrationCollection, {
|
||||
'constructor-checks': require.resolve('./constructor-checks_input.ts')
|
||||
});
|
||||
|
||||
expect(logOutput).toMatch(/\[22.*Found "NativeDateAdapter"/,
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import {resolveBazelDataFile, runTestCases} from '../index.spec';
|
||||
import {runTestCases} from '@angular/cdk/schematics';
|
||||
import {migrationCollection} from '../index.spec';
|
||||
|
||||
describe('v6 import misc checks', () => {
|
||||
|
||||
it('should report imports for deleted animation constants', async () => {
|
||||
const {logOutput} = await runTestCases('migration-v6', {
|
||||
'import-checks': resolveBazelDataFile(`misc/import-checks_input.ts`)
|
||||
const {logOutput} = await runTestCases('migration-v6', migrationCollection, {
|
||||
'import-checks': require.resolve('./import-checks_input.ts')
|
||||
});
|
||||
|
||||
expect(logOutput).toMatch(/Found deprecated symbol "SHOW_ANIMATION"/);
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
import {resolveBazelDataFile, runTestCases} from '../index.spec';
|
||||
|
||||
describe('v6 method call checks', () => {
|
||||
|
||||
it('should properly report invalid method calls', async () => {
|
||||
const {logOutput} = await runTestCases('migration-v6', {
|
||||
'method-call-checks': resolveBazelDataFile(`misc/method-call-checks_input.ts`)
|
||||
});
|
||||
|
||||
// TODO(devversion): Move this test case to the CDK and assert that this failure has been
|
||||
// reported by TSLint.
|
||||
expect(logOutput)
|
||||
.not.toMatch(/\[15,.*Found call to "FocusMonitor\.monitor".*renderer.*has been removed/);
|
||||
expect(logOutput)
|
||||
.not.toMatch(/\[16,.*Found call to "FocusMonitor\.monitor".*renderer.*has been removed/);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import {join} from 'path';
|
||||
import {readFileContent, resolveBazelDataFile, runTestCases} from './index.spec';
|
||||
import {readFileContent, runTestCases} from '@angular/cdk/schematics';
|
||||
import {migrationCollection} from './index.spec';
|
||||
|
||||
describe('v6 upgrade test cases', () => {
|
||||
|
||||
@@ -21,19 +22,19 @@ describe('v6 upgrade test cases', () => {
|
||||
|
||||
beforeAll(async () => {
|
||||
const testCaseInputs = testCases.reduce((inputs, testCaseName) => {
|
||||
inputs[testCaseName] = resolveBazelDataFile(`${testCaseName}_input.ts`);
|
||||
inputs[testCaseName] = require.resolve(`./${testCaseName}_input.ts`);
|
||||
return inputs;
|
||||
}, {});
|
||||
}, {} as {[name: string]: string});
|
||||
|
||||
const {tempPath} = await runTestCases('migration-v6', testCaseInputs);
|
||||
const {tempPath} = await runTestCases('migration-v6', migrationCollection, testCaseInputs);
|
||||
|
||||
testCasesOutputPath = join(tempPath, 'projects/material/src/test-cases/');
|
||||
testCasesOutputPath = join(tempPath, 'projects/cdk-testing/src/test-cases/');
|
||||
});
|
||||
|
||||
// Iterates through every test case directory and generates a jasmine test block that will
|
||||
// verify that the update schematics properly updated the test input to the expected output.
|
||||
testCases.forEach(testCaseName => {
|
||||
const expectedOutputPath = resolveBazelDataFile(`${testCaseName}_expected_output.ts`);
|
||||
const expectedOutputPath = require.resolve(`./${testCaseName}_expected_output.ts`);
|
||||
|
||||
it(`should apply update schematics to test case: ${testCaseName}`, () => {
|
||||
expect(readFileContent(join(testCasesOutputPath, `${testCaseName}.ts`)))
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import {join} from 'path';
|
||||
import {readFileContent, resolveBazelDataFile, runTestCases} from './index.spec';
|
||||
import {readFileContent, runTestCases} from '@angular/cdk/schematics';
|
||||
import {migrationCollection} from './index.spec';
|
||||
|
||||
describe('v7 upgrade test cases', () => {
|
||||
|
||||
@@ -16,19 +17,19 @@ describe('v7 upgrade test cases', () => {
|
||||
|
||||
beforeAll(async () => {
|
||||
const testCaseInputs = testCases.reduce((inputs, testCaseName) => {
|
||||
inputs[testCaseName] = resolveBazelDataFile(`${testCaseName}_input.ts`);
|
||||
inputs[testCaseName] = require.resolve(`./${testCaseName}_input.ts`);
|
||||
return inputs;
|
||||
}, {});
|
||||
}, {} as {[name: string]: string});
|
||||
|
||||
const {tempPath} = await runTestCases('migration-v7', testCaseInputs);
|
||||
const {tempPath} = await runTestCases('migration-v7', migrationCollection, testCaseInputs);
|
||||
|
||||
testCasesOutputPath = join(tempPath, 'projects/material/src/test-cases/');
|
||||
testCasesOutputPath = join(tempPath, 'projects/cdk-testing/src/test-cases/');
|
||||
});
|
||||
|
||||
// Iterates through every test case directory and generates a jasmine test block that will
|
||||
// verify that the update schematics properly updated the test input to the expected output.
|
||||
testCases.forEach(testCaseName => {
|
||||
const expectedOutputPath = resolveBazelDataFile(`${testCaseName}_expected_output.ts`);
|
||||
const expectedOutputPath = require.resolve(`./${testCaseName}_expected_output.ts`);
|
||||
|
||||
it(`should apply update schematics to test case: ${testCaseName}`, () => {
|
||||
expect(readFileContent(join(testCasesOutputPath, `${testCaseName}.ts`)))
|
||||
|
||||
@@ -136,6 +136,7 @@
|
||||
// Exclude schematic template files and test cases that can't be linted.
|
||||
"src/lib/schematics/**/files/**/*",
|
||||
"src/cdk/schematics/**/files/**/*",
|
||||
"src/cdk/schematics/ng-update/test-cases/**/*",
|
||||
"src/lib/schematics/ng-update/test-cases/**/*"
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user