Skip to content

Commit 7604181

Browse files
committed
fix(material/schematics): handle form-field appearance
1 parent 6642a76 commit 7604181

File tree

8 files changed

+250
-33
lines changed

8 files changed

+250
-33
lines changed

integration/mdc-migration/golden/src/app/components/form-field/form-field.component.html

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,8 @@ <h2>Form field example</h2>
22
<mat-form-field hintLabel="Max 10 characters" appearance="fill">
33
<mat-label>Enter some input</mat-label>
44
<input matInput #input maxlength="10" placeholder="Ex. Nougat">
5-
<mat-hint align="end">{{input.value?.length || 0}}/10</mat-hint>
5+
<mat-hint align="end">{{input.value.length}}/10</mat-hint>
66
</mat-form-field>
7+
<mat-form-field appearance="outline"><input matInput></mat-form-field>
8+
<mat-form-field><input matInput></mat-form-field>
9+
<mat-form-field><input matInput></mat-form-field>

integration/mdc-migration/sample-project/src/app/components/form-field/form-field.component.html

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,8 @@ <h2>Form field example</h2>
22
<mat-form-field hintLabel="Max 10 characters" appearance="fill">
33
<mat-label>Enter some input</mat-label>
44
<input matInput #input maxlength="10" placeholder="Ex. Nougat">
5-
<mat-hint align="end">{{input.value?.length || 0}}/10</mat-hint>
5+
<mat-hint align="end">{{input.value.length}}/10</mat-hint>
66
</mat-form-field>
7+
<mat-form-field appearance="outline"><input matInput></mat-form-field>
8+
<mat-form-field appearance="standard"><input matInput></mat-form-field>
9+
<mat-form-field appearance="legacy"><input matInput></mat-form-field>

src/material/schematics/ng-generate/mdc-migration/rules/components/card/card-template.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
import * as compiler from '@angular/compiler';
1010
import {TemplateMigrator} from '../../template-migrator';
11-
import {addAttribute, visitElements} from '../../tree-traversal';
11+
import {updateAttribute, visitElements} from '../../tree-traversal';
1212
import {Update} from '../../../../../migration-utilities';
1313

1414
export class CardTemplateMigrator extends TemplateMigrator {
@@ -22,7 +22,7 @@ export class CardTemplateMigrator extends TemplateMigrator {
2222

2323
updates.push({
2424
offset: node.startSourceSpan.start.offset,
25-
updateFn: html => addAttribute(html, node, 'appearance', 'outlined'),
25+
updateFn: html => updateAttribute(html, node, 'appearance', () => 'outlined'),
2626
});
2727
});
2828

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import {createTestApp, patchDevkitTreeToExposeTypeScript} from '@angular/cdk/schematics/testing';
2+
import {SchematicTestRunner, UnitTestTree} from '@angular-devkit/schematics/testing';
3+
import {
4+
APP_MODULE_FILE,
5+
createNewTestRunner,
6+
migrateComponents,
7+
TEMPLATE_FILE,
8+
} from '../test-setup-helper';
9+
10+
describe('form-field template migrator', () => {
11+
let runner: SchematicTestRunner;
12+
let cliAppTree: UnitTestTree;
13+
14+
async function runMigrationTest(oldFileContent: string, newFileContent: string) {
15+
cliAppTree.overwrite(TEMPLATE_FILE, oldFileContent);
16+
const tree = await migrateComponents(['form-field'], runner, cliAppTree);
17+
expect(tree.readContent(TEMPLATE_FILE)).toBe(newFileContent);
18+
}
19+
20+
beforeEach(async () => {
21+
runner = createNewTestRunner();
22+
cliAppTree = patchDevkitTreeToExposeTypeScript(await createTestApp(runner));
23+
});
24+
25+
it('should not update other elements', async () => {
26+
await runMigrationTest(
27+
'<mat-card appearance="raised"></mat-card>',
28+
'<mat-card appearance="raised"></mat-card>',
29+
);
30+
});
31+
32+
it('should not update default appearance', async () => {
33+
await runMigrationTest(
34+
'<mat-form-field appearance="outline"></mat-form-field>',
35+
'<mat-form-field appearance="outline"></mat-form-field>',
36+
);
37+
});
38+
39+
it('should not update outline appearance', async () => {
40+
await runMigrationTest(
41+
'<mat-form-field appearance="outline"></mat-form-field>',
42+
'<mat-form-field appearance="outline"></mat-form-field>',
43+
);
44+
});
45+
46+
it('should not update fill appearance', async () => {
47+
await runMigrationTest(
48+
'<mat-form-field appearance="fill"></mat-form-field>',
49+
'<mat-form-field appearance="fill"></mat-form-field>',
50+
);
51+
});
52+
53+
it('should update standard appearance', async () => {
54+
await runMigrationTest(
55+
'<mat-form-field appearance="standard"></mat-form-field>',
56+
'<mat-form-field></mat-form-field>',
57+
);
58+
});
59+
60+
it('should update legacy appearance', async () => {
61+
await runMigrationTest(
62+
'<mat-form-field appearance="legacy"></mat-form-field>',
63+
'<mat-form-field></mat-form-field>',
64+
);
65+
});
66+
});
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
import * as compiler from '@angular/compiler';
10+
import {TemplateMigrator} from '../../template-migrator';
11+
import {updateAttribute, visitElements} from '../../tree-traversal';
12+
import {Update} from '../../../../../migration-utilities';
13+
14+
export class FormFieldTemplateMigrator extends TemplateMigrator {
15+
getUpdates(ast: compiler.ParsedTemplate): Update[] {
16+
const updates: Update[] = [];
17+
18+
visitElements(ast.nodes, (node: compiler.TmplAstElement) => {
19+
if (node.name !== 'mat-form-field') {
20+
return;
21+
}
22+
23+
updates.push({
24+
offset: node.startSourceSpan.start.offset,
25+
updateFn: html =>
26+
updateAttribute(html, node, 'appearance', old =>
27+
['legacy', 'standard'].includes(old || '') ? null : old,
28+
),
29+
});
30+
});
31+
32+
return updates;
33+
}
34+
}

