diff --git a/src/cdk/schematics/BUILD.bazel b/src/cdk/schematics/BUILD.bazel index fbbd6da4210..db686759070 100644 --- a/src/cdk/schematics/BUILD.bazel +++ b/src/cdk/schematics/BUILD.bazel @@ -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, +) diff --git a/src/cdk/schematics/ng-update/test-cases/index.spec.ts b/src/cdk/schematics/ng-update/test-cases/index.spec.ts new file mode 100644 index 00000000000..45ca97c2664 --- /dev/null +++ b/src/cdk/schematics/ng-update/test-cases/index.spec.ts @@ -0,0 +1,2 @@ +/** Path to the schematic collection that includes the migrations. */ +export const migrationCollection = require.resolve('../../migration.json'); diff --git a/src/cdk/schematics/ng-update/test-cases/misc/method-call-checks.spec.ts b/src/cdk/schematics/ng-update/test-cases/misc/method-call-checks.spec.ts new file mode 100644 index 00000000000..37be49ece93 --- /dev/null +++ b/src/cdk/schematics/ng-update/test-cases/misc/method-call-checks.spec.ts @@ -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/); + }); +}); diff --git a/src/lib/schematics/ng-update/test-cases/misc/method-call-checks_input.ts b/src/cdk/schematics/ng-update/test-cases/misc/method-call-checks_input.ts similarity index 99% rename from src/lib/schematics/ng-update/test-cases/misc/method-call-checks_input.ts rename to src/cdk/schematics/ng-update/test-cases/misc/method-call-checks_input.ts index bf41d919e30..8008bb22b0b 100644 --- a/src/lib/schematics/ng-update/test-cases/misc/method-call-checks_input.ts +++ b/src/cdk/schematics/ng-update/test-cases/misc/method-call-checks_input.ts @@ -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); } -} +} \ No newline at end of file diff --git a/src/cdk/schematics/ng-update/test-cases/v6-test-cases.spec.ts b/src/cdk/schematics/ng-update/test-cases/v6-test-cases.spec.ts new file mode 100644 index 00000000000..5d0a68be683 --- /dev/null +++ b/src/cdk/schematics/ng-update/test-cases/v6-test-cases.spec.ts @@ -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)); + }); + }); +}); + + diff --git a/src/cdk/schematics/ng-update/test-cases/v6/attribute-selectors_expected_output.ts b/src/cdk/schematics/ng-update/test-cases/v6/attribute-selectors_expected_output.ts new file mode 100644 index 00000000000..f162584e957 --- /dev/null +++ b/src/cdk/schematics/ng-update/test-cases/v6/attribute-selectors_expected_output.ts @@ -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: ` +
+
+ `, + styles: [ + '[cdkPortalOutlet] {background: red}', + 'div[cdkPortalOutlet] {color: blue}' + ] +}) +class E {} + +@Component({ + template: ` +
+
+ `, + styles: [ + '[cdkPortalOutlet] {background: red}', + 'div[cdkPortalOutlet] {color: blue}' + ] +}) +class F {} \ No newline at end of file diff --git a/src/cdk/schematics/ng-update/test-cases/v6/attribute-selectors_input.ts b/src/cdk/schematics/ng-update/test-cases/v6/attribute-selectors_input.ts new file mode 100644 index 00000000000..2ba661c3553 --- /dev/null +++ b/src/cdk/schematics/ng-update/test-cases/v6/attribute-selectors_input.ts @@ -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: ` +
+
+ `, + styles: [ + '[cdkPortalHost] {background: red}', + 'div[cdkPortalHost] {color: blue}' + ] +}) +class E {} + +@Component({ + template: ` +
+
+ `, + styles: [ + '[portalHost] {background: red}', + 'div[portalHost] {color: blue}' + ] +}) +class F {} \ No newline at end of file diff --git a/src/cdk/schematics/ng-update/test-cases/v6/class-names_expected_output.ts b/src/cdk/schematics/ng-update/test-cases/v6/class-names_expected_output.ts new file mode 100644 index 00000000000..0ed0327d3d5 --- /dev/null +++ b/src/cdk/schematics/ng-update/test-cases/v6/class-names_expected_output.ts @@ -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(); diff --git a/src/cdk/schematics/ng-update/test-cases/v6/class-names_input.ts b/src/cdk/schematics/ng-update/test-cases/v6/class-names_input.ts new file mode 100644 index 00000000000..dc2fc838124 --- /dev/null +++ b/src/cdk/schematics/ng-update/test-cases/v6/class-names_input.ts @@ -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(); diff --git a/src/cdk/schematics/ng-update/test-cases/v6/input-names_expected_output.ts b/src/cdk/schematics/ng-update/test-cases/v6/input-names_expected_output.ts new file mode 100644 index 00000000000..54168124324 --- /dev/null +++ b/src/cdk/schematics/ng-update/test-cases/v6/input-names_expected_output.ts @@ -0,0 +1,21 @@ +import {Component} from '@angular/core'; + +@Component({ + template: ` + + + ` +}) +class A {} diff --git a/src/cdk/schematics/ng-update/test-cases/v6/input-names_input.ts b/src/cdk/schematics/ng-update/test-cases/v6/input-names_input.ts new file mode 100644 index 00000000000..eb66045b207 --- /dev/null +++ b/src/cdk/schematics/ng-update/test-cases/v6/input-names_input.ts @@ -0,0 +1,21 @@ +import {Component} from '@angular/core'; + +@Component({ + template: ` + + + ` +}) +class A {} diff --git a/src/cdk/schematics/ng-update/test-cases/v6/property-names_expected_output.ts b/src/cdk/schematics/ng-update/test-cases/v6/property-names_expected_output.ts new file mode 100644 index 00000000000..623778a952d --- /dev/null +++ b/src/cdk/schematics/ng-update/test-cases/v6/property-names_expected_output.ts @@ -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); + } + } +} \ No newline at end of file diff --git a/src/cdk/schematics/ng-update/test-cases/v6/property-names_input.ts b/src/cdk/schematics/ng-update/test-cases/v6/property-names_input.ts new file mode 100644 index 00000000000..6bfd787a2eb --- /dev/null +++ b/src/cdk/schematics/ng-update/test-cases/v6/property-names_input.ts @@ -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); + } + } +} \ No newline at end of file diff --git a/src/cdk/schematics/ng-update/test-cases/v7-test-cases.spec.ts b/src/cdk/schematics/ng-update/test-cases/v7-test-cases.spec.ts new file mode 100644 index 00000000000..ab4d02b8dd5 --- /dev/null +++ b/src/cdk/schematics/ng-update/test-cases/v7-test-cases.spec.ts @@ -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)); + }); + }); +}); + diff --git a/src/cdk/schematics/ng-update/test-cases/v7/property-names_expected_output.ts b/src/cdk/schematics/ng-update/test-cases/v7/property-names_expected_output.ts new file mode 100644 index 00000000000..fb4c6314fb1 --- /dev/null +++ b/src/cdk/schematics/ng-update/test-cases/v7/property-names_expected_output.ts @@ -0,0 +1,28 @@ +import {EventEmitter, OnInit} from '@angular/core'; + +class SelectionModel { + onChange = new EventEmitter(); +} + +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'; + } +} \ No newline at end of file diff --git a/src/cdk/schematics/ng-update/test-cases/v7/property-names_input.ts b/src/cdk/schematics/ng-update/test-cases/v7/property-names_input.ts new file mode 100644 index 00000000000..c3dfdc3781f --- /dev/null +++ b/src/cdk/schematics/ng-update/test-cases/v7/property-names_input.ts @@ -0,0 +1,28 @@ +import {EventEmitter, OnInit} from '@angular/core'; + +class SelectionModel { + onChange = new EventEmitter(); +} + +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'; + } +} \ No newline at end of file diff --git a/src/cdk/schematics/testing/index.ts b/src/cdk/schematics/testing/index.ts index 1141e3fde64..900f9c06084 100644 --- a/src/cdk/schematics/testing/index.ts +++ b/src/cdk/schematics/testing/index.ts @@ -8,3 +8,4 @@ export * from './post-scheduled-tasks'; export * from './test-app'; +export * from './test-case-setup'; diff --git a/src/cdk/schematics/testing/test-app.ts b/src/cdk/schematics/testing/test-app.ts index b1b87bfd596..8bc1399a5da 100644 --- a/src/cdk/schematics/testing/test-app.ts +++ b/src/cdk/schematics/testing/test-app.ts @@ -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); } diff --git a/src/cdk/schematics/testing/test-case-setup.ts b/src/cdk/schematics/testing/test-case-setup.ts new file mode 100644 index 00000000000..9099d325d1a --- /dev/null +++ b/src/cdk/schematics/testing/test-case-setup.ts @@ -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}; +} diff --git a/src/cdk/schematics/tsconfig.json b/src/cdk/schematics/tsconfig.json index 137ad9553d8..50dee289ddd 100644 --- a/src/cdk/schematics/tsconfig.json +++ b/src/cdk/schematics/tsconfig.json @@ -19,7 +19,8 @@ }, "exclude": [ "**/files/**/*", - "**/*.spec.ts" + "**/*.spec.ts", + "ng-update/test-cases/**/*" ], "bazelOptions": { "suppressTsconfigOverrideWarnings": true diff --git a/src/lib/schematics/BUILD.bazel b/src/lib/schematics/BUILD.bazel index 061bc48922c..96bfc45107e 100644 --- a/src/lib/schematics/BUILD.bazel +++ b/src/lib/schematics/BUILD.bazel @@ -57,7 +57,6 @@ ts_library( "@npm//@angular-devkit/schematics", "@npm//@types/jasmine", "@npm//@types/node", - "@npm//fs-extra", ], tsconfig = ":tsconfig.json", testonly = True, diff --git a/src/lib/schematics/ng-update/test-cases/index.spec.ts b/src/lib/schematics/ng-update/test-cases/index.spec.ts index b2990c39d60..40ec043f89e 100644 --- a/src/lib/schematics/ng-update/test-cases/index.spec.ts +++ b/src/lib/schematics/ng-update/test-cases/index.spec.ts @@ -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'); diff --git a/src/lib/schematics/ng-update/test-cases/misc/constructor-checks.spec.ts b/src/lib/schematics/ng-update/test-cases/misc/constructor-checks.spec.ts index b702af39517..f71a602936b 100644 --- a/src/lib/schematics/ng-update/test-cases/misc/constructor-checks.spec.ts +++ b/src/lib/schematics/ng-update/test-cases/misc/constructor-checks.spec.ts @@ -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"/, diff --git a/src/lib/schematics/ng-update/test-cases/misc/import-checks.spec.ts b/src/lib/schematics/ng-update/test-cases/misc/import-checks.spec.ts index 25a8029700e..46d53b7502f 100644 --- a/src/lib/schematics/ng-update/test-cases/misc/import-checks.spec.ts +++ b/src/lib/schematics/ng-update/test-cases/misc/import-checks.spec.ts @@ -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"/); diff --git a/src/lib/schematics/ng-update/test-cases/misc/method-call-checks.spec.ts b/src/lib/schematics/ng-update/test-cases/misc/method-call-checks.spec.ts deleted file mode 100644 index 5a2f00f9842..00000000000 --- a/src/lib/schematics/ng-update/test-cases/misc/method-call-checks.spec.ts +++ /dev/null @@ -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/); - }); -}); - - diff --git a/src/lib/schematics/ng-update/test-cases/v6-test-cases.spec.ts b/src/lib/schematics/ng-update/test-cases/v6-test-cases.spec.ts index 0764e64e754..743773d0817 100644 --- a/src/lib/schematics/ng-update/test-cases/v6-test-cases.spec.ts +++ b/src/lib/schematics/ng-update/test-cases/v6-test-cases.spec.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('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`))) diff --git a/src/lib/schematics/ng-update/test-cases/v7-test-cases.spec.ts b/src/lib/schematics/ng-update/test-cases/v7-test-cases.spec.ts index bbdc6b5a577..fbc59cc52a1 100644 --- a/src/lib/schematics/ng-update/test-cases/v7-test-cases.spec.ts +++ b/src/lib/schematics/ng-update/test-cases/v7-test-cases.spec.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`))) diff --git a/tslint.json b/tslint.json index b6f248504e7..9b641777a4b 100644 --- a/tslint.json +++ b/tslint.json @@ -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/**/*" ] }