Skip to content

Conversation

@poltorak
Copy link
Collaborator

@poltorak poltorak commented Sep 5, 2025

Centralize and Standardize Vitest Configurations

What Changed

  • New centralized configuration system: Created testing/test-setup-config package with a factory-based approach for generating Vitest configurations
  • Setup presets: Introduced modular setup file presets that can be composed for different test types (unit, integration, e2e)
  • Simplified configuration files: Refactored 6 existing Vitest config files to use the new centralized system, reducing them from verbose configurations to simple function calls

Benefits

  • Eliminates duplication: Removes repetitive boilerplate across multiple vitest config files
  • Ensures consistency: Standardizes test configurations, coverage settings, and setup files across all packages
  • Improves maintainability: Changes to test configuration defaults now only need to be made in one place
  • Better developer experience: New configs are more readable and easier to customize with sensible defaults and preset combinations

The new system provides createUnitConfig, createIntConfig, and createE2eConfig factory functions that automatically handle common configuration patterns while still allowing customization through override parameters.

Relates to first part of #1065

@github-actions github-actions bot added 📖 Project documentation improvements or additions to the project documentation 🔬 testing writing tests 🛠️ tooling labels Sep 5, 2025
@nx-cloud
Copy link

nx-cloud bot commented Sep 5, 2025

View your CI Pipeline Execution ↗ for commit 38b7a6e

Command Status Duration Result
nx code-pushup --nx-bail -- compare ✅ Succeeded 56s View ↗
nx code-pushup --nx-bail -- ✅ Succeeded 1m 7s View ↗
nx code-pushup --nx-bail -- print-config --outp... ✅ Succeeded 4m 3s View ↗

☁️ Nx Cloud last updated this comment at 2025-10-29 15:53:39 UTC

@pkg-pr-new
Copy link

pkg-pr-new bot commented Sep 5, 2025

Open in StackBlitz

@code-pushup/ci

npm i https://pkg.pr.new/code-pushup/cli/@code-pushup/ci@1107

@code-pushup/cli

npm i https://pkg.pr.new/code-pushup/cli/@code-pushup/cli@1107

@code-pushup/create-cli

npm i https://pkg.pr.new/code-pushup/cli/@code-pushup/create-cli@1107

@code-pushup/core

npm i https://pkg.pr.new/code-pushup/cli/@code-pushup/core@1107

@code-pushup/models

npm i https://pkg.pr.new/code-pushup/cli/@code-pushup/models@1107

@code-pushup/nx-plugin

npm i https://pkg.pr.new/code-pushup/cli/@code-pushup/nx-plugin@1107

@code-pushup/coverage-plugin

npm i https://pkg.pr.new/code-pushup/cli/@code-pushup/coverage-plugin@1107

@code-pushup/eslint-plugin

npm i https://pkg.pr.new/code-pushup/cli/@code-pushup/eslint-plugin@1107

@code-pushup/js-packages-plugin

npm i https://pkg.pr.new/code-pushup/cli/@code-pushup/js-packages-plugin@1107

@code-pushup/jsdocs-plugin

npm i https://pkg.pr.new/code-pushup/cli/@code-pushup/jsdocs-plugin@1107

@code-pushup/lighthouse-plugin

npm i https://pkg.pr.new/code-pushup/cli/@code-pushup/lighthouse-plugin@1107

@code-pushup/typescript-plugin

npm i https://pkg.pr.new/code-pushup/cli/@code-pushup/typescript-plugin@1107

@code-pushup/utils

npm i https://pkg.pr.new/code-pushup/cli/@code-pushup/utils@1107

@code-pushup/models-transformers

npm i https://pkg.pr.new/code-pushup/cli/@code-pushup/models-transformers@1107

commit: 38b7a6e

@github-actions
Copy link
Contributor

github-actions bot commented Sep 5, 2025

Code PushUp

🤨 Code PushUp report has both improvements and regressions – compared current commit cc49e4f with previous commit 471c847.

🕵️ See full comparison in Code PushUp portal 🔍

🏷️ Categories

🏷️ Category ⭐ Previous score ⭐ Current score 🔄 Score change
Performance 🔴 36 🔴 28 ↓ −8.7
Code coverage 🟢 90 🟢 90 ↑ +0.1
Documentation 🔴 36 🔴 36 ↓ −0.1
Security 🟡 56 🟡 56
Updates 🟡 73 🟡 73
Accessibility 🟢 92 🟢 92
Best Practices 🟢 97 🟢 97
SEO 🟡 61 🟡 61
Type Safety 🟢 100 🟢 100
Bug prevention 🟢 100 🟢 100
Miscellaneous 🟢 100 🟢 100
Code style 🟢 100 🟢 100
👍 1 group improved, 👎 2 groups regressed, 👍 2 audits improved, 👎 5 audits regressed, 14 audits changed without impacting score

🗃️ Groups

