diff --git a/schematics/ng-update/index.ts b/schematics/ng-update/index.ts index ab902695192..f196110277e 100644 --- a/schematics/ng-update/index.ts +++ b/schematics/ng-update/index.ts @@ -2,6 +2,8 @@ import { Rule } from '@angular-devkit/schematics'; import { createUpgradeRule, TargetVersion } from '@angular/cdk/schematics'; import chalk from 'chalk'; import { ruleUpgradeData } from './upgrade-data'; +import { DropdownClassRule } from './upgrade-rules/checks/dropdown-class-rule'; +import { DropdownTemplateRule } from './upgrade-rules/checks/dropdown-template-rule'; import { TooltipLikeTemplateRule } from './upgrade-rules/checks/tooltip-like-template-rule'; /** Entry point for the migration schematics with target of NG-ZORRO v7 */ @@ -12,7 +14,11 @@ export function updateToV7(): Rule { /** Entry point for the migration schematics with target of NG-ZORRO v9 */ export function updateToV9(): Rule { return createUpgradeRule( - TargetVersion.V9, [TooltipLikeTemplateRule], ruleUpgradeData, postUpdate); + TargetVersion.V9, [ + TooltipLikeTemplateRule, + DropdownTemplateRule, + DropdownClassRule + ], ruleUpgradeData, postUpdate); } /** Post-update schematic to be called when update is finished. */ diff --git a/schematics/ng-update/test-cases/v9/deprecated-class-dropdown.spec.ts b/schematics/ng-update/test-cases/v9/deprecated-class-dropdown.spec.ts new file mode 100644 index 00000000000..cc2377c539d --- /dev/null +++ b/schematics/ng-update/test-cases/v9/deprecated-class-dropdown.spec.ts @@ -0,0 +1,127 @@ +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('dropdown class migration', () => { + let runner: SchematicTestRunner; + let host: TempScopedNodeJsSyncHost; + let tree: UnitTestTree; + let tmpDirPath: string; + let previousWorkingDir: string; + let warnOutput: string[]; + let errorOutput: string[]; + + beforeEach(() => { + runner = new SchematicTestRunner('test', require.resolve('../../../migration.json')); + host = new TempScopedNodeJsSyncHost(); + tree = new UnitTestTree(new HostTree(host)); + + writeFile('/tsconfig.json', JSON.stringify({ + compilerOptions: { + experimentalDecorators: true, + lib: ['es2015'] + } + })); + writeFile('/angular.json', JSON.stringify({ + projects: {t: {architect: {build: {options: {tsConfig: './tsconfig.json'}}}}} + })); + + warnOutput = []; + errorOutput = []; + runner.logger.subscribe(logEntry => { + if (logEntry.level === 'warn') { + warnOutput.push(logEntry.message); + } else if (logEntry.level === 'error') { + errorOutput.push(logEntry.message); + } + }); + + previousWorkingDir = shx.pwd(); + tmpDirPath = getSystemPath(host.root); + + shx.cd(tmpDirPath); + + writeFakeAngular(); + }); + + afterEach(() => { + shx.cd(previousWorkingDir); + shx.rm('-r', tmpDirPath); + }); + + function writeFakeAngular(): void { writeFile('/node_modules/@angular/core/index.d.ts', ``); } + + function writeFile(filePath: string, contents: string): void { + host.sync.write(normalize(filePath), virtualFs.stringToFileBuffer(contents)); + } + + // tslint:disable-next-line:no-any + async function runMigration(): Promise { + await runner.runSchematicAsync('migration-v9', {}, tree).toPromise(); + } + + describe('dropdown class', () => { + + it('should properly report invalid deprecated class', async() => { + writeFile('/index.ts', ` + import { Component, TemplateRef } from '@angular/core'; + import { NzDropdownContextComponent, NzDropdownService, NzMenuItemDirective } from 'ng-zorro-antd'; + + @Component({ + selector: 'nz-demo-dropdown-context-menu', + template: \` +
+ +
    +
  • 1st menu item
  • +
  • + sub menu +
      +
    • 4th menu item
    • +
    +
  • +
+
+ Context Menu +
+ \`, + styles: [] + }) + export class NzDemoDropdownContextMenuComponent { + private dropdown: NzDropdownContextComponent; + + contextMenu($event: MouseEvent, template: TemplateRef): void { + this.dropdown = this.nzDropdownService.create($event, template); + } + + close(e: NzMenuItemDirective): void { + console.log(e); + this.dropdown.close(); + } + + constructor(private nzDropdownService: NzDropdownService) {} + }`); + await runMigration(); + + const messages = [ + 'index.ts@3:16 - Found "NzDropdownContextComponent" which has been removed. Your code need to be updated.', + 'index.ts@3:44 - Found usage of "NzDropdownService" which has been removed. Please use "NzContextMenuService" ' + + 'instead.', + 'index.ts@29:27 - Found "NzDropdownContextComponent" which has been removed. Your code need to be updated.', + 'index.ts@40:48 - Found usage of "NzDropdownService" which has been removed. Please use "NzContextMenuService" ' + + 'instead.' + ]; + + messages.forEach(message => { + expect(warnOutput).toContain( message); + }) + }); + + }); + +}); diff --git a/schematics/ng-update/test-cases/v9/deprecated-component-dropdown.spec.ts b/schematics/ng-update/test-cases/v9/deprecated-component-dropdown.spec.ts new file mode 100644 index 00000000000..bf016d5a1ad --- /dev/null +++ b/schematics/ng-update/test-cases/v9/deprecated-component-dropdown.spec.ts @@ -0,0 +1,107 @@ +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('dropdown components migration', () => { + let runner: SchematicTestRunner; + let host: TempScopedNodeJsSyncHost; + let tree: UnitTestTree; + let tmpDirPath: string; + let previousWorkingDir: string; + let warnOutput: string[]; + let errorOutput: string[]; + + beforeEach(() => { + runner = new SchematicTestRunner('test', require.resolve('../../../migration.json')); + host = new TempScopedNodeJsSyncHost(); + tree = new UnitTestTree(new HostTree(host)); + + writeFile('/tsconfig.json', JSON.stringify({ + compilerOptions: { + experimentalDecorators: true, + lib: ['es2015'] + } + })); + writeFile('/angular.json', JSON.stringify({ + projects: {t: {architect: {build: {options: {tsConfig: './tsconfig.json'}}}}} + })); + + warnOutput = []; + errorOutput = []; + runner.logger.subscribe(logEntry => { + if (logEntry.level === 'warn') { + warnOutput.push(logEntry.message); + } else if (logEntry.level === 'error') { + errorOutput.push(logEntry.message); + } + }); + + previousWorkingDir = shx.pwd(); + tmpDirPath = getSystemPath(host.root); + + shx.cd(tmpDirPath); + + writeFakeAngular(); + }); + + afterEach(() => { + shx.cd(previousWorkingDir); + shx.rm('-r', tmpDirPath); + }); + + function writeFakeAngular(): void { writeFile('/node_modules/@angular/core/index.d.ts', ``); } + + function writeFile(filePath: string, contents: string): void { + host.sync.write(normalize(filePath), virtualFs.stringToFileBuffer(contents)); + } + + // tslint:disable-next-line:no-any + async function runMigration(): Promise { + await runner.runSchematicAsync('migration-v9', {}, tree).toPromise(); + } + + describe('dropdown components', () => { + + it('should properly report invalid deprecated component', async() => { + writeFile('/index.ts', `; + import {Component} from '@angular/core' + @Component({ + template: \` + + Hover me + + + + DropDown +
    +
  • 2nd menu item
  • +
  • + sub menu +
      +
    • 3rd menu item
    • +
    +
  • +
+
+ \` + }) + export class MyComp { + log() {} + }`); + await runMigration(); + + expect(warnOutput).toContain( 'index.ts@5:9 - Found deprecated "" component. ' + + 'Use "[nz-dropdown]" to instead please.'); + expect(warnOutput).toContain( 'index.ts@13:9 - Found deprecated "" component. ' + + 'Use "[nz-dropdown]" to instead please.'); + }); + + }); + +}); diff --git a/schematics/ng-update/upgrade-rules/checks/dropdown-class-rule.ts b/schematics/ng-update/upgrade-rules/checks/dropdown-class-rule.ts new file mode 100644 index 00000000000..096fefbfa86 --- /dev/null +++ b/schematics/ng-update/upgrade-rules/checks/dropdown-class-rule.ts @@ -0,0 +1,28 @@ +import { MigrationRule, TargetVersion } from '@angular/cdk/schematics'; +import * as ts from 'typescript'; + +export class DropdownClassRule extends MigrationRule { + + ruleEnabled = this.targetVersion === TargetVersion.V9; + + visitNode(node: ts.Node): void { + if (ts.isIdentifier(node)) { + this._visitIdentifier(node); + } + } + + // tslint:disable-next-line:typedef + private _visitIdentifier(identifier: ts.Identifier) { + if (identifier.getText() === 'NzDropdownContextComponent') { + this.createFailureAtNode( + identifier, + `Found "NzDropdownContextComponent" which has been removed. Your code need to be updated.`); + } + + if (identifier.getText() === 'NzDropdownService') { + this.createFailureAtNode( + identifier, + `Found usage of "NzDropdownService" which has been removed. Please use "NzContextMenuService" instead.`); + } + } +} diff --git a/schematics/ng-update/upgrade-rules/checks/dropdown-template-rule.ts b/schematics/ng-update/upgrade-rules/checks/dropdown-template-rule.ts new file mode 100644 index 00000000000..3b546e2d7d7 --- /dev/null +++ b/schematics/ng-update/upgrade-rules/checks/dropdown-template-rule.ts @@ -0,0 +1,28 @@ +import { + MigrationRule, + ResolvedResource, TargetVersion +} from '@angular/cdk/schematics'; +import { findElementWithTag } from '../../../utils/ng-update/elements'; + +export class DropdownTemplateRule extends MigrationRule { + + ruleEnabled = this.targetVersion === TargetVersion.V9; + + visitTemplate(template: ResolvedResource): void { + + const deprecatedComponent = (deprecated: string, instead: string) => { + findElementWithTag(template.content, deprecated) + .forEach(offset => { + this.failures.push({ + filePath: template.filePath, + position: template.getCharacterAndLineOfPosition(offset), + message: `Found deprecated "<${deprecated}>" component. Use "${instead}" to instead please.` + }); + }) + }; + + deprecatedComponent('nz-dropdown', '[nz-dropdown]'); + deprecatedComponent('nz-dropdown-button', '[nz-dropdown]'); + + } +} diff --git a/scripts/gulp/tasks/schematic.ts b/scripts/gulp/tasks/schematic.ts index 8f57451c5af..6eef95f56b9 100644 --- a/scripts/gulp/tasks/schematic.ts +++ b/scripts/gulp/tasks/schematic.ts @@ -53,3 +53,10 @@ task('test:schematics', execNodeTask( 'jasmine', [ 'publish/schematics/**/*.spec.js' ] )); + +/** Test the ng-update schematics */ +task('test:schematics-update', execNodeTask( + 'jasmine', + 'jasmine', + [ 'publish/schematics/ng-update/**/*.spec.js' ] +)); \ No newline at end of file