From 12426a5ab66c125157ac44fb84401891e5501f55 Mon Sep 17 00:00:00 2001 From: Vladimir Sheremet Date: Fri, 5 Aug 2022 18:28:51 +0300 Subject: [PATCH 1/7] feat: add option to keep class names for css modules --- packages/vitest/src/node/config.ts | 5 +++- .../vitest/src/node/plugins/cssEnabler.ts | 26 ++++++++++++++++++- packages/vitest/src/types/config.ts | 5 +++- 3 files changed, 33 insertions(+), 3 deletions(-) diff --git a/packages/vitest/src/node/config.ts b/packages/vitest/src/node/config.ts index 4e1b6ddba6cc..d68bec67f1f4 100644 --- a/packages/vitest/src/node/config.ts +++ b/packages/vitest/src/node/config.ts @@ -182,8 +182,11 @@ export function resolveConfig( resolved.passWithNoTests ??= true resolved.css ??= {} - if (typeof resolved.css === 'object') + if (typeof resolved.css === 'object') { resolved.css.include ??= [/\.module\./] + resolved.css.modules ??= {} + resolved.css.modules.mangleClassName ??= false + } resolved.cache ??= { dir: '' } if (resolved.cache) diff --git a/packages/vitest/src/node/plugins/cssEnabler.ts b/packages/vitest/src/node/plugins/cssEnabler.ts index 6afa2548737b..b42b3f71417d 100644 --- a/packages/vitest/src/node/plugins/cssEnabler.ts +++ b/packages/vitest/src/node/plugins/cssEnabler.ts @@ -4,11 +4,16 @@ import type { Vitest } from '../core' const cssLangs = '\\.(css|less|sass|scss|styl|stylus|pcss|postcss)($|\\?)' const cssLangRE = new RegExp(cssLangs) +const cssModuleRE = new RegExp(`\\.module${cssLangs}`) const isCSS = (id: string) => { return cssLangRE.test(id) } +const isCSSModule = (id: string) => { + return cssModuleRE.test(id) +} + export function CSSEnablerPlugin(ctx: Vitest): VitePlugin { const shouldProcessCSS = (id: string) => { const { css } = ctx.config @@ -21,14 +26,33 @@ 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)) + if (!shouldProcessCSS(id)) { return { code: '' } + } + else if (shouldReturnProxy(id)) { + // TODO parse and check if object actually exists + const code = `export default new Proxy(Object.create(null), { + get(_, style) { + return style; + }, + })` + return { code } + } }, } } diff --git a/packages/vitest/src/types/config.ts b/packages/vitest/src/types/config.ts index db8767fde28f..24ba21c0bda8 100644 --- a/packages/vitest/src/types/config.ts +++ b/packages/vitest/src/types/config.ts @@ -375,11 +375,14 @@ export interface InlineConfig { * * When excluded, the CSS files will be replaced with empty strings to bypass the subsequent processing. * - * @default { include: [/\.module\./] } + * @default { include: [/\.module\./], modules: { mangleClassName: false } } */ css?: boolean | { include?: RegExp | RegExp[] exclude?: RegExp | RegExp[] + modules?: { + mangleClassName?: boolean + } } /** * A number of tests that are allowed to run at the same time marked with `test.concurrent`. From b6872c4797e4f5936d2fdaf6844c536678c7cde5 Mon Sep 17 00:00:00 2001 From: Vladimir Sheremet Date: Sat, 3 Sep 2022 15:05:58 +0300 Subject: [PATCH 2/7] chore: don't process module.css files, rename mangleClassName -> scopeClassNames --- docs/config/index.md | 33 ++++++++++++++++--- packages/vitest/src/node/config.ts | 3 +- .../vitest/src/node/plugins/cssEnabler.ts | 30 +++++++---------- packages/vitest/src/node/plugins/index.ts | 6 ++++ packages/vitest/src/types/config.ts | 4 +-- 5 files changed, 50 insertions(+), 26 deletions(-) diff --git a/docs/config/index.md b/docs/config/index.md index cfd33bd63090..a98fcef9fca9 100644 --- a/docs/config/index.md +++ b/docs/config/index.md @@ -706,14 +706,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. @@ -724,6 +722,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 24ba21c0bda8..cc6258e0eadb 100644 --- a/packages/vitest/src/types/config.ts +++ b/packages/vitest/src/types/config.ts @@ -375,13 +375,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 } } /** From 94ab61eefc9e44df0c3648b92590bc8bd20f9832 Mon Sep 17 00:00:00 2001 From: Vladimir Sheremet Date: Sat, 3 Sep 2022 16:01:20 +0300 Subject: [PATCH 3/7] test: testing css --- docs/config/index.md | 2 +- packages/vitest/src/defaults.ts | 2 +- .../vitest/src/node/plugins/cssEnabler.ts | 46 +++++++++++-------- packages/vitest/src/node/plugins/index.ts | 2 +- pnpm-lock.yaml | 14 +++++- test/css/package.json | 12 +++++ test/css/src/App.css | 4 ++ test/css/src/App.module.css | 7 +++ test/css/test/default-css.spec.ts | 27 +++++++++++ test/css/test/process-css.spec.ts | 27 +++++++++++ test/css/test/process-module.spec.ts | 27 +++++++++++ test/css/test/scope-module.spec.ts | 20 ++++++++ test/css/test/utils.ts | 13 ++++++ test/css/testing.mjs | 25 ++++++++++ test/css/vitest.config.ts | 7 +++ 15 files changed, 212 insertions(+), 23 deletions(-) create mode 100644 test/css/package.json create mode 100644 test/css/src/App.css create mode 100644 test/css/src/App.module.css create mode 100644 test/css/test/default-css.spec.ts create mode 100644 test/css/test/process-css.spec.ts create mode 100644 test/css/test/process-module.spec.ts create mode 100644 test/css/test/scope-module.spec.ts create mode 100644 test/css/test/utils.ts create mode 100644 test/css/testing.mjs create mode 100644 test/css/vitest.config.ts diff --git a/docs/config/index.md b/docs/config/index.md index a98fcef9fca9..839e214a8cb4 100644 --- a/docs/config/index.md +++ b/docs/config/index.md @@ -745,7 +745,7 @@ You might want to enable this, if your CSS classes are conflicting with each oth // test const styles = window.getComputedStyles(
) -// it's possible to have two different classes with conflicting styles +// this will fail, if global css is loaded after element expect(styles).toMatchObject({ with: '100px' }) ``` diff --git a/packages/vitest/src/defaults.ts b/packages/vitest/src/defaults.ts index e8d2814a01bd..bd29f09802f2 100644 --- a/packages/vitest/src/defaults.ts +++ b/packages/vitest/src/defaults.ts @@ -76,7 +76,7 @@ const config = { uiBase: '/__vitest__/', open: true, css: { - include: [/\.module\./], + include: [], }, coverage: coverageConfigDefaults, fakeTimers: fakeTimersDefaults, diff --git a/packages/vitest/src/node/plugins/cssEnabler.ts b/packages/vitest/src/node/plugins/cssEnabler.ts index 86ff46cc813d..c69105b045ef 100644 --- a/packages/vitest/src/node/plugins/cssEnabler.ts +++ b/packages/vitest/src/node/plugins/cssEnabler.ts @@ -14,7 +14,7 @@ const isCSSModule = (id: string) => { return cssModuleRE.test(id) } -export function CSSEnablerPlugin(ctx: Vitest): VitePlugin { +export function CSSEnablerPlugin(ctx: Vitest): VitePlugin[] { const shouldProcessCSS = (id: string) => { const { css } = ctx.config if (typeof css === 'boolean') @@ -26,27 +26,37 @@ export function CSSEnablerPlugin(ctx: Vitest): VitePlugin { return false } - return { - name: 'vitest:css-enabler', - enforce: 'pre', - transform(code, id) { - if (!isCSS(id)) - return - if (shouldProcessCSS(id)) - return + return [ + { + name: 'vitest:css-disable', + enforce: 'pre', + transform(code, id) { + if (!isCSS(id)) + return + // css plugin inside Vite won't do anything if the code is empty + // but it will put __vite__updateStyle anyway + if (!shouldProcessCSS(id)) + return { code: '' } + }, + }, + { + name: 'vitest:css-empty-post', + enforce: 'post', + transform(_, id) { + if (!isCSS(id) || 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), { + if (isCSSModule(id)) { + // return proxy for css modules, so that imported module has names: + // styles.foo returns a "foo" instead of "undefined" + const code = `export default new Proxy(Object.create(null), { get(_, style) { return style; }, })` - return { code } - } - - return { code: '' } + return { code } + } + }, }, - } + ] } diff --git a/packages/vitest/src/node/plugins/index.ts b/packages/vitest/src/node/plugins/index.ts index bebd2c9c23b4..a4ecc7439e91 100644 --- a/packages/vitest/src/node/plugins/index.ts +++ b/packages/vitest/src/node/plugins/index.ts @@ -170,7 +170,7 @@ export async function VitestPlugin(options: UserConfig = {}, ctx = new Vitest()) ...(options.browser ? await BrowserPlugin() : []), - CSSEnablerPlugin(ctx), + ...CSSEnablerPlugin(ctx), CoverageTransform(ctx), options.ui ? await UIPlugin() diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7aefb30ea826..9d6359754131 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -126,7 +126,7 @@ importers: unocss: 0.45.13_vite@3.0.9 unplugin-vue-components: 0.22.4_vite@3.0.9+vue@3.2.38 vite: 3.0.9 - vite-plugin-pwa: 0.12.3_vite@3.0.9 + vite-plugin-pwa: 0.12.3_f7se6o6eqkwcix4u3svh6mkvda vitepress: 1.0.0-alpha.13 workbox-window: 6.5.4 @@ -932,6 +932,14 @@ importers: vitest: link:../../packages/vitest vue: 3.2.38 + test/css: + specifiers: + jsdom: ^20.0.0 + vitest: workspace:* + devDependencies: + jsdom: 20.0.0 + vitest: link:../../packages/vitest + test/edge-runtime: specifiers: '@edge-runtime/vm': 1.1.0-beta.26 @@ -17836,6 +17844,7 @@ packages: /unified/9.2.0: resolution: {integrity: sha512-vx2Z0vY+a3YoTj8+pttM3tiJHCwY5UFbYdiWrwBEbHmK8pvsPj2rtAX2BFfgXen8T39CJWblWRDT4L5WGXtDdg==} dependencies: + '@types/unist': 2.0.6 bail: 1.0.5 extend: 3.0.2 is-buffer: 2.0.5 @@ -18387,10 +18396,11 @@ packages: - supports-color dev: true - /vite-plugin-pwa/0.12.3_vite@3.0.9: + /vite-plugin-pwa/0.12.3_f7se6o6eqkwcix4u3svh6mkvda: resolution: {integrity: sha512-gmYdIVXpmBuNjzbJFPZFzxWYrX4lHqwMAlOtjmXBbxApiHjx9QPXKQPJjSpeTeosLKvVbNcKSAAhfxMda0QVNQ==} peerDependencies: vite: ^2.0.0 || ^3.0.0-0 + workbox-window: ^6.4.0 dependencies: debug: 4.3.4 fast-glob: 3.2.11 diff --git a/test/css/package.json b/test/css/package.json new file mode 100644 index 000000000000..fa8898f68362 --- /dev/null +++ b/test/css/package.json @@ -0,0 +1,12 @@ +{ + "name": "@vitest/test-css", + "private": true, + "scripts": { + "test": "node testing.mjs", + "coverage": "vitest run --coverage" + }, + "devDependencies": { + "jsdom": "^20.0.0", + "vitest": "workspace:*" + } +} diff --git a/test/css/src/App.css b/test/css/src/App.css new file mode 100644 index 000000000000..df27a059f185 --- /dev/null +++ b/test/css/src/App.css @@ -0,0 +1,4 @@ +.main { + display: flex; + width: 100px; +} diff --git a/test/css/src/App.module.css b/test/css/src/App.module.css new file mode 100644 index 000000000000..f9fbbb6ccd4e --- /dev/null +++ b/test/css/src/App.module.css @@ -0,0 +1,7 @@ +.main { + display: flex; +} + +.module { + width: 100px; +} \ No newline at end of file diff --git a/test/css/test/default-css.spec.ts b/test/css/test/default-css.spec.ts new file mode 100644 index 000000000000..fc26e535b299 --- /dev/null +++ b/test/css/test/default-css.spec.ts @@ -0,0 +1,27 @@ +import { describe, expect, test } from 'vitest' +import { useRemoveStyles } from './utils' + +describe('don\'t process css by default', () => { + useRemoveStyles() + + test('doesn\'t apply css', async () => { + await import('../src/App.css') + + const element = document.createElement('div') + element.className = 'main' + const computed = window.getComputedStyle(element) + expect(computed.display).toBe('block') + }) + + test('module is not processed', async () => { + const { default: styles } = await import('../src/App.module.css') + + expect(styles.module).toBe('module') + expect(styles.someRandomValue).toBe('someRandomValue') + const element = document.createElement('div') + element.className = 'module' + const computed = window.getComputedStyle(element) + expect(computed.display).toBe('block') + expect(computed.width).toBe('') + }) +}) diff --git a/test/css/test/process-css.spec.ts b/test/css/test/process-css.spec.ts new file mode 100644 index 000000000000..ed5d4bb658a0 --- /dev/null +++ b/test/css/test/process-css.spec.ts @@ -0,0 +1,27 @@ +import { describe, expect, test } from 'vitest' +import { useRemoveStyles } from './utils' + +describe('don\'t process css by default', () => { + useRemoveStyles() + + test('apply css', async () => { + await import('../src/App.css') + + const element = document.createElement('div') + element.className = 'main' + const computed = window.getComputedStyle(element) + expect(computed.display).toBe('flex') + }) + + test('module is not processed', async () => { + const { default: styles } = await import('../src/App.module.css') + + expect(styles.module).toBe('module') + expect(styles.someRandomValue).toBe('someRandomValue') + const element = document.createElement('div') + element.className = 'module' + const computed = window.getComputedStyle(element) + expect(computed.display).toBe('block') + expect(computed.width).toBe('') + }) +}) diff --git a/test/css/test/process-module.spec.ts b/test/css/test/process-module.spec.ts new file mode 100644 index 000000000000..15d3639399d3 --- /dev/null +++ b/test/css/test/process-module.spec.ts @@ -0,0 +1,27 @@ +import { describe, expect, test } from 'vitest' +import { useRemoveStyles } from './utils' + +describe('don\'t process css by default', () => { + useRemoveStyles() + + test('doesn\'t apply css', async () => { + await import('../src/App.css') + + const element = document.createElement('div') + element.className = 'main' + const computed = window.getComputedStyle(element) + expect(computed.display).toBe('block') + }) + + test('module is processed', async () => { + const { default: styles } = await import('../src/App.module.css') + + expect(styles.module).toBe('module') + expect(styles.someRandomValue).toBeUndefined() + const element = document.createElement('div') + element.className = 'main module' + const computed = window.getComputedStyle(element) + expect(computed.display).toBe('flex') + expect(computed.width).toBe('100px') + }) +}) diff --git a/test/css/test/scope-module.spec.ts b/test/css/test/scope-module.spec.ts new file mode 100644 index 000000000000..409da3d2ada3 --- /dev/null +++ b/test/css/test/scope-module.spec.ts @@ -0,0 +1,20 @@ +import { describe, expect, test } from 'vitest' + +describe('don\'t process css by default', () => { + test('module is processed and scoped', async () => { + const { default: styles } = await import('../src/App.module.css') + + expect(styles.module).toMatch(/_module/) + expect(styles.someRandomValue).toBeUndefined() + const element = document.createElement('div') + element.className = 'module main' + const computedStatic = window.getComputedStyle(element) + expect(computedStatic.display).toBe('block') + expect(computedStatic.width).toBe('') + + element.className = `${styles.module} ${styles.main}` + const computedModules = window.getComputedStyle(element) + expect(computedModules.display).toBe('flex') + expect(computedModules.width).toBe('100px') + }) +}) diff --git a/test/css/test/utils.ts b/test/css/test/utils.ts new file mode 100644 index 000000000000..ccb4219f5318 --- /dev/null +++ b/test/css/test/utils.ts @@ -0,0 +1,13 @@ +import { afterEach, beforeEach } from 'vitest' + +const removeStyles = () => { + document.head.querySelectorAll('style').forEach(style => style.remove()) +} +export function useRemoveStyles() { + beforeEach(() => removeStyles()) + afterEach(() => removeStyles()) + + return { + removeStyles, + } +} diff --git a/test/css/testing.mjs b/test/css/testing.mjs new file mode 100644 index 000000000000..7dc16fa817cd --- /dev/null +++ b/test/css/testing.mjs @@ -0,0 +1,25 @@ +import { startVitest } from 'vitest/node' + +const configs = [ + ['default-css', {}], + ['process-css', { include: [/App\.css/] }], + ['process-module', { include: [/App\.module\.css/] }], + ['scope-module', { include: [/App\.module\.css/], modules: { scopeClassNames: true } }], +] + +async function runTests() { + for (const [name, config] of configs) { + const success = await startVitest([name], { + run: true, + css: config, + teardownTimeout: 1000_000_000, + }) + + if (!success) + process.exit(1) + } + + process.exit(0) +} + +runTests() diff --git a/test/css/vitest.config.ts b/test/css/vitest.config.ts new file mode 100644 index 000000000000..68e3449dc6ee --- /dev/null +++ b/test/css/vitest.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + test: { + environment: 'jsdom', + }, +}) From 24ded1f622be21dc863bebe187854723994699bb Mon Sep 17 00:00:00 2001 From: Vladimir Sheremet Date: Sat, 3 Sep 2022 16:07:14 +0300 Subject: [PATCH 4/7] chore: return empty css --- packages/vitest/src/node/plugins/cssEnabler.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/vitest/src/node/plugins/cssEnabler.ts b/packages/vitest/src/node/plugins/cssEnabler.ts index c69105b045ef..0c68098dd3da 100644 --- a/packages/vitest/src/node/plugins/cssEnabler.ts +++ b/packages/vitest/src/node/plugins/cssEnabler.ts @@ -56,6 +56,8 @@ export function CSSEnablerPlugin(ctx: Vitest): VitePlugin[] { })` return { code } } + + return { code: '' } }, }, ] From acb209a8d6e0037f976400bb0e02d014ec1f54c1 Mon Sep 17 00:00:00 2001 From: Vladimir Sheremet Date: Sun, 4 Sep 2022 09:07:08 +0300 Subject: [PATCH 5/7] refactor: rmeove scopedClassNames, add classNamesStrategy option --- docs/config/index.md | 27 ++++--------- .../src/integrations/css/css-modules.ts | 19 +++++++++ packages/vitest/src/node/config.ts | 2 +- .../vitest/src/node/plugins/cssEnabler.ts | 15 ++++++- packages/vitest/src/node/plugins/index.ts | 17 ++++++-- packages/vitest/src/types/config.ts | 5 ++- test/css/test/default-css.spec.ts | 17 ++++++-- test/css/test/non-scope-module.spec.ts | 18 +++++++++ test/css/test/process-css.spec.ts | 18 +++++++-- test/css/test/process-module.spec.ts | 15 ++++--- test/css/test/scope-module.spec.ts | 40 +++++++++++-------- test/css/testing.mjs | 10 +++-- 12 files changed, 144 insertions(+), 59 deletions(-) create mode 100644 packages/vitest/src/integrations/css/css-modules.ts create mode 100644 test/css/test/non-scope-module.spec.ts diff --git a/docs/config/index.md b/docs/config/index.md index 839e214a8cb4..2f849b8da984 100644 --- a/docs/config/index.md +++ b/docs/config/index.md @@ -724,30 +724,19 @@ RegExp pattern for files that will return an empty CSS file. #### css.modules -- **Type**: `{ scopeClassNames? }` +- **Type**: `{ classNamesStrategy? }` - **Default**: `{}` -#### css.modules.scopeClassNames +#### css.modules.classNamesStrategy -- **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: +- **Type**: `'stable' | 'scoped' | 'non-scoped'` +- **Default**: `'stable'` -```tsx -// global.module.css -// .error { width: 600px } +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 can choose one of the options: -// element.module.css -// .error { width: 100px } - -// test -const styles = window.getComputedStyles(
) -// this will fail, if global css is loaded after element -expect(styles).toMatchObject({ with: '100px' }) -``` +- `stable`: class names will be generated as `_${name}_${hashedFilename}`, which means that generated class will stay the same, if CSS content is changed, but will change, if the name of the file is modified, or file is moved to another folder. This setting is useful, if you use snapshot feature. +- `scoped`: class names will be generated as usual, respecting `css.modules.generateScopeName` method, if you have one. By default, filename will be generated as `_${name}_${hash}`, where hash includes filename and content of the file. +- `non-scoped`: class names will stay as they are defined in CSS. ### maxConcurrency diff --git a/packages/vitest/src/integrations/css/css-modules.ts b/packages/vitest/src/integrations/css/css-modules.ts new file mode 100644 index 000000000000..7010059c0ca5 --- /dev/null +++ b/packages/vitest/src/integrations/css/css-modules.ts @@ -0,0 +1,19 @@ +import type { CSSModuleScopeStrategy } from '../../types' + +export function generateCssFilenameHash(filename: string) { + return Buffer.from(filename).toString('base64').substring(0, 6) +} + +export function generateScopedClassName( + strategy: CSSModuleScopeStrategy, + name: string, + filename: string, +) { + // should be configured by Vite defaults + if (strategy === 'scoped') + return null + if (strategy === 'non-scoped') + return name + const hash = generateCssFilenameHash(filename) + return `_${name}_${hash}` +} diff --git a/packages/vitest/src/node/config.ts b/packages/vitest/src/node/config.ts index 9cbfb18fae0a..4b8ad73d775f 100644 --- a/packages/vitest/src/node/config.ts +++ b/packages/vitest/src/node/config.ts @@ -184,7 +184,7 @@ export function resolveConfig( resolved.css ??= {} if (typeof resolved.css === 'object') { resolved.css.modules ??= {} - resolved.css.modules.scopeClassNames ??= false + resolved.css.modules.classNamesStrategy ??= 'stable' } resolved.cache ??= { dir: '' } diff --git a/packages/vitest/src/node/plugins/cssEnabler.ts b/packages/vitest/src/node/plugins/cssEnabler.ts index 0c68098dd3da..1bab19ad6013 100644 --- a/packages/vitest/src/node/plugins/cssEnabler.ts +++ b/packages/vitest/src/node/plugins/cssEnabler.ts @@ -1,4 +1,7 @@ +import { relative } from 'pathe' import type { Plugin as VitePlugin } from 'vite' +import { generateCssFilenameHash } from '../../integrations/css/css-modules' +import type { CSSModuleScopeStrategy } from '../../types' import { toArray } from '../../utils' import type { Vitest } from '../core' @@ -14,6 +17,13 @@ const isCSSModule = (id: string) => { return cssModuleRE.test(id) } +const getCSSModuleProxyReturn = (strategy: CSSModuleScopeStrategy, filename: string) => { + if (strategy === 'non-scoped') + return 'style' + const hash = generateCssFilenameHash(filename) + return `\`_\${style}_${hash}\`` +} + export function CSSEnablerPlugin(ctx: Vitest): VitePlugin[] { const shouldProcessCSS = (id: string) => { const { css } = ctx.config @@ -49,9 +59,12 @@ export function CSSEnablerPlugin(ctx: Vitest): VitePlugin[] { if (isCSSModule(id)) { // return proxy for css modules, so that imported module has names: // styles.foo returns a "foo" instead of "undefined" + // we don't use code content to generate hash for "scoped", because it's empty + const scopeStrategy = (typeof ctx.config.css !== 'boolean' && ctx.config.css.modules?.classNamesStrategy) || 'stable' + const proxyReturn = getCSSModuleProxyReturn(scopeStrategy, relative(ctx.config.root, id)) const code = `export default new Proxy(Object.create(null), { get(_, style) { - return style; + return ${proxyReturn}; }, })` return { code } diff --git a/packages/vitest/src/node/plugins/index.ts b/packages/vitest/src/node/plugins/index.ts index a4ecc7439e91..169245356a42 100644 --- a/packages/vitest/src/node/plugins/index.ts +++ b/packages/vitest/src/node/plugins/index.ts @@ -1,9 +1,11 @@ import type { UserConfig as ViteConfig, Plugin as VitePlugin } from 'vite' +import { relative } from 'pathe' import { configDefaults } from '../../defaults' import type { ResolvedConfig, UserConfig } from '../../types' import { deepMerge, ensurePackageInstalled, notNullish } from '../../utils' import { resolveApiConfig } from '../config' import { Vitest } from '../core' +import { generateScopedClassName } from '../../integrations/css/css-modules' import { EnvReplacerPlugin } from './envReplacer' import { GlobalSetupPlugin } from './globalSetup' import { MocksPlugin } from './mock' @@ -11,13 +13,15 @@ import { CSSEnablerPlugin } from './cssEnabler' import { CoverageTransform } from './coverageTransform' export async function VitestPlugin(options: UserConfig = {}, ctx = new Vitest()): Promise { + const getRoot = () => ctx.config?.root || options.root || process.cwd() + async function UIPlugin() { - await ensurePackageInstalled('@vitest/ui', ctx.config?.root || options.root || process.cwd()) + await ensurePackageInstalled('@vitest/ui', getRoot()) return (await import('@vitest/ui')).default(options.uiBase) } async function BrowserPlugin() { - await ensurePackageInstalled('@vitest/browser', ctx.config?.root || options.root || process.cwd()) + await ensurePackageInstalled('@vitest/browser', getRoot()) return (await import('@vitest/browser')).default('/') } @@ -98,10 +102,15 @@ export async function VitestPlugin(options: UserConfig = {}, ctx = new Vitest()) }, } - if (!preOptions?.css?.modules?.scopeClassNames) { + const classNamesStrategy = preOptions.css && preOptions.css?.modules?.classNamesStrategy + + if (classNamesStrategy !== 'scoped') { config.css ??= {} config.css.modules ??= {} - config.css.modules.generateScopedName = (name: string) => name + config.css.modules.generateScopedName = (name: string, filename: string) => { + const root = getRoot() + return generateScopedClassName(classNamesStrategy, name, relative(root, filename))! + } } if (!options.browser) { diff --git a/packages/vitest/src/types/config.ts b/packages/vitest/src/types/config.ts index cc6258e0eadb..da60845e4332 100644 --- a/packages/vitest/src/types/config.ts +++ b/packages/vitest/src/types/config.ts @@ -12,6 +12,7 @@ import type { Arrayable } from './general' export type BuiltinEnvironment = 'node' | 'jsdom' | 'happy-dom' | 'edge-runtime' // Record is used, so user can get intellisense for builtin environments, but still allow custom environments export type VitestEnvironment = BuiltinEnvironment | (string & Record) +export type CSSModuleScopeStrategy = 'stable' | 'scoped' | 'non-scoped' export type ApiConfig = Pick @@ -375,13 +376,13 @@ export interface InlineConfig { * * When excluded, the CSS files will be replaced with empty strings to bypass the subsequent processing. * - * @default { include: [], modules: { scopeClassNames: false } } + * @default { include: [], modules: { classNamesStrategy: false } } */ css?: boolean | { include?: RegExp | RegExp[] exclude?: RegExp | RegExp[] modules?: { - scopeClassNames?: boolean + classNamesStrategy?: CSSModuleScopeStrategy } } /** diff --git a/test/css/test/default-css.spec.ts b/test/css/test/default-css.spec.ts index fc26e535b299..ce7f2ffcb874 100644 --- a/test/css/test/default-css.spec.ts +++ b/test/css/test/default-css.spec.ts @@ -11,17 +11,28 @@ describe('don\'t process css by default', () => { element.className = 'main' const computed = window.getComputedStyle(element) expect(computed.display).toBe('block') + expect(element).toMatchInlineSnapshot(` +
+ `) }) test('module is not processed', async () => { const { default: styles } = await import('../src/App.module.css') - expect(styles.module).toBe('module') - expect(styles.someRandomValue).toBe('someRandomValue') + // HASH is static, based on the filepath to root + expect(styles.module).toBe('_module_c3JjL0') + expect(styles.someRandomValue).toBe('_someRandomValue_c3JjL0') const element = document.createElement('div') - element.className = 'module' + element.className = '_module_c3JjL0' const computed = window.getComputedStyle(element) expect(computed.display).toBe('block') expect(computed.width).toBe('') + expect(element).toMatchInlineSnapshot(` +
+ `) }) }) diff --git a/test/css/test/non-scope-module.spec.ts b/test/css/test/non-scope-module.spec.ts new file mode 100644 index 000000000000..20aae1621771 --- /dev/null +++ b/test/css/test/non-scope-module.spec.ts @@ -0,0 +1,18 @@ +import { expect, test } from 'vitest' + +test('module is processed', async () => { + const { default: styles } = await import('../src/App.module.css') + + expect(styles.module).toBe('module') + expect(styles.someRandomValue).toBeUndefined() + const element = document.createElement('div') + element.className = `${styles.main} ${styles.module}` + const computed = window.getComputedStyle(element) + expect(computed.display).toBe('flex') + expect(computed.width).toBe('100px') + expect(element).toMatchInlineSnapshot(` +
+ `) +}) diff --git a/test/css/test/process-css.spec.ts b/test/css/test/process-css.spec.ts index ed5d4bb658a0..1f5a414c8549 100644 --- a/test/css/test/process-css.spec.ts +++ b/test/css/test/process-css.spec.ts @@ -1,7 +1,7 @@ import { describe, expect, test } from 'vitest' import { useRemoveStyles } from './utils' -describe('don\'t process css by default', () => { +describe('process only css, not module css', () => { useRemoveStyles() test('apply css', async () => { @@ -11,17 +11,27 @@ describe('don\'t process css by default', () => { element.className = 'main' const computed = window.getComputedStyle(element) expect(computed.display).toBe('flex') + expect(element).toMatchInlineSnapshot(` +
+ `) }) test('module is not processed', async () => { const { default: styles } = await import('../src/App.module.css') - expect(styles.module).toBe('module') - expect(styles.someRandomValue).toBe('someRandomValue') + expect(styles.module).toBe('_module_c3JjL0') + expect(styles.someRandomValue).toBe('_someRandomValue_c3JjL0') const element = document.createElement('div') - element.className = 'module' + element.className = '_module_c3JjL0' const computed = window.getComputedStyle(element) expect(computed.display).toBe('block') expect(computed.width).toBe('') + expect(element).toMatchInlineSnapshot(` +
+ `) }) }) diff --git a/test/css/test/process-module.spec.ts b/test/css/test/process-module.spec.ts index 15d3639399d3..4d0bdf97bba9 100644 --- a/test/css/test/process-module.spec.ts +++ b/test/css/test/process-module.spec.ts @@ -1,7 +1,7 @@ import { describe, expect, test } from 'vitest' import { useRemoveStyles } from './utils' -describe('don\'t process css by default', () => { +describe('processing module css', () => { useRemoveStyles() test('doesn\'t apply css', async () => { @@ -10,18 +10,23 @@ describe('don\'t process css by default', () => { const element = document.createElement('div') element.className = 'main' const computed = window.getComputedStyle(element) - expect(computed.display).toBe('block') + expect(computed.display, 'css is not processed').toBe('block') }) test('module is processed', async () => { const { default: styles } = await import('../src/App.module.css') - expect(styles.module).toBe('module') + expect(styles.module).toBe('_module_c3JjL0') expect(styles.someRandomValue).toBeUndefined() const element = document.createElement('div') - element.className = 'main module' + element.className = '_main_c3JjL0 _module_c3JjL0' const computed = window.getComputedStyle(element) - expect(computed.display).toBe('flex') + expect(computed.display, 'css is processed').toBe('flex') expect(computed.width).toBe('100px') + expect(element).toMatchInlineSnapshot(` +
+ `) }) }) diff --git a/test/css/test/scope-module.spec.ts b/test/css/test/scope-module.spec.ts index 409da3d2ada3..1d3da3ab22df 100644 --- a/test/css/test/scope-module.spec.ts +++ b/test/css/test/scope-module.spec.ts @@ -1,20 +1,28 @@ -import { describe, expect, test } from 'vitest' +import { expect, test } from 'vitest' -describe('don\'t process css by default', () => { - test('module is processed and scoped', async () => { - const { default: styles } = await import('../src/App.module.css') +test('module is processed and scoped', async () => { + const { default: styles } = await import('../src/App.module.css') - expect(styles.module).toMatch(/_module/) - expect(styles.someRandomValue).toBeUndefined() - const element = document.createElement('div') - element.className = 'module main' - const computedStatic = window.getComputedStyle(element) - expect(computedStatic.display).toBe('block') - expect(computedStatic.width).toBe('') + expect(styles.module).toMatch(/_module_\w+_\w/) + expect(styles.someRandomValue).toBeUndefined() + const element = document.createElement('div') + element.className = 'module main' + const computedStatic = window.getComputedStyle(element) + expect(computedStatic.display).toBe('block') + expect(computedStatic.width).toBe('') + expect(element).toMatchInlineSnapshot(` +
+ `) - element.className = `${styles.module} ${styles.main}` - const computedModules = window.getComputedStyle(element) - expect(computedModules.display).toBe('flex') - expect(computedModules.width).toBe('100px') - }) + element.className = `${styles.module} ${styles.main}` + const computedModules = window.getComputedStyle(element) + expect(computedModules.display).toBe('flex') + expect(computedModules.width).toBe('100px') + expect(element).toMatchInlineSnapshot(` +
+ `) }) diff --git a/test/css/testing.mjs b/test/css/testing.mjs index 7dc16fa817cd..49d92bd65995 100644 --- a/test/css/testing.mjs +++ b/test/css/testing.mjs @@ -1,10 +1,11 @@ import { startVitest } from 'vitest/node' const configs = [ - ['default-css', {}], - ['process-css', { include: [/App\.css/] }], - ['process-module', { include: [/App\.module\.css/] }], - ['scope-module', { include: [/App\.module\.css/], modules: { scopeClassNames: true } }], + ['test/default-css', {}], + ['test/process-css', { include: [/App\.css/] }], + ['test/process-module', { include: [/App\.module\.css/] }], + ['test/scope-module', { include: [/App\.module\.css/], modules: { classNamesStrategy: 'scoped' } }], + ['test/non-scope-module', { include: [/App\.module\.css/], modules: { classNamesStrategy: 'non-scoped' } }], ] async function runTests() { @@ -12,6 +13,7 @@ async function runTests() { const success = await startVitest([name], { run: true, css: config, + update: false, teardownTimeout: 1000_000_000, }) From 58a777fbd237e0a29c656676f248b1c2c033d004 Mon Sep 17 00:00:00 2001 From: Vladimir Sheremet Date: Sun, 4 Sep 2022 09:08:07 +0300 Subject: [PATCH 6/7] chore: cleanup --- docs/config/index.md | 4 ++-- packages/vitest/src/node/config.ts | 2 +- packages/vitest/src/node/plugins/cssEnabler.ts | 2 +- packages/vitest/src/node/plugins/index.ts | 6 +++--- packages/vitest/src/types/config.ts | 4 ++-- test/css/testing.mjs | 4 ++-- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/docs/config/index.md b/docs/config/index.md index 2f849b8da984..ae77a259f98a 100644 --- a/docs/config/index.md +++ b/docs/config/index.md @@ -724,10 +724,10 @@ RegExp pattern for files that will return an empty CSS file. #### css.modules -- **Type**: `{ classNamesStrategy? }` +- **Type**: `{ classNameStrategy? }` - **Default**: `{}` -#### css.modules.classNamesStrategy +#### css.modules.classNameStrategy - **Type**: `'stable' | 'scoped' | 'non-scoped'` - **Default**: `'stable'` diff --git a/packages/vitest/src/node/config.ts b/packages/vitest/src/node/config.ts index 4b8ad73d775f..72999551a87f 100644 --- a/packages/vitest/src/node/config.ts +++ b/packages/vitest/src/node/config.ts @@ -184,7 +184,7 @@ export function resolveConfig( resolved.css ??= {} if (typeof resolved.css === 'object') { resolved.css.modules ??= {} - resolved.css.modules.classNamesStrategy ??= 'stable' + resolved.css.modules.classNameStrategy ??= 'stable' } resolved.cache ??= { dir: '' } diff --git a/packages/vitest/src/node/plugins/cssEnabler.ts b/packages/vitest/src/node/plugins/cssEnabler.ts index 1bab19ad6013..fda273615eb6 100644 --- a/packages/vitest/src/node/plugins/cssEnabler.ts +++ b/packages/vitest/src/node/plugins/cssEnabler.ts @@ -60,7 +60,7 @@ export function CSSEnablerPlugin(ctx: Vitest): VitePlugin[] { // return proxy for css modules, so that imported module has names: // styles.foo returns a "foo" instead of "undefined" // we don't use code content to generate hash for "scoped", because it's empty - const scopeStrategy = (typeof ctx.config.css !== 'boolean' && ctx.config.css.modules?.classNamesStrategy) || 'stable' + const scopeStrategy = (typeof ctx.config.css !== 'boolean' && ctx.config.css.modules?.classNameStrategy) || 'stable' const proxyReturn = getCSSModuleProxyReturn(scopeStrategy, relative(ctx.config.root, id)) const code = `export default new Proxy(Object.create(null), { get(_, style) { diff --git a/packages/vitest/src/node/plugins/index.ts b/packages/vitest/src/node/plugins/index.ts index 169245356a42..cc4ab2974cd7 100644 --- a/packages/vitest/src/node/plugins/index.ts +++ b/packages/vitest/src/node/plugins/index.ts @@ -102,14 +102,14 @@ export async function VitestPlugin(options: UserConfig = {}, ctx = new Vitest()) }, } - const classNamesStrategy = preOptions.css && preOptions.css?.modules?.classNamesStrategy + const classNameStrategy = preOptions.css && preOptions.css?.modules?.classNameStrategy - if (classNamesStrategy !== 'scoped') { + if (classNameStrategy !== 'scoped') { config.css ??= {} config.css.modules ??= {} config.css.modules.generateScopedName = (name: string, filename: string) => { const root = getRoot() - return generateScopedClassName(classNamesStrategy, name, relative(root, filename))! + return generateScopedClassName(classNameStrategy, name, relative(root, filename))! } } diff --git a/packages/vitest/src/types/config.ts b/packages/vitest/src/types/config.ts index da60845e4332..b588aa514bcb 100644 --- a/packages/vitest/src/types/config.ts +++ b/packages/vitest/src/types/config.ts @@ -376,13 +376,13 @@ export interface InlineConfig { * * When excluded, the CSS files will be replaced with empty strings to bypass the subsequent processing. * - * @default { include: [], modules: { classNamesStrategy: false } } + * @default { include: [], modules: { classNameStrategy: false } } */ css?: boolean | { include?: RegExp | RegExp[] exclude?: RegExp | RegExp[] modules?: { - classNamesStrategy?: CSSModuleScopeStrategy + classNameStrategy?: CSSModuleScopeStrategy } } /** diff --git a/test/css/testing.mjs b/test/css/testing.mjs index 49d92bd65995..cce302cac228 100644 --- a/test/css/testing.mjs +++ b/test/css/testing.mjs @@ -4,8 +4,8 @@ const configs = [ ['test/default-css', {}], ['test/process-css', { include: [/App\.css/] }], ['test/process-module', { include: [/App\.module\.css/] }], - ['test/scope-module', { include: [/App\.module\.css/], modules: { classNamesStrategy: 'scoped' } }], - ['test/non-scope-module', { include: [/App\.module\.css/], modules: { classNamesStrategy: 'non-scoped' } }], + ['test/scope-module', { include: [/App\.module\.css/], modules: { classNameStrategy: 'scoped' } }], + ['test/non-scope-module', { include: [/App\.module\.css/], modules: { classNameStrategy: 'non-scoped' } }], ] async function runTests() { From 3b46df30ac9d529ec3c486b5aa8ae825341d54d5 Mon Sep 17 00:00:00 2001 From: Vladimir Sheremet Date: Sun, 4 Sep 2022 10:39:04 +0300 Subject: [PATCH 7/7] refactor: update hashing for css modules --- packages/vitest/src/integrations/css/css-modules.ts | 5 +++-- test/css/test/default-css.spec.ts | 8 ++++---- test/css/test/process-css.spec.ts | 8 ++++---- test/css/test/process-module.spec.ts | 6 +++--- 4 files changed, 14 insertions(+), 13 deletions(-) diff --git a/packages/vitest/src/integrations/css/css-modules.ts b/packages/vitest/src/integrations/css/css-modules.ts index 7010059c0ca5..71b8f354ee46 100644 --- a/packages/vitest/src/integrations/css/css-modules.ts +++ b/packages/vitest/src/integrations/css/css-modules.ts @@ -1,7 +1,8 @@ +import { createHash } from 'node:crypto' import type { CSSModuleScopeStrategy } from '../../types' -export function generateCssFilenameHash(filename: string) { - return Buffer.from(filename).toString('base64').substring(0, 6) +export function generateCssFilenameHash(filepath: string) { + return createHash('md5').update(filepath).digest('hex').slice(0, 6) } export function generateScopedClassName( diff --git a/test/css/test/default-css.spec.ts b/test/css/test/default-css.spec.ts index ce7f2ffcb874..d7d4f62eebff 100644 --- a/test/css/test/default-css.spec.ts +++ b/test/css/test/default-css.spec.ts @@ -22,16 +22,16 @@ describe('don\'t process css by default', () => { const { default: styles } = await import('../src/App.module.css') // HASH is static, based on the filepath to root - expect(styles.module).toBe('_module_c3JjL0') - expect(styles.someRandomValue).toBe('_someRandomValue_c3JjL0') + expect(styles.module).toBe('_module_6dc87e') + expect(styles.someRandomValue).toBe('_someRandomValue_6dc87e') const element = document.createElement('div') - element.className = '_module_c3JjL0' + element.className = '_module_6dc87e' const computed = window.getComputedStyle(element) expect(computed.display).toBe('block') expect(computed.width).toBe('') expect(element).toMatchInlineSnapshot(`
`) }) diff --git a/test/css/test/process-css.spec.ts b/test/css/test/process-css.spec.ts index 1f5a414c8549..9e3130c78a6e 100644 --- a/test/css/test/process-css.spec.ts +++ b/test/css/test/process-css.spec.ts @@ -21,16 +21,16 @@ describe('process only css, not module css', () => { test('module is not processed', async () => { const { default: styles } = await import('../src/App.module.css') - expect(styles.module).toBe('_module_c3JjL0') - expect(styles.someRandomValue).toBe('_someRandomValue_c3JjL0') + expect(styles.module).toBe('_module_6dc87e') + expect(styles.someRandomValue).toBe('_someRandomValue_6dc87e') const element = document.createElement('div') - element.className = '_module_c3JjL0' + element.className = '_module_6dc87e' const computed = window.getComputedStyle(element) expect(computed.display).toBe('block') expect(computed.width).toBe('') expect(element).toMatchInlineSnapshot(`
`) }) diff --git a/test/css/test/process-module.spec.ts b/test/css/test/process-module.spec.ts index 4d0bdf97bba9..5510a95f536e 100644 --- a/test/css/test/process-module.spec.ts +++ b/test/css/test/process-module.spec.ts @@ -16,16 +16,16 @@ describe('processing module css', () => { test('module is processed', async () => { const { default: styles } = await import('../src/App.module.css') - expect(styles.module).toBe('_module_c3JjL0') + expect(styles.module).toBe('_module_6dc87e') expect(styles.someRandomValue).toBeUndefined() const element = document.createElement('div') - element.className = '_main_c3JjL0 _module_c3JjL0' + element.className = '_main_6dc87e _module_6dc87e' const computed = window.getComputedStyle(element) expect(computed.display, 'css is processed').toBe('flex') expect(computed.width).toBe('100px') expect(element).toMatchInlineSnapshot(`
`) })