🔌 Plugin 🗃️ Group ⭐ Previous score ⭐ Current score 🔄 Score change
Lighthouse Performance 🔴 36 🔴 28 ↓ −8.7
Code coverage Code coverage metrics 🟢 90 🟢 90 ↑ +0.1
JSDoc coverage Documentation coverage 🔴 36 🔴 36 ↓ −0.1

18 other groups are unchanged.

🛡️ Audits

🔌 Plugin 🛡️ Audit 📏 Previous value 📏 Current value 🔄 Value change
Lighthouse First Contentful Paint 🟥 3.2 s 🟥 4.4 s ↑ +39.7 %
Lighthouse Speed Index 🟥 7.5 s 🟥 12.2 s ↑ +61.4 %
Lighthouse Total Blocking Time 🟥 1,510 ms 🟥 3,190 ms ↑ +110.9 %
Lighthouse Time to Interactive 🟥 13.9 s 🟥 14.8 s ↑ +6.6 %
Code coverage Line coverage 🟨 86.8 % 🟨 87.2 % ↑ +0.5 %
Code coverage Branch coverage 🟨 85.9 % 🟨 85.8 % ↓ −0.2 %
Code coverage Function coverage 🟩 93.1 % 🟩 93.1 % ↑ +0.1 %
Lighthouse Avoids enormous network payloads 🟩 Total size was 2,028 KiB 🟩 Total size was 2,041 KiB ↑ +0.6 %
Lighthouse Minimizes main-thread work 🟥 10.8 s 🟥 16.9 s ↑ +56.5 %
Lighthouse JavaScript execution time 🟥 4.0 s 🟥 6.7 s ↑ +65.2 %
Lighthouse Initial server response time was short 🟥 Root document took 690 ms 🟥 Root document took 3,130 ms ↑ +351.3 %
Lighthouse Largest Contentful Paint 🟥 11.6 s 🟥 13.3 s ↑ +14.8 %
Lighthouse Metrics 🟩 100% 🟩 100% ↑ +6.6 %
Lighthouse Max Potential First Input Delay 🟥 1,550 ms 🟥 2,240 ms ↑ +44.7 %
Lighthouse Server Backend Latencies 🟩 440 ms 🟩 1,060 ms ↑ +142.4 %
Lighthouse Reduce unused CSS 🟥 Potential savings of 103 KiB 🟥 Potential savings of 103 KiB ↓ −28.6 %
Lighthouse Network Round Trip Times 🟩 50 ms 🟩 160 ms ↑ +207 %
Lighthouse Reduce unused JavaScript 🟥 Potential savings of 180 KiB 🟥 Potential savings of 180 KiB ↓ −5.3 %
Lighthouse Uses efficient cache policy on static assets 🟨 30 resources found 🟨 30 resources found ↑ +0.1 %
Lighthouse Remove duplicate modules in JavaScript bundles 🟥 Potential savings of 103 KiB 🟥 Potential savings of 94 KiB ↓ −4.3 %
Lighthouse Cumulative Layout Shift 🟩 0.009 🟩 0 ↓ −100 %

589 other audits are unchanged.

Copy link
Collaborator

@BioPhoton BioPhoton left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for cleaning up our test config mess :)

I left some comments.

  • please add a proper PR description that contains the scope and planned changes
  • move the config code under a Nx project in testing/vitest-setup. I updated the issue accordingly.

@poltorak poltorak marked this pull request as ready for review September 10, 2025 06:45
Copy link
Collaborator

@BioPhoton BioPhoton left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the changes! Nice testing tool! There is Readme, and tests, lovely.
I left some first comments and will wait for one feedback on the override logic for further comments.

@poltorak poltorak force-pushed the refactor/vitest-configurations branch from 9433ca5 to 7008a48 Compare October 6, 2025 15:17
Copy link
Collaborator

@matejchalk matejchalk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi, thanks for your contribution 🙂 I think it looks promising, but ...

I don't think this PR goes far enough towards unification. Ideally, only 2 parameters1 would determine the entire Vitest config:

  • Nx project name (e.g., cli, plugin-eslint, ci-e2e) 2
  • test kind ('unit' | 'int' | 'e2e')

My main criticism of this PR is that it doesn't commit to unification over flexibility. I believe we need less flexibility than it may appear, so I would go all in on unification. If we find some exception, we should question whether that exception is justified and get rid of it if possible.

In the current implementation, some things are automatically inferred, but there are many (IMHO unnecessary) optional arguments, and anything can be overridden by providing a Vitest config object. To me, this is a leaky abstraction.

I've suggested specifics on what can be unified in my comments. Let me know if I can clarify anything 🙂 I realize you couldn't reasonably have known many of these things, so no worries.

Footnotes

  1. By "parameter", I don't strictly mean a function parameter. Your approach of exporting different factory functions for each test kind also works, for example.

  2. It may turn out we also need something like projectRoot (e.g., 'packages/core'). In that case, we could either derive it via @nx/devkit's createProjectGraphAsync, provide the project folder instead and derive its name using path.basename, or add another parameter.


