build: prepare schematics for v8 (#14667)

* Prepares the schematics for V8
* Dynamically resolves the test cases so that we don't need to hardcode them for each version
This commit is contained in:
Paul Gschwendtner
2019-01-09 19:42:08 +01:00
committed by Andrew Seguin
parent 0d7258b4e2
commit bb419d3fb4
16 changed files with 168 additions and 206 deletions
+5
View File
@@ -11,6 +11,11 @@
"description": "Updates the Angular CDK to v7",
"factory": "./ng-update/index#updateToV7"
},
"migration-v8": {
"version": "8",
"description": "Updates the Angular CDK to v8",
"factory": "./ng-update/index#updateToV8"
},
"ng-post-update": {
"description": "Prints out results after ng-update.",
"factory": "./ng-update/index#postUpdate",
+5
View File
@@ -35,6 +35,11 @@ export function updateToV7(): Rule {
return createUpgradeRule(TargetVersion.V7, tslintUpgradeConfig);
}
/** Entry point for the migration schematics with target of Angular Material 8.0.0 */
export function updateToV8(): Rule {
return createUpgradeRule(TargetVersion.V8, tslintUpgradeConfig);
}
/** Post-update schematic to be called when update is finished. */
export function postUpdate(): Rule {
return () => {
@@ -10,4 +10,14 @@
export enum TargetVersion {
V6,
V7,
V8,
}
/**
* Returns all versions that are supported by "ng update". The versions are determined
* based on the "TargetVersion" enum.
*/
export function getAllVersionNames(): string[] {
return Object.keys(TargetVersion)
.filter(enumValue => typeof TargetVersion[enumValue] === 'number');
}
@@ -1,2 +1,18 @@
import {defineJasmineTestCases, findBazelVersionTestCases} from '@angular/cdk/schematics/testing';
import {getAllVersionNames} from '../target-version';
/** Path to the schematic collection that includes the migrations. */
export const migrationCollection = require.resolve('../../migration.json');
describe('CDK upgrade test cases', () => {
const versionNames = getAllVersionNames().map(versionName => versionName.toLowerCase());
const testCasesMap = findBazelVersionTestCases(
'angular_material/src/cdk/schematics/ng-update/test-cases');
// Setup the test cases for each target version. The test cases will be automatically
// detected through Bazel's runfiles manifest.
versionNames.forEach(version => describe(`${version} update`, () => {
defineJasmineTestCases(version, migrationCollection, testCasesMap.get(version));
}));
});
@@ -4,9 +4,8 @@ import {runTestCases} from '../../../testing';
describe('v6 method call checks', () => {
it('should properly report invalid method calls', async () => {
const {logOutput, removeTempDir} = await runTestCases('migration-v6', migrationCollection, {
'method-call-checks': require.resolve('./method-call-checks_input.ts')
});
const {logOutput, removeTempDir} = await runTestCases('migration-v6', migrationCollection,
[require.resolve('./method-call-checks_input.ts')]);
expect(logOutput)
.toMatch(/:15.*Found call to "FocusMonitor\.monitor".*renderer.*has been removed/);
@@ -1,48 +0,0 @@
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;
let cleanupTestApp: () => void;
beforeAll(async () => {
const testCaseInputs = testCases.reduce((inputs, testCaseName) => {
inputs[testCaseName] = require.resolve(`./${testCaseName}_input.ts`);
return inputs;
}, {} as {[name: string]: string});
const {tempPath, removeTempDir} =
await runTestCases('migration-v6', migrationCollection, testCaseInputs);
testCasesOutputPath = join(tempPath, 'projects/cdk-testing/src/test-cases/');
cleanupTestApp = removeTempDir;
});
afterAll(() => cleanupTestApp());
// 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));
});
});
});
@@ -1,44 +0,0 @@
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;
let cleanupTestApp: () => void;
beforeAll(async () => {
const testCaseInputs = testCases.reduce((inputs, testCaseName) => {
inputs[testCaseName] = require.resolve(`./${testCaseName}_input.ts`);
return inputs;
}, {} as {[name: string]: string});
const {tempPath, removeTempDir} =
await runTestCases('migration-v7', migrationCollection, testCaseInputs);
testCasesOutputPath = join(tempPath, 'projects/cdk-testing/src/test-cases/');
cleanupTestApp = removeTempDir;
});
afterAll(() => cleanupTestApp());
// 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));
});
});
});
@@ -14,10 +14,11 @@ targets a specific Angular CDK or Angular Material version.
As of right now, we have two migration entry-points that handle the breaking changes for the
given target version:
| Target Version | Description |
|----------------|-------------|
| Target Version | Description |
|----------------|------------------------|
| V6 | Upgrade from any version to v6.0.0 |
| V7 | Upgrade from any version to v7.0.0 |
| V8 | Upgrade from any version to v8.0.0 |
Note that the migrations run _in order_ if multiple versions are transitively targeted. For
example, consider an application which uses Angular Material v5.0.0. In case the developer runs
+3
View File
@@ -6,12 +6,15 @@ ts_library(
name = "testing",
module_name = "@angular/cdk/schematics/testing",
srcs = glob(["**/*.ts"]),
tsconfig = "tsconfig.json",
deps = [
"@matdeps//@angular-devkit/core",
"@matdeps//@angular-devkit/schematics",
"@matdeps//@schematics/angular",
"@matdeps//@types/node",
"@matdeps//@types/fs-extra",
"@matdeps//@types/jasmine",
"@matdeps//@types/glob",
"@matdeps//fs-extra",
"@matdeps//rxjs",
],
+97 -6
View File
@@ -11,9 +11,16 @@ 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 {mkdirpSync, readFileSync, writeFileSync, removeSync} from 'fs-extra';
import {dirname, join} from 'path';
import {sync as globSync} from 'glob';
import {dirname, join, basename, relative, sep} from 'path';
import {createTestApp, runPostScheduledTasks} from '../testing';
/** Suffix that indicates whether a given file is a test case input. */
const TEST_CASE_INPUT_SUFFIX = '_input.ts';
/** Suffix that indicates whether a given file is an expected output of a test case. */
const TEST_CASE_OUTPUT_SUFFIX = '_expected_output.ts';
/** Reads the UTF8 content of the specified file. Normalizes the path and ensures that */
export function readFileContent(filePath: string): string {
return readFileSync(filePath, 'utf8');
@@ -38,10 +45,9 @@ export function createFileSystemTestApp(runner: SchematicTestRunner) {
}
export async function runTestCases(migrationName: string, collectionPath: string,
inputs: {[name: string]: string}) {
inputFiles: string[]) {
const runner = new SchematicTestRunner('schematics', collectionPath);
const inputNames = Object.keys(inputs);
const initialWorkingDir = process.cwd();
let logOutput = '';
@@ -51,11 +57,12 @@ export async function runTestCases(migrationName: string, collectionPath: string
// 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`);
inputFiles.forEach(inputFilePath => {
const inputTestName = basename(inputFilePath);
const tempInputPath = join(tempPath, `projects/cdk-testing/src/test-cases/${inputTestName}.ts`);
mkdirpSync(dirname(tempInputPath));
writeFileSync(tempInputPath, readFileContent(inputs[inputName]));
writeFileSync(tempInputPath, readFileContent(inputFilePath));
});
runner.runSchematic(migrationName, {}, appTree);
@@ -73,3 +80,87 @@ export async function runTestCases(migrationName: string, collectionPath: string
return {tempPath, logOutput, removeTempDir};
}
/**
* Resolves all test cases for specified path using Bazel's runfile manifest. Note that we
* cannot just use "glob" since the test case files are not copied to the Bazel bin directory
* and are just runfiles.
*/
export function findBazelVersionTestCases(basePath: string) {
const testCasesMap = new Map<string, string[]>();
const manifestPath = process.env['RUNFILES_MANIFEST_FILE']!;
const runfilesDir = process.env['RUNFILES'];
// In case we are not on Windows where runfiles are symlinked, we just find all
// test case files by using "glob" and store them in our result map.
if (!manifestPath) {
const runfilesBaseDir = join(runfilesDir, basePath);
const inputFiles = globSync(`**/*${TEST_CASE_INPUT_SUFFIX}`, {cwd: runfilesBaseDir});
inputFiles.forEach(inputFile => {
// The target version of an input file will be determined from the first
// path segment. (e.g. "v6/my_rule_input.ts" will be for "v6")
const targetVersion = inputFile.split(sep)[0];
const resolvedInputPath = join(runfilesBaseDir, inputFile);
testCasesMap.set(targetVersion,
(testCasesMap.get(targetVersion) || []).concat(resolvedInputPath));
});
return testCasesMap;
}
// In case runfiles are not symlinked (e.g. on Windows), we resolve all test case files using
// the Bazel runfiles manifest. Read more about the manifest here: https://git.io/fhIZE
readFileSync(manifestPath, 'utf8').split('\n').forEach(line => {
const [runfilePath, realPath] = line.split(' ');
// In case the mapped runfile starts with the specified base path and ends with "_input.ts",
// we store it in our result map because we assume that this is a test case.
if (runfilePath.startsWith(basePath) && runfilePath.endsWith(TEST_CASE_INPUT_SUFFIX)) {
// The target version of an input file will be determined from the first
// path segment. (e.g. "v6/my_rule_input.ts" will be for "v6")
const targetVersion = relative(basePath, runfilePath).split(sep)[0];
testCasesMap.set(targetVersion, (testCasesMap.get(targetVersion) || []).concat(realPath));
}
});
return testCasesMap;
}
/**
* Sets up the specified test cases using Jasmine by creating the appropriate jasmine
* spec definitions. This should be used within a "describe" jasmine closure.
*/
export function defineJasmineTestCases(versionName: string, collectionFile: string,
inputFiles: string[] | undefined) {
// No test cases for the given version are available. Skip setting up tests for that
// version.
if (!inputFiles) {
return;
}
let testCasesOutputPath: string;
let cleanupTestApp: () => void;
beforeAll(async () => {
const {tempPath, removeTempDir} =
await runTestCases(`migration-${versionName}`, collectionFile, inputFiles);
testCasesOutputPath = join(tempPath, 'projects/cdk-testing/src/test-cases/');
cleanupTestApp = removeTempDir;
});
afterAll(() => cleanupTestApp());
// 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.
inputFiles.forEach(inputFile => {
const inputTestName = basename(inputFile);
it(`should apply update schematics to test case: ${inputTestName}`, () => {
expect(readFileContent(join(testCasesOutputPath, `${inputTestName}.ts`)))
.toBe(readFileContent(inputFile.replace(TEST_CASE_INPUT_SUFFIX, TEST_CASE_OUTPUT_SUFFIX)));
});
});
}
+6
View File
@@ -0,0 +1,6 @@
{
"compilerOptions": {
"lib": ["es2015"],
"types": ["node", "jasmine", "glob"]
}
}
@@ -1,2 +1,18 @@
/** Path to the schematic collection that includes the Angular Material migrations. */
import {defineJasmineTestCases, findBazelVersionTestCases} from '@angular/cdk/schematics/testing';
import {getAllVersionNames} from '@angular/cdk/schematics';
/** Path to the schematic collection that includes the migrations. */
export const migrationCollection = require.resolve('../../migration.json');
describe('Material upgrade test cases', () => {
const versionNames = getAllVersionNames().map(versionName => versionName.toLowerCase());
const testCasesMap = findBazelVersionTestCases(
'angular_material/src/lib/schematics/ng-update/test-cases');
// Setup the test cases for each target version. The test cases will be automatically
// detected through Bazel's runfiles manifest.
versionNames.forEach(version => describe(`${version} update`, () => {
defineJasmineTestCases(version, migrationCollection, testCasesMap.get(version));
}));
});
@@ -4,9 +4,8 @@ import {runTestCases} from '@angular/cdk/schematics/testing';
describe('constructor checks', () => {
it('should properly report invalid constructor expression signatures', async () => {
const {logOutput, removeTempDir} = await runTestCases('migration-v6', migrationCollection, {
'constructor-checks': require.resolve('./constructor-checks_input.ts')
});
const {logOutput, removeTempDir} = await runTestCases('migration-v6', migrationCollection,
[require.resolve('./constructor-checks_input.ts')]);
expect(logOutput).toMatch(/:22.*Found "NativeDateAdapter"/,
'Expected the constructor checks to report if an argument is not assignable.');
@@ -4,9 +4,8 @@ import {migrationCollection} from '../index.spec';
describe('v6 import misc checks', () => {
it('should report imports for deleted animation constants', async () => {
const {logOutput, removeTempDir} = await runTestCases('migration-v6', migrationCollection, {
'import-checks': require.resolve('./import-checks_input.ts')
});
const {logOutput, removeTempDir} = await runTestCases('migration-v6', migrationCollection,
[require.resolve('./import-checks_input.ts')]);
expect(logOutput).toMatch(/Found deprecated symbol "SHOW_ANIMATION"/);
expect(logOutput).toMatch(/Found deprecated symbol "HIDE_ANIMATION"/);
@@ -1,51 +0,0 @@
import {join} from 'path';
import {readFileContent, runTestCases} from '@angular/cdk/schematics/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/css-selectors',
'v6/element-selectors',
'v6/input-names',
'v6/output-names',
'v6/property-names',
];
let testCasesOutputPath: string;
let cleanupTestApp: () => void;
beforeAll(async () => {
const testCaseInputs = testCases.reduce((inputs, testCaseName) => {
inputs[testCaseName] = require.resolve(`./${testCaseName}_input.ts`);
return inputs;
}, {} as {[name: string]: string});
const {tempPath, removeTempDir} =
await runTestCases('migration-v6', migrationCollection, testCaseInputs);
testCasesOutputPath = join(tempPath, 'projects/cdk-testing/src/test-cases/');
cleanupTestApp = removeTempDir;
});
afterAll(() => cleanupTestApp());
// 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));
});
});
});
@@ -1,45 +0,0 @@
import {join} from 'path';
import {readFileContent, runTestCases} from '@angular/cdk/schematics/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',
'v7/ripple-speed-factor',
];
let testCasesOutputPath: string;
let cleanupTestApp: () => void;
beforeAll(async () => {
const testCaseInputs = testCases.reduce((inputs, testCaseName) => {
inputs[testCaseName] = require.resolve(`./${testCaseName}_input.ts`);
return inputs;
}, {} as {[name: string]: string});
const {tempPath, removeTempDir} =
await runTestCases('migration-v7', migrationCollection, testCaseInputs);
testCasesOutputPath = join(tempPath, 'projects/cdk-testing/src/test-cases/');
cleanupTestApp = removeTempDir;
});
afterAll(() => cleanupTestApp());
// 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));
});
});
});