@stryker-mutator/jest-runner
Version:
A plugin to use the jest test runner and framework in Stryker, the JavaScript mutation testing framework
110 lines (96 loc) • 4.39 kB
text/typescript
import path from 'path';
import fs from 'fs';
import { Logger } from '@stryker-mutator/api/logging';
import { tokens, commonTokens } from '@stryker-mutator/api/plugin';
import { StrykerOptions } from '@stryker-mutator/api/core';
import type { Config } from '@jest/types';
import { I, requireResolve } from '@stryker-mutator/util';
import { JestRunnerOptionsWithStrykerOptions } from '../jest-runner-options-with-stryker-options.js';
import { JestConfigWrapper } from '../utils/jest-config-wrapper.js';
import { pluginTokens } from '../plugin-di.js';
import { JestConfigLoader } from './jest-config-loader.js';
/**
* The Default config loader will load the Jest configuration using the package.json in the package root
*/
export class CustomJestConfigLoader implements JestConfigLoader {
private readonly options: JestRunnerOptionsWithStrykerOptions;
public static inject = tokens(commonTokens.logger, commonTokens.options, pluginTokens.requireFromCwd, pluginTokens.jestConfigWrapper);
constructor(
private readonly log: Logger,
options: StrykerOptions,
private readonly requireFromCwd: typeof requireResolve,
private readonly jestConfig: I<JestConfigWrapper>,
) {
this.options = options as JestRunnerOptionsWithStrykerOptions;
}
public async loadConfig(): Promise<Config.InitialOptions> {
try {
return await this.readConfigNative();
} catch {
return this.readConfigManually();
}
}
/**
* Tries to read the jest config via the native jest api, available since jest@>=29.3
*/
private async readConfigNative(): Promise<Config.InitialOptions> {
const { config, configPath } = await this.jestConfig.readInitialOptions(this.options.jest.configFile, { skipMultipleConfigError: true });
const hint = '(used native `readInitialOptions` from jest-config)';
if (configPath) {
this.log.debug(`Read config from "${path.relative(process.cwd(), configPath)}" ${hint}.`);
} else {
this.log.debug(`No config file read ${hint}.`);
}
return config;
}
/**
* The legacy readConfig functionality
*/
public async readConfigManually(): Promise<Config.InitialOptions> {
const jestConfig = (await this.readConfigFromJestConfigFile()) ?? (await this.readConfigFromPackageJson()) ?? {};
this.log.debug("Read config: %s (used stryker's own config reading functionality)", jestConfig);
return jestConfig;
}
private async readConfigFromJestConfigFile(): Promise<Config.InitialOptions | undefined> {
const configFilePath = this.resolveJestConfigFilePath();
if (configFilePath) {
let config = this.requireFromCwd(configFilePath) as Config.InitialOptions | (() => Promise<Config.InitialOptions>);
if (typeof config === 'function') {
config = await config();
}
this.log.debug(`Read Jest config from ${configFilePath}`);
this.setRootDir(config, configFilePath);
return config;
}
return undefined;
}
private async readConfigFromPackageJson(): Promise<Config.InitialOptions | undefined> {
const pkgJsonFilePath = this.resolvePackageJsonFilePath();
if (pkgJsonFilePath) {
const config: Config.InitialOptions = JSON.parse(await fs.promises.readFile(pkgJsonFilePath, 'utf8')).jest ?? {};
this.log.debug(`Read Jest config from ${pkgJsonFilePath}`);
this.setRootDir(config, pkgJsonFilePath);
return config;
}
return undefined;
}
private resolvePackageJsonFilePath(): string | undefined {
const jestOptions = this.options;
const packageJsonCandidate = path.resolve(jestOptions.jest.configFile ?? 'package.json');
if (packageJsonCandidate.endsWith('package.json') && (jestOptions.jest.configFile ?? fs.existsSync(packageJsonCandidate))) {
return packageJsonCandidate;
}
return undefined;
}
private setRootDir(config: Config.InitialOptions, configFilePath: string) {
config.rootDir = path.resolve(path.dirname(configFilePath), config.rootDir ?? '.');
}
private resolveJestConfigFilePath(): string | undefined {
const jestOptions = this.options;
const configFileCandidate = path.resolve(jestOptions.jest.configFile ?? 'jest.config.js');
if (!configFileCandidate.endsWith('package.json') && (jestOptions.jest.configFile ?? fs.existsSync(configFileCandidate))) {
return configFileCandidate;
}
return undefined;
}
}