From 504126dd0087fc648f3858c1de061f578aa5a880 Mon Sep 17 00:00:00 2001 From: "Szymon.Poltorak" Date: Fri, 5 Sep 2025 13:13:39 +0200 Subject: [PATCH 01/23] feat(): create common vitest config setup --- tools/README.md | 154 +++++++++++++++++++++++++++++++++ tools/vitest-config-factory.ts | 151 ++++++++++++++++++++++++++++++++ tools/vitest-setup-presets.ts | 33 +++++++ 3 files changed, 338 insertions(+) create mode 100644 tools/README.md create mode 100644 tools/vitest-config-factory.ts create mode 100644 tools/vitest-setup-presets.ts diff --git a/tools/README.md b/tools/README.md new file mode 100644 index 000000000..cd88d502f --- /dev/null +++ b/tools/README.md @@ -0,0 +1,154 @@ +## Vitest config factory and setup presets + +This folder contains utilities to centralize and standardize Vitest configuration across the monorepo. + +### Files + +- `vitest-config-factory.ts`: builds typed Vitest configs with sensible defaults. +- `vitest-setup-presets.ts`: reusable groups of `setupFiles` paths by test kind. + +### Goals + +- Reduce duplication across `vitest.*.config.ts` files. +- Keep per-package intent clear with minimal overrides. +- Provide safe defaults and easy extension points. + +### Quick start + +Use the factory in your suite configs (project root is required): + +```ts +/// +import { createE2eConfig, createIntConfig, createUnitConfig } from '../../tools/vitest-config-factory.js'; + +export default createUnitConfig('core', { + projectRoot: new URL('../../', import.meta.url), +}); +``` + +Creators: + +- `createUnitConfig(projectKey, { projectRoot, ...options })` +- `createIntConfig(projectKey, { projectRoot, ...options })` +- `createE2eConfig(projectKey, { projectRoot, ...options })` + +`projectKey` is used for cache and coverage directories. + +### Defaults + +Common to all kinds: + +- `reporters: ['basic']`, `globals: true`, `environment: 'node'` +- `alias: tsconfigPathAliases()` +- `pool: 'threads'` with `singleThread: true` +- Cache directories resolved from `projectRoot` (absolute paths) + +Coverage: + +- Unit/Int: enabled by default, reports to `/coverage//-tests` +- E2E: disabled by default +- Default exclude: `['mocks/**', '**/types.ts']` + +Global setup: + +- Unit/Int: `['/global-setup.ts']` by default +- E2E: none by default (set per-suite if needed) + +Include patterns: + +- Unit: `src/**/*.unit.test.*` +- Int: `src/**/*.int.test.*` +- E2E: `tests/**/*.e2e.test.*` + +### setupFiles strategy + +Baseline `setupFiles` are injected automatically by kind: + +- Unit baseline: `console.mock.ts`, `reset.mocks.ts` +- Int baseline: `console.mock.ts`, `reset.mocks.ts` +- E2E baseline: `reset.mocks.ts` + +Extend with additional files using `options.setupFiles` — they append after the baseline (paths are project-root-relative): + +```ts +export default createUnitConfig('core', { + projectRoot: new URL('../../', import.meta.url), + setupFiles: ['testing/test-setup/src/lib/cliui.mock.ts'], +}); +``` + +Replace entirely using `overrideSetupFiles: true` (paths are project-root-relative): + +```ts +export default createUnitConfig('core', { + projectRoot: new URL('../../', import.meta.url), + overrideSetupFiles: true, + setupFiles: ['testing/test-setup/src/lib/cliui.mock.ts', 'testing/test-setup/src/lib/fs.mock.ts'], +}); +``` + +### Using presets directly + +`vitest-setup-presets.ts` exposes grouped arrays you can compose if needed: + +```ts +import { setupPresets } from '../../tools/vitest-setup-presets.js'; + +export default createIntConfig('core', { + projectRoot: new URL('../../', import.meta.url), + setupFiles: [...setupPresets.int.portalClient], +}); +``` + +Preset keys: + +- `setupPresets.unit.{base,cliui,fs,git,portalClient,matchersCore,matcherPath}` +- `setupPresets.int.{base,cliui,fs,git,portalClient,matcherPath,chromePath}` +- `setupPresets.e2e.{base}` + +### Options reference + +`CreateVitestConfigOptions` (required + optional): + +- `projectKey` (string): coverage/cache naming. +- `kind` ('unit' | 'int' | 'e2e'): test kind. +- `projectRoot` (string | URL): absolute root for all paths. +- `include?: string[]`: override default include globs. +- `setupFiles?: string[]`: extra setup files (appended to baseline; project-root-relative). +- `overrideSetupFiles?: boolean`: skip baseline and use only provided list. +- `globalSetup?: string[]`: override default global setup (project-root-relative). +- `coverage?: { enabled?, exclude?, reportsSubdir? }` +- `testTimeout?: number`: e.g., for E2E. +- `typecheckInclude?: string[]`: include patterns for Vitest typecheck. +- `cacheKey?: string`: custom cache dir suffix. + +### Path and URL resolution + +- The factory requires `projectRoot` (string path or `URL`). +- Internally, it converts `projectRoot` into a `URL` and resolves all paths with `new URL(relativePath, projectRoot).pathname` to produce absolute filesystem paths. +- Affected fields: + - `cacheDir`, `test.cache.dir` + - `coverage.reportsDirectory` + - default `globalSetup` + - baseline `setupFiles` from presets and any extras you pass +- Expected inputs: + - `setupFiles` and `globalSetup` you pass should be project-root-relative strings. + - No `../../` paths are needed in configs; moving the factory won’t break resolution. + +### Merging behavior (arrays and overrides) + +- `setupFiles`: + - Baseline files (by kind) are injected automatically. + - Extras in `options.setupFiles` are appended after the baseline. + - Set `overrideSetupFiles: true` to replace the list entirely. +- `coverage.exclude`: + - Defaults to `['mocks/**', '**/types.ts']`. + - If you provide excludes, they are appended to the defaults. +- `include`, `globalSetup`, `testTimeout`, `typecheck.include`: + - If provided, they override the defaults for that suite. + +### Notes + +- Imports use `.js` extensions to work under ESM. +- No de-duplication of `setupFiles`. Avoid adding duplicates. +- You can opt-in to coverage for E2E by passing `coverage.enabled: true`. diff --git a/tools/vitest-config-factory.ts b/tools/vitest-config-factory.ts new file mode 100644 index 000000000..aa8b0c170 --- /dev/null +++ b/tools/vitest-config-factory.ts @@ -0,0 +1,151 @@ +/// +import { pathToFileURL } from 'node:url'; +import { defineConfig, mergeConfig } from 'vite'; +import type { UserConfig as ViteUserConfig } from 'vite'; +import type { CoverageOptions } from 'vitest'; +import { setupPresets } from './vitest-setup-presets.js'; +import { tsconfigPathAliases } from './vitest-tsconfig-path-aliases.js'; + +export type TestKind = 'unit' | 'int' | 'e2e'; + +export interface CreateVitestConfigOptions { + projectKey: string; + kind: TestKind; + projectRoot: string | URL; + include?: string[]; + setupFiles?: string[]; + /** If true, the factory will not inject the baseline setupFiles for the given kind. */ + overrideSetupFiles?: boolean; + globalSetup?: string[]; + coverage?: { + enabled?: boolean; + exclude?: string[]; + reportsSubdir?: string; + }; + testTimeout?: number; + typecheckInclude?: string[]; + cacheKey?: string; +} + +export function createVitestConfig( + options: CreateVitestConfigOptions, +): ViteUserConfig { + const projectRootUrl: URL = + typeof options.projectRoot === 'string' + ? pathToFileURL( + options.projectRoot.endsWith('/') + ? options.projectRoot + : options.projectRoot + '/', + ) + : options.projectRoot; + const cacheDirName = options.cacheKey ?? options.projectKey; + const reportsSubdir = + options.coverage?.reportsSubdir ?? `${options.kind}-tests`; + const coverageEnabled = options.coverage?.enabled ?? options.kind !== 'e2e'; + const defaultGlobalSetup = + options.kind === 'e2e' + ? undefined + : [new URL('global-setup.ts', projectRootUrl).pathname]; + + type VitestAwareUserConfig = ViteUserConfig & { test?: unknown }; + const baselineSetupByKind: Record = { + unit: setupPresets.unit.base, + int: setupPresets.int.base, + e2e: setupPresets.e2e.base, + } as const; + + const resolveFromRoot = (relativePath: string): string => + new URL(relativePath, projectRootUrl).pathname; + const mapToAbsolute = ( + paths: readonly string[] | undefined, + ): string[] | undefined => + paths == null ? paths : paths.map(resolveFromRoot); + + const defaultExclude = ['mocks/**', '**/types.ts']; + + const baselineSetupAbs = mapToAbsolute(baselineSetupByKind[options.kind])!; + const extraSetupAbs = mapToAbsolute(options.setupFiles) ?? []; + const finalSetupFiles = options.overrideSetupFiles + ? extraSetupAbs + : extraSetupAbs.length > 0 + ? [...baselineSetupAbs, ...extraSetupAbs] + : undefined; // let base keep baseline when no extras + + const baseConfig: VitestAwareUserConfig = { + cacheDir: new URL(`node_modules/.vite/${cacheDirName}`, projectRootUrl) + .pathname, + test: { + reporters: ['basic'], + globals: true, + cache: { dir: new URL('node_modules/.vitest', projectRootUrl).pathname }, + alias: tsconfigPathAliases(), + pool: 'threads', + poolOptions: { threads: { singleThread: true } }, + environment: 'node', + include: + options.kind === 'unit' + ? ['src/**/*.unit.test.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'] + : options.kind === 'int' + ? ['src/**/*.int.test.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'] + : ['tests/**/*.e2e.test.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], + globalSetup: defaultGlobalSetup, + setupFiles: baselineSetupAbs, + ...(coverageEnabled + ? { + coverage: { + reporter: ['text', 'lcov'], + reportsDirectory: new URL( + `coverage/${options.projectKey}/${reportsSubdir}`, + projectRootUrl, + ).pathname, + exclude: defaultExclude, + } as CoverageOptions, + } + : {}), + }, + }; + + const overrideConfig: VitestAwareUserConfig = { + test: { + ...(options.include ? { include: options.include } : {}), + ...(options.globalSetup + ? { globalSetup: mapToAbsolute(options.globalSetup) } + : {}), + ...(finalSetupFiles ? { setupFiles: finalSetupFiles } : {}), + ...(options.typecheckInclude + ? { typecheck: { include: options.typecheckInclude } } + : {}), + ...(options.testTimeout != null + ? { testTimeout: options.testTimeout } + : {}), + ...(coverageEnabled && options.coverage?.exclude + ? { + coverage: { + exclude: [...defaultExclude, ...options.coverage.exclude], + } as CoverageOptions, + } + : {}), + }, + }; + + const merged = mergeConfig( + baseConfig as ViteUserConfig, + overrideConfig as ViteUserConfig, + ); + return defineConfig(merged); +} + +export const createUnitConfig = ( + projectKey: string, + rest: Omit, +): ViteUserConfig => createVitestConfig({ projectKey, kind: 'unit', ...rest }); + +export const createIntConfig = ( + projectKey: string, + rest: Omit, +): ViteUserConfig => createVitestConfig({ projectKey, kind: 'int', ...rest }); + +export const createE2eConfig = ( + projectKey: string, + rest: Omit, +): ViteUserConfig => createVitestConfig({ projectKey, kind: 'e2e', ...rest }); diff --git a/tools/vitest-setup-presets.ts b/tools/vitest-setup-presets.ts new file mode 100644 index 000000000..4af8b329c --- /dev/null +++ b/tools/vitest-setup-presets.ts @@ -0,0 +1,33 @@ +export const setupPresets = { + unit: { + 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'], + matchersCore: [ + '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', + ], + matcherPath: ['testing/test-setup/src/lib/extend/path.matcher.ts'], + }, + int: { + 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'], + }, + e2e: { + base: ['testing/test-setup/src/lib/reset.mocks.ts'], + }, +} as const; From f15c31fee21244c74dae8cc90f4da402fbb66b25 Mon Sep 17 00:00:00 2001 From: "Szymon.Poltorak" Date: Fri, 5 Sep 2025 13:14:37 +0200 Subject: [PATCH 02/23] refactor(): wip use common vitest on few files --- e2e/ci-e2e/vitest.e2e.config.ts | 26 +++------- .../vitest.e2e.config.ts | 29 +++-------- packages/core/vitest.int.config.ts | 31 ++---------- packages/core/vitest.unit.config.ts | 45 +++++------------ packages/utils/vitest.int.config.ts | 32 ++---------- packages/utils/vitest.unit.config.ts | 49 ++++++------------- 6 files changed, 48 insertions(+), 164 deletions(-) diff --git a/e2e/ci-e2e/vitest.e2e.config.ts b/e2e/ci-e2e/vitest.e2e.config.ts index 90df62fed..21f998dcd 100644 --- a/e2e/ci-e2e/vitest.e2e.config.ts +++ b/e2e/ci-e2e/vitest.e2e.config.ts @@ -1,22 +1,10 @@ /// -import { defineConfig } from 'vitest/config'; -import { tsconfigPathAliases } from '../../tools/vitest-tsconfig-path-aliases.js'; +import { createE2eConfig } from '../../tools/vitest-config-factory.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', - }, - 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'], - }, +export default createE2eConfig('ci-e2e', { + projectRoot: new URL('../../', import.meta.url), + testTimeout: 60_000, + globalSetup: ['./global-setup.ts'], + coverage: { enabled: false }, + cacheKey: 'ci-e2e', }); diff --git a/e2e/plugin-typescript-e2e/vitest.e2e.config.ts b/e2e/plugin-typescript-e2e/vitest.e2e.config.ts index c34d55a36..62ddab384 100644 --- a/e2e/plugin-typescript-e2e/vitest.e2e.config.ts +++ b/e2e/plugin-typescript-e2e/vitest.e2e.config.ts @@ -1,26 +1,9 @@ /// -import { defineConfig } from 'vitest/config'; -import { tsconfigPathAliases } from '../../tools/vitest-tsconfig-path-aliases.js'; +import { createE2eConfig } from '../../tools/vitest-config-factory.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', - }, - environment: 'node', - include: ['tests/**/*.e2e.test.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], - setupFiles: ['../../testing/test-setup/src/lib/reset.mocks.ts'], - }, +export default createE2eConfig('plugin-typescript-e2e', { + projectRoot: new URL('../../', import.meta.url), + testTimeout: 20_000, + coverage: { enabled: true, reportsSubdir: 'e2e-tests' }, + cacheKey: 'plugin-typescript-e2e', }); diff --git a/packages/core/vitest.int.config.ts b/packages/core/vitest.int.config.ts index 7ff35029f..4d7b344ae 100644 --- a/packages/core/vitest.int.config.ts +++ b/packages/core/vitest.int.config.ts @@ -1,30 +1,7 @@ /// -import { defineConfig } from 'vitest/config'; -import { tsconfigPathAliases } from '../../tools/vitest-tsconfig-path-aliases.js'; +import { createIntConfig } from '../../tools/vitest-config-factory.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'], - }, - 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', - ], - }, +export default createIntConfig('core', { + projectRoot: new URL('../../', import.meta.url), + setupFiles: ['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 c46850c41..cd5cf0996 100644 --- a/packages/core/vitest.unit.config.ts +++ b/packages/core/vitest.unit.config.ts @@ -1,36 +1,15 @@ /// -import { defineConfig } from 'vitest/config'; -import { tsconfigPathAliases } from '../../tools/vitest-tsconfig-path-aliases.js'; +import { createUnitConfig } from '../../tools/vitest-config-factory.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'], - }, - 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', - ], - }, +export default createUnitConfig('core', { + projectRoot: new URL('../../', import.meta.url), + 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/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 b72908490..8b6b2ae1f 100644 --- a/packages/utils/vitest.int.config.ts +++ b/packages/utils/vitest.int.config.ts @@ -1,30 +1,8 @@ /// -import { defineConfig } from 'vitest/config'; -import { tsconfigPathAliases } from '../../tools/vitest-tsconfig-path-aliases.js'; +import { createIntConfig } from '../../tools/vitest-config-factory.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'], - }, - 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', - ], - }, +export default createIntConfig('utils', { + projectRoot: new URL('../../', import.meta.url), + setupFiles: ['testing/test-setup/src/lib/cliui.mock.ts'], + coverage: { exclude: ['perf/**'] }, }); diff --git a/packages/utils/vitest.unit.config.ts b/packages/utils/vitest.unit.config.ts index f55eb2326..295c96dc9 100644 --- a/packages/utils/vitest.unit.config.ts +++ b/packages/utils/vitest.unit.config.ts @@ -1,38 +1,17 @@ /// -import { defineConfig } from 'vitest/config'; -import { tsconfigPathAliases } from '../../tools/vitest-tsconfig-path-aliases.js'; +import { createUnitConfig } from '../../tools/vitest-config-factory.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'], - }, - 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', - ], - }, +export default createUnitConfig('utils', { + projectRoot: new URL('../../', import.meta.url), + include: ['src/**/*.{unit,type}.test.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], + typecheckInclude: ['**/*.type.test.ts'], + setupFiles: [ + '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', + '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', + ], + coverage: { exclude: ['perf/**'] }, }); From 597923d524b3082bf63a2571fdfeb1436cbddd4e Mon Sep 17 00:00:00 2001 From: "Szymon.Poltorak" Date: Fri, 5 Sep 2025 14:08:16 +0200 Subject: [PATCH 03/23] refactor(): change coverage reports directory --- .gitignore | 1 + e2e/plugin-typescript-e2e/vitest.e2e.config.ts | 2 +- tools/README.md | 6 +++--- tools/vitest-config-factory.ts | 7 +++---- 4 files changed, 8 insertions(+), 8 deletions(-) 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/plugin-typescript-e2e/vitest.e2e.config.ts b/e2e/plugin-typescript-e2e/vitest.e2e.config.ts index 62ddab384..75eabdab7 100644 --- a/e2e/plugin-typescript-e2e/vitest.e2e.config.ts +++ b/e2e/plugin-typescript-e2e/vitest.e2e.config.ts @@ -4,6 +4,6 @@ import { createE2eConfig } from '../../tools/vitest-config-factory.js'; export default createE2eConfig('plugin-typescript-e2e', { projectRoot: new URL('../../', import.meta.url), testTimeout: 20_000, - coverage: { enabled: true, reportsSubdir: 'e2e-tests' }, + coverage: { enabled: true }, cacheKey: 'plugin-typescript-e2e', }); diff --git a/tools/README.md b/tools/README.md index cd88d502f..e8fcd8875 100644 --- a/tools/README.md +++ b/tools/README.md @@ -45,8 +45,8 @@ Common to all kinds: Coverage: -- Unit/Int: enabled by default, reports to `/coverage//-tests` -- E2E: disabled by default +- Unit/Int: enabled by default, reports to `/packages//.coverage` +- E2E: disabled by default, reports to `/e2e//.coverage` if enabled - Default exclude: `['mocks/**', '**/types.ts']` Global setup: @@ -117,7 +117,7 @@ Preset keys: - `setupFiles?: string[]`: extra setup files (appended to baseline; project-root-relative). - `overrideSetupFiles?: boolean`: skip baseline and use only provided list. - `globalSetup?: string[]`: override default global setup (project-root-relative). -- `coverage?: { enabled?, exclude?, reportsSubdir? }` +- `coverage?: { enabled?, exclude? }` - `testTimeout?: number`: e.g., for E2E. - `typecheckInclude?: string[]`: include patterns for Vitest typecheck. - `cacheKey?: string`: custom cache dir suffix. diff --git a/tools/vitest-config-factory.ts b/tools/vitest-config-factory.ts index aa8b0c170..a4ec3b252 100644 --- a/tools/vitest-config-factory.ts +++ b/tools/vitest-config-factory.ts @@ -20,7 +20,6 @@ export interface CreateVitestConfigOptions { coverage?: { enabled?: boolean; exclude?: string[]; - reportsSubdir?: string; }; testTimeout?: number; typecheckInclude?: string[]; @@ -39,8 +38,6 @@ export function createVitestConfig( ) : options.projectRoot; const cacheDirName = options.cacheKey ?? options.projectKey; - const reportsSubdir = - options.coverage?.reportsSubdir ?? `${options.kind}-tests`; const coverageEnabled = options.coverage?.enabled ?? options.kind !== 'e2e'; const defaultGlobalSetup = options.kind === 'e2e' @@ -95,7 +92,9 @@ export function createVitestConfig( coverage: { reporter: ['text', 'lcov'], reportsDirectory: new URL( - `coverage/${options.projectKey}/${reportsSubdir}`, + options.kind === 'e2e' + ? `e2e/${options.projectKey}/.coverage` + : `packages/${options.projectKey}/.coverage`, projectRootUrl, ).pathname, exclude: defaultExclude, From 11337cfff630e1d0c7b94b7a340e20323d995f17 Mon Sep 17 00:00:00 2001 From: "Szymon.Poltorak" Date: Tue, 9 Sep 2025 10:25:32 +0200 Subject: [PATCH 04/23] refactor(): simplify vitest factory and split abstract logic --- e2e/ci-e2e/vitest.e2e.config.ts | 23 ++- .../vitest.e2e.config.ts | 21 +- packages/core/vitest.int.config.ts | 20 +- packages/core/vitest.unit.config.ts | 33 +-- packages/utils/vitest.int.config.ts | 22 +- packages/utils/vitest.unit.config.ts | 37 ++-- testing/test-setup/src/lib/config/README.md | 71 +++++++ .../src/lib/config/vitest-config-factory.ts | 195 ++++++++++++++++++ .../src/lib/config/vitest-setup-presets.ts | 84 ++++++++ tools/README.md | 154 -------------- tools/vitest-config-factory.ts | 150 -------------- tools/vitest-setup-presets.ts | 33 --- tools/vitest-tsconfig-path-aliases.ts | 7 +- 13 files changed, 457 insertions(+), 393 deletions(-) create mode 100644 testing/test-setup/src/lib/config/README.md create mode 100644 testing/test-setup/src/lib/config/vitest-config-factory.ts create mode 100644 testing/test-setup/src/lib/config/vitest-setup-presets.ts delete mode 100644 tools/README.md delete mode 100644 tools/vitest-config-factory.ts delete mode 100644 tools/vitest-setup-presets.ts diff --git a/e2e/ci-e2e/vitest.e2e.config.ts b/e2e/ci-e2e/vitest.e2e.config.ts index 21f998dcd..730db91cf 100644 --- a/e2e/ci-e2e/vitest.e2e.config.ts +++ b/e2e/ci-e2e/vitest.e2e.config.ts @@ -1,10 +1,17 @@ /// -import { createE2eConfig } from '../../tools/vitest-config-factory.js'; +import { createE2eConfig } from '../../testing/test-setup/src/lib/config/vitest-setup-presets.js'; -export default createE2eConfig('ci-e2e', { - projectRoot: new URL('../../', import.meta.url), - testTimeout: 60_000, - globalSetup: ['./global-setup.ts'], - coverage: { enabled: false }, - cacheKey: 'ci-e2e', -}); +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 }, + }, + }, +); diff --git a/e2e/plugin-typescript-e2e/vitest.e2e.config.ts b/e2e/plugin-typescript-e2e/vitest.e2e.config.ts index 75eabdab7..a9a75bb81 100644 --- a/e2e/plugin-typescript-e2e/vitest.e2e.config.ts +++ b/e2e/plugin-typescript-e2e/vitest.e2e.config.ts @@ -1,9 +1,16 @@ /// -import { createE2eConfig } from '../../tools/vitest-config-factory.js'; +import { createE2eConfig } from '../../testing/test-setup/src/lib/config/vitest-setup-presets.js'; -export default createE2eConfig('plugin-typescript-e2e', { - projectRoot: new URL('../../', import.meta.url), - testTimeout: 20_000, - coverage: { enabled: true }, - cacheKey: 'plugin-typescript-e2e', -}); +export default createE2eConfig( + 'plugin-typescript-e2e', + { + projectRoot: new URL('../../', import.meta.url), + cacheKey: 'plugin-typescript-e2e', + }, + { + test: { + testTimeout: 20_000, + coverage: { enabled: true }, + }, + }, +); diff --git a/packages/core/vitest.int.config.ts b/packages/core/vitest.int.config.ts index 4d7b344ae..b32e9afd9 100644 --- a/packages/core/vitest.int.config.ts +++ b/packages/core/vitest.int.config.ts @@ -1,7 +1,17 @@ /// -import { createIntConfig } from '../../tools/vitest-config-factory.js'; +import { + createIntConfig, + setupPresets, +} from '../../testing/test-setup/src/lib/config/vitest-setup-presets.js'; -export default createIntConfig('core', { - projectRoot: new URL('../../', import.meta.url), - setupFiles: ['testing/test-setup/src/lib/portal-client.mock.ts'], -}); +export default createIntConfig( + 'core', + { + projectRoot: new URL('../../', import.meta.url), + }, + { + test: { + setupFiles: [...setupPresets.int.base, ...setupPresets.int.portalClient], + }, + }, +); diff --git a/packages/core/vitest.unit.config.ts b/packages/core/vitest.unit.config.ts index cd5cf0996..09b28c623 100644 --- a/packages/core/vitest.unit.config.ts +++ b/packages/core/vitest.unit.config.ts @@ -1,15 +1,22 @@ /// -import { createUnitConfig } from '../../tools/vitest-config-factory.js'; +import { + createUnitConfig, + setupPresets, +} from '../../testing/test-setup/src/lib/config/vitest-setup-presets.js'; -export default createUnitConfig('core', { - projectRoot: new URL('../../', import.meta.url), - 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/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', - ], -}); +export default createUnitConfig( + 'core', + { + projectRoot: new URL('../../', import.meta.url), + }, + { + test: { + setupFiles: [ + ...setupPresets.unit.base, + ...setupPresets.unit.git, + ...setupPresets.unit.portalClient, + ...setupPresets.unit.matchersCore, + ], + }, + }, +); diff --git a/packages/utils/vitest.int.config.ts b/packages/utils/vitest.int.config.ts index 8b6b2ae1f..0f75d8b29 100644 --- a/packages/utils/vitest.int.config.ts +++ b/packages/utils/vitest.int.config.ts @@ -1,8 +1,18 @@ /// -import { createIntConfig } from '../../tools/vitest-config-factory.js'; +import { + createIntConfig, + setupPresets, +} from '../../testing/test-setup/src/lib/config/vitest-setup-presets.js'; -export default createIntConfig('utils', { - projectRoot: new URL('../../', import.meta.url), - setupFiles: ['testing/test-setup/src/lib/cliui.mock.ts'], - coverage: { exclude: ['perf/**'] }, -}); +export default createIntConfig( + 'utils', + { + projectRoot: new URL('../../', import.meta.url), + }, + { + test: { + coverage: { exclude: ['perf/**'] }, + setupFiles: [...setupPresets.int.base, ...setupPresets.int.cliui], + }, + }, +); diff --git a/packages/utils/vitest.unit.config.ts b/packages/utils/vitest.unit.config.ts index 295c96dc9..2030ba17d 100644 --- a/packages/utils/vitest.unit.config.ts +++ b/packages/utils/vitest.unit.config.ts @@ -1,17 +1,24 @@ /// -import { createUnitConfig } from '../../tools/vitest-config-factory.js'; +import { + createUnitConfig, + setupPresets, +} from '../../testing/test-setup/src/lib/config/vitest-setup-presets.js'; -export default createUnitConfig('utils', { - projectRoot: new URL('../../', import.meta.url), - include: ['src/**/*.{unit,type}.test.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], - typecheckInclude: ['**/*.type.test.ts'], - setupFiles: [ - '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', - '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', - ], - coverage: { exclude: ['perf/**'] }, -}); +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, + ], + }, + }, +); diff --git a/testing/test-setup/src/lib/config/README.md b/testing/test-setup/src/lib/config/README.md new file mode 100644 index 000000000..ce364a69c --- /dev/null +++ b/testing/test-setup/src/lib/config/README.md @@ -0,0 +1,71 @@ +## Vitest config factory and setup presets + +This folder contains utilities to centralize and standardize Vitest configuration across the monorepo. + +### Files + +- `vitest-config-factory.ts`: builds typed Vitest configs with sensible defaults. +- `vitest-setup-presets.ts`: provides create functions and exportable setup file groups. + +### Goals + +- Reduce duplication across `vitest.*.config.ts` files. +- Automatically include common setup files for each test type. +- Allow easy extension when additional setup files are needed. + +### How it works + +The create functions (`createUnitConfig`, `createIntConfig`, `createE2eConfig`) automatically include base setup files appropriate for each test type. If you need additional or different setup files, provide them in the test overrides and they will be used instead. + +### Defaults + +Common to all kinds: + +- `reporters: ['basic']`, `globals: true`, `environment: 'node'` +- `alias: tsconfigPathAliases()` +- `pool: 'threads'` with `singleThread: true` +- Cache directories resolved from `projectRoot` (absolute paths) + +Coverage: + +- Unit/Int: enabled by default, reports to `/packages//.coverage` +- E2E: disabled by default, reports to `/e2e//.coverage` if enabled +- Default exclude: `['mocks/**', '**/types.ts']` + +Global setup: + +- Unit/Int: `['/global-setup.ts']` by default +- E2E: none by default (set per-suite if needed) + +Include patterns: + +- Unit: `src/**/*.unit.test.*` +- Int: `src/**/*.int.test.*` +- E2E: `tests/**/*.e2e.test.*` + +### Setup files behavior + +**Automatic inclusion**: Each test type automatically includes its base setup files: + +- Unit tests: console mocking, cleanup, common UI/filesystem mocks, and basic matchers +- Integration tests: console mocking and cleanup only +- E2E tests: cleanup only + +**Custom setup files**: To use additional or different setup files, provide them in the test configuration overrides. The exported `setupPresets` object contains grouped setup files that can be combined as needed. + +**Available setup file groups**: + +- `setupPresets.unit.{base, git, portalClient, matchersCore, matcherPath}` +- `setupPresets.int.{base, cliui, fs, git, portalClient, matcherPath, chromePath}` +- `setupPresets.e2e.{base}` + +### Key parameters + +- `projectKey`: Used for cache and coverage directory naming +- `projectRoot`: Required path/URL to the project root for resolving all paths +- Standard Vitest configuration options can be provided in the overrides parameter + +### Notes + +- Coverage is enabled by default for unit/int tests, disabled by default for E2E tests +- All path resolution is handled automatically relative to the provided `projectRoot` diff --git a/testing/test-setup/src/lib/config/vitest-config-factory.ts b/testing/test-setup/src/lib/config/vitest-config-factory.ts new file mode 100644 index 000000000..b7c397da3 --- /dev/null +++ b/testing/test-setup/src/lib/config/vitest-config-factory.ts @@ -0,0 +1,195 @@ +import { pathToFileURL } from 'node:url'; +import { + type UserConfig as ViteUserConfig, + defineConfig, + mergeConfig, +} from 'vite'; +import type { CoverageOptions, InlineConfig } from 'vitest'; +import { tsconfigPathAliases } from '../../../../../tools/vitest-tsconfig-path-aliases.js'; + +export type TestKind = 'unit' | 'int' | 'e2e'; + +export type VitestConfigFactoryOptions = { + projectKey: string; + kind: TestKind; + projectRoot: string | URL; + 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 ?? 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 => new URL(p, projectRootUrl).pathname) + : []; +} + +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 + : [new URL('global-setup.ts', projectRootUrl).pathname]; +} + +function buildCoverageConfig(params: { + projectKey: string; + kind: TestKind; + projectRootUrl: URL; + overrideExclude?: string[]; +}): CoverageOptions { + const defaultExclude = ['mocks/**', '**/types.ts']; + const reportsDirectory = new URL( + params.kind === 'e2e' + ? `e2e/${params.projectKey}/.coverage` + : `packages/${params.projectKey}/.coverage/${params.kind}-tests`, + params.projectRootUrl, + ).pathname; + 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: new URL( + `node_modules/.vite/${params.cacheDirName}`, + params.projectRootUrl, + ).pathname, + test: { + reporters: ['basic'], + globals: true, + cache: { + dir: new URL('node_modules/.vitest', params.projectRootUrl).pathname, + }, + 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 }; +} diff --git a/testing/test-setup/src/lib/config/vitest-setup-presets.ts b/testing/test-setup/src/lib/config/vitest-setup-presets.ts new file mode 100644 index 000000000..d887747fa --- /dev/null +++ b/testing/test-setup/src/lib/config/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/tools/README.md b/tools/README.md deleted file mode 100644 index e8fcd8875..000000000 --- a/tools/README.md +++ /dev/null @@ -1,154 +0,0 @@ -## Vitest config factory and setup presets - -This folder contains utilities to centralize and standardize Vitest configuration across the monorepo. - -### Files - -- `vitest-config-factory.ts`: builds typed Vitest configs with sensible defaults. -- `vitest-setup-presets.ts`: reusable groups of `setupFiles` paths by test kind. - -### Goals - -- Reduce duplication across `vitest.*.config.ts` files. -- Keep per-package intent clear with minimal overrides. -- Provide safe defaults and easy extension points. - -### Quick start - -Use the factory in your suite configs (project root is required): - -```ts -/// -import { createE2eConfig, createIntConfig, createUnitConfig } from '../../tools/vitest-config-factory.js'; - -export default createUnitConfig('core', { - projectRoot: new URL('../../', import.meta.url), -}); -``` - -Creators: - -- `createUnitConfig(projectKey, { projectRoot, ...options })` -- `createIntConfig(projectKey, { projectRoot, ...options })` -- `createE2eConfig(projectKey, { projectRoot, ...options })` - -`projectKey` is used for cache and coverage directories. - -### Defaults - -Common to all kinds: - -- `reporters: ['basic']`, `globals: true`, `environment: 'node'` -- `alias: tsconfigPathAliases()` -- `pool: 'threads'` with `singleThread: true` -- Cache directories resolved from `projectRoot` (absolute paths) - -Coverage: - -- Unit/Int: enabled by default, reports to `/packages//.coverage` -- E2E: disabled by default, reports to `/e2e//.coverage` if enabled -- Default exclude: `['mocks/**', '**/types.ts']` - -Global setup: - -- Unit/Int: `['/global-setup.ts']` by default -- E2E: none by default (set per-suite if needed) - -Include patterns: - -- Unit: `src/**/*.unit.test.*` -- Int: `src/**/*.int.test.*` -- E2E: `tests/**/*.e2e.test.*` - -### setupFiles strategy - -Baseline `setupFiles` are injected automatically by kind: - -- Unit baseline: `console.mock.ts`, `reset.mocks.ts` -- Int baseline: `console.mock.ts`, `reset.mocks.ts` -- E2E baseline: `reset.mocks.ts` - -Extend with additional files using `options.setupFiles` — they append after the baseline (paths are project-root-relative): - -```ts -export default createUnitConfig('core', { - projectRoot: new URL('../../', import.meta.url), - setupFiles: ['testing/test-setup/src/lib/cliui.mock.ts'], -}); -``` - -Replace entirely using `overrideSetupFiles: true` (paths are project-root-relative): - -```ts -export default createUnitConfig('core', { - projectRoot: new URL('../../', import.meta.url), - overrideSetupFiles: true, - setupFiles: ['testing/test-setup/src/lib/cliui.mock.ts', 'testing/test-setup/src/lib/fs.mock.ts'], -}); -``` - -### Using presets directly - -`vitest-setup-presets.ts` exposes grouped arrays you can compose if needed: - -```ts -import { setupPresets } from '../../tools/vitest-setup-presets.js'; - -export default createIntConfig('core', { - projectRoot: new URL('../../', import.meta.url), - setupFiles: [...setupPresets.int.portalClient], -}); -``` - -Preset keys: - -- `setupPresets.unit.{base,cliui,fs,git,portalClient,matchersCore,matcherPath}` -- `setupPresets.int.{base,cliui,fs,git,portalClient,matcherPath,chromePath}` -- `setupPresets.e2e.{base}` - -### Options reference - -`CreateVitestConfigOptions` (required + optional): - -- `projectKey` (string): coverage/cache naming. -- `kind` ('unit' | 'int' | 'e2e'): test kind. -- `projectRoot` (string | URL): absolute root for all paths. -- `include?: string[]`: override default include globs. -- `setupFiles?: string[]`: extra setup files (appended to baseline; project-root-relative). -- `overrideSetupFiles?: boolean`: skip baseline and use only provided list. -- `globalSetup?: string[]`: override default global setup (project-root-relative). -- `coverage?: { enabled?, exclude? }` -- `testTimeout?: number`: e.g., for E2E. -- `typecheckInclude?: string[]`: include patterns for Vitest typecheck. -- `cacheKey?: string`: custom cache dir suffix. - -### Path and URL resolution - -- The factory requires `projectRoot` (string path or `URL`). -- Internally, it converts `projectRoot` into a `URL` and resolves all paths with `new URL(relativePath, projectRoot).pathname` to produce absolute filesystem paths. -- Affected fields: - - `cacheDir`, `test.cache.dir` - - `coverage.reportsDirectory` - - default `globalSetup` - - baseline `setupFiles` from presets and any extras you pass -- Expected inputs: - - `setupFiles` and `globalSetup` you pass should be project-root-relative strings. - - No `../../` paths are needed in configs; moving the factory won’t break resolution. - -### Merging behavior (arrays and overrides) - -- `setupFiles`: - - Baseline files (by kind) are injected automatically. - - Extras in `options.setupFiles` are appended after the baseline. - - Set `overrideSetupFiles: true` to replace the list entirely. -- `coverage.exclude`: - - Defaults to `['mocks/**', '**/types.ts']`. - - If you provide excludes, they are appended to the defaults. -- `include`, `globalSetup`, `testTimeout`, `typecheck.include`: - - If provided, they override the defaults for that suite. - -### Notes - -- Imports use `.js` extensions to work under ESM. -- No de-duplication of `setupFiles`. Avoid adding duplicates. -- You can opt-in to coverage for E2E by passing `coverage.enabled: true`. diff --git a/tools/vitest-config-factory.ts b/tools/vitest-config-factory.ts deleted file mode 100644 index a4ec3b252..000000000 --- a/tools/vitest-config-factory.ts +++ /dev/null @@ -1,150 +0,0 @@ -/// -import { pathToFileURL } from 'node:url'; -import { defineConfig, mergeConfig } from 'vite'; -import type { UserConfig as ViteUserConfig } from 'vite'; -import type { CoverageOptions } from 'vitest'; -import { setupPresets } from './vitest-setup-presets.js'; -import { tsconfigPathAliases } from './vitest-tsconfig-path-aliases.js'; - -export type TestKind = 'unit' | 'int' | 'e2e'; - -export interface CreateVitestConfigOptions { - projectKey: string; - kind: TestKind; - projectRoot: string | URL; - include?: string[]; - setupFiles?: string[]; - /** If true, the factory will not inject the baseline setupFiles for the given kind. */ - overrideSetupFiles?: boolean; - globalSetup?: string[]; - coverage?: { - enabled?: boolean; - exclude?: string[]; - }; - testTimeout?: number; - typecheckInclude?: string[]; - cacheKey?: string; -} - -export function createVitestConfig( - options: CreateVitestConfigOptions, -): ViteUserConfig { - const projectRootUrl: URL = - typeof options.projectRoot === 'string' - ? pathToFileURL( - options.projectRoot.endsWith('/') - ? options.projectRoot - : options.projectRoot + '/', - ) - : options.projectRoot; - const cacheDirName = options.cacheKey ?? options.projectKey; - const coverageEnabled = options.coverage?.enabled ?? options.kind !== 'e2e'; - const defaultGlobalSetup = - options.kind === 'e2e' - ? undefined - : [new URL('global-setup.ts', projectRootUrl).pathname]; - - type VitestAwareUserConfig = ViteUserConfig & { test?: unknown }; - const baselineSetupByKind: Record = { - unit: setupPresets.unit.base, - int: setupPresets.int.base, - e2e: setupPresets.e2e.base, - } as const; - - const resolveFromRoot = (relativePath: string): string => - new URL(relativePath, projectRootUrl).pathname; - const mapToAbsolute = ( - paths: readonly string[] | undefined, - ): string[] | undefined => - paths == null ? paths : paths.map(resolveFromRoot); - - const defaultExclude = ['mocks/**', '**/types.ts']; - - const baselineSetupAbs = mapToAbsolute(baselineSetupByKind[options.kind])!; - const extraSetupAbs = mapToAbsolute(options.setupFiles) ?? []; - const finalSetupFiles = options.overrideSetupFiles - ? extraSetupAbs - : extraSetupAbs.length > 0 - ? [...baselineSetupAbs, ...extraSetupAbs] - : undefined; // let base keep baseline when no extras - - const baseConfig: VitestAwareUserConfig = { - cacheDir: new URL(`node_modules/.vite/${cacheDirName}`, projectRootUrl) - .pathname, - test: { - reporters: ['basic'], - globals: true, - cache: { dir: new URL('node_modules/.vitest', projectRootUrl).pathname }, - alias: tsconfigPathAliases(), - pool: 'threads', - poolOptions: { threads: { singleThread: true } }, - environment: 'node', - include: - options.kind === 'unit' - ? ['src/**/*.unit.test.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'] - : options.kind === 'int' - ? ['src/**/*.int.test.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'] - : ['tests/**/*.e2e.test.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], - globalSetup: defaultGlobalSetup, - setupFiles: baselineSetupAbs, - ...(coverageEnabled - ? { - coverage: { - reporter: ['text', 'lcov'], - reportsDirectory: new URL( - options.kind === 'e2e' - ? `e2e/${options.projectKey}/.coverage` - : `packages/${options.projectKey}/.coverage`, - projectRootUrl, - ).pathname, - exclude: defaultExclude, - } as CoverageOptions, - } - : {}), - }, - }; - - const overrideConfig: VitestAwareUserConfig = { - test: { - ...(options.include ? { include: options.include } : {}), - ...(options.globalSetup - ? { globalSetup: mapToAbsolute(options.globalSetup) } - : {}), - ...(finalSetupFiles ? { setupFiles: finalSetupFiles } : {}), - ...(options.typecheckInclude - ? { typecheck: { include: options.typecheckInclude } } - : {}), - ...(options.testTimeout != null - ? { testTimeout: options.testTimeout } - : {}), - ...(coverageEnabled && options.coverage?.exclude - ? { - coverage: { - exclude: [...defaultExclude, ...options.coverage.exclude], - } as CoverageOptions, - } - : {}), - }, - }; - - const merged = mergeConfig( - baseConfig as ViteUserConfig, - overrideConfig as ViteUserConfig, - ); - return defineConfig(merged); -} - -export const createUnitConfig = ( - projectKey: string, - rest: Omit, -): ViteUserConfig => createVitestConfig({ projectKey, kind: 'unit', ...rest }); - -export const createIntConfig = ( - projectKey: string, - rest: Omit, -): ViteUserConfig => createVitestConfig({ projectKey, kind: 'int', ...rest }); - -export const createE2eConfig = ( - projectKey: string, - rest: Omit, -): ViteUserConfig => createVitestConfig({ projectKey, kind: 'e2e', ...rest }); diff --git a/tools/vitest-setup-presets.ts b/tools/vitest-setup-presets.ts deleted file mode 100644 index 4af8b329c..000000000 --- a/tools/vitest-setup-presets.ts +++ /dev/null @@ -1,33 +0,0 @@ -export const setupPresets = { - unit: { - 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'], - matchersCore: [ - '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', - ], - matcherPath: ['testing/test-setup/src/lib/extend/path.matcher.ts'], - }, - int: { - 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'], - }, - e2e: { - base: ['testing/test-setup/src/lib/reset.mocks.ts'], - }, -} as const; diff --git a/tools/vitest-tsconfig-path-aliases.ts b/tools/vitest-tsconfig-path-aliases.ts index ac8be04df..44d3353d0 100644 --- a/tools/vitest-tsconfig-path-aliases.ts +++ b/tools/vitest-tsconfig-path-aliases.ts @@ -1,8 +1,11 @@ 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 + ? new URL('tsconfig.base.json', projectRootUrl).pathname + : '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}`, From c31e21a369b7b48d4e88c6677e1840a1d135fa80 Mon Sep 17 00:00:00 2001 From: "Szymon.Poltorak" Date: Tue, 9 Sep 2025 12:00:47 +0200 Subject: [PATCH 05/23] docs(): update new tools readme --- testing/test-setup/README.md | 4 ++ testing/test-setup/src/lib/config/README.md | 73 ++++++++------------- 2 files changed, 30 insertions(+), 47 deletions(-) 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/testing/test-setup/src/lib/config/README.md b/testing/test-setup/src/lib/config/README.md index ce364a69c..730c625a0 100644 --- a/testing/test-setup/src/lib/config/README.md +++ b/testing/test-setup/src/lib/config/README.md @@ -1,71 +1,50 @@ ## Vitest config factory and setup presets -This folder contains utilities to centralize and standardize Vitest configuration across the monorepo. +Utilities to centralize and standardize Vitest configuration across the monorepo. -### Files +- `vitest-config-factory.ts`: builds typed Vitest configs with sensible defaults +- `vitest-setup-presets.ts`: provides create functions and exportable setup file groups -- `vitest-config-factory.ts`: builds typed Vitest configs with sensible defaults. -- `vitest-setup-presets.ts`: provides create functions and exportable setup file groups. - -### Goals - -- Reduce duplication across `vitest.*.config.ts` files. -- Automatically include common setup files for each test type. -- Allow easy extension when additional setup files are needed. - -### How it works - -The create functions (`createUnitConfig`, `createIntConfig`, `createE2eConfig`) automatically include base setup files appropriate for each test type. If you need additional or different setup files, provide them in the test overrides and they will be used instead. +The create functions (`createUnitConfig`, `createIntConfig`, `createE2eConfig`) automatically include appropriate setup files for each test type. ### Defaults -Common to all kinds: - -- `reporters: ['basic']`, `globals: true`, `environment: 'node'` -- `alias: tsconfigPathAliases()` -- `pool: 'threads'` with `singleThread: true` -- Cache directories resolved from `projectRoot` (absolute paths) - -Coverage: - -- Unit/Int: enabled by default, reports to `/packages//.coverage` -- E2E: disabled by default, reports to `/e2e//.coverage` if enabled -- Default exclude: `['mocks/**', '**/types.ts']` +**Common**: `reporters: ['basic']`, `globals: true`, `environment: 'node'`, `pool: 'threads'` with `singleThread: true`, alias from tsconfig paths -Global setup: +**Coverage**: Unit/Int enabled (reports to `/packages//.coverage`), E2E disabled. Excludes `['mocks/**', '**/types.ts']` -- Unit/Int: `['/global-setup.ts']` by default -- E2E: none by default (set per-suite if needed) +**Global setup**: Unit/Int use `['/global-setup.ts']`, E2E none by default -Include patterns: +**Include patterns**: Unit `src/**/*.unit.test.*`, Int `src/**/*.int.test.*`, E2E `tests/**/*.e2e.test.*` -- Unit: `src/**/*.unit.test.*` -- Int: `src/**/*.int.test.*` -- E2E: `tests/**/*.e2e.test.*` +### Setup files -### Setup files behavior +**Automatic inclusion**: Unit (console mocking, cleanup, UI/filesystem mocks, basic matchers), Int (console mocking, cleanup), E2E (cleanup only) -**Automatic inclusion**: Each test type automatically includes its base setup files: - -- Unit tests: console mocking, cleanup, common UI/filesystem mocks, and basic matchers -- Integration tests: console mocking and cleanup only -- E2E tests: cleanup only - -**Custom setup files**: To use additional or different setup files, provide them in the test configuration overrides. The exported `setupPresets` object contains grouped setup files that can be combined as needed. - -**Available setup file groups**: +**Custom setup files**: ⚠️ Specifying `setupFiles` in overrides will completely replace the defaults. To extend the default list, manually combine them with `setupPresets`: - `setupPresets.unit.{base, git, portalClient, matchersCore, matcherPath}` - `setupPresets.int.{base, cliui, fs, git, portalClient, matcherPath, chromePath}` - `setupPresets.e2e.{base}` -### Key parameters +### Parameters - `projectKey`: Used for cache and coverage directory naming - `projectRoot`: Required path/URL to the project root for resolving all paths - Standard Vitest configuration options can be provided in the overrides parameter -### Notes +### Examples + +**Using defaults:** + +```ts +export default createUnitConfig('my-package', import.meta.url); +``` + +**Extending default setup files:** -- Coverage is enabled by default for unit/int tests, disabled by default for E2E tests -- All path resolution is handled automatically relative to the provided `projectRoot` +```ts +export default createIntConfig('my-package', import.meta.url, { + setupFiles: [...setupPresets.int.base, ...setupPresets.int.git, './custom-setup.ts'], +}); +``` From e29df016e64c0e67e68b729865e81a101e943b00 Mon Sep 17 00:00:00 2001 From: "Szymon.Poltorak" Date: Tue, 9 Sep 2025 12:01:03 +0200 Subject: [PATCH 06/23] test(): add unit tests for new tools --- .../config/vitest-config-factory.unit.test.ts | 699 ++++++++++++++++++ .../config/vitest-setup-presets.unit.test.ts | 331 +++++++++ 2 files changed, 1030 insertions(+) create mode 100644 testing/test-setup/src/lib/config/vitest-config-factory.unit.test.ts create mode 100644 testing/test-setup/src/lib/config/vitest-setup-presets.unit.test.ts diff --git a/testing/test-setup/src/lib/config/vitest-config-factory.unit.test.ts b/testing/test-setup/src/lib/config/vitest-config-factory.unit.test.ts new file mode 100644 index 000000000..767f82314 --- /dev/null +++ b/testing/test-setup/src/lib/config/vitest-config-factory.unit.test.ts @@ -0,0 +1,699 @@ +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, +} 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('../../../../../tools/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}/`); + +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: `${MOCK_PROJECT_ROOT_STRING}/node_modules/.vite/test-package`, + test: expect.objectContaining({ + reporters: ['basic'], + globals: true, + cache: { + dir: `${MOCK_PROJECT_ROOT_STRING}/node_modules/.vitest`, + }, + alias: expect.any(Object), + pool: 'threads', + poolOptions: { threads: { singleThread: true } }, + environment: 'node', + include: EXPECTED_INCLUDES.unit, + globalSetup: [`${MOCK_PROJECT_ROOT_STRING}/global-setup.ts`], + setupFiles: [], + coverage: expect.objectContaining({ + reporter: ['text', 'lcov'], + reportsDirectory: `${MOCK_PROJECT_ROOT_STRING}/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: `${MOCK_PROJECT_ROOT_STRING}/node_modules/.vite/test-package`, + test: expect.objectContaining({ + include: EXPECTED_INCLUDES.unit, + globalSetup: [`${MOCK_PROJECT_ROOT_STRING}/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: [`${MOCK_PROJECT_ROOT_STRING}/global-setup.ts`], + coverage: expect.objectContaining({ + reportsDirectory: `${MOCK_PROJECT_ROOT_STRING}/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: `${MOCK_PROJECT_ROOT_STRING}/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: `${MOCK_PROJECT_ROOT_STRING}/node_modules/.vite/custom-cache-key`, + }), + ); + }); + + it('should fallback to 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: `${MOCK_PROJECT_ROOT_STRING}/node_modules/.vite/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: [`${MOCK_PROJECT_ROOT_STRING}/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: [ + `${MOCK_PROJECT_ROOT_STRING}/setup1.ts`, + `${MOCK_PROJECT_ROOT_STRING}/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: [ + `${MOCK_PROJECT_ROOT_STRING}/setup1.ts`, + `${MOCK_PROJECT_ROOT_STRING}/setup2.ts`, + `${MOCK_PROJECT_ROOT_STRING}/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: `${MOCK_PROJECT_ROOT_STRING}/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: `${MOCK_PROJECT_ROOT_STRING}/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: `${MOCK_PROJECT_ROOT_STRING}/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([ + `${MOCK_PROJECT_ROOT_STRING}/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: `${MOCK_PROJECT_ROOT_STRING}/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: `${MOCK_PROJECT_ROOT_STRING}/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 + : [`${MOCK_PROJECT_ROOT_STRING}/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: `${MOCK_PROJECT_ROOT_STRING}/node_modules/.vite/complex-scenario`, + build: { + target: 'es2020', + }, + test: expect.objectContaining({ + setupFiles: [ + `${MOCK_PROJECT_ROOT_STRING}/setup1.ts`, + `${MOCK_PROJECT_ROOT_STRING}/setup2.ts`, + ], + testTimeout: TEST_TIMEOUTS.LONG, + environment: 'jsdom', + include: EXPECTED_INCLUDES.int, + coverage: expect.objectContaining({ + exclude: ['mocks/**', '**/types.ts', 'e2e/**', 'dist/**'], + reportsDirectory: `${MOCK_PROJECT_ROOT_STRING}/packages/test-package/.coverage/int-tests`, + }), + }), + }), + ); + }); + }); +}); diff --git a/testing/test-setup/src/lib/config/vitest-setup-presets.unit.test.ts b/testing/test-setup/src/lib/config/vitest-setup-presets.unit.test.ts new file mode 100644 index 000000000..8051a7588 --- /dev/null +++ b/testing/test-setup/src/lib/config/vitest-setup-presets.unit.test.ts @@ -0,0 +1,331 @@ +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'); + }); +}); + +// Parameterized tests to eliminate duplication +describe.each([ + ['unit', setupPresets.unit.base, createUnitConfig], + ['int', setupPresets.int.base, createIntConfig], + ['e2e', setupPresets.e2e.base, createE2eConfig], +] as const)('%s config creation', (kind, baseSetupFiles, createFn) => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('should call createVitestConfig with correct parameters and default setupFiles', () => { + createFn(MOCK_PROJECT_KEY, MOCK_CONFIG_REST_PARAMS); + + expect(configFactory.createVitestConfig).toHaveBeenCalledWith( + { + projectKey: MOCK_PROJECT_KEY, + kind, + ...MOCK_CONFIG_REST_PARAMS, + }, + { + test: { + setupFiles: baseSetupFiles, + }, + }, + ); + }); + + it('should use custom setupFiles from overrides when provided', () => { + const customSetupFiles = [`${kind}-setup1.ts`, `${kind}-setup2.ts`]; + const overrides: VitestOverrides = { + test: { + setupFiles: customSetupFiles, + }, + }; + + createFn(MOCK_PROJECT_KEY, MOCK_CONFIG_REST_PARAMS, overrides); + + expect(configFactory.createVitestConfig).toHaveBeenCalledWith( + { + projectKey: MOCK_PROJECT_KEY, + kind, + ...MOCK_CONFIG_REST_PARAMS, + }, + { + test: { + setupFiles: customSetupFiles, + }, + }, + ); + }); + + it('should merge other overrides correctly while using default setupFiles', () => { + const testTimeout = + kind === 'unit' + ? TEST_TIMEOUTS.MEDIUM + : kind === 'int' + ? TEST_TIMEOUTS.LONG + : TEST_TIMEOUTS.E2E; + + const overrides: VitestOverrides = { + test: { + testTimeout, + globals: false, + }, + build: { + target: 'es2020', + }, + }; + + createFn(MOCK_PROJECT_KEY, MOCK_CONFIG_REST_PARAMS, overrides); + + expect(configFactory.createVitestConfig).toHaveBeenCalledWith( + { + projectKey: MOCK_PROJECT_KEY, + kind, + ...MOCK_CONFIG_REST_PARAMS, + }, + { + test: { + testTimeout, + globals: false, + setupFiles: baseSetupFiles, + }, + build: { + target: 'es2020', + }, + }, + ); + }); + + it('should handle overrides with custom setupFiles and other test options', () => { + const customSetupFiles = [`${kind}-custom.ts`]; + const overrides: VitestOverrides = { + test: { + setupFiles: customSetupFiles, + testTimeout: TEST_TIMEOUTS.SHORT, + environment: 'jsdom' as any, + }, + }; + + createFn(MOCK_PROJECT_KEY, MOCK_CONFIG_REST_PARAMS, overrides); + + expect(configFactory.createVitestConfig).toHaveBeenCalledWith( + { + projectKey: MOCK_PROJECT_KEY, + kind, + ...MOCK_CONFIG_REST_PARAMS, + }, + { + test: { + setupFiles: customSetupFiles, + testTimeout: TEST_TIMEOUTS.SHORT, + environment: 'jsdom', + }, + }, + ); + }); + + it('should handle undefined overrides', () => { + createFn(MOCK_PROJECT_KEY, MOCK_CONFIG_REST_PARAMS, undefined); + + expect(configFactory.createVitestConfig).toHaveBeenCalledWith( + { + projectKey: MOCK_PROJECT_KEY, + kind, + ...MOCK_CONFIG_REST_PARAMS, + }, + { + test: { + setupFiles: baseSetupFiles, + }, + }, + ); + }); + + it('should handle overrides without test config', () => { + const overrides: VitestOverrides = { + build: { + target: 'es2020', + }, + }; + + createFn(MOCK_PROJECT_KEY, MOCK_CONFIG_REST_PARAMS, overrides); + + expect(configFactory.createVitestConfig).toHaveBeenCalledWith( + { + projectKey: MOCK_PROJECT_KEY, + kind, + ...MOCK_CONFIG_REST_PARAMS, + }, + { + build: { + target: 'es2020', + }, + test: { + setupFiles: baseSetupFiles, + }, + }, + ); + }); + + it('should return the result from createVitestConfig', () => { + const result = createFn(MOCK_PROJECT_KEY, MOCK_CONFIG_REST_PARAMS); + + expect(result).toBe('mocked-config'); + }); + + it('should handle empty projectKey gracefully', () => { + const result = createFn('', MOCK_CONFIG_REST_PARAMS); + + expect(configFactory.createVitestConfig).toHaveBeenCalledWith( + { + projectKey: '', + kind, + ...MOCK_CONFIG_REST_PARAMS, + }, + { + test: { + setupFiles: baseSetupFiles, + }, + }, + ); + 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'); + }); +}); From 69255346f445271fcb77341cf03767f9b4df672e Mon Sep 17 00:00:00 2001 From: "Szymon.Poltorak" Date: Tue, 9 Sep 2025 15:02:41 +0200 Subject: [PATCH 07/23] refactor(): fix circular dependency by creating new internal library --- e2e/ci-e2e/vitest.e2e.config.ts | 2 +- .../vitest.e2e.config.ts | 2 +- packages/core/vitest.int.config.ts | 2 +- packages/core/vitest.unit.config.ts | 2 +- packages/utils/vitest.int.config.ts | 2 +- packages/utils/vitest.unit.config.ts | 2 +- .../config => test-setup-config}/README.md | 0 testing/test-setup-config/eslint.config.js | 12 +++++++++ testing/test-setup-config/project.json | 12 +++++++++ testing/test-setup-config/src/index.ts | 14 +++++++++++ .../src/lib}/vitest-config-factory.ts | 2 +- .../lib}/vitest-config-factory.unit.test.ts | 2 +- .../src/lib}/vitest-setup-presets.ts | 0 .../lib}/vitest-setup-presets.unit.test.ts | 0 .../src/lib/vitest-tsconfig-path-aliases.ts | 25 +++++++++++++++++++ testing/test-setup-config/tsconfig.json | 22 ++++++++++++++++ testing/test-setup-config/tsconfig.lib.json | 15 +++++++++++ testing/test-setup-config/tsconfig.test.json | 14 +++++++++++ .../test-setup-config/vitest.unit.config.ts | 23 +++++++++++++++++ tsconfig.base.json | 3 +++ 20 files changed, 148 insertions(+), 8 deletions(-) rename testing/{test-setup/src/lib/config => test-setup-config}/README.md (100%) create mode 100644 testing/test-setup-config/eslint.config.js create mode 100644 testing/test-setup-config/project.json create mode 100644 testing/test-setup-config/src/index.ts rename testing/{test-setup/src/lib/config => test-setup-config/src/lib}/vitest-config-factory.ts (98%) rename testing/{test-setup/src/lib/config => test-setup-config/src/lib}/vitest-config-factory.unit.test.ts (99%) rename testing/{test-setup/src/lib/config => test-setup-config/src/lib}/vitest-setup-presets.ts (100%) rename testing/{test-setup/src/lib/config => test-setup-config/src/lib}/vitest-setup-presets.unit.test.ts (100%) create mode 100644 testing/test-setup-config/src/lib/vitest-tsconfig-path-aliases.ts create mode 100644 testing/test-setup-config/tsconfig.json create mode 100644 testing/test-setup-config/tsconfig.lib.json create mode 100644 testing/test-setup-config/tsconfig.test.json create mode 100644 testing/test-setup-config/vitest.unit.config.ts diff --git a/e2e/ci-e2e/vitest.e2e.config.ts b/e2e/ci-e2e/vitest.e2e.config.ts index 730db91cf..37ea143d8 100644 --- a/e2e/ci-e2e/vitest.e2e.config.ts +++ b/e2e/ci-e2e/vitest.e2e.config.ts @@ -1,5 +1,5 @@ /// -import { createE2eConfig } from '../../testing/test-setup/src/lib/config/vitest-setup-presets.js'; +import { createE2eConfig } from '../../testing/test-setup-config/src/lib/vitest-setup-presets.js'; export default createE2eConfig( 'ci-e2e', diff --git a/e2e/plugin-typescript-e2e/vitest.e2e.config.ts b/e2e/plugin-typescript-e2e/vitest.e2e.config.ts index a9a75bb81..1e9fd4768 100644 --- a/e2e/plugin-typescript-e2e/vitest.e2e.config.ts +++ b/e2e/plugin-typescript-e2e/vitest.e2e.config.ts @@ -1,5 +1,5 @@ /// -import { createE2eConfig } from '../../testing/test-setup/src/lib/config/vitest-setup-presets.js'; +import { createE2eConfig } from '../../testing/test-setup-config/src/lib/vitest-setup-presets.js'; export default createE2eConfig( 'plugin-typescript-e2e', diff --git a/packages/core/vitest.int.config.ts b/packages/core/vitest.int.config.ts index b32e9afd9..981ee4e19 100644 --- a/packages/core/vitest.int.config.ts +++ b/packages/core/vitest.int.config.ts @@ -2,7 +2,7 @@ import { createIntConfig, setupPresets, -} from '../../testing/test-setup/src/lib/config/vitest-setup-presets.js'; +} from '../../testing/test-setup-config/src/lib/vitest-setup-presets.js'; export default createIntConfig( 'core', diff --git a/packages/core/vitest.unit.config.ts b/packages/core/vitest.unit.config.ts index 09b28c623..d11f288f9 100644 --- a/packages/core/vitest.unit.config.ts +++ b/packages/core/vitest.unit.config.ts @@ -2,7 +2,7 @@ import { createUnitConfig, setupPresets, -} from '../../testing/test-setup/src/lib/config/vitest-setup-presets.js'; +} from '../../testing/test-setup-config/src/lib/vitest-setup-presets.js'; export default createUnitConfig( 'core', diff --git a/packages/utils/vitest.int.config.ts b/packages/utils/vitest.int.config.ts index 0f75d8b29..1aeadf3d4 100644 --- a/packages/utils/vitest.int.config.ts +++ b/packages/utils/vitest.int.config.ts @@ -2,7 +2,7 @@ import { createIntConfig, setupPresets, -} from '../../testing/test-setup/src/lib/config/vitest-setup-presets.js'; +} from '../../testing/test-setup-config/src/lib/vitest-setup-presets.js'; export default createIntConfig( 'utils', diff --git a/packages/utils/vitest.unit.config.ts b/packages/utils/vitest.unit.config.ts index 2030ba17d..66bb6dd51 100644 --- a/packages/utils/vitest.unit.config.ts +++ b/packages/utils/vitest.unit.config.ts @@ -2,7 +2,7 @@ import { createUnitConfig, setupPresets, -} from '../../testing/test-setup/src/lib/config/vitest-setup-presets.js'; +} from '../../testing/test-setup-config/src/lib/vitest-setup-presets.js'; export default createUnitConfig( 'utils', diff --git a/testing/test-setup/src/lib/config/README.md b/testing/test-setup-config/README.md similarity index 100% rename from testing/test-setup/src/lib/config/README.md rename to testing/test-setup-config/README.md 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/src/lib/config/vitest-config-factory.ts b/testing/test-setup-config/src/lib/vitest-config-factory.ts similarity index 98% rename from testing/test-setup/src/lib/config/vitest-config-factory.ts rename to testing/test-setup-config/src/lib/vitest-config-factory.ts index b7c397da3..4ad7cae09 100644 --- a/testing/test-setup/src/lib/config/vitest-config-factory.ts +++ b/testing/test-setup-config/src/lib/vitest-config-factory.ts @@ -5,7 +5,7 @@ import { mergeConfig, } from 'vite'; import type { CoverageOptions, InlineConfig } from 'vitest'; -import { tsconfigPathAliases } from '../../../../../tools/vitest-tsconfig-path-aliases.js'; +import { tsconfigPathAliases } from './vitest-tsconfig-path-aliases.js'; export type TestKind = 'unit' | 'int' | 'e2e'; diff --git a/testing/test-setup/src/lib/config/vitest-config-factory.unit.test.ts b/testing/test-setup-config/src/lib/vitest-config-factory.unit.test.ts similarity index 99% rename from testing/test-setup/src/lib/config/vitest-config-factory.unit.test.ts rename to testing/test-setup-config/src/lib/vitest-config-factory.unit.test.ts index 767f82314..91359434d 100644 --- a/testing/test-setup/src/lib/config/vitest-config-factory.unit.test.ts +++ b/testing/test-setup-config/src/lib/vitest-config-factory.unit.test.ts @@ -19,7 +19,7 @@ vi.mock('vite', async importOriginal => { }); // Mock tsconfigPathAliases since it reads from filesystem and our fake paths don't exist -vi.mock('../../../../../tools/vitest-tsconfig-path-aliases.js', () => ({ +vi.mock('./vitest-tsconfig-path-aliases.js', () => ({ tsconfigPathAliases: vi.fn().mockReturnValue({ '@mock/alias': '/mock/path' }), })); diff --git a/testing/test-setup/src/lib/config/vitest-setup-presets.ts b/testing/test-setup-config/src/lib/vitest-setup-presets.ts similarity index 100% rename from testing/test-setup/src/lib/config/vitest-setup-presets.ts rename to testing/test-setup-config/src/lib/vitest-setup-presets.ts diff --git a/testing/test-setup/src/lib/config/vitest-setup-presets.unit.test.ts b/testing/test-setup-config/src/lib/vitest-setup-presets.unit.test.ts similarity index 100% rename from testing/test-setup/src/lib/config/vitest-setup-presets.unit.test.ts rename to testing/test-setup-config/src/lib/vitest-setup-presets.unit.test.ts 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..2566c1ae7 --- /dev/null +++ b/testing/test-setup-config/src/lib/vitest-tsconfig-path-aliases.ts @@ -0,0 +1,25 @@ +import { loadConfig } from 'tsconfig-paths'; +import type { Alias, AliasOptions } from 'vite'; + +export function tsconfigPathAliases(projectRootUrl?: URL): AliasOptions { + const tsconfigPath = projectRootUrl + ? new URL('tsconfig.base.json', projectRootUrl).pathname + : '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 + ? new URL(relativePath, projectRootUrl).pathname + : 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/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" From 7288d8ef3d4e7c547b96d7156fde449a92bf3287 Mon Sep 17 00:00:00 2001 From: "Szymon.Poltorak" Date: Tue, 9 Sep 2025 15:09:22 +0200 Subject: [PATCH 08/23] fix: path resolution --- .../src/lib/vitest-tsconfig-path-aliases.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) 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 index 2566c1ae7..aec88e199 100644 --- a/testing/test-setup-config/src/lib/vitest-tsconfig-path-aliases.ts +++ b/testing/test-setup-config/src/lib/vitest-tsconfig-path-aliases.ts @@ -1,9 +1,11 @@ +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 - ? new URL('tsconfig.base.json', projectRootUrl).pathname + ? path.resolve(fileURLToPath(projectRootUrl), 'tsconfig.base.json') : 'tsconfig.base.json'; const result = loadConfig(tsconfigPath); if (result.resultType === 'failed') { @@ -18,7 +20,7 @@ export function tsconfigPathAliases(projectRootUrl?: URL): AliasOptions { ([importPath, relativePath]): Alias => ({ find: importPath, replacement: projectRootUrl - ? new URL(relativePath, projectRootUrl).pathname + ? path.resolve(fileURLToPath(projectRootUrl), relativePath) : new URL(`../${relativePath}`, import.meta.url).pathname, }), ); From db99af280a0f80a51c0fdfd9cedfac117d1e960a Mon Sep 17 00:00:00 2001 From: "Szymon.Poltorak" Date: Tue, 9 Sep 2025 15:19:57 +0200 Subject: [PATCH 09/23] fix: path resolution #2 --- .../src/lib/vitest-config-factory.ts | 26 ++++++++++++------- tools/vitest-tsconfig-path-aliases.ts | 8 ++++-- 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/testing/test-setup-config/src/lib/vitest-config-factory.ts b/testing/test-setup-config/src/lib/vitest-config-factory.ts index 4ad7cae09..ec7cef34a 100644 --- a/testing/test-setup-config/src/lib/vitest-config-factory.ts +++ b/testing/test-setup-config/src/lib/vitest-config-factory.ts @@ -1,4 +1,5 @@ -import { pathToFileURL } from 'node:url'; +import path from 'node:path'; +import { fileURLToPath, pathToFileURL } from 'node:url'; import { type UserConfig as ViteUserConfig, defineConfig, @@ -68,7 +69,9 @@ function toAbsolutePaths( paths?: readonly string[], ): string[] { return paths && paths.length > 0 - ? paths.filter(Boolean).map(p => new URL(p, projectRootUrl).pathname) + ? paths + .filter(Boolean) + .map(p => path.resolve(fileURLToPath(projectRootUrl), p)) : []; } @@ -92,7 +95,7 @@ function defaultGlobalSetup( ): string[] | undefined { return kind === 'e2e' ? undefined - : [new URL('global-setup.ts', projectRootUrl).pathname]; + : [path.resolve(fileURLToPath(projectRootUrl), 'global-setup.ts')]; } function buildCoverageConfig(params: { @@ -102,12 +105,12 @@ function buildCoverageConfig(params: { overrideExclude?: string[]; }): CoverageOptions { const defaultExclude = ['mocks/**', '**/types.ts']; - const reportsDirectory = new URL( + const reportsDirectory = path.resolve( + fileURLToPath(params.projectRootUrl), params.kind === 'e2e' ? `e2e/${params.projectKey}/.coverage` : `packages/${params.projectKey}/.coverage/${params.kind}-tests`, - params.projectRootUrl, - ).pathname; + ); return { reporter: ['text', 'lcov'], reportsDirectory, @@ -128,15 +131,18 @@ function buildBaseConfig(params: { overrideExclude: string[]; }): VitestOverrides { const cfg: VitestOverrides = { - cacheDir: new URL( + cacheDir: path.resolve( + fileURLToPath(params.projectRootUrl), `node_modules/.vite/${params.cacheDirName}`, - params.projectRootUrl, - ).pathname, + ), test: { reporters: ['basic'], globals: true, cache: { - dir: new URL('node_modules/.vitest', params.projectRootUrl).pathname, + dir: path.resolve( + fileURLToPath(params.projectRootUrl), + 'node_modules/.vitest', + ), }, alias: tsconfigPathAliases(params.projectRootUrl), pool: 'threads', diff --git a/tools/vitest-tsconfig-path-aliases.ts b/tools/vitest-tsconfig-path-aliases.ts index 44d3353d0..aec88e199 100644 --- a/tools/vitest-tsconfig-path-aliases.ts +++ b/tools/vitest-tsconfig-path-aliases.ts @@ -1,9 +1,11 @@ +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 - ? new URL('tsconfig.base.json', projectRootUrl).pathname + ? path.resolve(fileURLToPath(projectRootUrl), 'tsconfig.base.json') : 'tsconfig.base.json'; const result = loadConfig(tsconfigPath); if (result.resultType === 'failed') { @@ -17,7 +19,9 @@ export function tsconfigPathAliases(projectRootUrl?: URL): 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, }), ); } From 44a0891236e486bebe4c1408c2c939ca8f738f90 Mon Sep 17 00:00:00 2001 From: "Szymon.Poltorak" Date: Tue, 9 Sep 2025 15:27:13 +0200 Subject: [PATCH 10/23] fix: path resolution #3 --- .../src/lib/vitest-config-factory.ts | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/testing/test-setup-config/src/lib/vitest-config-factory.ts b/testing/test-setup-config/src/lib/vitest-config-factory.ts index ec7cef34a..a045e477c 100644 --- a/testing/test-setup-config/src/lib/vitest-config-factory.ts +++ b/testing/test-setup-config/src/lib/vitest-config-factory.ts @@ -71,7 +71,7 @@ function toAbsolutePaths( return paths && paths.length > 0 ? paths .filter(Boolean) - .map(p => path.resolve(fileURLToPath(projectRootUrl), p)) + .map(p => path.resolve(getProjectRootPath(projectRootUrl), p)) : []; } @@ -95,7 +95,7 @@ function defaultGlobalSetup( ): string[] | undefined { return kind === 'e2e' ? undefined - : [path.resolve(fileURLToPath(projectRootUrl), 'global-setup.ts')]; + : [path.resolve(getProjectRootPath(projectRootUrl), 'global-setup.ts')]; } function buildCoverageConfig(params: { @@ -106,7 +106,7 @@ function buildCoverageConfig(params: { }): CoverageOptions { const defaultExclude = ['mocks/**', '**/types.ts']; const reportsDirectory = path.resolve( - fileURLToPath(params.projectRootUrl), + getProjectRootPath(params.projectRootUrl), params.kind === 'e2e' ? `e2e/${params.projectKey}/.coverage` : `packages/${params.projectKey}/.coverage/${params.kind}-tests`, @@ -132,7 +132,7 @@ function buildBaseConfig(params: { }): VitestOverrides { const cfg: VitestOverrides = { cacheDir: path.resolve( - fileURLToPath(params.projectRootUrl), + getProjectRootPath(params.projectRootUrl), `node_modules/.vite/${params.cacheDirName}`, ), test: { @@ -140,7 +140,7 @@ function buildBaseConfig(params: { globals: true, cache: { dir: path.resolve( - fileURLToPath(params.projectRootUrl), + getProjectRootPath(params.projectRootUrl), 'node_modules/.vitest', ), }, @@ -199,3 +199,13 @@ function sanitizeOverrides(overrides: VitestOverrides): VitestOverrides { return { ...overrides, test: sanitizedTest }; } + +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}`; + } +} From 0eaaa5a28ebb2b316395dff3eca2d81d72edfc83 Mon Sep 17 00:00:00 2001 From: "Szymon.Poltorak" Date: Tue, 9 Sep 2025 15:37:13 +0200 Subject: [PATCH 11/23] fix: path resolution #4 --- .../lib/vitest-config-factory.unit.test.ts | 69 +++++++++++-------- 1 file changed, 40 insertions(+), 29 deletions(-) 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 index 91359434d..1ebe0cde5 100644 --- 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 @@ -1,3 +1,4 @@ +import path from 'node:path'; import { pathToFileURL } from 'node:url'; import { defineConfig } from 'vite'; import { beforeEach, describe, expect, it, vi } from 'vitest'; @@ -26,6 +27,18 @@ vi.mock('./vitest-tsconfig-path-aliases.js', () => ({ const MOCK_PROJECT_ROOT_STRING = '/Users/test/project'; const MOCK_PROJECT_ROOT_URL = pathToFileURL(`${MOCK_PROJECT_ROOT_STRING}/`); +// Cross-platform path helpers to match what the actual code generates +const mockPath = (...segments: string[]) => + path.resolve(MOCK_PROJECT_ROOT_STRING, ...segments); +const mockCacheDir = (name: string) => mockPath('node_modules', '.vite', name); +const mockVitestCacheDir = () => mockPath('node_modules', '.vitest'); +const mockGlobalSetup = () => mockPath('global-setup.ts'); +const mockReportsDir = (projectKey: string, kind: string) => + kind === 'e2e' + ? mockPath('e2e', projectKey, '.coverage') + : mockPath('packages', projectKey, '.coverage', `${kind}-tests`); +const mockSetupFile = mockPath; + const TEST_TIMEOUTS = { SHORT: 5000, MEDIUM: 10_000, @@ -61,23 +74,23 @@ describe('createVitestConfig', () => { expect(config).toEqual( expect.objectContaining({ - cacheDir: `${MOCK_PROJECT_ROOT_STRING}/node_modules/.vite/test-package`, + cacheDir: mockCacheDir('test-package'), test: expect.objectContaining({ reporters: ['basic'], globals: true, cache: { - dir: `${MOCK_PROJECT_ROOT_STRING}/node_modules/.vitest`, + dir: mockVitestCacheDir(), }, alias: expect.any(Object), pool: 'threads', poolOptions: { threads: { singleThread: true } }, environment: 'node', include: EXPECTED_INCLUDES.unit, - globalSetup: [`${MOCK_PROJECT_ROOT_STRING}/global-setup.ts`], + globalSetup: [mockGlobalSetup()], setupFiles: [], coverage: expect.objectContaining({ reporter: ['text', 'lcov'], - reportsDirectory: `${MOCK_PROJECT_ROOT_STRING}/packages/test-package/.coverage/unit-tests`, + reportsDirectory: mockReportsDir('test-package', 'unit'), exclude: DEFAULT_EXCLUDES, }), }), @@ -98,10 +111,10 @@ describe('createVitestConfig', () => { expect(config).toEqual( expect.objectContaining({ - cacheDir: `${MOCK_PROJECT_ROOT_STRING}/node_modules/.vite/test-package`, + cacheDir: mockCacheDir('test-package'), test: expect.objectContaining({ include: EXPECTED_INCLUDES.unit, - globalSetup: [`${MOCK_PROJECT_ROOT_STRING}/global-setup.ts`], + globalSetup: [mockGlobalSetup()], }), }), ); @@ -148,9 +161,9 @@ describe('createVitestConfig', () => { expect.objectContaining({ test: expect.objectContaining({ include: EXPECTED_INCLUDES.int, - globalSetup: [`${MOCK_PROJECT_ROOT_STRING}/global-setup.ts`], + globalSetup: [mockGlobalSetup()], coverage: expect.objectContaining({ - reportsDirectory: `${MOCK_PROJECT_ROOT_STRING}/packages/test-package/.coverage/int-tests`, + reportsDirectory: mockReportsDir('test-package', 'int'), }), }), }), @@ -202,7 +215,7 @@ describe('createVitestConfig', () => { globalSetup: undefined, coverage: expect.objectContaining({ reporter: ['text', 'lcov'], - reportsDirectory: `${MOCK_PROJECT_ROOT_STRING}/e2e/test-package/.coverage`, + reportsDirectory: mockReportsDir('test-package', 'e2e'), exclude: DEFAULT_EXCLUDES, }), }), @@ -224,7 +237,7 @@ describe('createVitestConfig', () => { expect(config).toEqual( expect.objectContaining({ - cacheDir: `${MOCK_PROJECT_ROOT_STRING}/node_modules/.vite/custom-cache-key`, + cacheDir: mockCacheDir('custom-cache-key'), }), ); }); @@ -240,7 +253,7 @@ describe('createVitestConfig', () => { expect(config).toEqual( expect.objectContaining({ - cacheDir: `${MOCK_PROJECT_ROOT_STRING}/node_modules/.vite/test-package`, + cacheDir: mockCacheDir('test-package'), }), ); }); @@ -265,7 +278,7 @@ describe('createVitestConfig', () => { expect(config).toEqual( expect.objectContaining({ test: expect.objectContaining({ - setupFiles: [`${MOCK_PROJECT_ROOT_STRING}/setup.ts`], + setupFiles: [mockSetupFile('setup.ts')], }), }), ); @@ -290,8 +303,8 @@ describe('createVitestConfig', () => { expect.objectContaining({ test: expect.objectContaining({ setupFiles: [ - `${MOCK_PROJECT_ROOT_STRING}/setup1.ts`, - `${MOCK_PROJECT_ROOT_STRING}/setup2.ts`, + mockSetupFile('setup1.ts'), + mockSetupFile('setup2.ts'), ], }), }), @@ -317,9 +330,9 @@ describe('createVitestConfig', () => { expect.objectContaining({ test: expect.objectContaining({ setupFiles: [ - `${MOCK_PROJECT_ROOT_STRING}/setup1.ts`, - `${MOCK_PROJECT_ROOT_STRING}/setup2.ts`, - `${MOCK_PROJECT_ROOT_STRING}/setup3.ts`, + mockSetupFile('setup1.ts'), + mockSetupFile('setup2.ts'), + mockSetupFile('setup3.ts'), ], }), }), @@ -392,7 +405,7 @@ describe('createVitestConfig', () => { test: expect.objectContaining({ coverage: expect.objectContaining({ reporter: ['text', 'lcov'], - reportsDirectory: `${MOCK_PROJECT_ROOT_STRING}/packages/test-package/.coverage/unit-tests`, + reportsDirectory: mockReportsDir('test-package', 'unit'), exclude: [...DEFAULT_EXCLUDES, 'custom/**', 'ignore/**'], }), }), @@ -422,7 +435,7 @@ describe('createVitestConfig', () => { test: expect.objectContaining({ coverage: expect.objectContaining({ reporter: ['text', 'lcov'], - reportsDirectory: `${MOCK_PROJECT_ROOT_STRING}/packages/test-package/.coverage/unit-tests`, + reportsDirectory: mockReportsDir('test-package', 'unit'), exclude: DEFAULT_EXCLUDES, }), }), @@ -479,7 +492,7 @@ describe('createVitestConfig', () => { const config = createVitestConfig(options, overrides); expectCoverageConfig(config, { reporter: ['text', 'lcov', 'html', 'json'], - reportsDirectory: `${MOCK_PROJECT_ROOT_STRING}/packages/test-package/.coverage/unit-tests`, + reportsDirectory: mockReportsDir('test-package', 'unit'), exclude: [...DEFAULT_EXCLUDES, 'custom/**'], thresholds: { global: { @@ -536,7 +549,7 @@ describe('createVitestConfig', () => { const testConfig = (config as any).test; expect(testConfig.setupFiles).toEqual([ - `${MOCK_PROJECT_ROOT_STRING}/should-be-removed.ts`, + mockSetupFile('should-be-removed.ts'), ]); expect(testConfig.testTimeout).toBe(TEST_TIMEOUTS.SHORT); expect(testConfig.pool).toBe('forks'); @@ -579,7 +592,7 @@ describe('createVitestConfig', () => { expect((config as any).test.testTimeout).toBe(TEST_TIMEOUTS.SHORT); expectCoverageConfig(config, { reporter: ['text', 'lcov'], - reportsDirectory: `${MOCK_PROJECT_ROOT_STRING}/packages/test-package/.coverage/unit-tests`, + reportsDirectory: mockReportsDir('test-package', 'unit'), exclude: DEFAULT_EXCLUDES, }); }); @@ -603,7 +616,7 @@ describe('createVitestConfig', () => { expect((config as any).test.testTimeout).toBe(TEST_TIMEOUTS.SHORT); expectCoverageConfig(config, { reporter: ['text', 'lcov'], - reportsDirectory: `${MOCK_PROJECT_ROOT_STRING}/packages/test-package/.coverage/unit-tests`, + reportsDirectory: mockReportsDir('test-package', 'unit'), exclude: DEFAULT_EXCLUDES, }); }); @@ -632,9 +645,7 @@ describe('createVitestConfig', () => { expectedIncludes[kind], ); expect((config as any).test.globalSetup).toStrictEqual( - kind === 'e2e' - ? undefined - : [`${MOCK_PROJECT_ROOT_STRING}/global-setup.ts`], + kind === 'e2e' ? undefined : [mockGlobalSetup()], ); }); }); @@ -681,15 +692,15 @@ describe('createVitestConfig', () => { }, test: expect.objectContaining({ setupFiles: [ - `${MOCK_PROJECT_ROOT_STRING}/setup1.ts`, - `${MOCK_PROJECT_ROOT_STRING}/setup2.ts`, + mockSetupFile('setup1.ts'), + mockSetupFile('setup2.ts'), ], testTimeout: TEST_TIMEOUTS.LONG, environment: 'jsdom', include: EXPECTED_INCLUDES.int, coverage: expect.objectContaining({ exclude: ['mocks/**', '**/types.ts', 'e2e/**', 'dist/**'], - reportsDirectory: `${MOCK_PROJECT_ROOT_STRING}/packages/test-package/.coverage/int-tests`, + reportsDirectory: mockReportsDir('test-package', 'int'), }), }), }), From 0bf21ec3ed3f78736a3ab164cf5e7c4eeb69d6de Mon Sep 17 00:00:00 2001 From: "Szymon.Poltorak" Date: Tue, 9 Sep 2025 15:58:37 +0200 Subject: [PATCH 12/23] fix: path resolution #5 --- .../src/lib/vitest-config-factory.ts | 2 +- .../lib/vitest-config-factory.unit.test.ts | 120 +++++++++++------- 2 files changed, 78 insertions(+), 44 deletions(-) diff --git a/testing/test-setup-config/src/lib/vitest-config-factory.ts b/testing/test-setup-config/src/lib/vitest-config-factory.ts index a045e477c..3421740ff 100644 --- a/testing/test-setup-config/src/lib/vitest-config-factory.ts +++ b/testing/test-setup-config/src/lib/vitest-config-factory.ts @@ -200,7 +200,7 @@ function sanitizeOverrides(overrides: VitestOverrides): VitestOverrides { return { ...overrides, test: sanitizedTest }; } -function getProjectRootPath(projectRootUrl: URL): string { +export function getProjectRootPath(projectRootUrl: URL): string { try { return fileURLToPath(projectRootUrl); } catch { 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 index 1ebe0cde5..c122a3bc5 100644 --- 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 @@ -7,6 +7,7 @@ import { type VitestConfigFactoryOptions, type VitestOverrides, createVitestConfig, + getProjectRootPath, } from './vitest-config-factory.js'; // Only mock defineConfig - assume it works correctly, we're not testing Vite @@ -27,17 +28,11 @@ vi.mock('./vitest-tsconfig-path-aliases.js', () => ({ const MOCK_PROJECT_ROOT_STRING = '/Users/test/project'; const MOCK_PROJECT_ROOT_URL = pathToFileURL(`${MOCK_PROJECT_ROOT_STRING}/`); -// Cross-platform path helpers to match what the actual code generates +// Simple path helpers - just use them directly in tests! const mockPath = (...segments: string[]) => path.resolve(MOCK_PROJECT_ROOT_STRING, ...segments); -const mockCacheDir = (name: string) => mockPath('node_modules', '.vite', name); -const mockVitestCacheDir = () => mockPath('node_modules', '.vitest'); -const mockGlobalSetup = () => mockPath('global-setup.ts'); -const mockReportsDir = (projectKey: string, kind: string) => - kind === 'e2e' - ? mockPath('e2e', projectKey, '.coverage') - : mockPath('packages', projectKey, '.coverage', `${kind}-tests`); -const mockSetupFile = mockPath; +const mockUrlPath = (url: URL, ...segments: string[]) => + path.resolve(getProjectRootPath(url), ...segments); const TEST_TIMEOUTS = { SHORT: 5000, @@ -74,23 +69,28 @@ describe('createVitestConfig', () => { expect(config).toEqual( expect.objectContaining({ - cacheDir: mockCacheDir('test-package'), + cacheDir: mockPath('node_modules', '.vite', 'test-package'), test: expect.objectContaining({ reporters: ['basic'], globals: true, cache: { - dir: mockVitestCacheDir(), + dir: mockPath('node_modules', '.vitest'), }, alias: expect.any(Object), pool: 'threads', poolOptions: { threads: { singleThread: true } }, environment: 'node', include: EXPECTED_INCLUDES.unit, - globalSetup: [mockGlobalSetup()], + globalSetup: [mockPath('global-setup.ts')], setupFiles: [], coverage: expect.objectContaining({ reporter: ['text', 'lcov'], - reportsDirectory: mockReportsDir('test-package', 'unit'), + reportsDirectory: mockPath( + 'packages', + 'test-package', + '.coverage', + 'unit-tests', + ), exclude: DEFAULT_EXCLUDES, }), }), @@ -111,10 +111,17 @@ describe('createVitestConfig', () => { expect(config).toEqual( expect.objectContaining({ - cacheDir: mockCacheDir('test-package'), + cacheDir: mockUrlPath( + MOCK_PROJECT_ROOT_URL, + 'node_modules', + '.vite', + 'test-package', + ), test: expect.objectContaining({ include: EXPECTED_INCLUDES.unit, - globalSetup: [mockGlobalSetup()], + globalSetup: [ + mockUrlPath(MOCK_PROJECT_ROOT_URL, 'global-setup.ts'), + ], }), }), ); @@ -161,9 +168,14 @@ describe('createVitestConfig', () => { expect.objectContaining({ test: expect.objectContaining({ include: EXPECTED_INCLUDES.int, - globalSetup: [mockGlobalSetup()], + globalSetup: [mockPath('global-setup.ts')], coverage: expect.objectContaining({ - reportsDirectory: mockReportsDir('test-package', 'int'), + reportsDirectory: mockPath( + 'packages', + 'test-package', + '.coverage', + 'int-tests', + ), }), }), }), @@ -215,7 +227,7 @@ describe('createVitestConfig', () => { globalSetup: undefined, coverage: expect.objectContaining({ reporter: ['text', 'lcov'], - reportsDirectory: mockReportsDir('test-package', 'e2e'), + reportsDirectory: mockPath('e2e', 'test-package', '.coverage'), exclude: DEFAULT_EXCLUDES, }), }), @@ -237,7 +249,7 @@ describe('createVitestConfig', () => { expect(config).toEqual( expect.objectContaining({ - cacheDir: mockCacheDir('custom-cache-key'), + cacheDir: mockPath('node_modules', '.vite', 'custom-cache-key'), }), ); }); @@ -253,7 +265,7 @@ describe('createVitestConfig', () => { expect(config).toEqual( expect.objectContaining({ - cacheDir: mockCacheDir('test-package'), + cacheDir: mockPath('node_modules', '.vite', 'test-package'), }), ); }); @@ -278,7 +290,7 @@ describe('createVitestConfig', () => { expect(config).toEqual( expect.objectContaining({ test: expect.objectContaining({ - setupFiles: [mockSetupFile('setup.ts')], + setupFiles: [mockPath('setup.ts')], }), }), ); @@ -302,10 +314,7 @@ describe('createVitestConfig', () => { expect(config).toEqual( expect.objectContaining({ test: expect.objectContaining({ - setupFiles: [ - mockSetupFile('setup1.ts'), - mockSetupFile('setup2.ts'), - ], + setupFiles: [mockPath('setup1.ts'), mockPath('setup2.ts')], }), }), ); @@ -330,9 +339,9 @@ describe('createVitestConfig', () => { expect.objectContaining({ test: expect.objectContaining({ setupFiles: [ - mockSetupFile('setup1.ts'), - mockSetupFile('setup2.ts'), - mockSetupFile('setup3.ts'), + mockPath('setup1.ts'), + mockPath('setup2.ts'), + mockPath('setup3.ts'), ], }), }), @@ -405,7 +414,12 @@ describe('createVitestConfig', () => { test: expect.objectContaining({ coverage: expect.objectContaining({ reporter: ['text', 'lcov'], - reportsDirectory: mockReportsDir('test-package', 'unit'), + reportsDirectory: mockPath( + 'packages', + 'test-package', + '.coverage', + 'unit-tests', + ), exclude: [...DEFAULT_EXCLUDES, 'custom/**', 'ignore/**'], }), }), @@ -435,7 +449,12 @@ describe('createVitestConfig', () => { test: expect.objectContaining({ coverage: expect.objectContaining({ reporter: ['text', 'lcov'], - reportsDirectory: mockReportsDir('test-package', 'unit'), + reportsDirectory: mockPath( + 'packages', + 'test-package', + '.coverage', + 'unit-tests', + ), exclude: DEFAULT_EXCLUDES, }), }), @@ -492,7 +511,12 @@ describe('createVitestConfig', () => { const config = createVitestConfig(options, overrides); expectCoverageConfig(config, { reporter: ['text', 'lcov', 'html', 'json'], - reportsDirectory: mockReportsDir('test-package', 'unit'), + reportsDirectory: mockPath( + 'packages', + 'test-package', + '.coverage', + 'unit-tests', + ), exclude: [...DEFAULT_EXCLUDES, 'custom/**'], thresholds: { global: { @@ -548,9 +572,7 @@ describe('createVitestConfig', () => { const config = createVitestConfig(options, overrides); const testConfig = (config as any).test; - expect(testConfig.setupFiles).toEqual([ - mockSetupFile('should-be-removed.ts'), - ]); + expect(testConfig.setupFiles).toEqual([mockPath('should-be-removed.ts')]); expect(testConfig.testTimeout).toBe(TEST_TIMEOUTS.SHORT); expect(testConfig.pool).toBe('forks'); }); @@ -592,7 +614,12 @@ describe('createVitestConfig', () => { expect((config as any).test.testTimeout).toBe(TEST_TIMEOUTS.SHORT); expectCoverageConfig(config, { reporter: ['text', 'lcov'], - reportsDirectory: mockReportsDir('test-package', 'unit'), + reportsDirectory: mockPath( + 'packages', + 'test-package', + '.coverage', + 'unit-tests', + ), exclude: DEFAULT_EXCLUDES, }); }); @@ -616,7 +643,12 @@ describe('createVitestConfig', () => { expect((config as any).test.testTimeout).toBe(TEST_TIMEOUTS.SHORT); expectCoverageConfig(config, { reporter: ['text', 'lcov'], - reportsDirectory: mockReportsDir('test-package', 'unit'), + reportsDirectory: mockPath( + 'packages', + 'test-package', + '.coverage', + 'unit-tests', + ), exclude: DEFAULT_EXCLUDES, }); }); @@ -645,7 +677,7 @@ describe('createVitestConfig', () => { expectedIncludes[kind], ); expect((config as any).test.globalSetup).toStrictEqual( - kind === 'e2e' ? undefined : [mockGlobalSetup()], + kind === 'e2e' ? undefined : [mockPath('global-setup.ts')], ); }); }); @@ -686,21 +718,23 @@ describe('createVitestConfig', () => { expect(config).toEqual( expect.objectContaining({ - cacheDir: `${MOCK_PROJECT_ROOT_STRING}/node_modules/.vite/complex-scenario`, + cacheDir: mockPath('node_modules', '.vite', 'complex-scenario'), build: { target: 'es2020', }, test: expect.objectContaining({ - setupFiles: [ - mockSetupFile('setup1.ts'), - mockSetupFile('setup2.ts'), - ], + 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: mockReportsDir('test-package', 'int'), + reportsDirectory: mockPath( + 'packages', + 'test-package', + '.coverage', + 'int-tests', + ), }), }), }), From c0d6e77109f9b21ccfd09035f0f515727c2ef853 Mon Sep 17 00:00:00 2001 From: "Szymon.Poltorak" Date: Mon, 6 Oct 2025 17:16:29 +0200 Subject: [PATCH 13/23] refactor(testing): improve vitest config factory and test coverage --- testing/test-setup-config/README.md | 28 +- .../src/lib/vitest-config-factory.ts | 14 +- .../lib/vitest-config-factory.unit.test.ts | 8 +- .../src/lib/vitest-setup-presets.unit.test.ts | 408 ++++++++++++++++-- 4 files changed, 390 insertions(+), 68 deletions(-) diff --git a/testing/test-setup-config/README.md b/testing/test-setup-config/README.md index 730c625a0..42aa20ae8 100644 --- a/testing/test-setup-config/README.md +++ b/testing/test-setup-config/README.md @@ -5,33 +5,7 @@ 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. - -### Defaults - -**Common**: `reporters: ['basic']`, `globals: true`, `environment: 'node'`, `pool: 'threads'` with `singleThread: true`, alias from tsconfig paths - -**Coverage**: Unit/Int enabled (reports to `/packages//.coverage`), E2E disabled. Excludes `['mocks/**', '**/types.ts']` - -**Global setup**: Unit/Int use `['/global-setup.ts']`, E2E none by default - -**Include patterns**: Unit `src/**/*.unit.test.*`, Int `src/**/*.int.test.*`, E2E `tests/**/*.e2e.test.*` - -### Setup files - -**Automatic inclusion**: Unit (console mocking, cleanup, UI/filesystem mocks, basic matchers), Int (console mocking, cleanup), E2E (cleanup only) - -**Custom setup files**: ⚠️ Specifying `setupFiles` in overrides will completely replace the defaults. To extend the default list, manually combine them with `setupPresets`: - -- `setupPresets.unit.{base, git, portalClient, matchersCore, matcherPath}` -- `setupPresets.int.{base, cliui, fs, git, portalClient, matcherPath, chromePath}` -- `setupPresets.e2e.{base}` - -### Parameters - -- `projectKey`: Used for cache and coverage directory naming -- `projectRoot`: Required path/URL to the project root for resolving all paths -- Standard Vitest configuration options can be provided in the overrides parameter +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 diff --git a/testing/test-setup-config/src/lib/vitest-config-factory.ts b/testing/test-setup-config/src/lib/vitest-config-factory.ts index 3421740ff..31882ce9a 100644 --- a/testing/test-setup-config/src/lib/vitest-config-factory.ts +++ b/testing/test-setup-config/src/lib/vitest-config-factory.ts @@ -11,9 +11,21 @@ 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; }; @@ -35,7 +47,7 @@ export function createVitestConfig( : `${options.projectRoot}/`, ) : options.projectRoot; - const cacheDirName = options.cacheKey ?? options.projectKey; + const cacheDirName = options.cacheKey ?? `cache-${options.projectKey}`; const coverageEnabled = overrides.test?.coverage?.enabled ?? options.kind !== 'e2e'; 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 index c122a3bc5..9eae528fb 100644 --- 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 @@ -69,7 +69,7 @@ describe('createVitestConfig', () => { expect(config).toEqual( expect.objectContaining({ - cacheDir: mockPath('node_modules', '.vite', 'test-package'), + cacheDir: mockPath('node_modules', '.vite', 'cache-test-package'), test: expect.objectContaining({ reporters: ['basic'], globals: true, @@ -115,7 +115,7 @@ describe('createVitestConfig', () => { MOCK_PROJECT_ROOT_URL, 'node_modules', '.vite', - 'test-package', + 'cache-test-package', ), test: expect.objectContaining({ include: EXPECTED_INCLUDES.unit, @@ -254,7 +254,7 @@ describe('createVitestConfig', () => { ); }); - it('should fallback to projectKey when cacheKey is not provided', () => { + it('should fallback to cache-{projectKey} when cacheKey is not provided', () => { const options: VitestConfigFactoryOptions = { projectKey: 'test-package', kind: 'unit', @@ -265,7 +265,7 @@ describe('createVitestConfig', () => { expect(config).toEqual( expect.objectContaining({ - cacheDir: mockPath('node_modules', '.vite', 'test-package'), + cacheDir: mockPath('node_modules', '.vite', 'cache-test-package'), }), ); }); 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 index 8051a7588..cc303dd87 100644 --- 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 @@ -72,47 +72,42 @@ describe('setupPresets', () => { }); }); -// Parameterized tests to eliminate duplication -describe.each([ - ['unit', setupPresets.unit.base, createUnitConfig], - ['int', setupPresets.int.base, createIntConfig], - ['e2e', setupPresets.e2e.base, createE2eConfig], -] as const)('%s config creation', (kind, baseSetupFiles, createFn) => { +describe('createUnitConfig', () => { beforeEach(() => { vi.clearAllMocks(); }); it('should call createVitestConfig with correct parameters and default setupFiles', () => { - createFn(MOCK_PROJECT_KEY, MOCK_CONFIG_REST_PARAMS); + createUnitConfig(MOCK_PROJECT_KEY, MOCK_CONFIG_REST_PARAMS); expect(configFactory.createVitestConfig).toHaveBeenCalledWith( { projectKey: MOCK_PROJECT_KEY, - kind, + kind: 'unit', ...MOCK_CONFIG_REST_PARAMS, }, { test: { - setupFiles: baseSetupFiles, + setupFiles: setupPresets.unit.base, }, }, ); }); it('should use custom setupFiles from overrides when provided', () => { - const customSetupFiles = [`${kind}-setup1.ts`, `${kind}-setup2.ts`]; + const customSetupFiles = ['unit-setup1.ts', 'unit-setup2.ts']; const overrides: VitestOverrides = { test: { setupFiles: customSetupFiles, }, }; - createFn(MOCK_PROJECT_KEY, MOCK_CONFIG_REST_PARAMS, overrides); + createUnitConfig(MOCK_PROJECT_KEY, MOCK_CONFIG_REST_PARAMS, overrides); expect(configFactory.createVitestConfig).toHaveBeenCalledWith( { projectKey: MOCK_PROJECT_KEY, - kind, + kind: 'unit', ...MOCK_CONFIG_REST_PARAMS, }, { @@ -124,16 +119,357 @@ describe.each([ }); it('should merge other overrides correctly while using default setupFiles', () => { - const testTimeout = - kind === 'unit' - ? TEST_TIMEOUTS.MEDIUM - : kind === 'int' - ? TEST_TIMEOUTS.LONG - : TEST_TIMEOUTS.E2E; + 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, + testTimeout: TEST_TIMEOUTS.MEDIUM, globals: false, }, build: { @@ -141,19 +477,19 @@ describe.each([ }, }; - createFn(MOCK_PROJECT_KEY, MOCK_CONFIG_REST_PARAMS, overrides); + createE2eConfig(MOCK_PROJECT_KEY, MOCK_CONFIG_REST_PARAMS, overrides); expect(configFactory.createVitestConfig).toHaveBeenCalledWith( { projectKey: MOCK_PROJECT_KEY, - kind, + kind: 'e2e', ...MOCK_CONFIG_REST_PARAMS, }, { test: { - testTimeout, + testTimeout: TEST_TIMEOUTS.MEDIUM, globals: false, - setupFiles: baseSetupFiles, + setupFiles: setupPresets.e2e.base, }, build: { target: 'es2020', @@ -163,7 +499,7 @@ describe.each([ }); it('should handle overrides with custom setupFiles and other test options', () => { - const customSetupFiles = [`${kind}-custom.ts`]; + const customSetupFiles = ['e2e-custom.ts']; const overrides: VitestOverrides = { test: { setupFiles: customSetupFiles, @@ -172,12 +508,12 @@ describe.each([ }, }; - createFn(MOCK_PROJECT_KEY, MOCK_CONFIG_REST_PARAMS, overrides); + createE2eConfig(MOCK_PROJECT_KEY, MOCK_CONFIG_REST_PARAMS, overrides); expect(configFactory.createVitestConfig).toHaveBeenCalledWith( { projectKey: MOCK_PROJECT_KEY, - kind, + kind: 'e2e', ...MOCK_CONFIG_REST_PARAMS, }, { @@ -191,17 +527,17 @@ describe.each([ }); it('should handle undefined overrides', () => { - createFn(MOCK_PROJECT_KEY, MOCK_CONFIG_REST_PARAMS, undefined); + createE2eConfig(MOCK_PROJECT_KEY, MOCK_CONFIG_REST_PARAMS, undefined); expect(configFactory.createVitestConfig).toHaveBeenCalledWith( { projectKey: MOCK_PROJECT_KEY, - kind, + kind: 'e2e', ...MOCK_CONFIG_REST_PARAMS, }, { test: { - setupFiles: baseSetupFiles, + setupFiles: setupPresets.e2e.base, }, }, ); @@ -214,12 +550,12 @@ describe.each([ }, }; - createFn(MOCK_PROJECT_KEY, MOCK_CONFIG_REST_PARAMS, overrides); + createE2eConfig(MOCK_PROJECT_KEY, MOCK_CONFIG_REST_PARAMS, overrides); expect(configFactory.createVitestConfig).toHaveBeenCalledWith( { projectKey: MOCK_PROJECT_KEY, - kind, + kind: 'e2e', ...MOCK_CONFIG_REST_PARAMS, }, { @@ -227,30 +563,30 @@ describe.each([ target: 'es2020', }, test: { - setupFiles: baseSetupFiles, + setupFiles: setupPresets.e2e.base, }, }, ); }); it('should return the result from createVitestConfig', () => { - const result = createFn(MOCK_PROJECT_KEY, MOCK_CONFIG_REST_PARAMS); + const result = createE2eConfig(MOCK_PROJECT_KEY, MOCK_CONFIG_REST_PARAMS); expect(result).toBe('mocked-config'); }); it('should handle empty projectKey gracefully', () => { - const result = createFn('', MOCK_CONFIG_REST_PARAMS); + const result = createE2eConfig('', MOCK_CONFIG_REST_PARAMS); expect(configFactory.createVitestConfig).toHaveBeenCalledWith( { projectKey: '', - kind, + kind: 'e2e', ...MOCK_CONFIG_REST_PARAMS, }, { test: { - setupFiles: baseSetupFiles, + setupFiles: setupPresets.e2e.base, }, }, ); From 32ce2cb43c142a270719121b123aba9a2f95e607 Mon Sep 17 00:00:00 2001 From: "Szymon.Poltorak" Date: Wed, 29 Oct 2025 10:52:41 +0100 Subject: [PATCH 14/23] refactor(testing): standardize Vitest configuration and improve setup file management --- e2e/ci-e2e/vitest.e2e.config.ts | 27 +- .../vitest.e2e.config.ts | 18 +- packages/core/vitest.int.config.ts | 17 +- packages/core/vitest.unit.config.ts | 22 +- packages/utils/vitest.int.config.ts | 18 +- packages/utils/vitest.unit.config.ts | 24 +- testing/test-setup-config/README.md | 52 +- testing/test-setup-config/src/index.ts | 19 +- .../src/lib/vitest-config-factory.ts | 245 ++--- .../lib/vitest-config-factory.unit.test.ts | 836 +++++------------- .../src/lib/vitest-setup-files.ts | 72 ++ .../src/lib/vitest-setup-files.unit.test.ts | 175 ++++ .../src/lib/vitest-setup-presets.ts | 141 ++- .../src/lib/vitest-setup-presets.unit.test.ts | 755 +++------------- .../src/lib/vitest-tsconfig-path-aliases.ts | 18 +- .../test-setup-config/vitest.unit.config.ts | 23 +- 16 files changed, 811 insertions(+), 1651 deletions(-) create mode 100644 testing/test-setup-config/src/lib/vitest-setup-files.ts create mode 100644 testing/test-setup-config/src/lib/vitest-setup-files.unit.test.ts diff --git a/e2e/ci-e2e/vitest.e2e.config.ts b/e2e/ci-e2e/vitest.e2e.config.ts index 37ea143d8..50c5b9bbc 100644 --- a/e2e/ci-e2e/vitest.e2e.config.ts +++ b/e2e/ci-e2e/vitest.e2e.config.ts @@ -1,17 +1,16 @@ /// -import { createE2eConfig } from '../../testing/test-setup-config/src/lib/vitest-setup-presets.js'; +import type { UserConfig } from 'vite'; +import { createE2ETestConfig } from '../../testing/test-setup-config/src/index.js'; -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 }, - }, +const baseConfig = createE2ETestConfig('ci-e2e', { + testTimeout: 60_000, + disableCoverage: true, +}); + +export default { + ...baseConfig, + test: { + ...(baseConfig as any).test, + globalSetup: ['./global-setup.ts'], }, -); +} as UserConfig; diff --git a/e2e/plugin-typescript-e2e/vitest.e2e.config.ts b/e2e/plugin-typescript-e2e/vitest.e2e.config.ts index 1e9fd4768..41a602360 100644 --- a/e2e/plugin-typescript-e2e/vitest.e2e.config.ts +++ b/e2e/plugin-typescript-e2e/vitest.e2e.config.ts @@ -1,16 +1,6 @@ /// -import { createE2eConfig } from '../../testing/test-setup-config/src/lib/vitest-setup-presets.js'; +import { createE2ETestConfig } from '../../testing/test-setup-config/src/index.js'; -export default createE2eConfig( - 'plugin-typescript-e2e', - { - projectRoot: new URL('../../', import.meta.url), - cacheKey: 'plugin-typescript-e2e', - }, - { - test: { - testTimeout: 20_000, - coverage: { enabled: true }, - }, - }, -); +export default createE2ETestConfig('plugin-typescript-e2e', { + testTimeout: 20_000, +}); diff --git a/packages/core/vitest.int.config.ts b/packages/core/vitest.int.config.ts index 981ee4e19..f16b4cd5b 100644 --- a/packages/core/vitest.int.config.ts +++ b/packages/core/vitest.int.config.ts @@ -1,17 +1,4 @@ /// -import { - createIntConfig, - setupPresets, -} from '../../testing/test-setup-config/src/lib/vitest-setup-presets.js'; +import { createIntTestConfig } from '../../testing/test-setup-config/src/index.js'; -export default createIntConfig( - 'core', - { - projectRoot: new URL('../../', import.meta.url), - }, - { - test: { - setupFiles: [...setupPresets.int.base, ...setupPresets.int.portalClient], - }, - }, -); +export default createIntTestConfig('core'); diff --git a/packages/core/vitest.unit.config.ts b/packages/core/vitest.unit.config.ts index d11f288f9..698818f7a 100644 --- a/packages/core/vitest.unit.config.ts +++ b/packages/core/vitest.unit.config.ts @@ -1,22 +1,4 @@ /// -import { - createUnitConfig, - setupPresets, -} from '../../testing/test-setup-config/src/lib/vitest-setup-presets.js'; +import { createUnitTestConfig } from '../../testing/test-setup-config/src/index.js'; -export default createUnitConfig( - 'core', - { - projectRoot: new URL('../../', import.meta.url), - }, - { - test: { - setupFiles: [ - ...setupPresets.unit.base, - ...setupPresets.unit.git, - ...setupPresets.unit.portalClient, - ...setupPresets.unit.matchersCore, - ], - }, - }, -); +export default createUnitTestConfig('core'); diff --git a/packages/utils/vitest.int.config.ts b/packages/utils/vitest.int.config.ts index 1aeadf3d4..2ec648817 100644 --- a/packages/utils/vitest.int.config.ts +++ b/packages/utils/vitest.int.config.ts @@ -1,18 +1,4 @@ /// -import { - createIntConfig, - setupPresets, -} from '../../testing/test-setup-config/src/lib/vitest-setup-presets.js'; +import { createIntTestConfig } from '../../testing/test-setup-config/src/index.js'; -export default createIntConfig( - 'utils', - { - projectRoot: new URL('../../', import.meta.url), - }, - { - test: { - coverage: { exclude: ['perf/**'] }, - setupFiles: [...setupPresets.int.base, ...setupPresets.int.cliui], - }, - }, -); +export default createIntTestConfig('utils'); diff --git a/packages/utils/vitest.unit.config.ts b/packages/utils/vitest.unit.config.ts index 66bb6dd51..f85142065 100644 --- a/packages/utils/vitest.unit.config.ts +++ b/packages/utils/vitest.unit.config.ts @@ -1,24 +1,4 @@ /// -import { - createUnitConfig, - setupPresets, -} from '../../testing/test-setup-config/src/lib/vitest-setup-presets.js'; +import { createUnitTestConfig } from '../../testing/test-setup-config/src/index.js'; -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, - ], - }, - }, -); +export default createUnitTestConfig('utils'); diff --git a/testing/test-setup-config/README.md b/testing/test-setup-config/README.md index 42aa20ae8..7663efa9e 100644 --- a/testing/test-setup-config/README.md +++ b/testing/test-setup-config/README.md @@ -1,24 +1,50 @@ -## Vitest config factory and setup presets +## Vitest Config Factory -Utilities to centralize and standardize Vitest configuration across the monorepo. +Standardized Vitest configuration for the Code PushUp monorepo. -- `vitest-config-factory.ts`: builds typed Vitest configs with sensible defaults -- `vitest-setup-presets.ts`: provides create functions and exportable setup file groups +### Usage -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. +**Unit tests:** -### Examples +```typescript +import { createUnitTestConfig } from '@code-pushup/test-setup-config'; -**Using defaults:** +export default createUnitTestConfig('my-package'); +``` + +**Integration tests:** + +```typescript +import { createIntTestConfig } from '@code-pushup/test-setup-config'; -```ts -export default createUnitConfig('my-package', import.meta.url); +export default createIntTestConfig('my-package'); ``` -**Extending default setup files:** +**E2E tests:** + +```typescript +import { createE2ETestConfig } from '@code-pushup/test-setup-config'; + +export default createE2ETestConfig('my-e2e'); -```ts -export default createIntConfig('my-package', import.meta.url, { - setupFiles: [...setupPresets.int.base, ...setupPresets.int.git, './custom-setup.ts'], +// With options: +export default createE2ETestConfig('my-e2e', { + testTimeout: 60_000, + disableCoverage: true, }); ``` + +### Advanced: Overriding Config + +For edge cases, use the spread operator to override any property: + +```typescript +const baseConfig = createE2ETestConfig('my-e2e'); +export default { + ...baseConfig, + test: { + ...(baseConfig as any).test, + globalSetup: ['./custom-setup.ts'], + }, +}; +``` diff --git a/testing/test-setup-config/src/index.ts b/testing/test-setup-config/src/index.ts index ef10c13bc..68d9a93c6 100644 --- a/testing/test-setup-config/src/index.ts +++ b/testing/test-setup-config/src/index.ts @@ -1,14 +1,9 @@ export { - createVitestConfig, - type TestKind, - type VitestConfigFactoryOptions, - type VitestOverrides, - type ConfigRestParams, -} from './lib/vitest-config-factory.js'; - -export { - setupPresets, - createUnitConfig, - createIntConfig, - createE2eConfig, + createUnitTestConfig, + createIntTestConfig, + createE2ETestConfig, } from './lib/vitest-setup-presets.js'; + +export type { E2ETestOptions } from './lib/vitest-config-factory.js'; + +export { getSetupFiles } from './lib/vitest-setup-files.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 index 31882ce9a..f9baa7da3 100644 --- a/testing/test-setup-config/src/lib/vitest-config-factory.ts +++ b/testing/test-setup-config/src/lib/vitest-config-factory.ts @@ -1,223 +1,88 @@ import path from 'node:path'; -import { fileURLToPath, pathToFileURL } from 'node:url'; -import { - type UserConfig as ViteUserConfig, - defineConfig, - mergeConfig, -} from 'vite'; +import { type UserConfig as ViteUserConfig, defineConfig } from 'vite'; import type { CoverageOptions, InlineConfig } from 'vitest'; +import { getSetupFiles } from './vitest-setup-files.js'; 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 E2ETestOptions = { + testTimeout?: number; + disableCoverage?: boolean; }; -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]; +export type VitestConfig = ViteUserConfig & { test?: InlineConfig }; + +function getIncludePatterns(kind: TestKind): string[] { + switch (kind) { + case 'unit': + return [ + 'src/**/*.unit.test.{js,mjs,cjs,ts,mts,cts,jsx,tsx}', + 'src/**/*.type.test.{js,mjs,cjs,ts,mts,cts,jsx,tsx}', + ]; + case 'int': + return ['src/**/*.int.test.{js,mjs,cjs,ts,mts,cts,jsx,tsx}']; + case 'e2e': + return ['tests/**/*.e2e.test.{js,mjs,cjs,ts,mts,cts,jsx,tsx}']; + } } -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 getGlobalSetup(kind: TestKind): string[] | undefined { + return kind === 'e2e' ? undefined : ['../../global-setup.ts']; } -function defaultGlobalSetup( +function buildCoverageConfig( + projectKey: string, kind: TestKind, - projectRootUrl: URL, -): string[] | undefined { - return kind === 'e2e' - ? undefined - : [path.resolve(getProjectRootPath(projectRootUrl), 'global-setup.ts')]; -} + disableCoverage?: boolean, +): CoverageOptions | undefined { + if (disableCoverage || kind === 'e2e') { + return undefined; + } + + const defaultExclude = ['mocks/**', '**/types.ts', 'perf/**']; + const reportsDirectory = `../../coverage/${projectKey}/${kind}-tests`; -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, + exclude: 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}`, - ), +export function createVitestConfig( + projectKey: string, + kind: TestKind, + options?: E2ETestOptions, +): ViteUserConfig { + const coverage = buildCoverageConfig( + projectKey, + kind, + options?.disableCoverage, + ); + + const config: VitestConfig = { + cacheDir: `../../node_modules/.vite/${projectKey}`, test: { reporters: ['basic'], globals: true, cache: { - dir: path.resolve( - getProjectRootPath(params.projectRootUrl), - 'node_modules/.vitest', - ), + dir: '../../node_modules/.vitest', }, - alias: tsconfigPathAliases(params.projectRootUrl), + alias: tsconfigPathAliases(), 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, - }), - } + include: getIncludePatterns(kind), + globalSetup: getGlobalSetup(kind), + setupFiles: [...getSetupFiles(kind)], + ...(options?.testTimeout ? { testTimeout: options.testTimeout } : {}), + ...(coverage ? { coverage } : {}), + ...(kind === 'unit' + ? { typecheck: { include: ['**/*.type.test.ts'] } } : {}), }, }; - 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}`; - } + return defineConfig(config); } 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 index 9eae528fb..eacf122b0 100644 --- 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 @@ -1,17 +1,8 @@ -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 +import type { E2ETestOptions, TestKind } from './vitest-config-factory.js'; +import { createVitestConfig } from './vitest-config-factory.js'; + vi.mock('vite', async importOriginal => { const actual = await importOriginal(); return { @@ -20,725 +11,354 @@ vi.mock('vite', async importOriginal => { }; }); -// 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' }), + tsconfigPathAliases: vi + .fn() + .mockReturnValue([{ find: '@test/alias', replacement: '/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); + describe('unit test configuration', () => { + it('should create a complete unit test config with all defaults', () => { + const config = createVitestConfig('test-package', 'unit'); expect(config).toEqual( expect.objectContaining({ - cacheDir: mockPath('node_modules', '.vite', 'cache-test-package'), + cacheDir: '../../node_modules/.vite/test-package', test: expect.objectContaining({ reporters: ['basic'], globals: true, - cache: { - dir: mockPath('node_modules', '.vitest'), - }, - alias: expect.any(Object), + cache: { dir: '../../node_modules/.vitest' }, + alias: expect.any(Array), pool: 'threads', poolOptions: { threads: { singleThread: true } }, environment: 'node', - include: EXPECTED_INCLUDES.unit, - globalSetup: [mockPath('global-setup.ts')], - setupFiles: [], + include: [ + 'src/**/*.unit.test.{js,mjs,cjs,ts,mts,cts,jsx,tsx}', + 'src/**/*.type.test.{js,mjs,cjs,ts,mts,cts,jsx,tsx}', + ], + globalSetup: ['../../global-setup.ts'], + setupFiles: expect.arrayContaining([ + '../../testing/test-setup/src/lib/console.mock.ts', + '../../testing/test-setup/src/lib/reset.mocks.ts', + '../../testing/test-setup/src/lib/fs.mock.ts', + ]), coverage: expect.objectContaining({ reporter: ['text', 'lcov'], - reportsDirectory: mockPath( - 'packages', - 'test-package', - '.coverage', - 'unit-tests', - ), - exclude: DEFAULT_EXCLUDES, + reportsDirectory: '../../coverage/test-package/unit-tests', + exclude: ['mocks/**', '**/types.ts', 'perf/**'], }), + typecheck: { include: ['**/*.type.test.ts'] }, }), }), ); 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); + it('should include all required setup files for unit tests', () => { + const config = createVitestConfig('test-package', 'unit'); - 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'), - ], - }), - }), + const setupFiles = (config as any).test.setupFiles; + expect(setupFiles).toContain( + '../../testing/test-setup/src/lib/console.mock.ts', + ); + expect(setupFiles).toContain( + '../../testing/test-setup/src/lib/reset.mocks.ts', + ); + expect(setupFiles).toContain( + '../../testing/test-setup/src/lib/cliui.mock.ts', + ); + expect(setupFiles).toContain( + '../../testing/test-setup/src/lib/fs.mock.ts', + ); + expect(setupFiles).toContain( + '../../testing/test-setup/src/lib/git.mock.ts', + ); + expect(setupFiles).toContain( + '../../testing/test-setup/src/lib/portal-client.mock.ts', + ); + expect(setupFiles).toContain( + '../../testing/test-setup/src/lib/extend/ui-logger.matcher.ts', + ); + expect(setupFiles).toContain( + '../../testing/test-setup/src/lib/extend/markdown-table.matcher.ts', + ); + expect(setupFiles).toContain( + '../../testing/test-setup/src/lib/extend/jest-extended.matcher.ts', + ); + expect(setupFiles).toContain( + '../../testing/test-setup/src/lib/extend/path.matcher.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); + it('should include type test pattern in unit tests', () => { + const config = createVitestConfig('test-package', 'unit'); - expect((config as any).test.alias).toBeDefined(); + expect((config as any).test.include).toContain( + 'src/**/*.type.test.{js,mjs,cjs,ts,mts,cts,jsx,tsx}', + ); }); - it('should handle projectRoot string with trailing slash', () => { - const projectRoot = '/Users/test/project/'; - const options: VitestConfigFactoryOptions = { - projectKey: 'test-package', - kind: 'unit', - projectRoot, - }; + it('should enable typecheck for unit tests', () => { + const config = createVitestConfig('test-package', 'unit'); + + expect((config as any).test.typecheck).toEqual({ + include: ['**/*.type.test.ts'], + }); + }); - const config = createVitestConfig(options); + it('should always include perf/** in coverage exclusions', () => { + const config = createVitestConfig('test-package', 'unit'); - expect((config as any).test.alias).toBeDefined(); + expect((config as any).test.coverage.exclude).toContain('perf/**'); }); }); - 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); + describe('integration test configuration', () => { + it('should create a complete integration test config', () => { + const config = createVitestConfig('test-package', 'int'); expect(config).toEqual( expect.objectContaining({ + cacheDir: '../../node_modules/.vite/test-package', test: expect.objectContaining({ - include: EXPECTED_INCLUDES.int, - globalSetup: [mockPath('global-setup.ts')], + reporters: ['basic'], + globals: true, + include: ['src/**/*.int.test.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], + globalSetup: ['../../global-setup.ts'], coverage: expect.objectContaining({ - reportsDirectory: mockPath( - 'packages', - 'test-package', - '.coverage', - 'int-tests', - ), + reportsDirectory: '../../coverage/test-package/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); + it('should include correct setup files for integration tests', () => { + const config = createVitestConfig('test-package', 'int'); - expect(config).toEqual( - expect.objectContaining({ - test: expect.objectContaining({ - include: EXPECTED_INCLUDES.e2e, - globalSetup: undefined, - }), - }), + const setupFiles = (config as any).test.setupFiles; + // Should include console mock + expect(setupFiles).toContain( + '../../testing/test-setup/src/lib/console.mock.ts', ); - - 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, - }), - }), - }), + // Should NOT include fs, cliui, or git mocks (integration tests need real implementations) + expect(setupFiles).not.toContain( + '../../testing/test-setup/src/lib/fs.mock.ts', ); - }); - }); - - 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'), - }), + expect(setupFiles).not.toContain( + '../../testing/test-setup/src/lib/cliui.mock.ts', + ); + expect(setupFiles).not.toContain( + '../../testing/test-setup/src/lib/git.mock.ts', + ); + // Should include all matchers + expect(setupFiles).toContain( + '../../testing/test-setup/src/lib/extend/path.matcher.ts', ); }); - 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); + it('should not enable typecheck for integration tests', () => { + const config = createVitestConfig('test-package', 'int'); - expect(config).toEqual( - expect.objectContaining({ - cacheDir: mockPath('node_modules', '.vite', 'cache-test-package'), - }), - ); + expect((config as any).test.typecheck).toBeUndefined(); }); }); - 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); + describe('e2e test configuration', () => { + it('should create e2e config without coverage by default', () => { + const config = createVitestConfig('test-package', 'e2e'); expect(config).toEqual( expect.objectContaining({ + cacheDir: '../../node_modules/.vite/test-package', test: expect.objectContaining({ - setupFiles: [mockPath('setup.ts')], + reporters: ['basic'], + globals: true, + include: ['tests/**/*.e2e.test.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], + globalSetup: undefined, }), }), ); + expect((config as any).test.coverage).toBeUndefined(); }); - 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); + it('should include minimal setup files for e2e tests', () => { + const config = createVitestConfig('test-package', 'e2e'); - expect(config).toEqual( - expect.objectContaining({ - test: expect.objectContaining({ - setupFiles: [mockPath('setup1.ts'), mockPath('setup2.ts')], - }), - }), + const setupFiles = (config as any).test.setupFiles; + // Should only include reset mocks + expect(setupFiles).toContain( + '../../testing/test-setup/src/lib/reset.mocks.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'), - ], - }), - }), + // Should NOT include console, fs, git, etc. + expect(setupFiles).not.toContain( + '../../testing/test-setup/src/lib/console.mock.ts', + ); + expect(setupFiles).not.toContain( + '../../testing/test-setup/src/lib/fs.mock.ts', + ); + // Should include all matchers + expect(setupFiles).toContain( + '../../testing/test-setup/src/lib/extend/path.matcher.ts', ); }); - it('should handle empty setupFiles array', () => { - const options: VitestConfigFactoryOptions = { - projectKey: 'test-package', - kind: 'unit', - projectRoot: MOCK_PROJECT_ROOT_STRING, - }; + it('should support custom testTimeout option', () => { + const options: E2ETestOptions = { testTimeout: 60_000 }; + const config = createVitestConfig('test-package', 'e2e', options); - const overrides: VitestOverrides = { - test: { - setupFiles: [], - }, - }; + expect((config as any).test.testTimeout).toBe(60_000); + }); - const config = createVitestConfig(options, overrides); + it('should support disableCoverage option', () => { + const options: E2ETestOptions = { disableCoverage: true }; + const config = createVitestConfig('test-package', 'e2e', options); - expect(config).toEqual( - expect.objectContaining({ - test: expect.objectContaining({ - setupFiles: [], - }), - }), - ); + expect((config as any).test.coverage).toBeUndefined(); }); - it('should use empty setupFiles when not provided in overrides', () => { - const options: VitestConfigFactoryOptions = { - projectKey: 'test-package', - kind: 'unit', - projectRoot: MOCK_PROJECT_ROOT_STRING, + it('should support multiple options together', () => { + const options: E2ETestOptions = { + testTimeout: 30_000, + disableCoverage: true, }; + const config = createVitestConfig('test-package', 'e2e', options); - const config = createVitestConfig(options, {}); - - expect(config).toEqual( - expect.objectContaining({ - test: expect.objectContaining({ - setupFiles: [], - }), - }), - ); + expect((config as any).test.testTimeout).toBe(30_000); + expect((config as any).test.coverage).toBeUndefined(); }); }); - 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/**'], - }, - }, - }; + describe('cacheDir naming', () => { + it('should use projectKey for cacheDir', () => { + const config = createVitestConfig('my-custom-name', 'unit'); - 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/**'], - }), - }), - }), - ); + expect(config.cacheDir).toBe('../../node_modules/.vite/my-custom-name'); }); - 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); + it('should use projectKey for coverage directory', () => { + const config = createVitestConfig('my-package', 'unit'); - 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, - }), - }), - }), + expect((config as any).test.coverage.reportsDirectory).toBe( + '../../coverage/my-package/unit-tests', ); }); + }); - 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, - }, - }, - }; + describe('test kind variations', () => { + it('should handle all test kinds correctly', () => { + const testKinds: TestKind[] = ['unit', 'int', 'e2e']; - const config = createVitestConfig(options, overrides); + testKinds.forEach(kind => { + const config = createVitestConfig('test-package', kind); - expect((config as any).test.coverage).toStrictEqual({}); - expect(config).toBeDefined(); - }); + const expectedIncludes = { + unit: [ + 'src/**/*.unit.test.{js,mjs,cjs,ts,mts,cts,jsx,tsx}', + 'src/**/*.type.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}'], + }; - 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, - }; + expect((config as any).test.include).toStrictEqual( + expectedIncludes[kind], + ); - const overrides: VitestOverrides = { - test: { - coverage: { - enabled: true, - exclude: ['custom/**'], - reporter: ['html', 'json'], - thresholds: { - global: { - branches: 80, - functions: 80, - lines: 80, - statements: 80, - }, - }, - }, - }, - }; + const expectedGlobalSetup = { + unit: ['../../global-setup.ts'], + int: ['../../global-setup.ts'], + e2e: undefined, + }; - 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 as any).test.globalSetup).toStrictEqual( + expectedGlobalSetup[kind], + ); }); - - 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); + describe('coverage configuration', () => { + it('should enable coverage for unit tests by default', () => { + const config = createVitestConfig('test-package', 'unit'); - const testConfig = (config as any).test; - expect(testConfig.testTimeout).toBe(TEST_TIMEOUTS.MEDIUM); - expect(testConfig.pool).toBe('forks'); + expect((config as any).test.coverage).toBeDefined(); + expect((config as any).test.coverage.reporter).toEqual(['text', 'lcov']); }); - it('should sanitize overrides by removing setupFiles from test config', () => { - const options: VitestConfigFactoryOptions = { - projectKey: 'test-package', - kind: 'unit', - projectRoot: MOCK_PROJECT_ROOT_STRING, - }; + it('should enable coverage for integration tests by default', () => { + const config = createVitestConfig('test-package', 'int'); - 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'); + expect((config as any).test.coverage).toBeDefined(); }); - it('should handle overrides without test config', () => { - const options: VitestConfigFactoryOptions = { - projectKey: 'test-package', - kind: 'unit', - projectRoot: MOCK_PROJECT_ROOT_STRING, - }; + it('should disable coverage for e2e tests by default', () => { + const config = createVitestConfig('test-package', 'e2e'); - const overrides: VitestOverrides = { - build: { - target: 'node14', - }, - }; + expect((config as any).test.coverage).toBeUndefined(); + }); - const config = createVitestConfig(options, overrides); + it('should always exclude mocks, types.ts, and perf folders', () => { + const config = createVitestConfig('test-package', 'unit'); - expect((config as any).build.target).toBe('node14'); + expect((config as any).test.coverage.exclude).toEqual([ + 'mocks/**', + '**/types.ts', + 'perf/**', + ]); }); + }); - 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, - }, - }; + describe('relative paths', () => { + it('should use relative paths for all file references', () => { + const config = createVitestConfig('test-package', 'unit'); - 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, - }); - }); + // Setup files should be relative + const setupFiles = (config as any).test.setupFiles; + expect(setupFiles[0]).toMatch(/^\.\.\/\.\.\//); - it('should handle coverage options as null', () => { - const options: VitestConfigFactoryOptions = { - projectKey: 'test-package', - kind: 'unit', - projectRoot: MOCK_PROJECT_ROOT_STRING, - }; + // GlobalSetup should be relative + expect((config as any).test.globalSetup[0]).toBe('../../global-setup.ts'); - const overrides: VitestOverrides = { - test: { - coverage: null as any, - testTimeout: TEST_TIMEOUTS.SHORT, - }, - }; + // Cache dirs should be relative + expect(config.cacheDir).toMatch(/^\.\.\/\.\.\//); + expect((config as any).test.cache.dir).toMatch(/^\.\.\/\.\.\//); - 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, - }); + // Coverage directory should be relative + expect((config as any).test.coverage.reportsDirectory).toMatch( + /^\.\.\/\.\.\//, + ); }); }); - 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); + describe('edge cases', () => { + it('should handle empty projectKey gracefully', () => { + const config = createVitestConfig('', 'unit'); - 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')], - ); - }); + expect(config.cacheDir).toBe('../../node_modules/.vite/'); + expect((config as any).test.coverage.reportsDirectory).toBe( + '../../coverage//unit-tests', + ); }); - it('should handle complex override scenarios', () => { - const options: VitestConfigFactoryOptions = { - projectKey: 'test-package', - kind: 'int', - projectRoot: MOCK_PROJECT_ROOT_STRING, - cacheKey: 'complex-scenario', - }; + it('should handle projectKey with special characters', () => { + const config = createVitestConfig('my-special_package.v2', 'unit'); - 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', - }, - }; + expect(config.cacheDir).toBe( + '../../node_modules/.vite/my-special_package.v2', + ); + }); - const config = createVitestConfig(options, overrides); + it('should not modify config when no options provided to e2e', () => { + const config = createVitestConfig('test-package', 'e2e'); - 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', - ), - }), - }), - }), - ); + expect((config as any).test.testTimeout).toBeUndefined(); + expect((config as any).test.globalSetup).toBeUndefined(); }); }); }); diff --git a/testing/test-setup-config/src/lib/vitest-setup-files.ts b/testing/test-setup-config/src/lib/vitest-setup-files.ts new file mode 100644 index 000000000..332c9f759 --- /dev/null +++ b/testing/test-setup-config/src/lib/vitest-setup-files.ts @@ -0,0 +1,72 @@ +import type { TestKind } from './vitest-config-factory.js'; + +/** + * Setup files for unit tests. + * + * These paths are relative to the config file location (typically `packages//vitest.unit.config.ts`), + * which is why they use `../../` to navigate to the workspace root first. + */ +const UNIT_TEST_SETUP_FILES = [ + '../../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/git.mock.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', + '../../testing/test-setup/src/lib/extend/path.matcher.ts', +] as const; + +/** + * Setup files for integration tests. + * + * These paths are relative to the config file location (typically `packages//vitest.int.config.ts`), + * which is why they use `../../` to navigate to the workspace root first. + */ +const INT_TEST_SETUP_FILES = [ + '../../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/jest-extended.matcher.ts', + '../../testing/test-setup/src/lib/extend/path.matcher.ts', +] as const; + +/** + * Setup files for E2E tests. + * + * These paths are relative to the config file location (typically `e2e//vitest.e2e.config.ts`), + * which is why they use `../../` to navigate to the workspace root first. + */ +const E2E_TEST_SETUP_FILES = [ + '../../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/jest-extended.matcher.ts', + '../../testing/test-setup/src/lib/extend/path.matcher.ts', +] as const; + +/** + * Returns the appropriate setup files for the given test kind. + * + * @param kind - The type of test (unit, int, or e2e) + * @returns Array of setup file paths relative to the config file location + * + * @example + * ```typescript + * const setupFiles = getSetupFiles('unit'); + * // Returns all unit test setup files including mocks and matchers + * ``` + */ +export function getSetupFiles(kind: TestKind): readonly string[] { + switch (kind) { + case 'unit': + return UNIT_TEST_SETUP_FILES; + case 'int': + return INT_TEST_SETUP_FILES; + case 'e2e': + return E2E_TEST_SETUP_FILES; + } +} diff --git a/testing/test-setup-config/src/lib/vitest-setup-files.unit.test.ts b/testing/test-setup-config/src/lib/vitest-setup-files.unit.test.ts new file mode 100644 index 000000000..b3ad4c445 --- /dev/null +++ b/testing/test-setup-config/src/lib/vitest-setup-files.unit.test.ts @@ -0,0 +1,175 @@ +import { describe, expect, it } from 'vitest'; +import { getSetupFiles } from './vitest-setup-files.js'; + +describe('vitest-setup-files', () => { + describe('getSetupFiles', () => { + describe('unit test setup files', () => { + it('should return all required setup files for unit tests', () => { + const setupFiles = getSetupFiles('unit'); + + expect(setupFiles).toHaveLength(10); + expect(setupFiles).toContain( + '../../testing/test-setup/src/lib/console.mock.ts', + ); + expect(setupFiles).toContain( + '../../testing/test-setup/src/lib/reset.mocks.ts', + ); + expect(setupFiles).toContain( + '../../testing/test-setup/src/lib/cliui.mock.ts', + ); + expect(setupFiles).toContain( + '../../testing/test-setup/src/lib/fs.mock.ts', + ); + expect(setupFiles).toContain( + '../../testing/test-setup/src/lib/git.mock.ts', + ); + expect(setupFiles).toContain( + '../../testing/test-setup/src/lib/portal-client.mock.ts', + ); + expect(setupFiles).toContain( + '../../testing/test-setup/src/lib/extend/ui-logger.matcher.ts', + ); + expect(setupFiles).toContain( + '../../testing/test-setup/src/lib/extend/markdown-table.matcher.ts', + ); + expect(setupFiles).toContain( + '../../testing/test-setup/src/lib/extend/jest-extended.matcher.ts', + ); + expect(setupFiles).toContain( + '../../testing/test-setup/src/lib/extend/path.matcher.ts', + ); + }); + }); + + describe('integration test setup files', () => { + it('should return exactly 6 setup files with correct includes', () => { + const setupFiles = getSetupFiles('int'); + + expect(setupFiles).toHaveLength(6); + expect(setupFiles).toContain( + '../../testing/test-setup/src/lib/console.mock.ts', + ); + expect(setupFiles).toContain( + '../../testing/test-setup/src/lib/reset.mocks.ts', + ); + expect(setupFiles).toContain( + '../../testing/test-setup/src/lib/extend/ui-logger.matcher.ts', + ); + expect(setupFiles).toContain( + '../../testing/test-setup/src/lib/extend/markdown-table.matcher.ts', + ); + expect(setupFiles).toContain( + '../../testing/test-setup/src/lib/extend/jest-extended.matcher.ts', + ); + expect(setupFiles).toContain( + '../../testing/test-setup/src/lib/extend/path.matcher.ts', + ); + }); + + it('should NOT include fs, cliui, git, and portal-client mocks for integration tests', () => { + const setupFiles = getSetupFiles('int'); + + expect(setupFiles).not.toContain( + '../../testing/test-setup/src/lib/fs.mock.ts', + ); + expect(setupFiles).not.toContain( + '../../testing/test-setup/src/lib/cliui.mock.ts', + ); + expect(setupFiles).not.toContain( + '../../testing/test-setup/src/lib/git.mock.ts', + ); + expect(setupFiles).not.toContain( + '../../testing/test-setup/src/lib/portal-client.mock.ts', + ); + }); + }); + + describe('e2e test setup files', () => { + it('should return exactly 5 setup files with minimal mocks', () => { + const setupFiles = getSetupFiles('e2e'); + + expect(setupFiles).toHaveLength(5); + expect(setupFiles).toContain( + '../../testing/test-setup/src/lib/reset.mocks.ts', + ); + expect(setupFiles).toContain( + '../../testing/test-setup/src/lib/extend/ui-logger.matcher.ts', + ); + expect(setupFiles).toContain( + '../../testing/test-setup/src/lib/extend/markdown-table.matcher.ts', + ); + expect(setupFiles).toContain( + '../../testing/test-setup/src/lib/extend/jest-extended.matcher.ts', + ); + expect(setupFiles).toContain( + '../../testing/test-setup/src/lib/extend/path.matcher.ts', + ); + }); + + it('should NOT include any other mocks for e2e tests', () => { + const setupFiles = getSetupFiles('e2e'); + + expect(setupFiles).not.toContain( + '../../testing/test-setup/src/lib/console.mock.ts', + ); + expect(setupFiles).not.toContain( + '../../testing/test-setup/src/lib/fs.mock.ts', + ); + expect(setupFiles).not.toContain( + '../../testing/test-setup/src/lib/git.mock.ts', + ); + expect(setupFiles).not.toContain( + '../../testing/test-setup/src/lib/cliui.mock.ts', + ); + expect(setupFiles).not.toContain( + '../../testing/test-setup/src/lib/portal-client.mock.ts', + ); + }); + }); + + describe('relative paths', () => { + it('should return paths relative to config file location', () => { + const unitFiles = getSetupFiles('unit'); + const intFiles = getSetupFiles('int'); + const e2eFiles = getSetupFiles('e2e'); + + // All paths should start with ../../ + [...unitFiles, ...intFiles, ...e2eFiles].forEach(path => { + expect(path).toMatch(/^\.\.\/\.\.\//); + }); + }); + }); + + describe('return type', () => { + it('should return a readonly array', () => { + const setupFiles = getSetupFiles('unit'); + + // TypeScript will enforce readonly at compile time, + // but we can verify it's an array at runtime + expect(Array.isArray(setupFiles)).toBe(true); + }); + }); + + describe('test kind differences', () => { + it('should return different setup files for different test kinds', () => { + const unitFiles = getSetupFiles('unit'); + const intFiles = getSetupFiles('int'); + const e2eFiles = getSetupFiles('e2e'); + + // Different lengths means different setup files + expect(unitFiles.length).not.toBe(intFiles.length); + expect(intFiles.length).not.toBe(e2eFiles.length); + expect(unitFiles.length).not.toBe(e2eFiles.length); + }); + + it('should show hierarchy: unit has most, e2e has least', () => { + const unitFiles = getSetupFiles('unit'); + const intFiles = getSetupFiles('int'); + const e2eFiles = getSetupFiles('e2e'); + + expect(unitFiles.length).toBeGreaterThan(intFiles.length); + expect(intFiles.length).toBeGreaterThan(e2eFiles.length); + }); + }); + }); +}); diff --git a/testing/test-setup-config/src/lib/vitest-setup-presets.ts b/testing/test-setup-config/src/lib/vitest-setup-presets.ts index d887747fa..e0aaf3b47 100644 --- a/testing/test-setup-config/src/lib/vitest-setup-presets.ts +++ b/testing/test-setup-config/src/lib/vitest-setup-presets.ts @@ -1,84 +1,71 @@ +import type { UserConfig as ViteUserConfig } from 'vite'; import { - type ConfigRestParams, - type VitestOverrides, + type E2ETestOptions, 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'; +/** + * Creates a standardized Vitest configuration for unit tests. + * + * @param projectKey - The project name (used for cache and coverage directory naming) + * @returns Vitest configuration object + * + * @example + * ```typescript + * export default createUnitTestConfig('my-package'); + * ``` + */ +export function createUnitTestConfig(projectKey: string): ViteUserConfig { + return createVitestConfig(projectKey, 'unit'); +} -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; +/** + * Creates a standardized Vitest configuration for integration tests. + * + * @param projectKey - The project name (used for cache and coverage directory naming) + * @returns Vitest configuration object + * + * @example + * ```typescript + * export default createIntTestConfig('my-package'); + * ``` + */ +export function createIntTestConfig(projectKey: string): ViteUserConfig { + return createVitestConfig(projectKey, 'int'); +} -export const createUnitConfig = ( +/** + * Creates a standardized Vitest configuration for E2E tests. + * + * @param projectKey - The project name (used for cache and coverage directory naming) + * @param options - Optional configuration for E2E tests + * @returns Vitest configuration object + * + * @example + * ```typescript + * // Basic usage + * export default createE2ETestConfig('my-e2e'); + * + * // With options + * export default createE2ETestConfig('my-e2e', { + * testTimeout: 60_000, + * disableCoverage: true, + * }); + * + * // Override any config using spread operator + * const baseConfig = createE2ETestConfig('my-e2e', { testTimeout: 60_000 }); + * export default { + * ...baseConfig, + * test: { + * ...(baseConfig as any).test, + * globalSetup: ['./custom-setup.ts'], + * }, + * }; + * ``` + */ +export function createE2ETestConfig( 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 } }, - ); -}; + options?: E2ETestOptions, +): ViteUserConfig { + return createVitestConfig(projectKey, 'e2e', options); +} 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 index cc303dd87..984e56c44 100644 --- 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 @@ -1,11 +1,9 @@ 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, + createE2ETestConfig, + createIntTestConfig, + createUnitTestConfig, } from './vitest-setup-presets.js'; vi.mock('./vitest-config-factory.js', () => ({ @@ -13,655 +11,170 @@ vi.mock('./vitest-config-factory.js', () => ({ })); 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', () => { +describe('vitest-setup-presets', () => { 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); + describe('createUnitTestConfig', () => { + it('should call createVitestConfig with unit kind', () => { + const result = createUnitTestConfig(MOCK_PROJECT_KEY); - 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'); - }); + expect(configFactory.createVitestConfig).toHaveBeenCalledWith( + MOCK_PROJECT_KEY, + 'unit', + ); + expect(result).toBe('mocked-config'); + }); - it('should handle empty projectKey gracefully', () => { - const result = createIntConfig('', MOCK_CONFIG_REST_PARAMS); + it('should handle different project names', () => { + createUnitTestConfig('my-custom-package'); - 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(); - }); + expect(configFactory.createVitestConfig).toHaveBeenCalledWith( + 'my-custom-package', + 'unit', + ); + }); - it('should call createVitestConfig with correct parameters and default setupFiles', () => { - createE2eConfig(MOCK_PROJECT_KEY, MOCK_CONFIG_REST_PARAMS); + it('should handle empty projectKey', () => { + createUnitTestConfig(''); - expect(configFactory.createVitestConfig).toHaveBeenCalledWith( - { - projectKey: MOCK_PROJECT_KEY, - kind: 'e2e', - ...MOCK_CONFIG_REST_PARAMS, - }, - { - test: { - setupFiles: setupPresets.e2e.base, - }, - }, - ); + expect(configFactory.createVitestConfig).toHaveBeenCalledWith('', 'unit'); + }); }); - 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, - }, - }, - ); - }); + describe('createIntTestConfig', () => { + it('should call createVitestConfig with int kind', () => { + const result = createIntTestConfig(MOCK_PROJECT_KEY); - it('should merge other overrides correctly while using default setupFiles', () => { - const overrides: VitestOverrides = { - test: { - testTimeout: TEST_TIMEOUTS.MEDIUM, - globals: false, - }, - build: { - target: 'es2020', - }, - }; + expect(configFactory.createVitestConfig).toHaveBeenCalledWith( + MOCK_PROJECT_KEY, + 'int', + ); + expect(result).toBe('mocked-config'); + }); - createE2eConfig(MOCK_PROJECT_KEY, MOCK_CONFIG_REST_PARAMS, overrides); + it('should handle different project names', () => { + createIntTestConfig('integration-package'); - 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', - }, - }, - ); + expect(configFactory.createVitestConfig).toHaveBeenCalledWith( + 'integration-package', + 'int', + ); + }); }); - 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, - }, - }; + describe('createE2ETestConfig', () => { + it('should call createVitestConfig with e2e kind and no options', () => { + const result = createE2ETestConfig(MOCK_PROJECT_KEY); - 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', - }, - }, - ); - }); + expect(configFactory.createVitestConfig).toHaveBeenCalledWith( + MOCK_PROJECT_KEY, + 'e2e', + undefined, + ); + expect(result).toBe('mocked-config'); + }); - it('should handle undefined overrides', () => { - createE2eConfig(MOCK_PROJECT_KEY, MOCK_CONFIG_REST_PARAMS, undefined); + it('should pass options to createVitestConfig', () => { + const options = { + testTimeout: 60_000, + disableCoverage: true, + }; - expect(configFactory.createVitestConfig).toHaveBeenCalledWith( - { - projectKey: MOCK_PROJECT_KEY, - kind: 'e2e', - ...MOCK_CONFIG_REST_PARAMS, - }, - { - test: { - setupFiles: setupPresets.e2e.base, - }, - }, - ); - }); + createE2ETestConfig(MOCK_PROJECT_KEY, options); - it('should handle overrides without test config', () => { - const overrides: VitestOverrides = { - build: { - target: 'es2020', - }, - }; + expect(configFactory.createVitestConfig).toHaveBeenCalledWith( + MOCK_PROJECT_KEY, + 'e2e', + options, + ); + }); - createE2eConfig(MOCK_PROJECT_KEY, MOCK_CONFIG_REST_PARAMS, overrides); + it('should handle testTimeout option', () => { + createE2ETestConfig(MOCK_PROJECT_KEY, { testTimeout: 30_000 }); - expect(configFactory.createVitestConfig).toHaveBeenCalledWith( - { - projectKey: MOCK_PROJECT_KEY, - kind: 'e2e', - ...MOCK_CONFIG_REST_PARAMS, - }, - { - build: { - target: 'es2020', - }, - test: { - setupFiles: setupPresets.e2e.base, - }, - }, - ); - }); + expect(configFactory.createVitestConfig).toHaveBeenCalledWith( + MOCK_PROJECT_KEY, + 'e2e', + { testTimeout: 30_000 }, + ); + }); - it('should return the result from createVitestConfig', () => { - const result = createE2eConfig(MOCK_PROJECT_KEY, MOCK_CONFIG_REST_PARAMS); + it('should handle disableCoverage option', () => { + createE2ETestConfig(MOCK_PROJECT_KEY, { disableCoverage: true }); - expect(result).toBe('mocked-config'); - }); + expect(configFactory.createVitestConfig).toHaveBeenCalledWith( + MOCK_PROJECT_KEY, + 'e2e', + { disableCoverage: true }, + ); + }); - it('should handle empty projectKey gracefully', () => { - const result = createE2eConfig('', MOCK_CONFIG_REST_PARAMS); + it('should handle multiple options', () => { + const options = { + testTimeout: 45_000, + disableCoverage: false, + }; - expect(configFactory.createVitestConfig).toHaveBeenCalledWith( - { - projectKey: '', - kind: 'e2e', - ...MOCK_CONFIG_REST_PARAMS, - }, - { - test: { - setupFiles: setupPresets.e2e.base, - }, - }, - ); - expect(result).toBe('mocked-config'); - }); -}); + createE2ETestConfig(MOCK_PROJECT_KEY, options); -describe('integration between preset functions', () => { - beforeEach(() => { - vi.clearAllMocks(); + expect(configFactory.createVitestConfig).toHaveBeenCalledWith( + MOCK_PROJECT_KEY, + 'e2e', + options, + ); + }); }); - 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 } }, - ); + describe('function naming', () => { + it('should use clear descriptive names', () => { + expect(createUnitTestConfig).toBeDefined(); + expect(createIntTestConfig).toBeDefined(); + expect(createE2ETestConfig).toBeDefined(); + }); }); - 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']); + describe('integration with factory', () => { + it('should call factory with correct test kinds', () => { + createUnitTestConfig('pkg1'); + createIntTestConfig('pkg2'); + createE2ETestConfig('pkg3'); + + expect(configFactory.createVitestConfig).toHaveBeenNthCalledWith( + 1, + 'pkg1', + 'unit', + ); + expect(configFactory.createVitestConfig).toHaveBeenNthCalledWith( + 2, + 'pkg2', + 'int', + ); + expect(configFactory.createVitestConfig).toHaveBeenNthCalledWith( + 3, + 'pkg3', + 'e2e', + undefined, + ); }); - expect(calls[0][0].kind).toBe('unit'); - expect(calls[1][0].kind).toBe('int'); - expect(calls[2][0].kind).toBe('e2e'); + it('should return whatever the factory returns', () => { + const mockConfigs = { + unit: { test: 'unit-config' }, + int: { test: 'int-config' }, + e2e: { test: 'e2e-config' }, + }; + + vi.mocked(configFactory.createVitestConfig) + .mockReturnValueOnce(mockConfigs.unit as any) + .mockReturnValueOnce(mockConfigs.int as any) + .mockReturnValueOnce(mockConfigs.e2e as any); + + expect(createUnitTestConfig('test')).toBe(mockConfigs.unit); + expect(createIntTestConfig('test')).toBe(mockConfigs.int); + expect(createE2ETestConfig('test')).toBe(mockConfigs.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 index aec88e199..f1a9cc0c3 100644 --- a/testing/test-setup-config/src/lib/vitest-tsconfig-path-aliases.ts +++ b/testing/test-setup-config/src/lib/vitest-tsconfig-path-aliases.ts @@ -1,27 +1,29 @@ 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'; +/** + * Loads TypeScript path aliases from tsconfig.base.json for use in Vitest. + * Uses process.cwd() as the workspace root to load the tsconfig. + */ +export function tsconfigPathAliases(): AliasOptions { + const tsconfigPath = path.resolve(process.cwd(), '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, + // Make paths relative to workspace root (../../ from config file) + replacement: path.resolve(process.cwd(), relativePath), }), ); } diff --git a/testing/test-setup-config/vitest.unit.config.ts b/testing/test-setup-config/vitest.unit.config.ts index 95bef2c28..1c135686a 100644 --- a/testing/test-setup-config/vitest.unit.config.ts +++ b/testing/test-setup-config/vitest.unit.config.ts @@ -1,23 +1,4 @@ /// -import { - createUnitConfig, - setupPresets, -} from './src/lib/vitest-setup-presets.js'; +import { createUnitTestConfig } from './src/index.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}'], - }, - }, - }, -); +export default createUnitTestConfig('test-setup-config'); From 5f32d0d805437fc0ef377868a3fd91dc51051c13 Mon Sep 17 00:00:00 2001 From: "Szymon.Poltorak" Date: Wed, 29 Oct 2025 16:32:40 +0100 Subject: [PATCH 15/23] refactor(testing): consolidate global setup logic and remove redundant files --- e2e/ci-e2e/global-setup.ts | 16 ------------ e2e/ci-e2e/tsconfig.test.json | 3 +-- e2e/ci-e2e/vitest.e2e.config.ts | 12 +-------- global-setup.ts | 17 +++++++++++- testing/test-setup-config/README.md | 1 - .../src/lib/vitest-config-factory.ts | 13 +++------- .../lib/vitest-config-factory.unit.test.ts | 14 +++------- .../src/lib/vitest-setup-presets.ts | 3 +-- .../src/lib/vitest-setup-presets.unit.test.ts | 26 ------------------- 9 files changed, 25 insertions(+), 80 deletions(-) delete mode 100644 e2e/ci-e2e/global-setup.ts diff --git a/e2e/ci-e2e/global-setup.ts b/e2e/ci-e2e/global-setup.ts deleted file mode 100644 index 92893c11c..000000000 --- a/e2e/ci-e2e/global-setup.ts +++ /dev/null @@ -1,16 +0,0 @@ -/* eslint-disable functional/immutable-data */ - -const originalCI = process.env['CI']; - -export function setup() { - // package is expected to run in CI environment - process.env['CI'] = 'true'; -} - -export function teardown() { - if (originalCI === undefined) { - delete process.env['CI']; - } else { - process.env['CI'] = originalCI; - } -} diff --git a/e2e/ci-e2e/tsconfig.test.json b/e2e/ci-e2e/tsconfig.test.json index 5248dd739..307a55e79 100644 --- a/e2e/ci-e2e/tsconfig.test.json +++ b/e2e/ci-e2e/tsconfig.test.json @@ -8,7 +8,6 @@ "vitest.e2e.config.ts", "tests/**/*.e2e.test.ts", "tests/**/*.d.ts", - "mocks/**/*.ts", - "global-setup.ts" + "mocks/**/*.ts" ] } diff --git a/e2e/ci-e2e/vitest.e2e.config.ts b/e2e/ci-e2e/vitest.e2e.config.ts index 50c5b9bbc..ff9d2856e 100644 --- a/e2e/ci-e2e/vitest.e2e.config.ts +++ b/e2e/ci-e2e/vitest.e2e.config.ts @@ -1,16 +1,6 @@ /// -import type { UserConfig } from 'vite'; import { createE2ETestConfig } from '../../testing/test-setup-config/src/index.js'; -const baseConfig = createE2ETestConfig('ci-e2e', { +export default createE2ETestConfig('ci-e2e', { testTimeout: 60_000, - disableCoverage: true, }); - -export default { - ...baseConfig, - test: { - ...(baseConfig as any).test, - globalSetup: ['./global-setup.ts'], - }, -} as UserConfig; diff --git a/global-setup.ts b/global-setup.ts index 65522d48f..76e5c8dea 100644 --- a/global-setup.ts +++ b/global-setup.ts @@ -1,3 +1,18 @@ -export async function setup() { +/* eslint-disable functional/immutable-data */ + +const originalCI = process.env['CI']; + +export function setup() { process.env.TZ = 'UTC'; + + // package is expected to run in CI environment + process.env['CI'] = 'true'; +} + +export function teardown() { + if (originalCI === undefined) { + delete process.env['CI']; + } else { + process.env['CI'] = originalCI; + } } diff --git a/testing/test-setup-config/README.md b/testing/test-setup-config/README.md index 7663efa9e..a7750a4cf 100644 --- a/testing/test-setup-config/README.md +++ b/testing/test-setup-config/README.md @@ -30,7 +30,6 @@ export default createE2ETestConfig('my-e2e'); // With options: export default createE2ETestConfig('my-e2e', { testTimeout: 60_000, - disableCoverage: true, }); ``` diff --git a/testing/test-setup-config/src/lib/vitest-config-factory.ts b/testing/test-setup-config/src/lib/vitest-config-factory.ts index f9baa7da3..21235ebef 100644 --- a/testing/test-setup-config/src/lib/vitest-config-factory.ts +++ b/testing/test-setup-config/src/lib/vitest-config-factory.ts @@ -1,6 +1,5 @@ -import path from 'node:path'; -import { type UserConfig as ViteUserConfig, defineConfig } from 'vite'; import type { CoverageOptions, InlineConfig } from 'vitest'; +import { type UserConfig as ViteUserConfig, defineConfig } from 'vitest/config'; import { getSetupFiles } from './vitest-setup-files.js'; import { tsconfigPathAliases } from './vitest-tsconfig-path-aliases.js'; @@ -8,7 +7,6 @@ export type TestKind = 'unit' | 'int' | 'e2e'; export type E2ETestOptions = { testTimeout?: number; - disableCoverage?: boolean; }; export type VitestConfig = ViteUserConfig & { test?: InlineConfig }; @@ -34,9 +32,8 @@ function getGlobalSetup(kind: TestKind): string[] | undefined { function buildCoverageConfig( projectKey: string, kind: TestKind, - disableCoverage?: boolean, ): CoverageOptions | undefined { - if (disableCoverage || kind === 'e2e') { + if (kind === 'e2e') { return undefined; } @@ -55,11 +52,7 @@ export function createVitestConfig( kind: TestKind, options?: E2ETestOptions, ): ViteUserConfig { - const coverage = buildCoverageConfig( - projectKey, - kind, - options?.disableCoverage, - ); + const coverage = buildCoverageConfig(projectKey, kind); const config: VitestConfig = { cacheDir: `../../node_modules/.vite/${projectKey}`, 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 index eacf122b0..a6b80933d 100644 --- 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 @@ -1,10 +1,10 @@ -import { defineConfig } from 'vite'; import { beforeEach, describe, expect, it, vi } from 'vitest'; +import { defineConfig } from 'vitest/config'; import type { E2ETestOptions, TestKind } from './vitest-config-factory.js'; import { createVitestConfig } from './vitest-config-factory.js'; -vi.mock('vite', async importOriginal => { - const actual = await importOriginal(); +vi.mock('vitest/config', async importOriginal => { + const actual = await importOriginal(); return { ...actual, defineConfig: vi.fn(config => config), @@ -215,17 +215,9 @@ describe('createVitestConfig', () => { expect((config as any).test.testTimeout).toBe(60_000); }); - it('should support disableCoverage option', () => { - const options: E2ETestOptions = { disableCoverage: true }; - const config = createVitestConfig('test-package', 'e2e', options); - - expect((config as any).test.coverage).toBeUndefined(); - }); - it('should support multiple options together', () => { const options: E2ETestOptions = { testTimeout: 30_000, - disableCoverage: true, }; const config = createVitestConfig('test-package', 'e2e', options); diff --git a/testing/test-setup-config/src/lib/vitest-setup-presets.ts b/testing/test-setup-config/src/lib/vitest-setup-presets.ts index e0aaf3b47..b3502ef86 100644 --- a/testing/test-setup-config/src/lib/vitest-setup-presets.ts +++ b/testing/test-setup-config/src/lib/vitest-setup-presets.ts @@ -1,4 +1,4 @@ -import type { UserConfig as ViteUserConfig } from 'vite'; +import type { UserConfig as ViteUserConfig } from 'vitest/config'; import { type E2ETestOptions, createVitestConfig, @@ -49,7 +49,6 @@ export function createIntTestConfig(projectKey: string): ViteUserConfig { * // With options * export default createE2ETestConfig('my-e2e', { * testTimeout: 60_000, - * disableCoverage: true, * }); * * // Override any config using spread operator 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 index 984e56c44..e94b7e3e9 100644 --- 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 @@ -80,7 +80,6 @@ describe('vitest-setup-presets', () => { it('should pass options to createVitestConfig', () => { const options = { testTimeout: 60_000, - disableCoverage: true, }; createE2ETestConfig(MOCK_PROJECT_KEY, options); @@ -101,31 +100,6 @@ describe('vitest-setup-presets', () => { { testTimeout: 30_000 }, ); }); - - it('should handle disableCoverage option', () => { - createE2ETestConfig(MOCK_PROJECT_KEY, { disableCoverage: true }); - - expect(configFactory.createVitestConfig).toHaveBeenCalledWith( - MOCK_PROJECT_KEY, - 'e2e', - { disableCoverage: true }, - ); - }); - - it('should handle multiple options', () => { - const options = { - testTimeout: 45_000, - disableCoverage: false, - }; - - createE2ETestConfig(MOCK_PROJECT_KEY, options); - - expect(configFactory.createVitestConfig).toHaveBeenCalledWith( - MOCK_PROJECT_KEY, - 'e2e', - options, - ); - }); }); describe('function naming', () => { From abf7f5d74dbf0f140123c52af9c0a03615f86ca8 Mon Sep 17 00:00:00 2001 From: "Szymon.Poltorak" Date: Thu, 30 Oct 2025 10:58:08 +0100 Subject: [PATCH 16/23] refactor(testing): enhance documentation and Vitest configuration setup --- .gitignore | 1 - testing/test-setup-config/README.md | 21 +-- .../src/lib/vitest-config-factory.ts | 16 +- .../lib/vitest-config-factory.unit.test.ts | 161 ++++++++---------- .../src/lib/vitest-setup-files.ts | 28 +-- .../src/lib/vitest-setup-presets.ts | 11 -- testing/test-setup/README.md | 2 +- 7 files changed, 99 insertions(+), 141 deletions(-) diff --git a/.gitignore b/.gitignore index e6c77e8f8..5e706fa10 100644 --- a/.gitignore +++ b/.gitignore @@ -32,7 +32,6 @@ node_modules /.sass-cache /connect.lock /coverage -**/.coverage/** /examples/react-todos-app/coverage /libpeerconnection.log npm-debug.log diff --git a/testing/test-setup-config/README.md b/testing/test-setup-config/README.md index a7750a4cf..f9047bdbc 100644 --- a/testing/test-setup-config/README.md +++ b/testing/test-setup-config/README.md @@ -4,7 +4,7 @@ Standardized Vitest configuration for the Code PushUp monorepo. ### Usage -**Unit tests:** +#### Unit tests: ```typescript import { createUnitTestConfig } from '@code-pushup/test-setup-config'; @@ -12,7 +12,7 @@ import { createUnitTestConfig } from '@code-pushup/test-setup-config'; export default createUnitTestConfig('my-package'); ``` -**Integration tests:** +#### Integration tests: ```typescript import { createIntTestConfig } from '@code-pushup/test-setup-config'; @@ -20,7 +20,7 @@ import { createIntTestConfig } from '@code-pushup/test-setup-config'; export default createIntTestConfig('my-package'); ``` -**E2E tests:** +#### E2E tests: ```typescript import { createE2ETestConfig } from '@code-pushup/test-setup-config'; @@ -32,18 +32,3 @@ export default createE2ETestConfig('my-e2e', { testTimeout: 60_000, }); ``` - -### Advanced: Overriding Config - -For edge cases, use the spread operator to override any property: - -```typescript -const baseConfig = createE2ETestConfig('my-e2e'); -export default { - ...baseConfig, - test: { - ...(baseConfig as any).test, - globalSetup: ['./custom-setup.ts'], - }, -}; -``` diff --git a/testing/test-setup-config/src/lib/vitest-config-factory.ts b/testing/test-setup-config/src/lib/vitest-config-factory.ts index 21235ebef..9a277d650 100644 --- a/testing/test-setup-config/src/lib/vitest-config-factory.ts +++ b/testing/test-setup-config/src/lib/vitest-config-factory.ts @@ -1,4 +1,4 @@ -import type { CoverageOptions, InlineConfig } from 'vitest'; +import type { CoverageOptions } from 'vitest'; import { type UserConfig as ViteUserConfig, defineConfig } from 'vitest/config'; import { getSetupFiles } from './vitest-setup-files.js'; import { tsconfigPathAliases } from './vitest-tsconfig-path-aliases.js'; @@ -9,8 +9,6 @@ export type E2ETestOptions = { testTimeout?: number; }; -export type VitestConfig = ViteUserConfig & { test?: InlineConfig }; - function getIncludePatterns(kind: TestKind): string[] { switch (kind) { case 'unit': @@ -25,10 +23,6 @@ function getIncludePatterns(kind: TestKind): string[] { } } -function getGlobalSetup(kind: TestKind): string[] | undefined { - return kind === 'e2e' ? undefined : ['../../global-setup.ts']; -} - function buildCoverageConfig( projectKey: string, kind: TestKind, @@ -37,13 +31,13 @@ function buildCoverageConfig( return undefined; } - const defaultExclude = ['mocks/**', '**/types.ts', 'perf/**']; + const exclude = ['mocks/**', '**/types.ts', 'perf/**']; const reportsDirectory = `../../coverage/${projectKey}/${kind}-tests`; return { reporter: ['text', 'lcov'], reportsDirectory, - exclude: defaultExclude, + exclude: exclude, }; } @@ -54,7 +48,7 @@ export function createVitestConfig( ): ViteUserConfig { const coverage = buildCoverageConfig(projectKey, kind); - const config: VitestConfig = { + const config: ViteUserConfig = { cacheDir: `../../node_modules/.vite/${projectKey}`, test: { reporters: ['basic'], @@ -67,7 +61,7 @@ export function createVitestConfig( poolOptions: { threads: { singleThread: true } }, environment: 'node', include: getIncludePatterns(kind), - globalSetup: getGlobalSetup(kind), + globalSetup: ['../../global-setup.ts'], setupFiles: [...getSetupFiles(kind)], ...(options?.testTimeout ? { testTimeout: options.testTimeout } : {}), ...(coverage ? { coverage } : {}), 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 index a6b80933d..984f12f01 100644 --- 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 @@ -1,4 +1,4 @@ -import { beforeEach, describe, expect, it, vi } from 'vitest'; +import { describe, expect, it, vi } from 'vitest'; import { defineConfig } from 'vitest/config'; import type { E2ETestOptions, TestKind } from './vitest-config-factory.js'; import { createVitestConfig } from './vitest-config-factory.js'; @@ -18,51 +18,45 @@ vi.mock('./vitest-tsconfig-path-aliases.js', () => ({ })); describe('createVitestConfig', () => { - beforeEach(() => { - vi.clearAllMocks(); - }); - describe('unit test configuration', () => { it('should create a complete unit test config with all defaults', () => { const config = createVitestConfig('test-package', 'unit'); - expect(config).toEqual( - expect.objectContaining({ - cacheDir: '../../node_modules/.vite/test-package', - test: expect.objectContaining({ - reporters: ['basic'], - globals: true, - cache: { dir: '../../node_modules/.vitest' }, - alias: expect.any(Array), - pool: 'threads', - poolOptions: { threads: { singleThread: true } }, - environment: 'node', - include: [ - 'src/**/*.unit.test.{js,mjs,cjs,ts,mts,cts,jsx,tsx}', - 'src/**/*.type.test.{js,mjs,cjs,ts,mts,cts,jsx,tsx}', - ], - globalSetup: ['../../global-setup.ts'], - setupFiles: expect.arrayContaining([ - '../../testing/test-setup/src/lib/console.mock.ts', - '../../testing/test-setup/src/lib/reset.mocks.ts', - '../../testing/test-setup/src/lib/fs.mock.ts', - ]), - coverage: expect.objectContaining({ - reporter: ['text', 'lcov'], - reportsDirectory: '../../coverage/test-package/unit-tests', - exclude: ['mocks/**', '**/types.ts', 'perf/**'], - }), - typecheck: { include: ['**/*.type.test.ts'] }, + expect(config).toEqual({ + cacheDir: '../../node_modules/.vite/test-package', + test: expect.objectContaining({ + reporters: ['basic'], + globals: true, + cache: { dir: '../../node_modules/.vitest' }, + alias: expect.any(Array), + pool: 'threads', + poolOptions: { threads: { singleThread: true } }, + environment: 'node', + include: [ + 'src/**/*.unit.test.{js,mjs,cjs,ts,mts,cts,jsx,tsx}', + 'src/**/*.type.test.{js,mjs,cjs,ts,mts,cts,jsx,tsx}', + ], + globalSetup: ['../../global-setup.ts'], + setupFiles: expect.arrayContaining([ + '../../testing/test-setup/src/lib/console.mock.ts', + '../../testing/test-setup/src/lib/reset.mocks.ts', + '../../testing/test-setup/src/lib/fs.mock.ts', + ]), + coverage: expect.objectContaining({ + reporter: ['text', 'lcov'], + reportsDirectory: '../../coverage/test-package/unit-tests', + exclude: ['mocks/**', '**/types.ts', 'perf/**'], }), + typecheck: { include: ['**/*.type.test.ts'] }, }), - ); + }); expect(defineConfig).toHaveBeenCalledWith(config); }); it('should include all required setup files for unit tests', () => { const config = createVitestConfig('test-package', 'unit'); - const setupFiles = (config as any).test.setupFiles; + const setupFiles = config.test!.setupFiles; expect(setupFiles).toContain( '../../testing/test-setup/src/lib/console.mock.ts', ); @@ -98,7 +92,7 @@ describe('createVitestConfig', () => { it('should include type test pattern in unit tests', () => { const config = createVitestConfig('test-package', 'unit'); - expect((config as any).test.include).toContain( + expect(config.test!.include).toContain( 'src/**/*.type.test.{js,mjs,cjs,ts,mts,cts,jsx,tsx}', ); }); @@ -106,7 +100,7 @@ describe('createVitestConfig', () => { it('should enable typecheck for unit tests', () => { const config = createVitestConfig('test-package', 'unit'); - expect((config as any).test.typecheck).toEqual({ + expect(config.test!.typecheck).toEqual({ include: ['**/*.type.test.ts'], }); }); @@ -114,7 +108,7 @@ describe('createVitestConfig', () => { it('should always include perf/** in coverage exclusions', () => { const config = createVitestConfig('test-package', 'unit'); - expect((config as any).test.coverage.exclude).toContain('perf/**'); + expect(config.test!.coverage!.exclude).toContain('perf/**'); }); }); @@ -122,26 +116,24 @@ describe('createVitestConfig', () => { it('should create a complete integration test config', () => { const config = createVitestConfig('test-package', 'int'); - expect(config).toEqual( - expect.objectContaining({ - cacheDir: '../../node_modules/.vite/test-package', - test: expect.objectContaining({ - reporters: ['basic'], - globals: true, - include: ['src/**/*.int.test.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], - globalSetup: ['../../global-setup.ts'], - coverage: expect.objectContaining({ - reportsDirectory: '../../coverage/test-package/int-tests', - }), + expect(config).toEqual({ + cacheDir: '../../node_modules/.vite/test-package', + test: expect.objectContaining({ + reporters: ['basic'], + globals: true, + include: ['src/**/*.int.test.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], + globalSetup: ['../../global-setup.ts'], + coverage: expect.objectContaining({ + reportsDirectory: '../../coverage/test-package/int-tests', }), }), - ); + }); }); it('should include correct setup files for integration tests', () => { const config = createVitestConfig('test-package', 'int'); - const setupFiles = (config as any).test.setupFiles; + const setupFiles = config.test!.setupFiles; // Should include console mock expect(setupFiles).toContain( '../../testing/test-setup/src/lib/console.mock.ts', @@ -165,7 +157,7 @@ describe('createVitestConfig', () => { it('should not enable typecheck for integration tests', () => { const config = createVitestConfig('test-package', 'int'); - expect((config as any).test.typecheck).toBeUndefined(); + expect(config.test?.typecheck).toBeUndefined(); }); }); @@ -173,24 +165,22 @@ describe('createVitestConfig', () => { it('should create e2e config without coverage by default', () => { const config = createVitestConfig('test-package', 'e2e'); - expect(config).toEqual( - expect.objectContaining({ - cacheDir: '../../node_modules/.vite/test-package', - test: expect.objectContaining({ - reporters: ['basic'], - globals: true, - include: ['tests/**/*.e2e.test.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], - globalSetup: undefined, - }), + expect(config).toEqual({ + cacheDir: '../../node_modules/.vite/test-package', + test: expect.objectContaining({ + reporters: ['basic'], + globals: true, + include: ['tests/**/*.e2e.test.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], + globalSetup: ['../../global-setup.ts'], }), - ); - expect((config as any).test.coverage).toBeUndefined(); + }); + expect(config.test?.coverage).toBeUndefined(); }); it('should include minimal setup files for e2e tests', () => { const config = createVitestConfig('test-package', 'e2e'); - const setupFiles = (config as any).test.setupFiles; + const setupFiles = config.test!.setupFiles; // Should only include reset mocks expect(setupFiles).toContain( '../../testing/test-setup/src/lib/reset.mocks.ts', @@ -212,7 +202,7 @@ describe('createVitestConfig', () => { const options: E2ETestOptions = { testTimeout: 60_000 }; const config = createVitestConfig('test-package', 'e2e', options); - expect((config as any).test.testTimeout).toBe(60_000); + expect(config.test!.testTimeout).toBe(60_000); }); it('should support multiple options together', () => { @@ -221,8 +211,8 @@ describe('createVitestConfig', () => { }; const config = createVitestConfig('test-package', 'e2e', options); - expect((config as any).test.testTimeout).toBe(30_000); - expect((config as any).test.coverage).toBeUndefined(); + expect(config.test!.testTimeout).toBe(30_000); + expect(config.test?.coverage).toBeUndefined(); }); }); @@ -236,7 +226,7 @@ describe('createVitestConfig', () => { it('should use projectKey for coverage directory', () => { const config = createVitestConfig('my-package', 'unit'); - expect((config as any).test.coverage.reportsDirectory).toBe( + expect(config.test!.coverage!.reportsDirectory).toBe( '../../coverage/my-package/unit-tests', ); }); @@ -258,17 +248,15 @@ describe('createVitestConfig', () => { e2e: ['tests/**/*.e2e.test.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], }; - expect((config as any).test.include).toStrictEqual( - expectedIncludes[kind], - ); + expect(config.test!.include).toStrictEqual(expectedIncludes[kind]); const expectedGlobalSetup = { unit: ['../../global-setup.ts'], int: ['../../global-setup.ts'], - e2e: undefined, + e2e: ['../../global-setup.ts'], }; - expect((config as any).test.globalSetup).toStrictEqual( + expect(config.test!.globalSetup).toStrictEqual( expectedGlobalSetup[kind], ); }); @@ -279,26 +267,26 @@ describe('createVitestConfig', () => { it('should enable coverage for unit tests by default', () => { const config = createVitestConfig('test-package', 'unit'); - expect((config as any).test.coverage).toBeDefined(); - expect((config as any).test.coverage.reporter).toEqual(['text', 'lcov']); + expect(config.test!.coverage).toBeDefined(); + expect((config.test!.coverage as any).reporter).toEqual(['text', 'lcov']); }); it('should enable coverage for integration tests by default', () => { const config = createVitestConfig('test-package', 'int'); - expect((config as any).test.coverage).toBeDefined(); + expect(config.test!.coverage).toBeDefined(); }); it('should disable coverage for e2e tests by default', () => { const config = createVitestConfig('test-package', 'e2e'); - expect((config as any).test.coverage).toBeUndefined(); + expect(config.test?.coverage).toBeUndefined(); }); it('should always exclude mocks, types.ts, and perf folders', () => { const config = createVitestConfig('test-package', 'unit'); - expect((config as any).test.coverage.exclude).toEqual([ + expect(config.test!.coverage!.exclude).toEqual([ 'mocks/**', '**/types.ts', 'perf/**', @@ -311,20 +299,19 @@ describe('createVitestConfig', () => { const config = createVitestConfig('test-package', 'unit'); // Setup files should be relative - const setupFiles = (config as any).test.setupFiles; - expect(setupFiles[0]).toMatch(/^\.\.\/\.\.\//); + const setupFiles = config.test!.setupFiles; + expect(setupFiles).toBeDefined(); + expect(setupFiles![0]).toMatch(/^\.\.\/\.\.\//); // GlobalSetup should be relative - expect((config as any).test.globalSetup[0]).toBe('../../global-setup.ts'); + expect(config.test!.globalSetup![0]).toBe('../../global-setup.ts'); // Cache dirs should be relative expect(config.cacheDir).toMatch(/^\.\.\/\.\.\//); - expect((config as any).test.cache.dir).toMatch(/^\.\.\/\.\.\//); + expect((config.test!.cache as any).dir).toMatch(/^\.\.\/\.\.\//); // Coverage directory should be relative - expect((config as any).test.coverage.reportsDirectory).toMatch( - /^\.\.\/\.\.\//, - ); + expect(config.test!.coverage!.reportsDirectory).toMatch(/^\.\.\/\.\.\//); }); }); @@ -333,7 +320,7 @@ describe('createVitestConfig', () => { const config = createVitestConfig('', 'unit'); expect(config.cacheDir).toBe('../../node_modules/.vite/'); - expect((config as any).test.coverage.reportsDirectory).toBe( + expect(config.test!.coverage!.reportsDirectory).toBe( '../../coverage//unit-tests', ); }); @@ -349,8 +336,8 @@ describe('createVitestConfig', () => { it('should not modify config when no options provided to e2e', () => { const config = createVitestConfig('test-package', 'e2e'); - expect((config as any).test.testTimeout).toBeUndefined(); - expect((config as any).test.globalSetup).toBeUndefined(); + expect(config.test?.testTimeout).toBeUndefined(); + expect(config.test?.globalSetup).toEqual(['../../global-setup.ts']); }); }); }); diff --git a/testing/test-setup-config/src/lib/vitest-setup-files.ts b/testing/test-setup-config/src/lib/vitest-setup-files.ts index 332c9f759..cbd137105 100644 --- a/testing/test-setup-config/src/lib/vitest-setup-files.ts +++ b/testing/test-setup-config/src/lib/vitest-setup-files.ts @@ -1,5 +1,18 @@ import type { TestKind } from './vitest-config-factory.js'; +/** + * Custom matchers that extend Vitest's assertion library. + * + * These paths are relative to the config file location, + * which is why they use `../../` to navigate to the workspace root first. + */ +const CUSTOM_MATCHERS = [ + '../../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', + '../../testing/test-setup/src/lib/extend/path.matcher.ts', +] as const; + /** * Setup files for unit tests. * @@ -13,10 +26,7 @@ const UNIT_TEST_SETUP_FILES = [ '../../testing/test-setup/src/lib/fs.mock.ts', '../../testing/test-setup/src/lib/git.mock.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', - '../../testing/test-setup/src/lib/extend/path.matcher.ts', + ...CUSTOM_MATCHERS, ] as const; /** @@ -28,10 +38,7 @@ const UNIT_TEST_SETUP_FILES = [ const INT_TEST_SETUP_FILES = [ '../../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/jest-extended.matcher.ts', - '../../testing/test-setup/src/lib/extend/path.matcher.ts', + ...CUSTOM_MATCHERS, ] as const; /** @@ -42,10 +49,7 @@ const INT_TEST_SETUP_FILES = [ */ const E2E_TEST_SETUP_FILES = [ '../../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/jest-extended.matcher.ts', - '../../testing/test-setup/src/lib/extend/path.matcher.ts', + ...CUSTOM_MATCHERS, ] as const; /** diff --git a/testing/test-setup-config/src/lib/vitest-setup-presets.ts b/testing/test-setup-config/src/lib/vitest-setup-presets.ts index b3502ef86..ed26d7bbb 100644 --- a/testing/test-setup-config/src/lib/vitest-setup-presets.ts +++ b/testing/test-setup-config/src/lib/vitest-setup-presets.ts @@ -50,17 +50,6 @@ export function createIntTestConfig(projectKey: string): ViteUserConfig { * export default createE2ETestConfig('my-e2e', { * testTimeout: 60_000, * }); - * - * // Override any config using spread operator - * const baseConfig = createE2ETestConfig('my-e2e', { testTimeout: 60_000 }); - * export default { - * ...baseConfig, - * test: { - * ...(baseConfig as any).test, - * globalSetup: ['./custom-setup.ts'], - * }, - * }; - * ``` */ export function createE2ETestConfig( projectKey: string, diff --git a/testing/test-setup/README.md b/testing/test-setup/README.md index 3e9c2c236..c3b1e60e0 100644 --- a/testing/test-setup/README.md +++ b/testing/test-setup/README.md @@ -6,7 +6,7 @@ More on this subject as well as all the testing strategy principles can be found ## Shared config -[README](./src/lib/config/README.md) how to use vitest config factory. +See [`@code-pushup/test-setup-config` docs](../test-setup-config/README.md) on how to use our Vitest config factory. ## Mock setup From 3efd81fc31fbe229dde0d32236b7e19c3d96fcac Mon Sep 17 00:00:00 2001 From: "Szymon.Poltorak" Date: Thu, 30 Oct 2025 15:50:39 +0100 Subject: [PATCH 17/23] refactor(testing): use config factory for vitest config files --- e2e/cli-e2e/vitest.e2e.config.ts | 21 +------ e2e/create-cli-e2e/vitest.e2e.config.ts | 22 +------ e2e/nx-plugin-e2e/vitest.e2e.config.ts | 21 +------ e2e/plugin-coverage-e2e/vitest.e2e.config.ts | 21 +------ e2e/plugin-eslint-e2e/vitest.e2e.config.ts | 21 +------ .../vitest.e2e.config.ts | 21 +------ e2e/plugin-jsdocs-e2e/vitest.e2e.config.ts | 21 +------ .../vitest.e2e.config.ts | 21 +------ examples/plugins/vitest.int.config.ts | 30 +--------- examples/plugins/vitest.unit.config.ts | 31 +--------- packages/ci/vitest.int.config.ts | 29 +--------- packages/ci/vitest.unit.config.ts | 32 +---------- packages/cli/vitest.int.config.ts | 29 +--------- packages/cli/vitest.unit.config.ts | 34 +---------- packages/create-cli/vitest.unit.config.ts | 30 +--------- .../lib/implementation/validate.unit.test.ts | 12 ++++ packages/models/vitest.unit.config.ts | 30 +--------- packages/nx-plugin/vitest.int.config.ts | 28 +-------- packages/nx-plugin/vitest.unit.config.ts | 29 +--------- packages/plugin-coverage/vitest.int.config.ts | 29 +--------- .../plugin-coverage/vitest.unit.config.ts | 32 +---------- packages/plugin-eslint/vitest.int.config.ts | 57 ++++++++++--------- packages/plugin-eslint/vitest.unit.config.ts | 32 +---------- .../plugin-js-packages/vitest.int.config.ts | 29 +--------- .../plugin-js-packages/vitest.unit.config.ts | 30 +--------- packages/plugin-jsdocs/vitest.int.config.ts | 30 +--------- packages/plugin-jsdocs/vitest.unit.config.ts | 32 +---------- .../plugin-lighthouse/vitest.unit.config.ts | 33 +---------- .../plugin-typescript/vitest.int.config.ts | 35 +----------- .../plugin-typescript/vitest.unit.config.ts | 37 +----------- .../src/lib/utils/nx-plugin.unit.test.ts | 4 +- .../src/lib/utils/nx.unit.test.ts | 4 +- testing/test-nx-utils/vitest.unit.config.ts | 24 +------- testing/test-setup-config/src/index.ts | 7 ++- .../lib/vitest-config-factory.unit.test.ts | 6 +- .../src/lib/vitest-setup-files.ts | 4 +- .../src/lib/vitest-setup-files.unit.test.ts | 16 ++++-- testing/test-setup/vitest.unit.config.ts | 29 +--------- .../lib/utils/os-agnostic-paths.unit.test.ts | 8 ++- testing/test-utils/vitest.unit.config.ts | 24 +------- .../vitest.unit.config.ts | 32 +---------- 41 files changed, 145 insertions(+), 872 deletions(-) diff --git a/e2e/cli-e2e/vitest.e2e.config.ts b/e2e/cli-e2e/vitest.e2e.config.ts index ab4256183..730c6d3fd 100644 --- a/e2e/cli-e2e/vitest.e2e.config.ts +++ b/e2e/cli-e2e/vitest.e2e.config.ts @@ -1,21 +1,6 @@ /// -import { defineConfig } from 'vitest/config'; -import { tsconfigPathAliases } from '../../tools/vitest-tsconfig-path-aliases.js'; +import { createE2ETestConfig } from '../../testing/test-setup-config/src/index.js'; -export default defineConfig({ - cacheDir: '../../node_modules/.vite/cli-e2e', - test: { - reporters: ['basic'], - testTimeout: 20_000, - globals: true, - alias: tsconfigPathAliases(), - pool: 'threads', - poolOptions: { threads: { singleThread: true } }, - cache: { - dir: '../../node_modules/.vitest', - }, - environment: 'node', - include: ['tests/**/*.e2e.test.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], - setupFiles: ['../../testing/test-setup/src/lib/reset.mocks.ts'], - }, +export default createE2ETestConfig('cli-e2e', { + testTimeout: 20_000, }); diff --git a/e2e/create-cli-e2e/vitest.e2e.config.ts b/e2e/create-cli-e2e/vitest.e2e.config.ts index 9eca9c7e9..2a5be63f3 100644 --- a/e2e/create-cli-e2e/vitest.e2e.config.ts +++ b/e2e/create-cli-e2e/vitest.e2e.config.ts @@ -1,22 +1,6 @@ /// -import { defineConfig } from 'vitest/config'; -import { tsconfigPathAliases } from '../../tools/vitest-tsconfig-path-aliases.js'; +import { createE2ETestConfig } from '../../testing/test-setup-config/src/index.js'; -export default defineConfig({ - cacheDir: '../../node_modules/.vite/create-cli-e2e', - test: { - reporters: ['basic'], - testTimeout: 20_000, - hookTimeout: 20_000, - globals: true, - alias: tsconfigPathAliases(), - pool: 'threads', - poolOptions: { threads: { singleThread: true } }, - cache: { - dir: '../../node_modules/.vitest', - }, - environment: 'node', - include: ['tests/**/*.e2e.test.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], - setupFiles: ['../../testing/test-setup/src/lib/reset.mocks.ts'], - }, +export default createE2ETestConfig('create-cli-e2e', { + testTimeout: 20_000, }); diff --git a/e2e/nx-plugin-e2e/vitest.e2e.config.ts b/e2e/nx-plugin-e2e/vitest.e2e.config.ts index b6fe4be83..72576b045 100644 --- a/e2e/nx-plugin-e2e/vitest.e2e.config.ts +++ b/e2e/nx-plugin-e2e/vitest.e2e.config.ts @@ -1,21 +1,6 @@ /// -import { defineConfig } from 'vitest/config'; -import { tsconfigPathAliases } from '../../tools/vitest-tsconfig-path-aliases.js'; +import { createE2ETestConfig } from '../../testing/test-setup-config/src/index.js'; -export default defineConfig({ - cacheDir: '../../node_modules/.vite/nx-plugin-e2e', - test: { - reporters: ['basic'], - testTimeout: 80_000, - globals: true, - alias: tsconfigPathAliases(), - pool: 'threads', - poolOptions: { threads: { singleThread: true } }, - cache: { - dir: '../../node_modules/.vitest', - }, - environment: 'node', - include: ['tests/**/*.e2e.test.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], - setupFiles: ['../../testing/test-setup/src/lib/reset.mocks.ts'], - }, +export default createE2ETestConfig('nx-plugin-e2e', { + testTimeout: 80_000, }); diff --git a/e2e/plugin-coverage-e2e/vitest.e2e.config.ts b/e2e/plugin-coverage-e2e/vitest.e2e.config.ts index 27a4a055d..34517b1e1 100644 --- a/e2e/plugin-coverage-e2e/vitest.e2e.config.ts +++ b/e2e/plugin-coverage-e2e/vitest.e2e.config.ts @@ -1,21 +1,6 @@ /// -import { defineConfig } from 'vitest/config'; -import { tsconfigPathAliases } from '../../tools/vitest-tsconfig-path-aliases.js'; +import { createE2ETestConfig } from '../../testing/test-setup-config/src/index.js'; -export default defineConfig({ - cacheDir: '../../node_modules/.vite/plugin-coverage-e2e', - test: { - reporters: ['basic'], - testTimeout: 40_000, - globals: true, - alias: tsconfigPathAliases(), - pool: 'threads', - poolOptions: { threads: { singleThread: true } }, - cache: { - dir: '../../node_modules/.vitest', - }, - environment: 'node', - include: ['tests/**/*.e2e.test.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], - setupFiles: ['../../testing/test-setup/src/lib/reset.mocks.ts'], - }, +export default createE2ETestConfig('plugin-coverage-e2e', { + testTimeout: 40_000, }); diff --git a/e2e/plugin-eslint-e2e/vitest.e2e.config.ts b/e2e/plugin-eslint-e2e/vitest.e2e.config.ts index d54795b9b..9690a49b1 100644 --- a/e2e/plugin-eslint-e2e/vitest.e2e.config.ts +++ b/e2e/plugin-eslint-e2e/vitest.e2e.config.ts @@ -1,21 +1,6 @@ /// -import { defineConfig } from 'vitest/config'; -import { tsconfigPathAliases } from '../../tools/vitest-tsconfig-path-aliases.js'; +import { createE2ETestConfig } from '../../testing/test-setup-config/src/index.js'; -export default defineConfig({ - cacheDir: '../../node_modules/.vite/plugin-lighthouse-e2e', - test: { - reporters: ['basic'], - testTimeout: 20_000, - globals: true, - alias: tsconfigPathAliases(), - pool: 'threads', - poolOptions: { threads: { singleThread: true } }, - cache: { - dir: '../../node_modules/.vitest', - }, - environment: 'node', - include: ['tests/**/*.e2e.test.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], - setupFiles: ['../../testing/test-setup/src/lib/reset.mocks.ts'], - }, +export default createE2ETestConfig('plugin-eslint-e2e', { + testTimeout: 20_000, }); diff --git a/e2e/plugin-js-packages-e2e/vitest.e2e.config.ts b/e2e/plugin-js-packages-e2e/vitest.e2e.config.ts index d6c21a4da..aecd2506a 100644 --- a/e2e/plugin-js-packages-e2e/vitest.e2e.config.ts +++ b/e2e/plugin-js-packages-e2e/vitest.e2e.config.ts @@ -1,21 +1,6 @@ /// -import { defineConfig } from 'vitest/config'; -import { tsconfigPathAliases } from '../../tools/vitest-tsconfig-path-aliases.js'; +import { createE2ETestConfig } from '../../testing/test-setup-config/src/index.js'; -export default defineConfig({ - cacheDir: '../../node_modules/.vite/plugin-js-packages-e2e', - test: { - reporters: ['basic'], - testTimeout: 120_000, - globals: true, - alias: tsconfigPathAliases(), - pool: 'threads', - poolOptions: { threads: { singleThread: true } }, - cache: { - dir: '../../node_modules/.vitest', - }, - environment: 'node', - include: ['tests/**/*.e2e.test.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], - setupFiles: ['../../testing/test-setup/src/lib/reset.mocks.ts'], - }, +export default createE2ETestConfig('plugin-js-packages-e2e', { + testTimeout: 120_000, }); diff --git a/e2e/plugin-jsdocs-e2e/vitest.e2e.config.ts b/e2e/plugin-jsdocs-e2e/vitest.e2e.config.ts index 9b069c338..0cde9c424 100644 --- a/e2e/plugin-jsdocs-e2e/vitest.e2e.config.ts +++ b/e2e/plugin-jsdocs-e2e/vitest.e2e.config.ts @@ -1,21 +1,6 @@ /// -import { defineConfig } from 'vitest/config'; -import { tsconfigPathAliases } from '../../tools/vitest-tsconfig-path-aliases.js'; +import { createE2ETestConfig } from '../../testing/test-setup-config/src/index.js'; -export default defineConfig({ - cacheDir: '../../node_modules/.vite/plugin-jsdocs-e2e', - test: { - reporters: ['basic'], - testTimeout: 20_000, - globals: true, - alias: tsconfigPathAliases(), - pool: 'threads', - poolOptions: { threads: { singleThread: true } }, - cache: { - dir: '../../node_modules/.vitest', - }, - environment: 'node', - include: ['tests/**/*.e2e.test.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], - setupFiles: ['../../testing/test-setup/src/lib/reset.mocks.ts'], - }, +export default createE2ETestConfig('plugin-jsdocs-e2e', { + testTimeout: 20_000, }); diff --git a/e2e/plugin-lighthouse-e2e/vitest.e2e.config.ts b/e2e/plugin-lighthouse-e2e/vitest.e2e.config.ts index 302f6aa75..5964409c7 100644 --- a/e2e/plugin-lighthouse-e2e/vitest.e2e.config.ts +++ b/e2e/plugin-lighthouse-e2e/vitest.e2e.config.ts @@ -1,21 +1,6 @@ /// -import { defineConfig } from 'vitest/config'; -import { tsconfigPathAliases } from '../../tools/vitest-tsconfig-path-aliases.js'; +import { createE2ETestConfig } from '../../testing/test-setup-config/src/index.js'; -export default defineConfig({ - cacheDir: '../../node_modules/.vite/plugin-lighthouse-e2e', - test: { - reporters: ['basic'], - testTimeout: 80_000, - globals: true, - alias: tsconfigPathAliases(), - pool: 'threads', - poolOptions: { threads: { singleThread: true } }, - cache: { - dir: '../../node_modules/.vitest', - }, - environment: 'node', - include: ['tests/**/*.e2e.test.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], - setupFiles: ['../../testing/test-setup/src/lib/reset.mocks.ts'], - }, +export default createE2ETestConfig('plugin-lighthouse-e2e', { + testTimeout: 80_000, }); diff --git a/examples/plugins/vitest.int.config.ts b/examples/plugins/vitest.int.config.ts index 3390c1dba..fdb7bfb02 100644 --- a/examples/plugins/vitest.int.config.ts +++ b/examples/plugins/vitest.int.config.ts @@ -1,30 +1,4 @@ /// -import { defineConfig } from 'vitest/config'; -import { tsconfigPathAliases } from '../../tools/vitest-tsconfig-path-aliases.js'; +import { createIntTestConfig } from '../../testing/test-setup-config/src/index.js'; -export default defineConfig({ - cacheDir: '../../node_modules/.vite/examples-plugins', - test: { - reporters: ['basic'], - globals: true, - cache: { - dir: '../../node_modules/.vitest', - }, - alias: tsconfigPathAliases(), - pool: 'threads', - poolOptions: { threads: { singleThread: true } }, - coverage: { - reporter: ['text', 'lcov'], - reportsDirectory: '../../coverage/examples-plugins/int-tests', - exclude: ['**/mocks/**', '**/mock/**', 'code-pushup.config.ts'], - }, - environment: 'node', - include: ['src/**/*.int.test.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], - globalSetup: ['../../global-setup.ts'], - setupFiles: [ - '../../testing/test-setup/src/lib/git.mock.ts', - '../../testing/test-setup/src/lib/console.mock.ts', - '../../testing/test-setup/src/lib/reset.mocks.ts', - ], - }, -}); +export default createIntTestConfig('examples-plugins'); diff --git a/examples/plugins/vitest.unit.config.ts b/examples/plugins/vitest.unit.config.ts index e49f12646..a2172a666 100644 --- a/examples/plugins/vitest.unit.config.ts +++ b/examples/plugins/vitest.unit.config.ts @@ -1,31 +1,4 @@ /// -import { defineConfig } from 'vitest/config'; -import { tsconfigPathAliases } from '../../tools/vitest-tsconfig-path-aliases.js'; +import { createUnitTestConfig } from '../../testing/test-setup-config/src/index.js'; -export default defineConfig({ - cacheDir: '../../node_modules/.vite/examples-plugins', - test: { - reporters: ['basic'], - globals: true, - cache: { - dir: '../../node_modules/.vitest', - }, - alias: tsconfigPathAliases(), - pool: 'threads', - poolOptions: { threads: { singleThread: true } }, - coverage: { - reporter: ['text', 'lcov'], - reportsDirectory: '../../coverage/examples-plugins/unit-tests', - exclude: ['**/mocks/**', '**/mock/**', 'code-pushup.config.ts'], - }, - environment: 'node', - include: ['src/**/*.unit.test.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], - globalSetup: ['../../global-setup.ts'], - setupFiles: [ - '../../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', - ], - }, -}); +export default createUnitTestConfig('examples-plugins'); diff --git a/packages/ci/vitest.int.config.ts b/packages/ci/vitest.int.config.ts index 88ea3988c..c1f292fd0 100644 --- a/packages/ci/vitest.int.config.ts +++ b/packages/ci/vitest.int.config.ts @@ -1,29 +1,4 @@ /// -import { defineConfig } from 'vitest/config'; -import { tsconfigPathAliases } from '../../tools/vitest-tsconfig-path-aliases.js'; +import { createIntTestConfig } from '../../testing/test-setup-config/src/index.js'; -export default defineConfig({ - cacheDir: '../../node_modules/.vite/ci', - test: { - reporters: ['basic'], - globals: true, - cache: { - dir: '../../node_modules/.vitest', - }, - alias: tsconfigPathAliases(), - pool: 'threads', - poolOptions: { threads: { singleThread: true } }, - coverage: { - reporter: ['text', 'lcov'], - reportsDirectory: '../../coverage/ci/int-tests', - exclude: ['mocks/**', '**/types.ts'], - }, - environment: 'node', - include: ['src/**/*.int.test.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], - globalSetup: ['../../global-setup.ts'], - setupFiles: [ - '../../testing/test-setup/src/lib/logger.mock.ts', - '../../testing/test-setup/src/lib/reset.mocks.ts', - ], - }, -}); +export default createIntTestConfig('ci'); diff --git a/packages/ci/vitest.unit.config.ts b/packages/ci/vitest.unit.config.ts index 854777d69..cb9ecaa3b 100644 --- a/packages/ci/vitest.unit.config.ts +++ b/packages/ci/vitest.unit.config.ts @@ -1,32 +1,4 @@ /// -import { defineConfig } from 'vitest/config'; -import { tsconfigPathAliases } from '../../tools/vitest-tsconfig-path-aliases.js'; +import { createUnitTestConfig } from '../../testing/test-setup-config/src/index.js'; -export default defineConfig({ - cacheDir: '../../node_modules/.vite/ci', - test: { - reporters: ['basic'], - globals: true, - cache: { - dir: '../../node_modules/.vitest', - }, - alias: tsconfigPathAliases(), - pool: 'threads', - poolOptions: { threads: { singleThread: true } }, - coverage: { - reporter: ['text', 'lcov'], - reportsDirectory: '../../coverage/ci/unit-tests', - exclude: ['mocks/**', '**/types.ts'], - }, - environment: 'node', - include: ['src/**/*.unit.test.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], - globalSetup: ['../../global-setup.ts'], - setupFiles: [ - '../../testing/test-setup/src/lib/fs.mock.ts', - '../../testing/test-setup/src/lib/git.mock.ts', - '../../testing/test-setup/src/lib/logger.mock.ts', - '../../testing/test-setup/src/lib/reset.mocks.ts', - '../../testing/test-setup/src/lib/extend/jest-extended.matcher.ts', - ], - }, -}); +export default createUnitTestConfig('ci'); diff --git a/packages/cli/vitest.int.config.ts b/packages/cli/vitest.int.config.ts index 491b566d2..15ed90048 100644 --- a/packages/cli/vitest.int.config.ts +++ b/packages/cli/vitest.int.config.ts @@ -1,29 +1,4 @@ /// -import { defineConfig } from 'vitest/config'; -import { tsconfigPathAliases } from '../../tools/vitest-tsconfig-path-aliases.js'; +import { createIntTestConfig } from '../../testing/test-setup-config/src/index.js'; -export default defineConfig({ - cacheDir: '../../node_modules/.vite/cli', - test: { - reporters: ['basic'], - globals: true, - cache: { - dir: '../../node_modules/.vitest', - }, - alias: tsconfigPathAliases(), - pool: 'threads', - poolOptions: { threads: { singleThread: true } }, - coverage: { - reporter: ['text', 'lcov'], - reportsDirectory: '../../coverage/cli/int-tests', - exclude: ['mocks/**', '**/types.ts'], - }, - 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', - ], - }, -}); +export default createIntTestConfig('cli'); diff --git a/packages/cli/vitest.unit.config.ts b/packages/cli/vitest.unit.config.ts index cd943a42b..e5c8fe760 100644 --- a/packages/cli/vitest.unit.config.ts +++ b/packages/cli/vitest.unit.config.ts @@ -1,34 +1,4 @@ /// -import { defineConfig } from 'vitest/config'; -import { tsconfigPathAliases } from '../../tools/vitest-tsconfig-path-aliases.js'; +import { createUnitTestConfig } from '../../testing/test-setup-config/src/index.js'; -export default defineConfig({ - cacheDir: '../../node_modules/.vite/cli', - test: { - reporters: ['basic'], - globals: true, - cache: { - dir: '../../node_modules/.vitest', - }, - alias: tsconfigPathAliases(), - pool: 'threads', - poolOptions: { threads: { singleThread: true } }, - coverage: { - reporter: ['text', 'lcov'], - reportsDirectory: '../../coverage/cli/unit-tests', - exclude: ['mocks/**', '**/types.ts'], - }, - 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/portal-client.mock.ts', - '../../testing/test-setup/src/lib/reset.mocks.ts', - '../../testing/test-setup/src/lib/extend/ui-logger.matcher.ts', - ], - }, -}); +export default createUnitTestConfig('cli'); diff --git a/packages/create-cli/vitest.unit.config.ts b/packages/create-cli/vitest.unit.config.ts index 3b327e222..30363d3cf 100644 --- a/packages/create-cli/vitest.unit.config.ts +++ b/packages/create-cli/vitest.unit.config.ts @@ -1,30 +1,4 @@ /// -import { defineConfig } from 'vitest/config'; -import { tsconfigPathAliases } from '../../tools/vitest-tsconfig-path-aliases.js'; +import { createUnitTestConfig } from '../../testing/test-setup-config/src/index.js'; -export default defineConfig({ - cacheDir: '../../node_modules/.vite/create-cli', - test: { - reporters: ['basic'], - globals: true, - cache: { - dir: '../../node_modules/.vitest', - }, - alias: tsconfigPathAliases(), - pool: 'threads', - poolOptions: { threads: { singleThread: true } }, - coverage: { - reporter: ['text', 'lcov'], - reportsDirectory: '../../coverage/create-cli/unit-tests', - exclude: ['mocks/**', '**/types.ts'], - }, - environment: 'node', - include: ['src/**/*.unit.test.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], - globalSetup: ['../../global-setup.ts'], - setupFiles: [ - '../../testing/test-setup/src/lib/fs.mock.ts', - '../../testing/test-setup/src/lib/console.mock.ts', - '../../testing/test-setup/src/lib/reset.mocks.ts', - ], - }, -}); +export default createUnitTestConfig('create-cli'); diff --git a/packages/models/src/lib/implementation/validate.unit.test.ts b/packages/models/src/lib/implementation/validate.unit.test.ts index 96e36891c..6495e3800 100644 --- a/packages/models/src/lib/implementation/validate.unit.test.ts +++ b/packages/models/src/lib/implementation/validate.unit.test.ts @@ -6,6 +6,18 @@ import { ZodError, z } from 'zod'; import { SchemaValidationError, validate, validateAsync } from './validate.js'; describe('validate', () => { + beforeEach(() => { + // Set up memfs with package.json for tests that use async transforms + // This prevents unhandled rejections when async operations continue after validation errors + vol.fromJSON({ 'package.json': '{ "name": "test" }' }, '/test'); + }); + + afterEach(async () => { + // Allow any lingering async operations from transforms to complete + // This prevents unhandled rejections in subsequent tests + await new Promise(resolve => setImmediate(resolve)); + }); + it('should return parsed data if valid', () => { const configSchema = z .object({ diff --git a/packages/models/vitest.unit.config.ts b/packages/models/vitest.unit.config.ts index bc7f57676..5c07f1b7f 100644 --- a/packages/models/vitest.unit.config.ts +++ b/packages/models/vitest.unit.config.ts @@ -1,30 +1,4 @@ /// -import { defineConfig } from 'vitest/config'; -import { tsconfigPathAliases } from '../../tools/vitest-tsconfig-path-aliases.js'; +import { createUnitTestConfig } from '../../testing/test-setup-config/src/index.js'; -export default defineConfig({ - cacheDir: '../../node_modules/.vite/models', - test: { - reporters: ['basic'], - globals: true, - cache: { - dir: '../../node_modules/.vitest', - }, - alias: tsconfigPathAliases(), - pool: 'threads', - poolOptions: { threads: { singleThread: true } }, - coverage: { - reporter: ['text', 'lcov'], - reportsDirectory: '../../coverage/models/unit-tests', - exclude: ['mocks/**', '**/types.ts', 'zod2md.config.ts'], - }, - environment: 'node', - include: ['src/**/*.unit.test.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], - globalSetup: ['../../global-setup.ts'], - setupFiles: [ - '../../testing/test-setup/src/lib/fs.mock.ts', - '../../testing/test-setup/src/lib/console.mock.ts', - '../../testing/test-setup/src/lib/reset.mocks.ts', - ], - }, -}); +export default createUnitTestConfig('models'); diff --git a/packages/nx-plugin/vitest.int.config.ts b/packages/nx-plugin/vitest.int.config.ts index 0f9f0340b..24a8e1c1b 100644 --- a/packages/nx-plugin/vitest.int.config.ts +++ b/packages/nx-plugin/vitest.int.config.ts @@ -1,28 +1,4 @@ /// -import { defineConfig } from 'vitest/config'; -import { tsconfigPathAliases } from '../../tools/vitest-tsconfig-path-aliases.js'; +import { createIntTestConfig } from '../../testing/test-setup-config/src/index.js'; -export default defineConfig({ - cacheDir: '../../node_modules/.vite/nx-plugin', - test: { - reporters: ['basic'], - globals: true, - cache: { - dir: '../../node_modules/.vitest', - }, - alias: tsconfigPathAliases(), - pool: 'threads', - poolOptions: { threads: { singleThread: true } }, - coverage: { - reporter: ['text', 'lcov'], - reportsDirectory: '../../coverage/nx-plugin/int-tests', - exclude: ['mocks/**', '**/types.ts', '**/__snapshots__/**'], - }, - environment: 'node', - include: ['src/**/*.int.test.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], - setupFiles: [ - '../../testing/test-setup/src/lib/console.mock.ts', - '../../testing/test-setup/src/lib/reset.mocks.ts', - ], - }, -}); +export default createIntTestConfig('nx-plugin'); diff --git a/packages/nx-plugin/vitest.unit.config.ts b/packages/nx-plugin/vitest.unit.config.ts index b42185d97..150c8ed55 100644 --- a/packages/nx-plugin/vitest.unit.config.ts +++ b/packages/nx-plugin/vitest.unit.config.ts @@ -1,29 +1,4 @@ /// -import { defineConfig } from 'vitest/config'; -import { tsconfigPathAliases } from '../../tools/vitest-tsconfig-path-aliases.js'; +import { createUnitTestConfig } from '../../testing/test-setup-config/src/index.js'; -export default defineConfig({ - cacheDir: '../../node_modules/.vite/nx-plugin', - test: { - reporters: ['basic'], - globals: true, - cache: { - dir: '../../node_modules/.vitest', - }, - alias: tsconfigPathAliases(), - pool: 'threads', - poolOptions: { threads: { singleThread: true } }, - coverage: { - reporter: ['text', 'lcov'], - reportsDirectory: '../../coverage/nx-plugin/unit-tests', - exclude: ['mocks/**', '**/types.ts', '**/__snapshots__/**'], - }, - environment: 'node', - include: ['src/**/*.unit.test.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], - setupFiles: [ - '../../testing/test-setup/src/lib/fs.mock.ts', - '../../testing/test-setup/src/lib/console.mock.ts', - '../../testing/test-setup/src/lib/reset.mocks.ts', - ], - }, -}); +export default createUnitTestConfig('nx-plugin'); diff --git a/packages/plugin-coverage/vitest.int.config.ts b/packages/plugin-coverage/vitest.int.config.ts index ca03251a4..5da4f09de 100644 --- a/packages/plugin-coverage/vitest.int.config.ts +++ b/packages/plugin-coverage/vitest.int.config.ts @@ -1,29 +1,4 @@ /// -import { defineConfig } from 'vitest/config'; -import { tsconfigPathAliases } from '../../tools/vitest-tsconfig-path-aliases.js'; +import { createIntTestConfig } from '../../testing/test-setup-config/src/index.js'; -export default defineConfig({ - cacheDir: '../../node_modules/.vite/plugin-coverage', - test: { - reporters: ['basic'], - globals: true, - cache: { - dir: '../../node_modules/.vitest', - }, - alias: tsconfigPathAliases(), - pool: 'threads', - poolOptions: { threads: { singleThread: true } }, - coverage: { - reporter: ['text', 'lcov'], - reportsDirectory: '../../coverage/plugin-coverage/int-tests', - exclude: ['mocks/**', '**/types.ts', '**/vitest.*.config.ts'], - }, - 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', - ], - }, -}); +export default createIntTestConfig('plugin-coverage'); diff --git a/packages/plugin-coverage/vitest.unit.config.ts b/packages/plugin-coverage/vitest.unit.config.ts index 4e8c164fd..a6dc83bc4 100644 --- a/packages/plugin-coverage/vitest.unit.config.ts +++ b/packages/plugin-coverage/vitest.unit.config.ts @@ -1,32 +1,4 @@ /// -import { defineConfig } from 'vitest/config'; -import { tsconfigPathAliases } from '../../tools/vitest-tsconfig-path-aliases.js'; +import { createUnitTestConfig } from '../../testing/test-setup-config/src/index.js'; -export default defineConfig({ - cacheDir: '../../node_modules/.vite/plugin-coverage', - test: { - reporters: ['basic'], - globals: true, - cache: { - dir: '../../node_modules/.vitest', - }, - alias: tsconfigPathAliases(), - pool: 'threads', - poolOptions: { threads: { singleThread: true } }, - coverage: { - reporter: ['text', 'lcov'], - reportsDirectory: '../../coverage/plugin-coverage/unit-tests', - exclude: ['mocks/**', '**/types.ts', '**/vitest.*.config.ts'], - }, - 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/console.mock.ts', - '../../testing/test-setup/src/lib/reset.mocks.ts', - '../../testing/test-setup/src/lib/extend/ui-logger.matcher.ts', - ], - }, -}); +export default createUnitTestConfig('plugin-coverage'); diff --git a/packages/plugin-eslint/vitest.int.config.ts b/packages/plugin-eslint/vitest.int.config.ts index cad993f3e..2b823ff01 100644 --- a/packages/plugin-eslint/vitest.int.config.ts +++ b/packages/plugin-eslint/vitest.int.config.ts @@ -1,29 +1,32 @@ /// -import { defineConfig } from 'vitest/config'; -import { tsconfigPathAliases } from '../../tools/vitest-tsconfig-path-aliases.js'; +// import { tsconfigPathAliases } from '../../testing/test-setup-config/src/lib/vitest-tsconfig-path-aliases'; +// import { defineConfig } from 'vitest/config'; +// // import { tsconfigPathAliases } from '../../tools/vitest-tsconfig-path-aliases.js'; +// export default defineConfig({ +// cacheDir: '../../node_modules/.vite/plugin-eslint', +// test: { +// reporters: ['basic'], +// globals: true, +// cache: { +// dir: '../../node_modules/.vitest', +// }, +// alias: tsconfigPathAliases(), +// pool: 'threads', +// poolOptions: { threads: { singleThread: true } }, +// coverage: { +// reporter: ['text', 'lcov'], +// reportsDirectory: '../../coverage/plugin-eslint/int-tests', +// exclude: ['mocks/**', '**/types.ts'], +// }, +// 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', +// ], +// }, +// }); +import { createIntTestConfig } from '../../testing/test-setup-config/src/index.js'; -export default defineConfig({ - cacheDir: '../../node_modules/.vite/plugin-eslint', - test: { - reporters: ['basic'], - globals: true, - cache: { - dir: '../../node_modules/.vitest', - }, - alias: tsconfigPathAliases(), - pool: 'threads', - poolOptions: { threads: { singleThread: true } }, - coverage: { - reporter: ['text', 'lcov'], - reportsDirectory: '../../coverage/plugin-eslint/int-tests', - exclude: ['mocks/**', '**/types.ts'], - }, - 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', - ], - }, -}); +export default createIntTestConfig('plugin-eslint'); diff --git a/packages/plugin-eslint/vitest.unit.config.ts b/packages/plugin-eslint/vitest.unit.config.ts index a9baf4edb..6b6757207 100644 --- a/packages/plugin-eslint/vitest.unit.config.ts +++ b/packages/plugin-eslint/vitest.unit.config.ts @@ -1,32 +1,4 @@ /// -import { defineConfig } from 'vitest/config'; -import { tsconfigPathAliases } from '../../tools/vitest-tsconfig-path-aliases.js'; +import { createUnitTestConfig } from '../../testing/test-setup-config/src/index.js'; -export default defineConfig({ - cacheDir: '../../node_modules/.vite/plugin-eslint', - test: { - reporters: ['basic'], - globals: true, - cache: { - dir: '../../node_modules/.vitest', - }, - alias: tsconfigPathAliases(), - pool: 'threads', - poolOptions: { threads: { singleThread: true } }, - coverage: { - reporter: ['text', 'lcov'], - reportsDirectory: '../../coverage/plugin-eslint/unit-tests', - exclude: ['mocks/**', '**/types.ts'], - }, - environment: 'node', - include: ['src/**/*.unit.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/fs.mock.ts', - '../../testing/test-setup/src/lib/reset.mocks.ts', - '../../testing/test-setup/src/lib/cliui.mock.ts', - '../../testing/test-setup/src/lib/extend/ui-logger.matcher.ts', - ], - }, -}); +export default createUnitTestConfig('plugin-eslint'); diff --git a/packages/plugin-js-packages/vitest.int.config.ts b/packages/plugin-js-packages/vitest.int.config.ts index 00e1f7be0..36b49dc30 100644 --- a/packages/plugin-js-packages/vitest.int.config.ts +++ b/packages/plugin-js-packages/vitest.int.config.ts @@ -1,29 +1,4 @@ /// -import { defineConfig } from 'vitest/config'; -import { tsconfigPathAliases } from '../../tools/vitest-tsconfig-path-aliases.js'; +import { createIntTestConfig } from '../../testing/test-setup-config/src/index.js'; -export default defineConfig({ - cacheDir: '../../node_modules/.vite/plugin-js-packages', - test: { - reporters: ['basic'], - globals: true, - cache: { - dir: '../../node_modules/.vitest', - }, - alias: tsconfigPathAliases(), - pool: 'threads', - poolOptions: { threads: { singleThread: true } }, - coverage: { - reporter: ['text', 'lcov'], - reportsDirectory: '../../coverage/plugin-js-packages/int-tests', - exclude: ['mocks/**', '**/types.ts'], - }, - 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', - ], - }, -}); +export default createIntTestConfig('plugin-js-packages'); diff --git a/packages/plugin-js-packages/vitest.unit.config.ts b/packages/plugin-js-packages/vitest.unit.config.ts index d75e48879..5912ace39 100644 --- a/packages/plugin-js-packages/vitest.unit.config.ts +++ b/packages/plugin-js-packages/vitest.unit.config.ts @@ -1,30 +1,4 @@ /// -import { defineConfig } from 'vitest/config'; -import { tsconfigPathAliases } from '../../tools/vitest-tsconfig-path-aliases.js'; +import { createUnitTestConfig } from '../../testing/test-setup-config/src/index.js'; -export default defineConfig({ - cacheDir: '../../node_modules/.vite/plugin-js-packages', - test: { - reporters: ['basic'], - globals: true, - cache: { - dir: '../../node_modules/.vitest', - }, - alias: tsconfigPathAliases(), - pool: 'threads', - poolOptions: { threads: { singleThread: true } }, - coverage: { - reporter: ['text', 'lcov'], - reportsDirectory: '../../coverage/plugin-js-packages/unit-tests', - exclude: ['mocks/**', '**/types.ts'], - }, - environment: 'node', - include: ['src/**/*.unit.test.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], - globalSetup: ['../../global-setup.ts'], - setupFiles: [ - '../../testing/test-setup/src/lib/fs.mock.ts', - '../../testing/test-setup/src/lib/console.mock.ts', - '../../testing/test-setup/src/lib/reset.mocks.ts', - ], - }, -}); +export default createUnitTestConfig('plugin-js-packages'); diff --git a/packages/plugin-jsdocs/vitest.int.config.ts b/packages/plugin-jsdocs/vitest.int.config.ts index 57d6dfc75..7e7422e36 100644 --- a/packages/plugin-jsdocs/vitest.int.config.ts +++ b/packages/plugin-jsdocs/vitest.int.config.ts @@ -1,30 +1,4 @@ /// -import { defineConfig } from 'vitest/config'; -import { tsconfigPathAliases } from '../../tools/vitest-tsconfig-path-aliases.js'; +import { createIntTestConfig } from '../../testing/test-setup-config/src/index.js'; -export default defineConfig({ - cacheDir: '../../node_modules/.vite/plugin-coverage', - test: { - reporters: ['basic'], - globals: true, - cache: { - dir: '../../node_modules/.vitest', - }, - alias: tsconfigPathAliases(), - pool: 'threads', - poolOptions: { threads: { singleThread: true } }, - coverage: { - reporter: ['text', 'lcov'], - reportsDirectory: '../../coverage/plugin-jsdocs/int-tests', - exclude: ['mocks/**', '**/types.ts'], - }, - 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/extend/path.matcher.ts', - ], - }, -}); +export default createIntTestConfig('plugin-jsdocs'); diff --git a/packages/plugin-jsdocs/vitest.unit.config.ts b/packages/plugin-jsdocs/vitest.unit.config.ts index 0ead182dc..f45cbef7a 100644 --- a/packages/plugin-jsdocs/vitest.unit.config.ts +++ b/packages/plugin-jsdocs/vitest.unit.config.ts @@ -1,32 +1,4 @@ /// -import { defineConfig } from 'vitest/config'; -import { tsconfigPathAliases } from '../../tools/vitest-tsconfig-path-aliases.js'; +import { createUnitTestConfig } from '../../testing/test-setup-config/src/index.js'; -export default defineConfig({ - cacheDir: '../../node_modules/.vite/plugin-coverage', - test: { - reporters: ['basic'], - globals: true, - cache: { - dir: '../../node_modules/.vitest', - }, - alias: tsconfigPathAliases(), - pool: 'threads', - poolOptions: { threads: { singleThread: true } }, - coverage: { - reporter: ['text', 'lcov'], - reportsDirectory: '../../coverage/plugin-jsdocs/unit-tests', - exclude: ['mocks/**', '**/types.ts'], - }, - 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/console.mock.ts', - '../../testing/test-setup/src/lib/reset.mocks.ts', - '../../testing/test-setup/src/lib/extend/path.matcher.ts', - ], - }, -}); +export default createUnitTestConfig('plugin-jsdocs'); diff --git a/packages/plugin-lighthouse/vitest.unit.config.ts b/packages/plugin-lighthouse/vitest.unit.config.ts index a53798020..d83a973a6 100644 --- a/packages/plugin-lighthouse/vitest.unit.config.ts +++ b/packages/plugin-lighthouse/vitest.unit.config.ts @@ -1,33 +1,4 @@ /// -import { defineConfig } from 'vitest/config'; -import { tsconfigPathAliases } from '../../tools/vitest-tsconfig-path-aliases.js'; +import { createUnitTestConfig } from '../../testing/test-setup-config/src/index.js'; -export default defineConfig({ - cacheDir: '../../node_modules/.vite/plugin-lighthouse', - test: { - reporters: ['basic'], - globals: true, - cache: { - dir: '../../node_modules/.vitest', - }, - alias: tsconfigPathAliases(), - pool: 'threads', - poolOptions: { threads: { singleThread: true } }, - coverage: { - reporter: ['text', 'lcov'], - reportsDirectory: '../../coverage/plugin-lighthouse/unit-tests', - exclude: ['mocks/**', '**/types.ts'], - }, - 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/console.mock.ts', - '../../testing/test-setup/src/lib/reset.mocks.ts', - '../../testing/test-setup/src/lib/extend/path.matcher.ts', - '../../testing/test-setup/src/lib/extend/ui-logger.matcher.ts', - ], - }, -}); +export default createUnitTestConfig('plugin-lighthouse'); diff --git a/packages/plugin-typescript/vitest.int.config.ts b/packages/plugin-typescript/vitest.int.config.ts index 3573540d7..21c2405ad 100644 --- a/packages/plugin-typescript/vitest.int.config.ts +++ b/packages/plugin-typescript/vitest.int.config.ts @@ -1,35 +1,4 @@ /// -import { defineConfig } from 'vitest/config'; -import { tsconfigPathAliases } from '../../tools/vitest-tsconfig-path-aliases.js'; +import { createIntTestConfig } from '../../testing/test-setup-config/src/index.js'; -export default defineConfig({ - cacheDir: '../../node_modules/.vite/plugin-typescript', - test: { - reporters: ['basic'], - globals: true, - cache: { - dir: '../../node_modules/.vitest/plugin-typescript', - }, - alias: tsconfigPathAliases(), - pool: 'threads', - poolOptions: { threads: { singleThread: true } }, - coverage: { - reporter: ['text', 'lcov'], - reportsDirectory: '../../coverage/plugin-typescript/int-tests', - exclude: [ - 'mocks/**', - '**/types.ts', - '**/index.ts', - 'vitest.{unit,int}.config.ts', - ], - }, - 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/reset.mocks.ts', - '../../testing/test-setup/src/lib/chrome-path.mock.ts', - ], - }, -}); +export default createIntTestConfig('plugin-typescript'); diff --git a/packages/plugin-typescript/vitest.unit.config.ts b/packages/plugin-typescript/vitest.unit.config.ts index 2dcce41e8..cc4bab174 100644 --- a/packages/plugin-typescript/vitest.unit.config.ts +++ b/packages/plugin-typescript/vitest.unit.config.ts @@ -1,37 +1,4 @@ /// -import { defineConfig } from 'vitest/config'; -import { tsconfigPathAliases } from '../../tools/vitest-tsconfig-path-aliases.js'; +import { createUnitTestConfig } from '../../testing/test-setup-config/src/index.js'; -export default defineConfig({ - cacheDir: '../../node_modules/.vite/plugin-typescript', - test: { - reporters: ['basic'], - globals: true, - cache: { - dir: '../../node_modules/.vitest/plugin-typescript', - }, - alias: tsconfigPathAliases(), - pool: 'threads', - poolOptions: { threads: { singleThread: true } }, - coverage: { - reporter: ['text', 'lcov'], - reportsDirectory: '../../coverage/plugin-typescript/unit-tests', - exclude: [ - 'mocks/**', - '**/types.ts', - '**/index.ts', - 'vitest.{unit,int}.config.ts', - ], - }, - 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/extend/ui-logger.matcher.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', - ], - }, -}); +export default createUnitTestConfig('plugin-typescript'); diff --git a/testing/test-nx-utils/src/lib/utils/nx-plugin.unit.test.ts b/testing/test-nx-utils/src/lib/utils/nx-plugin.unit.test.ts index 7710cfccf..013d06bcc 100644 --- a/testing/test-nx-utils/src/lib/utils/nx-plugin.unit.test.ts +++ b/testing/test-nx-utils/src/lib/utils/nx-plugin.unit.test.ts @@ -1,5 +1,5 @@ -import * as process from 'node:process'; import { describe, expect } from 'vitest'; +import { MEMFS_VOLUME } from '@code-pushup/test-utils'; import { createNodesContext, invokeCreateNodesOnVirtualFiles, @@ -23,7 +23,7 @@ describe('createNodesContext', () => { const context = createNodesContext(); expect(context).toStrictEqual( expect.objectContaining({ - workspaceRoot: process.cwd(), + workspaceRoot: MEMFS_VOLUME, nxJsonConfiguration: {}, }), ); diff --git a/testing/test-nx-utils/src/lib/utils/nx.unit.test.ts b/testing/test-nx-utils/src/lib/utils/nx.unit.test.ts index 7587f05fc..fd6593875 100644 --- a/testing/test-nx-utils/src/lib/utils/nx.unit.test.ts +++ b/testing/test-nx-utils/src/lib/utils/nx.unit.test.ts @@ -1,12 +1,12 @@ -import * as process from 'node:process'; import { createTreeWithEmptyWorkspace } from 'nx/src/generators/testing-utils/create-tree-with-empty-workspace'; import { describe, expect } from 'vitest'; +import { MEMFS_VOLUME } from '@code-pushup/test-utils'; import { executorContext, registerPluginInWorkspace } from './nx.js'; describe('executorContext', () => { it('should create context for given project name', () => { expect(executorContext('my-lib')).toStrictEqual({ - cwd: process.cwd(), + cwd: MEMFS_VOLUME, isVerbose: false, projectName: 'my-lib', projectsConfigurations: { diff --git a/testing/test-nx-utils/vitest.unit.config.ts b/testing/test-nx-utils/vitest.unit.config.ts index 0e5103fa3..cf971691f 100644 --- a/testing/test-nx-utils/vitest.unit.config.ts +++ b/testing/test-nx-utils/vitest.unit.config.ts @@ -1,24 +1,4 @@ /// -import { defineConfig } from 'vitest/config'; -import { tsconfigPathAliases } from '../../tools/vitest-tsconfig-path-aliases.js'; +import { createUnitTestConfig } from '../../testing/test-setup-config/src/index.js'; -export default defineConfig({ - cacheDir: '../node_modules/.vite/test-nx-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/test-nx-utils/unit-tests', - exclude: ['**/*.mock.{mjs,ts}', '**/*.config.{js,mjs,ts}'], - }, - environment: 'node', - include: ['src/**/*.unit.test.ts'], - }, -}); +export default createUnitTestConfig('test-nx-utils'); diff --git a/testing/test-setup-config/src/index.ts b/testing/test-setup-config/src/index.ts index 68d9a93c6..4c0219d3b 100644 --- a/testing/test-setup-config/src/index.ts +++ b/testing/test-setup-config/src/index.ts @@ -4,6 +4,11 @@ export { createE2ETestConfig, } from './lib/vitest-setup-presets.js'; -export type { E2ETestOptions } from './lib/vitest-config-factory.js'; +export type { E2ETestOptions, TestKind } from './lib/vitest-config-factory.js'; + +export { + createVitestConfig, + type E2ETestOptions as E2ETestOptionsAlias, +} from './lib/vitest-config-factory.js'; export { getSetupFiles } from './lib/vitest-setup-files.js'; 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 index 984f12f01..85a81d4c4 100644 --- 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 @@ -134,11 +134,9 @@ describe('createVitestConfig', () => { const config = createVitestConfig('test-package', 'int'); const setupFiles = config.test!.setupFiles; - // Should include console mock expect(setupFiles).toContain( '../../testing/test-setup/src/lib/console.mock.ts', ); - // Should NOT include fs, cliui, or git mocks (integration tests need real implementations) expect(setupFiles).not.toContain( '../../testing/test-setup/src/lib/fs.mock.ts', ); @@ -148,10 +146,12 @@ describe('createVitestConfig', () => { expect(setupFiles).not.toContain( '../../testing/test-setup/src/lib/git.mock.ts', ); - // Should include all matchers expect(setupFiles).toContain( '../../testing/test-setup/src/lib/extend/path.matcher.ts', ); + expect(setupFiles).toContain( + '../../testing/test-setup/src/lib/extend/ui-logger.matcher.ts', + ); }); it('should not enable typecheck for integration tests', () => { diff --git a/testing/test-setup-config/src/lib/vitest-setup-files.ts b/testing/test-setup-config/src/lib/vitest-setup-files.ts index cbd137105..45b24cc36 100644 --- a/testing/test-setup-config/src/lib/vitest-setup-files.ts +++ b/testing/test-setup-config/src/lib/vitest-setup-files.ts @@ -20,10 +20,10 @@ const CUSTOM_MATCHERS = [ * which is why they use `../../` to navigate to the workspace root first. */ const UNIT_TEST_SETUP_FILES = [ + '../../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/cliui.mock.ts', - '../../testing/test-setup/src/lib/fs.mock.ts', '../../testing/test-setup/src/lib/git.mock.ts', '../../testing/test-setup/src/lib/portal-client.mock.ts', ...CUSTOM_MATCHERS, @@ -34,10 +34,12 @@ const UNIT_TEST_SETUP_FILES = [ * * These paths are relative to the config file location (typically `packages//vitest.int.config.ts`), * which is why they use `../../` to navigate to the workspace root first. + */ const INT_TEST_SETUP_FILES = [ '../../testing/test-setup/src/lib/console.mock.ts', '../../testing/test-setup/src/lib/reset.mocks.ts', + '../../testing/test-setup/src/lib/chrome-path.mock.ts', ...CUSTOM_MATCHERS, ] as const; diff --git a/testing/test-setup-config/src/lib/vitest-setup-files.unit.test.ts b/testing/test-setup-config/src/lib/vitest-setup-files.unit.test.ts index b3ad4c445..3ed6c44f1 100644 --- a/testing/test-setup-config/src/lib/vitest-setup-files.unit.test.ts +++ b/testing/test-setup-config/src/lib/vitest-setup-files.unit.test.ts @@ -42,16 +42,24 @@ describe('vitest-setup-files', () => { }); describe('integration test setup files', () => { - it('should return exactly 6 setup files with correct includes', () => { + it('should return exactly 7 setup files with essential mocks and custom matchers', () => { const setupFiles = getSetupFiles('int'); - expect(setupFiles).toHaveLength(6); + expect(setupFiles).toHaveLength(7); expect(setupFiles).toContain( '../../testing/test-setup/src/lib/console.mock.ts', ); expect(setupFiles).toContain( '../../testing/test-setup/src/lib/reset.mocks.ts', ); + expect(setupFiles).toContain( + '../../testing/test-setup/src/lib/chrome-path.mock.ts', + ); + }); + + it('should include custom matchers for integration tests', () => { + const setupFiles = getSetupFiles('int'); + expect(setupFiles).toContain( '../../testing/test-setup/src/lib/extend/ui-logger.matcher.ts', ); @@ -133,7 +141,6 @@ describe('vitest-setup-files', () => { const intFiles = getSetupFiles('int'); const e2eFiles = getSetupFiles('e2e'); - // All paths should start with ../../ [...unitFiles, ...intFiles, ...e2eFiles].forEach(path => { expect(path).toMatch(/^\.\.\/\.\.\//); }); @@ -144,8 +151,6 @@ describe('vitest-setup-files', () => { it('should return a readonly array', () => { const setupFiles = getSetupFiles('unit'); - // TypeScript will enforce readonly at compile time, - // but we can verify it's an array at runtime expect(Array.isArray(setupFiles)).toBe(true); }); }); @@ -156,7 +161,6 @@ describe('vitest-setup-files', () => { const intFiles = getSetupFiles('int'); const e2eFiles = getSetupFiles('e2e'); - // Different lengths means different setup files expect(unitFiles.length).not.toBe(intFiles.length); expect(intFiles.length).not.toBe(e2eFiles.length); expect(unitFiles.length).not.toBe(e2eFiles.length); diff --git a/testing/test-setup/vitest.unit.config.ts b/testing/test-setup/vitest.unit.config.ts index a275ed108..c581b0f4d 100644 --- a/testing/test-setup/vitest.unit.config.ts +++ b/testing/test-setup/vitest.unit.config.ts @@ -1,29 +1,4 @@ /// -import { defineConfig } from 'vitest/config'; -import { tsconfigPathAliases } from '../../tools/vitest-tsconfig-path-aliases.js'; +import { createUnitTestConfig } from '../../testing/test-setup-config/src/index.js'; -export default defineConfig({ - cacheDir: '../node_modules/.vite/test-setup', - test: { - reporters: ['basic'], - globals: true, - cache: { - dir: '../node_modules/.vitest', - }, - alias: tsconfigPathAliases(), - pool: 'threads', - poolOptions: { threads: { singleThread: true } }, - coverage: { - reporter: ['text', 'lcov'], - reportsDirectory: '../../coverage/test-setup/unit-tests', - exclude: ['**/*.mock.{mjs,ts}', '**/*.config.{js,mjs,ts}'], - }, - environment: 'node', - include: ['src/**/*.unit.test.ts'], - setupFiles: [ - '../test-setup/src/lib/reset.mocks.ts', - '../test-setup/src/lib/extend/path.matcher.ts', - '../test-setup/src/lib/extend/markdown-table.matcher.ts', - ], - }, -}); +export default createUnitTestConfig('test-setup'); diff --git a/testing/test-utils/src/lib/utils/os-agnostic-paths.unit.test.ts b/testing/test-utils/src/lib/utils/os-agnostic-paths.unit.test.ts index 4925084fd..6a9d6ee85 100644 --- a/testing/test-utils/src/lib/utils/os-agnostic-paths.unit.test.ts +++ b/testing/test-utils/src/lib/utils/os-agnostic-paths.unit.test.ts @@ -10,7 +10,7 @@ import { import { osAgnosticPath } from './os-agnostic-paths.js'; describe('osAgnosticPath', () => { - const cwdSpy: MockInstance<[], string> = vi.spyOn(process, 'cwd'); + let cwdSpy: MockInstance<[], string>; it('should forward nullish paths on Linux/macOS and Windows', () => { expect(osAgnosticPath(undefined)).toBeUndefined(); @@ -20,11 +20,12 @@ describe('osAgnosticPath', () => { const unixCwd = '/Users/jerry'; beforeEach(() => { + cwdSpy = vi.spyOn(process, 'cwd'); cwdSpy.mockReturnValue(unixCwd); }); afterEach(() => { - cwdSpy.mockReset(); + cwdSpy.mockRestore(); }); it('should convert a path within the CWD to an OS-agnostic path on Linux/macOS', () => { @@ -74,11 +75,12 @@ describe('osAgnosticPath', () => { const windowsCWD = String.raw`D:\users\jerry`; beforeEach(() => { + cwdSpy = vi.spyOn(process, 'cwd'); cwdSpy.mockReturnValue(windowsCWD); }); afterEach(() => { - cwdSpy.mockReset(); + cwdSpy.mockRestore(); }); it('should return paths outside of CWD on Windows', () => { diff --git a/testing/test-utils/vitest.unit.config.ts b/testing/test-utils/vitest.unit.config.ts index 990029cec..c9e679060 100644 --- a/testing/test-utils/vitest.unit.config.ts +++ b/testing/test-utils/vitest.unit.config.ts @@ -1,24 +1,4 @@ /// -import { defineConfig } from 'vitest/config'; -import { tsconfigPathAliases } from '../../tools/vitest-tsconfig-path-aliases.js'; +import { createUnitTestConfig } from '../../testing/test-setup-config/src/index.js'; -export default defineConfig({ - cacheDir: '../node_modules/.vite/test-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/test-utils/unit-tests', - exclude: ['**/*.mock.{mjs,ts}', '**/*.config.{js,mjs,ts}'], - }, - environment: 'node', - include: ['src/**/*.unit.test.ts'], - }, -}); +export default createUnitTestConfig('test-utils'); diff --git a/tools/eslint-formatter-multi/vitest.unit.config.ts b/tools/eslint-formatter-multi/vitest.unit.config.ts index 75295eb74..3974e3457 100644 --- a/tools/eslint-formatter-multi/vitest.unit.config.ts +++ b/tools/eslint-formatter-multi/vitest.unit.config.ts @@ -1,32 +1,4 @@ /// -import { defineConfig } from 'vitest/config'; -// eslint-disable-next-line import/no-useless-path-segments -import { tsconfigPathAliases } from '../../tools/vitest-tsconfig-path-aliases.js'; +import { createUnitTestConfig } from '../../testing/test-setup-config/src/index.js'; -export default defineConfig({ - cacheDir: '../../node_modules/.vite/plugin-eslint', - test: { - reporters: ['basic'], - globals: true, - cache: { - dir: '../../node_modules/.vitest', - }, - alias: tsconfigPathAliases(), - pool: 'threads', - poolOptions: { threads: { singleThread: true } }, - coverage: { - reporter: ['text', 'lcov'], - reportsDirectory: '../../coverage/plugin-eslint/unit-tests', - exclude: ['mocks/**', '**/types.ts'], - }, - environment: 'node', - include: ['src/**/*.unit.test.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], - exclude: ['src/index.ts'], - globalSetup: ['../../global-setup.ts'], - setupFiles: [ - '../../testing/test-setup/src/lib/console.mock.ts', - '../../testing/test-setup/src/lib/fs.mock.ts', - '../../testing/test-setup/src/lib/reset.mocks.ts', - ], - }, -}); +export default createUnitTestConfig('eslint-formatter-multi'); From cef39d3438ff8f85148cabf9a20582232ac7381d Mon Sep 17 00:00:00 2001 From: "Szymon.Poltorak" Date: Fri, 31 Oct 2025 16:35:25 +0100 Subject: [PATCH 18/23] refactor(testing): replace logger with console.warn in test folder teardown utility --- testing/test-utils/src/lib/utils/test-folder-setup.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/testing/test-utils/src/lib/utils/test-folder-setup.ts b/testing/test-utils/src/lib/utils/test-folder-setup.ts index 7b4750b35..8e3853011 100644 --- a/testing/test-utils/src/lib/utils/test-folder-setup.ts +++ b/testing/test-utils/src/lib/utils/test-folder-setup.ts @@ -1,4 +1,3 @@ -import { logger } from '@nx/devkit'; import { bold } from 'ansis'; import { mkdir, rm, stat } from 'node:fs/promises'; @@ -11,7 +10,7 @@ export async function teardownTestFolder(dirName: string) { try { const stats = await stat(dirName); if (!stats.isDirectory()) { - logger.warn( + console.warn( `⚠️ You are trying to delete a file instead of a directory - ${bold( dirName, )}.`, @@ -30,7 +29,7 @@ export async function teardownTestFolder(dirName: string) { retryDelay: 100, }); } catch { - logger.warn( + console.warn( `⚠️ Failed to delete test artefact ${bold( dirName, )} so the folder is still in the file system!\nIt may require a deletion before running e2e tests again.`, From 38bc0bb456963c51352f16ac187e23824c1fc680 Mon Sep 17 00:00:00 2001 From: "Szymon.Poltorak" Date: Tue, 4 Nov 2025 11:14:47 +0100 Subject: [PATCH 19/23] chore(testing): add logger mock to integration test setup files and update test expectations --- .../mocks/fixtures/nx-monorepo/package-lock.json | 6 ++++++ testing/test-setup-config/src/lib/vitest-setup-files.ts | 1 + .../src/lib/vitest-setup-files.unit.test.ts | 7 +++++-- 3 files changed, 12 insertions(+), 2 deletions(-) create mode 100644 packages/plugin-eslint/mocks/fixtures/nx-monorepo/package-lock.json diff --git a/packages/plugin-eslint/mocks/fixtures/nx-monorepo/package-lock.json b/packages/plugin-eslint/mocks/fixtures/nx-monorepo/package-lock.json new file mode 100644 index 000000000..22d47ba27 --- /dev/null +++ b/packages/plugin-eslint/mocks/fixtures/nx-monorepo/package-lock.json @@ -0,0 +1,6 @@ +{ + "name": "mock-nx-workspace", + "lockfileVersion": 3, + "requires": true, + "packages": {} +} diff --git a/testing/test-setup-config/src/lib/vitest-setup-files.ts b/testing/test-setup-config/src/lib/vitest-setup-files.ts index 45b24cc36..b152edea8 100644 --- a/testing/test-setup-config/src/lib/vitest-setup-files.ts +++ b/testing/test-setup-config/src/lib/vitest-setup-files.ts @@ -40,6 +40,7 @@ const INT_TEST_SETUP_FILES = [ '../../testing/test-setup/src/lib/console.mock.ts', '../../testing/test-setup/src/lib/reset.mocks.ts', '../../testing/test-setup/src/lib/chrome-path.mock.ts', + '../../testing/test-setup/src/lib/logger.mock.ts', ...CUSTOM_MATCHERS, ] as const; diff --git a/testing/test-setup-config/src/lib/vitest-setup-files.unit.test.ts b/testing/test-setup-config/src/lib/vitest-setup-files.unit.test.ts index 3ed6c44f1..0e8d9c2a2 100644 --- a/testing/test-setup-config/src/lib/vitest-setup-files.unit.test.ts +++ b/testing/test-setup-config/src/lib/vitest-setup-files.unit.test.ts @@ -42,10 +42,10 @@ describe('vitest-setup-files', () => { }); describe('integration test setup files', () => { - it('should return exactly 7 setup files with essential mocks and custom matchers', () => { + it('should return exactly 8 setup files with essential mocks and custom matchers', () => { const setupFiles = getSetupFiles('int'); - expect(setupFiles).toHaveLength(7); + expect(setupFiles).toHaveLength(8); expect(setupFiles).toContain( '../../testing/test-setup/src/lib/console.mock.ts', ); @@ -55,6 +55,9 @@ describe('vitest-setup-files', () => { expect(setupFiles).toContain( '../../testing/test-setup/src/lib/chrome-path.mock.ts', ); + expect(setupFiles).toContain( + '../../testing/test-setup/src/lib/logger.mock.ts', + ); }); it('should include custom matchers for integration tests', () => { From fa74e58e6df5ac1e34d509d27c83272fda218a7b Mon Sep 17 00:00:00 2001 From: "Szymon.Poltorak" Date: Tue, 4 Nov 2025 11:39:30 +0100 Subject: [PATCH 20/23] refactor(testing): include logger mock configs and adjust related tests --- packages/ci/vitest.int.config.ts | 15 ++++++++++- packages/ci/vitest.unit.config.ts | 15 ++++++++++- .../src/lib/vitest-setup-files.ts | 1 - .../src/lib/vitest-setup-files.unit.test.ts | 7 ++--- tools/vitest-tsconfig-path-aliases.ts | 27 ------------------- 5 files changed, 30 insertions(+), 35 deletions(-) delete mode 100644 tools/vitest-tsconfig-path-aliases.ts diff --git a/packages/ci/vitest.int.config.ts b/packages/ci/vitest.int.config.ts index c1f292fd0..961c02161 100644 --- a/packages/ci/vitest.int.config.ts +++ b/packages/ci/vitest.int.config.ts @@ -1,4 +1,17 @@ /// import { createIntTestConfig } from '../../testing/test-setup-config/src/index.js'; -export default createIntTestConfig('ci'); +let config = createIntTestConfig('ci'); + +config = { + ...config, + test: { + ...config.test, + setupFiles: [ + ...(config.test!.setupFiles || []), + '../../testing/test-setup/src/lib/logger.mock.ts', + ], + }, +}; + +export default config; diff --git a/packages/ci/vitest.unit.config.ts b/packages/ci/vitest.unit.config.ts index cb9ecaa3b..8d1bb9c85 100644 --- a/packages/ci/vitest.unit.config.ts +++ b/packages/ci/vitest.unit.config.ts @@ -1,4 +1,17 @@ /// import { createUnitTestConfig } from '../../testing/test-setup-config/src/index.js'; -export default createUnitTestConfig('ci'); +let config = createUnitTestConfig('ci'); + +config = { + ...config, + test: { + ...config.test, + setupFiles: [ + ...(config.test!.setupFiles || []), + '../../testing/test-setup/src/lib/logger.mock.ts', + ], + }, +}; + +export default config; diff --git a/testing/test-setup-config/src/lib/vitest-setup-files.ts b/testing/test-setup-config/src/lib/vitest-setup-files.ts index b152edea8..45b24cc36 100644 --- a/testing/test-setup-config/src/lib/vitest-setup-files.ts +++ b/testing/test-setup-config/src/lib/vitest-setup-files.ts @@ -40,7 +40,6 @@ const INT_TEST_SETUP_FILES = [ '../../testing/test-setup/src/lib/console.mock.ts', '../../testing/test-setup/src/lib/reset.mocks.ts', '../../testing/test-setup/src/lib/chrome-path.mock.ts', - '../../testing/test-setup/src/lib/logger.mock.ts', ...CUSTOM_MATCHERS, ] as const; diff --git a/testing/test-setup-config/src/lib/vitest-setup-files.unit.test.ts b/testing/test-setup-config/src/lib/vitest-setup-files.unit.test.ts index 0e8d9c2a2..3ed6c44f1 100644 --- a/testing/test-setup-config/src/lib/vitest-setup-files.unit.test.ts +++ b/testing/test-setup-config/src/lib/vitest-setup-files.unit.test.ts @@ -42,10 +42,10 @@ describe('vitest-setup-files', () => { }); describe('integration test setup files', () => { - it('should return exactly 8 setup files with essential mocks and custom matchers', () => { + it('should return exactly 7 setup files with essential mocks and custom matchers', () => { const setupFiles = getSetupFiles('int'); - expect(setupFiles).toHaveLength(8); + expect(setupFiles).toHaveLength(7); expect(setupFiles).toContain( '../../testing/test-setup/src/lib/console.mock.ts', ); @@ -55,9 +55,6 @@ describe('vitest-setup-files', () => { expect(setupFiles).toContain( '../../testing/test-setup/src/lib/chrome-path.mock.ts', ); - expect(setupFiles).toContain( - '../../testing/test-setup/src/lib/logger.mock.ts', - ); }); it('should include custom matchers for integration tests', () => { diff --git a/tools/vitest-tsconfig-path-aliases.ts b/tools/vitest-tsconfig-path-aliases.ts deleted file mode 100644 index aec88e199..000000000 --- a/tools/vitest-tsconfig-path-aliases.ts +++ /dev/null @@ -1,27 +0,0 @@ -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, - }), - ); -} From b792eeb584f9e3d27ab3668e5f5a4e6f80e74adc Mon Sep 17 00:00:00 2001 From: "Szymon.Poltorak" Date: Tue, 4 Nov 2025 12:12:03 +0100 Subject: [PATCH 21/23] refactor(testing): add guard check for console info in mock file --- testing/test-setup/src/lib/chrome-path.mock.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/testing/test-setup/src/lib/chrome-path.mock.ts b/testing/test-setup/src/lib/chrome-path.mock.ts index dbe1542af..c31788adf 100644 --- a/testing/test-setup/src/lib/chrome-path.mock.ts +++ b/testing/test-setup/src/lib/chrome-path.mock.ts @@ -15,9 +15,12 @@ beforeEach(async () => { error.message.includes('No Chrome installations found.') ) { const chromium = await import('chromium'); - console.info( - `${error.message} Using chromium from node_modules instead: ${chromium.path}`, - ); + // console.info may be overridden by multi-progress-bars or other libraries + if (typeof console.info === 'function') { + console.info( + `${error.message} Using chromium from node_modules instead: ${chromium.path}`, + ); + } vi.stubEnv('CHROME_PATH', chromium.path); } else { throw error; From 9f41bb252d6938dcb01efa74608cd125f8dac03c Mon Sep 17 00:00:00 2001 From: "Szymon.Poltorak" Date: Tue, 4 Nov 2025 16:22:46 +0100 Subject: [PATCH 22/23] refactor(testing): remove redundant comment --- packages/plugin-eslint/vitest.int.config.ts | 28 --------------------- 1 file changed, 28 deletions(-) diff --git a/packages/plugin-eslint/vitest.int.config.ts b/packages/plugin-eslint/vitest.int.config.ts index 2b823ff01..78b456133 100644 --- a/packages/plugin-eslint/vitest.int.config.ts +++ b/packages/plugin-eslint/vitest.int.config.ts @@ -1,32 +1,4 @@ /// -// import { tsconfigPathAliases } from '../../testing/test-setup-config/src/lib/vitest-tsconfig-path-aliases'; -// import { defineConfig } from 'vitest/config'; -// // import { tsconfigPathAliases } from '../../tools/vitest-tsconfig-path-aliases.js'; -// export default defineConfig({ -// cacheDir: '../../node_modules/.vite/plugin-eslint', -// test: { -// reporters: ['basic'], -// globals: true, -// cache: { -// dir: '../../node_modules/.vitest', -// }, -// alias: tsconfigPathAliases(), -// pool: 'threads', -// poolOptions: { threads: { singleThread: true } }, -// coverage: { -// reporter: ['text', 'lcov'], -// reportsDirectory: '../../coverage/plugin-eslint/int-tests', -// exclude: ['mocks/**', '**/types.ts'], -// }, -// 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', -// ], -// }, -// }); import { createIntTestConfig } from '../../testing/test-setup-config/src/index.js'; export default createIntTestConfig('plugin-eslint'); From 2772f2b617dabac33445faa7eea996e46f3d7f34 Mon Sep 17 00:00:00 2001 From: "Szymon.Poltorak" Date: Wed, 5 Nov 2025 09:56:41 +0100 Subject: [PATCH 23/23] refactor(testing): remove Vitest reference comments from configuration files --- e2e/ci-e2e/vitest.e2e.config.ts | 1 - e2e/cli-e2e/vitest.e2e.config.ts | 1 - e2e/create-cli-e2e/vitest.e2e.config.ts | 1 - e2e/nx-plugin-e2e/vitest.e2e.config.ts | 1 - e2e/plugin-coverage-e2e/vitest.e2e.config.ts | 1 - e2e/plugin-eslint-e2e/vitest.e2e.config.ts | 1 - e2e/plugin-js-packages-e2e/vitest.e2e.config.ts | 1 - e2e/plugin-jsdocs-e2e/vitest.e2e.config.ts | 1 - e2e/plugin-lighthouse-e2e/vitest.e2e.config.ts | 1 - e2e/plugin-typescript-e2e/vitest.e2e.config.ts | 1 - examples/plugins/vitest.int.config.ts | 1 - examples/plugins/vitest.unit.config.ts | 1 - packages/ci/vitest.int.config.ts | 1 - packages/ci/vitest.unit.config.ts | 1 - packages/cli/vitest.int.config.ts | 1 - packages/cli/vitest.unit.config.ts | 1 - packages/core/vitest.int.config.ts | 1 - packages/core/vitest.unit.config.ts | 1 - packages/create-cli/vitest.unit.config.ts | 1 - packages/models/vitest.unit.config.ts | 1 - packages/nx-plugin/vitest.int.config.ts | 1 - packages/nx-plugin/vitest.unit.config.ts | 1 - packages/plugin-coverage/vitest.int.config.ts | 1 - packages/plugin-coverage/vitest.unit.config.ts | 1 - .../mocks/fixtures/nx-monorepo/package-lock.json | 6 ------ packages/plugin-eslint/vitest.int.config.ts | 1 - packages/plugin-eslint/vitest.unit.config.ts | 1 - packages/plugin-js-packages/vitest.int.config.ts | 1 - .../plugin-js-packages/vitest.unit.config.ts | 1 - packages/plugin-jsdocs/vitest.int.config.ts | 1 - packages/plugin-jsdocs/vitest.unit.config.ts | 1 - packages/plugin-lighthouse/vitest.unit.config.ts | 1 - packages/plugin-typescript/vitest.int.config.ts | 1 - packages/plugin-typescript/vitest.unit.config.ts | 1 - packages/utils/vitest.int.config.ts | 1 - packages/utils/vitest.unit.config.ts | 1 - testing/test-nx-utils/vitest.unit.config.ts | 1 - .../src/lib/vitest-config-factory.ts | 6 ++---- .../src/lib/vitest-config-factory.unit.test.ts | 16 ++++++---------- .../src/lib/vitest-setup-files.ts | 2 ++ .../src/lib/vitest-setup-files.unit.test.ts | 12 +++++++++--- testing/test-setup-config/vitest.unit.config.ts | 1 - testing/test-setup/src/lib/logger.mock.ts | 6 ++++-- testing/test-setup/vitest.unit.config.ts | 1 - testing/test-utils/vitest.unit.config.ts | 1 - .../eslint-formatter-multi/vitest.unit.config.ts | 1 - 46 files changed, 23 insertions(+), 65 deletions(-) delete mode 100644 packages/plugin-eslint/mocks/fixtures/nx-monorepo/package-lock.json diff --git a/e2e/ci-e2e/vitest.e2e.config.ts b/e2e/ci-e2e/vitest.e2e.config.ts index ff9d2856e..cc856f443 100644 --- a/e2e/ci-e2e/vitest.e2e.config.ts +++ b/e2e/ci-e2e/vitest.e2e.config.ts @@ -1,4 +1,3 @@ -/// import { createE2ETestConfig } from '../../testing/test-setup-config/src/index.js'; export default createE2ETestConfig('ci-e2e', { diff --git a/e2e/cli-e2e/vitest.e2e.config.ts b/e2e/cli-e2e/vitest.e2e.config.ts index 730c6d3fd..a4c7769c8 100644 --- a/e2e/cli-e2e/vitest.e2e.config.ts +++ b/e2e/cli-e2e/vitest.e2e.config.ts @@ -1,4 +1,3 @@ -/// import { createE2ETestConfig } from '../../testing/test-setup-config/src/index.js'; export default createE2ETestConfig('cli-e2e', { diff --git a/e2e/create-cli-e2e/vitest.e2e.config.ts b/e2e/create-cli-e2e/vitest.e2e.config.ts index 2a5be63f3..a1bcedbb8 100644 --- a/e2e/create-cli-e2e/vitest.e2e.config.ts +++ b/e2e/create-cli-e2e/vitest.e2e.config.ts @@ -1,4 +1,3 @@ -/// import { createE2ETestConfig } from '../../testing/test-setup-config/src/index.js'; export default createE2ETestConfig('create-cli-e2e', { diff --git a/e2e/nx-plugin-e2e/vitest.e2e.config.ts b/e2e/nx-plugin-e2e/vitest.e2e.config.ts index 72576b045..b1b60a1aa 100644 --- a/e2e/nx-plugin-e2e/vitest.e2e.config.ts +++ b/e2e/nx-plugin-e2e/vitest.e2e.config.ts @@ -1,4 +1,3 @@ -/// import { createE2ETestConfig } from '../../testing/test-setup-config/src/index.js'; export default createE2ETestConfig('nx-plugin-e2e', { diff --git a/e2e/plugin-coverage-e2e/vitest.e2e.config.ts b/e2e/plugin-coverage-e2e/vitest.e2e.config.ts index 34517b1e1..d8321ccd7 100644 --- a/e2e/plugin-coverage-e2e/vitest.e2e.config.ts +++ b/e2e/plugin-coverage-e2e/vitest.e2e.config.ts @@ -1,4 +1,3 @@ -/// import { createE2ETestConfig } from '../../testing/test-setup-config/src/index.js'; export default createE2ETestConfig('plugin-coverage-e2e', { diff --git a/e2e/plugin-eslint-e2e/vitest.e2e.config.ts b/e2e/plugin-eslint-e2e/vitest.e2e.config.ts index 9690a49b1..b16df27c6 100644 --- a/e2e/plugin-eslint-e2e/vitest.e2e.config.ts +++ b/e2e/plugin-eslint-e2e/vitest.e2e.config.ts @@ -1,4 +1,3 @@ -/// import { createE2ETestConfig } from '../../testing/test-setup-config/src/index.js'; export default createE2ETestConfig('plugin-eslint-e2e', { diff --git a/e2e/plugin-js-packages-e2e/vitest.e2e.config.ts b/e2e/plugin-js-packages-e2e/vitest.e2e.config.ts index aecd2506a..30f03ebb5 100644 --- a/e2e/plugin-js-packages-e2e/vitest.e2e.config.ts +++ b/e2e/plugin-js-packages-e2e/vitest.e2e.config.ts @@ -1,4 +1,3 @@ -/// import { createE2ETestConfig } from '../../testing/test-setup-config/src/index.js'; export default createE2ETestConfig('plugin-js-packages-e2e', { diff --git a/e2e/plugin-jsdocs-e2e/vitest.e2e.config.ts b/e2e/plugin-jsdocs-e2e/vitest.e2e.config.ts index 0cde9c424..2ccf45da4 100644 --- a/e2e/plugin-jsdocs-e2e/vitest.e2e.config.ts +++ b/e2e/plugin-jsdocs-e2e/vitest.e2e.config.ts @@ -1,4 +1,3 @@ -/// import { createE2ETestConfig } from '../../testing/test-setup-config/src/index.js'; export default createE2ETestConfig('plugin-jsdocs-e2e', { diff --git a/e2e/plugin-lighthouse-e2e/vitest.e2e.config.ts b/e2e/plugin-lighthouse-e2e/vitest.e2e.config.ts index 5964409c7..422d0003f 100644 --- a/e2e/plugin-lighthouse-e2e/vitest.e2e.config.ts +++ b/e2e/plugin-lighthouse-e2e/vitest.e2e.config.ts @@ -1,4 +1,3 @@ -/// import { createE2ETestConfig } from '../../testing/test-setup-config/src/index.js'; export default createE2ETestConfig('plugin-lighthouse-e2e', { diff --git a/e2e/plugin-typescript-e2e/vitest.e2e.config.ts b/e2e/plugin-typescript-e2e/vitest.e2e.config.ts index 41a602360..50fb4e0a1 100644 --- a/e2e/plugin-typescript-e2e/vitest.e2e.config.ts +++ b/e2e/plugin-typescript-e2e/vitest.e2e.config.ts @@ -1,4 +1,3 @@ -/// import { createE2ETestConfig } from '../../testing/test-setup-config/src/index.js'; export default createE2ETestConfig('plugin-typescript-e2e', { diff --git a/examples/plugins/vitest.int.config.ts b/examples/plugins/vitest.int.config.ts index fdb7bfb02..e36e7c452 100644 --- a/examples/plugins/vitest.int.config.ts +++ b/examples/plugins/vitest.int.config.ts @@ -1,4 +1,3 @@ -/// import { createIntTestConfig } from '../../testing/test-setup-config/src/index.js'; export default createIntTestConfig('examples-plugins'); diff --git a/examples/plugins/vitest.unit.config.ts b/examples/plugins/vitest.unit.config.ts index a2172a666..db3f979f3 100644 --- a/examples/plugins/vitest.unit.config.ts +++ b/examples/plugins/vitest.unit.config.ts @@ -1,4 +1,3 @@ -/// import { createUnitTestConfig } from '../../testing/test-setup-config/src/index.js'; export default createUnitTestConfig('examples-plugins'); diff --git a/packages/ci/vitest.int.config.ts b/packages/ci/vitest.int.config.ts index 961c02161..6e5eecf6d 100644 --- a/packages/ci/vitest.int.config.ts +++ b/packages/ci/vitest.int.config.ts @@ -1,4 +1,3 @@ -/// import { createIntTestConfig } from '../../testing/test-setup-config/src/index.js'; let config = createIntTestConfig('ci'); diff --git a/packages/ci/vitest.unit.config.ts b/packages/ci/vitest.unit.config.ts index 8d1bb9c85..be6aa8cb1 100644 --- a/packages/ci/vitest.unit.config.ts +++ b/packages/ci/vitest.unit.config.ts @@ -1,4 +1,3 @@ -/// import { createUnitTestConfig } from '../../testing/test-setup-config/src/index.js'; let config = createUnitTestConfig('ci'); diff --git a/packages/cli/vitest.int.config.ts b/packages/cli/vitest.int.config.ts index 15ed90048..e20715cb2 100644 --- a/packages/cli/vitest.int.config.ts +++ b/packages/cli/vitest.int.config.ts @@ -1,4 +1,3 @@ -/// import { createIntTestConfig } from '../../testing/test-setup-config/src/index.js'; export default createIntTestConfig('cli'); diff --git a/packages/cli/vitest.unit.config.ts b/packages/cli/vitest.unit.config.ts index e5c8fe760..43e719425 100644 --- a/packages/cli/vitest.unit.config.ts +++ b/packages/cli/vitest.unit.config.ts @@ -1,4 +1,3 @@ -/// import { createUnitTestConfig } from '../../testing/test-setup-config/src/index.js'; export default createUnitTestConfig('cli'); diff --git a/packages/core/vitest.int.config.ts b/packages/core/vitest.int.config.ts index f16b4cd5b..b129829a4 100644 --- a/packages/core/vitest.int.config.ts +++ b/packages/core/vitest.int.config.ts @@ -1,4 +1,3 @@ -/// import { createIntTestConfig } from '../../testing/test-setup-config/src/index.js'; export default createIntTestConfig('core'); diff --git a/packages/core/vitest.unit.config.ts b/packages/core/vitest.unit.config.ts index 698818f7a..e418e452d 100644 --- a/packages/core/vitest.unit.config.ts +++ b/packages/core/vitest.unit.config.ts @@ -1,4 +1,3 @@ -/// import { createUnitTestConfig } from '../../testing/test-setup-config/src/index.js'; export default createUnitTestConfig('core'); diff --git a/packages/create-cli/vitest.unit.config.ts b/packages/create-cli/vitest.unit.config.ts index 30363d3cf..fae682415 100644 --- a/packages/create-cli/vitest.unit.config.ts +++ b/packages/create-cli/vitest.unit.config.ts @@ -1,4 +1,3 @@ -/// import { createUnitTestConfig } from '../../testing/test-setup-config/src/index.js'; export default createUnitTestConfig('create-cli'); diff --git a/packages/models/vitest.unit.config.ts b/packages/models/vitest.unit.config.ts index 5c07f1b7f..1923730a5 100644 --- a/packages/models/vitest.unit.config.ts +++ b/packages/models/vitest.unit.config.ts @@ -1,4 +1,3 @@ -/// import { createUnitTestConfig } from '../../testing/test-setup-config/src/index.js'; export default createUnitTestConfig('models'); diff --git a/packages/nx-plugin/vitest.int.config.ts b/packages/nx-plugin/vitest.int.config.ts index 24a8e1c1b..96d09e577 100644 --- a/packages/nx-plugin/vitest.int.config.ts +++ b/packages/nx-plugin/vitest.int.config.ts @@ -1,4 +1,3 @@ -/// import { createIntTestConfig } from '../../testing/test-setup-config/src/index.js'; export default createIntTestConfig('nx-plugin'); diff --git a/packages/nx-plugin/vitest.unit.config.ts b/packages/nx-plugin/vitest.unit.config.ts index 150c8ed55..25e47a0a0 100644 --- a/packages/nx-plugin/vitest.unit.config.ts +++ b/packages/nx-plugin/vitest.unit.config.ts @@ -1,4 +1,3 @@ -/// import { createUnitTestConfig } from '../../testing/test-setup-config/src/index.js'; export default createUnitTestConfig('nx-plugin'); diff --git a/packages/plugin-coverage/vitest.int.config.ts b/packages/plugin-coverage/vitest.int.config.ts index 5da4f09de..bbf96c4fd 100644 --- a/packages/plugin-coverage/vitest.int.config.ts +++ b/packages/plugin-coverage/vitest.int.config.ts @@ -1,4 +1,3 @@ -/// import { createIntTestConfig } from '../../testing/test-setup-config/src/index.js'; export default createIntTestConfig('plugin-coverage'); diff --git a/packages/plugin-coverage/vitest.unit.config.ts b/packages/plugin-coverage/vitest.unit.config.ts index a6dc83bc4..e6e8018e7 100644 --- a/packages/plugin-coverage/vitest.unit.config.ts +++ b/packages/plugin-coverage/vitest.unit.config.ts @@ -1,4 +1,3 @@ -/// import { createUnitTestConfig } from '../../testing/test-setup-config/src/index.js'; export default createUnitTestConfig('plugin-coverage'); diff --git a/packages/plugin-eslint/mocks/fixtures/nx-monorepo/package-lock.json b/packages/plugin-eslint/mocks/fixtures/nx-monorepo/package-lock.json deleted file mode 100644 index 22d47ba27..000000000 --- a/packages/plugin-eslint/mocks/fixtures/nx-monorepo/package-lock.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "mock-nx-workspace", - "lockfileVersion": 3, - "requires": true, - "packages": {} -} diff --git a/packages/plugin-eslint/vitest.int.config.ts b/packages/plugin-eslint/vitest.int.config.ts index 78b456133..e251e54f9 100644 --- a/packages/plugin-eslint/vitest.int.config.ts +++ b/packages/plugin-eslint/vitest.int.config.ts @@ -1,4 +1,3 @@ -/// import { createIntTestConfig } from '../../testing/test-setup-config/src/index.js'; export default createIntTestConfig('plugin-eslint'); diff --git a/packages/plugin-eslint/vitest.unit.config.ts b/packages/plugin-eslint/vitest.unit.config.ts index 6b6757207..ca304267e 100644 --- a/packages/plugin-eslint/vitest.unit.config.ts +++ b/packages/plugin-eslint/vitest.unit.config.ts @@ -1,4 +1,3 @@ -/// import { createUnitTestConfig } from '../../testing/test-setup-config/src/index.js'; export default createUnitTestConfig('plugin-eslint'); diff --git a/packages/plugin-js-packages/vitest.int.config.ts b/packages/plugin-js-packages/vitest.int.config.ts index 36b49dc30..7d6d9bf8c 100644 --- a/packages/plugin-js-packages/vitest.int.config.ts +++ b/packages/plugin-js-packages/vitest.int.config.ts @@ -1,4 +1,3 @@ -/// import { createIntTestConfig } from '../../testing/test-setup-config/src/index.js'; export default createIntTestConfig('plugin-js-packages'); diff --git a/packages/plugin-js-packages/vitest.unit.config.ts b/packages/plugin-js-packages/vitest.unit.config.ts index 5912ace39..855c3ace3 100644 --- a/packages/plugin-js-packages/vitest.unit.config.ts +++ b/packages/plugin-js-packages/vitest.unit.config.ts @@ -1,4 +1,3 @@ -/// import { createUnitTestConfig } from '../../testing/test-setup-config/src/index.js'; export default createUnitTestConfig('plugin-js-packages'); diff --git a/packages/plugin-jsdocs/vitest.int.config.ts b/packages/plugin-jsdocs/vitest.int.config.ts index 7e7422e36..f7fb998ba 100644 --- a/packages/plugin-jsdocs/vitest.int.config.ts +++ b/packages/plugin-jsdocs/vitest.int.config.ts @@ -1,4 +1,3 @@ -/// import { createIntTestConfig } from '../../testing/test-setup-config/src/index.js'; export default createIntTestConfig('plugin-jsdocs'); diff --git a/packages/plugin-jsdocs/vitest.unit.config.ts b/packages/plugin-jsdocs/vitest.unit.config.ts index f45cbef7a..88a3855cc 100644 --- a/packages/plugin-jsdocs/vitest.unit.config.ts +++ b/packages/plugin-jsdocs/vitest.unit.config.ts @@ -1,4 +1,3 @@ -/// import { createUnitTestConfig } from '../../testing/test-setup-config/src/index.js'; export default createUnitTestConfig('plugin-jsdocs'); diff --git a/packages/plugin-lighthouse/vitest.unit.config.ts b/packages/plugin-lighthouse/vitest.unit.config.ts index d83a973a6..16a9ae972 100644 --- a/packages/plugin-lighthouse/vitest.unit.config.ts +++ b/packages/plugin-lighthouse/vitest.unit.config.ts @@ -1,4 +1,3 @@ -/// import { createUnitTestConfig } from '../../testing/test-setup-config/src/index.js'; export default createUnitTestConfig('plugin-lighthouse'); diff --git a/packages/plugin-typescript/vitest.int.config.ts b/packages/plugin-typescript/vitest.int.config.ts index 21c2405ad..49425e520 100644 --- a/packages/plugin-typescript/vitest.int.config.ts +++ b/packages/plugin-typescript/vitest.int.config.ts @@ -1,4 +1,3 @@ -/// import { createIntTestConfig } from '../../testing/test-setup-config/src/index.js'; export default createIntTestConfig('plugin-typescript'); diff --git a/packages/plugin-typescript/vitest.unit.config.ts b/packages/plugin-typescript/vitest.unit.config.ts index cc4bab174..6fa6ed0ef 100644 --- a/packages/plugin-typescript/vitest.unit.config.ts +++ b/packages/plugin-typescript/vitest.unit.config.ts @@ -1,4 +1,3 @@ -/// import { createUnitTestConfig } from '../../testing/test-setup-config/src/index.js'; export default createUnitTestConfig('plugin-typescript'); diff --git a/packages/utils/vitest.int.config.ts b/packages/utils/vitest.int.config.ts index 2ec648817..2fd47c4e5 100644 --- a/packages/utils/vitest.int.config.ts +++ b/packages/utils/vitest.int.config.ts @@ -1,4 +1,3 @@ -/// import { createIntTestConfig } from '../../testing/test-setup-config/src/index.js'; export default createIntTestConfig('utils'); diff --git a/packages/utils/vitest.unit.config.ts b/packages/utils/vitest.unit.config.ts index f85142065..2c735217d 100644 --- a/packages/utils/vitest.unit.config.ts +++ b/packages/utils/vitest.unit.config.ts @@ -1,4 +1,3 @@ -/// import { createUnitTestConfig } from '../../testing/test-setup-config/src/index.js'; export default createUnitTestConfig('utils'); diff --git a/testing/test-nx-utils/vitest.unit.config.ts b/testing/test-nx-utils/vitest.unit.config.ts index cf971691f..c7bd7a43a 100644 --- a/testing/test-nx-utils/vitest.unit.config.ts +++ b/testing/test-nx-utils/vitest.unit.config.ts @@ -1,4 +1,3 @@ -/// import { createUnitTestConfig } from '../../testing/test-setup-config/src/index.js'; export default createUnitTestConfig('test-nx-utils'); diff --git a/testing/test-setup-config/src/lib/vitest-config-factory.ts b/testing/test-setup-config/src/lib/vitest-config-factory.ts index 9a277d650..5bf1ad41c 100644 --- a/testing/test-setup-config/src/lib/vitest-config-factory.ts +++ b/testing/test-setup-config/src/lib/vitest-config-factory.ts @@ -1,5 +1,5 @@ import type { CoverageOptions } from 'vitest'; -import { type UserConfig as ViteUserConfig, defineConfig } from 'vitest/config'; +import { type UserConfig as ViteUserConfig } from 'vitest/config'; import { getSetupFiles } from './vitest-setup-files.js'; import { tsconfigPathAliases } from './vitest-tsconfig-path-aliases.js'; @@ -48,7 +48,7 @@ export function createVitestConfig( ): ViteUserConfig { const coverage = buildCoverageConfig(projectKey, kind); - const config: ViteUserConfig = { + return { cacheDir: `../../node_modules/.vite/${projectKey}`, test: { reporters: ['basic'], @@ -70,6 +70,4 @@ export function createVitestConfig( : {}), }, }; - - return defineConfig(config); } 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 index 85a81d4c4..efd688d88 100644 --- 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 @@ -1,16 +1,7 @@ import { describe, expect, it, vi } from 'vitest'; -import { defineConfig } from 'vitest/config'; import type { E2ETestOptions, TestKind } from './vitest-config-factory.js'; import { createVitestConfig } from './vitest-config-factory.js'; -vi.mock('vitest/config', async importOriginal => { - const actual = await importOriginal(); - return { - ...actual, - defineConfig: vi.fn(config => config), - }; -}); - vi.mock('./vitest-tsconfig-path-aliases.js', () => ({ tsconfigPathAliases: vi .fn() @@ -50,7 +41,6 @@ describe('createVitestConfig', () => { typecheck: { include: ['**/*.type.test.ts'] }, }), }); - expect(defineConfig).toHaveBeenCalledWith(config); }); it('should include all required setup files for unit tests', () => { @@ -75,6 +65,9 @@ describe('createVitestConfig', () => { expect(setupFiles).toContain( '../../testing/test-setup/src/lib/portal-client.mock.ts', ); + expect(setupFiles).toContain( + '../../testing/test-setup/src/lib/logger.mock.ts', + ); expect(setupFiles).toContain( '../../testing/test-setup/src/lib/extend/ui-logger.matcher.ts', ); @@ -137,6 +130,9 @@ describe('createVitestConfig', () => { expect(setupFiles).toContain( '../../testing/test-setup/src/lib/console.mock.ts', ); + expect(setupFiles).toContain( + '../../testing/test-setup/src/lib/logger.mock.ts', + ); expect(setupFiles).not.toContain( '../../testing/test-setup/src/lib/fs.mock.ts', ); diff --git a/testing/test-setup-config/src/lib/vitest-setup-files.ts b/testing/test-setup-config/src/lib/vitest-setup-files.ts index 45b24cc36..2f67c4c6d 100644 --- a/testing/test-setup-config/src/lib/vitest-setup-files.ts +++ b/testing/test-setup-config/src/lib/vitest-setup-files.ts @@ -26,6 +26,7 @@ const UNIT_TEST_SETUP_FILES = [ '../../testing/test-setup/src/lib/cliui.mock.ts', '../../testing/test-setup/src/lib/git.mock.ts', '../../testing/test-setup/src/lib/portal-client.mock.ts', + '../../testing/test-setup/src/lib/logger.mock.ts', ...CUSTOM_MATCHERS, ] as const; @@ -40,6 +41,7 @@ const INT_TEST_SETUP_FILES = [ '../../testing/test-setup/src/lib/console.mock.ts', '../../testing/test-setup/src/lib/reset.mocks.ts', '../../testing/test-setup/src/lib/chrome-path.mock.ts', + '../../testing/test-setup/src/lib/logger.mock.ts', ...CUSTOM_MATCHERS, ] as const; diff --git a/testing/test-setup-config/src/lib/vitest-setup-files.unit.test.ts b/testing/test-setup-config/src/lib/vitest-setup-files.unit.test.ts index 3ed6c44f1..f585bc0b5 100644 --- a/testing/test-setup-config/src/lib/vitest-setup-files.unit.test.ts +++ b/testing/test-setup-config/src/lib/vitest-setup-files.unit.test.ts @@ -7,7 +7,7 @@ describe('vitest-setup-files', () => { it('should return all required setup files for unit tests', () => { const setupFiles = getSetupFiles('unit'); - expect(setupFiles).toHaveLength(10); + expect(setupFiles).toHaveLength(11); expect(setupFiles).toContain( '../../testing/test-setup/src/lib/console.mock.ts', ); @@ -26,6 +26,9 @@ describe('vitest-setup-files', () => { expect(setupFiles).toContain( '../../testing/test-setup/src/lib/portal-client.mock.ts', ); + expect(setupFiles).toContain( + '../../testing/test-setup/src/lib/logger.mock.ts', + ); expect(setupFiles).toContain( '../../testing/test-setup/src/lib/extend/ui-logger.matcher.ts', ); @@ -42,10 +45,10 @@ describe('vitest-setup-files', () => { }); describe('integration test setup files', () => { - it('should return exactly 7 setup files with essential mocks and custom matchers', () => { + it('should return exactly 8 setup files with essential mocks and custom matchers', () => { const setupFiles = getSetupFiles('int'); - expect(setupFiles).toHaveLength(7); + expect(setupFiles).toHaveLength(8); expect(setupFiles).toContain( '../../testing/test-setup/src/lib/console.mock.ts', ); @@ -55,6 +58,9 @@ describe('vitest-setup-files', () => { expect(setupFiles).toContain( '../../testing/test-setup/src/lib/chrome-path.mock.ts', ); + expect(setupFiles).toContain( + '../../testing/test-setup/src/lib/logger.mock.ts', + ); }); it('should include custom matchers for integration tests', () => { diff --git a/testing/test-setup-config/vitest.unit.config.ts b/testing/test-setup-config/vitest.unit.config.ts index 1c135686a..0f76d26b5 100644 --- a/testing/test-setup-config/vitest.unit.config.ts +++ b/testing/test-setup-config/vitest.unit.config.ts @@ -1,4 +1,3 @@ -/// import { createUnitTestConfig } from './src/index.js'; export default createUnitTestConfig('test-setup-config'); diff --git a/testing/test-setup/src/lib/logger.mock.ts b/testing/test-setup/src/lib/logger.mock.ts index 8e2e00ad7..6efe9f92f 100644 --- a/testing/test-setup/src/lib/logger.mock.ts +++ b/testing/test-setup/src/lib/logger.mock.ts @@ -1,9 +1,11 @@ import { type MockInstance, afterAll, beforeAll, vi } from 'vitest'; -import { logger } from '@code-pushup/utils'; const loggerSpies: MockInstance[] = []; -beforeAll(() => { +beforeAll(async () => { + const { logger }: typeof import('@code-pushup/utils') = + await vi.importActual('@code-pushup/utils'); + // TODO: use vi.mockObject after Vitest update: https://vitest.dev/api/vi.html#vi-mockobject-3-2-0 if (process.env['NX_VERBOSE_LOGGING'] === 'true') { // only track calls, but preserve original implementation so logs are printed diff --git a/testing/test-setup/vitest.unit.config.ts b/testing/test-setup/vitest.unit.config.ts index c581b0f4d..3ed3fa745 100644 --- a/testing/test-setup/vitest.unit.config.ts +++ b/testing/test-setup/vitest.unit.config.ts @@ -1,4 +1,3 @@ -/// import { createUnitTestConfig } from '../../testing/test-setup-config/src/index.js'; export default createUnitTestConfig('test-setup'); diff --git a/testing/test-utils/vitest.unit.config.ts b/testing/test-utils/vitest.unit.config.ts index c9e679060..61d9b2a20 100644 --- a/testing/test-utils/vitest.unit.config.ts +++ b/testing/test-utils/vitest.unit.config.ts @@ -1,4 +1,3 @@ -/// import { createUnitTestConfig } from '../../testing/test-setup-config/src/index.js'; export default createUnitTestConfig('test-utils'); diff --git a/tools/eslint-formatter-multi/vitest.unit.config.ts b/tools/eslint-formatter-multi/vitest.unit.config.ts index 3974e3457..3ef0b549a 100644 --- a/tools/eslint-formatter-multi/vitest.unit.config.ts +++ b/tools/eslint-formatter-multi/vitest.unit.config.ts @@ -1,4 +1,3 @@ -/// import { createUnitTestConfig } from '../../testing/test-setup-config/src/index.js'; export default createUnitTestConfig('eslint-formatter-multi');