src/material/schematics/ng-generate/mdc-migration/rules/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import {TabsStylesMigrator} from './components/tabs/tabs-styles';
3434
import {TooltipStylesMigrator} from './components/tooltip/tooltip-styles';
3535
import {OptgroupStylesMigrator} from './components/optgroup/optgroup-styles';
3636
import {OptionStylesMigrator} from './components/option/option-styles';
37+
import {FormFieldTemplateMigrator} from './components/form-field/form-field-template';
3738

3839
/** Contains the migrators to migrate a single component. */
3940
export interface ComponentMigrator {
@@ -121,6 +122,7 @@ export const MIGRATORS: ComponentMigrator[] = [
121122
{
122123
component: 'form-field',
123124
styles: new FormFieldStylesMigrator(),
125+
template: new FormFieldTemplateMigrator(),
124126
},
125127
{
126128
component: 'input',

src/material/schematics/ng-generate/mdc-migration/rules/tree-traversal.spec.ts

Lines changed: 98 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import {
2-
addAttribute,
2+
updateAttribute,
33
visitElements,
44
parseTemplate,
55
replaceStartTag,
@@ -21,7 +21,28 @@ function runTagNameDuplicationTest(html: string, result: string): void {
2121

2222
function runAddAttributeTest(html: string, result: string): void {
2323
visitElements(parseTemplate(html).nodes, undefined, node => {
24-
html = addAttribute(html, node, 'attr', 'val');
24+
html = updateAttribute(html, node, 'add', () => 'val');
25+
});
26+
expect(html).toBe(result);
27+
}
28+
29+
function runRemoveAttributeTest(html: string, result: string): void {
30+
visitElements(parseTemplate(html).nodes, undefined, node => {
31+
html = updateAttribute(html, node, 'rm', () => null);
32+
});
33+
expect(html).toBe(result);
34+
}
35+
36+
function runChangeAttributeTest(html: string, result: string): void {
37+
visitElements(parseTemplate(html).nodes, undefined, node => {
38+
html = updateAttribute(html, node, 'change', old => (old == ':(' ? ':)' : old));
39+
});
40+
expect(html).toBe(result);
41+
}
42+
43+
function runClearAttributeTest(html: string, result: string): void {
44+
visitElements(parseTemplate(html).nodes, undefined, node => {
45+
html = updateAttribute(html, node, 'clear', () => '');
2546
});
2647
expect(html).toBe(result);
2748
}
@@ -92,39 +113,103 @@ describe('#visitElements', () => {
92113

93114
describe('add attribute tests', () => {
94115
it('should handle single element', async () => {
95-
runAddAttributeTest('<a></a>', '<a attr="val"></a>');
116+
runAddAttributeTest('<a></a>', '<a add="val"></a>');
96117
});
97118

98119
it('should handle multiple unnested', async () => {
99-
runAddAttributeTest('<a></a><b></b>', '<a attr="val"></a><b attr="val"></b>');
120+
runAddAttributeTest('<a></a><b></b>', '<a add="val"></a><b add="val"></b>');
100121
});
101122

102123
it('should handle multiple nested', async () => {
103-
runAddAttributeTest('<a><b></b></a>', '<a attr="val"><b attr="val"></b></a>');
124+
runAddAttributeTest('<a><b></b></a>', '<a add="val"><b add="val"></b></a>');
104125
});
105126

106127
it('should handle multiple nested and unnested', async () => {
107128
runAddAttributeTest(
108129
'<a><b></b><c></c></a>',
109-
'<a attr="val"><b attr="val"></b><c attr="val"></c></a>',
130+
'<a add="val"><b add="val"></b><c add="val"></c></a>',
110131
);
111132
});
112133

113134
it('should handle adding multiple attrs to a single element', async () => {
114135
let html = '<a></a>';
115136
visitElements(parseTemplate(html).nodes, undefined, node => {
116-
html = addAttribute(html, node, 'attr1', 'val1');
117-
html = addAttribute(html, node, 'attr2', 'val2');
137+
html = updateAttribute(html, node, 'attr1', () => 'val1');
138+
html = updateAttribute(html, node, 'attr2', () => 'val2');
118139
});
119140
expect(html).toBe('<a attr2="val2" attr1="val1"></a>');
120141
});
121142

122143
it('should replace value of existing attribute', async () => {
123-
runAddAttributeTest('<a attr="default"></a>', '<a attr="val"></a>');
144+
runAddAttributeTest('<a add="default"></a>', '<a add="val"></a>');
124145
});
125146

126147
it('should add value to existing attribute that does not have a value', async () => {
127-
runAddAttributeTest('<a attr></a>', '<a attr="val"></a>');
148+
runAddAttributeTest('<a add></a>', '<a add="val"></a>');
149+
});
150+
});
151+
152+
describe('remove attribute tests', () => {
153+
it('should remove attribute', () => {
154+
runRemoveAttributeTest('<a rm="something"></a>', '<a></a>');
155+
});
156+
157+
it('should remove empty attribute', () => {
158+
runRemoveAttributeTest('<a rm></a>', '<a></a>');
159+
});
160+
161+
it('should remove unquoted attribute', () => {
162+
runRemoveAttributeTest('<a rm=3></a>', '<a></a>');
163+
});
164+
165+
it('should remove value-less attribute', () => {
166+
runRemoveAttributeTest('<a rm></a>', '<a></a>');
167+
});
168+
169+
it('should not change element without attribute', () => {
170+
runRemoveAttributeTest('<a></a>', '<a></a>');
171+
});
172+
173+
it('should not remove other attributes', () => {
174+
runRemoveAttributeTest(
175+
`
176+
<a
177+
first="1"
178+
rm="2"
179+
last="3">
180+
</a>
181+
`,
182+
`
183+
<a
184+
first="1"
185+
last="3">
186+
</a>
187+
`,
188+
);
189+
});
190+
});
191+
192+
describe('change attribute tests', () => {
193+
it('should change attribute with matching value', () => {
194+
runChangeAttributeTest('<a change=":("></a>', '<a change=":)"></a>');
195+
});
196+
197+
it('should not change attribute with non-matching value', () => {
198+
runChangeAttributeTest('<a change="x"></a>', '<a change="x"></a>');
199+
});
200+
});
201+
202+
describe('clear attribute tests', () => {
203+
it('should clear attribute with value', () => {
204+
runClearAttributeTest('<a clear="something"></a>', '<a clear></a>');
205+
});
206+
207+
it('should preserve value-less attribute', () => {
208+
runClearAttributeTest('<a clear></a>', '<a clear></a>');
209+
});
210+
211+
it('should add attribute to element without it', () => {
212+
runClearAttributeTest('<a></a>', '<a clear></a>');
128213
});
129214
});
130215

@@ -139,7 +224,7 @@ describe('#visitElements', () => {
139224
`,
140225
`
141226
<a
142-
attr="val"
227+
add="val"
143228
class="a"
144229
aria-label="a"
145230
aria-describedby="a"
@@ -157,8 +242,8 @@ describe('#visitElements', () => {
157242
></a>
158243
`;
159244
visitElements(parseTemplate(html).nodes, undefined, node => {
160-
html = addAttribute(html, node, 'attr1', 'val1');
161-
html = addAttribute(html, node, 'attr2', 'val2');
245+
html = updateAttribute(html, node, 'attr1', () => 'val1');
246+
html = updateAttribute(html, node, 'attr2', () => 'val2');
162247
});
163248
expect(html).toBe(`
164249
<a

0 commit comments

Comments
 (0)