fix(ng-add): do not add theme file if existing theme is set up (#13468)

* fix(ng-add): do not add theme file if existing theme is set up

* No longer adds other theme files to the workspace configuration if a custom theme has been generated in a previous run already.

* Instead of silently doing nothing if a prebuilt theme is being generated while another prebuilt theme is already defined, we now replace the previous prebuilt theme with the new theme.

* Do not overwrite existing custom-theme files
This commit is contained in:
Paul Gschwendtner
2018-10-10 20:26:49 +02:00
committed by Andrew Seguin
parent 21de68e6d7
commit d1e59a21fc
2 changed files with 113 additions and 11 deletions
+73
View File
@@ -207,4 +207,77 @@ describe('ng-add schematic', () => {
'Expected the project app module to not import the "NoopAnimationsModule".');
});
});
describe('theme files', () => {
/** Path to the default prebuilt theme file that will be added when running ng-add. */
const defaultPrebuiltThemePath =
'./node_modules/@angular/material/prebuilt-themes/indigo-pink.css';
/** Writes a specific style file to the workspace in the given tree */
function writeStyleFileToWorkspace(tree: Tree, stylePath: string) {
const workspace = getWorkspace(tree);
const project = getProjectFromWorkspace(workspace);
const buildOptions = getProjectTargetOptions(project, 'build');
if (!buildOptions.styles) {
buildOptions.styles = [stylePath];
} else {
buildOptions.styles.push(stylePath);
}
tree.overwrite('/angular.json', JSON.stringify(workspace, null, 2));
}
it('should replace existing prebuilt theme files', () => {
const existingThemePath =
'./node_modules/@angular/material/prebuilt-themes/purple-green.css';
writeStyleFileToWorkspace(appTree, existingThemePath);
const tree = runner.runSchematic('ng-add-setup-project', {}, appTree);
const workspace = getWorkspace(tree);
const project = getProjectFromWorkspace(workspace);
const styles = getProjectTargetOptions(project, 'build').styles;
expect(styles).not.toContain(existingThemePath,
'Expected the existing prebuilt theme file to be removed.');
expect(styles).toContain(defaultPrebuiltThemePath,
'Expected the default prebuilt theme to be added.');
});
it('should not replace existing custom theme files', () => {
spyOn(console, 'warn');
writeStyleFileToWorkspace(appTree, './projects/material/custom-theme.scss');
const tree = runner.runSchematic('ng-add-setup-project', {}, appTree);
const workspace = getWorkspace(tree);
const project = getProjectFromWorkspace(workspace);
const styles = getProjectTargetOptions(project, 'build').styles;
expect(styles).not.toContain(defaultPrebuiltThemePath,
'Expected the default prebuilt theme to be not configured.');
expect(console.warn).toHaveBeenCalledWith(
jasmine.stringMatching(/Cannot add.*already a custom theme/));
});
it('should not add a theme file multiple times', () => {
writeStyleFileToWorkspace(appTree, defaultPrebuiltThemePath);
const tree = runner.runSchematic('ng-add-setup-project', {}, appTree);
const workspace = getWorkspace(tree);
const project = getProjectFromWorkspace(workspace);
const styles = getProjectTargetOptions(project, 'build').styles;
expect(styles).toEqual(['projects/material/src/styles.css', defaultPrebuiltThemePath],
'Expected the "styles.css" file and default prebuilt theme to be the only styles');
});
it('should not overwrite existing custom theme files', () => {
appTree.create('/projects/material/custom-theme.scss', 'custom-theme');
const tree = runner.runSchematic('ng-add-setup-project', {theme: 'custom'}, appTree);
expect(tree.readContent('/projects/material/custom-theme.scss')).toBe('custom-theme',
'Expected the old custom theme content to be unchanged.');
});
});
});
+40 -11
View File
@@ -19,7 +19,13 @@ import {getWorkspace} from '@schematics/angular/utility/config';
import {join} from 'path';
import {Schema} from '../schema';
import {createCustomTheme} from './custom-theme';
import {red, bold} from 'chalk';
/** Path segment that can be found in paths that refer to a prebuilt theme. */
const prebuiltThemePathSegment = '@angular/material/prebuilt-themes';
/** Default file name of the custom theme that can be generated. */
const defaultCustomThemeFilename = 'custom-theme.scss';
/** Add pre-built styles to the main project style file. */
export function addThemeToAppStyles(options: Schema): (host: Tree) => Tree {
@@ -60,11 +66,17 @@ function insertCustomTheme(project: WorkspaceProject, projectName: string, host:
// Normalize the path through the devkit utilities because we want to avoid having
// unnecessary path segments and windows backslash delimiters.
const customThemePath = normalize(join(project.sourceRoot, 'custom-theme.scss'));
const customThemePath = normalize(join(project.sourceRoot, defaultCustomThemeFilename));
if (host.exists(customThemePath)) {
console.warn(red(`Cannot create a custom Angular Material theme because
"${customThemePath}" already exists. Skipping custom theme generation.`));
return;
}
host.create(customThemePath, themeContent);
return addStyleToTarget(project, 'build', host, customThemePath, workspace);
addThemeStyleToTarget(project, 'build', host, customThemePath, workspace);
return;
}
const insertion = new InsertChange(stylesPath, 0, themeContent);
@@ -81,25 +93,42 @@ function insertPrebuiltTheme(project: WorkspaceProject, host: Tree, theme: strin
// Path needs to be always relative to the `package.json` or workspace root.
const themePath = `./node_modules/@angular/material/prebuilt-themes/${theme}.css`;
addStyleToTarget(project, 'build', host, themePath, workspace);
addStyleToTarget(project, 'test', host, themePath, workspace);
addThemeStyleToTarget(project, 'build', host, themePath, workspace);
addThemeStyleToTarget(project, 'test', host, themePath, workspace);
}
/** Adds a style entry to the given project target. */
function addStyleToTarget(project: WorkspaceProject, targetName: string, host: Tree,
/** Adds a theming style entry to the given project target options. */
function addThemeStyleToTarget(project: WorkspaceProject, targetName: string, host: Tree,
assetPath: string, workspace: WorkspaceSchema) {
const targetOptions = getProjectTargetOptions(project, targetName);
if (!targetOptions.styles) {
targetOptions.styles = [assetPath];
} else {
const existingStyles = targetOptions.styles.map(s => typeof s === 'string' ? s : s.input);
const hasGivenTheme = existingStyles.find(s => s.includes(assetPath));
const hasOtherTheme = existingStyles.find(s => s.includes('material/prebuilt'));
if (!hasGivenTheme && !hasOtherTheme) {
targetOptions.styles.unshift(assetPath);
for (let [index, stylePath] of existingStyles.entries()) {
// If the given asset is already specified in the styles, we don't need to do anything.
if (stylePath === assetPath) {
return;
}
// In case a prebuilt theme is already set up, we can safely replace the theme with the new
// theme file. If a custom theme is set up, we are not able to safely replace the custom
// theme because these files can contain custom styles, while prebuilt themes are
// always packaged and considered replaceable.
if (stylePath.includes(defaultCustomThemeFilename)) {
console.warn(red(`Cannot add "${bold(assetPath)} to the CLI project configuration ` +
`because there is already a custom theme file referenced. Please manually add ` +
`the "${bold(assetPath)}" style file to your configuration.`));
return;
} else if (stylePath.includes(prebuiltThemePathSegment)) {
targetOptions.styles.splice(index, 1);
}
}
targetOptions.styles.unshift(assetPath);
}
host.overwrite('angular.json', JSON.stringify(workspace, null, 2));