import { describe } from 'vitest'
import { candidate, css, fetchStyles, js, json, retryAssertion, test } from '../utils'
test(
'production build',
{
fs: {
'package.json': json`
{
"dependencies": {
"react": "^18",
"react-dom": "^18",
"next": "^14"
},
"devDependencies": {
"@tailwindcss/postcss": "workspace:^",
"tailwindcss": "workspace:^"
}
}
`,
'postcss.config.mjs': js`
export default {
plugins: {
'@tailwindcss/postcss': {},
},
}
`,
'next.config.mjs': js`export default {}`,
'app/layout.js': js`
import './globals.css'
export default function RootLayout({ children }) {
return (
<html>
<body>{children}</body>
</html>
)
}
`,
'app/page.js': js`
import styles from './page.module.css'
export default function Page() {
return (
<h1 className={styles.heading + ' text-3xl font-bold underline'}>Hello, Next.js!</h1>
)
}
`,
'app/page.module.css': css`
@reference './globals.css';
.heading {
@apply text-red-500 animate-ping;
}
`,
'app/globals.css': css`
@reference 'tailwindcss/theme';
@import 'tailwindcss/utilities';
`,
},
},
async ({ fs, exec, expect }) => {
await exec('pnpm next build')
let files = await fs.glob('.next/static/css/**/*.css')
expect(files).toHaveLength(2)
let globalCss: string | null = null
let moduleCss: string | null = null
for (let [filename, content] of files) {
if (content.includes('@keyframes page_ping')) moduleCss = filename
else globalCss = filename
}
await fs.expectFileToContain(globalCss!, [
candidate`underline`,
candidate`font-bold`,
candidate`text-3xl`,
])
await fs.expectFileToContain(moduleCss!, [
'color:var(--color-red-500,oklch(.637 .237 25.331)',
'animation:var(--animate-ping,ping 1s cubic-bezier(0,0,.2,1) infinite)',
/@keyframes page_ping.*{75%,to{transform:scale\(2\);opacity:0}/,
])
},
)
describe.each(['turbo', 'webpack'])('%s', (bundler) => {
test(
'dev mode',
{
fs: {
'package.json': json`
{
"dependencies": {
"react": "^18",
"react-dom": "^18",
"next": "^14"
},
"devDependencies": {
"@tailwindcss/postcss": "workspace:^",
"tailwindcss": "workspace:^"
}
}
`,
'postcss.config.mjs': js`
export default {
plugins: {
'@tailwindcss/postcss': {},
},
}
`,
'next.config.mjs': js`export default {}`,
'app/layout.js': js`
import './globals.css'
export default function RootLayout({ children }) {
return (
<html>
<body>{children}</body>
</html>
)
}
`,
'app/page.js': js`
import styles from './page.module.css'
export default function Page() {
return <h1 className={styles.heading + ' underline'}>Hello, Next.js!</h1>
}
`,
'app/page.module.css': css`
@reference './globals.css';
.heading {
@apply text-red-500 animate-ping content-['module'];
}
`,
'app/globals.css': css`
@reference 'tailwindcss/theme';
@import 'tailwindcss/utilities';
`,
},
},
async ({ fs, spawn, expect }) => {
let process = await spawn(`pnpm next dev ${bundler === 'turbo' ? '--turbo' : ''}`)
let url = ''
await process.onStdout((m) => {
let match = /Local:\s*(http.*)/.exec(m)
if (match) url = match[1]
return Boolean(url)
})
await process.onStdout((m) => m.includes('Ready in'))
await retryAssertion(async () => {
let css = await fetchStyles(url)
expect(css).toContain(candidate`underline`)
expect(css).toContain('content: var(--tw-content)')
expect(css).toContain('@keyframes')
})
await fs.write(
'app/page.js',
js`
import styles from './page.module.css'
export default function Page() {
return <h1 className={styles.heading + ' underline bg-red-500'}>Hello, Next.js!</h1>
}
`,
)
await process.onStdout((m) => m.includes('Compiled in'))
await retryAssertion(async () => {
let css = await fetchStyles(url)
expect(css).toContain(candidate`underline`)
expect(css).toContain(candidate`bg-red-500`)
expect(css).toContain('content: var(--tw-content)')
expect(css).toContain('@keyframes')
})
},
)
})
test(
'should scan dynamic route segments',
{
fs: {
'package.json': json`
{
"dependencies": {
"react": "^18",
"react-dom": "^18",
"next": "^14"
},
"devDependencies": {
"@tailwindcss/postcss": "workspace:^",
"tailwindcss": "workspace:^"
}
}
`,
'postcss.config.mjs': js`
export default {
plugins: {
'@tailwindcss/postcss': {},
},
}
`,
'next.config.mjs': js`export default {}`,
'app/a/[slug]/page.js': js`
export default function Page() {
return <h1 className="content-['[slug]']">Hello, Next.js!</h1>
}
`,
'app/b/[...slug]/page.js': js`
export default function Page() {
return <h1 className="content-['[...slug]']">Hello, Next.js!</h1>
}
`,
'app/c/[[...slug]]/page.js': js`
export default function Page() {
return <h1 className="content-['[[...slug]]']">Hello, Next.js!</h1>
}
`,
'app/d/(theme)/page.js': js`
export default function Page() {
return <h1 className="content-['(theme)']">Hello, Next.js!</h1>
}
`,
'app/layout.js': js`
import './globals.css'
export default function RootLayout({ children }) {
return (
<html>
<body>{children}</body>
</html>
)
}
`,
'app/globals.css': css`
@import 'tailwindcss/utilities' source(none);
@source './**/*.{js,ts,jsx,tsx,mdx}';
`,
},
},
async ({ fs, exec, expect }) => {
await exec('pnpm next build')
let files = await fs.glob('.next/static/css/**/*.css')
expect(files).toHaveLength(1)
let [filename] = files[0]
await fs.expectFileToContain(filename, [
candidate`content-['[slug]']`,
candidate`content-['[...slug]']`,
candidate`content-['[[...slug]]']`,
candidate`content-['(theme)']`,
])
},
)