import { candidate, css, fetchStyles, json, retryAssertion, test, ts, txt } from '../utils' const WORKSPACE = { 'package.json': json` { "type": "module", "dependencies": { "@react-router/dev": "^7", "@react-router/node": "^7", "@react-router/serve": "^7", "@tailwindcss/vite": "workspace:^", "@types/node": "^20", "@types/react-dom": "^19", "@types/react": "^19", "isbot": "^5", "react-dom": "^19", "react-router": "^7", "react": "^19", "tailwindcss": "workspace:^", "vite": "^5" } } `, 'react-router.config.ts': ts` import type { Config } from '@react-router/dev/config' export default { ssr: true } satisfies Config `, 'vite.config.ts': ts` import { defineConfig } from 'vite' import { reactRouter } from '@react-router/dev/vite' import tailwindcss from '@tailwindcss/vite' export default defineConfig({ plugins: [tailwindcss(), reactRouter()], }) `, 'app/routes/home.tsx': ts` export default function Home() { return

Welcome to React Router

} `, 'app/app.css': css`@import 'tailwindcss';`, 'app/routes.ts': ts` import { type RouteConfig, index } from '@react-router/dev/routes' export default [index('routes/home.tsx')] satisfies RouteConfig `, 'app/root.tsx': ts` import { Links, Meta, Outlet, Scripts, ScrollRestoration } from 'react-router' import './app.css' export function Layout({ children }: { children: React.ReactNode }) { return ( {children} ) } export default function App() { return } `, } test('dev mode', { fs: WORKSPACE }, async ({ fs, spawn, expect }) => { let process = await spawn('pnpm react-router dev') let url = '' await process.onStdout((m) => { let match = /Local:\s*(http.*)\//.exec(m) if (match) url = match[1] return Boolean(url) }) await retryAssertion(async () => { let css = await fetchStyles(url) expect(css).toContain(candidate`font-bold`) }) await retryAssertion(async () => { await fs.write( 'app/routes/home.tsx', ts` export default function Home() { return

Welcome to React Router

} `, ) let css = await fetchStyles(url) expect(css).toContain(candidate`underline`) expect(css).toContain(candidate`font-bold`) }) }) test('build mode', { fs: WORKSPACE }, async ({ spawn, exec, expect }) => { await exec('pnpm react-router build') let process = await spawn('pnpm react-router-serve ./build/server/index.js') let url = '' await process.onStdout((m) => { let match = /\[react-router-serve\]\s*(http.*)\ \/?/.exec(m) if (match) url = match[1] return url != '' }) await retryAssertion(async () => { let css = await fetchStyles(url) expect(css).toContain(candidate`font-bold`) }) }) test( 'build mode using ?url stylesheet imports should only build one stylesheet (requires `file-system` scanner)', { fs: { ...WORKSPACE, 'app/root.tsx': ts` import { Links, Meta, Outlet, Scripts, ScrollRestoration } from 'react-router' import styles from './app.css?url' export const links: Route.LinksFunction = () => [{ rel: 'stylesheet', href: styles }] export function Layout({ children }: { children: React.ReactNode }) { return ( {children} ) } export default function App() { return } `, 'vite.config.ts': ts` import { defineConfig } from 'vite' import { reactRouter } from '@react-router/dev/vite' import tailwindcss from '@tailwindcss/vite' export default defineConfig({ plugins: [tailwindcss(), reactRouter()], }) `, '.gitignore': txt` node_modules/ build/ `, }, }, async ({ fs, exec, expect }) => { await exec('pnpm react-router build') let files = await fs.glob('build/client/assets/**/*.css') expect(files).toHaveLength(1) let [filename] = files[0] await fs.expectFileToContain(filename, [candidate`font-bold`]) }, )