## Shared config

[README](./src/lib/config/README.md) how to use vitest config factory.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The link is broken, and this doesn't really work as an English sentence.

Suggested change
[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.


export function tsconfigPathAliases(): AliasOptions {
const result = loadConfig('tsconfig.base.json');
export function tsconfigPathAliases(projectRootUrl?: URL): AliasOptions {
Copy link
Collaborator

@matejchalk matejchalk Oct 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function is now duplicated in testing/test-setup-config/src/lib/vitest-tsconfig-path-aliases.ts. I'm guessing we don't need it here in tools anymore?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We actually need both for some time. The one from tool is used in other not migrated config files.
Once we migrate all configs then we can remove it.

/.sass-cache
/connect.lock
/coverage
**/.coverage/**
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Which tool uses .coverage folders? 🤨

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one could be used to exclude colocated coverage folders of each project.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one could be used to exclude colocated coverage folders of each project.

I've no clue what you mean by that 😅

There's only 1 coverage folder with all projects at the workspace root, and it's called coverage, not .coverage. So what does ignoring **/.coverage/** achieve?

@poltorak poltorak force-pushed the refactor/vitest-configurations branch from 7008a48 to a11d027 Compare October 29, 2025 09:53
@poltorak poltorak force-pushed the refactor/vitest-configurations branch from a11d027 to 38b7a6e Compare October 29, 2025 15:36
Copy link
Collaborator

@matejchalk matejchalk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is headed in the right direction. I also reviewed the internal implementation this time, where I have some minor suggestions.

However, I think the pattern is now fairly clear, so please go ahead and apply it to other Vitest configs. 🙏

}

function getGlobalSetup(kind: TestKind): string[] | undefined {
return kind === 'e2e' ? undefined : ['../../global-setup.ts'];
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The global-setup.ts should be included for all tests, including E2E.

testTimeout?: number;
};

export type VitestConfig = ViteUserConfig & { test?: InlineConfig };
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be better to use the original UserConfig type from vitest/config, instead of defining a custom type.

Comment on lines +53 to +62
*
* // 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'],
* },
* };
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd remove this example, because I wouldn't like to encourage this type of usage.

Comment on lines +36 to +49
### 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'],
},
};
```
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same reasoning as previous comment:

Suggested change
### 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'],
},
};
```

Comment on lines +7 to +23
**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:**
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's more semantic to use headings in Markdown - e.g.: #### Unit tests instead of **Unit tests:**.

Comment on lines +40 to +47
const defaultExclude = ['mocks/**', '**/types.ts', 'perf/**'];
const reportsDirectory = `../../coverage/${projectKey}/${kind}-tests`;

return {
reporter: ['text', 'lcov'],
reportsDirectory,
exclude: defaultExclude,
};
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The name defaultExclude implies there'll be some additional overrides, which isn't the case.

Suggested change
const defaultExclude = ['mocks/**', '**/types.ts', 'perf/**'];
const reportsDirectory = `../../coverage/${projectKey}/${kind}-tests`;
return {
reporter: ['text', 'lcov'],
reportsDirectory,
exclude: defaultExclude,
};
const exclude = ['mocks/**', '**/types.ts', 'perf/**'];
const reportsDirectory = `../../coverage/${projectKey}/${kind}-tests`;
return {
reporter: ['text', 'lcov'],
reportsDirectory,
exclude,
};

Comment on lines +21 to +23
beforeEach(() => {
vi.clearAllMocks();
});
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Including reset.mocks.ts already takes care of this:

Comment on lines +29 to +58
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'] },
}),
}),
);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No properties other than cacheDir and test should be created, so expect.objectContaining shouldn't be needed.

Comment on lines +101 to +117
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/**');
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are a lot of unnecessary config as any castings in this test suite. To access properties under config.test, use ! 1 or ?..

Footnotes

  1. Non-null assertion with ! isn't safe to use in general, but in tests it doesn't really matter - a runtime error is much the same as a failing test.

Comment on lines +3 to +49
/**
* Setup files for unit tests.
*
* These paths are relative to the config file location (typically `packages/<project>/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/<project>/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/<project-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;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For easier maintenance, I would extract all matchers to a shared variable that's spread into each array of setup files.

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;

const UNIT_TEST_SETUP_FILES = [
  // ... mocks ...
  ...CUSTOM_MATCHERS,
] as const;

const INT_TEST_SETUP_FILES = [
  // ... mocks ...
  ...CUSTOM_MATCHERS,
] as const;


const E2E_TEST_SETUP_FILES = [
  // ... mocks ...
  ...CUSTOM_MATCHERS,
] as const;

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

📖 Project documentation improvements or additions to the project documentation 🔬 testing writing tests 🛠️ tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants