Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 2 additions & 6 deletions packages/schematics/angular/application/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -396,13 +396,9 @@ function addAppToWorkspaceFile(options: ApplicationOptions, appDir: string): Rul
options: {},
}
: {
builder: Builders.BuildKarma,
builder: Builders.BuildUnitTest,
options: {
polyfills: options.zoneless ? undefined : ['zone.js', 'zone.js/testing'],
tsConfig: `${projectRoot}tsconfig.spec.json`,
inlineStyleLanguage,
assets: [{ 'glob': '**/*', 'input': `${projectRoot}public` }],
styles: [`${sourceRoot}/styles.${options.style}`],
runner: 'karma',
},
},
},
Expand Down
9 changes: 5 additions & 4 deletions packages/schematics/angular/application/index_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -448,10 +448,11 @@ describe('Application Schematic', () => {
const config = JSON.parse(tree.readContent('/angular.json'));
const prj = config.projects.foo;
const testOpt = prj.architect.test;
expect(testOpt.builder).toEqual('@angular/build:karma');
expect(testOpt.options.tsConfig).toEqual('tsconfig.spec.json');
expect(testOpt.options.assets).toEqual([{ glob: '**/*', input: 'public' }]);
expect(testOpt.options.styles).toEqual(['src/styles.css']);
expect(testOpt.builder).toEqual('@angular/build:unit-test');
expect(testOpt.options.runner).toEqual('karma');
expect(testOpt.options.tsConfig).toBeUndefined();
expect(testOpt.options.assets).toBeUndefined();
expect(testOpt.options.styles).toBeUndefined();
});

it('should set the relative tsconfig paths', async () => {
Expand Down
119 changes: 79 additions & 40 deletions packages/schematics/angular/config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,49 +47,88 @@ async function addBrowserslistConfig(projectRoot: string): Promise<Rule> {
}

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.`);
}
return (_, context) =>
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.',
);
}
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 &&
testTarget.builder !== AngularBuilder.BuildKarma
) {
throw new SchematicsException(
`Cannot add a karma configuration as builder for "test" target in project does not` +
` use "${AngularBuilder.Karma}" or "${AngularBuilder.BuildKarma}".`,
);
}
if (
testTarget.builder !== AngularBuilder.Karma &&
testTarget.builder !== AngularBuilder.BuildKarma &&
testTarget.builder !== AngularBuilder.BuildUnitTest
) {
throw new SchematicsException(
`Cannot add a karma configuration as builder for "test" target in project does not` +
` use "${AngularBuilder.Karma}", "${AngularBuilder.BuildKarma}", or ${AngularBuilder.BuildUnitTest}.`,
);
}

testTarget.options ??= {};
if (testTarget.builder !== AngularBuilder.BuildUnitTest) {
testTarget.options.karmaConfig = path.join(project.root, 'karma.conf.js');
} else {
// `unit-test` uses the `runnerConfig` option which has configuration discovery if enabled
testTarget.options.runnerConfig = true;

testTarget.options ??= {};
testTarget.options.karmaConfig = path.join(project.root, 'karma.conf.js');
let isKarmaRunnerConfigured = false;
// Check runner option
if (testTarget.options.runner) {
if (testTarget.options.runner === 'karma') {
isKarmaRunnerConfigured = true;
} else {
context.logger.warn(
`The "test" target is configured to use a runner other than "karma" in the main options.` +
' The generated "karma.conf.js" file may not be used.',
);
}
}

// 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);
}
for (const [name, config] of Object.entries(testTarget.configurations ?? {})) {
if (config && typeof config === 'object' && 'runner' in config) {
if (config.runner !== 'karma') {
context.logger.warn(
`The "test" target's "${name}" configuration is configured to use a runner other than "karma".` +
' The generated "karma.conf.js" file may not be used for that configuration.',
);
} else {
isKarmaRunnerConfigured = true;
}
}
}

return mergeWith(
apply(url(https://github.com/'./files'), [
filter((p) => p.endsWith('karma.conf.js.template')),
applyTemplates({
relativePathToWorkspaceRoot: relativePathToWorkspaceRoot(project.root),
folderName,
needDevkitPlugin: testTarget.builder === AngularBuilder.Karma,
}),
move(project.root),
]),
);
});
if (!isKarmaRunnerConfigured) {
context.logger.warn(
`The "test" target is not explicitly configured to use the "karma" runner.` +
' The generated "karma.conf.js" file may not be used as the default runner is "vitest".',
);
}
}
// 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(https://github.com/'./files'), [
filter((p) => p.endsWith('karma.conf.js.template')),
applyTemplates({
relativePathToWorkspaceRoot: relativePathToWorkspaceRoot(project.root),
folderName,
needDevkitPlugin: testTarget.builder === AngularBuilder.Karma,
}),
move(project.root),
]),
);
});
}
82 changes: 82 additions & 0 deletions packages/schematics/angular/config/index_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,88 @@ describe('Config Schematic', () => {
const { karmaConfig } = prj.architect.test.options;
expect(karmaConfig).toBe('projects/foo/karma.conf.js');
});

