mirror of
https://github.com/tailwindlabs/tailwindcss.git
synced 2026-02-01 17:26:34 +00:00
This PR replaces the default spacing scale (`--spacing-*`) with a
generative system based on a default spacing _unit_.
Instead of the default theme containing values like `--spacing-4`,
`--spacing-6`, `--spacing-8`, etc., instead we just define a single
`--spacing` value:
```css
@theme {
--spacing: 0.25rem;
}
```
Utilities like `px-4` are derived from this unit by multiplying it by
the value in the utility (4 in this case):
```css
.px-4 {
padding-inline: calc(var(--spacing) * 4);
}
```
The biggest consequence of this change is that every value is available
now, rather than just the explicitly configured values.
This means utilities like `px-42` will work now, whereas prior to this
PR only `px-40` and `px-44` were valid utilities. I personally found it
very difficult to know which values actually existed at the higher end
of the scale without IntelliSense, and in practice even when working
with a skilled designer like [Steve](https://x.com/steveschoger) who
helped design Tailwind's default spacing scale, I'd very often need to
break out of it to implement a design, and trying to round to a value
that was in the scale made the design worse, not better.
This PR allows you to use any whole number, as well as decimal numbers
that are multiples of `0.25` to ensure classes like `px-1.5` continue to
work. While this means you can now technically do things like
`pt-97.25`, I think the presence of the fractional value will be enough
of a signal to developers that they are doing something a little
unusual, and they can use their judgment as to whether they are making
the right decision or not.
I'll update this PR with a lot more detail when I have a chance, as
there are a few other things to explain like:
- Unifying all of the values for
width/min-width/max-width/height/min-height/max-height utilities
- Deriving numeric line-height values from the spacing multiplier
instead of a separate line-height scale
- Using `--spacing: initial` to disable the multiplier
- How you can still use an explicit spacing scale and ignore this change
- How we plan to use IntelliSense to surface a more curated set of
spacing values even if smaller increments work when you type them
explicitly
---------
Co-authored-by: Adam Wathan <4323180+adamwathan@users.noreply.github.com>
1806 lines
46 KiB
TypeScript
1806 lines
46 KiB
TypeScript
import { expect } from 'vitest'
|
|
import { candidate, css, html, js, json, test } from '../utils'
|
|
|
|
test(
|
|
'error when no CSS file with @tailwind is used',
|
|
{
|
|
fs: {
|
|
'package.json': json`
|
|
{
|
|
"dependencies": {
|
|
"@tailwindcss/upgrade": "workspace:^"
|
|
},
|
|
"devDependencies": {
|
|
"@tailwindcss/cli": "workspace:^"
|
|
}
|
|
}
|
|
`,
|
|
'tailwind.config.js': js`
|
|
/** @type {import('tailwindcss').Config} */
|
|
module.exports = {
|
|
content: ['./src/**/*.{html,js}'],
|
|
}
|
|
`,
|
|
'src/index.html': html`
|
|
<h1>🤠👋</h1>
|
|
<div class="!flex"></div>
|
|
`,
|
|
'src/fonts.css': css`/* Unrelated CSS file */`,
|
|
},
|
|
},
|
|
async ({ fs, exec }) => {
|
|
let output = await exec('npx @tailwindcss/upgrade')
|
|
expect(output).toContain('Cannot find any CSS files that reference Tailwind CSS.')
|
|
|
|
// Files should not be modified
|
|
expect(await fs.dumpFiles('./src/**/*.{css,html}')).toMatchInlineSnapshot(`
|
|
"
|
|
--- ./src/index.html ---
|
|
<h1>🤠👋</h1>
|
|
<div class="!flex"></div>
|
|
|
|
--- ./src/fonts.css ---
|
|
/* Unrelated CSS file */
|
|
"
|
|
`)
|
|
},
|
|
)
|
|
|
|
test(
|
|
`upgrades a v3 project to v4`,
|
|
{
|
|
fs: {
|
|
'package.json': json`
|
|
{
|
|
"dependencies": {
|
|
"@tailwindcss/upgrade": "workspace:^"
|
|
},
|
|
"devDependencies": {
|
|
"@tailwindcss/cli": "workspace:^"
|
|
}
|
|
}
|
|
`,
|
|
'tailwind.config.js': js`
|
|
/** @type {import('tailwindcss').Config} */
|
|
module.exports = {
|
|
content: ['./src/**/*.{html,js}'],
|
|
}
|
|
`,
|
|
'src/index.html': html`
|
|
<h1>🤠👋</h1>
|
|
<div
|
|
class="!flex sm:!block bg-gradient-to-t bg-[--my-red] max-w-screen-md ml-[theme(screens.md)]"
|
|
></div>
|
|
`,
|
|
'src/input.css': css`
|
|
@tailwind base;
|
|
@tailwind components;
|
|
@tailwind utilities;
|
|
|
|
@variants hover, focus {
|
|
.foo {
|
|
color: red;
|
|
}
|
|
}
|
|
`,
|
|
},
|
|
},
|
|
async ({ exec, fs }) => {
|
|
await exec('npx @tailwindcss/upgrade')
|
|
|
|
expect(await fs.dumpFiles('./src/**/*.{css,html}')).toMatchInlineSnapshot(`
|
|
"
|
|
--- ./src/index.html ---
|
|
<h1>🤠👋</h1>
|
|
<div
|
|
class="flex! sm:block! bg-linear-to-t bg-[var(--my-red)] max-w-[var(--breakpoint-md)] ml-[var(--breakpoint-md)]"
|
|
></div>
|
|
|
|
--- ./src/input.css ---
|
|
@import 'tailwindcss';
|
|
|
|
/*
|
|
The default border color has changed to \`currentColor\` in Tailwind CSS v4,
|
|
so we've added these compatibility styles to make sure everything still
|
|
looks the same as it did with Tailwind CSS v3.
|
|
|
|
If we ever want to remove these styles, we need to add an explicit border
|
|
color utility to any element that depends on these defaults.
|
|
*/
|
|
@layer base {
|
|
*,
|
|
::after,
|
|
::before,
|
|
::backdrop,
|
|
::file-selector-button {
|
|
border-color: var(--color-gray-200, currentColor);
|
|
}
|
|
}
|
|
|
|
/*
|
|
Form elements have a 1px border by default in Tailwind CSS v4, so we've
|
|
added these compatibility styles to make sure everything still looks the
|
|
same as it did with Tailwind CSS v3.
|
|
|
|
If we ever want to remove these styles, we need to add \`border-0\` to
|
|
any form elements that shouldn't have a border.
|
|
*/
|
|
@layer base {
|
|
input:where(:not([type='button'], [type='reset'], [type='submit'])),
|
|
select,
|
|
textarea {
|
|
border-width: 0;
|
|
}
|
|
}
|
|
|
|
@utility foo {
|
|
color: red;
|
|
}
|
|
"
|
|
`)
|
|
|
|
let packageJsonContent = await fs.read('package.json')
|
|
let packageJson = JSON.parse(packageJsonContent)
|
|
expect(packageJson.dependencies).toMatchObject({
|
|
tailwindcss: expect.stringContaining('4.0.0'),
|
|
})
|
|
|
|
// Ensure the v4 project compiles correctly
|
|
await exec('npx tailwindcss --input src/input.css --output dist/out.css')
|
|
|
|
await fs.expectFileToContain('dist/out.css', [
|
|
candidate`flex!`,
|
|
candidate`sm:block!`,
|
|
candidate`bg-linear-to-t`,
|
|
candidate`bg-[var(--my-red)]`,
|
|
candidate`max-w-[var(--breakpoint-md)]`,
|
|
candidate`ml-[var(--breakpoint-md)`,
|
|
])
|
|
},
|
|
)
|
|
|
|
test(
|
|
`upgrades a v3 project with prefixes to v4`,
|
|
{
|
|
fs: {
|
|
'package.json': json`
|
|
{
|
|
"dependencies": {
|
|
"@tailwindcss/upgrade": "workspace:^"
|
|
}
|
|
}
|
|
`,
|
|
'tailwind.config.js': js`
|
|
/** @type {import('tailwindcss').Config} */
|
|
module.exports = {
|
|
content: ['./src/**/*.{html,js}'],
|
|
prefix: 'tw__',
|
|
}
|
|
`,
|
|
'src/index.html': html`
|
|
<div
|
|
class="!tw__flex sm:!tw__block tw__bg-gradient-to-t flex [color:red]"
|
|
></div>
|
|
`,
|
|
'src/input.css': css`
|
|
@tailwind base;
|
|
@tailwind components;
|
|
@tailwind utilities;
|
|
|
|
.btn {
|
|
@apply !tw__rounded-md tw__px-2 tw__py-1 tw__bg-blue-500 tw__text-white;
|
|
}
|
|
`,
|
|
},
|
|
},
|
|
async ({ exec, fs }) => {
|
|
await exec('npx @tailwindcss/upgrade')
|
|
|
|
expect(await fs.dumpFiles('./src/**/*.{css,html}')).toMatchInlineSnapshot(`
|
|
"
|
|
--- ./src/index.html ---
|
|
<div
|
|
class="tw:flex! tw:sm:block! tw:bg-linear-to-t flex tw:[color:red]"
|
|
></div>
|
|
|
|
--- ./src/input.css ---
|
|
@import 'tailwindcss' prefix(tw);
|
|
|
|
/*
|
|
The default border color has changed to \`currentColor\` in Tailwind CSS v4,
|
|
so we've added these compatibility styles to make sure everything still
|
|
looks the same as it did with Tailwind CSS v3.
|
|
|
|
If we ever want to remove these styles, we need to add an explicit border
|
|
color utility to any element that depends on these defaults.
|
|
*/
|
|
@layer base {
|
|
*,
|
|
::after,
|
|
::before,
|
|
::backdrop,
|
|
::file-selector-button {
|
|
border-color: var(--color-gray-200, currentColor);
|
|
}
|
|
}
|
|
|
|
/*
|
|
Form elements have a 1px border by default in Tailwind CSS v4, so we've
|
|
added these compatibility styles to make sure everything still looks the
|
|
same as it did with Tailwind CSS v3.
|
|
|
|
If we ever want to remove these styles, we need to add \`border-0\` to
|
|
any form elements that shouldn't have a border.
|
|
*/
|
|
@layer base {
|
|
input:where(:not([type='button'], [type='reset'], [type='submit'])),
|
|
select,
|
|
textarea {
|
|
border-width: 0;
|
|
}
|
|
}
|
|
|
|
.btn {
|
|
@apply tw:rounded-md! tw:px-2 tw:py-1 tw:bg-blue-500 tw:text-white;
|
|
}
|
|
"
|
|
`)
|
|
},
|
|
)
|
|
|
|
test(
|
|
'migrate @apply',
|
|
{
|
|
fs: {
|
|
'package.json': json`
|
|
{
|
|
"dependencies": {
|
|
"tailwindcss": "workspace:^",
|
|
"@tailwindcss/upgrade": "workspace:^"
|
|
}
|
|
}
|
|
`,
|
|
'tailwind.config.js': js`module.exports = {}`,
|
|
'src/index.css': css`
|
|
@import 'tailwindcss';
|
|
|
|
.a {
|
|
@apply flex;
|
|
}
|
|
|
|
.b {
|
|
@apply !flex;
|
|
}
|
|
|
|
.c {
|
|
@apply !flex flex-col! items-center !important;
|
|
}
|
|
`,
|
|
},
|
|
},
|
|
async ({ fs, exec }) => {
|
|
await exec('npx @tailwindcss/upgrade')
|
|
|
|
expect(await fs.dumpFiles('./src/**/*.css')).toMatchInlineSnapshot(`
|
|
"
|
|
--- ./src/index.css ---
|
|
@import 'tailwindcss';
|
|
|
|
/*
|
|
The default border color has changed to \`currentColor\` in Tailwind CSS v4,
|
|
so we've added these compatibility styles to make sure everything still
|
|
looks the same as it did with Tailwind CSS v3.
|
|
|
|
If we ever want to remove these styles, we need to add an explicit border
|
|
color utility to any element that depends on these defaults.
|
|
*/
|
|
@layer base {
|
|
*,
|
|
::after,
|
|
::before,
|
|
::backdrop,
|
|
::file-selector-button {
|
|
border-color: var(--color-gray-200, currentColor);
|
|
}
|
|
}
|
|
|
|
/*
|
|
Form elements have a 1px border by default in Tailwind CSS v4, so we've
|
|
added these compatibility styles to make sure everything still looks the
|
|
same as it did with Tailwind CSS v3.
|
|
|
|
If we ever want to remove these styles, we need to add \`border-0\` to
|
|
any form elements that shouldn't have a border.
|
|
*/
|
|
@layer base {
|
|
input:where(:not([type='button'], [type='reset'], [type='submit'])),
|
|
select,
|
|
textarea {
|
|
border-width: 0;
|
|
}
|
|
}
|
|
|
|
.a {
|
|
@apply flex;
|
|
}
|
|
|
|
.b {
|
|
@apply flex!;
|
|
}
|
|
|
|
.c {
|
|
@apply flex! flex-col! items-center!;
|
|
}
|
|
"
|
|
`)
|
|
},
|
|
)
|
|
|
|
test(
|
|
'migrate `@tailwind` directives',
|
|
{
|
|
fs: {
|
|
'package.json': json`
|
|
{
|
|
"dependencies": {
|
|
"tailwindcss": "workspace:^",
|
|
"@tailwindcss/upgrade": "workspace:^"
|
|
}
|
|
}
|
|
`,
|
|
'tailwind.config.js': js`module.exports = {}`,
|
|
'src/index.css': css`
|
|
@tailwind base;
|
|
|
|
html {
|
|
color: #333;
|
|
}
|
|
|
|
@tailwind components;
|
|
|
|
.btn {
|
|
color: red;
|
|
}
|
|
|
|
@tailwind utilities;
|
|
`,
|
|
},
|
|
},
|
|
async ({ fs, exec }) => {
|
|
await exec('npx @tailwindcss/upgrade')
|
|
|
|
expect(await fs.dumpFiles('./src/**/*.css')).toMatchInlineSnapshot(`
|
|
"
|
|
--- ./src/index.css ---
|
|
@import 'tailwindcss';
|
|
|
|
/*
|
|
The default border color has changed to \`currentColor\` in Tailwind CSS v4,
|
|
so we've added these compatibility styles to make sure everything still
|
|
looks the same as it did with Tailwind CSS v3.
|
|
|
|
If we ever want to remove these styles, we need to add an explicit border
|
|
color utility to any element that depends on these defaults.
|
|
*/
|
|
|
|
@layer base {
|
|
*,
|
|
::after,
|
|
::before,
|
|
::backdrop,
|
|
::file-selector-button {
|
|
border-color: var(--color-gray-200, currentColor);
|
|
}
|
|
}
|
|
|
|
/*
|
|
Form elements have a 1px border by default in Tailwind CSS v4, so we've
|
|
added these compatibility styles to make sure everything still looks the
|
|
same as it did with Tailwind CSS v3.
|
|
|
|
If we ever want to remove these styles, we need to add \`border-0\` to
|
|
any form elements that shouldn't have a border.
|
|
*/
|
|
@layer base {
|
|
input:where(:not([type='button'], [type='reset'], [type='submit'])),
|
|
select,
|
|
textarea {
|
|
border-width: 0;
|
|
}
|
|
}
|
|
|
|
@layer base {
|
|
html {
|
|
color: #333;
|
|
}
|
|
}
|
|
|
|
@layer components {
|
|
.btn {
|
|
color: red;
|
|
}
|
|
}
|
|
"
|
|
`)
|
|
},
|
|
)
|
|
|
|
test(
|
|
'migrate `@layer utilities` and `@layer components`',
|
|
{
|
|
fs: {
|
|
'package.json': json`
|
|
{
|
|
"dependencies": {
|
|
"tailwindcss": "workspace:^",
|
|
"@tailwindcss/upgrade": "workspace:^"
|
|
}
|
|
}
|
|
`,
|
|
'tailwind.config.js': js`module.exports = {}`,
|
|
'src/index.css': css`
|
|
@import 'tailwindcss';
|
|
|
|
@layer components {
|
|
.btn {
|
|
@apply rounded-md px-2 py-1 bg-blue-500 text-white;
|
|
}
|
|
}
|
|
|
|
@layer utilities {
|
|
.no-scrollbar::-webkit-scrollbar {
|
|
display: none;
|
|
}
|
|
|
|
.no-scrollbar {
|
|
-ms-overflow-style: none;
|
|
scrollbar-width: none;
|
|
}
|
|
}
|
|
`,
|
|
},
|
|
},
|
|
async ({ fs, exec }) => {
|
|
await exec('npx @tailwindcss/upgrade')
|
|
|
|
expect(await fs.dumpFiles('./src/**/*.css')).toMatchInlineSnapshot(`
|
|
"
|
|
--- ./src/index.css ---
|
|
@import 'tailwindcss';
|
|
|
|
/*
|
|
The default border color has changed to \`currentColor\` in Tailwind CSS v4,
|
|
so we've added these compatibility styles to make sure everything still
|
|
looks the same as it did with Tailwind CSS v3.
|
|
|
|
If we ever want to remove these styles, we need to add an explicit border
|
|
color utility to any element that depends on these defaults.
|
|
*/
|
|
@layer base {
|
|
*,
|
|
::after,
|
|
::before,
|
|
::backdrop,
|
|
::file-selector-button {
|
|
border-color: var(--color-gray-200, currentColor);
|
|
}
|
|
}
|
|
|
|
/*
|
|
Form elements have a 1px border by default in Tailwind CSS v4, so we've
|
|
added these compatibility styles to make sure everything still looks the
|
|
same as it did with Tailwind CSS v3.
|
|
|
|
If we ever want to remove these styles, we need to add \`border-0\` to
|
|
any form elements that shouldn't have a border.
|
|
*/
|
|
@layer base {
|
|
input:where(:not([type='button'], [type='reset'], [type='submit'])),
|
|
select,
|
|
textarea {
|
|
border-width: 0;
|
|
}
|
|
}
|
|
|
|
@utility btn {
|
|
@apply rounded-md px-2 py-1 bg-blue-500 text-white;
|
|
}
|
|
|
|
@utility no-scrollbar {
|
|
&::-webkit-scrollbar {
|
|
display: none;
|
|
}
|
|
-ms-overflow-style: none;
|
|
scrollbar-width: none;
|
|
}
|
|
"
|
|
`)
|
|
},
|
|
)
|
|
|
|
test(
|
|
'migrates a simple postcss setup',
|
|
{
|
|
fs: {
|
|
'package.json': json`
|
|
{
|
|
"dependencies": {
|
|
"postcss": "^8",
|
|
"postcss-cli": "^10",
|
|
"postcss-import": "^16",
|
|
"autoprefixer": "^10",
|
|
"tailwindcss": "^3",
|
|
"@tailwindcss/upgrade": "workspace:^"
|
|
}
|
|
}
|
|
`,
|
|
'tailwind.config.js': js`
|
|
/** @type {import('tailwindcss').Config} */
|
|
module.exports = {
|
|
content: ['./src/**/*.{html,js}'],
|
|
}
|
|
`,
|
|
'postcss.config.js': js`
|
|
module.exports = {
|
|
plugins: {
|
|
'postcss-import': {},
|
|
'tailwindcss/nesting': 'postcss-nesting',
|
|
tailwindcss: {},
|
|
autoprefixer: {},
|
|
},
|
|
}
|
|
`,
|
|
'src/index.html': html`
|
|
<div class="bg-[--my-red]"></div>
|
|
`,
|
|
'src/index.css': css`
|
|
@tailwind base;
|
|
@tailwind components;
|
|
@tailwind utilities;
|
|
`,
|
|
},
|
|
},
|
|
async ({ fs, exec }) => {
|
|
await exec('npx @tailwindcss/upgrade')
|
|
|
|
await fs.expectFileToContain(
|
|
'postcss.config.js',
|
|
js`
|
|
module.exports = {
|
|
plugins: {
|
|
'@tailwindcss/postcss': {},
|
|
},
|
|
}
|
|
`,
|
|
)
|
|
await fs.expectFileToContain('src/index.css', css`@import 'tailwindcss';`)
|
|
await fs.expectFileToContain(
|
|
'src/index.html',
|
|
// prettier-ignore
|
|
js`
|
|
<div class="bg-[var(--my-red)]"></div>
|
|
`,
|
|
)
|
|
|
|
let packageJsonContent = await fs.read('package.json')
|
|
let packageJson = JSON.parse(packageJsonContent)
|
|
expect(packageJson.dependencies).toMatchObject({
|
|
tailwindcss: expect.stringContaining('4.0.0'),
|
|
})
|
|
expect(packageJson.dependencies).not.toHaveProperty('autoprefixer')
|
|
expect(packageJson.dependencies).not.toHaveProperty('postcss-import')
|
|
expect(packageJson.dependencies).toMatchObject({
|
|
'@tailwindcss/postcss': expect.stringContaining('4.0.0'),
|
|
})
|
|
},
|
|
)
|
|
|
|
test(
|
|
'`@tailwindcss/postcss` should be installed in dependencies when `tailwindcss` exists in dependencies',
|
|
{
|
|
fs: {
|
|
'package.json': json`
|
|
{
|
|
"dependencies": {
|
|
"postcss": "^8",
|
|
"tailwindcss": "^3",
|
|
"@tailwindcss/upgrade": "workspace:^"
|
|
}
|
|
}
|
|
`,
|
|
'tailwind.config.js': js`
|
|
/** @type {import('tailwindcss').Config} */
|
|
module.exports = {
|
|
content: ['./src/**/*.{html,js}'],
|
|
}
|
|
`,
|
|
'postcss.config.js': js`
|
|
module.exports = {
|
|
plugins: {
|
|
tailwindcss: {},
|
|
},
|
|
}
|
|
`,
|
|
'src/index.html': html`
|
|
<div class="bg-[--my-red]"></div>
|
|
`,
|
|
'src/index.css': css`
|
|
@tailwind base;
|
|
@tailwind components;
|
|
@tailwind utilities;
|
|
`,
|
|
},
|
|
},
|
|
async ({ fs, exec }) => {
|
|
await exec('npx @tailwindcss/upgrade')
|
|
|
|
let packageJsonContent = await fs.read('package.json')
|
|
let packageJson = JSON.parse(packageJsonContent)
|
|
expect(packageJson.dependencies).toMatchObject({
|
|
'@tailwindcss/postcss': expect.stringContaining('4.0.0'),
|
|
})
|
|
},
|
|
)
|
|
|
|
test(
|
|
'`@tailwindcss/postcss` should be installed in devDependencies when `tailwindcss` exists in dev dependencies',
|
|
{
|
|
fs: {
|
|
'package.json': json`
|
|
{
|
|
"devDependencies": {
|
|
"postcss": "^8",
|
|
"tailwindcss": "^3",
|
|
"@tailwindcss/upgrade": "workspace:^"
|
|
}
|
|
}
|
|
`,
|
|
'tailwind.config.js': js`
|
|
/** @type {import('tailwindcss').Config} */
|
|
module.exports = {
|
|
content: ['./src/**/*.{html,js}'],
|
|
}
|
|
`,
|
|
'postcss.config.js': js`
|
|
module.exports = {
|
|
plugins: {
|
|
tailwindcss: {},
|
|
},
|
|
}
|
|
`,
|
|
'src/index.html': html`
|
|
<div class="bg-[--my-red]"></div>
|
|
`,
|
|
'src/index.css': css`
|
|
@tailwind base;
|
|
@tailwind components;
|
|
@tailwind utilities;
|
|
`,
|
|
},
|
|
},
|
|
async ({ fs, exec }) => {
|
|
await exec('npx @tailwindcss/upgrade')
|
|
|
|
let packageJsonContent = await fs.read('package.json')
|
|
let packageJson = JSON.parse(packageJsonContent)
|
|
expect(packageJson.devDependencies).toMatchObject({
|
|
'@tailwindcss/postcss': expect.stringContaining('4.0.0'),
|
|
})
|
|
},
|
|
)
|
|
|
|
test(
|
|
'migrates a postcss setup using package.json config',
|
|
{
|
|
fs: {
|
|
'package.json': json`
|
|
{
|
|
"dependencies": {
|
|
"postcss": "^8",
|
|
"postcss-cli": "^10",
|
|
"postcss-import": "^16",
|
|
"autoprefixer": "^10",
|
|
"tailwindcss": "^3",
|
|
"@tailwindcss/upgrade": "workspace:^"
|
|
},
|
|
"postcss": {
|
|
"plugins": {
|
|
"postcss-import": {},
|
|
"tailwindcss/nesting": "postcss-nesting",
|
|
"tailwindcss": {},
|
|
"autoprefixer": {}
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
'tailwind.config.js': js`
|
|
/** @type {import('tailwindcss').Config} */
|
|
module.exports = {
|
|
content: ['./src/**/*.{html,js}'],
|
|
}
|
|
`,
|
|
'src/index.html': html`
|
|
<div class="bg-[--my-red]"></div>
|
|
`,
|
|
'src/index.css': css`
|
|
@tailwind base;
|
|
@tailwind components;
|
|
@tailwind utilities;
|
|
`,
|
|
},
|
|
},
|
|
async ({ fs, exec }) => {
|
|
await exec('npx @tailwindcss/upgrade')
|
|
|
|
await fs.expectFileToContain('src/index.css', css`@import 'tailwindcss';`)
|
|
await fs.expectFileToContain(
|
|
'src/index.html',
|
|
// prettier-ignore
|
|
js`
|
|
<div class="bg-[var(--my-red)]"></div>
|
|
`,
|
|
)
|
|
|
|
let packageJsonContent = await fs.read('package.json')
|
|
let packageJson = JSON.parse(packageJsonContent)
|
|
expect(packageJson.postcss).toMatchInlineSnapshot(`
|
|
{
|
|
"plugins": {
|
|
"@tailwindcss/postcss": {},
|
|
},
|
|
}
|
|
`)
|
|
|
|
expect(packageJson.dependencies).toMatchObject({
|
|
tailwindcss: expect.stringContaining('4.0.0'),
|
|
})
|
|
expect(packageJson.dependencies).not.toHaveProperty('autoprefixer')
|
|
expect(packageJson.dependencies).not.toHaveProperty('postcss-import')
|
|
expect(packageJson.dependencies).toMatchObject({
|
|
'@tailwindcss/postcss': expect.stringContaining('4.0.0'),
|
|
})
|
|
},
|
|
)
|
|
|
|
test(
|
|
'migrates a postcss setup using a json based config file',
|
|
{
|
|
fs: {
|
|
'package.json': json`
|
|
{
|
|
"dependencies": {
|
|
"postcss": "^8",
|
|
"postcss-cli": "^10",
|
|
"postcss-import": "^16",
|
|
"autoprefixer": "^10",
|
|
"tailwindcss": "^3",
|
|
"@tailwindcss/upgrade": "workspace:^"
|
|
}
|
|
}
|
|
`,
|
|
'.postcssrc.json': json`
|
|
{
|
|
"plugins": {
|
|
"postcss-import": {},
|
|
"tailwindcss/nesting": "postcss-nesting",
|
|
"tailwindcss": {},
|
|
"autoprefixer": {}
|
|
}
|
|
}
|
|
`,
|
|
'tailwind.config.js': js`
|
|
/** @type {import('tailwindcss').Config} */
|
|
module.exports = {
|
|
content: ['./src/**/*.{html,js}'],
|
|
}
|
|
`,
|
|
'src/index.html': html`
|
|
<div class="bg-[--my-red]"></div>
|
|
`,
|
|
'src/index.css': css`
|
|
@tailwind base;
|
|
@tailwind components;
|
|
@tailwind utilities;
|
|
`,
|
|
},
|
|
},
|
|
async ({ fs, exec }) => {
|
|
await exec('npx @tailwindcss/upgrade')
|
|
|
|
await fs.expectFileToContain('src/index.css', css`@import 'tailwindcss';`)
|
|
await fs.expectFileToContain(
|
|
'src/index.html',
|
|
// prettier-ignore
|
|
js`
|
|
<div class="bg-[var(--my-red)]"></div>
|
|
`,
|
|
)
|
|
|
|
let jsonConfigContent = await fs.read('.postcssrc.json')
|
|
let jsonConfig = JSON.parse(jsonConfigContent)
|
|
expect(jsonConfig).toMatchInlineSnapshot(`
|
|
{
|
|
"plugins": {
|
|
"@tailwindcss/postcss": {},
|
|
},
|
|
}
|
|
`)
|
|
|
|
let packageJsonContent = await fs.read('package.json')
|
|
let packageJson = JSON.parse(packageJsonContent)
|
|
expect(packageJson.dependencies).toMatchObject({
|
|
tailwindcss: expect.stringContaining('4.0.0'),
|
|
})
|
|
expect(packageJson.dependencies).not.toHaveProperty('autoprefixer')
|
|
expect(packageJson.dependencies).not.toHaveProperty('postcss-import')
|
|
expect(packageJson.dependencies).toMatchObject({
|
|
'@tailwindcss/postcss': expect.stringContaining('4.0.0'),
|
|
})
|
|
},
|
|
)
|
|
|
|
test(
|
|
`migrates prefixes even if other files have unprefixed versions of the candidate`,
|
|
{
|
|
fs: {
|
|
'package.json': json`
|
|
{
|
|
"dependencies": {
|
|
"@tailwindcss/upgrade": "workspace:^"
|
|
}
|
|
}
|
|
`,
|
|
'tailwind.config.js': js`
|
|
/** @type {import('tailwindcss').Config} */
|
|
module.exports = {
|
|
content: ['./src/**/*.{html,js}'],
|
|
prefix: 'tw__',
|
|
}
|
|
`,
|
|
'src/index.html': html`
|
|
<div class="flex"></div>
|
|
`,
|
|
'src/other.html': html`
|
|
<div class="tw__flex"></div>
|
|
`,
|
|
'src/input.css': css`
|
|
@tailwind base;
|
|
@tailwind components;
|
|
@tailwind utilities;
|
|
`,
|
|
},
|
|
},
|
|
async ({ exec, fs }) => {
|
|
await exec('npx @tailwindcss/upgrade')
|
|
|
|
expect(await fs.dumpFiles('./src/**/*.html')).toMatchInlineSnapshot(`
|
|
"
|
|
--- ./src/index.html ---
|
|
<div class="flex"></div>
|
|
|
|
--- ./src/other.html ---
|
|
<div class="tw:flex"></div>
|
|
"
|
|
`)
|
|
},
|
|
)
|
|
|
|
test(
|
|
`prefixed variants do not cause their unprefixed counterparts to be valid`,
|
|
{
|
|
fs: {
|
|
'package.json': json`
|
|
{
|
|
"dependencies": {
|
|
"@tailwindcss/upgrade": "workspace:^"
|
|
}
|
|
}
|
|
`,
|
|
'tailwind.config.js': js`
|
|
/** @type {import('tailwindcss').Config} */
|
|
module.exports = {
|
|
content: ['./src/**/*.{html,js}'],
|
|
prefix: 'tw__',
|
|
}
|
|
`,
|
|
'src/index.css': css`
|
|
@tailwind base;
|
|
@tailwind components;
|
|
@tailwind utilities;
|
|
`,
|
|
'src/index.html': html`
|
|
<div class="tw__bg-gradient-to-t"></div>
|
|
`,
|
|
'src/other.html': html`
|
|
<div class="bg-gradient-to-t"></div>
|
|
`,
|
|
},
|
|
},
|
|
async ({ exec, fs }) => {
|
|
await exec('npx @tailwindcss/upgrade')
|
|
|
|
expect(await fs.dumpFiles('./src/**/*.html')).toMatchInlineSnapshot(`
|
|
"
|
|
--- ./src/index.html ---
|
|
<div class="tw:bg-linear-to-t"></div>
|
|
|
|
--- ./src/other.html ---
|
|
<div class="bg-gradient-to-t"></div>
|
|
"
|
|
`)
|
|
},
|
|
)
|
|
|
|
test(
|
|
'migrate utilities in an imported file',
|
|
{
|
|
fs: {
|
|
'package.json': json`
|
|
{
|
|
"dependencies": {
|
|
"tailwindcss": "workspace:^",
|
|
"@tailwindcss/upgrade": "workspace:^"
|
|
}
|
|
}
|
|
`,
|
|
'tailwind.config.js': js`module.exports = {}`,
|
|
'src/index.css': css`
|
|
@import 'tailwindcss';
|
|
@import './utilities.css' layer(utilities);
|
|
`,
|
|
'src/utilities.css': css`
|
|
.no-scrollbar::-webkit-scrollbar {
|
|
display: none;
|
|
}
|
|
|
|
.no-scrollbar {
|
|
-ms-overflow-style: none;
|
|
scrollbar-width: none;
|
|
}
|
|
`,
|
|
},
|
|
},
|
|
async ({ fs, exec }) => {
|
|
await exec('npx @tailwindcss/upgrade --force')
|
|
|
|
expect(await fs.dumpFiles('./src/**/*.css')).toMatchInlineSnapshot(`
|
|
"
|
|
--- ./src/index.css ---
|
|
@import 'tailwindcss';
|
|
@import './utilities.css';
|
|
|
|
/*
|
|
The default border color has changed to \`currentColor\` in Tailwind CSS v4,
|
|
so we've added these compatibility styles to make sure everything still
|
|
looks the same as it did with Tailwind CSS v3.
|
|
|
|
If we ever want to remove these styles, we need to add an explicit border
|
|
color utility to any element that depends on these defaults.
|
|
*/
|
|
@layer base {
|
|
*,
|
|
::after,
|
|
::before,
|
|
::backdrop,
|
|
::file-selector-button {
|
|
border-color: var(--color-gray-200, currentColor);
|
|
}
|
|
}
|
|
/*
|
|
Form elements have a 1px border by default in Tailwind CSS v4, so we've
|
|
added these compatibility styles to make sure everything still looks the
|
|
same as it did with Tailwind CSS v3.
|
|
|
|
If we ever want to remove these styles, we need to add \`border-0\` to
|
|
any form elements that shouldn't have a border.
|
|
*/
|
|
@layer base {
|
|
input:where(:not([type='button'], [type='reset'], [type='submit'])),
|
|
select,
|
|
textarea {
|
|
border-width: 0;
|
|
}
|
|
}
|
|
|
|
--- ./src/utilities.css ---
|
|
@utility no-scrollbar {
|
|
&::-webkit-scrollbar {
|
|
display: none;
|
|
}
|
|
-ms-overflow-style: none;
|
|
scrollbar-width: none;
|
|
}
|
|
"
|
|
`)
|
|
},
|
|
)
|
|
|
|
test(
|
|
'migrate utilities in an imported file and keep @utility top-level',
|
|
{
|
|
fs: {
|
|
'package.json': json`
|
|
{
|
|
"dependencies": {
|
|
"tailwindcss": "workspace:^",
|
|
"@tailwindcss/upgrade": "workspace:^"
|
|
}
|
|
}
|
|
`,
|
|
'tailwind.config.js': js`module.exports = {}`,
|
|
'src/index.css': css`
|
|
@import 'tailwindcss/utilities';
|
|
@import './utilities.css';
|
|
@import 'tailwindcss/components';
|
|
`,
|
|
'src/utilities.css': css`
|
|
@layer utilities {
|
|
.no-scrollbar::-webkit-scrollbar {
|
|
display: none;
|
|
}
|
|
|
|
.no-scrollbar {
|
|
-ms-overflow-style: none;
|
|
scrollbar-width: none;
|
|
}
|
|
}
|
|
`,
|
|
},
|
|
},
|
|
async ({ fs, exec }) => {
|
|
await exec('npx @tailwindcss/upgrade --force')
|
|
|
|
expect(await fs.dumpFiles('./src/**/*.css')).toMatchInlineSnapshot(`
|
|
"
|
|
--- ./src/index.css ---
|
|
@import 'tailwindcss/utilities' layer(utilities);
|
|
@import './utilities.css';
|
|
|
|
--- ./src/utilities.css ---
|
|
@utility no-scrollbar {
|
|
&::-webkit-scrollbar {
|
|
display: none;
|
|
}
|
|
-ms-overflow-style: none;
|
|
scrollbar-width: none;
|
|
}
|
|
"
|
|
`)
|
|
},
|
|
)
|
|
|
|
test(
|
|
'migrate utilities in deep import trees',
|
|
{
|
|
fs: {
|
|
'package.json': json`
|
|
{
|
|
"dependencies": {
|
|
"tailwindcss": "workspace:^",
|
|
"@tailwindcss/cli": "workspace:^",
|
|
"@tailwindcss/upgrade": "workspace:^"
|
|
}
|
|
}
|
|
`,
|
|
'tailwind.config.js': js`module.exports = {}`,
|
|
'src/index.html': html`
|
|
<div class="hover:thing"></div>
|
|
`,
|
|
'src/index.css': css`
|
|
@import 'tailwindcss/utilities';
|
|
@import './a.1.css' layer(utilities);
|
|
@import './b.1.css' layer(components);
|
|
@import './c.1.css';
|
|
@import './d.1.css';
|
|
`,
|
|
'src/a.1.css': css`
|
|
@import './a.1.utilities.css';
|
|
|
|
.foo-from-a {
|
|
color: red;
|
|
}
|
|
`,
|
|
'src/a.1.utilities.css': css`
|
|
#foo {
|
|
--keep: me;
|
|
}
|
|
|
|
.foo-from-import {
|
|
color: blue;
|
|
}
|
|
`,
|
|
'src/b.1.css': css`
|
|
@import './b.1.components.css';
|
|
|
|
.bar-from-b {
|
|
color: red;
|
|
}
|
|
`,
|
|
'src/b.1.components.css': css`
|
|
.bar-from-import {
|
|
color: blue;
|
|
}
|
|
`,
|
|
'src/c.1.css': css`
|
|
@import './c.2.css' layer(utilities);
|
|
.baz-from-c {
|
|
color: green;
|
|
}
|
|
`,
|
|
'src/c.2.css': css`
|
|
@import './c.3.css';
|
|
#baz {
|
|
--keep: me;
|
|
}
|
|
.baz-from-import {
|
|
color: yellow;
|
|
}
|
|
`,
|
|
'src/c.3.css': css`
|
|
#baz {
|
|
--keep: me;
|
|
}
|
|
.baz-from-import {
|
|
color: yellow;
|
|
}
|
|
`,
|
|
|
|
// This is a super deep import chain
|
|
// And no `*.utilities.css` files should be created for these
|
|
// because there are no rules that need to be separated
|
|
'src/d.1.css': css`@import './d.2.css' layer(utilities);`,
|
|
'src/d.2.css': css`@import './d.3.css';`,
|
|
'src/d.3.css': css`@import './d.4.css';`,
|
|
'src/d.4.css': css`
|
|
.from-a-4 {
|
|
color: blue;
|
|
}
|
|
`,
|
|
},
|
|
},
|
|
async ({ fs, exec }) => {
|
|
await exec('npx @tailwindcss/upgrade --force')
|
|
|
|
expect(await fs.dumpFiles('./src/**/*.css')).toMatchInlineSnapshot(`
|
|
"
|
|
--- ./src/index.css ---
|
|
@import 'tailwindcss/utilities' layer(utilities);
|
|
@import './a.1.css' layer(utilities);
|
|
@import './a.1.utilities.1.css';
|
|
@import './b.1.css';
|
|
@import './c.1.css' layer(utilities);
|
|
@import './c.1.utilities.css';
|
|
@import './d.1.css';
|
|
|
|
--- ./src/a.1.css ---
|
|
@import './a.1.utilities.css'
|
|
|
|
--- ./src/a.1.utilities.1.css ---
|
|
@import './a.1.utilities.utilities.css';
|
|
@utility foo-from-a {
|
|
color: red;
|
|
}
|
|
|
|
--- ./src/a.1.utilities.css ---
|
|
#foo {
|
|
--keep: me;
|
|
}
|
|
|
|
--- ./src/a.1.utilities.utilities.css ---
|
|
@utility foo-from-import {
|
|
color: blue;
|
|
}
|
|
|
|
--- ./src/b.1.components.css ---
|
|
@utility bar-from-import {
|
|
color: blue;
|
|
}
|
|
|
|
--- ./src/b.1.css ---
|
|
@import './b.1.components.css';
|
|
@utility bar-from-b {
|
|
color: red;
|
|
}
|
|
|
|
--- ./src/c.1.css ---
|
|
@import './c.2.css' layer(utilities);
|
|
.baz-from-c {
|
|
color: green;
|
|
}
|
|
|
|
--- ./src/c.1.utilities.css ---
|
|
@import './c.2.utilities.css'
|
|
|
|
--- ./src/c.2.css ---
|
|
@import './c.3.css';
|
|
#baz {
|
|
--keep: me;
|
|
}
|
|
|
|
--- ./src/c.2.utilities.css ---
|
|
@import './c.3.utilities.css';
|
|
@utility baz-from-import {
|
|
color: yellow;
|
|
}
|
|
|
|
--- ./src/c.3.css ---
|
|
#baz {
|
|
--keep: me;
|
|
}
|
|
|
|
--- ./src/c.3.utilities.css ---
|
|
@utility baz-from-import {
|
|
color: yellow;
|
|
}
|
|
|
|
--- ./src/d.1.css ---
|
|
@import './d.2.css'
|
|
|
|
--- ./src/d.2.css ---
|
|
@import './d.3.css'
|
|
|
|
--- ./src/d.3.css ---
|
|
@import './d.4.css'
|
|
|
|
--- ./src/d.4.css ---
|
|
@utility from-a-4 {
|
|
color: blue;
|
|
}
|
|
"
|
|
`)
|
|
},
|
|
)
|
|
|
|
test(
|
|
'migrate utility files imported by multiple roots',
|
|
{
|
|
fs: {
|
|
'package.json': json`
|
|
{
|
|
"dependencies": {
|
|
"tailwindcss": "workspace:^",
|
|
"@tailwindcss/cli": "workspace:^",
|
|
"@tailwindcss/upgrade": "workspace:^"
|
|
}
|
|
}
|
|
`,
|
|
'tailwind.config.js': js`module.exports = {}`,
|
|
'src/index.html': html`
|
|
<div class="hover:thing"></div>
|
|
`,
|
|
'src/root.1.css': css`
|
|
@import 'tailwindcss/utilities';
|
|
@import './a.1.css' layer(utilities);
|
|
`,
|
|
'src/root.2.css': css`
|
|
@import 'tailwindcss/utilities';
|
|
@import './a.1.css' layer(components);
|
|
`,
|
|
'src/root.3.css': css`
|
|
@import 'tailwindcss/utilities';
|
|
@import './a.1.css';
|
|
`,
|
|
'src/a.1.css': css`
|
|
.foo-from-a {
|
|
color: red;
|
|
}
|
|
`,
|
|
},
|
|
},
|
|
async ({ fs, exec }) => {
|
|
let output = await exec('npx @tailwindcss/upgrade --force')
|
|
|
|
expect(output).toMatch(
|
|
/You have one or more stylesheets that are imported into a utility layer and non-utility layer./,
|
|
)
|
|
|
|
expect(await fs.dumpFiles('./src/**/*.css')).toMatchInlineSnapshot(`
|
|
"
|
|
--- ./src/a.1.css ---
|
|
.foo-from-a {
|
|
color: red;
|
|
}
|
|
|
|
--- ./src/root.1.css ---
|
|
@import 'tailwindcss/utilities' layer(utilities);
|
|
@import './a.1.css' layer(utilities);
|
|
|
|
--- ./src/root.2.css ---
|
|
@import 'tailwindcss/utilities' layer(utilities);
|
|
@import './a.1.css' layer(components);
|
|
|
|
--- ./src/root.3.css ---
|
|
@import 'tailwindcss/utilities' layer(utilities);
|
|
@import './a.1.css' layer(utilities);
|
|
"
|
|
`)
|
|
},
|
|
)
|
|
|
|
test(
|
|
'injecting `@config` when a tailwind.config.{js,ts,…} is detected',
|
|
{
|
|
fs: {
|
|
'package.json': json`
|
|
{
|
|
"dependencies": {
|
|
"@tailwindcss/upgrade": "workspace:^"
|
|
}
|
|
}
|
|
`,
|
|
'tailwind.config.ts': js`
|
|
export default {
|
|
content: ['./src/**/*.{html,js}'],
|
|
plugins: [
|
|
() => {
|
|
// custom stuff which is too complicated to migrate to CSS
|
|
},
|
|
],
|
|
}
|
|
`,
|
|
'src/index.html': html`
|
|
<div
|
|
class="!flex sm:!block bg-gradient-to-t bg-[--my-red]"
|
|
></div>
|
|
`,
|
|
'src/root.1.css': css`
|
|
/* Inject missing @config */
|
|
@tailwind base;
|
|
@tailwind components;
|
|
@tailwind utilities;
|
|
`,
|
|
'src/root.2.css': css`
|
|
/* Already contains @config */
|
|
@tailwind base;
|
|
@tailwind components;
|
|
@tailwind utilities;
|
|
@config "../tailwind.config.ts";
|
|
`,
|
|
'src/root.3.css': css`
|
|
/* Inject missing @config above first @theme */
|
|
@tailwind base;
|
|
@tailwind components;
|
|
@tailwind utilities;
|
|
|
|
@variant hocus (&:hover, &:focus);
|
|
|
|
@theme {
|
|
--color-red-500: #f00;
|
|
}
|
|
|
|
@theme {
|
|
--color-blue-500: #00f;
|
|
}
|
|
`,
|
|
'src/root.4.css': css`
|
|
/* Inject missing @config due to nested imports with tailwind imports */
|
|
@import './root.4/base.css';
|
|
@import './root.4/utilities.css';
|
|
`,
|
|
'src/root.4/base.css': css`@import 'tailwindcss/base';`,
|
|
'src/root.4/utilities.css': css`@import 'tailwindcss/utilities';`,
|
|
|
|
'src/root.5.css': css`@import './root.5/tailwind.css';`,
|
|
'src/root.5/tailwind.css': css`
|
|
/* Inject missing @config in this file, due to full import */
|
|
@import 'tailwindcss';
|
|
`,
|
|
},
|
|
},
|
|
async ({ exec, fs }) => {
|
|
await exec('npx @tailwindcss/upgrade --force')
|
|
|
|
expect(await fs.dumpFiles('./src/**/*.{html,css}')).toMatchInlineSnapshot(`
|
|
"
|
|
--- ./src/index.html ---
|
|
<div
|
|
class="flex! sm:block! bg-linear-to-t bg-[var(--my-red)]"
|
|
></div>
|
|
|
|
--- ./src/root.1.css ---
|
|
/* Inject missing @config */
|
|
@import 'tailwindcss';
|
|
|
|
/*
|
|
The default border color has changed to \`currentColor\` in Tailwind CSS v4,
|
|
so we've added these compatibility styles to make sure everything still
|
|
looks the same as it did with Tailwind CSS v3.
|
|
|
|
If we ever want to remove these styles, we need to add an explicit border
|
|
color utility to any element that depends on these defaults.
|
|
*/
|
|
@layer base {
|
|
*,
|
|
::after,
|
|
::before,
|
|
::backdrop,
|
|
::file-selector-button {
|
|
border-color: var(--color-gray-200, currentColor);
|
|
}
|
|
}
|
|
/*
|
|
Form elements have a 1px border by default in Tailwind CSS v4, so we've
|
|
added these compatibility styles to make sure everything still looks the
|
|
same as it did with Tailwind CSS v3.
|
|
|
|
If we ever want to remove these styles, we need to add \`border-0\` to
|
|
any form elements that shouldn't have a border.
|
|
*/
|
|
@layer base {
|
|
input:where(:not([type='button'], [type='reset'], [type='submit'])),
|
|
select,
|
|
textarea {
|
|
border-width: 0;
|
|
}
|
|
}
|
|
@config '../tailwind.config.ts';
|
|
|
|
--- ./src/root.2.css ---
|
|
/* Already contains @config */
|
|
@import 'tailwindcss';
|
|
|
|
/*
|
|
The default border color has changed to \`currentColor\` in Tailwind CSS v4,
|
|
so we've added these compatibility styles to make sure everything still
|
|
looks the same as it did with Tailwind CSS v3.
|
|
|
|
If we ever want to remove these styles, we need to add an explicit border
|
|
color utility to any element that depends on these defaults.
|
|
*/
|
|
@layer base {
|
|
*,
|
|
::after,
|
|
::before,
|
|
::backdrop,
|
|
::file-selector-button {
|
|
border-color: var(--color-gray-200, currentColor);
|
|
}
|
|
}
|
|
/*
|
|
Form elements have a 1px border by default in Tailwind CSS v4, so we've
|
|
added these compatibility styles to make sure everything still looks the
|
|
same as it did with Tailwind CSS v3.
|
|
|
|
If we ever want to remove these styles, we need to add \`border-0\` to
|
|
any form elements that shouldn't have a border.
|
|
*/
|
|
@layer base {
|
|
input:where(:not([type='button'], [type='reset'], [type='submit'])),
|
|
select,
|
|
textarea {
|
|
border-width: 0;
|
|
}
|
|
}
|
|
@config "../tailwind.config.ts";
|
|
|
|
--- ./src/root.3.css ---
|
|
/* Inject missing @config above first @theme */
|
|
@import 'tailwindcss';
|
|
|
|
/*
|
|
The default border color has changed to \`currentColor\` in Tailwind CSS v4,
|
|
so we've added these compatibility styles to make sure everything still
|
|
looks the same as it did with Tailwind CSS v3.
|
|
|
|
If we ever want to remove these styles, we need to add an explicit border
|
|
color utility to any element that depends on these defaults.
|
|
*/
|
|
@layer base {
|
|
*,
|
|
::after,
|
|
::before,
|
|
::backdrop,
|
|
::file-selector-button {
|
|
border-color: var(--color-gray-200, currentColor);
|
|
}
|
|
}
|
|
/*
|
|
Form elements have a 1px border by default in Tailwind CSS v4, so we've
|
|
added these compatibility styles to make sure everything still looks the
|
|
same as it did with Tailwind CSS v3.
|
|
|
|
If we ever want to remove these styles, we need to add \`border-0\` to
|
|
any form elements that shouldn't have a border.
|
|
*/
|
|
@layer base {
|
|
input:where(:not([type='button'], [type='reset'], [type='submit'])),
|
|
select,
|
|
textarea {
|
|
border-width: 0;
|
|
}
|
|
}
|
|
@config '../tailwind.config.ts';
|
|
|
|
@variant hocus (&:hover, &:focus);
|
|
|
|
@theme {
|
|
--color-red-500: #f00;
|
|
}
|
|
|
|
@theme {
|
|
--color-blue-500: #00f;
|
|
}
|
|
|
|
--- ./src/root.4.css ---
|
|
/* Inject missing @config due to nested imports with tailwind imports */
|
|
@import './root.4/base.css';
|
|
@import './root.4/utilities.css';
|
|
@config '../tailwind.config.ts';
|
|
|
|
--- ./src/root.5.css ---
|
|
@import './root.5/tailwind.css';
|
|
|
|
--- ./src/root.4/base.css ---
|
|
@import 'tailwindcss/theme' layer(theme);
|
|
@import 'tailwindcss/preflight' layer(base);
|
|
|
|
/*
|
|
The default border color has changed to \`currentColor\` in Tailwind CSS v4,
|
|
so we've added these compatibility styles to make sure everything still
|
|
looks the same as it did with Tailwind CSS v3.
|
|
|
|
If we ever want to remove these styles, we need to add an explicit border
|
|
color utility to any element that depends on these defaults.
|
|
*/
|
|
@layer base {
|
|
*,
|
|
::after,
|
|
::before,
|
|
::backdrop,
|
|
::file-selector-button {
|
|
border-color: var(--color-gray-200, currentColor);
|
|
}
|
|
}
|
|
|
|
/*
|
|
Form elements have a 1px border by default in Tailwind CSS v4, so we've
|
|
added these compatibility styles to make sure everything still looks the
|
|
same as it did with Tailwind CSS v3.
|
|
|
|
If we ever want to remove these styles, we need to add \`border-0\` to
|
|
any form elements that shouldn't have a border.
|
|
*/
|
|
@layer base {
|
|
input:where(:not([type='button'], [type='reset'], [type='submit'])),
|
|
select,
|
|
textarea {
|
|
border-width: 0;
|
|
}
|
|
}
|
|
|
|
--- ./src/root.4/utilities.css ---
|
|
@import 'tailwindcss/utilities' layer(utilities);
|
|
|
|
--- ./src/root.5/tailwind.css ---
|
|
/* Inject missing @config in this file, due to full import */
|
|
@import 'tailwindcss';
|
|
|
|
/*
|
|
The default border color has changed to \`currentColor\` in Tailwind CSS v4,
|
|
so we've added these compatibility styles to make sure everything still
|
|
looks the same as it did with Tailwind CSS v3.
|
|
|
|
If we ever want to remove these styles, we need to add an explicit border
|
|
color utility to any element that depends on these defaults.
|
|
*/
|
|
@layer base {
|
|
*,
|
|
::after,
|
|
::before,
|
|
::backdrop,
|
|
::file-selector-button {
|
|
border-color: var(--color-gray-200, currentColor);
|
|
}
|
|
}
|
|
/*
|
|
Form elements have a 1px border by default in Tailwind CSS v4, so we've
|
|
added these compatibility styles to make sure everything still looks the
|
|
same as it did with Tailwind CSS v3.
|
|
|
|
If we ever want to remove these styles, we need to add \`border-0\` to
|
|
any form elements that shouldn't have a border.
|
|
*/
|
|
@layer base {
|
|
input:where(:not([type='button'], [type='reset'], [type='submit'])),
|
|
select,
|
|
textarea {
|
|
border-width: 0;
|
|
}
|
|
}
|
|
@config '../../tailwind.config.ts';
|
|
"
|
|
`)
|
|
},
|
|
)
|
|
|
|
test(
|
|
'relative imports without a relative path prefix are migrated to include a relative path prefix',
|
|
{
|
|
fs: {
|
|
'package.json': json`
|
|
{
|
|
"dependencies": {
|
|
"tailwindcss": "workspace:^",
|
|
"@tailwindcss/upgrade": "workspace:^"
|
|
}
|
|
}
|
|
`,
|
|
'tailwind.config.js': js`module.exports = {}`,
|
|
'src/index.css': css`
|
|
@import 'tailwindcss/base';
|
|
@import 'tailwindcss/components';
|
|
@import 'styles/components';
|
|
@import 'tailwindcss/utilities';
|
|
`,
|
|
'src/styles/components.css': css`
|
|
.btn {
|
|
@apply bg-black px-4 py-2 rounded-md text-white font-medium hover:bg-zinc-800;
|
|
}
|
|
`,
|
|
},
|
|
},
|
|
async ({ fs, exec }) => {
|
|
await exec('npx @tailwindcss/upgrade --force')
|
|
|
|
expect(await fs.dumpFiles('./src/**/*.css')).toMatchInlineSnapshot(`
|
|
"
|
|
--- ./src/index.css ---
|
|
@import 'tailwindcss';
|
|
@import './styles/components.css' layer(components);
|
|
|
|
/*
|
|
The default border color has changed to \`currentColor\` in Tailwind CSS v4,
|
|
so we've added these compatibility styles to make sure everything still
|
|
looks the same as it did with Tailwind CSS v3.
|
|
|
|
If we ever want to remove these styles, we need to add an explicit border
|
|
color utility to any element that depends on these defaults.
|
|
*/
|
|
@layer base {
|
|
*,
|
|
::after,
|
|
::before,
|
|
::backdrop,
|
|
::file-selector-button {
|
|
border-color: var(--color-gray-200, currentColor);
|
|
}
|
|
}
|
|
/*
|
|
Form elements have a 1px border by default in Tailwind CSS v4, so we've
|
|
added these compatibility styles to make sure everything still looks the
|
|
same as it did with Tailwind CSS v3.
|
|
|
|
If we ever want to remove these styles, we need to add \`border-0\` to
|
|
any form elements that shouldn't have a border.
|
|
*/
|
|
@layer base {
|
|
input:where(:not([type='button'], [type='reset'], [type='submit'])),
|
|
select,
|
|
textarea {
|
|
border-width: 0;
|
|
}
|
|
}
|
|
|
|
--- ./src/styles/components.css ---
|
|
.btn {
|
|
@apply bg-black px-4 py-2 rounded-md text-white font-medium hover:bg-zinc-800;
|
|
}
|
|
"
|
|
`)
|
|
},
|
|
)
|
|
|
|
test(
|
|
'that it attaches the correct layers to the imported files',
|
|
{
|
|
fs: {
|
|
'package.json': json`
|
|
{
|
|
"dependencies": {
|
|
"tailwindcss": "workspace:^",
|
|
"@tailwindcss/upgrade": "workspace:^"
|
|
}
|
|
}
|
|
`,
|
|
'tailwind.config.js': js`module.exports = {}`,
|
|
'src/index.css': css`
|
|
@import 'tailwindcss/components';
|
|
|
|
/* No layer expected */
|
|
@import './my-components.css';
|
|
|
|
/* No layer expected */
|
|
@import './my-utilities.css';
|
|
|
|
/* Expecting a layer */
|
|
@import './my-other.css';
|
|
|
|
@import 'tailwindcss/utilities';
|
|
`,
|
|
'src/my-components.css': css`
|
|
@layer components {
|
|
.foo {
|
|
color: red;
|
|
}
|
|
}
|
|
`,
|
|
'src/my-utilities.css': css`
|
|
@layer utilities {
|
|
.css {
|
|
color: red;
|
|
}
|
|
}
|
|
`,
|
|
'src/my-other.css': css`
|
|
/* All my fonts! */
|
|
@font-face {
|
|
}
|
|
`,
|
|
},
|
|
},
|
|
async ({ fs, exec }) => {
|
|
await exec('npx @tailwindcss/upgrade --force')
|
|
|
|
expect(await fs.dumpFiles('./src/**/*.css')).toMatchInlineSnapshot(`
|
|
"
|
|
--- ./src/index.css ---
|
|
/* No layer expected */
|
|
@import './my-components.css';
|
|
|
|
/* No layer expected */
|
|
@import './my-utilities.css';
|
|
|
|
/* Expecting a layer */
|
|
@import './my-other.css' layer(components);
|
|
|
|
@import 'tailwindcss/utilities' layer(utilities);
|
|
|
|
--- ./src/my-components.css ---
|
|
@utility foo {
|
|
color: red;
|
|
}
|
|
|
|
--- ./src/my-other.css ---
|
|
/* All my fonts! */
|
|
@font-face {
|
|
}
|
|
|
|
--- ./src/my-utilities.css ---
|
|
@utility css {
|
|
color: red;
|
|
}
|
|
"
|
|
`)
|
|
},
|
|
)
|
|
|
|
test(
|
|
'migrating the prettier-plugin-tailwindcss version',
|
|
{
|
|
fs: {
|
|
'package.json': json`
|
|
{
|
|
"dependencies": {
|
|
"tailwindcss": "workspace:^",
|
|
"@tailwindcss/upgrade": "workspace:^"
|
|
},
|
|
"devDependencies": {
|
|
"prettier-plugin-tailwindcss": "0.5.0"
|
|
}
|
|
}
|
|
`,
|
|
'tailwind.config.js': js`module.exports = {}`,
|
|
},
|
|
},
|
|
async ({ fs, exec }) => {
|
|
await exec('npx @tailwindcss/upgrade --force')
|
|
|
|
let pkg = JSON.parse(await fs.read('package.json'))
|
|
|
|
expect(pkg.devDependencies).toMatchObject({
|
|
'prettier-plugin-tailwindcss': expect.any(String),
|
|
})
|
|
expect(pkg.devDependencies['prettier-plugin-tailwindcss']).not.toEqual('0.5.0')
|
|
},
|
|
)
|