Skip to content

Commit 25159cd

Browse files
committed
fix: ignore custom Jest configuration file and warn if one is found
Currently with Jest we're experimenting with whether or not Angular CLI can meet user needs without exposing the underlying configuration file. See angular#25434 (comment) for more context. Jest was incorrectly picking up custom configurations, even though they exist outside Jest's root directory. This commit fixes that behavior so Jest does not see user configurations and does not apply them.
1 parent f2f0ac4 commit 25159cd

File tree

3 files changed

+73
-1
lines changed

3 files changed

+73
-1
lines changed

packages/angular_devkit/build_angular/src/builders/jest/index.ts

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88

99
import { BuilderContext, BuilderOutput, createBuilder } from '@angular-devkit/architect';
1010
import { execFile as execFileCb } from 'child_process';
11+
import { promises as fs } from 'fs';
12+
import { platform } from 'os';
1113
import * as path from 'path';
1214
import { promisify } from 'util';
1315
import { colors } from '../../utils/color';
@@ -20,6 +22,8 @@ import { Schema as JestBuilderSchema } from './schema';
2022

2123
const execFile = promisify(execFileCb);
2224

25+
const USING_WINDOWS = platform() === 'win32';
26+
2327
/** Main execution function for the Jest builder. */
2428
export default createBuilder(
2529
async (schema: JestBuilderSchema, context: BuilderContext): Promise<BuilderOutput> => {
@@ -54,8 +58,25 @@ export default createBuilder(
5458
};
5559
}
5660

61+
const [testFiles, customConfig] = await Promise.all([
62+
findTestFiles(options.include, options.exclude, context.workspaceRoot),
63+
findCustomJestConfig(context.workspaceRoot),
64+
]);
65+
66+
// Wan if a custom Jest configuration is found. We won't use it, so if a developer is trying to use a custom config, this hopefully
67+
// makes a better experience than silently ignoring the configuration.
68+
// Ideally, this would be a hard error. However a Jest config could exist for testing other files in the workspace outside of Angular
69+
// CLI, so we likely can't produce a hard error in this situation without an opt-out.
70+
if (customConfig) {
71+
context.logger.warn(
72+
'A custom Jest config was found, but this is not supported by `@angular-devkit/build-angular:jest` and will be' +
73+
` ignored: ${customConfig}. This is an experiment to see if completely abstracting away Jest's configuration is viable. Please` +
74+
` consider if your use case can be met without directly modifying the Jest config. If this is a major obstacle for your use` +
75+
` case, please post it in this issue so we can collect feedback and evaluate: https://github.com/angular/angular-cli/issues/25434.`,
76+
);
77+
}
78+
5779
// Build all the test files.
58-
const testFiles = await findTestFiles(options.include, options.exclude, context.workspaceRoot);
5980
const jestGlobal = path.join(__dirname, 'jest-global.mjs');
6081
const initTestBed = path.join(__dirname, 'init-test-bed.mjs');
6182
const buildResult = await build(context, {
@@ -85,6 +106,7 @@ export default createBuilder(
85106
jest,
86107

87108
`--rootDir="${path.join(testOut, 'browser')}"`,
109+
`--config=${path.join(__dirname, 'jest.config.mjs')}`,
88110
'--testEnvironment=jsdom',
89111

90112
// TODO(dgp1130): Enable cache once we have a mechanism for properly clearing / disabling it.
@@ -162,3 +184,17 @@ function resolveModule(module: string): string | undefined {
162184
return undefined;
163185
}
164186
}
187+
188+
/** Returns whether or not the provided directory includes a Jest configuration file. */
189+
async function findCustomJestConfig(dir: string): Promise<string | undefined> {
190+
const entries = await fs.readdir(dir, { withFileTypes: true });
191+
192+
// Jest supports many file extensions (`js`, `ts`, `cjs`, `cts`, `json`, etc.) Just look
193+
// for anything with that prefix.
194+
const config = entries.find((entry) => entry.isFile() && entry.name.startsWith('jest.config.'));
195+
if (!config) {
196+
return undefined;
197+
}
198+
199+
return path.join(dir, config.name);
200+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
// Empty config file, everything is specified via CLI options right now.
10+
// This file is used just so Jest doesn't accidentally inherit a custom user-specified Jest config.
11+
export default {};
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { writeFile } from '../../utils/fs';
2+
import { applyJestBuilder } from '../../utils/jest';
3+
import { ng } from '../../utils/process';
4+
5+
export default async function (): Promise<void> {
6+
await applyJestBuilder();
7+
8+
// Users may incorrectly write a Jest config believing it to be used by Angular.
9+
await writeFile(
10+
'jest.config.mjs',
11+
`
12+
export default {
13+
runner: 'does-not-exist',
14+
};
15+
`.trim(),
16+
);
17+
18+
// Should not fail from the above (broken) configuration. Shouldn't use it at all.
19+
const { stderr } = await ng('test');
20+
21+
// Should warn that a Jest configuration was found but not used.
22+
if (!stderr.includes('A custom Jest config was found')) {
23+
throw new Error(`No warning about custom Jest config:\nSTDERR:\n\n${stderr}`);
24+
}
25+
}

0 commit comments

Comments
 (0)