Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(material/schematics): add chips template migrator (#24601)
* feat(material/schematics): add chips template migrator * refactor some of the template migrator logic * impl ChipsTemplateMigrator and added it to list of migrators * remove unnecessary class vars from TemplateMigrator * unit tested
- Loading branch information
1 parent
4792672
commit 825688f
Showing
6 changed files
with
237 additions
and
33 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
96 changes: 96 additions & 0 deletions
96
...terial/schematics/ng-generate/mdc-migration/rules/components/chips/chips-template.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
import {createTestApp, patchDevkitTreeToExposeTypeScript} from '@angular/cdk/schematics/testing'; | ||
import {SchematicTestRunner, UnitTestTree} from '@angular-devkit/schematics/testing'; | ||
import {createNewTestRunner, migrateComponents, TEMPLATE_FILE} from '../test-setup-helper'; | ||
|
||
describe('chips template migrator', () => { | ||
let runner: SchematicTestRunner; | ||
let cliAppTree: UnitTestTree; | ||
|
||
async function runMigrationTest(oldFileContent: string, newFileContent: string) { | ||
cliAppTree.overwrite(TEMPLATE_FILE, oldFileContent); | ||
const tree = await migrateComponents(['chips'], runner, cliAppTree); | ||
expect(tree.readContent(TEMPLATE_FILE)).toBe(newFileContent); | ||
} | ||
|
||
beforeEach(async () => { | ||
runner = createNewTestRunner(); | ||
cliAppTree = patchDevkitTreeToExposeTypeScript(await createTestApp(runner)); | ||
}); | ||
|
||
it('should not update other elements', async () => { | ||
await runMigrationTest('<mat-button></mat-button>', '<mat-button></mat-button>'); | ||
}); | ||
|
||
it('should update list to listbox', async () => { | ||
await runMigrationTest( | ||
'<mat-chip-list></mat-chip-list>', | ||
'<mat-chip-listbox></mat-chip-listbox>', | ||
); | ||
}); | ||
|
||
it('should update list to grid if referenced by an input', async () => { | ||
await runMigrationTest( | ||
` | ||
<mat-chip-list #chipList> | ||
<input [matChipInputFor]="chipList"> | ||
</mat-chip-list> | ||
`, | ||
` | ||
<mat-chip-grid #chipList> | ||
<input [matChipInputFor]="chipList"> | ||
</mat-chip-grid> | ||
`, | ||
); | ||
}); | ||
|
||
it('should update mat-chip inside a listbox to option', async () => { | ||
await runMigrationTest( | ||
` | ||
<mat-chip-list> | ||
<mat-chip>One</mat-chip> | ||
<mat-chip>Two</mat-chip> | ||
<mat-chip>Three</mat-chip> | ||
</mat-chip-list> | ||
`, | ||
` | ||
<mat-chip-listbox> | ||
<mat-chip-option>One</mat-chip-option> | ||
<mat-chip-option>Two</mat-chip-option> | ||
<mat-chip-option>Three</mat-chip-option> | ||
</mat-chip-listbox> | ||
`, | ||
); | ||
}); | ||
|
||
it('should update mat-chip inside a grid to row', async () => { | ||
await runMigrationTest( | ||
` | ||
<mat-chip-list #chipList> | ||
<mat-chip>One</mat-chip> | ||
<mat-chip>Two</mat-chip> | ||
<mat-chip>Three</mat-chip> | ||
<input [matChipInputFor]="chipList"> | ||
</mat-chip-list> | ||
`, | ||
` | ||
<mat-chip-grid #chipList> | ||
<mat-chip-row>One</mat-chip-row> | ||
<mat-chip-row>Two</mat-chip-row> | ||
<mat-chip-row>Three</mat-chip-row> | ||
<input [matChipInputFor]="chipList"> | ||
</mat-chip-grid> | ||
`, | ||
); | ||
}); | ||
|
||
it('should update list to listbox correctly even if it has a ref', async () => { | ||
await runMigrationTest( | ||
'<mat-chip-list #chipList></mat-chip-list>', | ||
'<mat-chip-listbox #chipList></mat-chip-listbox>', | ||
); | ||
}); | ||
|
||
it('should update standalone chips', async () => { | ||
await runMigrationTest('<mat-chip></mat-chip>', '<mat-chip-option></mat-chip-option>'); | ||
}); | ||
}); |
121 changes: 121 additions & 0 deletions
121
src/material/schematics/ng-generate/mdc-migration/rules/components/chips/chips-template.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
/** | ||
* @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 * as compiler from '@angular/compiler'; | ||
import {TemplateMigrator, Update} from '../../template-migrator'; | ||
import {replaceStartTag, replaceEndTag, visitElements} from '../../tree-traversal'; | ||
|
||
/** Stores a mat-chip-list with the mat-chip elements nested within it. */ | ||
interface ChipMap { | ||
chipList: compiler.TmplAstElement; | ||
chips: compiler.TmplAstElement[]; | ||
} | ||
|
||
export class ChipsTemplateMigrator extends TemplateMigrator { | ||
/** Stores the mat-chip-list elements with their nested mat-chip elements. */ | ||
chipMap?: ChipMap; | ||
|
||
/** All of the ChipMaps found while parsing a template AST. */ | ||
chipMaps: ChipMap[] = []; | ||
|
||
/** Chips that are not nested within mat-chip elements. */ | ||
standaloneChips: compiler.TmplAstElement[] = []; | ||
|
||
/** Input elements that have matChipInputFor attributes. */ | ||
chipInputs: compiler.TmplAstBoundAttribute[] = []; | ||
|
||
getUpdates(ast: compiler.ParsedTemplate): Update[] { | ||
this._gatherDomData(ast); | ||
const updates: Update[] = []; | ||
this.chipMaps.forEach(chipMap => { | ||
if (this._isChipGrid(chipMap.chipList)) { | ||
updates.push(...this._buildUpdatesForChipMap(chipMap, 'mat-chip-grid', 'mat-chip-row')); | ||
return; | ||
} | ||
updates.push(...this._buildUpdatesForChipMap(chipMap, 'mat-chip-listbox', 'mat-chip-option')); | ||
}); | ||
this.standaloneChips.forEach(chip => { | ||
updates.push(...this._buildTagUpdates(chip, 'mat-chip-option')); | ||
}); | ||
return updates; | ||
} | ||
|
||
/** Traverses the AST and stores all relevant DOM data needed for building updates. */ | ||
private _gatherDomData(ast: compiler.ParsedTemplate): void { | ||
this.chipMap = undefined; | ||
this.chipMaps = []; | ||
this.standaloneChips = []; | ||
this.chipInputs = []; | ||
|
||
visitElements( | ||
ast.nodes, | ||
(node: compiler.TmplAstElement) => { | ||
switch (node.name) { | ||
case 'input': | ||
this._handleInputNode(node); | ||
break; | ||
case 'mat-chip-list': | ||
this.chipMap = {chipList: node, chips: []}; | ||
break; | ||
case 'mat-chip': | ||
this.chipMap ? this.chipMap.chips.push(node) : this.standaloneChips.push(node); | ||
} | ||
}, | ||
(node: compiler.TmplAstElement) => { | ||
if (node.name === 'mat-chip-list') { | ||
this.chipMaps.push(this.chipMap!); | ||
this.chipMap = undefined; | ||
} | ||
}, | ||
); | ||
} | ||
|
||
/** Returns the mat-chip-list and mat-chip updates for the given ChipMap. */ | ||
private _buildUpdatesForChipMap( | ||
chipMap: ChipMap, | ||
chipListTagName: string, | ||
chipTagName: string, | ||
): Update[] { | ||
const updates: Update[] = []; | ||
updates.push(...this._buildTagUpdates(chipMap.chipList, chipListTagName)); | ||
chipMap.chips.forEach(chip => updates.push(...this._buildTagUpdates(chip, chipTagName))); | ||
return updates; | ||
} | ||
|
||
/** Creates and returns the start and end tag updates for the given node. */ | ||
private _buildTagUpdates(node: compiler.TmplAstElement, tagName: string): Update[] { | ||
return [ | ||
{ | ||
location: node.startSourceSpan.start, | ||
updateFn: html => replaceStartTag(html, node, tagName), | ||
}, | ||
{ | ||
location: node.endSourceSpan!.start, | ||
updateFn: html => replaceEndTag(html, node, tagName), | ||
}, | ||
]; | ||
} | ||
|
||
/** Stores the given input node if it has a matChipInputFor attribute. */ | ||
private _handleInputNode(node: compiler.TmplAstElement): void { | ||
node.inputs.forEach(attr => { | ||
if (attr.name === 'matChipInputFor') { | ||
this.chipInputs.push(attr); | ||
} | ||
}); | ||
} | ||
|
||
/** Returns true if the given mat-chip-list is referenced by any inputs. */ | ||
private _isChipGrid(node: compiler.TmplAstElement): boolean { | ||
return node.references.some(ref => { | ||
return this.chipInputs.some(attr => { | ||
return ref.name === (attr.value as compiler.ASTWithSource).source; | ||
}); | ||
}); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters