Skip to content

Commit

Permalink
fix(material/schematics): not migrating elements with template direct…
Browse files Browse the repository at this point in the history
…ives

The MDC migration wasn't traversing into template elements which meant that it wouldn't migrate elements with an `*ngIf` or an `*ngFor` on them.

Fixes #25824.
  • Loading branch information
crisbeto committed Nov 11, 2022
1 parent 1e56524 commit 73d5151
Show file tree
Hide file tree
Showing 4 changed files with 120 additions and 5 deletions.
Expand Up @@ -15,12 +15,12 @@ <h2>Chips example</h2>
<mat-form-field class="example-chip-list" appearance="fill">
<mat-label>Favorite Fruits</mat-label>
<mat-chip-grid #fruitChipList aria-label="Fruit selection">
<mat-chip *ngFor="let fruit of fruits" (removed)="remove(fruit)">
<mat-chip-row *ngFor="let fruit of fruits" (removed)="remove(fruit)">
{{fruit.name}}
<button matChipRemove>
<mat-icon>cancel</mat-icon>
</button>
</mat-chip>
</mat-chip-row>
<input placeholder="New fruit..."
[matChipInputFor]="fruitChipList"
[matChipInputSeparatorKeyCodes]="separatorKeysCodes"
Expand Down
Expand Up @@ -93,4 +93,66 @@ describe('chips template migrator', () => {
it('should update standalone chips', async () => {
await runMigrationTest('<mat-chip></mat-chip>', '<mat-chip-option></mat-chip-option>');
});

it('should update mat-chip with an *ngFor', async () => {
await runMigrationTest(
`
<mat-chip-list>
<mat-chip *ngFor="let chip of chips">{{chip}}</mat-chip>
</mat-chip-list>
`,
`
<mat-chip-listbox>
<mat-chip-option *ngFor="let chip of chips">{{chip}}</mat-chip-option>
</mat-chip-listbox>
`,
);
});

it('should update a chip listbox with a nested ng-container', async () => {
await runMigrationTest(
`
<mat-chip-list>
<ng-container *ngFor="let category of categories">
<ng-container *ngIf="category === 'something'">
<mat-chip *ngFor="let chip of category.chips" [selectable]="false">{{chip}}</mat-chip>
</ng-container>
</ng-container>
</mat-chip-list>
`,
`
<mat-chip-listbox>
<ng-container *ngFor="let category of categories">
<ng-container *ngIf="category === 'something'">
<mat-chip-option *ngFor="let chip of category.chips" [selectable]="false">{{chip}}</mat-chip-option>
</ng-container>
</ng-container>
</mat-chip-listbox>
`,
);
});

it('should update a chip with an *ngIf', async () => {
await runMigrationTest(
'<mat-chip *ngIf="isShown"></mat-chip>',
'<mat-chip-option *ngIf="isShown"></mat-chip-option>',
);
});

it('should update a chip grid with an *ngFor', async () => {
await runMigrationTest(
`
<mat-chip-list #chipList>
<mat-chip *ngFor="let chip of chips">{{chip}}</mat-chip>
<input type="text" matInput [matChipInputFor]="chipList">
</mat-chip-list>
`,
`
<mat-chip-grid #chipList>
<mat-chip-row *ngFor="let chip of chips">{{chip}}</mat-chip-row>
<input type="text" matInput [matChipInputFor]="chipList">
</mat-chip-grid>
`,
);
});
});
Expand Up @@ -48,6 +48,49 @@ function runClearAttributeTest(html: string, result: string): void {
}

describe('#visitElements', () => {
describe('visitElements', () => {
it('should traverse elements with an *ngFor', () => {
const visitedElements: string[] = [];
const template = `
<parent>
<child *ngFor="let c of children">
<grandchild *ngFor="let g of c.children"></grandchild>
</child>
</parent>
`;

visitElements(parseTemplate(template).nodes, node => visitedElements.push(node.name));
expect(visitedElements).toEqual(['parent', 'child', 'grandchild']);
});

it('should traverse elements inside ng-container', () => {
const visitedElements: string[] = [];
const template = `
<ng-container>
<parent>
<ng-container>
<child>
<ng-container>
<grandchild></grandchild>
</ng-container>
</child>
</ng-container>
</parent>
</ng-container>
`;

visitElements(parseTemplate(template).nodes, node => visitedElements.push(node.name));
expect(visitedElements).toEqual([
'ng-container',
'parent',
'ng-container',
'child',
'ng-container',
'grandchild',
]);
});
});

describe('tag name replacements', () => {
it('should handle basic cases', async () => {
runTagNameDuplicationTest('<a></a>', '<aa></aa>');
Expand Down
Expand Up @@ -10,6 +10,7 @@ import {
ParsedTemplate,
TmplAstElement,
TmplAstNode,
TmplAstTemplate,
parseTemplate as parseTemplateUsingCompiler,
} from '@angular/compiler';

Expand All @@ -32,9 +33,18 @@ export function visitElements(
nodes.reverse();
for (let i = 0; i < nodes.length; i++) {
const node = nodes[i];
if (node instanceof TmplAstElement) {
const isElement = node instanceof TmplAstElement;

if (isElement) {
preorderCallback(node);
}

// Descend both into elements and templates in order to cover cases like `*ngIf` and `*ngFor`.
if (isElement || node instanceof TmplAstTemplate) {
visitElements(node.children, preorderCallback, postorderCallback);
}

if (isElement) {
postorderCallback(node);
}
}
Expand All @@ -46,8 +56,8 @@ export function visitElements(
*
* For more details, see https://github.com/angular/angular/blob/4332897baa2226ef246ee054fdd5254e3c129109/packages/compiler-cli/src/ngtsc/annotations/component/src/resources.ts#L230.
*
* @param html text of the template to parse
* @param filePath URL to use for source mapping of the parsed template
* @param template text of the template to parse
* @param templateUrl URL to use for source mapping of the parsed template
* @returns the updated template html.
*/
export function parseTemplate(template: string, templateUrl: string = ''): ParsedTemplate {
Expand Down

0 comments on commit 73d5151

Please sign in to comment.