Enter some input
- {{input.value?.length || 0}}/10
+ {{input.value.length}}/10
+
+
+
diff --git a/integration/mdc-migration/golden/src/styles.scss b/integration/mdc-migration/golden/src/styles.scss
index caa63eef72c2..914651b6c30b 100644
--- a/integration/mdc-migration/golden/src/styles.scss
+++ b/integration/mdc-migration/golden/src/styles.scss
@@ -7,8 +7,8 @@
// Include the common styles for Angular Material. We include this here so that you only
// have to load a single css file for Angular Material in your app.
// Be sure that you only ever include this mixin once!
-@include mat.all-component-typographies();
-@include mat.legacy-core();
+@include mat.all-component-typographies(mat.define-typography-config());
+@include mat.core();
// Define the palettes for your theme using the Material Design palettes available in palette.scss
// (imported above). For each palette, you can optionally specify a default, lighter, and darker
@@ -26,9 +26,11 @@ $sample-project-theme: mat.define-light-theme((
primary: $sample-project-primary,
accent: $sample-project-accent,
warn: $sample-project-warn,
- )
+ ),
+ typography: mat.define-typography-config(),
));
+@include mat.core-theme($sample-project-theme);
@include mat.autocomplete-theme($sample-project-theme);
@include mat.button-theme($sample-project-theme);
@include mat.fab-theme($sample-project-theme);
@@ -45,13 +47,14 @@ $sample-project-theme: mat.define-light-theme((
@include mat.progress-bar-theme($sample-project-theme);
@include mat.progress-spinner-theme($sample-project-theme);
@include mat.radio-theme($sample-project-theme);
-@include mat.legacy-select-theme($sample-project-theme);
@include mat.slide-toggle-theme($sample-project-theme);
@include mat.slider-theme($sample-project-theme);
@include mat.snack-bar-theme($sample-project-theme);
@include mat.table-theme($sample-project-theme);
@include mat.tabs-theme($sample-project-theme);
@include mat.tooltip-theme($sample-project-theme);
+@include mat.option-theme($sample-project-theme);
+@include mat.optgroup-theme($sample-project-theme);
/* You can add global styles to this file, and also import other style files */
diff --git a/integration/mdc-migration/migration-test.bzl b/integration/mdc-migration/migration-test.bzl
index 635f5cf72bad..56ff9d4ccb38 100644
--- a/integration/mdc-migration/migration-test.bzl
+++ b/integration/mdc-migration/migration-test.bzl
@@ -21,7 +21,7 @@ IGNORED_FILES = [
"yarn.lock",
]
-def migration_test(name, srcs, approve):
+def migration_test(name, srcs, approve, verify = []):
node_integration_test(
name = name,
srcs = srcs,
@@ -32,15 +32,13 @@ def migration_test(name, srcs, approve):
# TODO(devversion): determine if a solution/workaround could live in the test runner.
"yarn install --cache-folder .yarn_cache_folder/",
"yarn ng generate @angular/material:mdc-migration --components all",
- # TODO(amysorto): add back once MDC components are in @angular/material
- # "yarn test",
" ".join([
"$(rootpath :verify_golden)",
"%s" % approve,
"../golden",
"integration/mdc-migration/golden",
] + IGNORED_FILES),
- ],
+ ] + verify,
data = [
":golden_project",
":test_project",
diff --git a/integration/mdc-migration/sample-project/angular.json b/integration/mdc-migration/sample-project/angular.json
index 2ea486ad4f2a..3b3031758564 100644
--- a/integration/mdc-migration/sample-project/angular.json
+++ b/integration/mdc-migration/sample-project/angular.json
@@ -36,7 +36,7 @@
{
"type": "initial",
"maximumWarning": "500kb",
- "maximumError": "1mb"
+ "maximumError": "2mb"
},
{
"type": "anyComponentStyle",
diff --git a/integration/mdc-migration/sample-project/src/app/app.module.ts b/integration/mdc-migration/sample-project/src/app/app.module.ts
index ce2c7171427a..e947003d16d5 100644
--- a/integration/mdc-migration/sample-project/src/app/app.module.ts
+++ b/integration/mdc-migration/sample-project/src/app/app.module.ts
@@ -25,6 +25,7 @@ import {MatLegacySnackBarModule as MatSnackBarModule} from '@angular/material/le
import {MatLegacyTableModule as MatTableModule} from '@angular/material/legacy-table';
import {MatLegacyTabsModule as MatTabsModule} from '@angular/material/legacy-tabs';
import {MatLegacyTooltipModule as MatTooltipModule} from '@angular/material/legacy-tooltip';
+import {MatLegacyOptionModule, LEGACY_VERSION} from '@angular/material/legacy-core';
import {AutocompleteComponent} from './components/autocomplete/autocomplete.component';
import {ButtonComponent} from './components/button/button.component';
import {CardComponent} from './components/card/card.component';
@@ -94,9 +95,12 @@ import {TooltipComponent} from './components/tooltip/tooltip.component';
MatTableModule,
MatTabsModule,
MatTooltipModule,
+ MatLegacyOptionModule,
ReactiveFormsModule,
],
providers: [],
bootstrap: [AppComponent],
})
-export class AppModule {}
+export class AppModule {
+ version = LEGACY_VERSION;
+}
diff --git a/integration/mdc-migration/sample-project/src/app/components/form-field/form-field.component.html b/integration/mdc-migration/sample-project/src/app/components/form-field/form-field.component.html
index e5dafc96c321..ae3d5dc73e2f 100644
--- a/integration/mdc-migration/sample-project/src/app/components/form-field/form-field.component.html
+++ b/integration/mdc-migration/sample-project/src/app/components/form-field/form-field.component.html
@@ -2,5 +2,8 @@
Form field example
Enter some input
- {{input.value?.length || 0}}/10
+ {{input.value.length}}/10
+
+
+
diff --git a/integration/mdc-migration/sample-project/src/app/components/progress-spinner/progress-spinner.component.spec.ts b/integration/mdc-migration/sample-project/src/app/components/progress-spinner/progress-spinner.component.spec.ts
index 252afcca0109..6e31495e80a9 100644
--- a/integration/mdc-migration/sample-project/src/app/components/progress-spinner/progress-spinner.component.spec.ts
+++ b/integration/mdc-migration/sample-project/src/app/components/progress-spinner/progress-spinner.component.spec.ts
@@ -1,6 +1,6 @@
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
-import {MatProgressSpinnerModule} from '@angular/material/legacy-progress-spinner';
+import {MatLegacyProgressSpinnerModule as MatProgressSpinnerModule} from '@angular/material/legacy-progress-spinner';
import {ProgressSpinnerComponent} from './progress-spinner.component';
describe('ProgressSpinnerComponent', () => {
diff --git a/integration/mdc-migration/sample-project/src/app/components/slide-toggle/slide-toggle.component.spec.ts b/integration/mdc-migration/sample-project/src/app/components/slide-toggle/slide-toggle.component.spec.ts
index f0dd9f1f7996..0f2efb09ffd4 100644
--- a/integration/mdc-migration/sample-project/src/app/components/slide-toggle/slide-toggle.component.spec.ts
+++ b/integration/mdc-migration/sample-project/src/app/components/slide-toggle/slide-toggle.component.spec.ts
@@ -1,6 +1,6 @@
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
-import {MatSlideToggleModule} from '@angular/material/legacy-slide-toggle';
+import {MatLegacySlideToggleModule as MatSlideToggleModule} from '@angular/material/legacy-slide-toggle';
import {SlideToggleComponent} from './slide-toggle.component';
describe('SlideToggleComponent', () => {
diff --git a/integration/mdc-migration/sample-project/src/app/components/snack-bar/snack-bar.component.spec.ts b/integration/mdc-migration/sample-project/src/app/components/snack-bar/snack-bar.component.spec.ts
index 6f3df4abb170..271826d5c30a 100644
--- a/integration/mdc-migration/sample-project/src/app/components/snack-bar/snack-bar.component.spec.ts
+++ b/integration/mdc-migration/sample-project/src/app/components/snack-bar/snack-bar.component.spec.ts
@@ -1,6 +1,6 @@
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
-import {MatSnackBarModule} from '@angular/material/legacy-snack-bar';
+import {MatLegacySnackBarModule as MatSnackBarModule} from '@angular/material/legacy-snack-bar';
import {SnackBarComponent} from './snack-bar.component';
describe('SnackBarComponent', () => {
diff --git a/integration/mdc-migration/sample-project/src/app/components/table/table.component.spec.ts b/integration/mdc-migration/sample-project/src/app/components/table/table.component.spec.ts
index 33f4828d8c83..126b4a7ec3a0 100644
--- a/integration/mdc-migration/sample-project/src/app/components/table/table.component.spec.ts
+++ b/integration/mdc-migration/sample-project/src/app/components/table/table.component.spec.ts
@@ -1,6 +1,6 @@
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
-import {MatTableModule} from '@angular/material/legacy-table';
+import {MatLegacyTableModule as MatTableModule} from '@angular/material/legacy-table';
import {TableComponent} from './table.component';
describe('TableComponent', () => {
diff --git a/integration/mdc-migration/sample-project/src/app/components/tabs/tabs.component.spec.ts b/integration/mdc-migration/sample-project/src/app/components/tabs/tabs.component.spec.ts
index fd2d18d138af..6123b38fe221 100644
--- a/integration/mdc-migration/sample-project/src/app/components/tabs/tabs.component.spec.ts
+++ b/integration/mdc-migration/sample-project/src/app/components/tabs/tabs.component.spec.ts
@@ -1,6 +1,6 @@
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
-import {MatTabsModule} from '@angular/material/legacy-tabs';
+import {MatLegacyTabsModule as MatTabsModule} from '@angular/material/legacy-tabs';
import {TabsComponent} from './tabs.component';
describe('TabsComponent', () => {
diff --git a/integration/mdc-migration/sample-project/src/styles.scss b/integration/mdc-migration/sample-project/src/styles.scss
index aad7a527813f..324e725b8970 100644
--- a/integration/mdc-migration/sample-project/src/styles.scss
+++ b/integration/mdc-migration/sample-project/src/styles.scss
@@ -7,7 +7,7 @@
// Include the common styles for Angular Material. We include this here so that you only
// have to load a single css file for Angular Material in your app.
// Be sure that you only ever include this mixin once!
-@include mat.all-legacy-component-typographies();
+@include mat.all-legacy-component-typographies(mat.define-legacy-typography-config());
@include mat.legacy-core();
// Define the palettes for your theme using the Material Design palettes available in palette.scss
@@ -26,9 +26,11 @@ $sample-project-theme: mat.define-light-theme((
primary: $sample-project-primary,
accent: $sample-project-accent,
warn: $sample-project-warn,
- )
+ ),
+ typography: mat.define-legacy-typography-config(),
));
+@include mat.legacy-core-theme($sample-project-theme);
@include mat.legacy-autocomplete-theme($sample-project-theme);
@include mat.legacy-button-theme($sample-project-theme);
@include mat.legacy-card-theme($sample-project-theme);
@@ -49,6 +51,8 @@ $sample-project-theme: mat.define-light-theme((
@include mat.legacy-table-theme($sample-project-theme);
@include mat.legacy-tabs-theme($sample-project-theme);
@include mat.legacy-tooltip-theme($sample-project-theme);
+@include mat.legacy-option-theme($sample-project-theme);
+@include mat.legacy-optgroup-theme($sample-project-theme);
/* You can add global styles to this file, and also import other style files */
diff --git a/integration/mdc-migration/verify-golden.ts b/integration/mdc-migration/verify-golden.ts
index 4e789b38c31b..e0cb0d1a1496 100644
--- a/integration/mdc-migration/verify-golden.ts
+++ b/integration/mdc-migration/verify-golden.ts
@@ -64,8 +64,8 @@ async function compareFiles(
const [testContent, goldenContent] = await Promise.allSettled(contentPromises);
const diff = {
filename,
- actual: getDiffValue(goldenStats, goldenContent),
- expected: getDiffValue(testStats, testContent),
+ actual: getDiffValue(testStats, testContent),
+ expected: getDiffValue(goldenStats, goldenContent),
};
if (testStats.status === 'rejected' && goldenStats.status === 'rejected') {
return null; // Neither file exists.
@@ -92,9 +92,9 @@ function showDiffs(diffs: FileDiff[]) {
console.error(
[
''.padEnd(80, '='),
- `----- ${diff.filename} (actual) `.padEnd(80, '-'),
+ `----- ${diff.filename} (this run) `.padEnd(80, '-'),
diff.actual,
- `----- ${diff.filename} (expected) `.padEnd(80, '-'),
+ `----- ${diff.filename} (golden) `.padEnd(80, '-'),
diff.expected,
'',
].join('\n'),
diff --git a/src/material/schematics/ng-generate/mdc-migration/rules/components/card/card-template.ts b/src/material/schematics/ng-generate/mdc-migration/rules/components/card/card-template.ts
index 6ed14ecddd99..f99c22420e19 100644
--- a/src/material/schematics/ng-generate/mdc-migration/rules/components/card/card-template.ts
+++ b/src/material/schematics/ng-generate/mdc-migration/rules/components/card/card-template.ts
@@ -8,7 +8,7 @@
import * as compiler from '@angular/compiler';
import {TemplateMigrator} from '../../template-migrator';
-import {addAttribute, visitElements} from '../../tree-traversal';
+import {updateAttribute, visitElements} from '../../tree-traversal';
import {Update} from '../../../../../migration-utilities';
export class CardTemplateMigrator extends TemplateMigrator {
@@ -22,7 +22,7 @@ export class CardTemplateMigrator extends TemplateMigrator {
updates.push({
offset: node.startSourceSpan.start.offset,
- updateFn: html => addAttribute(html, node, 'appearance', 'outlined'),
+ updateFn: html => updateAttribute(html, node, 'appearance', () => 'outlined'),
});
});
diff --git a/src/material/schematics/ng-generate/mdc-migration/rules/components/form-field/form-field-template.spec.ts b/src/material/schematics/ng-generate/mdc-migration/rules/components/form-field/form-field-template.spec.ts
new file mode 100644
index 000000000000..11b7b3ab945b
--- /dev/null
+++ b/src/material/schematics/ng-generate/mdc-migration/rules/components/form-field/form-field-template.spec.ts
@@ -0,0 +1,61 @@
+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('form-field template migrator', () => {
+ let runner: SchematicTestRunner;
+ let cliAppTree: UnitTestTree;
+
+ async function runMigrationTest(oldFileContent: string, newFileContent: string) {
+ cliAppTree.overwrite(TEMPLATE_FILE, oldFileContent);
+ const tree = await migrateComponents(['form-field'], runner, cliAppTree);
+ expect(tree.readContent(TEMPLATE_FILE)).toBe(newFileContent);
+ }
+
+ beforeEach(async () => {
+ runner = createNewTestRunner();
+ cliAppTree = patchDevkitTreeToExposeTypeScript(await createTestApp(runner));
+ });
+
+ it('should not update other elements appearance', async () => {
+ await runMigrationTest(
+ '',
+ '',
+ );
+ });
+
+ it('should not update default appearance', async () => {
+ await runMigrationTest(
+ '',
+ '',
+ );
+ });
+
+ it('should not update outline appearance', async () => {
+ await runMigrationTest(
+ '',
+ '',
+ );
+ });
+
+ it('should not update fill appearance', async () => {
+ await runMigrationTest(
+ '',
+ '',
+ );
+ });
+
+ it('should update standard appearance', async () => {
+ await runMigrationTest(
+ '',
+ '',
+ );
+ });
+
+ it('should update legacy appearance', async () => {
+ await runMigrationTest(
+ '',
+ '',
+ );
+ });
+});
diff --git a/src/material/schematics/ng-generate/mdc-migration/rules/components/form-field/form-field-template.ts b/src/material/schematics/ng-generate/mdc-migration/rules/components/form-field/form-field-template.ts
new file mode 100644
index 000000000000..b58962afd155
--- /dev/null
+++ b/src/material/schematics/ng-generate/mdc-migration/rules/components/form-field/form-field-template.ts
@@ -0,0 +1,34 @@
+/**
+ * @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} from '../../template-migrator';
+import {updateAttribute, visitElements} from '../../tree-traversal';
+import {Update} from '../../../../../migration-utilities';
+
+export class FormFieldTemplateMigrator extends TemplateMigrator {
+ getUpdates(ast: compiler.ParsedTemplate): Update[] {
+ const updates: Update[] = [];
+
+ visitElements(ast.nodes, (node: compiler.TmplAstElement) => {
+ if (node.name !== 'mat-form-field') {
+ return;
+ }
+
+ updates.push({
+ offset: node.startSourceSpan.start.offset,
+ updateFn: html =>
+ updateAttribute(html, node, 'appearance', old =>
+ ['legacy', 'standard'].includes(old || '') ? null : old,
+ ),
+ });
+ });
+
+ return updates;
+ }
+}
diff --git a/src/material/schematics/ng-generate/mdc-migration/rules/components/multiple-components-styles.spec.ts b/src/material/schematics/ng-generate/mdc-migration/rules/components/multiple-components-styles.spec.ts
index d4db7d01bc1d..6cb67bf2beaa 100644
--- a/src/material/schematics/ng-generate/mdc-migration/rules/components/multiple-components-styles.spec.ts
+++ b/src/material/schematics/ng-generate/mdc-migration/rules/components/multiple-components-styles.spec.ts
@@ -60,12 +60,33 @@ describe('multiple component styles', () => {
);
});
+ it('should remove legacy mixin if all replacements are already accounted for', async () => {
+ await runMigrationTest(
+ ['paginator', 'select'],
+ `
+ @use '@angular/material' as mat;
+ $theme: ();
+ @include mat.legacy-paginator-theme($theme);
+ @include mat.legacy-select-theme($theme);
+ `,
+ `
+ @use '@angular/material' as mat;
+ $theme: ();
+ @include mat.paginator-theme($theme);
+ @include mat.icon-button-theme($theme);
+ @include mat.form-field-theme($theme);
+ @include mat.select-theme($theme);
+ `,
+ );
+ });
+
it('should migrate all component mixins for a full migration', async () => {
await runMigrationTest(
['all'],
`
@use '@angular/material' as mat;
$theme: ();
+ @include mat.legacy-core();
@include mat.all-legacy-component-themes($sample-project-themes);
@include mat.all-legacy-component-colors($sample-colors);
@include mat.all-legacy-component-typographies($sample-typographies);
@@ -73,6 +94,7 @@ describe('multiple component styles', () => {
`
@use '@angular/material' as mat;
$theme: ();
+ @include mat.core();
@include mat.all-component-themes($sample-project-themes);
@include mat.all-component-colors($sample-colors);
@include mat.all-component-typographies($sample-typographies);
@@ -86,6 +108,7 @@ describe('multiple component styles', () => {
`
@use '@angular/material' as mat;
$theme: ();
+ @include mat.legacy-core();
@include mat.all-legacy-component-themes($sample-project-themes);
@include mat.all-legacy-component-colors($sample-colors);
@include mat.all-legacy-component-typographies($sample-typographies);
@@ -93,6 +116,9 @@ describe('multiple component styles', () => {
`
@use '@angular/material' as mat;
$theme: ();
+ /* TODO(mdc-migration): Remove legacy-core once all legacy components are migrated */
+ @include mat.legacy-core();
+ @include mat.core();
/* TODO(mdc-migration): Remove all-legacy-component-themes once all legacy components are migrated */
@include mat.all-legacy-component-themes($sample-project-themes);
@include mat.all-component-themes($sample-project-themes);
diff --git a/src/material/schematics/ng-generate/mdc-migration/rules/components/option/option-styles.ts b/src/material/schematics/ng-generate/mdc-migration/rules/components/option/option-styles.ts
index a7b78f462eba..e62565e57466 100644
--- a/src/material/schematics/ng-generate/mdc-migration/rules/components/option/option-styles.ts
+++ b/src/material/schematics/ng-generate/mdc-migration/rules/components/option/option-styles.ts
@@ -26,6 +26,18 @@ export class OptionStylesMigrator extends StyleMigrator {
old: 'legacy-option-typography',
new: ['option-typography'],
},
+ {
+ old: 'legacy-core-theme',
+ new: ['core-theme'],
+ },
+ {
+ old: 'legacy-core-color',
+ new: ['core-color'],
+ },
+ {
+ old: 'legacy-core-typography',
+ new: ['core-typography'],
+ },
];
classChanges: ClassNameChange[] = [
diff --git a/src/material/schematics/ng-generate/mdc-migration/rules/index.ts b/src/material/schematics/ng-generate/mdc-migration/rules/index.ts
index 3022be5b2045..ee1ee5fc1c61 100644
--- a/src/material/schematics/ng-generate/mdc-migration/rules/index.ts
+++ b/src/material/schematics/ng-generate/mdc-migration/rules/index.ts
@@ -34,6 +34,7 @@ import {TabsStylesMigrator} from './components/tabs/tabs-styles';
import {TooltipStylesMigrator} from './components/tooltip/tooltip-styles';
import {OptgroupStylesMigrator} from './components/optgroup/optgroup-styles';
import {OptionStylesMigrator} from './components/option/option-styles';
+import {FormFieldTemplateMigrator} from './components/form-field/form-field-template';
/** Contains the migrators to migrate a single component. */
export interface ComponentMigrator {
@@ -121,6 +122,7 @@ export const MIGRATORS: ComponentMigrator[] = [
{
component: 'form-field',
styles: new FormFieldStylesMigrator(),
+ template: new FormFieldTemplateMigrator(),
},
{
component: 'input',
diff --git a/src/material/schematics/ng-generate/mdc-migration/rules/style-migrator.ts b/src/material/schematics/ng-generate/mdc-migration/rules/style-migrator.ts
index 52e23dcdb793..26d5184bebd6 100644
--- a/src/material/schematics/ng-generate/mdc-migration/rules/style-migrator.ts
+++ b/src/material/schematics/ng-generate/mdc-migration/rules/style-migrator.ts
@@ -26,7 +26,7 @@ export interface MixinChange {
old: string;
/** The name(s) of the new scss mixin(s). */
- new: string[];
+ new: string[] | null;
/** Optional check to see if new scss mixin(s) already exist in the styles */
checkForDuplicates?: boolean;
@@ -75,7 +75,7 @@ export abstract class StyleMigrator {
}
// Check if mixin replacements already exist in the stylesheet
- const replacements = [...change.new];
+ const replacements = [...(change.new ?? [])];
if (change.checkForDuplicates) {
const mixinArgumentMatches = atRule.params?.match(MIXIN_ARGUMENTS_REGEX);
atRule.root().walkAtRules(rule => {
@@ -94,12 +94,7 @@ export abstract class StyleMigrator {
});
}
- // Don't do anything if all the new changes already exist in the stylesheet
- if (replacements.length < 1) {
- return null;
- }
-
- return {old: change.old, new: replacements};
+ return {old: change.old, new: replacements.length ? replacements : null};
}
/**
diff --git a/src/material/schematics/ng-generate/mdc-migration/rules/theming-styles.ts b/src/material/schematics/ng-generate/mdc-migration/rules/theming-styles.ts
index 74054ddba117..9fe3d21bfec2 100644
--- a/src/material/schematics/ng-generate/mdc-migration/rules/theming-styles.ts
+++ b/src/material/schematics/ng-generate/mdc-migration/rules/theming-styles.ts
@@ -12,17 +12,21 @@ import * as postcss from 'postcss';
import * as scss from 'postcss-scss';
import {ComponentMigrator, MIGRATORS} from '.';
-const ALL_LEGACY_COMPONENTS_MIXIN_NAME = '(?:\\.)(.*)(?:\\()';
+const COMPONENTS_MIXIN_NAME = /\.([^(;]*)/;
export class ThemingStylesMigration extends Migration {
enabled = true;
namespace: string;
override visitStylesheet(stylesheet: ResolvedResource) {
+ const migratedContent = this.migrate(stylesheet.content, stylesheet.filePath).replace(
+ new RegExp(`${this.namespace}.define-legacy-typography-config\\(`, 'g'),
+ `${this.namespace}.define-typography-config(`,
+ );
this.fileSystem
.edit(stylesheet.filePath)
.remove(stylesheet.start, stylesheet.content.length)
- .insertRight(stylesheet.start, this.migrate(stylesheet.content, stylesheet.filePath));
+ .insertRight(stylesheet.start, migratedContent);
}
migrate(styles: string, filename: string): string {
@@ -59,23 +63,33 @@ export class ThemingStylesMigration extends Migration new RegExp(r).test(mixinText));
+ }
+
isPartialMigration() {
return this.upgradeData.length !== MIGRATORS.length;
}
@@ -157,7 +171,7 @@ function addLegacyCommentForPartialMigrations(
/**
* Adds comment before postcss rule or at rule node
*
- * @param rule a postcss rule.
+ * @param node a postcss rule.
* @param comment the text content for the comment
*/
function addCommentBeforeNode(node: postcss.Rule | postcss.AtRule, comment: string): void {
@@ -174,15 +188,18 @@ function addCommentBeforeNode(node: postcss.Rule | postcss.AtRule, comment: stri
}
/**
- * Replaces mixin prefixed with `all-legacy-component` to the MDC equivalent.
+ * Replaces a cross-cutting mixin that affects multiple components with the MDC equivalent.
*
- * @param allComponentThemesNode a all-components-theme mixin node
+ * @param atRule A mixin inclusion node
+ * @param namespace The @angular/material namespace
*/
-function replaceAllComponentsMixin(allComponentNode: postcss.AtRule) {
- allComponentNode.cloneBefore({
- params: allComponentNode.params.replace('all-legacy-component', 'all-component'),
+function replaceCrossCuttingMixin(atRule: postcss.AtRule, namespace: string) {
+ atRule.cloneBefore({
+ params: atRule.params
+ .replace(`${namespace}.all-legacy-component`, `${namespace}.all-component`)
+ .replace(`${namespace}.legacy-core`, `${namespace}.core`),
});
- allComponentNode.remove();
+ atRule.remove();
}
/**
diff --git a/src/material/schematics/ng-generate/mdc-migration/rules/tree-traversal.spec.ts b/src/material/schematics/ng-generate/mdc-migration/rules/tree-traversal.spec.ts
index 518d55f7f532..ff90a640bd17 100644
--- a/src/material/schematics/ng-generate/mdc-migration/rules/tree-traversal.spec.ts
+++ b/src/material/schematics/ng-generate/mdc-migration/rules/tree-traversal.spec.ts
@@ -1,5 +1,5 @@
import {
- addAttribute,
+ updateAttribute,
visitElements,
parseTemplate,
replaceStartTag,
@@ -21,7 +21,28 @@ function runTagNameDuplicationTest(html: string, result: string): void {
function runAddAttributeTest(html: string, result: string): void {
visitElements(parseTemplate(html).nodes, undefined, node => {
- html = addAttribute(html, node, 'attr', 'val');
+ html = updateAttribute(html, node, 'add', () => 'val');
+ });
+ expect(html).toBe(result);
+}
+
+function runRemoveAttributeTest(html: string, result: string): void {
+ visitElements(parseTemplate(html).nodes, undefined, node => {
+ html = updateAttribute(html, node, 'rm', () => null);
+ });
+ expect(html).toBe(result);
+}
+
+function runChangeAttributeTest(html: string, result: string): void {
+ visitElements(parseTemplate(html).nodes, undefined, node => {
+ html = updateAttribute(html, node, 'change', old => (old == ':(' ? ':)' : old));
+ });
+ expect(html).toBe(result);
+}
+
+function runClearAttributeTest(html: string, result: string): void {
+ visitElements(parseTemplate(html).nodes, undefined, node => {
+ html = updateAttribute(html, node, 'clear', () => '');
});
expect(html).toBe(result);
}
@@ -92,39 +113,103 @@ describe('#visitElements', () => {
describe('add attribute tests', () => {
it('should handle single element', async () => {
- runAddAttributeTest('', '');
+ runAddAttributeTest('', '');
});
it('should handle multiple unnested', async () => {
- runAddAttributeTest('', '');
+ runAddAttributeTest('', '');
});
it('should handle multiple nested', async () => {
- runAddAttributeTest('', '');
+ runAddAttributeTest('', '');
});
it('should handle multiple nested and unnested', async () => {
runAddAttributeTest(
'',
- '',
+ '',
);
});
it('should handle adding multiple attrs to a single element', async () => {
let html = '';
visitElements(parseTemplate(html).nodes, undefined, node => {
- html = addAttribute(html, node, 'attr1', 'val1');
- html = addAttribute(html, node, 'attr2', 'val2');
+ html = updateAttribute(html, node, 'attr1', () => 'val1');
+ html = updateAttribute(html, node, 'attr2', () => 'val2');
});
expect(html).toBe('');
});
it('should replace value of existing attribute', async () => {
- runAddAttributeTest('', '');
+ runAddAttributeTest('', '');
});
it('should add value to existing attribute that does not have a value', async () => {
- runAddAttributeTest('', '');
+ runAddAttributeTest('', '');
+ });
+ });
+
+ describe('remove attribute tests', () => {
+ it('should remove attribute', () => {
+ runRemoveAttributeTest('', '');
+ });
+
+ it('should remove empty attribute', () => {
+ runRemoveAttributeTest('', '');
+ });
+
+ it('should remove unquoted attribute', () => {
+ runRemoveAttributeTest('', '');
+ });
+
+ it('should remove value-less attribute', () => {
+ runRemoveAttributeTest('', '');
+ });
+
+ it('should not change element without attribute', () => {
+ runRemoveAttributeTest('', '');
+ });
+
+ it('should not remove other attributes', () => {
+ runRemoveAttributeTest(
+ `
+
+
+ `,
+ `
+
+
+ `,
+ );
+ });
+ });
+
+ describe('change attribute tests', () => {
+ it('should change attribute with matching value', () => {
+ runChangeAttributeTest('', '');
+ });
+
+ it('should not change attribute with non-matching value', () => {
+ runChangeAttributeTest('', '');
+ });
+ });
+
+ describe('clear attribute tests', () => {
+ it('should clear attribute with value', () => {
+ runClearAttributeTest('', '');
+ });
+
+ it('should preserve value-less attribute', () => {
+ runClearAttributeTest('', '');
+ });
+
+ it('should add attribute to element without it', () => {
+ runClearAttributeTest('', '');
});
});
@@ -139,7 +224,7 @@ describe('#visitElements', () => {
`,
`
{
>
`;
visitElements(parseTemplate(html).nodes, undefined, node => {
- html = addAttribute(html, node, 'attr1', 'val1');
- html = addAttribute(html, node, 'attr2', 'val2');
+ html = updateAttribute(html, node, 'attr1', () => 'val1');
+ html = updateAttribute(html, node, 'attr2', () => 'val2');
});
expect(html).toBe(`
string | null,
): string {
const existingAttr = node.attributes.find(currentAttr => currentAttr.name === name);
- if (existingAttr) {
- // If the attribute has a value already, replace it.
- if (existingAttr.valueSpan) {
+ // If the attribute has a value already, replace it.
+ if (existingAttr && existingAttr.keySpan) {
+ const updatedValue = update(existingAttr.valueSpan?.toString() || '');
+ if (updatedValue == null) {
+ // Delete attribute
return (
- html.slice(0, existingAttr.valueSpan.start.offset) +
- value +
- html.slice(existingAttr.valueSpan.end.offset)
+ html.slice(0, existingAttr.sourceSpan.start.offset).trimEnd() +
+ html.slice(existingAttr.sourceSpan.end.offset)
);
- } else if (existingAttr.keySpan) {
- // Otherwise add a value to a value-less attribute. Note that the `keySpan` null check is
- // only necessary for the compiler. Technically an attribute should always have a key.
+ } else if (updatedValue == '') {
+ // Delete value from attribute
return (
html.slice(0, existingAttr.keySpan.end.offset) +
- `="${value}"` +
- html.slice(existingAttr.keySpan.end.offset)
+ html.slice(existingAttr.sourceSpan.end.offset)
);
+ } else {
+ // Set attribute value
+ if (existingAttr.valueSpan) {
+ // Replace attribute value
+ return (
+ html.slice(0, existingAttr.valueSpan.start.offset) +
+ updatedValue +
+ html.slice(existingAttr.valueSpan.end.offset)
+ );
+ } else {
+ // Add value to attribute
+ return (
+ html.slice(0, existingAttr.keySpan.end.offset) +
+ `="${updatedValue}"` +
+ html.slice(existingAttr.keySpan.end.offset)
+ );
+ }
}
}
+ const newValue = update(null);
+
+ // No change needed if attribute should be deleted and is already not present.
+ if (newValue == null) {
+ return html;
+ }
+
// Otherwise insert a new attribute.
const index = node.startSourceSpan.start.offset + node.name.length + 1;
const prefix = html.slice(0, index);
const suffix = html.slice(index);
+ const attrText = newValue ? `${name}="${newValue}"` : `${name}`;
if (node.startSourceSpan.start.line === node.startSourceSpan.end.line) {
- return prefix + ` ${name}="${value}"` + suffix;
+ return `${prefix} ${attrText}${suffix}`;
}
const attr = node.attributes[0];
const ctx = attr.sourceSpan.start.getContext(attr.sourceSpan.start.col + 1, 1)!;
const indentation = ctx.before;
- return prefix + indentation + `${name}="${value}"` + suffix;
+ return prefix + indentation + attrText + suffix;
}
/**
diff --git a/src/material/schematics/ng-update/test-cases/v15/legacy-components-v15.spec.ts b/src/material/schematics/ng-update/test-cases/v15/legacy-components-v15.spec.ts
index cbed08f8c524..19dfab508fa8 100644
--- a/src/material/schematics/ng-update/test-cases/v15/legacy-components-v15.spec.ts
+++ b/src/material/schematics/ng-update/test-cases/v15/legacy-components-v15.spec.ts
@@ -286,6 +286,7 @@ describe('v15 legacy components migration', () => {
`@use '@angular/material' as mat;`,
`@include mat.core();`,
`@include mat.core(mat.define-typography-config());`,
+ `@include mat.core-theme(())`,
],
new: [
`@use '@angular/material' as mat;`,
@@ -299,6 +300,7 @@ describe('v15 legacy components migration', () => {
`// If you add typography styles elsewhere, you may want to remove this.`,
`@include mat.all-legacy-component-typographies(mat.define-legacy-typography-config());`,
`@include mat.legacy-core();`,
+ `@include mat.legacy-core-theme(())`,
],
});
});