diff --git a/packages/angular_devkit/build_angular/src/builders/karma/index.ts b/packages/angular_devkit/build_angular/src/builders/karma/index.ts index 1d16eeba4753..6f232944290b 100644 --- a/packages/angular_devkit/build_angular/src/builders/karma/index.ts +++ b/packages/angular_devkit/build_angular/src/builders/karma/index.ts @@ -96,7 +96,7 @@ export function execute( const karmaOptions: KarmaConfigOptions = options.karmaConfig ? {} - : getBuiltInKarmaConfig(karma, context.workspaceRoot, projectName); + : getBuiltInKarmaConfig(context.workspaceRoot, projectName); karmaOptions.singleRun = singleRun; @@ -186,7 +186,6 @@ export function execute( } function getBuiltInKarmaConfig( - karma: typeof import('karma'), workspaceRoot: string, projectName: string, ): ConfigOptions & Record { @@ -197,6 +196,7 @@ function getBuiltInKarmaConfig( const workspaceRootRequire = createRequire(workspaceRoot + '/'); + // Any changes to the config here need to be synced to: packages/schematics/angular/config/files/karma.conf.js.template return { basePath: '', frameworks: ['jasmine', '@angular-devkit/build-angular'], @@ -219,10 +219,6 @@ function getBuiltInKarmaConfig( reporters: [{ type: 'html' }, { type: 'text-summary' }], }, reporters: ['progress', 'kjhtml'], - port: 9876, - colors: true, - logLevel: karma.constants.LOG_INFO, - autoWatch: true, browsers: ['Chrome'], restartOnFileChange: true, }; diff --git a/packages/schematics/angular/collection.json b/packages/schematics/angular/collection.json index 9b828f5232e6..65a57023b64f 100755 --- a/packages/schematics/angular/collection.json +++ b/packages/schematics/angular/collection.json @@ -120,6 +120,11 @@ "factory": "./environments", "schema": "./environments/schema.json", "description": "Generate project environment files." + }, + "config": { + "factory": "./config", + "schema": "./config/schema.json", + "description": "Generates a configuration file." } } } diff --git a/packages/schematics/angular/config/files/.browserslistrc.template b/packages/schematics/angular/config/files/.browserslistrc.template new file mode 100644 index 000000000000..4f9ac26980c1 --- /dev/null +++ b/packages/schematics/angular/config/files/.browserslistrc.template @@ -0,0 +1,16 @@ +# This file is used by the build system to adjust CSS and JS output to support the specified browsers below. +# For additional information regarding the format and rule options, please see: +# https://github.com/browserslist/browserslist#queries + +# For the full list of supported browsers by the Angular framework, please see: +# https://angular.io/guide/browser-support + +# You can see what browsers were selected by your queries by running: +# npx browserslist + +last 1 Chrome version +last 1 Firefox version +last 2 Edge major versions +last 2 Safari major versions +last 2 iOS major versions +Firefox ESR diff --git a/packages/schematics/angular/config/files/karma.conf.js.template b/packages/schematics/angular/config/files/karma.conf.js.template new file mode 100644 index 000000000000..bac55a495a5b --- /dev/null +++ b/packages/schematics/angular/config/files/karma.conf.js.template @@ -0,0 +1,39 @@ +// Karma configuration file, see link for more information +// https://karma-runner.github.io/1.0/config/configuration-file.html + +module.exports = function (config) { + config.set({ + basePath: '', + frameworks: ['jasmine', '@angular-devkit/build-angular'], + plugins: [ + require('karma-jasmine'), + require('karma-chrome-launcher'), + require('karma-jasmine-html-reporter'), + require('karma-coverage'), + require('@angular-devkit/build-angular/plugins/karma') + ], + client: { + jasmine: { + // you can add configuration options for Jasmine here + // the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html + // for example, you can disable the random execution with `random: false` + // or set a specific seed with `seed: 4321` + }, + clearContext: false // leave Jasmine Spec Runner output visible in browser + }, + jasmineHtmlReporter: { + suppressAll: true // removes the duplicated traces + }, + coverageReporter: { + dir: require('path').join(__dirname, '<%= relativePathToWorkspaceRoot %>/coverage/<%= folderName %>'), + subdir: '.', + reporters: [ + { type: 'html' }, + { type: 'text-summary' } + ] + }, + reporters: ['progress', 'kjhtml'], + browsers: ['Chrome'], + restartOnFileChange: true + }); +}; diff --git a/packages/schematics/angular/config/index.ts b/packages/schematics/angular/config/index.ts new file mode 100644 index 000000000000..fea153389bbf --- /dev/null +++ b/packages/schematics/angular/config/index.ts @@ -0,0 +1,95 @@ +/** + * @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 { + Rule, + SchematicsException, + apply, + applyTemplates, + filter, + mergeWith, + move, + strings, + url, +} from '@angular-devkit/schematics'; +import { AngularBuilder, readWorkspace, updateWorkspace } from '@schematics/angular/utility'; +import { posix as path } from 'path'; +import { relativePathToWorkspaceRoot } from '../utility/paths'; +import { Schema as ConfigOptions, Type as ConfigType } from './schema'; + +export default function (options: ConfigOptions): Rule { + switch (options.type) { + case ConfigType.Karma: + return addKarmaConfig(options); + case ConfigType.Browserslist: + return addBrowserslistConfig(options); + default: + throw new SchematicsException(`"${options.type}" is an unknown configuration file type.`); + } +} + +function addBrowserslistConfig(options: ConfigOptions): Rule { + return async (host) => { + const workspace = await readWorkspace(host); + const project = workspace.projects.get(options.project); + if (!project) { + throw new SchematicsException(`Project name "${options.project}" doesn't not exist.`); + } + + return mergeWith( + apply(url('./files'), [ + filter((p) => p.endsWith('.browserslistrc.template')), + applyTemplates({}), + move(project.root), + ]), + ); + }; +} + +function addKarmaConfig(options: ConfigOptions): Rule { + return updateWorkspace((workspace) => { + const project = workspace.projects.get(options.project); + if (!project) { + throw new SchematicsException(`Project name "${options.project}" doesn't not exist.`); + } + + const testTarget = project.targets.get('test'); + if (!testTarget) { + throw new SchematicsException( + `No "test" target found for project "${options.project}".` + + ' A "test" target is required to generate a karma configuration.', + ); + } + + if (testTarget.builder !== AngularBuilder.Karma) { + throw new SchematicsException( + `Cannot add a karma configuration as builder for "test" target in project does not use "${AngularBuilder.Karma}".`, + ); + } + + testTarget.options ??= {}; + testTarget.options.karmaConfig = path.join(project.root, 'karma.conf.js'); + + // If scoped project (i.e. "@foo/bar"), convert dir to "foo/bar". + let folderName = options.project.startsWith('@') ? options.project.slice(1) : options.project; + if (/[A-Z]/.test(folderName)) { + folderName = strings.dasherize(folderName); + } + + return mergeWith( + apply(url('./files'), [ + filter((p) => p.endsWith('karma.conf.js.template')), + applyTemplates({ + relativePathToWorkspaceRoot: relativePathToWorkspaceRoot(project.root), + folderName, + }), + move(project.root), + ]), + ); + }); +} diff --git a/packages/schematics/angular/config/index_spec.ts b/packages/schematics/angular/config/index_spec.ts new file mode 100644 index 000000000000..9b549271f09a --- /dev/null +++ b/packages/schematics/angular/config/index_spec.ts @@ -0,0 +1,82 @@ +/** + * @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 { SchematicTestRunner, UnitTestTree } from '@angular-devkit/schematics/testing'; +import { Schema as ApplicationOptions } from '../application/schema'; +import { Schema as WorkspaceOptions } from '../workspace/schema'; +import { Schema as ConfigOptions, Type as ConfigType } from './schema'; + +describe('Application Schematic', () => { + const schematicRunner = new SchematicTestRunner( + '@schematics/angular', + require.resolve('../collection.json'), + ); + + const workspaceOptions: WorkspaceOptions = { + name: 'workspace', + newProjectRoot: 'projects', + version: '15.0.0', + }; + + const defaultAppOptions: ApplicationOptions = { + name: 'foo', + inlineStyle: true, + inlineTemplate: true, + routing: false, + skipPackageJson: false, + }; + + let applicationTree: UnitTestTree; + function runConfigSchematic(type: ConfigType): Promise { + return schematicRunner.runSchematic( + 'config', + { + project: 'foo', + type, + }, + applicationTree, + ); + } + + beforeEach(async () => { + const workspaceTree = await schematicRunner.runSchematic('workspace', workspaceOptions); + applicationTree = await schematicRunner.runSchematic( + 'application', + defaultAppOptions, + workspaceTree, + ); + }); + + describe(`when 'type' is 'karma'`, () => { + it('should create a karma.conf.js file', async () => { + const tree = await runConfigSchematic(ConfigType.Karma); + expect(tree.exists('projects/foo/karma.conf.js')).toBeTrue(); + }); + + it('should set the right coverage folder', async () => { + const tree = await runConfigSchematic(ConfigType.Karma); + const karmaConf = tree.readText('projects/foo/karma.conf.js'); + expect(karmaConf).toContain(`dir: require('path').join(__dirname, '../../coverage/foo')`); + }); + + it(`should set 'karmaConfig' in test builder`, async () => { + const tree = await runConfigSchematic(ConfigType.Karma); + const config = JSON.parse(tree.readContent('/angular.json')); + const prj = config.projects.foo; + const { karmaConfig } = prj.architect.test.options; + expect(karmaConfig).toBe('projects/foo/karma.conf.js'); + }); + }); + + describe(`when 'type' is 'browserslist'`, () => { + it('should create a .browserslistrc file', async () => { + const tree = await runConfigSchematic(ConfigType.Browserslist); + expect(tree.exists('projects/foo/.browserslistrc')).toBeTrue(); + }); + }); +}); diff --git a/packages/schematics/angular/config/schema.json b/packages/schematics/angular/config/schema.json new file mode 100644 index 000000000000..87cb32851223 --- /dev/null +++ b/packages/schematics/angular/config/schema.json @@ -0,0 +1,28 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema", + "$id": "SchematicsAngularConfig", + "title": "Angular Config File Options Schema", + "type": "object", + "additionalProperties": false, + "description": "Generates a configuration file in the given project.", + "properties": { + "project": { + "type": "string", + "description": "The name of the project.", + "$default": { + "$source": "projectName" + } + }, + "type": { + "type": "string", + "description": "Specifies which type of configuration file to create.", + "enum": ["karma", "browserslist"], + "x-prompt": "Which type of configuration file would you like to create?", + "$default": { + "$source": "argv", + "index": 0 + } + } + }, + "required": ["project", "type"] +} diff --git a/tests/legacy-cli/e2e/tests/generate/confg/type-browserslist.ts b/tests/legacy-cli/e2e/tests/generate/confg/type-browserslist.ts new file mode 100644 index 000000000000..b9717852a4f1 --- /dev/null +++ b/tests/legacy-cli/e2e/tests/generate/confg/type-browserslist.ts @@ -0,0 +1,6 @@ +import { ng } from '../../../utils/process'; + +export default async function () { + await ng('generate', 'config', 'browserslist'); + await ng('build'); +} diff --git a/tests/legacy-cli/e2e/tests/generate/confg/type-karma.ts b/tests/legacy-cli/e2e/tests/generate/confg/type-karma.ts new file mode 100644 index 000000000000..87ba023580b9 --- /dev/null +++ b/tests/legacy-cli/e2e/tests/generate/confg/type-karma.ts @@ -0,0 +1,6 @@ +import { ng } from '../../../utils/process'; + +export default async function () { + await ng('generate', 'config', 'karma'); + await ng('test', '--watch=false'); +}