Skip to content

Commit

Permalink
feat(schematics): add v9 migration rules for icon and calendar (#4467)
Browse files Browse the repository at this point in the history
  • Loading branch information
hsuanxyz authored Jan 8, 2020
1 parent fe8b469 commit 3c5d24e
Show file tree
Hide file tree
Showing 7 changed files with 324 additions and 3 deletions.
60 changes: 60 additions & 0 deletions schematics/ng-update/data/input-names.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,66 @@ export const inputNames: VersionChanges<InputNameUpgradeData> = {
}
}
]
},
{
pr : 'https://github.com/NG-ZORRO/ng-zorro-antd/pull/4375',
changes: [
{
replace : 'type',
replaceWith: 'nzType',
whitelist : {
attributes: ['nz-icon']
}
}
]
},
{
pr : 'https://github.com/NG-ZORRO/ng-zorro-antd/pull/4375',
changes: [
{
replace : 'iconfont',
replaceWith: 'nzIconfont',
whitelist : {
attributes: ['nz-icon']
}
}
]
},
{
pr : 'https://github.com/NG-ZORRO/ng-zorro-antd/pull/4375',
changes: [
{
replace : 'spin',
replaceWith: 'nzSpin',
whitelist : {
attributes: ['nz-icon']
}
}
]
},
{
pr : 'https://github.com/NG-ZORRO/ng-zorro-antd/pull/4375',
changes: [
{
replace : 'theme',
replaceWith: 'nzTheme',
whitelist : {
attributes: ['nz-icon']
}
}
]
},
{
pr : 'https://github.com/NG-ZORRO/ng-zorro-antd/pull/4375',
changes: [
{
replace : 'twoToneColor',
replaceWith: 'nzTwoToneColor',
whitelist : {
attributes: ['nz-icon']
}
}
]
}
]
};
14 changes: 11 additions & 3 deletions schematics/ng-update/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ import { Rule } from '@angular-devkit/schematics';
import { createUpgradeRule, TargetVersion } from '@angular/cdk/schematics';
import chalk from 'chalk';
import { ruleUpgradeData } from './upgrade-data';
import { CalendarTemplateRule } from './upgrade-rules/checks/calendar-input-rule';
import { DropdownClassRule } from './upgrade-rules/checks/dropdown-class-rule';
import { DropdownTemplateRule } from './upgrade-rules/checks/dropdown-template-rule';
import { IconTemplateRule } from './upgrade-rules/checks/icon-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 @@ -14,11 +16,17 @@ export function updateToV7(): Rule {
/** Entry point for the migration schematics with target of NG-ZORRO v9 */
export function updateToV9(): Rule {
return createUpgradeRule(
TargetVersion.V9, [
TargetVersion.V9,
[
TooltipLikeTemplateRule,
DropdownTemplateRule,
DropdownClassRule
], ruleUpgradeData, postUpdate);
DropdownClassRule,
IconTemplateRule,
CalendarTemplateRule
],
ruleUpgradeData,
postUpdate
);
}

