Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(material/schematics): set up a system for migrating scss #24326

Merged
merged 1 commit into from Feb 2, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -17,6 +17,8 @@ ts_library(
"//src/cdk/schematics",
"@npm//@angular-devkit/schematics",
"@npm//@types/node",
"@npm//postcss",
"@npm//postcss-scss",
],
)

Expand Down
11 changes: 10 additions & 1 deletion src/material/schematics/ng-generate/mdc-migration/index.ts
Expand Up @@ -10,7 +10,9 @@ import {Rule, SchematicContext, Tree} from '@angular-devkit/schematics';
import {Schema} from './schema';
import {DevkitFileSystem, UpdateProject, findStylesheetFiles} from '@angular/cdk/schematics';
import {ThemingStylesMigration} from './rules/theming-styles';
import {MIGRATORS} from './rules';
import {dirname} from 'path';
import {StyleMigrator} from './rules/style-migrator';

/** Groups of components that must be migrated together. */
const migrationGroups = [
Expand Down Expand Up @@ -59,6 +61,13 @@ export default function (options: Schema): Rule {
console.log('Migrating:', [...componentsToMigrate]);
console.log('Directory:', migrationDir);

const migrators: StyleMigrator[] = [];
for (let i = 0; i < MIGRATORS.length; i++) {
if (componentsToMigrate.has(MIGRATORS[i].component)) {
migrators.push(MIGRATORS[i]);
}
}

return (tree: Tree, context: SchematicContext) => {
const fileSystem = new DevkitFileSystem(tree);
const program = UpdateProject.createProgramFromTsconfig(
Expand All @@ -71,7 +80,7 @@ export default function (options: Schema): Rule {
const {hasFailures} = project.migrate(
[ThemingStylesMigration],
null,
null,
migrators,
additionalStylesheetPaths,
);

Expand Down
@@ -0,0 +1,196 @@
import {createTestApp, patchDevkitTreeToExposeTypeScript} from '@angular/cdk/schematics/testing';
import {SchematicTestRunner, UnitTestTree} from '@angular-devkit/schematics/testing';
import {Schema} from '../schema';
import {runfiles} from '@bazel/runfiles';

describe('button styles', () => {
let runner: SchematicTestRunner;
let cliAppTree: UnitTestTree;
const tsconfig = '/projects/material/tsconfig.app.json';
const themeFile = '/projects/material/src/theme.scss';

async function runMigrationTest(oldFileContent: string, newFileContent: string) {
cliAppTree.create(themeFile, oldFileContent);
const tree = await migrate({tsconfig, components: ['button']});
expect(tree.readContent(themeFile)).toBe(newFileContent);
}

beforeEach(async () => {
runner = new SchematicTestRunner(
'@angular/material',
runfiles.resolveWorkspaceRelative('src/material/schematics/collection.json'),
);
cliAppTree = patchDevkitTreeToExposeTypeScript(await createTestApp(runner));
});

async function migrate(options: Schema): Promise<UnitTestTree> {
return await runner.runSchematicAsync('mdcMigration', options, cliAppTree).toPromise();
}

describe('mixin migrations', () => {
it('should replace the old theme with the new ones', async () => {
await runMigrationTest(
`
@use '@angular/material' as mat;
$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-theme($theme);
@include mat.mdc-icon-typography($theme);
`,
);
});

it('should use the correct namespace', async () => {
await runMigrationTest(
`
@use '@angular/material' as arbitrary;
$theme: ();
@include arbitrary.button-theme($theme);
`,
`
@use '@angular/material' as arbitrary;
$theme: ();
@include arbitrary.mdc-button-theme($theme);
@include arbitrary.mdc-button-typography($theme);
@include arbitrary.mdc-fab-theme($theme);
@include arbitrary.mdc-fab-typography($theme);
@include arbitrary.mdc-icon-theme($theme);
@include arbitrary.mdc-icon-typography($theme);
`,
);
});

it('should handle updating multiple themes', async () => {
await runMigrationTest(
`
@use '@angular/material' as mat;
$light-theme: ();
$dark-theme: ();
@include mat.button-theme($light-theme);
@include mat.button-theme($dark-theme);
`,
`
@use '@angular/material' as mat;
$light-theme: ();
$dark-theme: ();
@include mat.mdc-button-theme($light-theme);
@include mat.mdc-button-typography($light-theme);
@include mat.mdc-fab-theme($light-theme);
@include mat.mdc-fab-typography($light-theme);
@include mat.mdc-icon-theme($light-theme);
@include mat.mdc-icon-typography($light-theme);
@include mat.mdc-button-theme($dark-theme);
@include mat.mdc-button-typography($dark-theme);
@include mat.mdc-fab-theme($dark-theme);
@include mat.mdc-fab-typography($dark-theme);
@include mat.mdc-icon-theme($dark-theme);
@include mat.mdc-icon-typography($dark-theme);
`,
);
});

it('should preserve whitespace', async () => {
await runMigrationTest(
`
@use '@angular/material' as mat;
$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-theme($theme);
@include mat.mdc-icon-typography($theme);
`,
);
});
});

describe('selector migrations', () => {
it('should update the legacy mat-button classname', async () => {
await runMigrationTest(
`
.mat-button {
padding: 50px;
}
`,
`
.mat-mdc-button {
padding: 50px;
}
`,
);
});

it('should update multiple legacy classnames', async () => {
await runMigrationTest(
`
.mat-button {
padding: 50px;
}
.mat-button-base {
padding: 25px;
}
`,
`
.mat-mdc-button {
padding: 50px;
}
.mat-mdc-button-base {
padding: 25px;
}
`,
);
});

it('should update a legacy classname w/ multiple selectors', async () => {
await runMigrationTest(
`
.some-class.mat-button, .another-class {
padding: 50px;
}
`,
`
.some-class.mat-mdc-button, .another-class {
padding: 50px;
}
`,
);
});

it('should preserve the whitespace of multiple selectors', async () => {
await runMigrationTest(
`
.some-class,
.mat-button,
.another-class { padding: 50px; }
`,
`
.some-class,
.mat-mdc-button,
.another-class { padding: 50px; }
`,
);
});
});
});
@@ -0,0 +1,38 @@
/**
* @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 ButtonStylesMigrator extends StyleMigrator {
component = 'button';

mixinChanges = [
{
old: 'button-theme',
new: [
'mdc-button-theme',
'mdc-button-typography',
'mdc-fab-theme',
'mdc-fab-typography',
'mdc-icon-theme',
'mdc-icon-typography',
],
},
];

classChanges: ClassNameChange[] = [
{old: '.mat-button-base', new: '.mat-mdc-button-base'},
{old: '.mat-button', new: '.mat-mdc-button'},
{old: '.mat-raised-button', new: '.mat-mdc-raised-button'},
{old: '.mat-icon-button', new: '.mat-mdc-icon-button'},
{old: '.mat-fab', new: '.mat-mdc-fab'},
{old: '.mat-mini-fab', new: '.mat-mdc-mini-fab'},
{old: '.mat-stroked-button', new: '.mat-mdc-outlined-button'},
{old: '.mat-flat-button', new: '.mat-mdc-flat-button'},
];
}
@@ -0,0 +1,12 @@
/**
* @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 {ButtonStylesMigrator} from './button-styles';
import {StyleMigrator} from './style-migrator';

export const MIGRATORS: StyleMigrator[] = [new ButtonStylesMigrator()];