Skip to content

Commit

Permalink
feat(schematics): add v9 migration rules for dropdown (#4466)
Browse files Browse the repository at this point in the history
  • Loading branch information
hsuanxyz authored and Yadong Xie committed Nov 22, 2019
1 parent f82e8a6 commit aebad87
Show file tree
Hide file tree
Showing 6 changed files with 304 additions and 1 deletion.
8 changes: 7 additions & 1 deletion schematics/ng-update/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand All @@ -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. */
Expand Down
127 changes: 127 additions & 0 deletions schematics/ng-update/test-cases/v9/deprecated-class-dropdown.spec.ts
Original file line number Diff line number Diff line change
@@ -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<any> {
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: \`
<div
style="background: rgb(190, 200, 200); padding: 32px;text-align: center"
(contextmenu)="contextMenu($event, template)"
>
<ng-template #template>
<ul nz-menu nzInDropDown (nzClick)="close($event)">
<li nz-menu-item>1st menu item</li>
<li nz-submenu>
<span title>sub menu</span>
<ul>
<li nz-menu-item>4th menu item</li>
</ul>
</li>
</ul>
</ng-template>
<span style="color:#fff;font-size: 14px;">Context Menu</span>
</div>
\`,
styles: []
})
export class NzDemoDropdownContextMenuComponent {
private dropdown: NzDropdownContextComponent;
contextMenu($event: MouseEvent, template: TemplateRef<void>): 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);
})
});

});

});
Original file line number Diff line number Diff line change
@@ -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<any> {
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: \`
<nz-dropdown>
<a nz-dropdown> Hover me <i nz-icon type="down"></i> </a>
<ul nz-menu nzSelectable>
<li nz-menu-item>
<a>1st menu item</a>
</li>
</ul>
</nz-dropdown>
<nz-dropdown-button (nzClick)="log()">
DropDown
<ul nz-menu>
<li nz-menu-item>2nd menu item</li>
<li nz-submenu>
<span title>sub menu</span>
<ul>
<li nz-menu-item>3rd menu item</li>
</ul>
</li>
</ul>
</nz-dropdown-button>
\`
})
export class MyComp {
log() {}
}`);
await runMigration();

expect(warnOutput).toContain( 'index.ts@5:9 - Found deprecated "<nz-dropdown>" component. ' +
'Use "[nz-dropdown]" to instead please.');
expect(warnOutput).toContain( 'index.ts@13:9 - Found deprecated "<nz-dropdown-button>" component. ' +
'Use "[nz-dropdown]" to instead please.');
});

});

});
28 changes: 28 additions & 0 deletions schematics/ng-update/upgrade-rules/checks/dropdown-class-rule.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { MigrationRule, TargetVersion } from '@angular/cdk/schematics';
import * as ts from 'typescript';

export class DropdownClassRule extends MigrationRule<null> {

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.`);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import {
MigrationRule,
ResolvedResource, TargetVersion
} from '@angular/cdk/schematics';
import { findElementWithTag } from '../../../utils/ng-update/elements';

export class DropdownTemplateRule extends MigrationRule<null> {

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]');

}
}
7 changes: 7 additions & 0 deletions scripts/gulp/tasks/schematic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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' ]
));

0 comments on commit aebad87

Please sign in to comment.