From 7460ccdd124dbf8e31e5c43f3c6debffe160fd37 Mon Sep 17 00:00:00 2001 From: Amy Sorto <8575252+amysorto@users.noreply.github.com> Date: Thu, 28 Apr 2022 21:56:52 +0000 Subject: [PATCH] feat(material/schematics): add snack-bar styles migrator and tests --- .../components/button/button-styles.spec.ts | 22 ++ .../rules/components/button/button-styles.ts | 1 + .../multiple-components-styles.spec.ts | 24 ++ .../snack-bar/snack-bar-styles.spec.ts | 216 ++++++++++++++++++ .../components/snack-bar/snack-bar-styles.ts | 36 +++ .../ng-generate/mdc-migration/rules/index.ts | 5 + .../mdc-migration/rules/style-migrator.ts | 34 ++- .../mdc-migration/rules/theming-styles.ts | 3 + 8 files changed, 337 insertions(+), 4 deletions(-) create mode 100644 src/material/schematics/ng-generate/mdc-migration/rules/components/snack-bar/snack-bar-styles.spec.ts create mode 100644 src/material/schematics/ng-generate/mdc-migration/rules/components/snack-bar/snack-bar-styles.ts diff --git a/src/material/schematics/ng-generate/mdc-migration/rules/components/button/button-styles.spec.ts b/src/material/schematics/ng-generate/mdc-migration/rules/components/button/button-styles.spec.ts index 8b3dfb1488e0..c9746aa52713 100644 --- a/src/material/schematics/ng-generate/mdc-migration/rules/components/button/button-styles.spec.ts +++ b/src/material/schematics/ng-generate/mdc-migration/rules/components/button/button-styles.spec.ts @@ -38,6 +38,28 @@ describe('button styles', () => { ); }); + it('should replace the old theme with the non-duplicated new ones', async () => { + await runMigrationTest( + ` + @use '@angular/material' as mat; + $theme: (); + @include mat.mdc-button-theme($theme); + @include mat.mdc-button-typography($theme); + @include mat.button-theme($theme); + `, + ` + @use '@angular/material' as mat; + $theme: (); + @include mat.mdc-button-theme($theme); + @include mat.mdc-button-typography($theme); + @include mat.mdc-fab-theme($theme); + @include mat.mdc-fab-typography($theme); + @include mat.mdc-icon-button-theme($theme); + @include mat.mdc-icon-button-typography($theme); + `, + ); + }); + it('should use the correct namespace', async () => { await runMigrationTest( ` diff --git a/src/material/schematics/ng-generate/mdc-migration/rules/components/button/button-styles.ts b/src/material/schematics/ng-generate/mdc-migration/rules/components/button/button-styles.ts index 6fa215e080a8..763bfb805c1d 100644 --- a/src/material/schematics/ng-generate/mdc-migration/rules/components/button/button-styles.ts +++ b/src/material/schematics/ng-generate/mdc-migration/rules/components/button/button-styles.ts @@ -24,6 +24,7 @@ export class ButtonStylesMigrator extends StyleMigrator { 'mdc-icon-button-theme', 'mdc-icon-button-typography', ], + checkForDuplicates: true, }, ]; 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 21e02aea0718..b0af3c69297f 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 @@ -42,6 +42,30 @@ describe('multiple component styles', () => { ); }); + it('should add theme once if both components include it', async () => { + await runMigrationTest( + ['button', 'snack-bar'], + ` + @use '@angular/material' as mat; + $theme: (); + @include mat.button-theme($theme); + @include mat.snack-bar-theme($theme); + `, + ` + @use '@angular/material' as mat; + $theme: (); + @include mat.mdc-button-theme($theme); + @include mat.mdc-button-typography($theme); + @include mat.mdc-fab-theme($theme); + @include mat.mdc-fab-typography($theme); + @include mat.mdc-icon-button-theme($theme); + @include mat.mdc-icon-button-typography($theme); + @include mat.mdc-snack-bar-theme($theme); + @include mat.mdc-snack-bar-typography($theme); + `, + ); + }); + it('should add correct theme if all-component-themes mixin included', async () => { await runMigrationTest( ['checkbox', 'radio'], diff --git a/src/material/schematics/ng-generate/mdc-migration/rules/components/snack-bar/snack-bar-styles.spec.ts b/src/material/schematics/ng-generate/mdc-migration/rules/components/snack-bar/snack-bar-styles.spec.ts new file mode 100644 index 000000000000..566a8d1996c3 --- /dev/null +++ b/src/material/schematics/ng-generate/mdc-migration/rules/components/snack-bar/snack-bar-styles.spec.ts @@ -0,0 +1,216 @@ +import {createTestApp, patchDevkitTreeToExposeTypeScript} from '@angular/cdk/schematics/testing'; +import {SchematicTestRunner, UnitTestTree} from '@angular-devkit/schematics/testing'; +import {createNewTestRunner, migrateComponents, THEME_FILE} from '../test-setup-helper'; + +describe('snack-bar styles', () => { + let runner: SchematicTestRunner; + let cliAppTree: UnitTestTree; + + async function runMigrationTest(oldFileContent: string, newFileContent: string) { + cliAppTree.create(THEME_FILE, oldFileContent); + const tree = await migrateComponents(['snack-bar'], runner, cliAppTree); + expect(tree.readContent(THEME_FILE)).toBe(newFileContent); + } + + beforeEach(async () => { + runner = createNewTestRunner(); + cliAppTree = patchDevkitTreeToExposeTypeScript(await createTestApp(runner)); + }); + + describe('mixin migrations', () => { + it('should replace the old theme with the new ones', async () => { + await runMigrationTest( + ` + @use '@angular/material' as mat; + $theme: (); + @include mat.snack-bar-theme($theme); + `, + ` + @use '@angular/material' as mat; + $theme: (); + @include mat.mdc-snack-bar-theme($theme); + @include mat.mdc-snack-bar-typography($theme); + @include mat.mdc-button-theme($theme); + @include mat.mdc-button-typography($theme); + `, + ); + }); + + it('should replace the old theme with the new ones and keep the non MDC button theme', async () => { + await runMigrationTest( + ` + @use '@angular/material' as mat; + $theme: (); + @include mat.button-theme($theme); + @include mat.snack-bar-theme($theme); + `, + ` + @use '@angular/material' as mat; + $theme: (); + @include mat.button-theme($theme); + @include mat.mdc-snack-bar-theme($theme); + @include mat.mdc-snack-bar-typography($theme); + @include mat.mdc-button-theme($theme); + @include mat.mdc-button-typography($theme); + `, + ); + }); + + it('should replace the old theme with the non-duplicated new ones', async () => { + await runMigrationTest( + ` + @use '@angular/material' as mat; + $theme: (); + @include mat.snack-bar-theme($theme); + @include mat.mdc-button-theme($theme); + @include mat.mdc-button-typography($theme); + `, + ` + @use '@angular/material' as mat; + $theme: (); + @include mat.mdc-snack-bar-theme($theme); + @include mat.mdc-snack-bar-typography($theme); + @include mat.mdc-button-theme($theme); + @include mat.mdc-button-typography($theme); + `, + ); + }); + + it('should use the correct namespace', async () => { + await runMigrationTest( + ` + @use '@angular/material' as arbitrary; + $theme: (); + @include arbitrary.snack-bar-theme($theme); + `, + ` + @use '@angular/material' as arbitrary; + $theme: (); + @include arbitrary.mdc-snack-bar-theme($theme); + @include arbitrary.mdc-snack-bar-typography($theme); + @include arbitrary.mdc-button-theme($theme); + @include arbitrary.mdc-button-typography($theme); + `, + ); + }); + + it('should handle updating multiple themes', async () => { + await runMigrationTest( + ` + @use '@angular/material' as mat; + $light-theme: (); + $dark-theme: (); + @include mat.snack-bar-theme($light-theme); + @include mat.snack-bar-theme($dark-theme); + `, + ` + @use '@angular/material' as mat; + $light-theme: (); + $dark-theme: (); + @include mat.mdc-snack-bar-theme($light-theme); + @include mat.mdc-snack-bar-typography($light-theme); + @include mat.mdc-button-theme($light-theme); + @include mat.mdc-button-typography($light-theme); + @include mat.mdc-snack-bar-theme($dark-theme); + @include mat.mdc-snack-bar-typography($dark-theme); + @include mat.mdc-button-theme($dark-theme); + @include mat.mdc-button-typography($dark-theme); + `, + ); + }); + + it('should preserve whitespace', async () => { + await runMigrationTest( + ` + @use '@angular/material' as mat; + $theme: (); + + + @include mat.snack-bar-theme($theme); + + + `, + ` + @use '@angular/material' as mat; + $theme: (); + + + @include mat.mdc-snack-bar-theme($theme); + @include mat.mdc-snack-bar-typography($theme); + @include mat.mdc-button-theme($theme); + @include mat.mdc-button-typography($theme); + + + `, + ); + }); + }); + + describe('selector migrations', () => { + it('should update the legacy mat-snack-bar-container classname', async () => { + await runMigrationTest( + ` + .mat-snack-bar-container { + padding: 24px; + } + `, + ` + .mat-mdc-snack-bar-container { + padding: 24px; + } + `, + ); + }); + + it('should update multiple legacy classnames', async () => { + await runMigrationTest( + ` + .mat-snack-bar-container { + padding: 24px; + } + .mat-simple-snackbar { + color: red; + } + `, + ` + .mat-mdc-snack-bar-container { + padding: 24px; + } + .mat-mdc-simple-snack-bar { + color: red; + } + `, + ); + }); + + it('should update a legacy classname w/ multiple selectors', async () => { + await runMigrationTest( + ` + .some-class.mat-snack-bar-container, .another-class { + padding: 24px; + } + `, + ` + .some-class.mat-mdc-snack-bar-container, .another-class { + padding: 24px; + } + `, + ); + }); + + it('should preserve the whitespace of multiple selectors', async () => { + await runMigrationTest( + ` + .some-class, + .mat-snack-bar-container, + .another-class { padding: 24px; } + `, + ` + .some-class, + .mat-mdc-snack-bar-container, + .another-class { padding: 24px; } + `, + ); + }); + }); +}); diff --git a/src/material/schematics/ng-generate/mdc-migration/rules/components/snack-bar/snack-bar-styles.ts b/src/material/schematics/ng-generate/mdc-migration/rules/components/snack-bar/snack-bar-styles.ts new file mode 100644 index 000000000000..bf5bfb7c08c1 --- /dev/null +++ b/src/material/schematics/ng-generate/mdc-migration/rules/components/snack-bar/snack-bar-styles.ts @@ -0,0 +1,36 @@ +/** + * @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 {ClassNameChange, StyleMigrator} from '../../style-migrator'; + +export class SnackBarMigrator extends StyleMigrator { + component = 'snack-bar'; + + // There are no other selectors with the 'mat-snack-bar' prefix available + // aside from the specified changes below + deprecatedPrefixes = []; + + mixinChanges = [ + { + old: 'snack-bar-theme', + new: [ + 'mdc-snack-bar-theme', + 'mdc-snack-bar-typography', + 'mdc-button-theme', + 'mdc-button-typography', + ], + checkForDuplicates: true, + }, + ]; + + classChanges: ClassNameChange[] = [ + {old: '.mat-snack-bar-container', new: '.mat-mdc-snack-bar-container'}, + {old: '.mat-snack-bar-handset', new: '.mat-mdc-snack-bar-handset'}, + {old: '.mat-simple-snackbar', new: '.mat-mdc-simple-snack-bar'}, + ]; +} 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 25c1e971f6cb..3ebc3d2d12ae 100644 --- a/src/material/schematics/ng-generate/mdc-migration/rules/index.ts +++ b/src/material/schematics/ng-generate/mdc-migration/rules/index.ts @@ -29,6 +29,7 @@ import {RuntimeMigrator} from './ts-migration/runtime-migrator'; import {SelectStylesMigrator} from './components/select/select-styles'; import {SlideToggleStylesMigrator} from './components/slide-toggle/slide-toggle-styles'; import {SliderStylesMigrator} from './components/slider/slider-styles'; +import {SnackBarMigrator} from './components/snack-bar/snack-bar-styles'; import {TableStylesMigrator} from './components/table/table-styles'; import {TabsStylesMigrator} from './components/tabs/tabs-styles'; import {TooltipStylesMigrator} from './components/tooltip/tooltip-styles'; @@ -129,6 +130,10 @@ export const MIGRATORS: ComponentMigrator[] = [ styles: new SliderStylesMigrator(), runtime: new RuntimeMigrator('slider'), }, + { + component: 'snack-bar', + styles: new SnackBarMigrator(), + }, { component: 'table', styles: new TableStylesMigrator(), 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 e701ef821f0f..1fe6953b9bb1 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 @@ -9,7 +9,7 @@ import * as postcss from 'postcss'; const END_OF_SELECTOR_REGEX = '(?!-)'; -const THEME_NAME_REGEX = '\\(((\\s|.)*)\\)'; +const MIXIN_ARGUMENTS_REGEX = '\\(((\\s|.)*)\\)'; /** The changes to a class names. */ export interface ClassNameChange { @@ -27,6 +27,9 @@ export interface MixinChange { /** The name(s) of the new scss mixin(s). */ new: string[]; + + /** Optional check to see if new scss mixin(s) already exist in the styles */ + checkForDuplicates?: boolean; } /** StyleMigrator implements the basic case for migrating old component styles to new ones. */ @@ -69,10 +72,33 @@ export abstract class StyleMigrator { return; } + // Check if mixin replacements already exist in the stylesheet + const replacements = [...change.new]; + if (change.checkForDuplicates) { + const mixinArgumentMatches = atRule.params.match(MIXIN_ARGUMENTS_REGEX); + atRule.root().walkAtRules(rule => { + for (const index in replacements) { + // Include arguments if applicable since there can be multiple themes. + // The first element of the match object includes parentheses since + // it's the whole match from the regex. + const mixinName = + replacements[index] + (mixinArgumentMatches ? mixinArgumentMatches[0] : ''); + if (rule.params.includes(mixinName)) { + replacements.splice(Number(index), 1); + } + } + }); + } + + // Don't do anything if all the new changes already exist in the stylesheet + if (replacements.length < 1) { + return; + } + // Cloning & inserting the first node before changing the // indentation preserves the indentation of the first node (e.g. 3 newlines). atRule.cloneBefore({ - params: atRule.params.replace(change.old, change.new[0]), + params: atRule.params.replace(change.old, replacements[0]), }); // We change the indentation before inserting all of the other nodes @@ -82,9 +108,9 @@ export abstract class StyleMigrator { // Note: It may be more efficient to create an array of clones and then insert // them all at once. If we are having performance issues, we should revisit this. - for (let i = 1; i < change.new.length; i++) { + for (let i = 1; i < replacements.length; i++) { atRule.cloneBefore({ - params: atRule.params.replace(change.old, change.new[i]), + params: atRule.params.replace(change.old, replacements[i]), }); } atRule.remove(); 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 375ff86e302a..f2099dae7c5e 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 @@ -45,6 +45,9 @@ export class ThemingStylesMigration extends Migration