diff --git a/src/material/schematics/ng-update/BUILD.bazel b/src/material/schematics/ng-update/BUILD.bazel index 5457154fa98d..6ee349fda826 100644 --- a/src/material/schematics/ng-update/BUILD.bazel +++ b/src/material/schematics/ng-update/BUILD.bazel @@ -68,6 +68,7 @@ ts_project( jasmine_test( name = "test", data = [ + ":ng_update_index", ":schematics_test_cases", ":test_lib", "//src/material/schematics:collection_assets", diff --git a/src/material/schematics/ng-update/index.ts b/src/material/schematics/ng-update/index.ts index cd99930de5ab..6f87cadb0a54 100644 --- a/src/material/schematics/ng-update/index.ts +++ b/src/material/schematics/ng-update/index.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import {Rule, SchematicContext} from '@angular-devkit/schematics'; +import {chain, Rule, SchematicContext} from '@angular-devkit/schematics'; import { createMigrationSchematicRule, NullableDevkitMigration, @@ -22,14 +22,72 @@ const materialMigrations: NullableDevkitMigration[] = [ ExplicitSystemVariablePrefixMigration, ]; -/** Entry point for the migration schematics with target of Angular Material v19 */ +/** Entry point for the migration schematics with target of Angular Material v20 */ export function updateToV20(): Rule { - return createMigrationSchematicRule( - TargetVersion.V20, - materialMigrations, - materialUpgradeData, - onMigrationComplete, - ); + return chain([ + createMigrationSchematicRule( + TargetVersion.V20, + materialMigrations, + materialUpgradeData, + onMigrationComplete, + ), + renameMdcTokens(), + renameComponentTokens(), + ]); +} + +// Renames any CSS variables beginning with "--mdc-" to be "--mat-". These CSS variables +// refer to tokens that used to be derived from a mix of MDC and Angular. Now all the tokens +// are converged on being prefixed "--mat-". +function renameMdcTokens(): Rule { + return tree => { + tree.visit(path => { + const content = tree.readText(path); + const updatedContent = content.replace('--mdc-', '--mat-'); + tree.overwrite(path, updatedContent); + }); + }; +} + +// Renames Angular Material component token CSS variables that were renamed so that the base +// component's name came first or otherwise renamed to match our terminology instead of MDC's. +function renameComponentTokens(): Rule { + const tokenPrefixes = [ + {old: '--mat-circular-progress', replacement: '--mat-progress-spinner'}, + {old: '--mat-elevated-card', replacement: '--mat-card-elevated'}, + {old: '--mat-extended-fab', replacement: '--mat-fab-extended'}, + {old: '--mat-filled-button', replacement: '--mat-button-filled'}, + {old: '--mat-filled-text-field', replacement: '--mat-form-field-filled'}, + {old: '--mat-full-pseudo-checkbox', replacement: '--mat-pseudo-checkbox-full'}, + {old: '--mat-legacy-button-toggle', replacement: '--mat-button-toggle-legacy'}, + {old: '--mat-linear-progress', replacement: '--mat-progress-bar'}, + {old: '--mat-minimal-pseudo-checkbox', replacement: '--mat-pseudo-checkbox-minimal'}, + {old: '--mat-outlined-button', replacement: '--mat-button-outlined'}, + {old: '--mat-outlined-card', replacement: '--mat-card-outlined'}, + {old: '--mat-outlined-text-field', replacement: '--mat-form-field-outlined'}, + {old: '--mat-plain-tooltip', replacement: '--mat-tooltip'}, + {old: '--mat-protected-button', replacement: '--mat-button-protected'}, + {old: '--mat-secondary-navigation-tab', replacement: '--mat-tab'}, + {old: '--mat-standard-button-toggle', replacement: '--mat-button-toggle'}, + {old: '--mat-switch', replacement: '--mat-slide-toggle'}, + {old: '--mat-tab-header', replacement: '--mat-tab'}, + {old: '--mat-tab-header-with-background', replacement: '--mat-tab'}, + {old: '--mat-tab-indicator', replacement: '--mat-tab'}, + {old: '--mat-text-button', replacement: '--mat-button-text'}, + {old: '--mat-tonal-button', replacement: '--mat-button-tonal'}, + ]; + return tree => { + tree.visit(path => { + const content = tree.readText(path); + let updatedContent = content; + for (const tokenPrefix of tokenPrefixes) { + updatedContent = updatedContent.replace(tokenPrefix.old, tokenPrefix.replacement); + } + if (content !== updatedContent) { + tree.overwrite(path, updatedContent); + } + }); + }; } /** Function that will be called when the migration completed. */ diff --git a/src/material/schematics/ng-update/test-cases/rename-mdc-tokens.spec.ts b/src/material/schematics/ng-update/test-cases/rename-mdc-tokens.spec.ts new file mode 100644 index 000000000000..153accbb0286 --- /dev/null +++ b/src/material/schematics/ng-update/test-cases/rename-mdc-tokens.spec.ts @@ -0,0 +1,51 @@ +import {UnitTestTree} from '@angular-devkit/schematics/testing'; +import {createTestCaseSetup} from '../../../../cdk/schematics/testing'; +import {MIGRATION_PATH} from '../../paths'; + +const THEME_FILE_PATH = '/projects/cdk-testing/src/theme.scss'; + +describe('v20 rename tokens migration', () => { + let tree: UnitTestTree; + let writeFile: (filename: string, content: string) => void; + let runMigration: () => Promise; + + function stripWhitespace(content: string): string { + return content.replace(/\s/g, ''); + } + + beforeEach(async () => { + const testSetup = await createTestCaseSetup('migration-v20', MIGRATION_PATH, []); + tree = testSetup.appTree; + writeFile = testSetup.writeFile; + runMigration = testSetup.runFixers; + }); + + it('should rename mdc tokens to mat and change component ordering', async () => { + writeFile( + THEME_FILE_PATH, + ` + html { + --mdc-icon-button-icon-size: 24px; + --mat-filled-button-color: red; + --mat-filled-text-field-color: red; + --mat-full-pseudo-checkbox-color: red; + --mat-legacy-button-toggle-color: red; + } + `, + ); + + await runMigration(); + + expect(stripWhitespace(tree.readText(THEME_FILE_PATH))).toBe( + stripWhitespace(` + html { + --mat-icon-button-icon-size: 24px; + --mat-button-filled-color: red; + --mat-form-field-filled-color: red; + --mat-pseudo-checkbox-full-color: red; + --mat-button-toggle-legacy-color: red; + } + `), + ); + }); +});