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 (#25956)

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.

(cherry picked from commit 952cf06)
  • Loading branch information
crisbeto committed Nov 11, 2022
1 parent 92b2299 commit 08f6ef4
Show file tree
Hide file tree
Showing 4 changed files with 120 additions and 5 deletions.
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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>
`,
);
});
});
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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 08f6ef4

Please sign in to comment.