diff --git a/docs/config/index.md b/docs/config/index.md index a868895c87a7..e026543306d7 100644 --- a/docs/config/index.md +++ b/docs/config/index.md @@ -679,14 +679,12 @@ Show heap usage after each test. Useful for debugging memory leaks. - **Type**: `boolean | { include?, exclude? }` -Configure if CSS should be processed. When excluded, CSS files will be replaced with empty strings to bypass the subsequent processing. - -By default, processes only CSS Modules, because it affects runtime. JSDOM and Happy DOM don't fully support injecting CSS, so disabling this setting might help with performance. +Configure if CSS should be processed. When excluded, CSS files will be replaced with empty strings to bypass the subsequent processing. CSS Modules will return a proxy to not affect runtime. #### css.include - **Type**: `RegExp | RegExp[]` -- **Default**: `[/\.module\./]` +- **Default**: `[]` RegExp pattern for files that should return actual CSS and will be processed by Vite pipeline. @@ -697,6 +695,33 @@ RegExp pattern for files that should return actual CSS and will be processed by RegExp pattern for files that will return an empty CSS file. +#### css.modules + +- **Type**: `{ scopeClassNames? }` +- **Default**: `{}` + +#### css.modules.scopeClassNames + +- **Type**: `boolean` +- **Default**: false + +If you decide to process CSS files, you can configure if class names inside CSS modules should be scoped. By default, Vitest exports a proxy, bypassing CSS Modules processing. + +You might want to enable this, if your CSS classes are conflicting with each other, when CSS is inlined. For example, when you are accessing computed styles: + +```tsx +// global.module.css +// .error { width: 600px } + +// element.module.css +// .error { width: 100px } + +// test +const styles = window.getComputedStyles(
) +// it's possible to have two different classes with conflicting styles +expect(styles).toMatchObject({ with: '100px' }) +``` + ### maxConcurrency - **Type**: `number` diff --git a/packages/vitest/src/node/config.ts b/packages/vitest/src/node/config.ts index d68bec67f1f4..9cbfb18fae0a 100644 --- a/packages/vitest/src/node/config.ts +++ b/packages/vitest/src/node/config.ts @@ -183,9 +183,8 @@ export function resolveConfig( resolved.css ??= {} if (typeof resolved.css === 'object') { - resolved.css.include ??= [/\.module\./] resolved.css.modules ??= {} - resolved.css.modules.mangleClassName ??= false + resolved.css.modules.scopeClassNames ??= false } resolved.cache ??= { dir: '' } diff --git a/packages/vitest/src/node/plugins/cssEnabler.ts b/packages/vitest/src/node/plugins/cssEnabler.ts index b42b3f71417d..86ff46cc813d 100644 --- a/packages/vitest/src/node/plugins/cssEnabler.ts +++ b/packages/vitest/src/node/plugins/cssEnabler.ts @@ -26,33 +26,27 @@ export function CSSEnablerPlugin(ctx: Vitest): VitePlugin { return false } - const shouldReturnProxy = (id: string) => { - const { css } = ctx.config - if (typeof css === 'boolean') - return css - if (!isCSSModule(id)) - return false - return !css.modules?.mangleClassName - } - return { name: 'vitest:css-enabler', enforce: 'pre', transform(code, id) { if (!isCSS(id)) return - if (!shouldProcessCSS(id)) { - return { code: '' } - } - else if (shouldReturnProxy(id)) { - // TODO parse and check if object actually exists + if (shouldProcessCSS(id)) + return + + // return proxy for css modules, so that imported module has names: + // styles.foo returns a "foo" instead of "undefined" + if (isCSSModule(id)) { const code = `export default new Proxy(Object.create(null), { - get(_, style) { - return style; - }, - })` + get(_, style) { + return style; + }, + })` return { code } } + + return { code: '' } }, } } diff --git a/packages/vitest/src/node/plugins/index.ts b/packages/vitest/src/node/plugins/index.ts index f0f02bda4475..bebd2c9c23b4 100644 --- a/packages/vitest/src/node/plugins/index.ts +++ b/packages/vitest/src/node/plugins/index.ts @@ -98,6 +98,12 @@ export async function VitestPlugin(options: UserConfig = {}, ctx = new Vitest()) }, } + if (!preOptions?.css?.modules?.scopeClassNames) { + config.css ??= {} + config.css.modules ??= {} + config.css.modules.generateScopedName = (name: string) => name + } + if (!options.browser) { // disable deps optimization Object.assign(config, { diff --git a/packages/vitest/src/types/config.ts b/packages/vitest/src/types/config.ts index 110127954f4a..31f4e23ea4fe 100644 --- a/packages/vitest/src/types/config.ts +++ b/packages/vitest/src/types/config.ts @@ -370,13 +370,13 @@ export interface InlineConfig { * * When excluded, the CSS files will be replaced with empty strings to bypass the subsequent processing. * - * @default { include: [/\.module\./], modules: { mangleClassName: false } } + * @default { include: [], modules: { scopeClassNames: false } } */ css?: boolean | { include?: RegExp | RegExp[] exclude?: RegExp | RegExp[] modules?: { - mangleClassName?: boolean + scopeClassNames?: boolean } } /**