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
Expand Up @@ -10,9 +10,8 @@ import type { BuilderOutput } from '@angular-devkit/architect';
import assert from 'node:assert';
import path from 'node:path';
import { isMatch } from 'picomatch';
import type { InlineConfig, Vitest } from 'vitest/node';
import type { Vitest } from 'vitest/node';
import { assertIsError } from '../../../../utils/error';
import { toPosixPath } from '../../../../utils/path';
import {
type FullResult,
type IncrementalResult,
Expand All @@ -23,9 +22,7 @@ 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>;
import { createVitestConfigPlugin, createVitestPlugins } from './plugins';

export class VitestExecutor implements TestExecutor {
private vitest: Vitest | undefined;
Expand Down Expand Up @@ -89,7 +86,9 @@ export class VitestExecutor implements TestExecutor {
if (source) {
modifiedSourceFiles.add(source);
}
vitest.invalidateFile(toPosixPath(path.join(this.options.workspaceRoot, modifiedFile)));
vitest.invalidateFile(
this.normalizePath(path.join(this.options.workspaceRoot, modifiedFile)),
);
}

const specsToRerun = [];
Expand Down Expand Up @@ -141,6 +140,7 @@ export class VitestExecutor implements TestExecutor {
browserViewport,
ui,
} = this.options;
const projectName = this.projectName;

let vitestNodeModule;
let vitestCoverageModule;
Expand Down Expand Up @@ -190,12 +190,10 @@ export class VitestExecutor implements TestExecutor {
);

const testSetupFiles = this.prepareSetupFiles();
const plugins = createVitestPlugins({
const projectPlugins = createVitestPlugins({
workspaceRoot,
projectSourceRoot: this.options.projectSourceRoot,
projectName: this.projectName,
include: this.options.include,
exclude: this.options.exclude,
projectName,
buildResultFiles: this.buildResultFiles,
testFileToEntryPoint: this.testFileToEntryPoint,
});
Expand All @@ -213,7 +211,6 @@ export class VitestExecutor implements TestExecutor {
runnerConfig === true
? await findVitestBaseConfig([this.options.projectRoot, this.options.workspaceRoot])
: runnerConfig;
const projectName = this.projectName;

return startVitest(
'test',
Expand All @@ -229,71 +226,23 @@ export class VitestExecutor implements TestExecutor {
...debugOptions,
},
{
test: {
coverage: await generateCoverageOption(coverage, this.projectName),
...(reporters ? { reporters } : {}),
projects: [
{
extends: externalConfigPath || true,
test: {
name: projectName,
globals: true,
setupFiles: testSetupFiles,
...(this.options.exclude ? { exclude: this.options.exclude } : {}),
browser: browserOptions.browser,
// Use `jsdom` if no browsers are explicitly configured.
...(browserOptions.browser ? {} : { environment: 'jsdom' }),
...(this.options.include ? { include: this.options.include } : {}),
},
optimizeDeps: {
noDiscovery: true,
},
plugins,
},
],
},
server: {
// Disable the actual file watcher. The boolean watch option above should still
// be enabled as it controls other internal behavior related to rerunning tests.
watch: null,
},
plugins: [
createVitestConfigPlugin({
browser: browserOptions.browser,
coverage,
projectName,
reporters,
setupFiles: testSetupFiles,
projectPlugins,
include: [...this.testFileToEntryPoint.keys()],
}),
],
},
);
}
}

async function generateCoverageOption(
coverage: NormalizedUnitTestBuilderOptions['coverage'],
projectName: string,
): Promise<VitestCoverageOption> {
let defaultExcludes: string[] = [];
if (coverage.exclude) {
try {
const vitestConfig = await import('vitest/config');
defaultExcludes = vitestConfig.coverageConfigDefaults.exclude;
} catch {}
}

return {
enabled: coverage.enabled,
excludeAfterRemap: true,
include: coverage.include,
reportsDirectory: toPosixPath(path.join('coverage', projectName)),
thresholds: coverage.thresholds,
watermarks: coverage.watermarks,
// Special handling for `exclude`/`reporters` due to an undefined value causing upstream failures
...(coverage.exclude
? {
exclude: [
// Augment the default exclude https://vitest.dev/config/#coverage-exclude
// with the user defined exclusions
...coverage.exclude,
...defaultExcludes,
],
}
: {}),
...(coverage.reporters
? ({ reporter: coverage.reporters } satisfies VitestCoverageOption)
: {}),
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,25 +9,76 @@
import assert from 'node:assert';
import { readFile } from 'node:fs/promises';
import path from 'node:path';
import type { VitestPlugin } from 'vitest/node';
import type {
BrowserConfigOptions,
InlineConfig,
UserWorkspaceConfig,
VitestPlugin,
} from 'vitest/node';
import { createBuildAssetsMiddleware } from '../../../../tools/vite/middlewares/assets-middleware';
import { toPosixPath } from '../../../../utils/path';
import type { ResultFile } from '../../../application/results';
import type { NormalizedUnitTestBuilderOptions } from '../../options';
import type { BrowserConfiguration } from './browser-provider';

type VitestPlugins = Awaited<ReturnType<typeof VitestPlugin>>;

interface PluginOptions {
workspaceRoot: string;
projectSourceRoot: string;
projectName: string;
include?: string[];
exclude?: string[];
buildResultFiles: ReadonlyMap<string, ResultFile>;
testFileToEntryPoint: ReadonlyMap<string, string>;
}

type VitestCoverageOption = Exclude<InlineConfig['coverage'], undefined>;

interface VitestConfigPluginOptions {
browser: BrowserConfigOptions | undefined;
coverage: NormalizedUnitTestBuilderOptions['coverage'];
projectName: string;
reporters?: string[] | [string, object][];
setupFiles: string[];
projectPlugins: VitestPlugins;
include: string[];
}

export function createVitestConfigPlugin(options: VitestConfigPluginOptions): VitestPlugins[0] {
const { include, browser, projectName, reporters, setupFiles, projectPlugins } = options;

return {
name: 'angular:vitest-configuration',
async config(config) {
const testConfig = config.test;

const projectConfig: UserWorkspaceConfig = {
test: {
...testConfig,
name: projectName,
setupFiles,
include,
globals: testConfig?.globals ?? true,
...(browser ? { browser } : {}),
// If the user has not specified an environment, use `jsdom`.
...(!testConfig?.environment ? { environment: 'jsdom' } : {}),
},
optimizeDeps: {
noDiscovery: true,
},
plugins: projectPlugins,
};

return {
test: {
coverage: await generateCoverageOption(options.coverage, projectName),
// eslint-disable-next-line @typescript-eslint/no-explicit-any
...(reporters ? ({ reporters } as any) : {}),
projects: [projectConfig],
},
};
},
};
}

export function createVitestPlugins(pluginOptions: PluginOptions): VitestPlugins {
const { workspaceRoot, buildResultFiles, testFileToEntryPoint } = pluginOptions;

Expand Down Expand Up @@ -134,3 +185,39 @@ export function createVitestPlugins(pluginOptions: PluginOptions): VitestPlugins
},
];
}

async function generateCoverageOption(
coverage: NormalizedUnitTestBuilderOptions['coverage'],
projectName: string,
): Promise<VitestCoverageOption> {
let defaultExcludes: string[] = [];
if (coverage.exclude) {
try {
const vitestConfig = await import('vitest/config');
defaultExcludes = vitestConfig.coverageConfigDefaults.exclude;
} catch {}
}

return {
enabled: coverage.enabled,
excludeAfterRemap: true,
include: coverage.include,
reportsDirectory: toPosixPath(path.join('coverage', projectName)),
thresholds: coverage.thresholds,
watermarks: coverage.watermarks,
// Special handling for `exclude`/`reporters` due to an undefined value causing upstream failures
...(coverage.exclude
? {
exclude: [
// Augment the default exclude https://vitest.dev/config/#coverage-exclude
// with the user defined exclusions
...coverage.exclude,
...defaultExcludes,
],
}
: {}),
...(coverage.reporters
? ({ reporter: coverage.reporters } satisfies VitestCoverageOption)
: {}),
};
}
Loading