diff --git a/.gitignore b/.gitignore index 5e706fa10..e6c77e8f8 100644 --- a/.gitignore +++ b/.gitignore @@ -32,6 +32,7 @@ node_modules /.sass-cache /connect.lock /coverage +**/.coverage/** /examples/react-todos-app/coverage /libpeerconnection.log npm-debug.log diff --git a/e2e/ci-e2e/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 90df62fed..ff9d2856e 100644 --- a/e2e/ci-e2e/vitest.e2e.config.ts +++ b/e2e/ci-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/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 createE2ETestConfig('ci-e2e', { + testTimeout: 60_000, }); diff --git a/e2e/plugin-typescript-e2e/vitest.e2e.config.ts b/e2e/plugin-typescript-e2e/vitest.e2e.config.ts index c34d55a36..41a602360 100644 --- a/e2e/plugin-typescript-e2e/vitest.e2e.config.ts +++ b/e2e/plugin-typescript-e2e/vitest.e2e.config.ts @@ -1,26 +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-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 createE2ETestConfig('plugin-typescript-e2e', { + testTimeout: 20_000, }); 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/packages/core/vitest.int.config.ts b/packages/core/vitest.int.config.ts index 7ff35029f..f16b4cd5b 100644 --- a/packages/core/vitest.int.config.ts +++ b/packages/core/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/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 createIntTestConfig('core'); diff --git a/packages/core/vitest.unit.config.ts b/packages/core/vitest.unit.config.ts index c46850c41..698818f7a 100644 --- a/packages/core/vitest.unit.config.ts +++ b/packages/core/vitest.unit.config.ts @@ -1,36 +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/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 createUnitTestConfig('core'); diff --git a/packages/utils/vitest.int.config.ts b/packages/utils/vitest.int.config.ts index b72908490..2ec648817 100644 --- a/packages/utils/vitest.int.config.ts +++ b/packages/utils/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/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 createIntTestConfig('utils'); diff --git a/packages/utils/vitest.unit.config.ts b/packages/utils/vitest.unit.config.ts index f55eb2326..f85142065 100644 --- a/packages/utils/vitest.unit.config.ts +++ b/packages/utils/vitest.unit.config.ts @@ -1,38 +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/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 createUnitTestConfig('utils'); diff --git a/testing/test-setup-config/README.md b/testing/test-setup-config/README.md new file mode 100644 index 000000000..a7750a4cf --- /dev/null +++ b/testing/test-setup-config/README.md @@ -0,0 +1,49 @@ +## Vitest Config Factory + +Standardized Vitest configuration for the Code PushUp monorepo. + +### Usage + +**Unit tests:** + +```typescript +import { createUnitTestConfig } from '@code-pushup/test-setup-config'; + +export default createUnitTestConfig('my-package'); +``` + +**Integration tests:** + +```typescript +import { createIntTestConfig } from '@code-pushup/test-setup-config'; + +export default createIntTestConfig('my-package'); +``` + +**E2E tests:** + +```typescript +import { createE2ETestConfig } from '@code-pushup/test-setup-config'; + +export default createE2ETestConfig('my-e2e'); + +// With options: +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/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..68d9a93c6 --- /dev/null +++ b/testing/test-setup-config/src/index.ts @@ -0,0 +1,9 @@ +export { + 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 new file mode 100644 index 000000000..21235ebef --- /dev/null +++ b/testing/test-setup-config/src/lib/vitest-config-factory.ts @@ -0,0 +1,81 @@ +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'; + +export type TestKind = 'unit' | 'int' | 'e2e'; + +export type E2ETestOptions = { + testTimeout?: number; +}; + +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 getGlobalSetup(kind: TestKind): string[] | undefined { + return kind === 'e2e' ? undefined : ['../../global-setup.ts']; +} + +function buildCoverageConfig( + projectKey: string, + kind: TestKind, +): CoverageOptions | undefined { + if (kind === 'e2e') { + return undefined; + } + + const defaultExclude = ['mocks/**', '**/types.ts', 'perf/**']; + const reportsDirectory = `../../coverage/${projectKey}/${kind}-tests`; + + return { + reporter: ['text', 'lcov'], + reportsDirectory, + exclude: defaultExclude, + }; +} + +export function createVitestConfig( + projectKey: string, + kind: TestKind, + options?: E2ETestOptions, +): ViteUserConfig { + const coverage = buildCoverageConfig(projectKey, kind); + + const config: VitestConfig = { + cacheDir: `../../node_modules/.vite/${projectKey}`, + test: { + reporters: ['basic'], + globals: true, + cache: { + dir: '../../node_modules/.vitest', + }, + alias: tsconfigPathAliases(), + pool: 'threads', + poolOptions: { threads: { singleThread: true } }, + environment: 'node', + include: getIncludePatterns(kind), + globalSetup: getGlobalSetup(kind), + setupFiles: [...getSetupFiles(kind)], + ...(options?.testTimeout ? { testTimeout: options.testTimeout } : {}), + ...(coverage ? { coverage } : {}), + ...(kind === 'unit' + ? { typecheck: { include: ['**/*.type.test.ts'] } } + : {}), + }, + }; + + 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 new file mode 100644 index 000000000..a6b80933d --- /dev/null +++ b/testing/test-setup-config/src/lib/vitest-config-factory.unit.test.ts @@ -0,0 +1,356 @@ +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('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() + .mockReturnValue([{ find: '@test/alias', replacement: '/mock/path' }]), +})); + +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(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; + 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 include type test pattern in unit tests', () => { + const config = createVitestConfig('test-package', 'unit'); + + expect((config as any).test.include).toContain( + 'src/**/*.type.test.{js,mjs,cjs,ts,mts,cts,jsx,tsx}', + ); + }); + + it('should enable typecheck for unit tests', () => { + const config = createVitestConfig('test-package', 'unit'); + + expect((config as any).test.typecheck).toEqual({ + include: ['**/*.type.test.ts'], + }); + }); + + it('should always include perf/** in coverage exclusions', () => { + const config = createVitestConfig('test-package', 'unit'); + + expect((config as any).test.coverage.exclude).toContain('perf/**'); + }); + }); + + 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({ + 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; + // 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', + ); + 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 not enable typecheck for integration tests', () => { + const config = createVitestConfig('test-package', 'int'); + + expect((config as any).test.typecheck).toBeUndefined(); + }); + }); + + 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({ + 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 include minimal setup files for e2e tests', () => { + const config = createVitestConfig('test-package', 'e2e'); + + const setupFiles = (config as any).test.setupFiles; + // Should only include reset mocks + expect(setupFiles).toContain( + '../../testing/test-setup/src/lib/reset.mocks.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 support custom testTimeout option', () => { + const options: E2ETestOptions = { testTimeout: 60_000 }; + const config = createVitestConfig('test-package', 'e2e', options); + + expect((config as any).test.testTimeout).toBe(60_000); + }); + + it('should support multiple options together', () => { + const options: E2ETestOptions = { + testTimeout: 30_000, + }; + const config = createVitestConfig('test-package', 'e2e', options); + + expect((config as any).test.testTimeout).toBe(30_000); + expect((config as any).test.coverage).toBeUndefined(); + }); + }); + + describe('cacheDir naming', () => { + it('should use projectKey for cacheDir', () => { + const config = createVitestConfig('my-custom-name', 'unit'); + + expect(config.cacheDir).toBe('../../node_modules/.vite/my-custom-name'); + }); + + it('should use projectKey for coverage directory', () => { + const config = createVitestConfig('my-package', 'unit'); + + expect((config as any).test.coverage.reportsDirectory).toBe( + '../../coverage/my-package/unit-tests', + ); + }); + }); + + describe('test kind variations', () => { + it('should handle all test kinds correctly', () => { + const testKinds: TestKind[] = ['unit', 'int', 'e2e']; + + testKinds.forEach(kind => { + const config = createVitestConfig('test-package', kind); + + 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}'], + }; + + expect((config as any).test.include).toStrictEqual( + expectedIncludes[kind], + ); + + const expectedGlobalSetup = { + unit: ['../../global-setup.ts'], + int: ['../../global-setup.ts'], + e2e: undefined, + }; + + expect((config as any).test.globalSetup).toStrictEqual( + expectedGlobalSetup[kind], + ); + }); + }); + }); + + describe('coverage configuration', () => { + 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']); + }); + + it('should enable coverage for integration tests by default', () => { + const config = createVitestConfig('test-package', 'int'); + + expect((config as any).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(); + }); + + it('should always exclude mocks, types.ts, and perf folders', () => { + const config = createVitestConfig('test-package', 'unit'); + + expect((config as any).test.coverage.exclude).toEqual([ + 'mocks/**', + '**/types.ts', + 'perf/**', + ]); + }); + }); + + describe('relative paths', () => { + it('should use relative paths for all file references', () => { + const config = createVitestConfig('test-package', 'unit'); + + // Setup files should be relative + const setupFiles = (config as any).test.setupFiles; + expect(setupFiles[0]).toMatch(/^\.\.\/\.\.\//); + + // GlobalSetup should be relative + expect((config as any).test.globalSetup[0]).toBe('../../global-setup.ts'); + + // Cache dirs should be relative + expect(config.cacheDir).toMatch(/^\.\.\/\.\.\//); + expect((config as any).test.cache.dir).toMatch(/^\.\.\/\.\.\//); + + // Coverage directory should be relative + expect((config as any).test.coverage.reportsDirectory).toMatch( + /^\.\.\/\.\.\//, + ); + }); + }); + + describe('edge cases', () => { + it('should handle empty projectKey gracefully', () => { + const config = createVitestConfig('', 'unit'); + + expect(config.cacheDir).toBe('../../node_modules/.vite/'); + expect((config as any).test.coverage.reportsDirectory).toBe( + '../../coverage//unit-tests', + ); + }); + + it('should handle projectKey with special characters', () => { + const config = createVitestConfig('my-special_package.v2', 'unit'); + + expect(config.cacheDir).toBe( + '../../node_modules/.vite/my-special_package.v2', + ); + }); + + 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(); + }); + }); +}); 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 new file mode 100644 index 000000000..b3502ef86 --- /dev/null +++ b/testing/test-setup-config/src/lib/vitest-setup-presets.ts @@ -0,0 +1,70 @@ +import type { UserConfig as ViteUserConfig } from 'vitest/config'; +import { + type E2ETestOptions, + createVitestConfig, +} from './vitest-config-factory.js'; + +/** + * 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'); +} + +/** + * 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'); +} + +/** + * 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, + * }); + * + * // 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, + 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 new file mode 100644 index 000000000..e94b7e3e9 --- /dev/null +++ b/testing/test-setup-config/src/lib/vitest-setup-presets.unit.test.ts @@ -0,0 +1,154 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest'; +import * as configFactory from './vitest-config-factory.js'; +import { + createE2ETestConfig, + createIntTestConfig, + createUnitTestConfig, +} from './vitest-setup-presets.js'; + +vi.mock('./vitest-config-factory.js', () => ({ + createVitestConfig: vi.fn().mockReturnValue('mocked-config'), +})); + +const MOCK_PROJECT_KEY = 'test-package'; + +describe('vitest-setup-presets', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + describe('createUnitTestConfig', () => { + it('should call createVitestConfig with unit kind', () => { + const result = createUnitTestConfig(MOCK_PROJECT_KEY); + + expect(configFactory.createVitestConfig).toHaveBeenCalledWith( + MOCK_PROJECT_KEY, + 'unit', + ); + expect(result).toBe('mocked-config'); + }); + + it('should handle different project names', () => { + createUnitTestConfig('my-custom-package'); + + expect(configFactory.createVitestConfig).toHaveBeenCalledWith( + 'my-custom-package', + 'unit', + ); + }); + + it('should handle empty projectKey', () => { + createUnitTestConfig(''); + + expect(configFactory.createVitestConfig).toHaveBeenCalledWith('', 'unit'); + }); + }); + + describe('createIntTestConfig', () => { + it('should call createVitestConfig with int kind', () => { + const result = createIntTestConfig(MOCK_PROJECT_KEY); + + expect(configFactory.createVitestConfig).toHaveBeenCalledWith( + MOCK_PROJECT_KEY, + 'int', + ); + expect(result).toBe('mocked-config'); + }); + + it('should handle different project names', () => { + createIntTestConfig('integration-package'); + + expect(configFactory.createVitestConfig).toHaveBeenCalledWith( + 'integration-package', + 'int', + ); + }); + }); + + describe('createE2ETestConfig', () => { + it('should call createVitestConfig with e2e kind and no options', () => { + const result = createE2ETestConfig(MOCK_PROJECT_KEY); + + expect(configFactory.createVitestConfig).toHaveBeenCalledWith( + MOCK_PROJECT_KEY, + 'e2e', + undefined, + ); + expect(result).toBe('mocked-config'); + }); + + it('should pass options to createVitestConfig', () => { + const options = { + testTimeout: 60_000, + }; + + createE2ETestConfig(MOCK_PROJECT_KEY, options); + + expect(configFactory.createVitestConfig).toHaveBeenCalledWith( + MOCK_PROJECT_KEY, + 'e2e', + options, + ); + }); + + it('should handle testTimeout option', () => { + createE2ETestConfig(MOCK_PROJECT_KEY, { testTimeout: 30_000 }); + + expect(configFactory.createVitestConfig).toHaveBeenCalledWith( + MOCK_PROJECT_KEY, + 'e2e', + { testTimeout: 30_000 }, + ); + }); + }); + + describe('function naming', () => { + it('should use clear descriptive names', () => { + expect(createUnitTestConfig).toBeDefined(); + expect(createIntTestConfig).toBeDefined(); + expect(createE2ETestConfig).toBeDefined(); + }); + }); + + 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, + ); + }); + + 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 new file mode 100644 index 000000000..f1a9cc0c3 --- /dev/null +++ b/testing/test-setup-config/src/lib/vitest-tsconfig-path-aliases.ts @@ -0,0 +1,29 @@ +import path from 'node:path'; +import { loadConfig } from 'tsconfig-paths'; +import type { Alias, AliasOptions } from 'vite'; + +/** + * 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, + // Make paths relative to workspace root (../../ from config file) + replacement: path.resolve(process.cwd(), relativePath), + }), + ); +} 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..1c135686a --- /dev/null +++ b/testing/test-setup-config/vitest.unit.config.ts @@ -0,0 +1,4 @@ +/// +import { createUnitTestConfig } from './src/index.js'; + +export default createUnitTestConfig('test-setup-config'); diff --git a/testing/test-setup/README.md b/testing/test-setup/README.md index db0ce1eba..3e9c2c236 100644 --- a/testing/test-setup/README.md +++ b/testing/test-setup/README.md @@ -4,6 +4,10 @@ This library contains test setup. More on this subject as well as all the testing strategy principles can be found on the GitHub [wiki](https://github.com/code-pushup/cli/wiki/Testing-Strategy#mocking). +## Shared config + +[README](./src/lib/config/README.md) how to use vitest config factory. + ## Mock setup In this library you can find all files that can be used in `setupFiles` property of `vitest.config.(unit|int|e2e).ts` files. Currently include: diff --git a/tools/vitest-tsconfig-path-aliases.ts b/tools/vitest-tsconfig-path-aliases.ts index ac8be04df..aec88e199 100644 --- a/tools/vitest-tsconfig-path-aliases.ts +++ b/tools/vitest-tsconfig-path-aliases.ts @@ -1,8 +1,13 @@ +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; import { loadConfig } from 'tsconfig-paths'; import type { Alias, AliasOptions } from 'vite'; -export function tsconfigPathAliases(): AliasOptions { - const result = loadConfig('tsconfig.base.json'); +export function tsconfigPathAliases(projectRootUrl?: URL): AliasOptions { + const tsconfigPath = projectRootUrl + ? path.resolve(fileURLToPath(projectRootUrl), 'tsconfig.base.json') + : 'tsconfig.base.json'; + const result = loadConfig(tsconfigPath); if (result.resultType === 'failed') { throw new Error( `Failed to load path aliases from tsconfig for Vitest: ${result.message}`, @@ -14,7 +19,9 @@ export function tsconfigPathAliases(): AliasOptions { .map( ([importPath, relativePath]): Alias => ({ find: importPath, - replacement: new URL(`../${relativePath}`, import.meta.url).pathname, + replacement: projectRootUrl + ? path.resolve(fileURLToPath(projectRootUrl), relativePath) + : new URL(`../${relativePath}`, import.meta.url).pathname, }), ); } diff --git a/tsconfig.base.json b/tsconfig.base.json index 7f3b4d21f..4bca1c69f 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -39,6 +39,9 @@ "@code-pushup/nx-plugin": ["packages/nx-plugin/src/index.ts"], "@code-pushup/test-nx-utils": ["testing/test-nx-utils/src/index.ts"], "@code-pushup/test-setup": ["testing/test-setup/src/index.ts"], + "@code-pushup/test-setup-config": [ + "testing/test-setup-config/src/index.ts" + ], "@code-pushup/test-utils": ["testing/test-utils/src/index.ts"], "@code-pushup/typescript-plugin": [ "packages/plugin-typescript/src/index.ts"