Files
vercel__next.js/test/production/css-features/css-modules-support.test.ts
Niklas Mischkulnig 1f52cb42ad Turbopack: shorten CSS module class names (#97944)
The class names were unnecessarily long.

Use the same as the lightningcss default:
`[hash]_[local]` which is
`<hash of the full file path>_<original class name or identifier>`

Keep the previous longer mechanism to aid in debugging in dev

Co-authored-by: vercel-fleet-prod[bot] <318278635+vercel-fleet-prod[bot]@users.noreply.github.com>
2026-08-27 21:22:53 +00:00

368 lines
11 KiB
TypeScript

/* eslint-disable jest/no-identical-title */
/* eslint-env jest */
import { nextTestSetup } from 'e2e-utils'
import { join } from 'path'
describe('Basic CSS Module Support', () => {
const { next, isTurbopack } = nextTestSetup({
files: join(__dirname, 'fixtures', 'basic-module'),
})
it(`should've emitted a single CSS file`, async () => {
const $ = await next.render$('/')
const cssSheet = $('link[rel="stylesheet"]')
expect(cssSheet.length).toBe(1)
const stylesheet = cssSheet.attr('href')!
const cssContent = await next.fetch(stylesheet).then((res) => res.text())
if (isTurbopack) {
expect(
cssContent.replace(/\/\*.*?\*\//g, '').trim()
).toMatchInlineSnapshot(`".KWKY6G_redText{color:red}"`)
} else {
expect(
cssContent.replace(/\/\*.*?\*\//g, '').trim()
).toMatchInlineSnapshot(`".index_redText__honUV{color:red}"`)
}
})
it(`should've injected the CSS on server render`, async () => {
const $ = await next.render$('/')
const cssPreload = $('link[rel="preload"][as="style"]')
expect(cssPreload.length).toBe(1)
expect(cssPreload.attr('href')).toMatch(
/^\/_next\/static\/.*\.css(\?dpl=.*)?$/
)
const cssSheet = $('link[rel="stylesheet"]')
expect(cssSheet.length).toBe(1)
expect(cssSheet.attr('href')).toMatch(
/^\/_next\/static\/.*\.css(\?dpl=.*)?$/
)
if (isTurbopack) {
expect($('#verify-red').attr('class')).toMatchInlineSnapshot(
`"KWKY6G_redText"`
)
} else {
expect($('#verify-red').attr('class')).toMatchInlineSnapshot(
`"index_redText__honUV"`
)
}
})
})
describe('3rd Party CSS Module Support', () => {
const { next, isTurbopack } = nextTestSetup({
files: join(__dirname, 'fixtures', '3rd-party-module'),
})
it(`should've emitted a single CSS file`, async () => {
const $ = await next.render$('/')
const cssSheet = $('link[rel="stylesheet"]')
expect(cssSheet.length).toBe(1)
const stylesheet = cssSheet.attr('href')!
const cssContent = await next.fetch(stylesheet).then((res) => res.text())
if (isTurbopack) {
expect(
cssContent.replace(/\/\*.*?\*\//g, '').trim()
).toMatchInlineSnapshot(
`".KWKY6G_foo{position:relative}:is(.KWKY6G_foo .bar,.KWKY6G_foo .baz){height:100%;overflow:hidden}.KWKY6G_foo .lol{width:80%}.KWKY6G_foo>.lel{width:80%}"`
)
} else {
expect(
cssContent.replace(/\/\*.*?\*\//g, '').trim()
).toMatchInlineSnapshot(
`".index_foo__6TgnK{position:relative}.index_foo__6TgnK .bar,.index_foo__6TgnK .baz{height:100%;overflow:hidden}.index_foo__6TgnK .lol,.index_foo__6TgnK>.lel{width:80%}"`
)
}
})
it(`should've injected the CSS on server render`, async () => {
const $ = await next.render$('/')
const cssPreload = $('link[rel="preload"][as="style"]')
expect(cssPreload.length).toBe(1)
expect(cssPreload.attr('href')).toMatch(
/^\/_next\/static\/.*\.css(\?dpl=.*)?$/
)
const cssSheet = $('link[rel="stylesheet"]')
expect(cssSheet.length).toBe(1)
expect(cssSheet.attr('href')).toMatch(
/^\/_next\/static\/.*\.css(\?dpl=.*)?$/
)
if (isTurbopack) {
expect($('#verify-div').attr('class')).toMatchInlineSnapshot(
`"KWKY6G_foo"`
)
} else {
expect($('#verify-div').attr('class')).toMatchInlineSnapshot(
`"index_foo__6TgnK"`
)
}
})
})
describe('Has CSS Module in computed styles in Production', () => {
const { next } = nextTestSetup({
files: join(__dirname, 'fixtures', 'prod-module'),
})
it('should have CSS for page', async () => {
const browser = await next.browser('/')
const currentColor = await browser.eval(
`window.getComputedStyle(document.querySelector('#verify-red')).color`
)
expect(currentColor).toMatchInlineSnapshot(`"rgb(255, 0, 0)"`)
})
})
describe('Valid CSS Module Usage from within node_modules', () => {
const { next, isTurbopack } = nextTestSetup({
files: join(__dirname, 'fixtures', 'nm-module'),
})
it(`should've prerendered with relevant data`, async () => {
const $ = await next.render$('/')
const cssPreload = $('#nm-div')
if (isTurbopack) {
expect(cssPreload.text()).toMatchInlineSnapshot(
`"{"message":"Why hello there","default":{"message":"Why hello there"}} {"redText":"PIKFPa_redText","default":{"redText":"PIKFPa_redText"}}"`
)
} else {
expect(cssPreload.text()).toMatchInlineSnapshot(
`"{"message":"Why hello there"} {"redText":"example_redText__0ctGB"}"`
)
}
})
it(`should've emitted a single CSS file`, async () => {
const $ = await next.render$('/')
const cssSheet = $('link[rel="stylesheet"]')
expect(cssSheet.length).toBe(1)
const stylesheet = cssSheet.attr('href')!
const cssContent = await next.fetch(stylesheet).then((res) => res.text())
if (isTurbopack) {
expect(
cssContent.replace(/\/\*.*?\*\//g, '').trim()
).toMatchInlineSnapshot(`".PIKFPa_redText{color:red}"`)
} else {
expect(
cssContent.replace(/\/\*.*?\*\//g, '').trim()
).toMatchInlineSnapshot(`".example_redText__0ctGB{color:red}"`)
}
})
})
// Disabled with Turbopack because `composes` from `.css` files in `.module.css` files is not supported.
;(process.env.IS_TURBOPACK_TEST ? describe.skip : describe)(
'Valid Nested CSS Module Usage from within node_modules',
() => {
const { next } = nextTestSetup({
files: join(__dirname, 'fixtures', 'nm-module-nested'),
})
it(`should've prerendered with relevant data`, async () => {
const $ = await next.render$('/')
const cssPreload = $('#nm-div')
expect(cssPreload.text()).toMatchInlineSnapshot(
`"{"message":"Why hello there"} {"subClass":"example_subClass__m6Tyy other_className__OA8dV"}"`
)
})
it(`should've emitted a single CSS file`, async () => {
const $ = await next.render$('/')
const cssSheet = $('link[rel="stylesheet"]')
expect(cssSheet.length).toBe(1)
const stylesheet = cssSheet.attr('href')!
const cssContent = await next.fetch(stylesheet).then((res) => res.text())
expect(
cssContent.replace(/\/\*.*?\*\//g, '').trim()
).toMatchInlineSnapshot(
`".other2_other2__dYPgz{color:red}.other3_other3__7hgUE{color:violet}.other_className__OA8dV{background:red;color:yellow}.example_subClass__m6Tyy{background:blue}"`
)
})
}
)
describe('CSS Module Composes Usage (Basic)', () => {
const { next, isTurbopack } = nextTestSetup({
files: join(__dirname, 'fixtures', 'composes-basic'),
})
it(`should've emitted a single CSS file`, async () => {
const $ = await next.render$('/')
const cssSheet = $('link[rel="stylesheet"]')
expect(cssSheet.length).toBe(1)
const stylesheet = cssSheet.attr('href')!
const cssContent = await next.fetch(stylesheet).then((res) => res.text())
if (isTurbopack) {
expect(
cssContent.replace(/\/\*.*?\*\//g, '').trim()
).toMatchInlineSnapshot(
`".KWKY6G_className{color:#ff0;background:red}.KWKY6G_subClass{background:#00f;}"`
)
} else {
expect(
cssContent.replace(/\/\*.*?\*\//g, '').trim()
).toMatchInlineSnapshot(
`".index_className__jjcZ1{background:red;color:yellow}.index_subClass__eDzaW{background:blue}"`
)
}
})
})
;(process.env.IS_TURBOPACK_TEST ? describe.skip : describe)(
'CSS Module Composes Usage (External)',
() => {
const { next } = nextTestSetup({
files: join(__dirname, 'fixtures', 'composes-external'),
})
it(`should've emitted a single CSS file`, async () => {
const $ = await next.render$('/')
const cssSheet = $('link[rel="stylesheet"]')
expect(cssSheet.length).toBe(1)
const stylesheet = cssSheet.attr('href')!
const cssContent = await next.fetch(stylesheet).then((res) => res.text())
expect(
cssContent.replace(/\/\*.*?\*\//g, '').trim()
).toMatchInlineSnapshot(
`".other_className__eZV4M{background:red;color:yellow}.index_subClass__eDzaW{background:blue}"`
)
})
}
)
describe('Dynamic Route CSS Module Usage', () => {
const { next, isTurbopack } = nextTestSetup({
files: join(__dirname, 'fixtures', 'dynamic-route-module'),
})
it('should apply styles correctly', async () => {
const browser = await next.browser('/post-1')
const background = await browser
.elementByCss('#my-div')
.getComputedCss('background-color')
expect(background).toMatch(/rgb(a|)\(255, 0, 0/)
})
it(`should've emitted a single CSS file`, async () => {
const $ = await next.render$('/post-1')
const cssSheet = $('link[rel="stylesheet"]')
expect(cssSheet.length).toBe(1)
const stylesheet = cssSheet.attr('href')!
const cssContent = await next.fetch(stylesheet).then((res) => res.text())
if (isTurbopack) {
expect(
cssContent.replace(/\/\*.*?\*\//g, '').trim()
).toMatchInlineSnapshot(`"._9fTRMq_home{background:red}"`)
} else {
expect(
cssContent.replace(/\/\*.*?\*\//g, '').trim()
).toMatchInlineSnapshot(`"._post__home__yRmHz{background:#f00}"`)
}
})
})
describe('Catch-all Route CSS Module Usage', () => {
const { next, isTurbopack } = nextTestSetup({
files: join(__dirname, 'fixtures', 'catch-all-module'),
})
it('should apply styles correctly', async () => {
const browser = await next.browser('/post-1')
const bg = await browser
.elementByCss('#my-div')
.getComputedCss('background-color')
expect(bg).toMatch(/rgb(a|)\(255, 0, 0/)
const fg = await browser.elementByCss('#my-div').getComputedCss('color')
expect(fg).toMatch(/rgb(a|)\(0, 128, 0/)
})
it(`should've emitted a single CSS file`, async () => {
const $ = await next.render$('/post-1')
const cssSheet = $('link[rel="stylesheet"]')
expect(cssSheet.length).toBe(1)
const stylesheet = cssSheet.attr('href')!
const cssContent = await next.fetch(stylesheet).then((res) => res.text())
if (isTurbopack) {
expect(cssContent.replace(/\/\*.*?\*\/\n?/g, '').trim())
.toMatchInlineSnapshot(`
".vy7_gG_home{background:red}
.c9Qeqa_home{color:green}"
`)
} else {
expect(
cssContent.replace(/\/\*.*?\*\//g, '').trim()
).toMatchInlineSnapshot(
`".___post__home__e4zfx{background:#f00}.__55css_home__r8Rnq{color:green}"`
)
}
})
})
describe('cssmodules-pure-no-check usage', () => {
const { next } = nextTestSetup({
files: join(__dirname, 'fixtures', 'cssmodules-pure-no-check'),
})
it('should apply styles correctly', async () => {
const browser = await next.browser('/')
const elementWithGlobalStyles = await browser
.elementByCss('#my-div')
.getComputedCss('font-weight')
expect(elementWithGlobalStyles).toBe('700')
})
it(`should've emitted a CSS file`, async () => {
const $ = await next.render$('/')
const cssSheet = $('link[rel="stylesheet"]')
expect(cssSheet.length).toBe(1)
const stylesheet = cssSheet[0].attribs['href']
const cssContent = await next.fetch(stylesheet).then((res) => res.text())
const cssCode = cssContent.replace(/\/\*.*?\*\//g, '').trim()
expect(cssCode).toInclude(`.global{font-weight:700}`)
expect(cssCode).toInclude(
`::view-transition-old(root){animation-duration:.3s}`
)
})
})