Skip to content
Open
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
4 changes: 4 additions & 0 deletions packages/jest/src/default-config.resolver.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { pick } from 'lodash';
import { getSystemPath, normalize, Path } from '@angular-devkit/core';
import * as path from 'node:path';

import { JestConfig } from './types';
import { getTsConfigPath } from './utils';
Expand Down Expand Up @@ -39,6 +40,9 @@ export class DefaultConfigResolver {
resolveForProject(projectRoot: Path): JestConfig {
return {
testMatch: [`${getSystemPath(projectRoot)}${testPattern}`],
// Scope coverage output to the project directory so that multiple projects
// in a workspace don't overwrite each other's coverage reports. See #1009.
coverageDirectory: path.join(getSystemPath(projectRoot), 'coverage'),
transform: {
[this.tsJestTransformRegExp]: [
'jest-preset-angular',
Expand Down
9 changes: 9 additions & 0 deletions packages/jest/tests/integration.js
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,15 @@ module.exports = [
'node ../../../packages/jest/tests/validate.js my-shared-library --find-related-tests projects/my-shared-library/src/lib/my-shared-library.service.ts projects/my-shared-library/src/lib/my-shared-library.component.ts --expect-suites=2 --expect-tests=2',
},

{
id: 'multi-project-coverage',
name: 'jest: multi-project coverage scoping',
purpose: 'Coverage output is scoped per project, not overwritten',
app: 'examples/jest/multiple-apps',
command:
'node ../../../packages/jest/tests/validate-coverage.js',
},

// E2E sanity
{
id: 'e2e-simple-app',
Expand Down
49 changes: 49 additions & 0 deletions packages/jest/tests/validate-coverage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#!/usr/bin/env node
/**
* Validates that coverage output is scoped per Angular project (fixes #1009).
*
* Each project should write coverage to <projectRoot>/coverage/, not to a shared
* root-level directory that gets overwritten when multiple projects run.
*
* Run from the multiple-apps example directory.
*/
const { execSync } = require('child_process');
const fs = require('fs');
const path = require('path');

const projects = ['my-first-app', 'my-second-app', 'my-shared-library'];
const cwd = process.cwd();
let allPassed = true;

for (const project of projects) {
console.log(`\nRunning: ng test ${project} --coverage`);
try {
execSync(`yarn test ${project} --coverage 2>&1`, { encoding: 'utf-8', stdio: 'pipe' });
} catch (e) {
// Jest exits non-zero on test failure but still writes coverage; ignore for this check
}

// Angular CLI project root is under `projects/<name>`
const projectRoot = path.join(cwd, 'projects', project);
const coverageDir = path.join(projectRoot, 'coverage');

if (!fs.existsSync(coverageDir)) {
console.error(`FAIL: Expected coverage directory not found: ${coverageDir}`);
console.error(` (coverage was likely written to root ./coverage instead)`);
allPassed = false;
} else {
console.log(`OK: Coverage scoped correctly at ${coverageDir}`);
}
}

// Also assert that no root-level coverage dir was created (it would indicate the fix didn't work)
const rootCoverageDir = path.join(cwd, 'coverage');
if (fs.existsSync(rootCoverageDir)) {
console.warn(`WARN: Root-level coverage/ directory exists at ${rootCoverageDir}`);
console.warn(` This may indicate per-project scoping is not fully working.`);
}

if (!allPassed) {
process.exit(1);
}
console.log('\nAll per-project coverage directories verified correctly.');
Loading