/** Post-update schematic to be called when update is finished. */
Expand Down
85 changes: 85 additions & 0 deletions schematics/ng-update/test-cases/v9/input-names-calendar.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
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('calendar 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('Calendar', () => {

it('should properly report invalid deprecated input', async() => {
writeFile('/index.ts', `;
import {Component} from '@angular/core'
@Component({
template: \`
<nz-calendar nzCard></nz-calendar>
\`
})
export class MyComp {
}`);
await runMigration();

expect(warnOutput).toContain( 'index.ts@5:24 - Found deprecated input "nzCard" component. ' +
'Use "nzFullscreen" to instead please.');
});

});

});
103 changes: 103 additions & 0 deletions schematics/ng-update/test-cases/v9/input-names-icon.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
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('icon 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('Icon', () => {

it('should rename deprecated input names', async() => {
writeFile('/index.ts', `
import {Component} from '@angular/core';
@Component({template: \`
<i nz-icon [iconfont]="'value'" [spin]="true" [type]="'play'" theme="o"></i>
\`})
export class MyComp {
}
`);

await runMigration();
const content = tree.readContent('/index.ts');
expect(content).toContain(`[nzIconfont]="'value'"`);
expect(content).toContain(`[nzSpin]="true"`);
expect(content).toContain(`[nzType]="'play'"`);
expect(content).toContain(`nzTheme="o"`);
});

it('should properly report invalid deprecated css selector', async() => {
writeFile('/index.ts', `;
import {Component} from '@angular/core'
@Component({
template: \`
<i class="anticon play"></i>
\`
})
export class MyComp {
}`);
await runMigration();

expect(warnOutput).toContain( 'index.ts@5:14 - Found deprecated css selector "i.anticon" component. ' +
'Use "i[nz-icon]" to instead please.');
});

});

});
20 changes: 20 additions & 0 deletions schematics/ng-update/upgrade-rules/checks/calendar-input-rule.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { findInputsOnElementWithTag, MigrationRule, ResolvedResource, TargetVersion } from '@angular/cdk/schematics';
import { findElementWithClassName } from '../../../utils/ng-update/elements';

export class CalendarTemplateRule extends MigrationRule<null> {

ruleEnabled = this.targetVersion === TargetVersion.V9;

visitTemplate(template: ResolvedResource): void {

findInputsOnElementWithTag(template.content, 'nzCard', ['nz-calendar'])
.forEach(offset => {
this.failures.push({
filePath: template.filePath,
position: template.getCharacterAndLineOfPosition(offset),
message: `Found deprecated input "nzCard" component. Use "nzFullscreen" to instead please.`
});
})

}
}
20 changes: 20 additions & 0 deletions schematics/ng-update/upgrade-rules/checks/icon-template-rule.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { MigrationRule, ResolvedResource, TargetVersion } from '@angular/cdk/schematics';
import { findElementWithClassName } from '../../../utils/ng-update/elements';

export class IconTemplateRule extends MigrationRule<null> {

ruleEnabled = this.targetVersion === TargetVersion.V9;

visitTemplate(template: ResolvedResource): void {

findElementWithClassName(template.content, 'anticon', 'i')
.forEach(offset => {
this.failures.push({
filePath: template.filePath,
position: template.getCharacterAndLineOfPosition(offset),
message: `Found deprecated css selector "i.anticon" component. Use "i[nz-icon]" to instead please.`
});
})

}
}
25 changes: 25 additions & 0 deletions schematics/utils/ng-update/elements.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { parseFragment, DefaultTreeDocument, DefaultTreeElement } from 'parse5';

const hasClassName = (node: DefaultTreeElement, className: string) => {
return Array.isArray(node.attrs) && node.attrs.find(attr => attr.name === 'class' && attr.value.indexOf(className) !== -1)
};

export function findElementWithTag(html: string, tagName: string): number[] {
const document = parseFragment(html, {sourceCodeLocationInfo: true}) as DefaultTreeDocument;
const elements: DefaultTreeElement[] = [];
Expand All @@ -20,3 +24,24 @@ export function findElementWithTag(html: string, tagName: string): number[] {

return elements.map(element => element.sourceCodeLocation.startTag.startOffset)
}

export function findElementWithClassName(html: string, className: string, tagName: string): number[] {
const document = parseFragment(html, {sourceCodeLocationInfo: true}) as DefaultTreeDocument;
const elements: DefaultTreeElement[] = [];

const visitNodes = nodes => {
nodes.forEach(node => {
if (node.childNodes) {
visitNodes(node.childNodes);
}

if (hasClassName(node, className) && node.tagName && node.tagName.toLowerCase() === tagName.toLowerCase()) {
elements.push(node);
}
});
};

visitNodes(document.childNodes);

return elements.map(element => element.sourceCodeLocation.attrs.class.startOffset)
}

0 comments on commit 3c5d24e

Please sign in to comment.