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
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/**
* @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.dev/license
*/

/**
* @fileoverview
* This file contains utility functions for finding the Vitest base configuration file.
*/

import { readdir } from 'node:fs/promises';
import path from 'node:path';

/**
* A list of potential Vitest configuration filenames.
* The order of the files is important as the first one found will be used.
*/
const POTENTIAL_CONFIGS = [
'vitest-base.config.ts',
'vitest-base.config.mts',
'vitest-base.config.cts',
'vitest-base.config.js',
'vitest-base.config.mjs',
'vitest-base.config.cjs',
];

/**
* Finds the Vitest configuration file in the given search directories.
*
* @param searchDirs An array of directories to search for the configuration file.
* @returns The path to the configuration file, or `false` if no file is found.
* Returning `false` is used to disable Vitest's default configuration file search.
*/
export async function findVitestBaseConfig(searchDirs: string[]): Promise<string | false> {
const uniqueDirs = new Set(searchDirs);
for (const dir of uniqueDirs) {
try {
const entries = await readdir(dir, { withFileTypes: true });
const files = new Set(entries.filter((e) => e.isFile()).map((e) => e.name));

for (const potential of POTENTIAL_CONFIGS) {
if (files.has(potential)) {
return path.join(dir, potential);
}
}
} catch {
// Ignore directories that cannot be read
}
}

return false;
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
import { NormalizedUnitTestBuilderOptions } from '../../options';
import type { TestExecutor } from '../api';
import { setupBrowserConfiguration } from './browser-provider';
import { findVitestBaseConfig } from './configuration';
import { createVitestPlugins } from './plugins';

type VitestCoverageOption = Exclude<InlineConfig['coverage'], undefined>;
Expand Down Expand Up @@ -207,11 +208,16 @@ export class VitestExecutor implements TestExecutor {
}
: {};

const runnerConfig = this.options.runnerConfig;

return startVitest(
'test',
undefined,
{
config: this.options.runnerConfig === true ? undefined : this.options.runnerConfig,
config:
runnerConfig === true
? await findVitestBaseConfig([this.options.projectRoot, this.options.workspaceRoot])
: runnerConfig,
root: workspaceRoot,
project: ['base', this.projectName],
name: 'base',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ describeBuilder(execute, UNIT_TEST_BUILDER_INFO, (harness) => {
});

it('should search for a config file when `true`', async () => {
harness.writeFile('vitest.config.ts', VITEST_CONFIG_CONTENT);
harness.writeFile('vitest-base.config.ts', VITEST_CONFIG_CONTENT);
harness.useTarget('test', {
...BASE_OPTIONS,
runnerConfig: true,
Expand All @@ -57,7 +57,7 @@ describeBuilder(execute, UNIT_TEST_BUILDER_INFO, (harness) => {
});

it('should ignore config file when `false`', async () => {
harness.writeFile('vitest.config.ts', VITEST_CONFIG_CONTENT);
harness.writeFile('vitest-base.config.ts', VITEST_CONFIG_CONTENT);
harness.useTarget('test', {
...BASE_OPTIONS,
runnerConfig: false,
Expand All @@ -70,9 +70,53 @@ describeBuilder(execute, UNIT_TEST_BUILDER_INFO, (harness) => {
});

it('should ignore config file by default', async () => {
harness.writeFile('vitest-base.config.ts', VITEST_CONFIG_CONTENT);
harness.useTarget('test', {
...BASE_OPTIONS,
});

const { result } = await harness.executeOnce();

expect(result?.success).toBeTrue();
harness.expectFile('vitest-results.xml').toNotExist();
});

it('should find and use a `vitest-base.config.mts` in the project root', async () => {
harness.writeFile('vitest-base.config.mts', VITEST_CONFIG_CONTENT);
harness.useTarget('test', {
...BASE_OPTIONS,
runnerConfig: true,
});

const { result } = await harness.executeOnce();

expect(result?.success).toBeTrue();
harness.expectFile('vitest-results.xml').toExist();
});

it('should find and use a `vitest-base.config.js` in the workspace root', async () => {
// This file should be ignored because the new logic looks for `vitest-base.config.*`.
harness.writeFile('vitest.config.ts', VITEST_CONFIG_CONTENT);
// The workspace root is the directory containing the project root in the test harness.
harness.writeFile('vitest-base.config.js', VITEST_CONFIG_CONTENT);
harness.useTarget('test', {
...BASE_OPTIONS,
runnerConfig: true,
});

const { result } = await harness.executeOnce();

expect(result?.success).toBeTrue();
harness.expectFile('vitest-results.xml').toExist();
});

it('should fallback to in-memory config when no base config is found', async () => {
// This file should be ignored because the new logic looks for `vitest-base.config.*`
// and when `runnerConfig` is true, it should not fall back to the default search.
harness.writeFile('vitest.config.ts', VITEST_CONFIG_CONTENT);
harness.useTarget('test', {
...BASE_OPTIONS,
runnerConfig: true,
});

const { result } = await harness.executeOnce();
Expand Down