describe('with "unit-test" builder', () => {
beforeEach(() => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const angularJson = applicationTree.readJson('angular.json') as any;
angularJson.projects.foo.architect.test.builder = '@angular/build:unit-test';
applicationTree.overwrite('angular.json', JSON.stringify(angularJson));
});

it(`should not 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).toBeUndefined();
});

it(`should warn when 'runner' is not specified`, async () => {
const logs: string[] = [];
schematicRunner.logger.subscribe(({ message }) => logs.push(message));
await runConfigSchematic(ConfigType.Karma);
expect(
logs.some((v) => v.includes('may not be used as the default runner is "vitest"')),
).toBeTrue();
});

it(`should warn when 'runner' is 'vitest' in options`, async () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const angularJson = applicationTree.readJson('angular.json') as any;
angularJson.projects.foo.architect.test.options ??= {};
angularJson.projects.foo.architect.test.options.runner = 'vitest';
applicationTree.overwrite('angular.json', JSON.stringify(angularJson));

const logs: string[] = [];
schematicRunner.logger.subscribe(({ message }) => logs.push(message));
await runConfigSchematic(ConfigType.Karma);
expect(
logs.some((v) => v.includes('runner other than "karma" in the main options')),
).toBeTrue();
});

it(`should warn when 'runner' is 'vitest' in a configuration`, async () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const angularJson = applicationTree.readJson('angular.json') as any;
angularJson.projects.foo.architect.test.configurations ??= {};
angularJson.projects.foo.architect.test.configurations.ci = { runner: 'vitest' };
applicationTree.overwrite('angular.json', JSON.stringify(angularJson));

const logs: string[] = [];
schematicRunner.logger.subscribe(({ message }) => logs.push(message));
await runConfigSchematic(ConfigType.Karma);
expect(
logs.some((v) => v.includes(`"ci" configuration is configured to use a runner`)),
).toBeTrue();
});

it(`should not warn when 'runner' is 'karma' in options`, async () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const angularJson = applicationTree.readJson('angular.json') as any;
angularJson.projects.foo.architect.test.options ??= {};
angularJson.projects.foo.architect.test.options.runner = 'karma';
applicationTree.overwrite('angular.json', JSON.stringify(angularJson));

const logs: string[] = [];
schematicRunner.logger.subscribe(({ message }) => logs.push(message));
await runConfigSchematic(ConfigType.Karma);
expect(logs.length).toBe(0);
});

it(`should not warn when 'runner' is 'karma' in a configuration`, async () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const angularJson = applicationTree.readJson('angular.json') as any;
angularJson.projects.foo.architect.test.configurations ??= {};
angularJson.projects.foo.architect.test.configurations.ci = { runner: 'karma' };
applicationTree.overwrite('angular.json', JSON.stringify(angularJson));

const logs: string[] = [];
schematicRunner.logger.subscribe(({ message }) => logs.push(message));
await runConfigSchematic(ConfigType.Karma);
expect(logs.length).toBe(0);
});
});
});

describe(`when 'type' is 'browserslist'`, () => {
Expand Down
3 changes: 2 additions & 1 deletion packages/schematics/angular/ng-new/index_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,8 @@ describe('Ng New Schematic', () => {
},
},
} = JSON.parse(tree.readContent('/bar/angular.json'));
expect(test.builder).toBe('@angular/build:karma');
expect(test.builder).toBe('@angular/build:unit-test');
expect(test.options).toEqual({ runner: 'karma' });

const { devDependencies } = JSON.parse(tree.readContent('/bar/package.json'));
expect(devDependencies['karma']).toBeDefined();
Expand Down
3 changes: 3 additions & 0 deletions tests/legacy-cli/e2e/initialize/500-create-project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@ export default async function () {

const test = json['projects']['test-project']['architect']['test'];
test.builder = '@angular-devkit/build-angular:karma';
test.options ??= {};
test.options.tsConfig = 'tsconfig.spec.json';
delete test.options.runner;
});
await updateJsonFile('tsconfig.json', (tsconfig) => {
delete tsconfig.compilerOptions.esModuleInterop;
Expand Down
9 changes: 8 additions & 1 deletion tests/legacy-cli/e2e/tests/basic/test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { ng } from '../../utils/process';
import { writeMultipleFiles } from '../../utils/fs';
import { getGlobalVariable } from '../../utils/env';

export default async function () {
// make sure both --watch=false work
Expand Down Expand Up @@ -48,5 +49,11 @@ export default async function () {
`,
});

await ng('test', '--watch=false', '--karma-config=karma.conf.bis.js');
const isWebpack = !getGlobalVariable('argv')['esbuild'];

if (isWebpack) {
await ng('test', '--watch=false', '--karma-config=karma.conf.bis.js');
} else {
await ng('test', '--watch=false', '--runner-config=karma.conf.bis.js');
}
}
10 changes: 7 additions & 3 deletions tests/legacy-cli/e2e/tests/test/test-code-coverage-exclude.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,23 @@
import { getGlobalVariable } from '../../utils/env';
import { expectFileToExist, rimraf } from '../../utils/fs';
import { silentNg } from '../../utils/process';
import { expectToFail } from '../../utils/utils';

export default async function () {
const isWebpack = !getGlobalVariable('argv')['esbuild'];
const coverageOptionName = isWebpack ? '--code-coverage' : '--coverage';

// This test is already in build-angular, but that doesn't run on Windows.
await silentNg('test', '--no-watch', '--code-coverage');
await silentNg('test', '--no-watch', coverageOptionName);
await expectFileToExist('coverage/test-project/app.ts.html');
// Delete coverage directory
await rimraf('coverage');

await silentNg(
'test',
'--no-watch',
'--code-coverage',
`--code-coverage-exclude='src/**/app.ts'`,
coverageOptionName,
`${coverageOptionName}-exclude='src/**/app.ts'`,
);

// Doesn't include excluded.
Expand Down
Loading