diff --git a/.gitignore b/.gitignore
index 5e706fa10..e6c77e8f8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -32,6 +32,7 @@ node_modules
/.sass-cache
/connect.lock
/coverage
+**/.coverage/**
/examples/react-todos-app/coverage
/libpeerconnection.log
npm-debug.log
diff --git a/e2e/ci-e2e/vitest.e2e.config.ts b/e2e/ci-e2e/vitest.e2e.config.ts
index 188911141..37ea143d8 100644
--- a/e2e/ci-e2e/vitest.e2e.config.ts
+++ b/e2e/ci-e2e/vitest.e2e.config.ts
@@ -1,22 +1,17 @@
///
-import { defineConfig } from 'vite';
-import { tsconfigPathAliases } from '../../tools/vitest-tsconfig-path-aliases.js';
+import { createE2eConfig } from '../../testing/test-setup-config/src/lib/vitest-setup-presets.js';
-export default defineConfig({
- cacheDir: '../../node_modules/.vite/ci-e2e',
- test: {
- reporters: ['basic'],
- testTimeout: 60_000,
- globals: true,
- alias: tsconfigPathAliases(),
- pool: 'threads',
- poolOptions: { threads: { singleThread: true } },
- cache: {
- dir: '../../node_modules/.vitest',
+export default createE2eConfig(
+ 'ci-e2e',
+ {
+ projectRoot: new URL('../../', import.meta.url),
+ cacheKey: 'ci-e2e',
+ },
+ {
+ test: {
+ testTimeout: 60_000,
+ globalSetup: ['./global-setup.ts'],
+ coverage: { enabled: false },
},
- environment: 'node',
- include: ['tests/**/*.e2e.test.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
- globalSetup: './global-setup.ts',
- setupFiles: ['../../testing/test-setup/src/lib/reset.mocks.ts'],
},
-});
+);
diff --git a/e2e/plugin-typescript-e2e/vitest.e2e.config.ts b/e2e/plugin-typescript-e2e/vitest.e2e.config.ts
index 8f30d049b..1e9fd4768 100644
--- a/e2e/plugin-typescript-e2e/vitest.e2e.config.ts
+++ b/e2e/plugin-typescript-e2e/vitest.e2e.config.ts
@@ -1,26 +1,16 @@
///
-import { defineConfig } from 'vite';
-import { tsconfigPathAliases } from '../../tools/vitest-tsconfig-path-aliases.js';
+import { createE2eConfig } from '../../testing/test-setup-config/src/lib/vitest-setup-presets.js';
-export default defineConfig({
- cacheDir: '../../node_modules/.vite/plugin-typescript-e2e',
- test: {
- reporters: ['basic'],
- testTimeout: 20_000,
- globals: true,
- alias: tsconfigPathAliases(),
- pool: 'threads',
- poolOptions: { threads: { singleThread: true } },
- coverage: {
- reporter: ['text', 'lcov'],
- reportsDirectory: '../../coverage/plugin-typescript-e2e/e2e-tests',
- exclude: ['mocks/**', '**/types.ts'],
- },
- cache: {
- dir: '../../node_modules/.vitest',
+export default createE2eConfig(
+ 'plugin-typescript-e2e',
+ {
+ projectRoot: new URL('../../', import.meta.url),
+ cacheKey: 'plugin-typescript-e2e',
+ },
+ {
+ test: {
+ testTimeout: 20_000,
+ coverage: { enabled: true },
},
- environment: 'node',
- include: ['tests/**/*.e2e.test.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
- setupFiles: ['../../testing/test-setup/src/lib/reset.mocks.ts'],
},
-});
+);
diff --git a/packages/core/vitest.int.config.ts b/packages/core/vitest.int.config.ts
index 819c3a5bb..981ee4e19 100644
--- a/packages/core/vitest.int.config.ts
+++ b/packages/core/vitest.int.config.ts
@@ -1,30 +1,17 @@
///
-import { defineConfig } from 'vite';
-import { tsconfigPathAliases } from '../../tools/vitest-tsconfig-path-aliases.js';
+import {
+ createIntConfig,
+ setupPresets,
+} from '../../testing/test-setup-config/src/lib/vitest-setup-presets.js';
-export default defineConfig({
- cacheDir: '../../node_modules/.vite/core',
- test: {
- reporters: ['basic'],
- globals: true,
- cache: {
- dir: '../../node_modules/.vitest',
- },
- alias: tsconfigPathAliases(),
- pool: 'threads',
- poolOptions: { threads: { singleThread: true } },
- coverage: {
- reporter: ['text', 'lcov'],
- reportsDirectory: '../../coverage/core/int-tests',
- exclude: ['mocks/**', '**/types.ts'],
+export default createIntConfig(
+ 'core',
+ {
+ projectRoot: new URL('../../', import.meta.url),
+ },
+ {
+ test: {
+ setupFiles: [...setupPresets.int.base, ...setupPresets.int.portalClient],
},
- environment: 'node',
- include: ['src/**/*.int.test.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
- globalSetup: ['../../global-setup.ts'],
- setupFiles: [
- '../../testing/test-setup/src/lib/console.mock.ts',
- '../../testing/test-setup/src/lib/reset.mocks.ts',
- '../../testing/test-setup/src/lib/portal-client.mock.ts',
- ],
},
-});
+);
diff --git a/packages/core/vitest.unit.config.ts b/packages/core/vitest.unit.config.ts
index 820eb538c..d11f288f9 100644
--- a/packages/core/vitest.unit.config.ts
+++ b/packages/core/vitest.unit.config.ts
@@ -1,36 +1,22 @@
///
-import { defineConfig } from 'vite';
-import { tsconfigPathAliases } from '../../tools/vitest-tsconfig-path-aliases.js';
+import {
+ createUnitConfig,
+ setupPresets,
+} from '../../testing/test-setup-config/src/lib/vitest-setup-presets.js';
-export default defineConfig({
- cacheDir: '../../node_modules/.vite/core',
- test: {
- reporters: ['basic'],
- globals: true,
- cache: {
- dir: '../../node_modules/.vitest',
- },
- alias: tsconfigPathAliases(),
- pool: 'threads',
- poolOptions: { threads: { singleThread: true } },
- coverage: {
- reporter: ['text', 'lcov'],
- reportsDirectory: '../../coverage/core/unit-tests',
- exclude: ['mocks/**', '**/types.ts'],
+export default createUnitConfig(
+ 'core',
+ {
+ projectRoot: new URL('../../', import.meta.url),
+ },
+ {
+ test: {
+ setupFiles: [
+ ...setupPresets.unit.base,
+ ...setupPresets.unit.git,
+ ...setupPresets.unit.portalClient,
+ ...setupPresets.unit.matchersCore,
+ ],
},
- environment: 'node',
- include: ['src/**/*.unit.test.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
- globalSetup: ['../../global-setup.ts'],
- setupFiles: [
- '../../testing/test-setup/src/lib/cliui.mock.ts',
- '../../testing/test-setup/src/lib/fs.mock.ts',
- '../../testing/test-setup/src/lib/git.mock.ts',
- '../../testing/test-setup/src/lib/console.mock.ts',
- '../../testing/test-setup/src/lib/reset.mocks.ts',
- '../../testing/test-setup/src/lib/portal-client.mock.ts',
- '../../testing/test-setup/src/lib/extend/ui-logger.matcher.ts',
- '../../testing/test-setup/src/lib/extend/markdown-table.matcher.ts',
- '../../testing/test-setup/src/lib/extend/jest-extended.matcher.ts',
- ],
},
-});
+);
diff --git a/packages/utils/vitest.int.config.ts b/packages/utils/vitest.int.config.ts
index 61c01ddad..1aeadf3d4 100644
--- a/packages/utils/vitest.int.config.ts
+++ b/packages/utils/vitest.int.config.ts
@@ -1,30 +1,18 @@
///
-import { defineConfig } from 'vite';
-import { tsconfigPathAliases } from '../../tools/vitest-tsconfig-path-aliases.js';
+import {
+ createIntConfig,
+ setupPresets,
+} from '../../testing/test-setup-config/src/lib/vitest-setup-presets.js';
-export default defineConfig({
- cacheDir: '../../node_modules/.vite/utils',
- test: {
- reporters: ['basic'],
- globals: true,
- cache: {
- dir: '../../node_modules/.vitest',
- },
- alias: tsconfigPathAliases(),
- pool: 'threads',
- poolOptions: { threads: { singleThread: true } },
- coverage: {
- reporter: ['text', 'lcov'],
- reportsDirectory: '../../coverage/utils/int-tests',
- exclude: ['mocks/**', 'perf/**', '**/types.ts'],
+export default createIntConfig(
+ 'utils',
+ {
+ projectRoot: new URL('../../', import.meta.url),
+ },
+ {
+ test: {
+ coverage: { exclude: ['perf/**'] },
+ setupFiles: [...setupPresets.int.base, ...setupPresets.int.cliui],
},
- environment: 'node',
- include: ['src/**/*.int.test.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
- globalSetup: ['../../global-setup.ts'],
- setupFiles: [
- '../../testing/test-setup/src/lib/cliui.mock.ts',
- '../../testing/test-setup/src/lib/console.mock.ts',
- '../../testing/test-setup/src/lib/reset.mocks.ts',
- ],
},
-});
+);
diff --git a/packages/utils/vitest.unit.config.ts b/packages/utils/vitest.unit.config.ts
index ac74f7af6..66bb6dd51 100644
--- a/packages/utils/vitest.unit.config.ts
+++ b/packages/utils/vitest.unit.config.ts
@@ -1,38 +1,24 @@
///
-import { defineConfig } from 'vite';
-import { tsconfigPathAliases } from '../../tools/vitest-tsconfig-path-aliases.js';
+import {
+ createUnitConfig,
+ setupPresets,
+} from '../../testing/test-setup-config/src/lib/vitest-setup-presets.js';
-export default defineConfig({
- cacheDir: '../../node_modules/.vite/utils',
- test: {
- reporters: ['basic'],
- globals: true,
- cache: {
- dir: '../../node_modules/.vitest',
- },
- alias: tsconfigPathAliases(),
- pool: 'threads',
- poolOptions: { threads: { singleThread: true } },
- coverage: {
- reporter: ['text', 'lcov'],
- reportsDirectory: '../../coverage/utils/unit-tests',
- exclude: ['mocks/**', 'perf/**', '**/types.ts'],
- },
- environment: 'node',
- include: ['src/**/*.{unit,type}.test.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
- typecheck: {
- include: ['**/*.type.test.ts'],
+export default createUnitConfig(
+ 'utils',
+ {
+ projectRoot: new URL('../../', import.meta.url),
+ },
+ {
+ test: {
+ include: ['src/**/*.{unit,type}.test.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
+ typecheck: { include: ['**/*.type.test.ts'] },
+ coverage: { exclude: ['perf/**'] },
+ setupFiles: [
+ ...setupPresets.unit.base,
+ ...setupPresets.unit.matchersCore,
+ ...setupPresets.unit.matcherPath,
+ ],
},
- globalSetup: ['../../global-setup.ts'],
- setupFiles: [
- '../../testing/test-setup/src/lib/cliui.mock.ts',
- '../../testing/test-setup/src/lib/fs.mock.ts',
- '../../testing/test-setup/src/lib/console.mock.ts',
- '../../testing/test-setup/src/lib/reset.mocks.ts',
- '../../testing/test-setup/src/lib/extend/ui-logger.matcher.ts',
- '../../testing/test-setup/src/lib/extend/markdown-table.matcher.ts',
- '../../testing/test-setup/src/lib/extend/path.matcher.ts',
- '../../testing/test-setup/src/lib/extend/jest-extended.matcher.ts',
- ],
},
-});
+);
diff --git a/testing/test-setup-config/README.md b/testing/test-setup-config/README.md
new file mode 100644
index 000000000..42aa20ae8
--- /dev/null
+++ b/testing/test-setup-config/README.md
@@ -0,0 +1,24 @@
+## Vitest config factory and setup presets
+
+Utilities to centralize and standardize Vitest configuration across the monorepo.
+
+- `vitest-config-factory.ts`: builds typed Vitest configs with sensible defaults
+- `vitest-setup-presets.ts`: provides create functions and exportable setup file groups
+
+The create functions (`createUnitConfig`, `createIntConfig`, `createE2eConfig`) automatically include appropriate setup files for each test type. See the unit tests for detailed documentation of defaults, coverage settings, and setup file presets.
+
+### Examples
+
+**Using defaults:**
+
+```ts
+export default createUnitConfig('my-package', import.meta.url);
+```
+
+**Extending default setup files:**
+
+```ts
+export default createIntConfig('my-package', import.meta.url, {
+ setupFiles: [...setupPresets.int.base, ...setupPresets.int.git, './custom-setup.ts'],
+});
+```
diff --git a/testing/test-setup-config/eslint.config.js b/testing/test-setup-config/eslint.config.js
new file mode 100644
index 000000000..2656b27cb
--- /dev/null
+++ b/testing/test-setup-config/eslint.config.js
@@ -0,0 +1,12 @@
+import tseslint from 'typescript-eslint';
+import baseConfig from '../../eslint.config.js';
+
+export default tseslint.config(...baseConfig, {
+ files: ['**/*.ts'],
+ languageOptions: {
+ parserOptions: {
+ projectService: true,
+ tsconfigRootDir: import.meta.dirname,
+ },
+ },
+});
diff --git a/testing/test-setup-config/project.json b/testing/test-setup-config/project.json
new file mode 100644
index 000000000..c4b32ba8a
--- /dev/null
+++ b/testing/test-setup-config/project.json
@@ -0,0 +1,12 @@
+{
+ "name": "test-setup-config",
+ "$schema": "../../node_modules/nx/schemas/project-schema.json",
+ "sourceRoot": "testing/test-setup/src",
+ "projectType": "library",
+ "targets": {
+ "build": {},
+ "lint": {},
+ "unit-test": {}
+ },
+ "tags": ["scope:shared", "type:testing"]
+}
diff --git a/testing/test-setup-config/src/index.ts b/testing/test-setup-config/src/index.ts
new file mode 100644
index 000000000..ef10c13bc
--- /dev/null
+++ b/testing/test-setup-config/src/index.ts
@@ -0,0 +1,14 @@
+export {
+ createVitestConfig,
+ type TestKind,
+ type VitestConfigFactoryOptions,
+ type VitestOverrides,
+ type ConfigRestParams,
+} from './lib/vitest-config-factory.js';
+
+export {
+ setupPresets,
+ createUnitConfig,
+ createIntConfig,
+ createE2eConfig,
+} from './lib/vitest-setup-presets.js';
diff --git a/testing/test-setup-config/src/lib/vitest-config-factory.ts b/testing/test-setup-config/src/lib/vitest-config-factory.ts
new file mode 100644
index 000000000..31882ce9a
--- /dev/null
+++ b/testing/test-setup-config/src/lib/vitest-config-factory.ts
@@ -0,0 +1,223 @@
+import path from 'node:path';
+import { fileURLToPath, pathToFileURL } from 'node:url';
+import {
+ type UserConfig as ViteUserConfig,
+ defineConfig,
+ mergeConfig,
+} from 'vite';
+import type { CoverageOptions, InlineConfig } from 'vitest';
+import { tsconfigPathAliases } from './vitest-tsconfig-path-aliases.js';
+
+export type TestKind = 'unit' | 'int' | 'e2e';
+
+export type VitestConfigFactoryOptions = {
+ /**
+ * Used for cache and coverage directory naming
+ */
+ projectKey: string;
+ /**
+ * Required path/URL to the project root for resolving all paths
+ */
+ kind: TestKind;
+ /**
+ * The root directory of the project
+ */
+ projectRoot: string | URL;
+ /**
+ * Optional test cache directory name
+ */
+ cacheKey?: string;
+};
+
+export type VitestOverrides = ViteUserConfig & { test?: InlineConfig };
+export type ConfigRestParams = Pick<
+ VitestConfigFactoryOptions,
+ 'projectRoot' | 'cacheKey'
+>;
+
+export function createVitestConfig(
+ options: VitestConfigFactoryOptions,
+ overrides: VitestOverrides = {},
+): ViteUserConfig {
+ const projectRootUrl: URL =
+ typeof options.projectRoot === 'string'
+ ? pathToFileURL(
+ options.projectRoot.endsWith('/')
+ ? options.projectRoot
+ : `${options.projectRoot}/`,
+ )
+ : options.projectRoot;
+ const cacheDirName = options.cacheKey ?? `cache-${options.projectKey}`;
+
+ const coverageEnabled =
+ overrides.test?.coverage?.enabled ?? options.kind !== 'e2e';
+
+ const overrideSetupFiles = overrides.test?.setupFiles;
+ const setupFiles = overrideSetupFiles
+ ? toAbsolutePaths(projectRootUrl, normalizeSetupFiles(overrideSetupFiles))
+ : [];
+
+ const baseConfig = buildBaseConfig({
+ projectKey: options.projectKey,
+ kind: options.kind,
+ projectRootUrl,
+ cacheDirName,
+ coverageEnabled,
+ setupFiles,
+ overrideExclude:
+ (overrides.test?.coverage?.exclude as string[] | undefined) ?? [],
+ });
+
+ const normalizedOverrides = sanitizeOverrides(overrides);
+ const merged = mergeConfig(
+ baseConfig as ViteUserConfig,
+ normalizedOverrides as ViteUserConfig,
+ );
+ return defineConfig(merged);
+}
+
+function toAbsolutePaths(
+ projectRootUrl: URL,
+ paths?: readonly string[],
+): string[] {
+ return paths && paths.length > 0
+ ? paths
+ .filter(Boolean)
+ .map(p => path.resolve(getProjectRootPath(projectRootUrl), p))
+ : [];
+}
+
+function normalizeSetupFiles(setupFiles: string | readonly string[]): string[] {
+ return Array.isArray(setupFiles)
+ ? (setupFiles as string[])
+ : [setupFiles as string];
+}
+
+function defaultInclude(kind: TestKind): string[] {
+ return kind === 'unit'
+ ? ['src/**/*.unit.test.{js,mjs,cjs,ts,mts,cts,jsx,tsx}']
+ : kind === 'int'
+ ? ['src/**/*.int.test.{js,mjs,cjs,ts,mts,cts,jsx,tsx}']
+ : ['tests/**/*.e2e.test.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'];
+}
+
+function defaultGlobalSetup(
+ kind: TestKind,
+ projectRootUrl: URL,
+): string[] | undefined {
+ return kind === 'e2e'
+ ? undefined
+ : [path.resolve(getProjectRootPath(projectRootUrl), 'global-setup.ts')];
+}
+
+function buildCoverageConfig(params: {
+ projectKey: string;
+ kind: TestKind;
+ projectRootUrl: URL;
+ overrideExclude?: string[];
+}): CoverageOptions {
+ const defaultExclude = ['mocks/**', '**/types.ts'];
+ const reportsDirectory = path.resolve(
+ getProjectRootPath(params.projectRootUrl),
+ params.kind === 'e2e'
+ ? `e2e/${params.projectKey}/.coverage`
+ : `packages/${params.projectKey}/.coverage/${params.kind}-tests`,
+ );
+ return {
+ reporter: ['text', 'lcov'],
+ reportsDirectory,
+ exclude:
+ params.overrideExclude && params.overrideExclude.length > 0
+ ? [...defaultExclude, ...params.overrideExclude]
+ : defaultExclude,
+ };
+}
+
+function buildBaseConfig(params: {
+ projectKey: string;
+ kind: TestKind;
+ projectRootUrl: URL;
+ cacheDirName: string;
+ coverageEnabled: boolean;
+ setupFiles: string[];
+ overrideExclude: string[];
+}): VitestOverrides {
+ const cfg: VitestOverrides = {
+ cacheDir: path.resolve(
+ getProjectRootPath(params.projectRootUrl),
+ `node_modules/.vite/${params.cacheDirName}`,
+ ),
+ test: {
+ reporters: ['basic'],
+ globals: true,
+ cache: {
+ dir: path.resolve(
+ getProjectRootPath(params.projectRootUrl),
+ 'node_modules/.vitest',
+ ),
+ },
+ alias: tsconfigPathAliases(params.projectRootUrl),
+ pool: 'threads',
+ poolOptions: { threads: { singleThread: true } },
+ environment: 'node',
+ include: defaultInclude(params.kind),
+ globalSetup: defaultGlobalSetup(params.kind, params.projectRootUrl),
+ setupFiles: params.setupFiles,
+ ...(params.coverageEnabled
+ ? {
+ coverage: buildCoverageConfig({
+ projectKey: params.projectKey,
+ kind: params.kind,
+ projectRootUrl: params.projectRootUrl,
+ overrideExclude: params.overrideExclude,
+ }),
+ }
+ : {}),
+ },
+ };
+ return cfg;
+}
+
+function sanitizeCoverageOptions(
+ coverage: unknown,
+): CoverageOptions | undefined {
+ if (!coverage) {
+ return undefined;
+ }
+
+ const {
+ enabled: _en,
+ exclude: _ex,
+ ...rest
+ } = coverage as CoverageOptions & {
+ enabled?: boolean;
+ exclude?: string[];
+ };
+ return rest as CoverageOptions;
+}
+
+function sanitizeOverrides(overrides: VitestOverrides): VitestOverrides {
+ if (!overrides?.test) {
+ return overrides;
+ }
+
+ // Remove setupFiles from sanitization since we handle it directly in main logic
+ const { setupFiles: _sf, coverage, ...restTest } = overrides.test;
+ const sanitizedCoverage = sanitizeCoverageOptions(coverage);
+
+ const sanitizedTest: InlineConfig = sanitizedCoverage
+ ? { ...restTest, coverage: sanitizedCoverage }
+ : restTest;
+
+ return { ...overrides, test: sanitizedTest };
+}
+
+export function getProjectRootPath(projectRootUrl: URL): string {
+ try {
+ return fileURLToPath(projectRootUrl);
+ } catch {
+ // Fallback for non-file:// URLs or invalid URLs
+ const pathname = projectRootUrl.pathname;
+ return pathname.startsWith('/') ? pathname : `/${pathname}`;
+ }
+}
diff --git a/testing/test-setup-config/src/lib/vitest-config-factory.unit.test.ts b/testing/test-setup-config/src/lib/vitest-config-factory.unit.test.ts
new file mode 100644
index 000000000..9eae528fb
--- /dev/null
+++ b/testing/test-setup-config/src/lib/vitest-config-factory.unit.test.ts
@@ -0,0 +1,744 @@
+import path from 'node:path';
+import { pathToFileURL } from 'node:url';
+import { defineConfig } from 'vite';
+import { beforeEach, describe, expect, it, vi } from 'vitest';
+import {
+ type TestKind,
+ type VitestConfigFactoryOptions,
+ type VitestOverrides,
+ createVitestConfig,
+ getProjectRootPath,
+} from './vitest-config-factory.js';
+
+// Only mock defineConfig - assume it works correctly, we're not testing Vite
+// Use importOriginal to keep mergeConfig real while mocking defineConfig
+vi.mock('vite', async importOriginal => {
+ const actual = await importOriginal();
+ return {
+ ...actual,
+ defineConfig: vi.fn(config => config),
+ };
+});
+
+// Mock tsconfigPathAliases since it reads from filesystem and our fake paths don't exist
+vi.mock('./vitest-tsconfig-path-aliases.js', () => ({
+ tsconfigPathAliases: vi.fn().mockReturnValue({ '@mock/alias': '/mock/path' }),
+}));
+
+const MOCK_PROJECT_ROOT_STRING = '/Users/test/project';
+const MOCK_PROJECT_ROOT_URL = pathToFileURL(`${MOCK_PROJECT_ROOT_STRING}/`);
+
+// Simple path helpers - just use them directly in tests!
+const mockPath = (...segments: string[]) =>
+ path.resolve(MOCK_PROJECT_ROOT_STRING, ...segments);
+const mockUrlPath = (url: URL, ...segments: string[]) =>
+ path.resolve(getProjectRootPath(url), ...segments);
+
+const TEST_TIMEOUTS = {
+ SHORT: 5000,
+ MEDIUM: 10_000,
+ LONG: 30_000,
+} as const;
+
+const EXPECTED_INCLUDES = {
+ unit: ['src/**/*.unit.test.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
+ int: ['src/**/*.int.test.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
+ e2e: ['tests/**/*.e2e.test.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
+} as const;
+
+const DEFAULT_EXCLUDES = ['mocks/**', '**/types.ts'] as const;
+
+const expectCoverageConfig = (config: any, expectedProps: Partial) => {
+ expect(config.test.coverage).toEqual(expect.objectContaining(expectedProps));
+};
+
+describe('createVitestConfig', () => {
+ beforeEach(() => {
+ vi.clearAllMocks();
+ });
+
+ describe('basic functionality', () => {
+ it('should create a basic unit test config with string projectRoot', () => {
+ const options: VitestConfigFactoryOptions = {
+ projectKey: 'test-package',
+ kind: 'unit',
+ projectRoot: MOCK_PROJECT_ROOT_STRING,
+ };
+
+ const config = createVitestConfig(options);
+
+ expect(config).toEqual(
+ expect.objectContaining({
+ cacheDir: mockPath('node_modules', '.vite', 'cache-test-package'),
+ test: expect.objectContaining({
+ reporters: ['basic'],
+ globals: true,
+ cache: {
+ dir: mockPath('node_modules', '.vitest'),
+ },
+ alias: expect.any(Object),
+ pool: 'threads',
+ poolOptions: { threads: { singleThread: true } },
+ environment: 'node',
+ include: EXPECTED_INCLUDES.unit,
+ globalSetup: [mockPath('global-setup.ts')],
+ setupFiles: [],
+ coverage: expect.objectContaining({
+ reporter: ['text', 'lcov'],
+ reportsDirectory: mockPath(
+ 'packages',
+ 'test-package',
+ '.coverage',
+ 'unit-tests',
+ ),
+ exclude: DEFAULT_EXCLUDES,
+ }),
+ }),
+ }),
+ );
+ expect(defineConfig).toHaveBeenCalledWith(config);
+ expect(defineConfig).toHaveBeenCalledTimes(1);
+ });
+
+ it('should create a basic unit test config with URL projectRoot', () => {
+ const options: VitestConfigFactoryOptions = {
+ projectKey: 'test-package',
+ kind: 'unit',
+ projectRoot: MOCK_PROJECT_ROOT_URL,
+ };
+
+ const config = createVitestConfig(options);
+
+ expect(config).toEqual(
+ expect.objectContaining({
+ cacheDir: mockUrlPath(
+ MOCK_PROJECT_ROOT_URL,
+ 'node_modules',
+ '.vite',
+ 'cache-test-package',
+ ),
+ test: expect.objectContaining({
+ include: EXPECTED_INCLUDES.unit,
+ globalSetup: [
+ mockUrlPath(MOCK_PROJECT_ROOT_URL, 'global-setup.ts'),
+ ],
+ }),
+ }),
+ );
+ });
+
+ it('should handle projectRoot string without trailing slash', () => {
+ const projectRoot = '/Users/test/project';
+ const options: VitestConfigFactoryOptions = {
+ projectKey: 'test-package',
+ kind: 'unit',
+ projectRoot,
+ };
+
+ const config = createVitestConfig(options);
+
+ expect((config as any).test.alias).toBeDefined();
+ });
+
+ it('should handle projectRoot string with trailing slash', () => {
+ const projectRoot = '/Users/test/project/';
+ const options: VitestConfigFactoryOptions = {
+ projectKey: 'test-package',
+ kind: 'unit',
+ projectRoot,
+ };
+
+ const config = createVitestConfig(options);
+
+ expect((config as any).test.alias).toBeDefined();
+ });
+ });
+
+ describe('test kind variations', () => {
+ it('should create integration test config', () => {
+ const options: VitestConfigFactoryOptions = {
+ projectKey: 'test-package',
+ kind: 'int',
+ projectRoot: MOCK_PROJECT_ROOT_STRING,
+ };
+
+ const config = createVitestConfig(options);
+
+ expect(config).toEqual(
+ expect.objectContaining({
+ test: expect.objectContaining({
+ include: EXPECTED_INCLUDES.int,
+ globalSetup: [mockPath('global-setup.ts')],
+ coverage: expect.objectContaining({
+ reportsDirectory: mockPath(
+ 'packages',
+ 'test-package',
+ '.coverage',
+ 'int-tests',
+ ),
+ }),
+ }),
+ }),
+ );
+ });
+
+ it('should create e2e test config without coverage by default', () => {
+ const options: VitestConfigFactoryOptions = {
+ projectKey: 'test-package',
+ kind: 'e2e',
+ projectRoot: MOCK_PROJECT_ROOT_STRING,
+ };
+
+ const config = createVitestConfig(options);
+
+ expect(config).toEqual(
+ expect.objectContaining({
+ test: expect.objectContaining({
+ include: EXPECTED_INCLUDES.e2e,
+ globalSetup: undefined,
+ }),
+ }),
+ );
+
+ expect((config as any).test.coverage).toBeUndefined();
+ });
+
+ it('should create e2e test config with coverage when explicitly enabled', () => {
+ const options: VitestConfigFactoryOptions = {
+ projectKey: 'test-package',
+ kind: 'e2e',
+ projectRoot: MOCK_PROJECT_ROOT_STRING,
+ };
+
+ const overrides: VitestOverrides = {
+ test: {
+ coverage: {
+ enabled: true,
+ },
+ },
+ };
+
+ const config = createVitestConfig(options, overrides);
+
+ expect(config).toEqual(
+ expect.objectContaining({
+ test: expect.objectContaining({
+ include: EXPECTED_INCLUDES.e2e,
+ globalSetup: undefined,
+ coverage: expect.objectContaining({
+ reporter: ['text', 'lcov'],
+ reportsDirectory: mockPath('e2e', 'test-package', '.coverage'),
+ exclude: DEFAULT_EXCLUDES,
+ }),
+ }),
+ }),
+ );
+ });
+ });
+
+ describe('cacheKey option', () => {
+ it('should use cacheKey when provided', () => {
+ const options: VitestConfigFactoryOptions = {
+ projectKey: 'test-package',
+ kind: 'unit',
+ projectRoot: MOCK_PROJECT_ROOT_STRING,
+ cacheKey: 'custom-cache-key',
+ };
+
+ const config = createVitestConfig(options);
+
+ expect(config).toEqual(
+ expect.objectContaining({
+ cacheDir: mockPath('node_modules', '.vite', 'custom-cache-key'),
+ }),
+ );
+ });
+
+ it('should fallback to cache-{projectKey} when cacheKey is not provided', () => {
+ const options: VitestConfigFactoryOptions = {
+ projectKey: 'test-package',
+ kind: 'unit',
+ projectRoot: MOCK_PROJECT_ROOT_STRING,
+ };
+
+ const config = createVitestConfig(options);
+
+ expect(config).toEqual(
+ expect.objectContaining({
+ cacheDir: mockPath('node_modules', '.vite', 'cache-test-package'),
+ }),
+ );
+ });
+ });
+
+ describe('setupFiles handling', () => {
+ it('should handle setupFiles as string in overrides', () => {
+ const options: VitestConfigFactoryOptions = {
+ projectKey: 'test-package',
+ kind: 'unit',
+ projectRoot: MOCK_PROJECT_ROOT_STRING,
+ };
+
+ const overrides: VitestOverrides = {
+ test: {
+ setupFiles: 'setup.ts',
+ },
+ };
+
+ const config = createVitestConfig(options, overrides);
+
+ expect(config).toEqual(
+ expect.objectContaining({
+ test: expect.objectContaining({
+ setupFiles: [mockPath('setup.ts')],
+ }),
+ }),
+ );
+ });
+
+ it('should handle setupFiles as array in overrides', () => {
+ const options: VitestConfigFactoryOptions = {
+ projectKey: 'test-package',
+ kind: 'unit',
+ projectRoot: MOCK_PROJECT_ROOT_STRING,
+ };
+
+ const overrides: VitestOverrides = {
+ test: {
+ setupFiles: ['setup1.ts', 'setup2.ts'],
+ },
+ };
+
+ const config = createVitestConfig(options, overrides);
+
+ expect(config).toEqual(
+ expect.objectContaining({
+ test: expect.objectContaining({
+ setupFiles: [mockPath('setup1.ts'), mockPath('setup2.ts')],
+ }),
+ }),
+ );
+ });
+
+ it('should filter out falsy values from setupFiles', () => {
+ const options: VitestConfigFactoryOptions = {
+ projectKey: 'test-package',
+ kind: 'unit',
+ projectRoot: MOCK_PROJECT_ROOT_STRING,
+ };
+
+ const overrides: VitestOverrides = {
+ test: {
+ setupFiles: ['setup1.ts', '', 'setup2.ts', null as any, 'setup3.ts'],
+ },
+ };
+
+ const config = createVitestConfig(options, overrides);
+
+ expect(config).toEqual(
+ expect.objectContaining({
+ test: expect.objectContaining({
+ setupFiles: [
+ mockPath('setup1.ts'),
+ mockPath('setup2.ts'),
+ mockPath('setup3.ts'),
+ ],
+ }),
+ }),
+ );
+ });
+
+ it('should handle empty setupFiles array', () => {
+ const options: VitestConfigFactoryOptions = {
+ projectKey: 'test-package',
+ kind: 'unit',
+ projectRoot: MOCK_PROJECT_ROOT_STRING,
+ };
+
+ const overrides: VitestOverrides = {
+ test: {
+ setupFiles: [],
+ },
+ };
+
+ const config = createVitestConfig(options, overrides);
+
+ expect(config).toEqual(
+ expect.objectContaining({
+ test: expect.objectContaining({
+ setupFiles: [],
+ }),
+ }),
+ );
+ });
+
+ it('should use empty setupFiles when not provided in overrides', () => {
+ const options: VitestConfigFactoryOptions = {
+ projectKey: 'test-package',
+ kind: 'unit',
+ projectRoot: MOCK_PROJECT_ROOT_STRING,
+ };
+
+ const config = createVitestConfig(options, {});
+
+ expect(config).toEqual(
+ expect.objectContaining({
+ test: expect.objectContaining({
+ setupFiles: [],
+ }),
+ }),
+ );
+ });
+ });
+
+ describe('coverage configuration', () => {
+ it('should apply custom coverage exclude paths', () => {
+ const options: VitestConfigFactoryOptions = {
+ projectKey: 'test-package',
+ kind: 'unit',
+ projectRoot: MOCK_PROJECT_ROOT_STRING,
+ };
+
+ const overrides: VitestOverrides = {
+ test: {
+ coverage: {
+ exclude: ['custom/**', 'ignore/**'],
+ },
+ },
+ };
+
+ const config = createVitestConfig(options, overrides);
+
+ expect(config).toEqual(
+ expect.objectContaining({
+ test: expect.objectContaining({
+ coverage: expect.objectContaining({
+ reporter: ['text', 'lcov'],
+ reportsDirectory: mockPath(
+ 'packages',
+ 'test-package',
+ '.coverage',
+ 'unit-tests',
+ ),
+ exclude: [...DEFAULT_EXCLUDES, 'custom/**', 'ignore/**'],
+ }),
+ }),
+ }),
+ );
+ });
+
+ it('should use default exclude when no custom excludes provided', () => {
+ const options: VitestConfigFactoryOptions = {
+ projectKey: 'test-package',
+ kind: 'unit',
+ projectRoot: MOCK_PROJECT_ROOT_STRING,
+ };
+
+ const overrides: VitestOverrides = {
+ test: {
+ coverage: {
+ exclude: [],
+ },
+ },
+ };
+
+ const config = createVitestConfig(options, overrides);
+
+ expect(config).toEqual(
+ expect.objectContaining({
+ test: expect.objectContaining({
+ coverage: expect.objectContaining({
+ reporter: ['text', 'lcov'],
+ reportsDirectory: mockPath(
+ 'packages',
+ 'test-package',
+ '.coverage',
+ 'unit-tests',
+ ),
+ exclude: DEFAULT_EXCLUDES,
+ }),
+ }),
+ }),
+ );
+ });
+
+ it('should disable coverage when explicitly disabled', () => {
+ const options: VitestConfigFactoryOptions = {
+ projectKey: 'test-package',
+ kind: 'unit',
+ projectRoot: MOCK_PROJECT_ROOT_STRING,
+ };
+
+ const overrides: VitestOverrides = {
+ test: {
+ coverage: {
+ enabled: false,
+ },
+ },
+ };
+
+ const config = createVitestConfig(options, overrides);
+
+ expect((config as any).test.coverage).toStrictEqual({});
+ expect(config).toBeDefined();
+ });
+
+ it('should sanitize coverage options by removing enabled and exclude from overrides', () => {
+ const options: VitestConfigFactoryOptions = {
+ projectKey: 'test-package',
+ kind: 'unit',
+ projectRoot: MOCK_PROJECT_ROOT_STRING,
+ };
+
+ const overrides: VitestOverrides = {
+ test: {
+ coverage: {
+ enabled: true,
+ exclude: ['custom/**'],
+ reporter: ['html', 'json'],
+ thresholds: {
+ global: {
+ branches: 80,
+ functions: 80,
+ lines: 80,
+ statements: 80,
+ },
+ },
+ },
+ },
+ };
+
+ const config = createVitestConfig(options, overrides);
+ expectCoverageConfig(config, {
+ reporter: ['text', 'lcov', 'html', 'json'],
+ reportsDirectory: mockPath(
+ 'packages',
+ 'test-package',
+ '.coverage',
+ 'unit-tests',
+ ),
+ exclude: [...DEFAULT_EXCLUDES, 'custom/**'],
+ thresholds: {
+ global: {
+ branches: 80,
+ functions: 80,
+ lines: 80,
+ statements: 80,
+ },
+ },
+ });
+
+ expect(config).toBeDefined();
+ });
+ });
+
+ describe('config merging and sanitization', () => {
+ it('should merge base config with overrides', () => {
+ const options: VitestConfigFactoryOptions = {
+ projectKey: 'test-package',
+ kind: 'unit',
+ projectRoot: MOCK_PROJECT_ROOT_STRING,
+ };
+
+ const overrides: VitestOverrides = {
+ test: {
+ testTimeout: TEST_TIMEOUTS.MEDIUM,
+ pool: 'forks' as any,
+ },
+ };
+
+ const config = createVitestConfig(options, overrides);
+
+ const testConfig = (config as any).test;
+ expect(testConfig.testTimeout).toBe(TEST_TIMEOUTS.MEDIUM);
+ expect(testConfig.pool).toBe('forks');
+ });
+
+ it('should sanitize overrides by removing setupFiles from test config', () => {
+ const options: VitestConfigFactoryOptions = {
+ projectKey: 'test-package',
+ kind: 'unit',
+ projectRoot: MOCK_PROJECT_ROOT_STRING,
+ };
+
+ const overrides: VitestOverrides = {
+ test: {
+ setupFiles: ['should-be-removed.ts'],
+ testTimeout: TEST_TIMEOUTS.SHORT,
+ pool: 'forks' as any,
+ },
+ };
+
+ const config = createVitestConfig(options, overrides);
+
+ const testConfig = (config as any).test;
+ expect(testConfig.setupFiles).toEqual([mockPath('should-be-removed.ts')]);
+ expect(testConfig.testTimeout).toBe(TEST_TIMEOUTS.SHORT);
+ expect(testConfig.pool).toBe('forks');
+ });
+
+ it('should handle overrides without test config', () => {
+ const options: VitestConfigFactoryOptions = {
+ projectKey: 'test-package',
+ kind: 'unit',
+ projectRoot: MOCK_PROJECT_ROOT_STRING,
+ };
+
+ const overrides: VitestOverrides = {
+ build: {
+ target: 'node14',
+ },
+ };
+
+ const config = createVitestConfig(options, overrides);
+
+ expect((config as any).build.target).toBe('node14');
+ });
+
+ it('should handle coverage options as undefined', () => {
+ const options: VitestConfigFactoryOptions = {
+ projectKey: 'test-package',
+ kind: 'unit',
+ projectRoot: MOCK_PROJECT_ROOT_STRING,
+ };
+
+ const overrides: VitestOverrides = {
+ test: {
+ coverage: undefined,
+ testTimeout: TEST_TIMEOUTS.SHORT,
+ },
+ };
+
+ const config = createVitestConfig(options, overrides);
+
+ expect((config as any).test.testTimeout).toBe(TEST_TIMEOUTS.SHORT);
+ expectCoverageConfig(config, {
+ reporter: ['text', 'lcov'],
+ reportsDirectory: mockPath(
+ 'packages',
+ 'test-package',
+ '.coverage',
+ 'unit-tests',
+ ),
+ exclude: DEFAULT_EXCLUDES,
+ });
+ });
+
+ it('should handle coverage options as null', () => {
+ const options: VitestConfigFactoryOptions = {
+ projectKey: 'test-package',
+ kind: 'unit',
+ projectRoot: MOCK_PROJECT_ROOT_STRING,
+ };
+
+ const overrides: VitestOverrides = {
+ test: {
+ coverage: null as any,
+ testTimeout: TEST_TIMEOUTS.SHORT,
+ },
+ };
+
+ const config = createVitestConfig(options, overrides);
+
+ expect((config as any).test.testTimeout).toBe(TEST_TIMEOUTS.SHORT);
+ expectCoverageConfig(config, {
+ reporter: ['text', 'lcov'],
+ reportsDirectory: mockPath(
+ 'packages',
+ 'test-package',
+ '.coverage',
+ 'unit-tests',
+ ),
+ exclude: DEFAULT_EXCLUDES,
+ });
+ });
+ });
+
+ describe('edge cases and error handling', () => {
+ it('should handle all test kinds correctly', () => {
+ const testKinds: TestKind[] = ['unit', 'int', 'e2e'];
+
+ testKinds.forEach(kind => {
+ const options: VitestConfigFactoryOptions = {
+ projectKey: 'test-package',
+ kind,
+ projectRoot: MOCK_PROJECT_ROOT_STRING,
+ };
+
+ const config = createVitestConfig(options);
+
+ const expectedIncludes = {
+ unit: ['src/**/*.unit.test.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
+ int: ['src/**/*.int.test.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
+ e2e: ['tests/**/*.e2e.test.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
+ };
+
+ expect((config as any).test.include).toStrictEqual(
+ expectedIncludes[kind],
+ );
+ expect((config as any).test.globalSetup).toStrictEqual(
+ kind === 'e2e' ? undefined : [mockPath('global-setup.ts')],
+ );
+ });
+ });
+
+ it('should handle complex override scenarios', () => {
+ const options: VitestConfigFactoryOptions = {
+ projectKey: 'test-package',
+ kind: 'int',
+ projectRoot: MOCK_PROJECT_ROOT_STRING,
+ cacheKey: 'complex-scenario',
+ };
+
+ const overrides: VitestOverrides = {
+ test: {
+ setupFiles: ['setup1.ts', 'setup2.ts'],
+ coverage: {
+ enabled: true,
+ exclude: ['e2e/**', 'dist/**'],
+ reporter: ['lcov', 'text-summary'],
+ thresholds: {
+ global: {
+ statements: 90,
+ branches: 85,
+ functions: 90,
+ lines: 90,
+ },
+ },
+ },
+ testTimeout: TEST_TIMEOUTS.LONG,
+ environment: 'jsdom' as any,
+ },
+ build: {
+ target: 'es2020',
+ },
+ };
+
+ const config = createVitestConfig(options, overrides);
+
+ expect(config).toEqual(
+ expect.objectContaining({
+ cacheDir: mockPath('node_modules', '.vite', 'complex-scenario'),
+ build: {
+ target: 'es2020',
+ },
+ test: expect.objectContaining({
+ setupFiles: [mockPath('setup1.ts'), mockPath('setup2.ts')],
+ testTimeout: TEST_TIMEOUTS.LONG,
+ environment: 'jsdom',
+ include: EXPECTED_INCLUDES.int,
+ coverage: expect.objectContaining({
+ exclude: ['mocks/**', '**/types.ts', 'e2e/**', 'dist/**'],
+ reportsDirectory: mockPath(
+ 'packages',
+ 'test-package',
+ '.coverage',
+ 'int-tests',
+ ),
+ }),
+ }),
+ }),
+ );
+ });
+ });
+});
diff --git a/testing/test-setup-config/src/lib/vitest-setup-presets.ts b/testing/test-setup-config/src/lib/vitest-setup-presets.ts
new file mode 100644
index 000000000..d887747fa
--- /dev/null
+++ b/testing/test-setup-config/src/lib/vitest-setup-presets.ts
@@ -0,0 +1,84 @@
+import {
+ type ConfigRestParams,
+ type VitestOverrides,
+ createVitestConfig,
+} from './vitest-config-factory.js';
+
+const CONSOLE_MOCK_PATH = 'testing/test-setup/src/lib/console.mock.ts';
+const RESET_MOCKS_PATH = 'testing/test-setup/src/lib/reset.mocks.ts';
+
+export const setupPresets = {
+ unit: {
+ base: [
+ CONSOLE_MOCK_PATH,
+ RESET_MOCKS_PATH,
+ 'testing/test-setup/src/lib/cliui.mock.ts',
+ 'testing/test-setup/src/lib/fs.mock.ts',
+ 'testing/test-setup/src/lib/extend/ui-logger.matcher.ts',
+ ],
+ git: ['testing/test-setup/src/lib/git.mock.ts'],
+ portalClient: ['testing/test-setup/src/lib/portal-client.mock.ts'],
+ matchersCore: [
+ 'testing/test-setup/src/lib/extend/markdown-table.matcher.ts',
+ 'testing/test-setup/src/lib/extend/jest-extended.matcher.ts',
+ ],
+ matcherPath: ['testing/test-setup/src/lib/extend/path.matcher.ts'],
+ },
+ int: {
+ base: [CONSOLE_MOCK_PATH, RESET_MOCKS_PATH],
+ cliui: ['testing/test-setup/src/lib/cliui.mock.ts'],
+ fs: ['testing/test-setup/src/lib/fs.mock.ts'],
+ git: ['testing/test-setup/src/lib/git.mock.ts'],
+ portalClient: ['testing/test-setup/src/lib/portal-client.mock.ts'],
+ matcherPath: ['testing/test-setup/src/lib/extend/path.matcher.ts'],
+ chromePath: ['testing/test-setup/src/lib/chrome-path.mock.ts'],
+ },
+ e2e: {
+ base: [RESET_MOCKS_PATH],
+ },
+} as const;
+
+export const createUnitConfig = (
+ projectKey: string,
+ rest: ConfigRestParams,
+ overrides?: VitestOverrides,
+) => {
+ const finalSetupFiles = overrides?.test?.setupFiles ?? [
+ ...setupPresets.unit.base,
+ ];
+
+ return createVitestConfig(
+ { projectKey, kind: 'unit', ...rest },
+ { ...overrides, test: { ...overrides?.test, setupFiles: finalSetupFiles } },
+ );
+};
+
+export const createIntConfig = (
+ projectKey: string,
+ rest: ConfigRestParams,
+ overrides?: VitestOverrides,
+) => {
+ const finalSetupFiles = overrides?.test?.setupFiles ?? [
+ ...setupPresets.int.base,
+ ];
+
+ return createVitestConfig(
+ { projectKey, kind: 'int', ...rest },
+ { ...overrides, test: { ...overrides?.test, setupFiles: finalSetupFiles } },
+ );
+};
+
+export const createE2eConfig = (
+ projectKey: string,
+ rest: ConfigRestParams,
+ overrides?: VitestOverrides,
+) => {
+ const finalSetupFiles = overrides?.test?.setupFiles ?? [
+ ...setupPresets.e2e.base,
+ ];
+
+ return createVitestConfig(
+ { projectKey, kind: 'e2e', ...rest },
+ { ...overrides, test: { ...overrides?.test, setupFiles: finalSetupFiles } },
+ );
+};
diff --git a/testing/test-setup-config/src/lib/vitest-setup-presets.unit.test.ts b/testing/test-setup-config/src/lib/vitest-setup-presets.unit.test.ts
new file mode 100644
index 000000000..cc303dd87
--- /dev/null
+++ b/testing/test-setup-config/src/lib/vitest-setup-presets.unit.test.ts
@@ -0,0 +1,667 @@
+import { beforeEach, describe, expect, it, vi } from 'vitest';
+import type { VitestOverrides } from './vitest-config-factory.js';
+import * as configFactory from './vitest-config-factory.js';
+import {
+ createE2eConfig,
+ createIntConfig,
+ createUnitConfig,
+ setupPresets,
+} from './vitest-setup-presets.js';
+
+vi.mock('./vitest-config-factory.js', () => ({
+ createVitestConfig: vi.fn().mockReturnValue('mocked-config'),
+}));
+
+const MOCK_PROJECT_KEY = 'test-package';
+const MOCK_CONFIG_REST_PARAMS = {
+ projectRoot: '/test/project',
+ cacheKey: 'test-cache',
+};
+
+const TEST_TIMEOUTS = {
+ SHORT: 5000,
+ MEDIUM: 10_000,
+ LONG: 15_000,
+ E2E: 60_000,
+} as const;
+
+describe('setupPresets', () => {
+ it('should export correct unit setup presets', () => {
+ expect(setupPresets.unit).toEqual({
+ base: [
+ 'testing/test-setup/src/lib/console.mock.ts',
+ 'testing/test-setup/src/lib/reset.mocks.ts',
+ 'testing/test-setup/src/lib/cliui.mock.ts',
+ 'testing/test-setup/src/lib/fs.mock.ts',
+ 'testing/test-setup/src/lib/extend/ui-logger.matcher.ts',
+ ],
+ git: ['testing/test-setup/src/lib/git.mock.ts'],
+ portalClient: ['testing/test-setup/src/lib/portal-client.mock.ts'],
+ matchersCore: [
+ 'testing/test-setup/src/lib/extend/markdown-table.matcher.ts',
+ 'testing/test-setup/src/lib/extend/jest-extended.matcher.ts',
+ ],
+ matcherPath: ['testing/test-setup/src/lib/extend/path.matcher.ts'],
+ });
+ });
+
+ it('should export correct integration setup presets', () => {
+ expect(setupPresets.int).toEqual({
+ base: [
+ 'testing/test-setup/src/lib/console.mock.ts',
+ 'testing/test-setup/src/lib/reset.mocks.ts',
+ ],
+ cliui: ['testing/test-setup/src/lib/cliui.mock.ts'],
+ fs: ['testing/test-setup/src/lib/fs.mock.ts'],
+ git: ['testing/test-setup/src/lib/git.mock.ts'],
+ portalClient: ['testing/test-setup/src/lib/portal-client.mock.ts'],
+ matcherPath: ['testing/test-setup/src/lib/extend/path.matcher.ts'],
+ chromePath: ['testing/test-setup/src/lib/chrome-path.mock.ts'],
+ });
+ });
+
+ it('should export correct e2e setup presets', () => {
+ expect(setupPresets.e2e).toEqual({
+ base: ['testing/test-setup/src/lib/reset.mocks.ts'],
+ });
+ });
+
+ it('should be defined as a const object', () => {
+ expect(setupPresets).toBeDefined();
+ expect(typeof setupPresets).toBe('object');
+ });
+});
+
+describe('createUnitConfig', () => {
+ beforeEach(() => {
+ vi.clearAllMocks();
+ });
+
+ it('should call createVitestConfig with correct parameters and default setupFiles', () => {
+ createUnitConfig(MOCK_PROJECT_KEY, MOCK_CONFIG_REST_PARAMS);
+
+ expect(configFactory.createVitestConfig).toHaveBeenCalledWith(
+ {
+ projectKey: MOCK_PROJECT_KEY,
+ kind: 'unit',
+ ...MOCK_CONFIG_REST_PARAMS,
+ },
+ {
+ test: {
+ setupFiles: setupPresets.unit.base,
+ },
+ },
+ );
+ });
+
+ it('should use custom setupFiles from overrides when provided', () => {
+ const customSetupFiles = ['unit-setup1.ts', 'unit-setup2.ts'];
+ const overrides: VitestOverrides = {
+ test: {
+ setupFiles: customSetupFiles,
+ },
+ };
+
+ createUnitConfig(MOCK_PROJECT_KEY, MOCK_CONFIG_REST_PARAMS, overrides);
+
+ expect(configFactory.createVitestConfig).toHaveBeenCalledWith(
+ {
+ projectKey: MOCK_PROJECT_KEY,
+ kind: 'unit',
+ ...MOCK_CONFIG_REST_PARAMS,
+ },
+ {
+ test: {
+ setupFiles: customSetupFiles,
+ },
+ },
+ );
+ });
+
+ it('should merge other overrides correctly while using default setupFiles', () => {
+ const overrides: VitestOverrides = {
+ test: {
+ testTimeout: TEST_TIMEOUTS.MEDIUM,
+ globals: false,
+ },
+ build: {
+ target: 'es2020',
+ },
+ };
+
+ createUnitConfig(MOCK_PROJECT_KEY, MOCK_CONFIG_REST_PARAMS, overrides);
+
+ expect(configFactory.createVitestConfig).toHaveBeenCalledWith(
+ {
+ projectKey: MOCK_PROJECT_KEY,
+ kind: 'unit',
+ ...MOCK_CONFIG_REST_PARAMS,
+ },
+ {
+ test: {
+ testTimeout: TEST_TIMEOUTS.MEDIUM,
+ globals: false,
+ setupFiles: setupPresets.unit.base,
+ },
+ build: {
+ target: 'es2020',
+ },
+ },
+ );
+ });
+
+ it('should handle overrides with custom setupFiles and other test options', () => {
+ const customSetupFiles = ['unit-custom.ts'];
+ const overrides: VitestOverrides = {
+ test: {
+ setupFiles: customSetupFiles,
+ testTimeout: TEST_TIMEOUTS.SHORT,
+ environment: 'jsdom' as any,
+ },
+ };
+
+ createUnitConfig(MOCK_PROJECT_KEY, MOCK_CONFIG_REST_PARAMS, overrides);
+
+ expect(configFactory.createVitestConfig).toHaveBeenCalledWith(
+ {
+ projectKey: MOCK_PROJECT_KEY,
+ kind: 'unit',
+ ...MOCK_CONFIG_REST_PARAMS,
+ },
+ {
+ test: {
+ setupFiles: customSetupFiles,
+ testTimeout: TEST_TIMEOUTS.SHORT,
+ environment: 'jsdom',
+ },
+ },
+ );
+ });
+
+ it('should handle undefined overrides', () => {
+ createUnitConfig(MOCK_PROJECT_KEY, MOCK_CONFIG_REST_PARAMS, undefined);
+
+ expect(configFactory.createVitestConfig).toHaveBeenCalledWith(
+ {
+ projectKey: MOCK_PROJECT_KEY,
+ kind: 'unit',
+ ...MOCK_CONFIG_REST_PARAMS,
+ },
+ {
+ test: {
+ setupFiles: setupPresets.unit.base,
+ },
+ },
+ );
+ });
+
+ it('should handle overrides without test config', () => {
+ const overrides: VitestOverrides = {
+ build: {
+ target: 'es2020',
+ },
+ };
+
+ createUnitConfig(MOCK_PROJECT_KEY, MOCK_CONFIG_REST_PARAMS, overrides);
+
+ expect(configFactory.createVitestConfig).toHaveBeenCalledWith(
+ {
+ projectKey: MOCK_PROJECT_KEY,
+ kind: 'unit',
+ ...MOCK_CONFIG_REST_PARAMS,
+ },
+ {
+ build: {
+ target: 'es2020',
+ },
+ test: {
+ setupFiles: setupPresets.unit.base,
+ },
+ },
+ );
+ });
+
+ it('should return the result from createVitestConfig', () => {
+ const result = createUnitConfig(MOCK_PROJECT_KEY, MOCK_CONFIG_REST_PARAMS);
+
+ expect(result).toBe('mocked-config');
+ });
+
+ it('should handle empty projectKey gracefully', () => {
+ const result = createUnitConfig('', MOCK_CONFIG_REST_PARAMS);
+
+ expect(configFactory.createVitestConfig).toHaveBeenCalledWith(
+ {
+ projectKey: '',
+ kind: 'unit',
+ ...MOCK_CONFIG_REST_PARAMS,
+ },
+ {
+ test: {
+ setupFiles: setupPresets.unit.base,
+ },
+ },
+ );
+ expect(result).toBe('mocked-config');
+ });
+});
+
+describe('createIntConfig', () => {
+ beforeEach(() => {
+ vi.clearAllMocks();
+ });
+
+ it('should call createVitestConfig with correct parameters and default setupFiles', () => {
+ createIntConfig(MOCK_PROJECT_KEY, MOCK_CONFIG_REST_PARAMS);
+
+ expect(configFactory.createVitestConfig).toHaveBeenCalledWith(
+ {
+ projectKey: MOCK_PROJECT_KEY,
+ kind: 'int',
+ ...MOCK_CONFIG_REST_PARAMS,
+ },
+ {
+ test: {
+ setupFiles: setupPresets.int.base,
+ },
+ },
+ );
+ });
+
+ it('should use custom setupFiles from overrides when provided', () => {
+ const customSetupFiles = ['int-setup1.ts', 'int-setup2.ts'];
+ const overrides: VitestOverrides = {
+ test: {
+ setupFiles: customSetupFiles,
+ },
+ };
+
+ createIntConfig(MOCK_PROJECT_KEY, MOCK_CONFIG_REST_PARAMS, overrides);
+
+ expect(configFactory.createVitestConfig).toHaveBeenCalledWith(
+ {
+ projectKey: MOCK_PROJECT_KEY,
+ kind: 'int',
+ ...MOCK_CONFIG_REST_PARAMS,
+ },
+ {
+ test: {
+ setupFiles: customSetupFiles,
+ },
+ },
+ );
+ });
+
+ it('should merge other overrides correctly while using default setupFiles', () => {
+ const overrides: VitestOverrides = {
+ test: {
+ testTimeout: TEST_TIMEOUTS.MEDIUM,
+ globals: false,
+ },
+ build: {
+ target: 'es2020',
+ },
+ };
+
+ createIntConfig(MOCK_PROJECT_KEY, MOCK_CONFIG_REST_PARAMS, overrides);
+
+ expect(configFactory.createVitestConfig).toHaveBeenCalledWith(
+ {
+ projectKey: MOCK_PROJECT_KEY,
+ kind: 'int',
+ ...MOCK_CONFIG_REST_PARAMS,
+ },
+ {
+ test: {
+ testTimeout: TEST_TIMEOUTS.MEDIUM,
+ globals: false,
+ setupFiles: setupPresets.int.base,
+ },
+ build: {
+ target: 'es2020',
+ },
+ },
+ );
+ });
+
+ it('should handle overrides with custom setupFiles and other test options', () => {
+ const customSetupFiles = ['int-custom.ts'];
+ const overrides: VitestOverrides = {
+ test: {
+ setupFiles: customSetupFiles,
+ testTimeout: TEST_TIMEOUTS.SHORT,
+ environment: 'jsdom' as any,
+ },
+ };
+
+ createIntConfig(MOCK_PROJECT_KEY, MOCK_CONFIG_REST_PARAMS, overrides);
+
+ expect(configFactory.createVitestConfig).toHaveBeenCalledWith(
+ {
+ projectKey: MOCK_PROJECT_KEY,
+ kind: 'int',
+ ...MOCK_CONFIG_REST_PARAMS,
+ },
+ {
+ test: {
+ setupFiles: customSetupFiles,
+ testTimeout: TEST_TIMEOUTS.SHORT,
+ environment: 'jsdom',
+ },
+ },
+ );
+ });
+
+ it('should handle undefined overrides', () => {
+ createIntConfig(MOCK_PROJECT_KEY, MOCK_CONFIG_REST_PARAMS, undefined);
+
+ expect(configFactory.createVitestConfig).toHaveBeenCalledWith(
+ {
+ projectKey: MOCK_PROJECT_KEY,
+ kind: 'int',
+ ...MOCK_CONFIG_REST_PARAMS,
+ },
+ {
+ test: {
+ setupFiles: setupPresets.int.base,
+ },
+ },
+ );
+ });
+
+ it('should handle overrides without test config', () => {
+ const overrides: VitestOverrides = {
+ build: {
+ target: 'es2020',
+ },
+ };
+
+ createIntConfig(MOCK_PROJECT_KEY, MOCK_CONFIG_REST_PARAMS, overrides);
+
+ expect(configFactory.createVitestConfig).toHaveBeenCalledWith(
+ {
+ projectKey: MOCK_PROJECT_KEY,
+ kind: 'int',
+ ...MOCK_CONFIG_REST_PARAMS,
+ },
+ {
+ build: {
+ target: 'es2020',
+ },
+ test: {
+ setupFiles: setupPresets.int.base,
+ },
+ },
+ );
+ });
+
+ it('should return the result from createVitestConfig', () => {
+ const result = createIntConfig(MOCK_PROJECT_KEY, MOCK_CONFIG_REST_PARAMS);
+
+ expect(result).toBe('mocked-config');
+ });
+
+ it('should handle empty projectKey gracefully', () => {
+ const result = createIntConfig('', MOCK_CONFIG_REST_PARAMS);
+
+ expect(configFactory.createVitestConfig).toHaveBeenCalledWith(
+ {
+ projectKey: '',
+ kind: 'int',
+ ...MOCK_CONFIG_REST_PARAMS,
+ },
+ {
+ test: {
+ setupFiles: setupPresets.int.base,
+ },
+ },
+ );
+ expect(result).toBe('mocked-config');
+ });
+});
+
+describe('createE2eConfig', () => {
+ beforeEach(() => {
+ vi.clearAllMocks();
+ });
+
+ it('should call createVitestConfig with correct parameters and default setupFiles', () => {
+ createE2eConfig(MOCK_PROJECT_KEY, MOCK_CONFIG_REST_PARAMS);
+
+ expect(configFactory.createVitestConfig).toHaveBeenCalledWith(
+ {
+ projectKey: MOCK_PROJECT_KEY,
+ kind: 'e2e',
+ ...MOCK_CONFIG_REST_PARAMS,
+ },
+ {
+ test: {
+ setupFiles: setupPresets.e2e.base,
+ },
+ },
+ );
+ });
+
+ it('should use custom setupFiles from overrides when provided', () => {
+ const customSetupFiles = ['e2e-setup1.ts', 'e2e-setup2.ts'];
+ const overrides: VitestOverrides = {
+ test: {
+ setupFiles: customSetupFiles,
+ },
+ };
+
+ createE2eConfig(MOCK_PROJECT_KEY, MOCK_CONFIG_REST_PARAMS, overrides);
+
+ expect(configFactory.createVitestConfig).toHaveBeenCalledWith(
+ {
+ projectKey: MOCK_PROJECT_KEY,
+ kind: 'e2e',
+ ...MOCK_CONFIG_REST_PARAMS,
+ },
+ {
+ test: {
+ setupFiles: customSetupFiles,
+ },
+ },
+ );
+ });
+
+ it('should merge other overrides correctly while using default setupFiles', () => {
+ const overrides: VitestOverrides = {
+ test: {
+ testTimeout: TEST_TIMEOUTS.MEDIUM,
+ globals: false,
+ },
+ build: {
+ target: 'es2020',
+ },
+ };
+
+ createE2eConfig(MOCK_PROJECT_KEY, MOCK_CONFIG_REST_PARAMS, overrides);
+
+ expect(configFactory.createVitestConfig).toHaveBeenCalledWith(
+ {
+ projectKey: MOCK_PROJECT_KEY,
+ kind: 'e2e',
+ ...MOCK_CONFIG_REST_PARAMS,
+ },
+ {
+ test: {
+ testTimeout: TEST_TIMEOUTS.MEDIUM,
+ globals: false,
+ setupFiles: setupPresets.e2e.base,
+ },
+ build: {
+ target: 'es2020',
+ },
+ },
+ );
+ });
+
+ it('should handle overrides with custom setupFiles and other test options', () => {
+ const customSetupFiles = ['e2e-custom.ts'];
+ const overrides: VitestOverrides = {
+ test: {
+ setupFiles: customSetupFiles,
+ testTimeout: TEST_TIMEOUTS.SHORT,
+ environment: 'jsdom' as any,
+ },
+ };
+
+ createE2eConfig(MOCK_PROJECT_KEY, MOCK_CONFIG_REST_PARAMS, overrides);
+
+ expect(configFactory.createVitestConfig).toHaveBeenCalledWith(
+ {
+ projectKey: MOCK_PROJECT_KEY,
+ kind: 'e2e',
+ ...MOCK_CONFIG_REST_PARAMS,
+ },
+ {
+ test: {
+ setupFiles: customSetupFiles,
+ testTimeout: TEST_TIMEOUTS.SHORT,
+ environment: 'jsdom',
+ },
+ },
+ );
+ });
+
+ it('should handle undefined overrides', () => {
+ createE2eConfig(MOCK_PROJECT_KEY, MOCK_CONFIG_REST_PARAMS, undefined);
+
+ expect(configFactory.createVitestConfig).toHaveBeenCalledWith(
+ {
+ projectKey: MOCK_PROJECT_KEY,
+ kind: 'e2e',
+ ...MOCK_CONFIG_REST_PARAMS,
+ },
+ {
+ test: {
+ setupFiles: setupPresets.e2e.base,
+ },
+ },
+ );
+ });
+
+ it('should handle overrides without test config', () => {
+ const overrides: VitestOverrides = {
+ build: {
+ target: 'es2020',
+ },
+ };
+
+ createE2eConfig(MOCK_PROJECT_KEY, MOCK_CONFIG_REST_PARAMS, overrides);
+
+ expect(configFactory.createVitestConfig).toHaveBeenCalledWith(
+ {
+ projectKey: MOCK_PROJECT_KEY,
+ kind: 'e2e',
+ ...MOCK_CONFIG_REST_PARAMS,
+ },
+ {
+ build: {
+ target: 'es2020',
+ },
+ test: {
+ setupFiles: setupPresets.e2e.base,
+ },
+ },
+ );
+ });
+
+ it('should return the result from createVitestConfig', () => {
+ const result = createE2eConfig(MOCK_PROJECT_KEY, MOCK_CONFIG_REST_PARAMS);
+
+ expect(result).toBe('mocked-config');
+ });
+
+ it('should handle empty projectKey gracefully', () => {
+ const result = createE2eConfig('', MOCK_CONFIG_REST_PARAMS);
+
+ expect(configFactory.createVitestConfig).toHaveBeenCalledWith(
+ {
+ projectKey: '',
+ kind: 'e2e',
+ ...MOCK_CONFIG_REST_PARAMS,
+ },
+ {
+ test: {
+ setupFiles: setupPresets.e2e.base,
+ },
+ },
+ );
+ expect(result).toBe('mocked-config');
+ });
+});
+
+describe('integration between preset functions', () => {
+ beforeEach(() => {
+ vi.clearAllMocks();
+ });
+
+ it('should use different setup presets for different test kinds', () => {
+ createUnitConfig('test-pkg', { projectRoot: '/test' });
+ createIntConfig('test-pkg', { projectRoot: '/test' });
+ createE2eConfig('test-pkg', { projectRoot: '/test' });
+
+ expect(configFactory.createVitestConfig).toHaveBeenCalledTimes(3);
+
+ expect(configFactory.createVitestConfig).toHaveBeenNthCalledWith(
+ 1,
+ { projectKey: 'test-pkg', kind: 'unit', projectRoot: '/test' },
+ { test: { setupFiles: setupPresets.unit.base } },
+ );
+
+ expect(configFactory.createVitestConfig).toHaveBeenNthCalledWith(
+ 2,
+ { projectKey: 'test-pkg', kind: 'int', projectRoot: '/test' },
+ { test: { setupFiles: setupPresets.int.base } },
+ );
+
+ expect(configFactory.createVitestConfig).toHaveBeenNthCalledWith(
+ 3,
+ { projectKey: 'test-pkg', kind: 'e2e', projectRoot: '/test' },
+ { test: { setupFiles: setupPresets.e2e.base } },
+ );
+ });
+
+ it('should handle complex scenarios with all preset functions', () => {
+ const complexOverrides: VitestOverrides = {
+ test: {
+ setupFiles: ['global-setup.ts'],
+ testTimeout: TEST_TIMEOUTS.LONG,
+ coverage: {
+ enabled: true,
+ thresholds: {
+ global: {
+ statements: 90,
+ },
+ },
+ },
+ },
+ build: {
+ target: 'es2022',
+ },
+ };
+
+ const restParams = {
+ projectRoot: '/complex/project',
+ cacheKey: 'complex-cache',
+ };
+
+ createUnitConfig('complex-unit', restParams, complexOverrides);
+ createIntConfig('complex-int', restParams, complexOverrides);
+ createE2eConfig('complex-e2e', restParams, complexOverrides);
+
+ expect(configFactory.createVitestConfig).toHaveBeenCalledTimes(3);
+
+ const calls = (configFactory.createVitestConfig as any).mock.calls;
+ calls.forEach((call: any) => {
+ expect(call[1].test.setupFiles).toStrictEqual(['global-setup.ts']);
+ });
+
+ expect(calls[0][0].kind).toBe('unit');
+ expect(calls[1][0].kind).toBe('int');
+ expect(calls[2][0].kind).toBe('e2e');
+ });
+});
diff --git a/testing/test-setup-config/src/lib/vitest-tsconfig-path-aliases.ts b/testing/test-setup-config/src/lib/vitest-tsconfig-path-aliases.ts
new file mode 100644
index 000000000..aec88e199
--- /dev/null
+++ b/testing/test-setup-config/src/lib/vitest-tsconfig-path-aliases.ts
@@ -0,0 +1,27 @@
+import path from 'node:path';
+import { fileURLToPath } from 'node:url';
+import { loadConfig } from 'tsconfig-paths';
+import type { Alias, AliasOptions } from 'vite';
+
+export function tsconfigPathAliases(projectRootUrl?: URL): AliasOptions {
+ const tsconfigPath = projectRootUrl
+ ? path.resolve(fileURLToPath(projectRootUrl), 'tsconfig.base.json')
+ : 'tsconfig.base.json';
+ const result = loadConfig(tsconfigPath);
+ if (result.resultType === 'failed') {
+ throw new Error(
+ `Failed to load path aliases from tsconfig for Vitest: ${result.message}`,
+ );
+ }
+ return Object.entries(result.paths)
+ .map(([key, value]) => [key, value[0]])
+ .filter((pair): pair is [string, string] => pair[1] != null)
+ .map(
+ ([importPath, relativePath]): Alias => ({
+ find: importPath,
+ replacement: projectRootUrl
+ ? path.resolve(fileURLToPath(projectRootUrl), relativePath)
+ : new URL(`../${relativePath}`, import.meta.url).pathname,
+ }),
+ );
+}
diff --git a/testing/test-setup-config/tsconfig.json b/testing/test-setup-config/tsconfig.json
new file mode 100644
index 000000000..465306e46
--- /dev/null
+++ b/testing/test-setup-config/tsconfig.json
@@ -0,0 +1,22 @@
+{
+ "extends": "../../tsconfig.base.json",
+ "compilerOptions": {
+ "module": "ESNext",
+ "forceConsistentCasingInFileNames": true,
+ "strict": true,
+ "noImplicitOverride": true,
+ "noPropertyAccessFromIndexSignature": true,
+ "noImplicitReturns": true,
+ "noFallthroughCasesInSwitch": true
+ },
+ "files": [],
+ "include": [],
+ "references": [
+ {
+ "path": "./tsconfig.lib.json"
+ },
+ {
+ "path": "./tsconfig.test.json"
+ }
+ ]
+}
diff --git a/testing/test-setup-config/tsconfig.lib.json b/testing/test-setup-config/tsconfig.lib.json
new file mode 100644
index 000000000..3cc313086
--- /dev/null
+++ b/testing/test-setup-config/tsconfig.lib.json
@@ -0,0 +1,15 @@
+{
+ "extends": "./tsconfig.json",
+ "compilerOptions": {
+ "outDir": "../dist/out-tsc",
+ "declaration": true,
+ "types": ["node"]
+ },
+ "include": ["src/**/*.ts"],
+ "exclude": [
+ "vitest.unit.config.ts",
+ "src/vitest.d.ts",
+ "src/**/*.unit.test.ts",
+ "src/**/*.int.test.ts"
+ ]
+}
diff --git a/testing/test-setup-config/tsconfig.test.json b/testing/test-setup-config/tsconfig.test.json
new file mode 100644
index 000000000..5fddc20ae
--- /dev/null
+++ b/testing/test-setup-config/tsconfig.test.json
@@ -0,0 +1,14 @@
+{
+ "extends": "./tsconfig.json",
+ "compilerOptions": {
+ "outDir": "../../dist/out-tsc",
+ "types": ["vitest/globals", "vitest/importMeta", "vite/client", "node"]
+ },
+ "include": [
+ "vitest.unit.config.ts",
+ "src/vitest.d.ts",
+ "src/**/*.unit.test.ts",
+ "src/**/*.d.ts",
+ "src/**/*.int.test.ts"
+ ]
+}
diff --git a/testing/test-setup-config/vitest.unit.config.ts b/testing/test-setup-config/vitest.unit.config.ts
new file mode 100644
index 000000000..95bef2c28
--- /dev/null
+++ b/testing/test-setup-config/vitest.unit.config.ts
@@ -0,0 +1,23 @@
+///
+import {
+ createUnitConfig,
+ setupPresets,
+} from './src/lib/vitest-setup-presets.js';
+
+export default createUnitConfig(
+ 'test-setup-config',
+ {
+ projectRoot: new URL('../../', import.meta.url),
+ },
+ {
+ test: {
+ include: ['src/**/*.{unit,type}.test.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
+ setupFiles: [...setupPresets.unit.base, ...setupPresets.unit.matcherPath],
+ coverage: {
+ enabled: true,
+ reporter: ['text', 'lcov'],
+ exclude: ['**/*.mock.{mjs,ts}', '**/*.config.{js,mjs,ts}'],
+ },
+ },
+ },
+);
diff --git a/testing/test-setup/README.md b/testing/test-setup/README.md
index db0ce1eba..3e9c2c236 100644
--- a/testing/test-setup/README.md
+++ b/testing/test-setup/README.md
@@ -4,6 +4,10 @@ This library contains test setup.
More on this subject as well as all the testing strategy principles can be found on the GitHub [wiki](https://github.com/code-pushup/cli/wiki/Testing-Strategy#mocking).
+## Shared config
+
+[README](./src/lib/config/README.md) how to use vitest config factory.
+
## Mock setup
In this library you can find all files that can be used in `setupFiles` property of `vitest.config.(unit|int|e2e).ts` files. Currently include:
diff --git a/tools/vitest-tsconfig-path-aliases.ts b/tools/vitest-tsconfig-path-aliases.ts
index ac8be04df..aec88e199 100644
--- a/tools/vitest-tsconfig-path-aliases.ts
+++ b/tools/vitest-tsconfig-path-aliases.ts
@@ -1,8 +1,13 @@
+import path from 'node:path';
+import { fileURLToPath } from 'node:url';
import { loadConfig } from 'tsconfig-paths';
import type { Alias, AliasOptions } from 'vite';
-export function tsconfigPathAliases(): AliasOptions {
- const result = loadConfig('tsconfig.base.json');
+export function tsconfigPathAliases(projectRootUrl?: URL): AliasOptions {
+ const tsconfigPath = projectRootUrl
+ ? path.resolve(fileURLToPath(projectRootUrl), 'tsconfig.base.json')
+ : 'tsconfig.base.json';
+ const result = loadConfig(tsconfigPath);
if (result.resultType === 'failed') {
throw new Error(
`Failed to load path aliases from tsconfig for Vitest: ${result.message}`,
@@ -14,7 +19,9 @@ export function tsconfigPathAliases(): AliasOptions {
.map(
([importPath, relativePath]): Alias => ({
find: importPath,
- replacement: new URL(`../${relativePath}`, import.meta.url).pathname,
+ replacement: projectRootUrl
+ ? path.resolve(fileURLToPath(projectRootUrl), relativePath)
+ : new URL(`../${relativePath}`, import.meta.url).pathname,
}),
);
}
diff --git a/tsconfig.base.json b/tsconfig.base.json
index 7f3b4d21f..4bca1c69f 100644
--- a/tsconfig.base.json
+++ b/tsconfig.base.json
@@ -39,6 +39,9 @@
"@code-pushup/nx-plugin": ["packages/nx-plugin/src/index.ts"],
"@code-pushup/test-nx-utils": ["testing/test-nx-utils/src/index.ts"],
"@code-pushup/test-setup": ["testing/test-setup/src/index.ts"],
+ "@code-pushup/test-setup-config": [
+ "testing/test-setup-config/src/index.ts"
+ ],
"@code-pushup/test-utils": ["testing/test-utils/src/index.ts"],
"@code-pushup/typescript-plugin": [
"packages/plugin-typescript/src/index.ts"